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