No issues found
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3 * Copyright (C) 2003 Colin Walters <walters@verbum.org>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * The Rhythmbox authors hereby grant permission for non-GPL compatible
11 * GStreamer plugins to be used and distributed together with GStreamer
12 * and Rhythmbox. This permission is above and beyond the permissions granted
13 * by the GPL license by which Rhythmbox is covered. If you modify this code
14 * you may extend this exception to your version of the code, but you are not
15 * obligated to do so. If you do not wish to do so, delete this exception
16 * statement from your version.
17 *
18 * This program is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 * GNU General Public License for more details.
22 *
23 * You should have received a copy of the GNU General Public License
24 * along with this program; if not, write to the Free Software
25 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
26 *
27 */
28
29 /**
30 * SECTION:rb-entry-view
31 * @short_description: a #GtkTreeView for displaying track listings
32 *
33 * This class provides a predefined set of columns for displaying the
34 * common set of #RhythmDBEntry properties, but also allows custom columns
35 * to be appended. The 'playing' column is always created as the first
36 * column in the tree view, displaying a playing or paused image next to
37 * the currently playing entry, and also an error image next to entries for
38 * which a playback error message has been set. Clicking on the error
39 * image opens a dialog displaying the full message.
40 *
41 * All columns added to entry view columns should be expandable, or have a fixed
42 * minimum width set. Otherwise, the tree view must measure the contents of each
43 * row to assign sizes, which is very slow for large track lists. All the predefined
44 * column types handle this correctly.
45 */
46
47 /**
48 * RBEntryViewColumn:
49 * @RB_ENTRY_VIEW_COL_TRACK_NUMBER: the track number column
50 * @RB_ENTRY_VIEW_COL_TITLE: the title column
51 * @RB_ENTRY_VIEW_COL_ARTIST: the artist column
52 * @RB_ENTRY_VIEW_COL_ALBUM: the album column
53 * @RB_ENTRY_VIEW_COL_GENRE: the genre column
54 * @RB_ENTRY_VIEW_COL_DURATION: the duration column
55 * @RB_ENTRY_VIEW_COL_QUALITY: the quality (bitrate) column
56 * @RB_ENTRY_VIEW_COL_RATING: the rating column
57 * @RB_ENTRY_VIEW_COL_PLAY_COUNT: the play count column
58 * @RB_ENTRY_VIEW_COL_YEAR: the year (release date) column
59 * @RB_ENTRY_VIEW_COL_LAST_PLAYED: the last played time column
60 * @RB_ENTRY_VIEW_COL_FIRST_SEEN: the first seen (imported) column
61 * @RB_ENTRY_VIEW_COL_LAST_SEEN: the last seen column
62 * @RB_ENTRY_VIEW_COL_LOCATION: the location column
63 * @RB_ENTRY_VIEW_COL_BPM: the BPM column
64 * @RB_ENTRY_VIEW_COL_COMMENT: the comment column
65 * @RB_ENTRY_VIEW_COL_ERROR: the error column
66 *
67 * Predefined column types to use in #RBEntryView<!-- -->s. Use
68 * #rb_entry_view_append_column to add these to an entry view.
69 * The predefined column names map directly to the #RhythmDBEntry properties
70 * the columns display.
71 */
72
73 #include "config.h"
74
75 #include <string.h>
76 #include <stdlib.h>
77 #include <math.h>
78 #include <time.h>
79
80 #include <glib/gi18n.h>
81 #include <gtk/gtk.h>
82 #include <gio/gio.h>
83 #include <glib.h>
84
85 #include "rb-tree-dnd.h"
86 #include "rb-entry-view.h"
87 #include "rb-dialog.h"
88 #include "rb-debug.h"
89 #include "rb-util.h"
90 #include "rhythmdb.h"
91 #include "rhythmdb-query-model.h"
92 #include "rb-cell-renderer-pixbuf.h"
93 #include "rb-cell-renderer-rating.h"
94 #include "rb-stock-icons.h"
95 #include "rb-shell-player.h"
96 #include "rb-cut-and-paste-code.h"
97
98 static const GtkTargetEntry rb_entry_view_drag_types[] = {
99 { "application/x-rhythmbox-entry", 0, 0 },
100 { "text/uri-list", 0, 1 }
101 };
102
103 struct RBEntryViewColumnSortData
104 {
105 GCompareDataFunc func;
106 gpointer data;
107 GDestroyNotify data_destroy;
108 };
109
110 /* GObject data item used to associate cell renderers with property IDs */
111 #define CELL_PROPID_ITEM "rb-cell-propid"
112
113 static void rb_entry_view_class_init (RBEntryViewClass *klass);
114 static void rb_entry_view_init (RBEntryView *view);
115 static void rb_entry_view_constructed (GObject *object);
116 static void rb_entry_view_dispose (GObject *object);
117 static void rb_entry_view_finalize (GObject *object);
118 static void rb_entry_view_sort_data_finalize (gpointer column,
119 gpointer sort_data,
120 gpointer user_data);
121 static void rb_entry_view_set_property (GObject *object,
122 guint prop_id,
123 const GValue *value,
124 GParamSpec *pspec);
125 static void rb_entry_view_get_property (GObject *object,
126 guint prop_id,
127 GValue *value,
128 GParamSpec *pspec);
129 static void rb_entry_view_selection_changed_cb (GtkTreeSelection *selection,
130 RBEntryView *view);
131 static void rb_entry_view_grab_focus (GtkWidget *widget);
132 static void rb_entry_view_row_activated_cb (GtkTreeView *treeview,
133 GtkTreePath *path,
134 GtkTreeViewColumn *column,
135 RBEntryView *view);
136 static void rb_entry_view_row_inserted_cb (GtkTreeModel *model,
137 GtkTreePath *path,
138 GtkTreeIter *iter,
139 RBEntryView *view);
140 static void rb_entry_view_row_deleted_cb (GtkTreeModel *model,
141 GtkTreePath *path,
142 RBEntryView *view);
143 static void rb_entry_view_rows_reordered_cb (GtkTreeModel *model,
144 GtkTreePath *path,
145 GtkTreeIter *iter,
146 gint *order,
147 RBEntryView *view);
148 static void rb_entry_view_sync_columns_visible (RBEntryView *view);
149 static void rb_entry_view_rated_cb (RBCellRendererRating *cellrating,
150 const char *path,
151 double rating,
152 RBEntryView *view);
153 static void rb_entry_view_pixbuf_clicked_cb (RBEntryView *view,
154 const char *path,
155 RBCellRendererPixbuf *cellpixbuf);
156 static gboolean rb_entry_view_button_press_cb (GtkTreeView *treeview,
157 GdkEventButton *event,
158 RBEntryView *view);
159 static gboolean rb_entry_view_popup_menu_cb (GtkTreeView *treeview,
160 RBEntryView *view);
161 static void rb_entry_view_entry_is_visible (RBEntryView *view, RhythmDBEntry *entry,
162 gboolean *realized, gboolean *visible,
163 GtkTreeIter *iter);
164 static void rb_entry_view_scroll_to_iter (RBEntryView *view,
165 GtkTreeIter *iter);
166 static gboolean rb_entry_view_emit_row_changed (RBEntryView *view,
167 RhythmDBEntry *entry);
168 static void rb_entry_view_playing_song_changed (RBShellPlayer *player,
169 RhythmDBEntry *entry,
170 RBEntryView *view);
171
172 struct RBEntryViewPrivate
173 {
174 RhythmDB *db;
175 RBShellPlayer *shell_player;
176
177 RhythmDBQueryModel *model;
178
179 GtkWidget *treeview;
180 GtkTreeSelection *selection;
181
182 RBEntryViewState playing_state;
183 RhythmDBQueryModel *playing_model;
184 RhythmDBEntry *playing_entry;
185 gboolean playing_entry_in_view;
186 guint selection_changed_id;
187
188 gboolean is_drag_source;
189 gboolean is_drag_dest;
190
191 char *sorting_key;
192 GtkTreeViewColumn *sorting_column;
193 gint sorting_order;
194 char *sorting_column_name;
195 RhythmDBPropType type_ahead_propid;
196 char **visible_columns;
197
198 gboolean have_selection, have_complete_selection;
199
200 GHashTable *column_key_map;
201
202 GHashTable *propid_column_map;
203 GHashTable *column_sort_data_map;
204 };
205
206
207 enum
208 {
209 ENTRY_ADDED,
210 ENTRY_DELETED,
211 ENTRIES_REPLACED,
212 SELECTION_CHANGED,
213 ENTRY_ACTIVATED,
214 SHOW_POPUP,
215 HAVE_SEL_CHANGED,
216 LAST_SIGNAL
217 };
218
219 enum
220 {
221 PROP_0,
222 PROP_DB,
223 PROP_SHELL_PLAYER,
224 PROP_MODEL,
225 PROP_SORT_ORDER,
226 PROP_IS_DRAG_SOURCE,
227 PROP_IS_DRAG_DEST,
228 PROP_PLAYING_STATE,
229 PROP_VISIBLE_COLUMNS
230 };
231
232 G_DEFINE_TYPE (RBEntryView, rb_entry_view, GTK_TYPE_SCROLLED_WINDOW)
233
234 static guint rb_entry_view_signals[LAST_SIGNAL] = { 0 };
235
236 static GQuark rb_entry_view_column_always_visible;
237
238 static gboolean
239 type_ahead_search_func (GtkTreeModel *model,
240 gint column,
241 const gchar *key,
242 GtkTreeIter *iter,
243 gpointer search_data)
244 {
245 RBEntryView *view = RB_ENTRY_VIEW (search_data);
246 RhythmDBEntry *entry;
247 gchar *folded;
248 const gchar *entry_folded;
249 gboolean res;
250
251 gtk_tree_model_get (model, iter, 0, &entry, -1);
252 folded = rb_search_fold (key);
253 entry_folded = rb_refstring_get_folded (rhythmdb_entry_get_refstring (entry, view->priv->type_ahead_propid));
254 rhythmdb_entry_unref (entry);
255
256 if (entry_folded == NULL || folded == NULL)
257 return 1;
258
259 res = (strstr (entry_folded, folded) == NULL);
260 g_free (folded);
261
262 return res;
263 }
264
265 static void
266 rb_entry_view_class_init (RBEntryViewClass *klass)
267 {
268 GObjectClass *object_class = G_OBJECT_CLASS (klass);
269 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
270
271 object_class->dispose = rb_entry_view_dispose;
272 object_class->finalize = rb_entry_view_finalize;
273 object_class->constructed = rb_entry_view_constructed;
274
275 object_class->set_property = rb_entry_view_set_property;
276 object_class->get_property = rb_entry_view_get_property;
277
278 widget_class->grab_focus = rb_entry_view_grab_focus;
279
280 /**
281 * RBEntryView:db:
282 *
283 * #RhythmDB instance
284 */
285 g_object_class_install_property (object_class,
286 PROP_DB,
287 g_param_spec_object ("db",
288 "RhythmDB",
289 "RhythmDB database",
290 RHYTHMDB_TYPE,
291 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
292 /**
293 * RBEntryView:shell-player:
294 *
295 * #RBShellPlayer instance
296 */
297 g_object_class_install_property (object_class,
298 PROP_SHELL_PLAYER,
299 g_param_spec_object ("shell-player",
300 "RBShellPlayer",
301 "RBShellPlayer object",
302 RB_TYPE_SHELL_PLAYER,
303 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
304 /**
305 * RBEntryView:model:
306 *
307 * The #RhythmDBQueryModel backing the view
308 */
309 g_object_class_install_property (object_class,
310 PROP_MODEL,
311 g_param_spec_object ("model",
312 "RhythmDBQueryModel",
313 "RhythmDBQueryModel",
314 RHYTHMDB_TYPE_QUERY_MODEL,
315 G_PARAM_READWRITE));
316 /**
317 * RBEntryView:sort-order:
318 *
319 * The sort order for the track listing.
320 */
321 g_object_class_install_property (object_class,
322 PROP_SORT_ORDER,
323 g_param_spec_string ("sort-order",
324 "sorting order",
325 "sorting order",
326 NULL,
327 G_PARAM_READWRITE));
328 /**
329 * RBEntryView:is-drag-source:
330 *
331 * If TRUE, the view acts as a data source for drag and drop operations.
332 */
333 g_object_class_install_property (object_class,
334 PROP_IS_DRAG_SOURCE,
335 g_param_spec_boolean ("is-drag-source",
336 "is drag source",
337 "whether or not this is a drag source",
338 FALSE,
339 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
340 /**
341 * RBEntryView:is-drag-dest:
342 *
343 * If TRUE, the view acts as a destination for drag and drop operations.
344 */
345 g_object_class_install_property (object_class,
346 PROP_IS_DRAG_DEST,
347 g_param_spec_boolean ("is-drag-dest",
348 "is drag dest",
349 "whether or not this is a drag dest",
350 FALSE,
351 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
352 /**
353 * RBEntryView:playing-state:
354 *
355 * Determines the icon to show in the 'playing' column next to the current
356 * playing entry.
357 */
358 g_object_class_install_property (object_class,
359 PROP_PLAYING_STATE,
360 g_param_spec_int ("playing-state",
361 "playing state",
362 "playback state for this entry view",
363 RB_ENTRY_VIEW_NOT_PLAYING,
364 RB_ENTRY_VIEW_PAUSED,
365 RB_ENTRY_VIEW_NOT_PLAYING,
366 G_PARAM_READWRITE));
367 /**
368 * RBEntryView:visible-columns:
369 *
370 * An array containing the names of the visible columns.
371 */
372 g_object_class_install_property (object_class,
373 PROP_VISIBLE_COLUMNS,
374 g_param_spec_boxed ("visible-columns",
375 "visible columns",
376 "visible columns",
377 G_TYPE_STRV,
378 G_PARAM_READWRITE));
379 /**
380 * RBEntryView::entry-added:
381 * @view: the #RBEntryView
382 * @entry: the #RhythmDBEntry that was added
383 *
384 * Emitted when an entry is added to the view
385 */
386 rb_entry_view_signals[ENTRY_ADDED] =
387 g_signal_new ("entry-added",
388 G_OBJECT_CLASS_TYPE (object_class),
389 G_SIGNAL_RUN_LAST,
390 G_STRUCT_OFFSET (RBEntryViewClass, entry_added),
391 NULL, NULL,
392 g_cclosure_marshal_VOID__BOXED,
393 G_TYPE_NONE,
394 1,
395 RHYTHMDB_TYPE_ENTRY);
396 /**
397 * RBEntryView::entry-deleted:
398 * @view: the #RBEntryView
399 * @entry: the #RhythmDBEntry that was removed
400 *
401 * Emitted when an entry has been removed from the view
402 */
403 rb_entry_view_signals[ENTRY_DELETED] =
404 g_signal_new ("entry-deleted",
405 G_OBJECT_CLASS_TYPE (object_class),
406 G_SIGNAL_RUN_LAST,
407 G_STRUCT_OFFSET (RBEntryViewClass, entry_deleted),
408 NULL, NULL,
409 g_cclosure_marshal_VOID__BOXED,
410 G_TYPE_NONE,
411 1,
412 RHYTHMDB_TYPE_ENTRY);
413 /**
414 * RBEntryView::entries-replaced:
415 * @view: the #RBEntryView
416 *
417 * Emitted when the model backing the entry view is replaced.
418 */
419 rb_entry_view_signals[ENTRIES_REPLACED] =
420 g_signal_new ("entries-replaced",
421 G_OBJECT_CLASS_TYPE (object_class),
422 G_SIGNAL_RUN_LAST,
423 G_STRUCT_OFFSET (RBEntryViewClass, entries_replaced),
424 NULL, NULL,
425 g_cclosure_marshal_VOID__VOID,
426 G_TYPE_NONE,
427 0);
428 /**
429 * RBEntryView::entry-activated:
430 * @view: the #RBEntryView
431 * @entry: the #RhythmDBEntry that was activated
432 *
433 * Emitted when an entry in the view is activated (by double clicking
434 * or by various key presses)
435 */
436 rb_entry_view_signals[ENTRY_ACTIVATED] =
437 g_signal_new ("entry-activated",
438 G_OBJECT_CLASS_TYPE (object_class),
439 G_SIGNAL_RUN_LAST,
440 G_STRUCT_OFFSET (RBEntryViewClass, entry_activated),
441 NULL, NULL,
442 g_cclosure_marshal_VOID__BOXED,
443 G_TYPE_NONE,
444 1,
445 RHYTHMDB_TYPE_ENTRY);
446 /**
447 * RBEntryView::selection-changed:
448 * @view: the #RBEntryView
449 *
450 * Emitted when the set of selected entries changes
451 */
452 rb_entry_view_signals[SELECTION_CHANGED] =
453 g_signal_new ("selection-changed",
454 G_OBJECT_CLASS_TYPE (object_class),
455 G_SIGNAL_RUN_LAST,
456 G_STRUCT_OFFSET (RBEntryViewClass, selection_changed),
457 NULL, NULL,
458 g_cclosure_marshal_VOID__VOID,
459 G_TYPE_NONE,
460 0);
461 /**
462 * RBEntryView::show-popup:
463 * @view: the #RBEntryView
464 * @over_entry: if TRUE, the popup request was made while pointing
465 * at an entry in the view
466 *
467 * Emitted when the user performs an action that should result in a
468 * popup menu appearing. If the action was a mouse button click,
469 * over_entry is FALSE if the mouse pointer was in the blank space after
470 * the last row in the view. If the action was a key press, over_entry
471 * is FALSE if no rows in the view are selected.
472 */
473 rb_entry_view_signals[SHOW_POPUP] =
474 g_signal_new ("show_popup",
475 G_OBJECT_CLASS_TYPE (object_class),
476 G_SIGNAL_RUN_LAST,
477 G_STRUCT_OFFSET (RBEntryViewClass, show_popup),
478 NULL, NULL,
479 g_cclosure_marshal_VOID__BOOLEAN,
480 G_TYPE_NONE,
481 1,
482 G_TYPE_BOOLEAN);
483 /**
484 * RBEntryView::have-selection-changed:
485 * @view: the #RBEntryView
486 * @have_selection: TRUE if one or more rows are selected
487 *
488 * Emitted when the user first selects a row, or when no rows are selected
489 * any more.
490 */
491 rb_entry_view_signals[HAVE_SEL_CHANGED] =
492 g_signal_new ("have_selection_changed",
493 G_OBJECT_CLASS_TYPE (object_class),
494 G_SIGNAL_RUN_LAST,
495 G_STRUCT_OFFSET (RBEntryViewClass, have_selection_changed),
496 NULL, NULL,
497 g_cclosure_marshal_VOID__BOOLEAN,
498 G_TYPE_NONE,
499 1,
500 G_TYPE_BOOLEAN);
501
502 g_type_class_add_private (klass, sizeof (RBEntryViewPrivate));
503
504 rb_entry_view_column_always_visible = g_quark_from_static_string ("rb_entry_view_column_always_visible");
505 }
506
507 static void
508 rb_entry_view_init (RBEntryView *view)
509 {
510 view->priv = G_TYPE_INSTANCE_GET_PRIVATE (view, RB_TYPE_ENTRY_VIEW, RBEntryViewPrivate);
511
512 view->priv->propid_column_map = g_hash_table_new (NULL, NULL);
513 view->priv->column_sort_data_map = g_hash_table_new_full (NULL, NULL, NULL, g_free);
514 view->priv->column_key_map = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
515 view->priv->type_ahead_propid = RHYTHMDB_PROP_TITLE;
516 }
517
518 static void
519 rb_entry_view_dispose (GObject *object)
520 {
521 RBEntryView *view;
522
523 g_return_if_fail (object != NULL);
524 g_return_if_fail (RB_IS_ENTRY_VIEW (object));
525
526 view = RB_ENTRY_VIEW (object);
527
528 g_return_if_fail (view->priv != NULL);
529
530 if (view->priv->selection_changed_id > 0) {
531 g_source_remove (view->priv->selection_changed_id);
532 view->priv->selection_changed_id = 0;
533 }
534
535 if (view->priv->playing_model != NULL) {
536 g_object_unref (view->priv->playing_model);
537 view->priv->playing_model = NULL;
538 }
539
540 if (view->priv->model != NULL) {
541 /* remove the model from the treeview so
542 * atk-bridge doesn't have to emit deletion events
543 * for each cell in the view.
544 */
545 gtk_tree_view_set_model (GTK_TREE_VIEW (view->priv->treeview), NULL);
546
547 g_object_unref (view->priv->model);
548 view->priv->model = NULL;
549 }
550
551 G_OBJECT_CLASS (rb_entry_view_parent_class)->dispose (object);
552 }
553
554 static void
555 rb_entry_view_finalize (GObject *object)
556 {
557 RBEntryView *view;
558
559 g_return_if_fail (object != NULL);
560 g_return_if_fail (RB_IS_ENTRY_VIEW (object));
561
562 view = RB_ENTRY_VIEW (object);
563
564 g_return_if_fail (view->priv != NULL);
565
566 g_hash_table_destroy (view->priv->propid_column_map);
567 g_hash_table_foreach (view->priv->column_sort_data_map,
568 rb_entry_view_sort_data_finalize, NULL);
569 g_hash_table_destroy (view->priv->column_sort_data_map);
570 g_hash_table_destroy (view->priv->column_key_map);
571
572 g_free (view->priv->sorting_column_name);
573
574 G_OBJECT_CLASS (rb_entry_view_parent_class)->finalize (object);
575 }
576
577 static void
578 rb_entry_view_sort_data_finalize (gpointer column,
579 gpointer gsort_data,
580 gpointer user_data)
581 {
582 struct RBEntryViewColumnSortData *sort_data = gsort_data;
583
584 if (sort_data->data_destroy) {
585 sort_data->data_destroy (sort_data->data);
586
587 sort_data->data_destroy = NULL;
588 sort_data->data = NULL;
589 sort_data->func = NULL;
590 }
591 }
592
593 static void
594 rb_entry_view_set_shell_player_internal (RBEntryView *view,
595 RBShellPlayer *player)
596 {
597 if (view->priv->shell_player != NULL) {
598 g_signal_handlers_disconnect_by_func (view->priv->shell_player,
599 G_CALLBACK (rb_entry_view_playing_song_changed),
600 view);
601 }
602
603 view->priv->shell_player = player;
604
605 g_signal_connect_object (view->priv->shell_player,
606 "playing-song-changed",
607 G_CALLBACK (rb_entry_view_playing_song_changed),
608 view, 0);
609 }
610
611 static void
612 rb_entry_view_set_model_internal (RBEntryView *view,
613 RhythmDBQueryModel *model)
614 {
615 if (view->priv->model != NULL) {
616 g_signal_handlers_disconnect_by_func (view->priv->model,
617 G_CALLBACK (rb_entry_view_row_inserted_cb),
618 view);
619 g_signal_handlers_disconnect_by_func (view->priv->model,
620 G_CALLBACK (rb_entry_view_row_deleted_cb),
621 view);
622 g_signal_handlers_disconnect_by_func (view->priv->model,
623 G_CALLBACK (rb_entry_view_rows_reordered_cb),
624 view);
625 g_object_unref (view->priv->model);
626 }
627
628 gtk_tree_selection_unselect_all (view->priv->selection);
629
630 view->priv->model = model;
631 if (view->priv->model != NULL) {
632 g_object_ref (view->priv->model);
633 g_signal_connect_object (view->priv->model,
634 "row_inserted",
635 G_CALLBACK (rb_entry_view_row_inserted_cb),
636 view,
637 0);
638 g_signal_connect_object (view->priv->model,
639 "row_deleted",
640 G_CALLBACK (rb_entry_view_row_deleted_cb),
641 view,
642 0);
643 g_signal_connect_object (view->priv->model,
644 "rows_reordered",
645 G_CALLBACK (rb_entry_view_rows_reordered_cb),
646 view,
647 0);
648
649 if (view->priv->sorting_column != NULL) {
650 rb_entry_view_resort_model (view);
651 }
652
653 gtk_tree_view_set_model (GTK_TREE_VIEW (view->priv->treeview),
654 GTK_TREE_MODEL (view->priv->model));
655 }
656
657 view->priv->have_selection = FALSE;
658 view->priv->have_complete_selection = FALSE;
659
660 g_signal_emit (G_OBJECT (view), rb_entry_view_signals[ENTRIES_REPLACED], 0);
661 }
662
663 static void
664 rb_entry_view_set_property (GObject *object,
665 guint prop_id,
666 const GValue *value,
667 GParamSpec *pspec)
668 {
669 RBEntryView *view = RB_ENTRY_VIEW (object);
670
671 switch (prop_id) {
672 case PROP_DB:
673 view->priv->db = g_value_get_object (value);
674 break;
675 case PROP_SHELL_PLAYER:
676 rb_entry_view_set_shell_player_internal (view, g_value_get_object (value));
677 break;
678 case PROP_SORT_ORDER:
679 rb_entry_view_set_sorting_type (view, g_value_get_string (value));
680 break;
681 case PROP_MODEL:
682 rb_entry_view_set_model_internal (view, g_value_get_object (value));
683 break;
684 case PROP_IS_DRAG_SOURCE:
685 view->priv->is_drag_source = g_value_get_boolean (value);
686 break;
687 case PROP_IS_DRAG_DEST:
688 view->priv->is_drag_dest = g_value_get_boolean (value);
689 break;
690 case PROP_PLAYING_STATE:
691 view->priv->playing_state = g_value_get_int (value);
692
693 /* redraw the playing entry, as the icon will have changed */
694 if (view->priv->playing_entry != NULL) {
695 rb_entry_view_emit_row_changed (view, view->priv->playing_entry);
696 }
697 break;
698 case PROP_VISIBLE_COLUMNS:
699 g_strfreev (view->priv->visible_columns);
700 view->priv->visible_columns = g_value_dup_boxed (value);
701 rb_entry_view_sync_columns_visible (view);
702 break;
703 default:
704 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
705 break;
706 }
707 }
708
709 static void
710 rb_entry_view_get_property (GObject *object,
711 guint prop_id,
712 GValue *value,
713 GParamSpec *pspec)
714 {
715 RBEntryView *view = RB_ENTRY_VIEW (object);
716
717 switch (prop_id) {
718 case PROP_DB:
719 g_value_set_object (value, view->priv->db);
720 break;
721 case PROP_SHELL_PLAYER:
722 g_value_set_object (value, view->priv->shell_player);
723 break;
724 case PROP_SORT_ORDER:
725 g_value_take_string (value, rb_entry_view_get_sorting_type (view));
726 break;
727 case PROP_MODEL:
728 g_value_set_object (value, view->priv->model);
729 break;
730 case PROP_IS_DRAG_SOURCE:
731 g_value_set_boolean (value, view->priv->is_drag_source);
732 break;
733 case PROP_IS_DRAG_DEST:
734 g_value_set_boolean (value, view->priv->is_drag_dest);
735 break;
736 case PROP_PLAYING_STATE:
737 g_value_set_int (value, view->priv->playing_state);
738 break;
739 case PROP_VISIBLE_COLUMNS:
740 g_value_set_boxed (value, view->priv->visible_columns);
741 break;
742 default:
743 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
744 break;
745 }
746 }
747
748 /**
749 * rb_entry_view_new:
750 * @db: the #RhythmDB instance
751 * @shell_player: the #RBShellPlayer instance
752 * @is_drag_source: if TRUE, the view should act as a drag and drop data source
753 * @is_drag_dest: if TRUE, the view should act as a drag and drop destination
754 *
755 * Creates a new entry view. If it makes sense to allow the user to drag entries
756 * from this entry view to other sources, @is_drag_source should be TRUE. If it
757 * makes sense to allow the user to drag entries from other sources to this view,
758 * @is_drag_dest should be TRUE. Drag and drop in this sense is used for two purposes:
759 * to transfer tracks between the filesystem and removable devices, and to add tracks
760 * to playlists.
761 *
762 * Return value: the new entry view
763 */
764 RBEntryView *
765 rb_entry_view_new (RhythmDB *db,
766 GObject *shell_player,
767 gboolean is_drag_source,
768 gboolean is_drag_dest)
769 {
770 RBEntryView *view;
771
772 view = RB_ENTRY_VIEW (g_object_new (RB_TYPE_ENTRY_VIEW,
773 "hadjustment", NULL,
774 "vadjustment", NULL,
775 "hscrollbar_policy", GTK_POLICY_AUTOMATIC,
776 "vscrollbar_policy", GTK_POLICY_AUTOMATIC,
777 "hexpand", TRUE,
778 "vexpand", TRUE,
779 "shadow_type", GTK_SHADOW_IN,
780 "db", db,
781 "shell-player", RB_SHELL_PLAYER (shell_player),
782 "is-drag-source", is_drag_source,
783 "is-drag-dest", is_drag_dest,
784 NULL));
785
786 g_return_val_if_fail (view->priv != NULL, NULL);
787
788 return view;
789 }
790
791 /**
792 * rb_entry_view_set_model:
793 * @view: the #RBEntryView
794 * @model: the new #RhythmDBQueryModel to use for the view
795 *
796 * Replaces the model backing the entry view.
797 */
798 void
799 rb_entry_view_set_model (RBEntryView *view,
800 RhythmDBQueryModel *model)
801 {
802 g_object_set (view, "model", model, NULL);
803 }
804
805 /* Sweet name, eh? */
806 struct RBEntryViewCellDataFuncData {
807 RBEntryView *view;
808 RhythmDBPropType propid;
809 };
810
811 static void
812 rb_entry_view_playing_cell_data_func (GtkTreeViewColumn *column,
813 GtkCellRenderer *renderer,
814 GtkTreeModel *tree_model,
815 GtkTreeIter *iter,
816 RBEntryView *view)
817 {
818 RhythmDBEntry *entry;
819 const char *name = NULL;
820
821 entry = rhythmdb_query_model_iter_to_entry (view->priv->model, iter);
822
823 if (entry == NULL) {
824 return;
825 }
826
827 if (entry == view->priv->playing_entry) {
828 switch (view->priv->playing_state) {
829 case RB_ENTRY_VIEW_PLAYING:
830 name = "media-playback-start-symbolic";
831 break;
832 case RB_ENTRY_VIEW_PAUSED:
833 name = "media-playback-pause-symbolic";
834 break;
835 default:
836 name = NULL;
837 break;
838 }
839 }
840
841 if (name == NULL && rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_PLAYBACK_ERROR)) {
842 name = "dialog-error-symbolic";
843 }
844
845 g_object_set (renderer, "icon-name", name, NULL);
846
847 rhythmdb_entry_unref (entry);
848 }
849
850 static void
851 rb_entry_view_rating_cell_data_func (GtkTreeViewColumn *column,
852 GtkCellRenderer *renderer,
853 GtkTreeModel *tree_model,
854 GtkTreeIter *iter,
855 RBEntryView *view)
856 {
857 RhythmDBEntry *entry;
858
859 entry = rhythmdb_query_model_iter_to_entry (view->priv->model, iter);
860
861 g_object_set (renderer,
862 "rating", rhythmdb_entry_get_double (entry, RHYTHMDB_PROP_RATING),
863 NULL);
864
865 rhythmdb_entry_unref (entry);
866 }
867
868 static void
869 rb_entry_view_bpm_cell_data_func (GtkTreeViewColumn *column,
870 GtkCellRenderer *renderer,
871 GtkTreeModel *tree_model,
872 GtkTreeIter *iter,
873 struct RBEntryViewCellDataFuncData *data)
874 {
875 RhythmDBEntry *entry;
876 char *str;
877 gdouble val;
878
879 entry = rhythmdb_query_model_iter_to_entry (data->view->priv->model, iter);
880
881 val = rhythmdb_entry_get_double (entry, data->propid);
882
883 if (val > 0.001)
884 str = g_strdup_printf ("%.2f", val);
885 else
886 str = g_strdup ("");
887
888 g_object_set (renderer, "text", str, NULL);
889 g_free (str);
890 rhythmdb_entry_unref (entry);
891 }
892
893 static void
894 rb_entry_view_long_cell_data_func (GtkTreeViewColumn *column,
895 GtkCellRenderer *renderer,
896 GtkTreeModel *tree_model,
897 GtkTreeIter *iter,
898 struct RBEntryViewCellDataFuncData *data)
899 {
900 RhythmDBEntry *entry;
901 char *str;
902 gulong val;
903
904 entry = rhythmdb_query_model_iter_to_entry (data->view->priv->model, iter);
905
906 val = rhythmdb_entry_get_ulong (entry, data->propid);
907
908 if (val > 0)
909 str = g_strdup_printf ("%lu", val);
910 else
911 str = g_strdup ("");
912
913 g_object_set (renderer, "text", str, NULL);
914 g_free (str);
915 rhythmdb_entry_unref (entry);
916 }
917
918 static void
919 rb_entry_view_play_count_cell_data_func (GtkTreeViewColumn *column,
920 GtkCellRenderer *renderer,
921 GtkTreeModel *tree_model,
922 GtkTreeIter * iter,
923 struct RBEntryViewCellDataFuncData *data)
924 {
925 RhythmDBEntry *entry;
926 gulong i;
927 char *str;
928
929 entry = rhythmdb_query_model_iter_to_entry (data->view->priv->model, iter);
930
931 i = rhythmdb_entry_get_ulong (entry, data->propid);
932 if (i == 0)
933 str = _("Never");
934 else
935 str = g_strdup_printf ("%ld", i);
936
937 g_object_set (renderer, "text", str, NULL);
938 if (i != 0)
939 g_free (str);
940
941 rhythmdb_entry_unref (entry);
942 }
943
944 static void
945 rb_entry_view_duration_cell_data_func (GtkTreeViewColumn *column,
946 GtkCellRenderer *renderer,
947 GtkTreeModel *tree_model,
948 GtkTreeIter *iter,
949 struct RBEntryViewCellDataFuncData *data)
950 {
951 RhythmDBEntry *entry;
952 gulong duration;
953 char *str;
954
955 entry = rhythmdb_query_model_iter_to_entry (data->view->priv->model, iter);
956 duration = rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_DURATION);
957
958 str = rb_make_duration_string (duration);
959 g_object_set (renderer, "text", str, NULL);
960 g_free (str);
961 rhythmdb_entry_unref (entry);
962 }
963
964 static void
965 rb_entry_view_year_cell_data_func (GtkTreeViewColumn *column,
966 GtkCellRenderer *renderer,
967 GtkTreeModel *tree_model,
968 GtkTreeIter *iter,
969 struct RBEntryViewCellDataFuncData *data)
970 {
971 RhythmDBEntry *entry;
972 char str[255];
973 int julian;
974 GDate *date;
975
976 entry = rhythmdb_query_model_iter_to_entry (data->view->priv->model, iter);
977 julian = rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_DATE);
978
979 if (julian > 0) {
980 date = g_date_new_julian (julian);
981 g_date_strftime (str, sizeof (str), "%Y", date);
982 g_object_set (renderer, "text", str, NULL);
983 g_date_free (date);
984 } else {
985 g_object_set (renderer, "text", _("Unknown"), NULL);
986 }
987
988 rhythmdb_entry_unref (entry);
989 }
990
991 static void
992 rb_entry_view_quality_cell_data_func (GtkTreeViewColumn *column,
993 GtkCellRenderer *renderer,
994 GtkTreeModel *tree_model,
995 GtkTreeIter *iter,
996 struct RBEntryViewCellDataFuncData *data)
997 {
998 RhythmDBEntry *entry;
999 gulong bitrate;
1000
1001 entry = rhythmdb_query_model_iter_to_entry (data->view->priv->model, iter);
1002 bitrate = rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_BITRATE);
1003
1004 if (rhythmdb_entry_is_lossless (entry)) {
1005 g_object_set (renderer, "text", _("Lossless"), NULL);
1006 } else if (bitrate == 0) {
1007 g_object_set (renderer, "text", _("Unknown"), NULL);
1008 } else {
1009 char *s;
1010
1011 s = g_strdup_printf (_("%lu kbps"), bitrate);
1012 g_object_set (renderer, "text", s, NULL);
1013 g_free (s);
1014 }
1015
1016 rhythmdb_entry_unref (entry);
1017 }
1018
1019 static void
1020 rb_entry_view_location_cell_data_func (GtkTreeViewColumn *column,
1021 GtkCellRenderer *renderer,
1022 GtkTreeModel *tree_model,
1023 GtkTreeIter *iter,
1024 struct RBEntryViewCellDataFuncData *data)
1025 {
1026 RhythmDBEntry *entry;
1027 const char *location;
1028 char *str;
1029
1030 entry = rhythmdb_query_model_iter_to_entry (data->view->priv->model, iter);
1031
1032 location = rhythmdb_entry_get_string (entry, data->propid);
1033 str = g_uri_unescape_string (location, NULL);
1034
1035 g_object_set (renderer, "text", str, NULL);
1036 g_free (str);
1037
1038 rhythmdb_entry_unref (entry);
1039 }
1040
1041 static void
1042 rb_entry_view_string_cell_data_func (GtkTreeViewColumn *column,
1043 GtkCellRenderer *renderer,
1044 GtkTreeModel *tree_model,
1045 GtkTreeIter *iter,
1046 struct RBEntryViewCellDataFuncData *data)
1047 {
1048 RhythmDBEntry *entry;
1049 const char *str;
1050
1051 entry = rhythmdb_query_model_iter_to_entry (data->view->priv->model, iter);
1052
1053 str = rhythmdb_entry_get_string (entry, data->propid);
1054 if (str != NULL) {
1055 g_object_set (renderer, "text", str, NULL);
1056 }
1057
1058 rhythmdb_entry_unref (entry);
1059 }
1060
1061 static void
1062 rb_entry_view_sync_sorting (RBEntryView *view)
1063 {
1064 GtkTreeViewColumn *column;
1065 gint direction;
1066 char *column_name;
1067 RhythmDBPropType type_ahead_propid;
1068 GList *renderers;
1069
1070 direction = GTK_SORT_ASCENDING;
1071 column_name = NULL;
1072 rb_entry_view_get_sorting_order (view, &column_name, &direction);
1073
1074 if (column_name == NULL) {
1075 return;
1076 }
1077
1078 column = g_hash_table_lookup (view->priv->column_key_map, column_name);
1079 if (column == NULL) {
1080 rb_debug ("couldn't find column %s", column_name);
1081 g_free (column_name);
1082 return;
1083 }
1084
1085 rb_debug ("Updating EntryView sort order to %s:%d", column_name, direction);
1086
1087 /* remove the old sorting indicator */
1088 if (view->priv->sorting_column)
1089 gtk_tree_view_column_set_sort_indicator (view->priv->sorting_column, FALSE);
1090
1091 /* set the sorting order and indicator of the new sorting column */
1092 view->priv->sorting_column = column;
1093 gtk_tree_view_column_set_sort_indicator (column, TRUE);
1094 gtk_tree_view_column_set_sort_order (column, direction);
1095
1096 /* set the property id to use for the typeahead search */
1097 renderers = gtk_cell_layout_get_cells (GTK_CELL_LAYOUT (column));
1098 type_ahead_propid = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (renderers->data), CELL_PROPID_ITEM));
1099 g_list_free (renderers);
1100 if (type_ahead_propid != 0 && rhythmdb_get_property_type (view->priv->db, type_ahead_propid) == G_TYPE_STRING)
1101 view->priv->type_ahead_propid = type_ahead_propid;
1102 else
1103 view->priv->type_ahead_propid = RHYTHMDB_PROP_TITLE;
1104
1105 g_free (column_name);
1106 }
1107
1108 /**
1109 * rb_entry_view_get_sorting_type:
1110 * @view: an #RBEntryView
1111 *
1112 * Constructs a string that describes the sort settings for the entry view.
1113 * This consists of a column name and an order ('ascending' or 'descending')
1114 * separated by a comma.
1115 *
1116 * Return value: (transfer full): sort order description
1117 */
1118 char *
1119 rb_entry_view_get_sorting_type (RBEntryView *view)
1120 {
1121 char *sorttype;
1122 GString *key = g_string_new (view->priv->sorting_column_name);
1123
1124 g_string_append_c (key, ',');
1125
1126 switch (view->priv->sorting_order)
1127 {
1128 case GTK_SORT_ASCENDING:
1129 g_string_append (key, "ascending");
1130 break;
1131 case GTK_SORT_DESCENDING:
1132 g_string_append (key, "descending");
1133 break;
1134 default:
1135 g_assert_not_reached ();
1136 }
1137
1138 sorttype = key->str;
1139 g_string_free (key, FALSE);
1140
1141 return sorttype;
1142 }
1143
1144 /**
1145 * rb_entry_view_set_sorting_type:
1146 * @view: a #RBEntryView
1147 * @sorttype: sort order description
1148 *
1149 * Changes the sort order for the entry view. The sort order
1150 * description must be a column name, followed by a comma, followed
1151 * by an order description ('ascending' or 'descending').
1152 */
1153 void
1154 rb_entry_view_set_sorting_type (RBEntryView *view,
1155 const char *sorttype)
1156 {
1157 char **strs;
1158
1159 if (!sorttype || !strchr (sorttype, ',')) {
1160 rb_debug ("malformed sort data: %s", (sorttype) ? sorttype : "(null)");
1161 return;
1162 }
1163
1164 strs = g_strsplit (sorttype, ",", 0);
1165
1166 g_free (view->priv->sorting_column_name);
1167 view->priv->sorting_column_name = g_strdup(strs[0]);
1168
1169 if (!strcmp ("ascending", strs[1]))
1170 view->priv->sorting_order = GTK_SORT_ASCENDING;
1171 else if (!strcmp ("descending", strs[1]))
1172 view->priv->sorting_order = GTK_SORT_DESCENDING;
1173 else {
1174 g_warning ("atttempting to sort in unknown direction");
1175 view->priv->sorting_order = GTK_SORT_ASCENDING;
1176 }
1177
1178 g_strfreev (strs);
1179
1180 rb_entry_view_sync_sorting (view);
1181 g_object_notify (G_OBJECT (view), "sort-order");
1182 }
1183
1184 /**
1185 * rb_entry_view_get_sorting_order:
1186 * @view: a #RBEntryView
1187 * @column_name: (out callee-allocates) (allow-none) (transfer full): returns the sort column name
1188 * @sort_order: (out) (allow-none) returns the sort ordering as a #GtkSortType value
1189 *
1190 * Retrieves the sort settings for the view.
1191 */
1192 void
1193 rb_entry_view_get_sorting_order (RBEntryView *view,
1194 char **column_name,
1195 gint *sort_order)
1196 {
1197 g_return_if_fail (RB_IS_ENTRY_VIEW (view));
1198
1199 if (column_name != NULL) {
1200 *column_name = g_strdup (view->priv->sorting_column_name);
1201 }
1202
1203 if (sort_order != NULL) {
1204 *sort_order = view->priv->sorting_order;
1205 }
1206 }
1207
1208 /**
1209 * rb_entry_view_set_sorting_order:
1210 * @view: a #RBEntryView
1211 * @column_name: name of the column to sort on
1212 * @sort_order: order to sort in, as a #GtkSortType
1213 *
1214 * Sets the sort order for the entry view.
1215 */
1216 void
1217 rb_entry_view_set_sorting_order (RBEntryView *view,
1218 const char *column_name,
1219 gint sort_order)
1220 {
1221 if (column_name == NULL)
1222 return;
1223
1224 g_free (view->priv->sorting_column_name);
1225 view->priv->sorting_column_name = g_strdup (column_name);
1226 view->priv->sorting_order = sort_order;
1227
1228 rb_entry_view_sync_sorting (view);
1229 g_object_notify (G_OBJECT (view), "sort-order");
1230 }
1231
1232 static void
1233 rb_entry_view_column_clicked_cb (GtkTreeViewColumn *column, RBEntryView *view)
1234 {
1235 gint sort_order;
1236 char *clicked_column;
1237
1238 rb_debug ("sorting on column %p", column);
1239
1240 /* identify the clicked column, and then update the sorting order */
1241 clicked_column = (char*) g_object_get_data (G_OBJECT (column), "rb-entry-view-key");
1242 sort_order = view->priv->sorting_order;
1243
1244 if (view->priv->sorting_column_name
1245 && !strcmp(clicked_column, view->priv->sorting_column_name)
1246 && (sort_order == GTK_SORT_ASCENDING))
1247 sort_order = GTK_SORT_DESCENDING;
1248 else
1249 sort_order = GTK_SORT_ASCENDING;
1250
1251 rb_entry_view_set_sorting_order (view, clicked_column, sort_order);
1252 }
1253
1254 /**
1255 * rb_entry_view_get_column:
1256 * @view: a #RBEntryView
1257 * @coltype: type of column to retrieve
1258 *
1259 * Retrieves a predefined column from the entry view. This can be used
1260 * to insert additional cell renderers into the column.
1261 *
1262 * Return value: (transfer none): a #GtkTreeViewColumn instance, or NULL
1263 */
1264 GtkTreeViewColumn *
1265 rb_entry_view_get_column (RBEntryView *view, RBEntryViewColumn coltype)
1266 {
1267 RhythmDBPropType propid;
1268
1269 /* convert column type to property ID */
1270 switch (coltype) {
1271 case RB_ENTRY_VIEW_COL_TRACK_NUMBER:
1272 propid = RHYTHMDB_PROP_TRACK_NUMBER;
1273 break;
1274 case RB_ENTRY_VIEW_COL_TITLE:
1275 propid = RHYTHMDB_PROP_TITLE;
1276 break;
1277 case RB_ENTRY_VIEW_COL_ARTIST:
1278 propid = RHYTHMDB_PROP_ARTIST;
1279 break;
1280 case RB_ENTRY_VIEW_COL_ALBUM:
1281 propid = RHYTHMDB_PROP_ALBUM;
1282 break;
1283 case RB_ENTRY_VIEW_COL_GENRE:
1284 propid = RHYTHMDB_PROP_GENRE;
1285 break;
1286 case RB_ENTRY_VIEW_COL_COMMENT:
1287 propid = RHYTHMDB_PROP_COMMENT;
1288 break;
1289 case RB_ENTRY_VIEW_COL_DURATION:
1290 propid = RHYTHMDB_PROP_DURATION;
1291 break;
1292 case RB_ENTRY_VIEW_COL_YEAR:
1293 propid = RHYTHMDB_PROP_DATE;
1294 break;
1295 case RB_ENTRY_VIEW_COL_QUALITY:
1296 propid = RHYTHMDB_PROP_BITRATE;
1297 break;
1298 case RB_ENTRY_VIEW_COL_RATING:
1299 propid = RHYTHMDB_PROP_RATING;
1300 break;
1301 case RB_ENTRY_VIEW_COL_PLAY_COUNT:
1302 propid = RHYTHMDB_PROP_PLAY_COUNT;
1303 break;
1304 case RB_ENTRY_VIEW_COL_LAST_PLAYED:
1305 propid = RHYTHMDB_PROP_LAST_PLAYED;
1306 break;
1307 case RB_ENTRY_VIEW_COL_FIRST_SEEN:
1308 propid = RHYTHMDB_PROP_FIRST_SEEN;
1309 break;
1310 case RB_ENTRY_VIEW_COL_LAST_SEEN:
1311 propid = RHYTHMDB_PROP_LAST_SEEN;
1312 break;
1313 case RB_ENTRY_VIEW_COL_LOCATION:
1314 propid = RHYTHMDB_PROP_LOCATION;
1315 break;
1316 case RB_ENTRY_VIEW_COL_ERROR:
1317 propid = RHYTHMDB_PROP_PLAYBACK_ERROR;
1318 break;
1319 default:
1320 g_assert_not_reached ();
1321 propid = -1;
1322 break;
1323 }
1324
1325 /* find the column */
1326 return (GtkTreeViewColumn *)g_hash_table_lookup (view->priv->propid_column_map, GINT_TO_POINTER (propid));
1327 }
1328
1329 static void
1330 rb_entry_view_cell_edited_cb (GtkCellRendererText *renderer,
1331 char *path_str,
1332 char *new_text,
1333 RBEntryView *view)
1334 {
1335 RhythmDBPropType propid;
1336 RhythmDBEntry *entry;
1337 GValue value = {0,};
1338 GtkTreePath *path;
1339
1340 /* get the property corresponding to the cell, filter out properties we can't edit */
1341 propid = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (renderer), CELL_PROPID_ITEM));
1342 switch (propid) {
1343 case RHYTHMDB_PROP_TITLE:
1344 case RHYTHMDB_PROP_GENRE:
1345 case RHYTHMDB_PROP_ARTIST:
1346 case RHYTHMDB_PROP_ALBUM:
1347 case RHYTHMDB_PROP_COMMENT:
1348 case RHYTHMDB_PROP_ARTIST_SORTNAME:
1349 case RHYTHMDB_PROP_ALBUM_SORTNAME:
1350 break;
1351
1352 default:
1353 rb_debug ("can't edit property %s", rhythmdb_nice_elt_name_from_propid (view->priv->db, propid));
1354 return;
1355 }
1356
1357 /* find entry */
1358 path = gtk_tree_path_new_from_string (path_str);
1359 entry = rhythmdb_query_model_tree_path_to_entry (view->priv->model, path);
1360 gtk_tree_path_free (path);
1361
1362 if (entry != NULL) {
1363 /* update it */
1364 g_value_init (&value, G_TYPE_STRING);
1365 g_value_set_string (&value, new_text);
1366 rhythmdb_entry_set (view->priv->db, entry, propid, &value);
1367 g_value_unset (&value);
1368
1369 rhythmdb_commit (view->priv->db);
1370 rhythmdb_entry_unref (entry);
1371 }
1372 }
1373
1374
1375 /**
1376 * rb_entry_view_append_column:
1377 * @view: a #RBEntryView
1378 * @coltype: type of column to append
1379 * @always_visible: if TRUE, ignore the user's column visibility settings
1380 *
1381 * Appends a predefined column type to the set of columns already present
1382 * in the entry view. If @always_visible is TRUE, the column will ignore
1383 * the user's coulmn visibility settings and will always be visible.
1384 * This should only be used when it is vital for the purpose of the
1385 * source that the column be visible.
1386 */
1387 void
1388 rb_entry_view_append_column (RBEntryView *view,
1389 RBEntryViewColumn coltype,
1390 gboolean always_visible)
1391 {
1392 GtkTreeViewColumn *column;
1393 GtkCellRenderer *renderer = NULL;
1394 struct RBEntryViewCellDataFuncData *cell_data;
1395 const char *title = NULL;
1396 const char *key = NULL;
1397 const char *strings[5] = {0};
1398 GtkTreeCellDataFunc cell_data_func = NULL;
1399 GCompareDataFunc sort_func = NULL;
1400 RhythmDBPropType propid;
1401 RhythmDBPropType sort_propid = RHYTHMDB_NUM_PROPERTIES;
1402 gboolean ellipsize = FALSE;
1403 gboolean resizable = TRUE;
1404 gint column_width = -1;
1405
1406 column = gtk_tree_view_column_new ();
1407
1408 cell_data = g_new0 (struct RBEntryViewCellDataFuncData, 1);
1409 cell_data->view = view;
1410
1411 switch (coltype) {
1412 case RB_ENTRY_VIEW_COL_TRACK_NUMBER:
1413 propid = RHYTHMDB_PROP_TRACK_NUMBER;
1414 cell_data->propid = propid;
1415 cell_data_func = (GtkTreeCellDataFunc) rb_entry_view_long_cell_data_func;
1416 sort_func = (GCompareDataFunc) rhythmdb_query_model_track_sort_func;
1417 title = _("Track");
1418 key = "Track";
1419 strings[0] = title;
1420 strings[1] = "9999";
1421 break;
1422 case RB_ENTRY_VIEW_COL_TITLE:
1423 propid = RHYTHMDB_PROP_TITLE;
1424 cell_data->propid = propid;
1425 cell_data_func = (GtkTreeCellDataFunc) rb_entry_view_string_cell_data_func;
1426 sort_propid = RHYTHMDB_PROP_TITLE_SORT_KEY;
1427 sort_func = (GCompareDataFunc) rhythmdb_query_model_string_sort_func;
1428 title = _("Title");
1429 key = "Title";
1430 ellipsize = TRUE;
1431 break;
1432 case RB_ENTRY_VIEW_COL_ARTIST:
1433 propid = RHYTHMDB_PROP_ARTIST;
1434 cell_data->propid = propid;
1435 cell_data_func = (GtkTreeCellDataFunc) rb_entry_view_string_cell_data_func;
1436 sort_propid = RHYTHMDB_PROP_ARTIST_SORT_KEY;
1437 sort_func = (GCompareDataFunc) rhythmdb_query_model_artist_sort_func;
1438 title = _("Artist");
1439 key = "Artist";
1440 ellipsize = TRUE;
1441 break;
1442 case RB_ENTRY_VIEW_COL_ALBUM:
1443 propid = RHYTHMDB_PROP_ALBUM;
1444 cell_data->propid = propid;
1445 cell_data_func = (GtkTreeCellDataFunc) rb_entry_view_string_cell_data_func;
1446 sort_propid = RHYTHMDB_PROP_ALBUM_SORT_KEY;
1447 sort_func = (GCompareDataFunc) rhythmdb_query_model_album_sort_func;
1448 title = _("Album");
1449 key = "Album";
1450 ellipsize = TRUE;
1451 break;
1452 case RB_ENTRY_VIEW_COL_GENRE:
1453 propid = RHYTHMDB_PROP_GENRE;
1454 cell_data->propid = propid;
1455 cell_data_func = (GtkTreeCellDataFunc) rb_entry_view_string_cell_data_func;
1456 sort_propid = RHYTHMDB_PROP_GENRE_SORT_KEY;
1457 sort_func = (GCompareDataFunc) rhythmdb_query_model_genre_sort_func;
1458 title = _("Genre");
1459 key = "Genre";
1460 ellipsize = TRUE;
1461 break;
1462 case RB_ENTRY_VIEW_COL_COMMENT:
1463 propid = RHYTHMDB_PROP_COMMENT;
1464 cell_data->propid = propid;
1465 cell_data_func = (GtkTreeCellDataFunc) rb_entry_view_string_cell_data_func;
1466 sort_propid = cell_data->propid;
1467 sort_func = (GCompareDataFunc) rhythmdb_query_model_string_sort_func;
1468 title = _("Comment");
1469 key = "Comment";
1470 ellipsize = TRUE;
1471 break;
1472 case RB_ENTRY_VIEW_COL_DURATION:
1473 propid = RHYTHMDB_PROP_DURATION;
1474 cell_data->propid = propid;
1475 cell_data_func = (GtkTreeCellDataFunc) rb_entry_view_duration_cell_data_func;
1476 sort_propid = cell_data->propid;
1477 sort_func = (GCompareDataFunc) rhythmdb_query_model_ulong_sort_func;
1478 title = _("Time");
1479 key = "Time";
1480 strings[0] = title;
1481 strings[1] = "000:00";
1482 strings[2] = _("Unknown");
1483 break;
1484 case RB_ENTRY_VIEW_COL_YEAR:
1485 propid = RHYTHMDB_PROP_DATE;
1486 cell_data->propid = propid;
1487 cell_data_func = (GtkTreeCellDataFunc) rb_entry_view_year_cell_data_func;
1488 sort_propid = cell_data->propid;
1489 sort_func = (GCompareDataFunc) rhythmdb_query_model_date_sort_func;
1490 title = _("Year");
1491 key = "Year";
1492 strings[0] = title;
1493 strings[1] = "0000";
1494 strings[2] = _("Unknown");
1495 break;
1496 case RB_ENTRY_VIEW_COL_QUALITY:
1497 propid = RHYTHMDB_PROP_BITRATE;
1498 cell_data->propid = propid;
1499 cell_data_func = (GtkTreeCellDataFunc) rb_entry_view_quality_cell_data_func;
1500 sort_propid = cell_data->propid;
1501 sort_func = (GCompareDataFunc) rhythmdb_query_model_bitrate_sort_func;
1502 title = _("Quality");
1503 key = "Quality";
1504 strings[0] = title;
1505 strings[1] = _("000 kbps");
1506 strings[2] = _("Unknown");
1507 strings[3] = _("Lossless");
1508 break;
1509 case RB_ENTRY_VIEW_COL_RATING:
1510 propid = RHYTHMDB_PROP_RATING;
1511 sort_func = (GCompareDataFunc) rhythmdb_query_model_double_ceiling_sort_func;
1512
1513 gtk_icon_size_lookup (GTK_ICON_SIZE_MENU, &column_width, NULL);
1514 column_width = column_width * 5 + 5;
1515 resizable = FALSE;
1516 title = _("Rating");
1517 key = "Rating";
1518
1519 renderer = rb_cell_renderer_rating_new ();
1520 gtk_tree_view_column_pack_start (column, renderer, TRUE);
1521 gtk_tree_view_column_set_cell_data_func (column, renderer,
1522 (GtkTreeCellDataFunc)
1523 rb_entry_view_rating_cell_data_func,
1524 view,
1525 NULL);
1526 g_signal_connect_object (renderer,
1527 "rated",
1528 G_CALLBACK (rb_entry_view_rated_cb),
1529 view,
1530 0);
1531 break;
1532 case RB_ENTRY_VIEW_COL_PLAY_COUNT:
1533 propid = RHYTHMDB_PROP_PLAY_COUNT;
1534 cell_data->propid = propid;
1535 cell_data_func = (GtkTreeCellDataFunc) rb_entry_view_play_count_cell_data_func;
1536 sort_propid = cell_data->propid;
1537 sort_func = (GCompareDataFunc) rhythmdb_query_model_ulong_sort_func;
1538 title = _("Play Count");
1539 key = "PlayCount";
1540 strings[0] = title;
1541 strings[1] = _("Never");
1542 strings[2] = "9999";
1543 break;
1544 case RB_ENTRY_VIEW_COL_LAST_PLAYED:
1545 propid = RHYTHMDB_PROP_LAST_PLAYED;
1546 cell_data->propid = RHYTHMDB_PROP_LAST_PLAYED_STR;
1547 cell_data_func = (GtkTreeCellDataFunc) rb_entry_view_string_cell_data_func;
1548 sort_propid = RHYTHMDB_PROP_LAST_PLAYED;
1549 sort_func = (GCompareDataFunc) rhythmdb_query_model_ulong_sort_func;
1550 title = _("Last Played");
1551 key = "LastPlayed";
1552 strings[0] = title;
1553 strings[1] = rb_entry_view_get_time_date_column_sample ();
1554 strings[2] = _("Never");
1555 break;
1556 case RB_ENTRY_VIEW_COL_FIRST_SEEN:
1557 propid = RHYTHMDB_PROP_FIRST_SEEN;
1558 cell_data->propid = RHYTHMDB_PROP_FIRST_SEEN_STR;
1559 cell_data_func = (GtkTreeCellDataFunc) rb_entry_view_string_cell_data_func;
1560 sort_propid = RHYTHMDB_PROP_FIRST_SEEN;
1561 sort_func = (GCompareDataFunc) rhythmdb_query_model_ulong_sort_func;
1562 title = _("Date Added");
1563 key = "FirstSeen";
1564 strings[0] = title;
1565 strings[1] = rb_entry_view_get_time_date_column_sample ();
1566 break;
1567 case RB_ENTRY_VIEW_COL_LAST_SEEN:
1568 propid = RHYTHMDB_PROP_LAST_SEEN;
1569 cell_data->propid = RHYTHMDB_PROP_LAST_SEEN_STR;
1570 cell_data_func = (GtkTreeCellDataFunc) rb_entry_view_string_cell_data_func;
1571 sort_propid = RHYTHMDB_PROP_LAST_SEEN;
1572 sort_func = (GCompareDataFunc) rhythmdb_query_model_ulong_sort_func;
1573 title = _("Last Seen");
1574 key = "LastSeen";
1575 strings[0] = title;
1576 strings[1] = rb_entry_view_get_time_date_column_sample ();
1577 break;
1578 case RB_ENTRY_VIEW_COL_LOCATION:
1579 propid = RHYTHMDB_PROP_LOCATION;
1580 cell_data->propid = RHYTHMDB_PROP_LOCATION;
1581 cell_data_func = (GtkTreeCellDataFunc) rb_entry_view_location_cell_data_func;
1582 sort_propid = RHYTHMDB_PROP_LOCATION;
1583 sort_func = (GCompareDataFunc) rhythmdb_query_model_location_sort_func;
1584 title = _("Location");
1585 key = "Location";
1586 ellipsize = TRUE;
1587 break;
1588 case RB_ENTRY_VIEW_COL_BPM:
1589 propid = RHYTHMDB_PROP_BPM;
1590 cell_data->propid = propid;
1591 cell_data_func = (GtkTreeCellDataFunc) rb_entry_view_bpm_cell_data_func;
1592 sort_func = (GCompareDataFunc) rhythmdb_query_model_double_ceiling_sort_func;
1593 title = _("BPM");
1594 key = "BPM";
1595 strings[0] = title;
1596 strings[1] = "999.99";
1597 break;
1598 case RB_ENTRY_VIEW_COL_ERROR:
1599 propid = RHYTHMDB_PROP_PLAYBACK_ERROR;
1600 cell_data->propid = RHYTHMDB_PROP_PLAYBACK_ERROR;
1601 cell_data_func = (GtkTreeCellDataFunc) rb_entry_view_string_cell_data_func;
1602 title = _("Error");
1603 key = "Error";
1604 ellipsize = TRUE;
1605 break;
1606 default:
1607 g_assert_not_reached ();
1608 propid = -1;
1609 break;
1610 }
1611
1612 if (sort_propid == RHYTHMDB_NUM_PROPERTIES)
1613 sort_propid = propid;
1614
1615 if (renderer == NULL) {
1616 renderer = gtk_cell_renderer_text_new ();
1617 gtk_tree_view_column_pack_start (column, renderer, TRUE);
1618 gtk_tree_view_column_set_cell_data_func (column, renderer,
1619 cell_data_func, cell_data, g_free);
1620
1621 g_object_set_data (G_OBJECT (renderer), CELL_PROPID_ITEM, GINT_TO_POINTER (propid));
1622 g_signal_connect_object (renderer, "edited",
1623 G_CALLBACK (rb_entry_view_cell_edited_cb),
1624 view, 0);
1625 g_object_set (renderer, "single-paragraph-mode", TRUE, NULL);
1626 } else {
1627 g_free (cell_data);
1628 }
1629
1630 /*
1631 * Columns must either be expanding (ellipsized) or have a
1632 * fixed minimum width specified. Otherwise, gtk+ gives them a
1633 * width of 0.
1634 */
1635 if (ellipsize) {
1636 g_object_set (renderer, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
1637 gtk_tree_view_column_set_expand (GTK_TREE_VIEW_COLUMN (column), TRUE);
1638 } else if (column_width != -1) {
1639 gtk_tree_view_column_set_fixed_width (column, column_width);
1640 } else {
1641 rb_entry_view_set_fixed_column_width (view, column, renderer, strings);
1642 }
1643
1644 if (resizable)
1645 gtk_tree_view_column_set_resizable (column, TRUE);
1646
1647 gtk_tree_view_column_set_sizing (column, GTK_TREE_VIEW_COLUMN_FIXED);
1648 gtk_tree_view_column_set_clickable (column, TRUE);
1649
1650 if (always_visible)
1651 g_object_set_qdata (G_OBJECT (column),
1652 rb_entry_view_column_always_visible,
1653 GINT_TO_POINTER (1));
1654
1655 g_hash_table_insert (view->priv->propid_column_map, GINT_TO_POINTER (propid), column);
1656
1657 rb_entry_view_append_column_custom (view, column, title, key, sort_func, GINT_TO_POINTER (sort_propid), NULL);
1658 }
1659
1660 /**
1661 * rb_entry_view_append_column_custom:
1662 * @view: a #RBEntryView
1663 * @column: (transfer full): a #GtkTreeViewColumn to append
1664 * @title: title for the column (translated)
1665 * @key: sort key for the column (not translated)
1666 * @sort_func: comparison function to use for sorting on the column
1667 * @data: (closure) (scope notified): data to pass to the sort function
1668 * @data_destroy: function to use to destroy the sort data
1669 *
1670 * Appends a custom column to the entry view.
1671 */
1672 void
1673 rb_entry_view_append_column_custom (RBEntryView *view,
1674 GtkTreeViewColumn *column,
1675 const char *title,
1676 const char *key,
1677 GCompareDataFunc sort_func,
1678 gpointer data,
1679 GDestroyNotify data_destroy)
1680 {
1681 rb_entry_view_insert_column_custom (view, column, title, key, sort_func, data, data_destroy, -1);
1682 }
1683
1684 /**
1685 * rb_entry_view_insert_column_custom:
1686 * @view: a #RBEntryView
1687 * @column: (transfer full): a #GtkTreeViewColumn to append
1688 * @title: title for the column (translated)
1689 * @key: sort key for the column (not translated)
1690 * @sort_func: comparison function to use for sorting on the column
1691 * @data: (closure) (scope notified): data to pass to the sort function
1692 * @data_destroy: function to use to destroy the sort data
1693 * @position: position at which to insert the column (-1 to insert at the end)
1694 *
1695 * Inserts a custom column at the specified position.
1696 */
1697 void
1698 rb_entry_view_insert_column_custom (RBEntryView *view,
1699 GtkTreeViewColumn *column,
1700 const char *title,
1701 const char *key,
1702 GCompareDataFunc sort_func,
1703 gpointer data,
1704 GDestroyNotify data_destroy,
1705 gint position)
1706 {
1707 struct RBEntryViewColumnSortData *sortdata;
1708
1709 gtk_tree_view_column_set_title (column, title);
1710 gtk_tree_view_column_set_reorderable (column, FALSE);
1711
1712
1713 g_object_set_data_full (G_OBJECT (column), "rb-entry-view-key",
1714 g_strdup (key), g_free);
1715
1716 rb_debug ("appending column: %p (title: %s, key: %s)", column, title, key);
1717
1718 gtk_tree_view_insert_column (GTK_TREE_VIEW (view->priv->treeview), column, position);
1719
1720 if (sort_func != NULL) {
1721 sortdata = g_new (struct RBEntryViewColumnSortData, 1);
1722 sortdata->func = (GCompareDataFunc) sort_func;
1723 sortdata->data = data;
1724 sortdata->data_destroy = data_destroy;
1725 g_hash_table_insert (view->priv->column_sort_data_map, column, sortdata);
1726
1727 g_signal_connect_object (column, "clicked",
1728 G_CALLBACK (rb_entry_view_column_clicked_cb),
1729 view, 0);
1730 }
1731 g_hash_table_insert (view->priv->column_key_map, g_strdup (key), column);
1732
1733 rb_entry_view_sync_columns_visible (view);
1734 rb_entry_view_sync_sorting (view);
1735 }
1736
1737 /**
1738 * rb_entry_view_set_columns_clickable:
1739 * @view: a #RBEntryView
1740 * @clickable: if TRUE, sortable columns will be made clickable
1741 *
1742 * Makes the headers for sortable columns (those for which a sort function was
1743 * provided) clickable, so the user can set the sort order.
1744 */
1745 void
1746 rb_entry_view_set_columns_clickable (RBEntryView *view,
1747 gboolean clickable)
1748 {
1749 GList *columns, *tem;
1750
1751 columns = gtk_tree_view_get_columns (GTK_TREE_VIEW (view->priv->treeview));
1752 for (tem = columns; tem; tem = tem->next) {
1753 /* only columns we can sort on should be clickable */
1754 GtkTreeViewColumn *column = (GtkTreeViewColumn *) tem->data;
1755 if (g_hash_table_lookup (view->priv->column_sort_data_map, column) != NULL)
1756 gtk_tree_view_column_set_clickable (tem->data, clickable);
1757 }
1758 g_list_free (columns);
1759 }
1760
1761 static void
1762 rb_entry_view_constructed (GObject *object)
1763 {
1764 RBEntryView *view;
1765
1766 RB_CHAIN_GOBJECT_METHOD (rb_entry_view_parent_class, constructed, object);
1767
1768 view = RB_ENTRY_VIEW (object);
1769
1770 view->priv->treeview = GTK_WIDGET (gtk_tree_view_new ());
1771 gtk_tree_view_set_fixed_height_mode (GTK_TREE_VIEW (view->priv->treeview), TRUE);
1772
1773 gtk_tree_view_set_search_equal_func (GTK_TREE_VIEW (view->priv->treeview),
1774 type_ahead_search_func,
1775 view, NULL);
1776
1777 g_signal_connect_object (view->priv->treeview,
1778 "button_press_event",
1779 G_CALLBACK (rb_entry_view_button_press_cb),
1780 view,
1781 0);
1782 g_signal_connect_object (view->priv->treeview,
1783 "row_activated",
1784 G_CALLBACK (rb_entry_view_row_activated_cb),
1785 view,
1786 0);
1787 g_signal_connect_object (view->priv->treeview,
1788 "popup_menu",
1789 G_CALLBACK (rb_entry_view_popup_menu_cb),
1790 view,
1791 0);
1792 view->priv->selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view->priv->treeview));
1793 g_signal_connect_object (view->priv->selection,
1794 "changed",
1795 G_CALLBACK (rb_entry_view_selection_changed_cb),
1796 view,
1797 0);
1798
1799 gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (view->priv->treeview), TRUE);
1800 gtk_tree_view_set_rules_hint (GTK_TREE_VIEW (view->priv->treeview), TRUE);
1801 gtk_tree_selection_set_mode (view->priv->selection, GTK_SELECTION_MULTIPLE);
1802
1803 if (view->priv->is_drag_source) {
1804 rb_tree_dnd_add_drag_source_support (GTK_TREE_VIEW (view->priv->treeview),
1805 GDK_BUTTON1_MASK,
1806 rb_entry_view_drag_types,
1807 G_N_ELEMENTS (rb_entry_view_drag_types),
1808 GDK_ACTION_COPY);
1809 }
1810
1811 if (view->priv->is_drag_dest) {
1812 rb_tree_dnd_add_drag_dest_support (GTK_TREE_VIEW (view->priv->treeview),
1813 RB_TREE_DEST_CAN_DROP_BETWEEN | RB_TREE_DEST_EMPTY_VIEW_DROP,
1814 rb_entry_view_drag_types,
1815 G_N_ELEMENTS (rb_entry_view_drag_types),
1816 GDK_ACTION_COPY | GDK_ACTION_MOVE);
1817 }
1818
1819 gtk_container_add (GTK_CONTAINER (view), view->priv->treeview);
1820
1821 {
1822 GtkTreeViewColumn *column;
1823 GtkCellRenderer *renderer;
1824 GtkWidget *image_widget;
1825 gint width;
1826
1827 /* Playing icon column */
1828 column = GTK_TREE_VIEW_COLUMN (gtk_tree_view_column_new ());
1829 renderer = rb_cell_renderer_pixbuf_new ();
1830 g_object_set (renderer, "stock-size", GTK_ICON_SIZE_MENU, NULL);
1831 gtk_tree_view_column_pack_start (column, renderer, TRUE);
1832 gtk_tree_view_column_set_cell_data_func (column, renderer,
1833 (GtkTreeCellDataFunc)
1834 rb_entry_view_playing_cell_data_func,
1835 view,
1836 NULL);
1837
1838 image_widget = gtk_image_new_from_icon_name ("audio-volume-high", GTK_ICON_SIZE_MENU);
1839 gtk_tree_view_column_set_widget (column, image_widget);
1840 gtk_widget_show (image_widget);
1841
1842 gtk_tree_view_column_set_sizing (column, GTK_TREE_VIEW_COLUMN_FIXED);
1843 gtk_tree_view_column_set_clickable (column, FALSE);
1844 gtk_icon_size_lookup (GTK_ICON_SIZE_MENU, &width, NULL);
1845 gtk_tree_view_column_set_fixed_width (column, width + 5);
1846 gtk_tree_view_append_column (GTK_TREE_VIEW (view->priv->treeview), column);
1847 g_signal_connect_swapped (renderer,
1848 "pixbuf-clicked",
1849 G_CALLBACK (rb_entry_view_pixbuf_clicked_cb),
1850 view);
1851
1852 gtk_widget_set_tooltip_text (gtk_tree_view_column_get_widget (column),
1853 _("Now Playing"));
1854 }
1855
1856 {
1857 RhythmDBQueryModel *query_model;
1858 query_model = rhythmdb_query_model_new_empty (view->priv->db);
1859 rb_entry_view_set_model (view, RHYTHMDB_QUERY_MODEL (query_model));
1860 g_object_unref (query_model);
1861 }
1862 }
1863
1864 static void
1865 rb_entry_view_rated_cb (RBCellRendererRating *cellrating,
1866 const char *path_string,
1867 double rating,
1868 RBEntryView *view)
1869 {
1870 GtkTreePath *path;
1871 RhythmDBEntry *entry;
1872 GValue value = { 0, };
1873
1874 g_return_if_fail (rating >= 0 && rating <= 5 );
1875 g_return_if_fail (path_string != NULL);
1876
1877 path = gtk_tree_path_new_from_string (path_string);
1878 entry = rhythmdb_query_model_tree_path_to_entry (view->priv->model, path);
1879 gtk_tree_path_free (path);
1880
1881 g_value_init (&value, G_TYPE_DOUBLE);
1882 g_value_set_double (&value, rating);
1883 rhythmdb_entry_set (view->priv->db, entry, RHYTHMDB_PROP_RATING, &value);
1884 g_value_unset (&value);
1885
1886 rhythmdb_commit (view->priv->db);
1887
1888 rhythmdb_entry_unref (entry);
1889 }
1890
1891 static void
1892 rb_entry_view_pixbuf_clicked_cb (RBEntryView *view,
1893 const char *path_string,
1894 RBCellRendererPixbuf *cellpixbuf)
1895 {
1896 GtkTreePath *path;
1897 RhythmDBEntry *entry;
1898 const gchar *error;
1899
1900 g_return_if_fail (path_string != NULL);
1901
1902 path = gtk_tree_path_new_from_string (path_string);
1903 entry = rhythmdb_query_model_tree_path_to_entry (view->priv->model, path);
1904
1905 gtk_tree_path_free (path);
1906
1907 error = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_PLAYBACK_ERROR);
1908 if (error) {
1909 rb_error_dialog (NULL, _("Playback Error"), "%s", error);
1910 }
1911
1912 rhythmdb_entry_unref (entry);
1913 }
1914
1915 static void
1916 rb_entry_view_playing_song_changed (RBShellPlayer *player,
1917 RhythmDBEntry *entry,
1918 RBEntryView *view)
1919 {
1920 gboolean realized, visible;
1921 GtkTreeIter iter;
1922
1923 g_return_if_fail (RB_IS_ENTRY_VIEW (view));
1924
1925 if (view->priv->playing_entry != NULL) {
1926 if (view->priv->playing_state != RB_ENTRY_VIEW_NOT_PLAYING)
1927 rb_entry_view_emit_row_changed (view, view->priv->playing_entry);
1928 g_object_unref (view->priv->playing_model);
1929 }
1930
1931 view->priv->playing_entry = entry;
1932 view->priv->playing_model = view->priv->model;
1933 g_object_ref (view->priv->playing_model);
1934
1935 if (view->priv->playing_state != RB_ENTRY_VIEW_NOT_PLAYING) {
1936 if (view->priv->playing_entry != NULL) {
1937 view->priv->playing_entry_in_view =
1938 rb_entry_view_emit_row_changed (view, view->priv->playing_entry);
1939 }
1940
1941 if (view->priv->playing_entry
1942 && view->priv->playing_entry_in_view) {
1943 rb_entry_view_entry_is_visible (view, view->priv->playing_entry,
1944 &realized, &visible, &iter);
1945 if (realized && !visible)
1946 rb_entry_view_scroll_to_iter (view, &iter);
1947 }
1948 }
1949 }
1950
1951 static gboolean
1952 harvest_entries (GtkTreeModel *model,
1953 GtkTreePath *path,
1954 GtkTreeIter *iter,
1955 GList **list)
1956 {
1957 RhythmDBEntry *entry;
1958
1959 gtk_tree_model_get (model, iter, 0, &entry, -1);
1960
1961 *list = g_list_prepend (*list, entry);
1962
1963 return FALSE;
1964 }
1965
1966 /**
1967 * rb_entry_view_get_selected_entries:
1968 * @view: a #RBEntryView
1969 *
1970 * Gathers the selected entries from the view.
1971 *
1972 * Return value: (element-type RhythmDBEntry) (transfer full): a #GList of
1973 * selected entries in the view.
1974 */
1975 GList *
1976 rb_entry_view_get_selected_entries (RBEntryView *view)
1977 {
1978 GList *list = NULL;
1979
1980 gtk_tree_selection_selected_foreach (view->priv->selection,
1981 (GtkTreeSelectionForeachFunc) harvest_entries,
1982 (gpointer) &list);
1983
1984 list = g_list_reverse (list);
1985 return list;
1986 }
1987
1988 static gboolean
1989 rb_entry_view_button_press_cb (GtkTreeView *treeview,
1990 GdkEventButton *event,
1991 RBEntryView *view)
1992 {
1993 if (event->button == 3) {
1994 GtkTreePath *path;
1995 RhythmDBEntry *entry;
1996
1997 gtk_tree_view_get_path_at_pos (treeview, event->x, event->y, &path, NULL, NULL, NULL);
1998 if (path != NULL) {
1999 GList *selected;
2000 entry = rhythmdb_query_model_tree_path_to_entry (view->priv->model, path);
2001
2002 selected = rb_entry_view_get_selected_entries (view);
2003
2004 if (!g_list_find (selected, entry))
2005 rb_entry_view_select_entry (view, entry);
2006
2007 g_list_free (selected);
2008
2009 rhythmdb_entry_unref (entry);
2010 }
2011 g_signal_emit (G_OBJECT (view), rb_entry_view_signals[SHOW_POPUP], 0, (path != NULL));
2012 return TRUE;
2013 }
2014
2015 return FALSE;
2016 }
2017
2018 static gboolean
2019 rb_entry_view_popup_menu_cb (GtkTreeView *treeview,
2020 RBEntryView *view)
2021 {
2022 if (gtk_tree_selection_count_selected_rows (gtk_tree_view_get_selection (treeview)) == 0)
2023 return FALSE;
2024
2025 g_signal_emit (G_OBJECT (view), rb_entry_view_signals[SHOW_POPUP], 0);
2026 return TRUE;
2027 }
2028
2029 static gboolean
2030 rb_entry_view_emit_selection_changed (RBEntryView *view)
2031 {
2032 gboolean available;
2033 gint sel_count;
2034
2035 GDK_THREADS_ENTER ();
2036 sel_count = gtk_tree_selection_count_selected_rows (view->priv->selection);
2037 available = (sel_count > 0);
2038
2039 if (available != view->priv->have_selection) {
2040 gint entry_count;
2041
2042 entry_count = gtk_tree_model_iter_n_children (GTK_TREE_MODEL (view->priv->model), NULL);
2043 view->priv->have_complete_selection = (sel_count == entry_count);
2044
2045 view->priv->have_selection = available;
2046
2047 g_signal_emit (G_OBJECT (view), rb_entry_view_signals[HAVE_SEL_CHANGED], 0, available);
2048 }
2049
2050 view->priv->selection_changed_id = 0;
2051 g_signal_emit (G_OBJECT (view), rb_entry_view_signals[SELECTION_CHANGED], 0);
2052
2053 GDK_THREADS_LEAVE ();
2054 return FALSE;
2055 }
2056
2057 static void
2058 rb_entry_view_selection_changed_cb (GtkTreeSelection *selection,
2059 RBEntryView *view)
2060 {
2061 if (view->priv->selection_changed_id == 0)
2062 view->priv->selection_changed_id = g_idle_add ((GSourceFunc)rb_entry_view_emit_selection_changed, view);
2063 }
2064
2065 /**
2066 * rb_entry_view_have_selection:
2067 * @view: a #RBEntryView
2068 *
2069 * Determines whether there is an active selection in the view.
2070 *
2071 * Return value: TRUE if one or more rows are selected
2072 */
2073 gboolean
2074 rb_entry_view_have_selection (RBEntryView *view)
2075 {
2076 return view->priv->have_selection;
2077 }
2078
2079 /**
2080 * rb_entry_view_have_complete_selection:
2081 * @view: a #RBEntryView
2082 *
2083 * Determines whether all entries in the view are selected.
2084 *
2085 * Return value: TRUE if all rows in the view are selected
2086 */
2087 gboolean
2088 rb_entry_view_have_complete_selection (RBEntryView *view)
2089 {
2090 return view->priv->have_complete_selection;
2091 }
2092
2093 static void
2094 rb_entry_view_row_activated_cb (GtkTreeView *treeview,
2095 GtkTreePath *path,
2096 GtkTreeViewColumn *column,
2097 RBEntryView *view)
2098 {
2099 RhythmDBEntry *entry;
2100
2101 rb_debug ("row activated");
2102
2103 entry = rhythmdb_query_model_tree_path_to_entry (view->priv->model, path);
2104
2105 rb_debug ("emitting entry activated");
2106 g_signal_emit (G_OBJECT (view), rb_entry_view_signals[ENTRY_ACTIVATED], 0, entry);
2107
2108 rhythmdb_entry_unref (entry);
2109 }
2110
2111 static void
2112 rb_entry_view_row_inserted_cb (GtkTreeModel *model,
2113 GtkTreePath *path,
2114 GtkTreeIter *iter,
2115 RBEntryView *view)
2116 {
2117 RhythmDBEntry *entry = rhythmdb_query_model_tree_path_to_entry (RHYTHMDB_QUERY_MODEL (model), path);
2118
2119 rb_debug ("row added");
2120 g_signal_emit (G_OBJECT (view), rb_entry_view_signals[ENTRY_ADDED], 0, entry);
2121 rhythmdb_entry_unref (entry);
2122 }
2123
2124 static void
2125 rb_entry_view_row_deleted_cb (GtkTreeModel *model,
2126 GtkTreePath *path,
2127 RBEntryView *view)
2128 {
2129 RhythmDBEntry *entry = rhythmdb_query_model_tree_path_to_entry (RHYTHMDB_QUERY_MODEL (model), path);
2130
2131 rb_debug ("row deleted");
2132 g_signal_emit (G_OBJECT (view), rb_entry_view_signals[ENTRY_DELETED], 0, entry);
2133 rhythmdb_entry_unref (entry);
2134 }
2135
2136 static void
2137 rb_entry_view_rows_reordered_cb (GtkTreeModel *model,
2138 GtkTreePath *path,
2139 GtkTreeIter *iter,
2140 gint *order,
2141 RBEntryView *view)
2142 {
2143 GList *selected_rows;
2144 GList *i;
2145 gint model_size;
2146 gboolean scrolled = FALSE;
2147
2148 rb_debug ("rows reordered");
2149
2150 model_size = gtk_tree_model_iter_n_children (model, NULL);
2151
2152 /* check if a selected row was moved; if so, we'll
2153 * need to move the selection too.
2154 */
2155 selected_rows = gtk_tree_selection_get_selected_rows (view->priv->selection,
2156 NULL);
2157 for (i = selected_rows; i != NULL; i = i->next) {
2158 GtkTreePath *path = (GtkTreePath *)i->data;
2159 gint index = gtk_tree_path_get_indices (path)[0];
2160 gint newindex;
2161 if (order[index] != index) {
2162 GtkTreePath *newpath;
2163 gtk_tree_selection_unselect_path (view->priv->selection, path);
2164
2165 for (newindex = 0; newindex < model_size; newindex++) {
2166 if (order[newindex] == index) {
2167 newpath = gtk_tree_path_new_from_indices (newindex, -1);
2168 gtk_tree_selection_select_path (view->priv->selection, newpath);
2169 if (!scrolled) {
2170 GtkTreeViewColumn *col;
2171 GtkTreeView *treeview = GTK_TREE_VIEW (view->priv->treeview);
2172
2173 col = gtk_tree_view_get_column (treeview, 0);
2174 gtk_tree_view_scroll_to_cell (treeview, newpath, col, TRUE, 0.5, 0.0);
2175 scrolled = TRUE;
2176 }
2177 gtk_tree_path_free (newpath);
2178 break;
2179 }
2180 }
2181
2182 }
2183 }
2184
2185 g_list_foreach (selected_rows, (GFunc) gtk_tree_path_free, NULL);
2186 g_list_free (selected_rows);
2187
2188 gtk_widget_queue_draw (GTK_WIDGET (view));
2189 }
2190
2191 /**
2192 * rb_entry_view_select_all:
2193 * @view: a #RBEntryView
2194 *
2195 * Selects all rows in the view
2196 */
2197 void
2198 rb_entry_view_select_all (RBEntryView *view)
2199 {
2200 gtk_tree_selection_select_all (view->priv->selection);
2201 }
2202
2203 /**
2204 * rb_entry_view_select_none:
2205 * @view: a #RBEntryView
2206 *
2207 * Deselects all rows in the view.
2208 */
2209 void
2210 rb_entry_view_select_none (RBEntryView *view)
2211 {
2212 gtk_tree_selection_unselect_all (view->priv->selection);
2213 }
2214
2215 /**
2216 * rb_entry_view_select_entry:
2217 * @view: a #RBEntryView
2218 * @entry: a #RhythmDBEntry to select
2219 *
2220 * If the specified entry is present in the view, it is added
2221 * to the selection.
2222 */
2223 void
2224 rb_entry_view_select_entry (RBEntryView *view,
2225 RhythmDBEntry *entry)
2226 {
2227 GtkTreeIter iter;
2228
2229 if (entry == NULL)
2230 return;
2231
2232 rb_entry_view_select_none (view);
2233
2234 if (rhythmdb_query_model_entry_to_iter (view->priv->model,
2235 entry, &iter)) {
2236 gtk_tree_selection_select_iter (view->priv->selection, &iter);
2237 }
2238 }
2239
2240 /**
2241 * rb_entry_view_scroll_to_entry:
2242 * @view: a #RBEntryView
2243 * @entry: a #RhythmDBEntry to scroll to
2244 *
2245 * If the specified entry is present in the view, the view will be
2246 * scrolled so that the entry is visible.
2247 */
2248 void
2249 rb_entry_view_scroll_to_entry (RBEntryView *view,
2250 RhythmDBEntry *entry)
2251 {
2252 GtkTreeIter iter;
2253
2254 if (rhythmdb_query_model_entry_to_iter (view->priv->model,
2255 entry, &iter)) {
2256 rb_entry_view_scroll_to_iter (view, &iter);
2257 }
2258 }
2259
2260 static void
2261 rb_entry_view_scroll_to_iter (RBEntryView *view,
2262 GtkTreeIter *iter)
2263 {
2264 GtkTreePath *path;
2265
2266 /* It's possible to we can be asked to scroll the play queue's entry
2267 * view to the playing entry before the view has ever been displayed.
2268 * This will result in gtk+ warnings, so we avoid it in this case.
2269 */
2270 if (!gtk_widget_get_realized (GTK_WIDGET (view)))
2271 return;
2272
2273 path = gtk_tree_model_get_path (GTK_TREE_MODEL (view->priv->model), iter);
2274 gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (view->priv->treeview), path,
2275 gtk_tree_view_get_column (GTK_TREE_VIEW (view->priv->treeview), 0),
2276 TRUE, 0.5, 0.0);
2277 gtk_tree_view_set_cursor (GTK_TREE_VIEW (view->priv->treeview), path,
2278 gtk_tree_view_get_column (GTK_TREE_VIEW (view->priv->treeview), 0), FALSE);
2279
2280 gtk_tree_path_free (path);
2281 }
2282
2283 /**
2284 * rb_entry_view_get_entry_visible:
2285 * @view: a #RBEntryView
2286 * @entry: a #RhythmDBEntry to check
2287 *
2288 * Determines whether a specified entry is present in the view
2289 * and is currently visible.
2290 *
2291 * Return value: TRUE if the entry is visible
2292 */
2293 gboolean
2294 rb_entry_view_get_entry_visible (RBEntryView *view,
2295 RhythmDBEntry *entry)
2296 {
2297 GtkTreeIter unused;
2298 gboolean realized, visible;
2299
2300 if (view->priv->playing_model != view->priv->model)
2301 return FALSE;
2302
2303 rb_entry_view_entry_is_visible (view, entry, &realized, &visible,
2304 &unused);
2305 return realized && visible;
2306 }
2307
2308 /**
2309 * rb_entry_view_get_entry_contained:
2310 * @view: a #RBEntryView
2311 * @entry: a #RhythmDBEntry to check
2312 *
2313 * Determines whether a specified entry is present in the view.
2314 *
2315 * Return value: TRUE if the entry is present in the view
2316 */
2317 gboolean
2318 rb_entry_view_get_entry_contained (RBEntryView *view,
2319 RhythmDBEntry *entry)
2320 {
2321 GtkTreeIter unused;
2322
2323 return rhythmdb_query_model_entry_to_iter (view->priv->model,
2324 entry, &unused);
2325 }
2326
2327 static void
2328 rb_entry_view_entry_is_visible (RBEntryView *view,
2329 RhythmDBEntry *entry,
2330 gboolean *realized,
2331 gboolean *visible,
2332 GtkTreeIter *iter)
2333 {
2334 GtkTreePath *path;
2335 GdkRectangle rect;
2336
2337 *realized = FALSE;
2338 *visible = FALSE;
2339
2340 g_return_if_fail (entry != NULL);
2341
2342 if (!gtk_widget_get_realized (GTK_WIDGET (view)))
2343 return;
2344
2345 *realized = TRUE;
2346
2347 if (!rhythmdb_query_model_entry_to_iter (view->priv->model,
2348 entry, iter))
2349 return;
2350
2351 path = gtk_tree_model_get_path (GTK_TREE_MODEL (view->priv->model), iter);
2352 gtk_tree_view_get_cell_area (GTK_TREE_VIEW (view->priv->treeview),
2353 path,
2354 gtk_tree_view_get_column (GTK_TREE_VIEW (view->priv->treeview), 0),
2355 &rect);
2356
2357 gtk_tree_path_free (path);
2358
2359 *visible = (rect.y != 0 && rect.height != 0);
2360 }
2361
2362 /**
2363 * rb_entry_view_enable_drag_source:
2364 * @view: a #RBEntryView
2365 * @targets: an array of #GtkTargetEntry structures defining the drag data targets
2366 * @n_targets: the number of entries in the target array
2367 *
2368 * Enables the entry view to act as a data source for drag an drop operations,
2369 * using a specified set of data targets.
2370 */
2371 void
2372 rb_entry_view_enable_drag_source (RBEntryView *view,
2373 const GtkTargetEntry *targets,
2374 int n_targets)
2375 {
2376 g_return_if_fail (view != NULL);
2377
2378 rb_tree_dnd_add_drag_source_support (GTK_TREE_VIEW (view->priv->treeview),
2379 GDK_BUTTON1_MASK | GDK_BUTTON3_MASK,
2380 targets, n_targets, GDK_ACTION_COPY);
2381 }
2382
2383 static void
2384 set_column_visibility (guint propid,
2385 GtkTreeViewColumn *column,
2386 GList *visible_props)
2387 {
2388 gboolean visible;
2389
2390 if (g_object_get_qdata (G_OBJECT (column),
2391 rb_entry_view_column_always_visible) == GINT_TO_POINTER (1))
2392 visible = TRUE;
2393 else
2394 visible = (g_list_find (visible_props, GINT_TO_POINTER (propid)) != NULL);
2395
2396 gtk_tree_view_column_set_visible (column, visible);
2397 }
2398
2399 static void
2400 rb_entry_view_sync_columns_visible (RBEntryView *view)
2401 {
2402 GList *visible_properties = NULL;
2403
2404 g_return_if_fail (view != NULL);
2405
2406 if (view->priv->visible_columns != NULL) {
2407 int i;
2408 for (i = 0; view->priv->visible_columns[i] != NULL && *(view->priv->visible_columns[i]); i++) {
2409 int value = rhythmdb_propid_from_nice_elt_name (view->priv->db, (const xmlChar *)view->priv->visible_columns[i]);
2410 rb_debug ("visible columns: %s => %d", view->priv->visible_columns[i], value);
2411
2412 if ((value >= 0) && (value < RHYTHMDB_NUM_PROPERTIES))
2413 visible_properties = g_list_prepend (visible_properties, GINT_TO_POINTER (value));
2414 }
2415 }
2416
2417 g_hash_table_foreach (view->priv->propid_column_map, (GHFunc) set_column_visibility, visible_properties);
2418 g_list_free (visible_properties);
2419 }
2420
2421 /**
2422 * rb_entry_view_set_state:
2423 * @view: a #RBEntryView
2424 * @state: the new playing entry state
2425 *
2426 * Sets the icon to be drawn in the 'playing' column next to the
2427 * current playing entry. RB_ENTRY_VIEW_PLAYING and RB_ENTRY_VIEW_PAUSED
2428 * should be used when the source containing the entry view is playing,
2429 * and RB_ENTRY_VIEW_NOT_PLAYING otherwise.
2430 */
2431 void
2432 rb_entry_view_set_state (RBEntryView *view,
2433 RBEntryViewState state)
2434 {
2435 g_return_if_fail (RB_IS_ENTRY_VIEW (view));
2436 g_object_set (view, "playing-state", state, NULL);
2437 }
2438
2439 static void
2440 rb_entry_view_grab_focus (GtkWidget *widget)
2441 {
2442 RBEntryView *view = RB_ENTRY_VIEW (widget);
2443
2444 gtk_widget_grab_focus (GTK_WIDGET (view->priv->treeview));
2445 }
2446
2447 static gboolean
2448 rb_entry_view_emit_row_changed (RBEntryView *view,
2449 RhythmDBEntry *entry)
2450 {
2451 GtkTreeIter iter;
2452 GtkTreePath *path;
2453
2454 if (!rhythmdb_query_model_entry_to_iter (view->priv->model, entry, &iter))
2455 return FALSE;
2456
2457 path = gtk_tree_model_get_path (GTK_TREE_MODEL (view->priv->model),
2458 &iter);
2459 gtk_tree_model_row_changed (GTK_TREE_MODEL (view->priv->model),
2460 path, &iter);
2461 gtk_tree_path_free (path);
2462 return TRUE;
2463 }
2464
2465 /**
2466 * rb_entry_view_set_fixed_column_width:
2467 * @view: a #RBEntryView
2468 * @column: the column to set the width for
2469 * @renderer: a temporary cell renderer to use
2470 * @strings: (array zero-terminated=1): a NULL-terminated array of strings that will be displayed in the column
2471 *
2472 * Helper function for calling @rb_set_tree_view_column_fixed_width on
2473 * a column. This is important for performance reasons, as having the
2474 * tree view measure the strings in each of 20000 rows is very slow.
2475 */
2476 void
2477 rb_entry_view_set_fixed_column_width (RBEntryView *view,
2478 GtkTreeViewColumn *column,
2479 GtkCellRenderer *renderer,
2480 const gchar **strings)
2481 {
2482 rb_set_tree_view_column_fixed_width (view->priv->treeview,
2483 column,
2484 renderer,
2485 strings,
2486 5);
2487 }
2488
2489 /**
2490 * rb_entry_view_get_time_date_column_sample:
2491 *
2492 * Returns a sample string for use in columns displaying times
2493 * and dates in 'friendly' form (see @rb_utf_friendly_time).
2494 * For use with @rb_entry_view_set_fixed_column_width.
2495 *
2496 * Return value: sample date string
2497 */
2498 const char *
2499 rb_entry_view_get_time_date_column_sample ()
2500 {
2501 static const char *sample = NULL;
2502 if (sample == NULL) {
2503 time_t then;
2504
2505 /* A reasonable estimate of the widest friendly date
2506 is "Yesterday NN:NN PM" */
2507 then = time (NULL) - 86400;
2508 sample = rb_utf_friendly_time (then);
2509 }
2510
2511 return sample;
2512 }
2513
2514 /**
2515 * rb_entry_view_resort_model:
2516 * @view: a #RBEntryView to resort
2517 *
2518 * Resorts the entries in the entry view. Mostly to be used
2519 * when a new model is associated with the view.
2520 */
2521 void
2522 rb_entry_view_resort_model (RBEntryView *view)
2523 {
2524 struct RBEntryViewColumnSortData *sort_data;
2525
2526 if (view->priv->sorting_column == NULL) {
2527 rb_debug ("can't sort yet, the sorting column isn't here");
2528 return;
2529 }
2530
2531 sort_data = g_hash_table_lookup (view->priv->column_sort_data_map,
2532 view->priv->sorting_column);
2533 g_assert (sort_data);
2534
2535 rhythmdb_query_model_set_sort_order (view->priv->model,
2536 sort_data->func,
2537 sort_data->data,
2538 NULL,
2539 (view->priv->sorting_order == GTK_SORT_DESCENDING));
2540 }
2541
2542 /**
2543 * rb_entry_view_set_column_editable:
2544 * @view: a #RBEntryView
2545 * @column: a #RBEntryViewColumn to update
2546 * @editable: %TRUE to make the column editable, %FALSE otherwise
2547 *
2548 * Enables in-place editing of the values in a column.
2549 * The underlying %RhythmDBEntry is updated when editing is complete.
2550 */
2551 void
2552 rb_entry_view_set_column_editable (RBEntryView *view,
2553 RBEntryViewColumn column_type,
2554 gboolean editable)
2555 {
2556 GtkTreeViewColumn *column;
2557 GList *renderers;
2558
2559 column = rb_entry_view_get_column (view, column_type);
2560 if (column == NULL)
2561 return;
2562
2563 renderers = gtk_cell_layout_get_cells (GTK_CELL_LAYOUT (column));
2564 g_object_set (renderers->data, "editable", editable, NULL);
2565 g_list_free (renderers);
2566 }
2567
2568 /* This should really be standard. */
2569 #define ENUM_ENTRY(NAME, DESC) { NAME, "" #NAME "", DESC }
2570
2571 GType
2572 rb_entry_view_column_get_type (void)
2573 {
2574 static GType etype = 0;
2575
2576 if (etype == 0) {
2577 static const GEnumValue values[] = {
2578 ENUM_ENTRY (RB_ENTRY_VIEW_COL_TRACK_NUMBER, "track-number"),
2579 ENUM_ENTRY (RB_ENTRY_VIEW_COL_TITLE, "title"),
2580 ENUM_ENTRY (RB_ENTRY_VIEW_COL_ARTIST, "artist"),
2581 ENUM_ENTRY (RB_ENTRY_VIEW_COL_ALBUM, "album"),
2582 ENUM_ENTRY (RB_ENTRY_VIEW_COL_GENRE, "genre"),
2583 ENUM_ENTRY (RB_ENTRY_VIEW_COL_COMMENT, "comment"),
2584 ENUM_ENTRY (RB_ENTRY_VIEW_COL_DURATION, "duration"),
2585 ENUM_ENTRY (RB_ENTRY_VIEW_COL_QUALITY, "quality"),
2586 ENUM_ENTRY (RB_ENTRY_VIEW_COL_RATING, "rating"),
2587 ENUM_ENTRY (RB_ENTRY_VIEW_COL_PLAY_COUNT, "play-count"),
2588 ENUM_ENTRY (RB_ENTRY_VIEW_COL_YEAR, "year"),
2589 ENUM_ENTRY (RB_ENTRY_VIEW_COL_LAST_PLAYED, "last-played"),
2590 ENUM_ENTRY (RB_ENTRY_VIEW_COL_FIRST_SEEN, "first-seen"),
2591 ENUM_ENTRY (RB_ENTRY_VIEW_COL_LAST_SEEN, "last-seen"),
2592 ENUM_ENTRY (RB_ENTRY_VIEW_COL_LOCATION, "location"),
2593 ENUM_ENTRY (RB_ENTRY_VIEW_COL_BPM, "bpm"),
2594 ENUM_ENTRY (RB_ENTRY_VIEW_COL_ERROR, "error"),
2595 { 0, 0, 0 }
2596 };
2597
2598 etype = g_enum_register_static ("RBEntryViewColumn", values);
2599 }
2600
2601 return etype;
2602 }
2603
2604 GType
2605 rb_entry_view_state_get_type (void)
2606 {
2607 static GType etype = 0;
2608
2609 if (etype == 0) {
2610 static const GEnumValue values[] = {
2611 ENUM_ENTRY (RB_ENTRY_VIEW_NOT_PLAYING, "not-playing"),
2612 ENUM_ENTRY (RB_ENTRY_VIEW_PLAYING, "playing"),
2613 ENUM_ENTRY (RB_ENTRY_VIEW_PAUSED, "paused"),
2614 { 0, 0, 0 }
2615 };
2616
2617 etype = g_enum_register_static ("RBEntryViewState", values);
2618 }
2619
2620 return etype;
2621 }