1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
3 * arch-tag: Implementation of widget to display RhythmDB entries
5 * Copyright (C) 2003 Colin Walters <walters@verbum.org>
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
30 #include <glib/gi18n.h>
32 #include <libgnomevfs/gnome-vfs-utils.h>
34 #include "rb-tree-dnd.h"
35 #include "rb-entry-view.h"
36 #include "rb-dialog.h"
40 #include "rhythmdb-query-model.h"
41 #include "rb-cell-renderer-pixbuf.h"
42 #include "rb-cell-renderer-rating.h"
43 #include "rb-stock-icons.h"
44 #include "rb-preferences.h"
45 #include "eel-gconf-extensions.h"
46 #include "rb-shell-player.h"
47 #include "rb-cut-and-paste-code.h"
49 static const GtkTargetEntry rb_entry_view_drag_types
[] = {
50 { "application/x-rhythmbox-entry", 0, 0 },
51 { "text/uri-list", 0, 1 }
54 struct RBEntryViewColumnSortData
56 GCompareDataFunc func
;
60 static void rb_entry_view_class_init (RBEntryViewClass
*klass
);
61 static void rb_entry_view_init (RBEntryView
*view
);
62 static GObject
*rb_entry_view_constructor (GType type
, guint n_construct_properties
,
63 GObjectConstructParam
*construct_properties
);
64 static void rb_entry_view_dispose (GObject
*object
);
65 static void rb_entry_view_finalize (GObject
*object
);
66 static void rb_entry_view_set_property (GObject
*object
,
70 static void rb_entry_view_get_property (GObject
*object
,
74 static void rb_entry_view_selection_changed_cb (GtkTreeSelection
*selection
,
76 static void rb_entry_view_grab_focus (GtkWidget
*widget
);
77 static void rb_entry_view_row_activated_cb (GtkTreeView
*treeview
,
79 GtkTreeViewColumn
*column
,
81 static void rb_entry_view_row_inserted_cb (GtkTreeModel
*model
,
85 static void rb_entry_view_row_deleted_cb (GtkTreeModel
*model
,
88 static void rb_entry_view_rows_reordered_cb (GtkTreeModel
*model
,
93 static void rb_entry_view_sync_columns_visible (RBEntryView
*view
);
94 static void rb_entry_view_columns_config_changed_cb (GConfClient
* client
,
98 static void rb_entry_view_sort_key_changed_cb (GConfClient
* client
,
102 static void rb_entry_view_rated_cb (RBCellRendererRating
*cellrating
,
106 static void rb_entry_view_pixbuf_clicked_cb (RBEntryView
*view
,
108 RBCellRendererPixbuf
*cellpixbuf
);
109 static gboolean
rb_entry_view_button_press_cb (GtkTreeView
*treeview
,
110 GdkEventButton
*event
,
112 static gboolean
rb_entry_view_popup_menu_cb (GtkTreeView
*treeview
,
114 static void rb_entry_view_entry_is_visible (RBEntryView
*view
, RhythmDBEntry
*entry
,
115 gboolean
*realized
, gboolean
*visible
,
117 static void rb_entry_view_scroll_to_iter (RBEntryView
*view
,
119 static gboolean
rb_entry_view_emit_row_changed (RBEntryView
*view
,
120 RhythmDBEntry
*entry
);
121 static void rb_entry_view_playing_song_changed (RBShellPlayer
*player
,
122 RhythmDBEntry
*entry
,
125 struct RBEntryViewPrivate
128 RBShellPlayer
*shell_player
;
130 RhythmDBQueryModel
*model
;
133 GtkTreeSelection
*selection
;
135 RBEntryViewState playing_state
;
136 RhythmDBQueryModel
*playing_model
;
137 RhythmDBEntry
*playing_entry
;
138 gboolean playing_entry_in_view
;
139 guint selection_changed_id
;
141 gboolean is_drag_source
;
142 gboolean is_drag_dest
;
144 GdkPixbuf
*playing_pixbuf
;
145 GdkPixbuf
*paused_pixbuf
;
146 GdkPixbuf
*error_pixbuf
;
149 guint sorting_gconf_notification_id
;
150 GtkTreeViewColumn
*sorting_column
;
152 char *sorting_column_name
;
154 gboolean have_selection
, have_complete_selection
;
156 GHashTable
*column_key_map
;
158 guint gconf_notification_id
;
159 GHashTable
*propid_column_map
;
160 GHashTable
*column_sort_data_map
;
163 #define RB_ENTRY_VIEW_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), RB_TYPE_ENTRY_VIEW, RBEntryViewPrivate))
190 G_DEFINE_TYPE (RBEntryView
, rb_entry_view
, GTK_TYPE_SCROLLED_WINDOW
)
192 static guint rb_entry_view_signals
[LAST_SIGNAL
] = { 0 };
194 static GQuark rb_entry_view_column_always_visible
;
197 type_ahead_search_func (GtkTreeModel
*model
,
201 gpointer search_data
)
203 RhythmDBEntry
*entry
;
205 const gchar
*entry_folded
;
208 gtk_tree_model_get (model
, iter
, 0, &entry
, -1);
209 folded
= rb_search_fold (key
);
210 entry_folded
= rhythmdb_entry_get_string (entry
, RHYTHMDB_PROP_TITLE_FOLDED
);
211 rhythmdb_entry_unref (entry
);
213 if (entry_folded
== NULL
|| folded
== NULL
)
216 res
= (strstr (entry_folded
, folded
) == NULL
);
223 rb_entry_view_class_init (RBEntryViewClass
*klass
)
225 GObjectClass
*object_class
= G_OBJECT_CLASS (klass
);
226 GtkWidgetClass
*widget_class
= GTK_WIDGET_CLASS (klass
);
228 object_class
->dispose
= rb_entry_view_dispose
;
229 object_class
->finalize
= rb_entry_view_finalize
;
230 object_class
->constructor
= rb_entry_view_constructor
;
232 object_class
->set_property
= rb_entry_view_set_property
;
233 object_class
->get_property
= rb_entry_view_get_property
;
235 widget_class
->grab_focus
= rb_entry_view_grab_focus
;
237 g_object_class_install_property (object_class
,
239 g_param_spec_object ("db",
243 G_PARAM_READWRITE
| G_PARAM_CONSTRUCT_ONLY
));
244 g_object_class_install_property (object_class
,
246 g_param_spec_object ("shell-player",
248 "RBShellPlayer object",
249 RB_TYPE_SHELL_PLAYER
,
250 G_PARAM_READWRITE
| G_PARAM_CONSTRUCT_ONLY
));
251 g_object_class_install_property (object_class
,
253 g_param_spec_object ("model",
254 "RhythmDBQueryModel",
255 "RhythmDBQueryModel",
256 RHYTHMDB_TYPE_QUERY_MODEL
,
258 g_object_class_install_property (object_class
,
260 g_param_spec_string ("sort-key",
264 G_PARAM_READWRITE
| G_PARAM_CONSTRUCT_ONLY
));
265 g_object_class_install_property (object_class
,
267 g_param_spec_boolean ("is-drag-source",
269 "whether or not this is a drag source",
271 G_PARAM_READWRITE
| G_PARAM_CONSTRUCT_ONLY
));
272 g_object_class_install_property (object_class
,
274 g_param_spec_boolean ("is-drag-dest",
276 "whether or not this is a drag dest",
278 G_PARAM_READWRITE
| G_PARAM_CONSTRUCT_ONLY
));
279 g_object_class_install_property (object_class
,
281 g_param_spec_int ("playing-state",
283 "playback state for this entry view",
284 RB_ENTRY_VIEW_NOT_PLAYING
,
285 RB_ENTRY_VIEW_PAUSED
,
286 RB_ENTRY_VIEW_NOT_PLAYING
,
288 rb_entry_view_signals
[ENTRY_ADDED
] =
289 g_signal_new ("entry-added",
290 G_OBJECT_CLASS_TYPE (object_class
),
292 G_STRUCT_OFFSET (RBEntryViewClass
, entry_added
),
294 g_cclosure_marshal_VOID__BOXED
,
297 RHYTHMDB_TYPE_ENTRY
);
298 rb_entry_view_signals
[ENTRY_DELETED
] =
299 g_signal_new ("entry-deleted",
300 G_OBJECT_CLASS_TYPE (object_class
),
302 G_STRUCT_OFFSET (RBEntryViewClass
, entry_deleted
),
304 g_cclosure_marshal_VOID__BOXED
,
307 RHYTHMDB_TYPE_ENTRY
);
308 rb_entry_view_signals
[ENTRIES_REPLACED
] =
309 g_signal_new ("entries-replaced",
310 G_OBJECT_CLASS_TYPE (object_class
),
312 G_STRUCT_OFFSET (RBEntryViewClass
, entries_replaced
),
314 g_cclosure_marshal_VOID__VOID
,
317 rb_entry_view_signals
[ENTRY_ACTIVATED
] =
318 g_signal_new ("entry-activated",
319 G_OBJECT_CLASS_TYPE (object_class
),
321 G_STRUCT_OFFSET (RBEntryViewClass
, entry_activated
),
323 g_cclosure_marshal_VOID__BOXED
,
326 RHYTHMDB_TYPE_ENTRY
);
327 rb_entry_view_signals
[SELECTION_CHANGED
] =
328 g_signal_new ("selection-changed",
329 G_OBJECT_CLASS_TYPE (object_class
),
331 G_STRUCT_OFFSET (RBEntryViewClass
, selection_changed
),
333 g_cclosure_marshal_VOID__VOID
,
336 rb_entry_view_signals
[SHOW_POPUP
] =
337 g_signal_new ("show_popup",
338 G_OBJECT_CLASS_TYPE (object_class
),
340 G_STRUCT_OFFSET (RBEntryViewClass
, show_popup
),
342 g_cclosure_marshal_VOID__BOOLEAN
,
346 rb_entry_view_signals
[HAVE_SEL_CHANGED
] =
347 g_signal_new ("have_selection_changed",
348 G_OBJECT_CLASS_TYPE (object_class
),
350 G_STRUCT_OFFSET (RBEntryViewClass
, have_selection_changed
),
352 g_cclosure_marshal_VOID__BOOLEAN
,
356 rb_entry_view_signals
[SORT_ORDER_CHANGED
] =
357 g_signal_new ("sort-order-changed",
358 G_OBJECT_CLASS_TYPE (object_class
),
360 G_STRUCT_OFFSET (RBEntryViewClass
, sort_order_changed
),
362 g_cclosure_marshal_VOID__VOID
,
366 g_type_class_add_private (klass
, sizeof (RBEntryViewPrivate
));
368 rb_entry_view_column_always_visible
= g_quark_from_static_string ("rb_entry_view_column_always_visible");
372 rb_entry_view_init (RBEntryView
*view
)
374 GtkIconTheme
*icon_theme
;
376 view
->priv
= RB_ENTRY_VIEW_GET_PRIVATE (view
);
378 icon_theme
= gtk_icon_theme_get_default ();
380 view
->priv
->playing_pixbuf
= gtk_icon_theme_load_icon (icon_theme
,
385 view
->priv
->paused_pixbuf
= gtk_icon_theme_load_icon (icon_theme
,
390 view
->priv
->error_pixbuf
= gtk_icon_theme_load_icon (icon_theme
,
391 "stock_dialog-error",
396 view
->priv
->propid_column_map
= g_hash_table_new (NULL
, NULL
);
397 view
->priv
->column_sort_data_map
= g_hash_table_new_full (NULL
, NULL
, NULL
, g_free
);
398 view
->priv
->column_key_map
= g_hash_table_new_full (g_str_hash
, g_str_equal
, g_free
, NULL
);
402 rb_entry_view_dispose (GObject
*object
)
406 g_return_if_fail (object
!= NULL
);
407 g_return_if_fail (RB_IS_ENTRY_VIEW (object
));
409 view
= RB_ENTRY_VIEW (object
);
411 g_return_if_fail (view
->priv
!= NULL
);
413 if (view
->priv
->gconf_notification_id
> 0) {
414 eel_gconf_notification_remove (view
->priv
->gconf_notification_id
);
415 view
->priv
->gconf_notification_id
= 0;
418 if (view
->priv
->sorting_gconf_notification_id
> 0) {
419 eel_gconf_notification_remove (view
->priv
->sorting_gconf_notification_id
);
420 view
->priv
->sorting_gconf_notification_id
= 0;
423 if (view
->priv
->selection_changed_id
> 0) {
424 g_source_remove (view
->priv
->selection_changed_id
);
425 view
->priv
->selection_changed_id
= 0;
428 if (view
->priv
->playing_pixbuf
!= NULL
) {
429 g_object_unref (view
->priv
->playing_pixbuf
);
430 view
->priv
->playing_pixbuf
= NULL
;
433 if (view
->priv
->paused_pixbuf
!= NULL
) {
434 g_object_unref (view
->priv
->paused_pixbuf
);
435 view
->priv
->paused_pixbuf
= NULL
;
438 if (view
->priv
->error_pixbuf
!= NULL
) {
439 g_object_unref (view
->priv
->error_pixbuf
);
440 view
->priv
->error_pixbuf
= NULL
;
443 if (view
->priv
->playing_model
!= NULL
) {
444 g_object_unref (view
->priv
->playing_model
);
445 view
->priv
->playing_model
= NULL
;
448 if (view
->priv
->model
!= NULL
) {
449 g_object_unref (view
->priv
->model
);
450 view
->priv
->model
= NULL
;
453 G_OBJECT_CLASS (rb_entry_view_parent_class
)->dispose (object
);
457 rb_entry_view_finalize (GObject
*object
)
461 g_return_if_fail (object
!= NULL
);
462 g_return_if_fail (RB_IS_ENTRY_VIEW (object
));
464 view
= RB_ENTRY_VIEW (object
);
466 g_return_if_fail (view
->priv
!= NULL
);
468 g_hash_table_destroy (view
->priv
->propid_column_map
);
469 g_hash_table_destroy (view
->priv
->column_sort_data_map
);
470 g_hash_table_destroy (view
->priv
->column_key_map
);
472 g_free (view
->priv
->sorting_key
);
473 g_free (view
->priv
->sorting_column_name
);
475 G_OBJECT_CLASS (rb_entry_view_parent_class
)->finalize (object
);
479 rb_entry_view_set_shell_player_internal (RBEntryView
*view
,
480 RBShellPlayer
*player
)
482 if (view
->priv
->shell_player
!= NULL
) {
483 g_signal_handlers_disconnect_by_func (view
->priv
->shell_player
,
484 G_CALLBACK (rb_entry_view_playing_song_changed
),
488 view
->priv
->shell_player
= player
;
490 g_signal_connect_object (view
->priv
->shell_player
,
491 "playing-song-changed",
492 G_CALLBACK (rb_entry_view_playing_song_changed
),
497 rb_entry_view_set_model_internal (RBEntryView
*view
,
498 RhythmDBQueryModel
*model
)
500 if (view
->priv
->model
!= NULL
) {
501 g_signal_handlers_disconnect_by_func (view
->priv
->model
,
502 G_CALLBACK (rb_entry_view_row_inserted_cb
),
504 g_signal_handlers_disconnect_by_func (view
->priv
->model
,
505 G_CALLBACK (rb_entry_view_row_deleted_cb
),
507 g_signal_handlers_disconnect_by_func (view
->priv
->model
,
508 G_CALLBACK (rb_entry_view_rows_reordered_cb
),
510 g_object_unref (view
->priv
->model
);
513 gtk_tree_selection_unselect_all (view
->priv
->selection
);
515 view
->priv
->model
= model
;
516 if (view
->priv
->model
!= NULL
) {
517 g_object_ref (view
->priv
->model
);
518 g_signal_connect_object (view
->priv
->model
,
520 G_CALLBACK (rb_entry_view_row_inserted_cb
),
523 g_signal_connect_object (view
->priv
->model
,
525 G_CALLBACK (rb_entry_view_row_deleted_cb
),
528 g_signal_connect_object (view
->priv
->model
,
530 G_CALLBACK (rb_entry_view_rows_reordered_cb
),
534 if (view
->priv
->sorting_column
!= NULL
) {
535 rb_entry_view_resort_model (view
);
538 gtk_tree_view_set_model (GTK_TREE_VIEW (view
->priv
->treeview
),
539 GTK_TREE_MODEL (view
->priv
->model
));
542 view
->priv
->have_selection
= FALSE
;
543 view
->priv
->have_complete_selection
= FALSE
;
545 g_signal_emit (G_OBJECT (view
), rb_entry_view_signals
[ENTRIES_REPLACED
], 0);
549 rb_entry_view_set_property (GObject
*object
,
554 RBEntryView
*view
= RB_ENTRY_VIEW (object
);
558 view
->priv
->db
= g_value_get_object (value
);
560 case PROP_SHELL_PLAYER
:
561 rb_entry_view_set_shell_player_internal (view
, g_value_get_object (value
));
563 case PROP_SORTING_KEY
:
564 g_free (view
->priv
->sorting_key
);
565 view
->priv
->sorting_key
= g_value_dup_string (value
);
568 rb_entry_view_set_model_internal (view
, g_value_get_object (value
));
570 case PROP_IS_DRAG_SOURCE
:
571 view
->priv
->is_drag_source
= g_value_get_boolean (value
);
573 case PROP_IS_DRAG_DEST
:
574 view
->priv
->is_drag_dest
= g_value_get_boolean (value
);
576 case PROP_PLAYING_STATE
:
577 view
->priv
->playing_state
= g_value_get_int (value
);
579 /* redraw the playing entry, as the icon will have changed */
580 if (view
->priv
->playing_entry
!= NULL
) {
581 rb_entry_view_emit_row_changed (view
, view
->priv
->playing_entry
);
585 G_OBJECT_WARN_INVALID_PROPERTY_ID (object
, prop_id
, pspec
);
591 rb_entry_view_get_property (GObject
*object
,
596 RBEntryView
*view
= RB_ENTRY_VIEW (object
);
600 g_value_set_object (value
, view
->priv
->db
);
602 case PROP_SHELL_PLAYER
:
603 g_value_set_object (value
, view
->priv
->shell_player
);
605 case PROP_SORTING_KEY
:
606 g_value_set_string (value
, view
->priv
->sorting_key
);
608 case PROP_IS_DRAG_SOURCE
:
609 g_value_set_boolean (value
, view
->priv
->is_drag_source
);
611 case PROP_IS_DRAG_DEST
:
612 g_value_set_boolean (value
, view
->priv
->is_drag_dest
);
614 case PROP_PLAYING_STATE
:
615 g_value_set_int (value
, view
->priv
->playing_state
);
618 G_OBJECT_WARN_INVALID_PROPERTY_ID (object
, prop_id
, pspec
);
624 rb_entry_view_new (RhythmDB
*db
,
625 GObject
*shell_player
,
626 const char *sort_key
,
627 gboolean is_drag_source
,
628 gboolean is_drag_dest
)
632 view
= RB_ENTRY_VIEW (g_object_new (RB_TYPE_ENTRY_VIEW
,
635 "hscrollbar_policy", GTK_POLICY_AUTOMATIC
,
636 "vscrollbar_policy", GTK_POLICY_ALWAYS
,
637 "shadow_type", GTK_SHADOW_IN
,
639 "shell-player", RB_SHELL_PLAYER (shell_player
),
640 "sort-key", sort_key
,
641 "is-drag-source", is_drag_source
,
642 "is-drag-dest", is_drag_dest
,
645 g_return_val_if_fail (view
->priv
!= NULL
, NULL
);
651 rb_entry_view_set_model (RBEntryView
*view
,
652 RhythmDBQueryModel
*model
)
654 g_object_set (view
, "model", model
, NULL
);
657 /* Sweet name, eh? */
658 struct RBEntryViewCellDataFuncData
{
660 RhythmDBPropType propid
;
664 rb_entry_view_playing_cell_data_func (GtkTreeViewColumn
*column
,
665 GtkCellRenderer
*renderer
,
666 GtkTreeModel
*tree_model
,
670 RhythmDBEntry
*entry
;
671 GdkPixbuf
*pixbuf
= NULL
;
673 entry
= rhythmdb_query_model_iter_to_entry (view
->priv
->model
, iter
);
679 if (entry
== view
->priv
->playing_entry
) {
680 switch (view
->priv
->playing_state
) {
681 case RB_ENTRY_VIEW_PLAYING
:
682 pixbuf
= view
->priv
->playing_pixbuf
;
684 case RB_ENTRY_VIEW_PAUSED
:
685 pixbuf
= view
->priv
->paused_pixbuf
;
693 if (pixbuf
== NULL
&& rhythmdb_entry_get_string (entry
, RHYTHMDB_PROP_PLAYBACK_ERROR
)) {
694 pixbuf
= view
->priv
->error_pixbuf
;
697 g_object_set (renderer
, "pixbuf", pixbuf
, NULL
);
699 rhythmdb_entry_unref (entry
);
703 rb_entry_view_rating_cell_data_func (GtkTreeViewColumn
*column
,
704 GtkCellRenderer
*renderer
,
705 GtkTreeModel
*tree_model
,
709 RhythmDBEntry
*entry
;
711 entry
= rhythmdb_query_model_iter_to_entry (view
->priv
->model
, iter
);
713 g_object_set (renderer
,
714 "rating", rhythmdb_entry_get_double (entry
, RHYTHMDB_PROP_RATING
),
717 rhythmdb_entry_unref (entry
);
721 rb_entry_view_long_cell_data_func (GtkTreeViewColumn
*column
,
722 GtkCellRenderer
*renderer
,
723 GtkTreeModel
*tree_model
,
725 struct RBEntryViewCellDataFuncData
*data
)
727 RhythmDBEntry
*entry
;
731 entry
= rhythmdb_query_model_iter_to_entry (data
->view
->priv
->model
, iter
);
733 val
= rhythmdb_entry_get_ulong (entry
, data
->propid
);
736 str
= g_strdup_printf ("%lu", val
);
740 g_object_set (renderer
, "text", str
, NULL
);
742 rhythmdb_entry_unref (entry
);
746 rb_entry_view_play_count_cell_data_func (GtkTreeViewColumn
*column
,
747 GtkCellRenderer
*renderer
,
748 GtkTreeModel
*tree_model
,
750 struct RBEntryViewCellDataFuncData
*data
)
752 RhythmDBEntry
*entry
;
756 entry
= rhythmdb_query_model_iter_to_entry (data
->view
->priv
->model
, iter
);
758 i
= rhythmdb_entry_get_ulong (entry
, data
->propid
);
762 str
= g_strdup_printf ("%ld", i
);
764 g_object_set (renderer
, "text", str
, NULL
);
768 rhythmdb_entry_unref (entry
);
772 rb_entry_view_duration_cell_data_func (GtkTreeViewColumn
*column
,
773 GtkCellRenderer
*renderer
,
774 GtkTreeModel
*tree_model
,
776 struct RBEntryViewCellDataFuncData
*data
)
778 RhythmDBEntry
*entry
;
782 entry
= rhythmdb_query_model_iter_to_entry (data
->view
->priv
->model
, iter
);
783 duration
= rhythmdb_entry_get_ulong (entry
, RHYTHMDB_PROP_DURATION
);
785 str
= rb_make_duration_string (duration
);
786 g_object_set (renderer
, "text", str
, NULL
);
788 rhythmdb_entry_unref (entry
);
792 rb_entry_view_year_cell_data_func (GtkTreeViewColumn
*column
,
793 GtkCellRenderer
*renderer
,
794 GtkTreeModel
*tree_model
,
796 struct RBEntryViewCellDataFuncData
*data
)
798 RhythmDBEntry
*entry
;
803 entry
= rhythmdb_query_model_iter_to_entry (data
->view
->priv
->model
, iter
);
804 julian
= rhythmdb_entry_get_ulong (entry
, RHYTHMDB_PROP_DATE
);
807 date
= g_date_new_julian (julian
);
808 g_date_strftime (str
, sizeof (str
), "%Y", date
);
809 g_object_set (renderer
, "text", str
, NULL
);
812 g_object_set (renderer
, "text", _("Unknown"), NULL
);
815 rhythmdb_entry_unref (entry
);
819 rb_entry_view_quality_cell_data_func (GtkTreeViewColumn
*column
,
820 GtkCellRenderer
*renderer
,
821 GtkTreeModel
*tree_model
,
823 struct RBEntryViewCellDataFuncData
*data
)
825 RhythmDBEntry
*entry
;
828 entry
= rhythmdb_query_model_iter_to_entry (data
->view
->priv
->model
, iter
);
830 bitrate
= rhythmdb_entry_get_ulong (entry
, data
->propid
);
833 char *s
= g_strdup_printf (_("%u kbps"), (guint
)bitrate
);
834 g_object_set (renderer
, "text", s
, NULL
);
837 g_object_set (renderer
, "text", _("Unknown"), NULL
);
840 rhythmdb_entry_unref (entry
);
844 rb_entry_view_location_cell_data_func (GtkTreeViewColumn
*column
,
845 GtkCellRenderer
*renderer
,
846 GtkTreeModel
*tree_model
,
848 struct RBEntryViewCellDataFuncData
*data
)
850 RhythmDBEntry
*entry
;
851 const char *location
;
854 entry
= rhythmdb_query_model_iter_to_entry (data
->view
->priv
->model
, iter
);
856 location
= rhythmdb_entry_get_string (entry
, data
->propid
);
857 str
= gnome_vfs_unescape_string_for_display (location
);
859 g_object_set (renderer
, "text", str
, NULL
);
862 rhythmdb_entry_unref (entry
);
866 rb_entry_view_string_cell_data_func (GtkTreeViewColumn
*column
,
867 GtkCellRenderer
*renderer
,
868 GtkTreeModel
*tree_model
,
870 struct RBEntryViewCellDataFuncData
*data
)
872 RhythmDBEntry
*entry
;
875 entry
= rhythmdb_query_model_iter_to_entry (data
->view
->priv
->model
, iter
);
877 str
= rhythmdb_entry_get_string (entry
, data
->propid
);
879 g_object_set (renderer
, "text", str
, NULL
);
882 rhythmdb_entry_unref (entry
);
886 rb_entry_view_sync_sorting (RBEntryView
*view
)
888 GtkTreeViewColumn
*column
;
892 direction
= GTK_SORT_ASCENDING
;
894 rb_entry_view_get_sorting_order (view
, &column_name
, &direction
);
896 if (column_name
== NULL
) {
900 column
= g_hash_table_lookup (view
->priv
->column_key_map
, column_name
);
901 if (column
== NULL
) {
902 g_free (column_name
);
906 rb_debug ("Updating EntryView sort order to %s:%d", column_name
, direction
);
908 /* remove the old sorting indicator */
909 if (view
->priv
->sorting_column
)
910 gtk_tree_view_column_set_sort_indicator (view
->priv
->sorting_column
, FALSE
);
912 /* set the sorting order and indicator of the new sorting column */
913 view
->priv
->sorting_column
= column
;
914 gtk_tree_view_column_set_sort_indicator (column
, TRUE
);
915 gtk_tree_view_column_set_sort_order (column
, direction
);
917 rb_debug ("emitting sort order changed");
918 g_signal_emit (G_OBJECT (view
), rb_entry_view_signals
[SORT_ORDER_CHANGED
], 0);
920 g_free (column_name
);
924 rb_entry_view_get_sorting_type (RBEntryView
*view
)
927 GString
*key
= g_string_new (view
->priv
->sorting_column_name
);
929 g_string_append_c (key
, ',');
931 switch (view
->priv
->sorting_order
)
933 case GTK_SORT_ASCENDING
:
934 g_string_append (key
, "ascending");
936 case GTK_SORT_DESCENDING
:
937 g_string_append (key
, "descending");
940 g_assert_not_reached ();
943 sorttype
= g_strdup(key
->str
);
944 g_string_free (key
, TRUE
);
950 rb_entry_view_set_sorting_type (RBEntryView
*view
,
951 const char *sorttype
)
955 if (!sorttype
|| !strchr (sorttype
, ',')) {
956 rb_debug ("malformed sort data: %s", (sorttype
) ? sorttype
: "(null)");
960 strs
= g_strsplit (sorttype
, ",", 0);
962 g_free (view
->priv
->sorting_column_name
);
963 view
->priv
->sorting_column_name
= g_strdup(strs
[0]);
965 if (!strcmp ("ascending", strs
[1]))
966 view
->priv
->sorting_order
= GTK_SORT_ASCENDING
;
967 else if (!strcmp ("descending", strs
[1]))
968 view
->priv
->sorting_order
= GTK_SORT_DESCENDING
;
970 g_warning ("atttempting to sort in unknown direction");
971 view
->priv
->sorting_order
= GTK_SORT_ASCENDING
;
976 rb_entry_view_sync_sorting (view
);
980 rb_entry_view_get_sorting_order (RBEntryView
*view
,
984 g_return_if_fail (RB_IS_ENTRY_VIEW (view
));
986 if (column_name
!= NULL
) {
987 *column_name
= g_strdup (view
->priv
->sorting_column_name
);
990 if (sort_order
!= NULL
) {
991 *sort_order
= view
->priv
->sorting_order
;
996 rb_entry_view_set_sorting_order (RBEntryView
*view
,
997 const char *column_name
,
1000 if (column_name
== NULL
)
1003 g_free (view
->priv
->sorting_column_name
);
1004 view
->priv
->sorting_column_name
= g_strdup (column_name
);
1005 view
->priv
->sorting_order
= sort_order
;
1007 rb_entry_view_sync_sorting (view
);
1011 rb_entry_view_column_clicked_cb (GtkTreeViewColumn
*column
, RBEntryView
*view
)
1014 char *clicked_column
;
1016 rb_debug ("sorting on column %p", column
);
1018 /* identify the clicked column, and then update the sorting order */
1019 clicked_column
= (char*) g_object_get_data (G_OBJECT (column
), "rb-entry-view-key");
1020 sort_order
= view
->priv
->sorting_order
;
1022 if (view
->priv
->sorting_column_name
1023 && !strcmp(clicked_column
, view
->priv
->sorting_column_name
)
1024 && (sort_order
== GTK_SORT_ASCENDING
))
1025 sort_order
= GTK_SORT_DESCENDING
;
1027 sort_order
= GTK_SORT_ASCENDING
;
1029 rb_entry_view_set_sorting_order (view
, clicked_column
, sort_order
);
1031 /* update the sort order in GConf */
1032 if (view
->priv
->sorting_key
)
1033 eel_gconf_set_string (view
->priv
->sorting_key
, rb_entry_view_get_sorting_type(view
));
1037 rb_entry_view_append_column (RBEntryView
*view
,
1038 RBEntryViewColumn coltype
,
1039 gboolean always_visible
)
1041 GtkTreeViewColumn
*column
;
1042 GtkCellRenderer
*renderer
= NULL
;
1043 struct RBEntryViewCellDataFuncData
*cell_data
;
1044 const char *title
= NULL
;
1045 const char *key
= NULL
;
1046 const char *strings
[4] = {0};
1047 GtkTreeCellDataFunc cell_data_func
= NULL
;
1048 GCompareDataFunc sort_func
= NULL
;
1049 RhythmDBPropType propid
;
1050 RhythmDBPropType sort_propid
= RHYTHMDB_NUM_PROPERTIES
;
1051 gboolean ellipsize
= FALSE
;
1052 gboolean resizable
= TRUE
;
1053 gint column_width
= -1;
1055 column
= gtk_tree_view_column_new ();
1057 cell_data
= g_new0 (struct RBEntryViewCellDataFuncData
, 1);
1058 cell_data
->view
= view
;
1061 case RB_ENTRY_VIEW_COL_TRACK_NUMBER
:
1062 propid
= RHYTHMDB_PROP_TRACK_NUMBER
;
1063 cell_data
->propid
= propid
;
1064 cell_data_func
= (GtkTreeCellDataFunc
) rb_entry_view_long_cell_data_func
;
1065 sort_func
= (GCompareDataFunc
) rhythmdb_query_model_track_sort_func
;
1069 strings
[1] = "9999";
1071 case RB_ENTRY_VIEW_COL_TITLE
:
1072 propid
= RHYTHMDB_PROP_TITLE
;
1073 cell_data
->propid
= propid
;
1074 cell_data_func
= (GtkTreeCellDataFunc
) rb_entry_view_string_cell_data_func
;
1075 sort_propid
= RHYTHMDB_PROP_TITLE_SORT_KEY
;
1076 sort_func
= (GCompareDataFunc
) rhythmdb_query_model_string_sort_func
;
1081 case RB_ENTRY_VIEW_COL_ARTIST
:
1082 propid
= RHYTHMDB_PROP_ARTIST
;
1083 cell_data
->propid
= propid
;
1084 cell_data_func
= (GtkTreeCellDataFunc
) rb_entry_view_string_cell_data_func
;
1085 sort_propid
= RHYTHMDB_PROP_ARTIST_SORT_KEY
;
1086 sort_func
= (GCompareDataFunc
) rhythmdb_query_model_artist_sort_func
;
1087 title
= _("Artist");
1091 case RB_ENTRY_VIEW_COL_ALBUM
:
1092 propid
= RHYTHMDB_PROP_ALBUM
;
1093 cell_data
->propid
= propid
;
1094 cell_data_func
= (GtkTreeCellDataFunc
) rb_entry_view_string_cell_data_func
;
1095 sort_propid
= RHYTHMDB_PROP_ALBUM_SORT_KEY
;
1096 sort_func
= (GCompareDataFunc
) rhythmdb_query_model_album_sort_func
;
1101 case RB_ENTRY_VIEW_COL_GENRE
:
1102 propid
= RHYTHMDB_PROP_GENRE
;
1103 cell_data
->propid
= propid
;
1104 cell_data_func
= (GtkTreeCellDataFunc
) rb_entry_view_string_cell_data_func
;
1105 sort_propid
= RHYTHMDB_PROP_GENRE_SORT_KEY
;
1106 sort_func
= (GCompareDataFunc
) rhythmdb_query_model_genre_sort_func
;
1111 case RB_ENTRY_VIEW_COL_DURATION
:
1112 propid
= RHYTHMDB_PROP_DURATION
;
1113 cell_data
->propid
= propid
;
1114 cell_data_func
= (GtkTreeCellDataFunc
) rb_entry_view_duration_cell_data_func
;
1115 sort_propid
= cell_data
->propid
;
1116 sort_func
= (GCompareDataFunc
) rhythmdb_query_model_ulong_sort_func
;
1120 strings
[1] = "000:00";
1121 strings
[2] = _("Unknown");
1123 case RB_ENTRY_VIEW_COL_YEAR
:
1124 propid
= RHYTHMDB_PROP_DATE
;
1125 cell_data
->propid
= propid
;
1126 cell_data_func
= (GtkTreeCellDataFunc
) rb_entry_view_year_cell_data_func
;
1127 sort_propid
= cell_data
->propid
;
1128 sort_func
= (GCompareDataFunc
) rhythmdb_query_model_date_sort_func
;
1132 strings
[1] = "0000";
1133 strings
[2] = _("Unknown");
1135 case RB_ENTRY_VIEW_COL_QUALITY
:
1136 propid
= RHYTHMDB_PROP_BITRATE
;
1137 cell_data
->propid
= propid
;
1138 cell_data_func
= (GtkTreeCellDataFunc
) rb_entry_view_quality_cell_data_func
;
1139 sort_propid
= cell_data
->propid
;
1140 sort_func
= (GCompareDataFunc
) rhythmdb_query_model_ulong_sort_func
;
1141 title
= _("Quality");
1144 strings
[1] = _("000 kbps");
1145 strings
[2] = _("Unknown");
1147 case RB_ENTRY_VIEW_COL_RATING
:
1148 propid
= RHYTHMDB_PROP_RATING
;
1149 sort_func
= (GCompareDataFunc
) rhythmdb_query_model_double_ceiling_sort_func
;
1151 gtk_icon_size_lookup (GTK_ICON_SIZE_MENU
, &column_width
, NULL
);
1152 column_width
= column_width
* 5 + 5;
1154 title
= _("Rating");
1157 renderer
= rb_cell_renderer_rating_new ();
1158 gtk_tree_view_column_pack_start (column
, renderer
, TRUE
);
1159 gtk_tree_view_column_set_cell_data_func (column
, renderer
,
1160 (GtkTreeCellDataFunc
)
1161 rb_entry_view_rating_cell_data_func
,
1164 g_signal_connect_object (renderer
,
1166 G_CALLBACK (rb_entry_view_rated_cb
),
1170 case RB_ENTRY_VIEW_COL_PLAY_COUNT
:
1171 propid
= RHYTHMDB_PROP_PLAY_COUNT
;
1172 cell_data
->propid
= propid
;
1173 cell_data_func
= (GtkTreeCellDataFunc
) rb_entry_view_play_count_cell_data_func
;
1174 sort_propid
= cell_data
->propid
;
1175 sort_func
= (GCompareDataFunc
) rhythmdb_query_model_ulong_sort_func
;
1176 title
= _("Play Count");
1179 strings
[1] = _("Never");
1180 strings
[2] = "9999";
1182 case RB_ENTRY_VIEW_COL_LAST_PLAYED
:
1183 propid
= RHYTHMDB_PROP_LAST_PLAYED
;
1184 cell_data
->propid
= RHYTHMDB_PROP_LAST_PLAYED_STR
;
1185 cell_data_func
= (GtkTreeCellDataFunc
) rb_entry_view_string_cell_data_func
;
1186 sort_propid
= RHYTHMDB_PROP_LAST_PLAYED
;
1187 sort_func
= (GCompareDataFunc
) rhythmdb_query_model_ulong_sort_func
;
1188 title
= _("Last Played");
1191 strings
[1] = rb_entry_view_get_time_date_column_sample ();
1192 strings
[2] = _("Never");
1194 case RB_ENTRY_VIEW_COL_FIRST_SEEN
:
1195 propid
= RHYTHMDB_PROP_FIRST_SEEN
;
1196 cell_data
->propid
= RHYTHMDB_PROP_FIRST_SEEN_STR
;
1197 cell_data_func
= (GtkTreeCellDataFunc
) rb_entry_view_string_cell_data_func
;
1198 sort_propid
= RHYTHMDB_PROP_FIRST_SEEN
;
1199 sort_func
= (GCompareDataFunc
) rhythmdb_query_model_ulong_sort_func
;
1200 title
= _("Date Added");
1203 strings
[1] = rb_entry_view_get_time_date_column_sample ();
1205 case RB_ENTRY_VIEW_COL_LAST_SEEN
:
1206 propid
= RHYTHMDB_PROP_LAST_SEEN
;
1207 cell_data
->propid
= RHYTHMDB_PROP_LAST_SEEN_STR
;
1208 cell_data_func
= (GtkTreeCellDataFunc
) rb_entry_view_string_cell_data_func
;
1209 sort_propid
= RHYTHMDB_PROP_LAST_SEEN
;
1210 sort_func
= (GCompareDataFunc
) rhythmdb_query_model_ulong_sort_func
;
1211 title
= _("Last Seen");
1214 strings
[1] = rb_entry_view_get_time_date_column_sample ();
1216 case RB_ENTRY_VIEW_COL_LOCATION
:
1217 propid
= RHYTHMDB_PROP_LOCATION
;
1218 cell_data
->propid
= RHYTHMDB_PROP_LOCATION
;
1219 cell_data_func
= (GtkTreeCellDataFunc
) rb_entry_view_location_cell_data_func
;
1220 sort_propid
= RHYTHMDB_PROP_LOCATION
;
1221 sort_func
= (GCompareDataFunc
) rhythmdb_query_model_location_sort_func
;
1222 title
= _("Location");
1226 case RB_ENTRY_VIEW_COL_ERROR
:
1227 propid
= RHYTHMDB_PROP_PLAYBACK_ERROR
;
1228 cell_data
->propid
= RHYTHMDB_PROP_PLAYBACK_ERROR
;
1229 cell_data_func
= (GtkTreeCellDataFunc
) rb_entry_view_string_cell_data_func
;
1235 g_assert_not_reached ();
1240 if (sort_propid
== RHYTHMDB_NUM_PROPERTIES
)
1241 sort_propid
= propid
;
1243 if (renderer
== NULL
) {
1244 renderer
= gtk_cell_renderer_text_new ();
1245 gtk_tree_view_column_pack_start (column
, renderer
, TRUE
);
1246 gtk_tree_view_column_set_cell_data_func (column
, renderer
,
1247 cell_data_func
, cell_data
, g_free
);
1253 * Columns must either be expanding (ellipsized) or have a
1254 * fixed minimum width specified. Otherwise, gtk+ gives them a
1258 g_object_set (renderer
, "ellipsize", PANGO_ELLIPSIZE_END
, NULL
);
1259 gtk_tree_view_column_set_expand (GTK_TREE_VIEW_COLUMN (column
), TRUE
);
1260 } else if (column_width
!= -1) {
1261 gtk_tree_view_column_set_fixed_width (column
, column_width
);
1263 rb_entry_view_set_fixed_column_width (view
, column
, renderer
, strings
);
1267 gtk_tree_view_column_set_resizable (column
, TRUE
);
1269 gtk_tree_view_column_set_sizing (column
, GTK_TREE_VIEW_COLUMN_FIXED
);
1270 gtk_tree_view_column_set_clickable (column
, TRUE
);
1273 g_object_set_qdata (G_OBJECT (column
),
1274 rb_entry_view_column_always_visible
,
1275 GINT_TO_POINTER (1));
1277 g_hash_table_insert (view
->priv
->propid_column_map
, GINT_TO_POINTER (propid
), column
);
1279 rb_entry_view_append_column_custom (view
, column
, title
, key
, sort_func
, GINT_TO_POINTER (sort_propid
));
1283 rb_entry_view_append_column_custom (RBEntryView
*view
,
1284 GtkTreeViewColumn
*column
,
1287 GCompareDataFunc sort_func
,
1290 rb_entry_view_insert_column_custom (view
, column
, title
, key
, sort_func
, data
, -1);
1294 rb_entry_view_insert_column_custom (RBEntryView
*view
,
1295 GtkTreeViewColumn
*column
,
1298 GCompareDataFunc sort_func
,
1302 struct RBEntryViewColumnSortData
*sortdata
;
1304 gtk_tree_view_column_set_title (column
, title
);
1305 gtk_tree_view_column_set_reorderable (column
, FALSE
);
1307 g_signal_connect_object (column
, "clicked",
1308 G_CALLBACK (rb_entry_view_column_clicked_cb
),
1311 g_object_set_data_full (G_OBJECT (column
), "rb-entry-view-key",
1312 g_strdup (key
), g_free
);
1314 rb_debug ("appending column: %p (%s)", column
, title
);
1316 gtk_tree_view_insert_column (GTK_TREE_VIEW (view
->priv
->treeview
), column
, position
);
1318 if (sort_func
!= NULL
) {
1319 sortdata
= g_new (struct RBEntryViewColumnSortData
, 1);
1320 sortdata
->func
= (GCompareDataFunc
) sort_func
;
1321 sortdata
->data
= data
;
1322 g_hash_table_insert (view
->priv
->column_sort_data_map
, column
, sortdata
);
1324 g_hash_table_insert (view
->priv
->column_key_map
, g_strdup (key
), column
);
1326 rb_entry_view_sync_columns_visible (view
);
1327 rb_entry_view_sync_sorting (view
);
1331 rb_entry_view_set_columns_clickable (RBEntryView
*view
,
1334 GList
*columns
, *tem
;
1336 columns
= gtk_tree_view_get_columns (GTK_TREE_VIEW (view
->priv
->treeview
));
1337 for (tem
= columns
; tem
; tem
= tem
->next
) {
1338 /* only columns we can sort on should be clickable */
1339 GtkTreeViewColumn
*column
= (GtkTreeViewColumn
*) tem
->data
;
1340 if (g_hash_table_lookup (view
->priv
->column_sort_data_map
, column
) != NULL
)
1341 gtk_tree_view_column_set_clickable (tem
->data
, clickable
);
1343 g_list_free (columns
);
1347 rb_entry_view_constructor (GType type
,
1348 guint n_construct_properties
,
1349 GObjectConstructParam
*construct_properties
)
1352 RBEntryViewClass
*klass
;
1353 klass
= RB_ENTRY_VIEW_CLASS (g_type_class_peek (RB_TYPE_ENTRY_VIEW
));
1355 view
= RB_ENTRY_VIEW (G_OBJECT_CLASS (rb_entry_view_parent_class
)
1356 ->constructor (type
, n_construct_properties
, construct_properties
));
1358 view
->priv
->treeview
= GTK_WIDGET (gtk_tree_view_new ());
1359 gtk_tree_view_set_fixed_height_mode (GTK_TREE_VIEW (view
->priv
->treeview
), TRUE
);
1361 gtk_tree_view_set_search_equal_func (GTK_TREE_VIEW (view
->priv
->treeview
),
1362 type_ahead_search_func
,
1365 g_signal_connect_object (view
->priv
->treeview
,
1366 "button_press_event",
1367 G_CALLBACK (rb_entry_view_button_press_cb
),
1370 g_signal_connect_object (view
->priv
->treeview
,
1372 G_CALLBACK (rb_entry_view_row_activated_cb
),
1375 g_signal_connect_object (view
->priv
->treeview
,
1377 G_CALLBACK (rb_entry_view_popup_menu_cb
),
1380 view
->priv
->selection
= gtk_tree_view_get_selection (GTK_TREE_VIEW (view
->priv
->treeview
));
1381 g_signal_connect_object (view
->priv
->selection
,
1383 G_CALLBACK (rb_entry_view_selection_changed_cb
),
1387 gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (view
->priv
->treeview
), TRUE
);
1388 gtk_tree_view_set_rules_hint (GTK_TREE_VIEW (view
->priv
->treeview
), TRUE
);
1389 gtk_tree_selection_set_mode (view
->priv
->selection
, GTK_SELECTION_MULTIPLE
);
1391 if (view
->priv
->is_drag_source
) {
1392 rb_tree_dnd_add_drag_source_support (GTK_TREE_VIEW (view
->priv
->treeview
),
1394 rb_entry_view_drag_types
,
1395 G_N_ELEMENTS (rb_entry_view_drag_types
),
1399 if (view
->priv
->is_drag_dest
) {
1400 rb_tree_dnd_add_drag_dest_support (GTK_TREE_VIEW (view
->priv
->treeview
),
1401 RB_TREE_DEST_CAN_DROP_BETWEEN
| RB_TREE_DEST_EMPTY_VIEW_DROP
,
1402 rb_entry_view_drag_types
,
1403 G_N_ELEMENTS (rb_entry_view_drag_types
),
1404 GDK_ACTION_COPY
| GDK_ACTION_MOVE
);
1407 gtk_container_add (GTK_CONTAINER (view
), view
->priv
->treeview
);
1410 GtkTreeViewColumn
*column
;
1411 GtkTooltips
*tooltip
;
1412 GtkCellRenderer
*renderer
;
1413 GtkWidget
*image_widget
;
1416 tooltip
= gtk_tooltips_new ();
1418 /* Playing icon column */
1419 column
= GTK_TREE_VIEW_COLUMN (gtk_tree_view_column_new ());
1420 renderer
= rb_cell_renderer_pixbuf_new ();
1421 gtk_tree_view_column_pack_start (column
, renderer
, TRUE
);
1422 gtk_tree_view_column_set_cell_data_func (column
, renderer
,
1423 (GtkTreeCellDataFunc
)
1424 rb_entry_view_playing_cell_data_func
,
1428 image_widget
= gtk_image_new_from_icon_name ("stock_volume-max", GTK_ICON_SIZE_MENU
);
1429 gtk_tree_view_column_set_widget (column
, image_widget
);
1430 gtk_widget_show (image_widget
);
1432 gtk_tree_view_column_set_sizing (column
, GTK_TREE_VIEW_COLUMN_FIXED
);
1433 gtk_tree_view_column_set_clickable (column
, FALSE
);
1434 gtk_icon_size_lookup (GTK_ICON_SIZE_MENU
, &width
, NULL
);
1435 gtk_tree_view_column_set_fixed_width (column
, width
+ 5);
1436 gtk_tree_view_append_column (GTK_TREE_VIEW (view
->priv
->treeview
), column
);
1437 g_signal_connect_swapped (renderer
,
1439 G_CALLBACK (rb_entry_view_pixbuf_clicked_cb
),
1442 gtk_tooltips_set_tip (GTK_TOOLTIPS (tooltip
), GTK_WIDGET (column
->button
),
1443 _("Now Playing"), NULL
);
1446 view
->priv
->gconf_notification_id
=
1447 eel_gconf_notification_add (CONF_UI_COLUMNS_SETUP
,
1448 rb_entry_view_columns_config_changed_cb
,
1450 if (view
->priv
->sorting_key
) {
1451 view
->priv
->sorting_gconf_notification_id
=
1452 eel_gconf_notification_add (view
->priv
->sorting_key
,
1453 rb_entry_view_sort_key_changed_cb
,
1457 if (view
->priv
->sorting_key
) {
1458 char *s
= eel_gconf_get_string (view
->priv
->sorting_key
);
1459 rb_entry_view_set_sorting_type (view
, s
);
1464 RhythmDBQueryModel
*query_model
;
1465 query_model
= rhythmdb_query_model_new_empty (view
->priv
->db
);
1466 rb_entry_view_set_model (view
, RHYTHMDB_QUERY_MODEL (query_model
));
1467 g_object_unref (query_model
);
1470 return G_OBJECT (view
);
1474 rb_entry_view_rated_cb (RBCellRendererRating
*cellrating
,
1475 const char *path_string
,
1480 RhythmDBEntry
*entry
;
1481 GValue value
= { 0, };
1483 g_return_if_fail (rating
>= 0 && rating
<= 5 );
1484 g_return_if_fail (path_string
!= NULL
);
1486 path
= gtk_tree_path_new_from_string (path_string
);
1487 entry
= rhythmdb_query_model_tree_path_to_entry (view
->priv
->model
, path
);
1488 gtk_tree_path_free (path
);
1490 g_value_init (&value
, G_TYPE_DOUBLE
);
1491 g_value_set_double (&value
, rating
);
1492 rhythmdb_entry_set (view
->priv
->db
, entry
, RHYTHMDB_PROP_RATING
, &value
);
1493 g_value_unset (&value
);
1495 rhythmdb_commit (view
->priv
->db
);
1497 rhythmdb_entry_unref (entry
);
1501 rb_entry_view_pixbuf_clicked_cb (RBEntryView
*view
,
1502 const char *path_string
,
1503 RBCellRendererPixbuf
*cellpixbuf
)
1506 RhythmDBEntry
*entry
;
1509 g_return_if_fail (path_string
!= NULL
);
1511 path
= gtk_tree_path_new_from_string (path_string
);
1512 entry
= rhythmdb_query_model_tree_path_to_entry (view
->priv
->model
, path
);
1514 gtk_tree_path_free (path
);
1516 error
= rhythmdb_entry_get_string (entry
, RHYTHMDB_PROP_PLAYBACK_ERROR
);
1518 rb_error_dialog (NULL
, _("Playback Error"), "%s", error
);
1521 rhythmdb_entry_unref (entry
);
1525 rb_entry_view_playing_song_changed (RBShellPlayer
*player
,
1526 RhythmDBEntry
*entry
,
1529 gboolean realized
, visible
;
1532 g_return_if_fail (RB_IS_ENTRY_VIEW (view
));
1534 if (view
->priv
->playing_entry
!= NULL
) {
1535 if (view
->priv
->playing_state
!= RB_ENTRY_VIEW_NOT_PLAYING
)
1536 rb_entry_view_emit_row_changed (view
, view
->priv
->playing_entry
);
1537 g_object_unref (view
->priv
->playing_model
);
1540 view
->priv
->playing_entry
= entry
;
1541 view
->priv
->playing_model
= view
->priv
->model
;
1542 g_object_ref (view
->priv
->playing_model
);
1544 if (view
->priv
->playing_state
!= RB_ENTRY_VIEW_NOT_PLAYING
) {
1545 if (view
->priv
->playing_entry
!= NULL
) {
1546 view
->priv
->playing_entry_in_view
=
1547 rb_entry_view_emit_row_changed (view
, view
->priv
->playing_entry
);
1550 if (view
->priv
->playing_entry
1551 && view
->priv
->playing_entry_in_view
) {
1552 rb_entry_view_entry_is_visible (view
, view
->priv
->playing_entry
,
1553 &realized
, &visible
, &iter
);
1554 if (realized
&& !visible
)
1555 rb_entry_view_scroll_to_iter (view
, &iter
);
1561 harvest_entries (GtkTreeModel
*model
,
1566 RhythmDBEntry
*entry
;
1568 gtk_tree_model_get (model
, iter
, 0, &entry
, -1);
1570 *list
= g_list_prepend (*list
, entry
);
1576 rb_entry_view_get_selected_entries (RBEntryView
*view
)
1580 gtk_tree_selection_selected_foreach (view
->priv
->selection
,
1581 (GtkTreeSelectionForeachFunc
) harvest_entries
,
1584 list
= g_list_reverse (list
);
1589 rb_entry_view_button_press_cb (GtkTreeView
*treeview
,
1590 GdkEventButton
*event
,
1593 if (event
->button
== 3) {
1595 RhythmDBEntry
*entry
;
1597 gtk_tree_view_get_path_at_pos (treeview
, event
->x
, event
->y
, &path
, NULL
, NULL
, NULL
);
1600 entry
= rhythmdb_query_model_tree_path_to_entry (view
->priv
->model
, path
);
1602 selected
= rb_entry_view_get_selected_entries (view
);
1604 if (!g_list_find (selected
, entry
))
1605 rb_entry_view_select_entry (view
, entry
);
1607 g_list_free (selected
);
1609 rhythmdb_entry_unref (entry
);
1611 g_signal_emit (G_OBJECT (view
), rb_entry_view_signals
[SHOW_POPUP
], 0, (path
!= NULL
));
1619 rb_entry_view_popup_menu_cb (GtkTreeView
*treeview
,
1622 if (gtk_tree_selection_count_selected_rows (gtk_tree_view_get_selection (treeview
)) == 0)
1625 g_signal_emit (G_OBJECT (view
), rb_entry_view_signals
[SHOW_POPUP
], 0);
1630 rb_entry_view_emit_selection_changed (RBEntryView
*view
)
1635 GDK_THREADS_ENTER ();
1636 sel_count
= gtk_tree_selection_count_selected_rows (view
->priv
->selection
);
1637 available
= (sel_count
> 0);
1639 if (available
!= view
->priv
->have_selection
) {
1642 entry_count
= gtk_tree_model_iter_n_children (GTK_TREE_MODEL (view
->priv
->model
), NULL
);
1643 view
->priv
->have_complete_selection
= (sel_count
== entry_count
);
1645 view
->priv
->have_selection
= available
;
1647 g_signal_emit (G_OBJECT (view
), rb_entry_view_signals
[HAVE_SEL_CHANGED
], 0, available
);
1650 view
->priv
->selection_changed_id
= 0;
1651 g_signal_emit (G_OBJECT (view
), rb_entry_view_signals
[SELECTION_CHANGED
], 0);
1653 GDK_THREADS_LEAVE ();
1658 rb_entry_view_selection_changed_cb (GtkTreeSelection
*selection
,
1661 if (view
->priv
->selection_changed_id
== 0)
1662 view
->priv
->selection_changed_id
= g_idle_add ((GSourceFunc
)rb_entry_view_emit_selection_changed
, view
);
1666 rb_entry_view_have_selection (RBEntryView
*view
)
1668 return view
->priv
->have_selection
;
1672 rb_entry_view_have_complete_selection (RBEntryView
*view
)
1674 return view
->priv
->have_complete_selection
;
1678 rb_entry_view_row_activated_cb (GtkTreeView
*treeview
,
1680 GtkTreeViewColumn
*column
,
1683 RhythmDBEntry
*entry
;
1685 rb_debug ("row activated");
1687 entry
= rhythmdb_query_model_tree_path_to_entry (view
->priv
->model
, path
);
1689 rb_debug ("emitting entry activated");
1690 g_signal_emit (G_OBJECT (view
), rb_entry_view_signals
[ENTRY_ACTIVATED
], 0, entry
);
1692 rhythmdb_entry_unref (entry
);
1696 rb_entry_view_row_inserted_cb (GtkTreeModel
*model
,
1701 RhythmDBEntry
*entry
= rhythmdb_query_model_tree_path_to_entry (RHYTHMDB_QUERY_MODEL (model
), path
);
1703 rb_debug ("row added");
1704 g_signal_emit (G_OBJECT (view
), rb_entry_view_signals
[ENTRY_ADDED
], 0, entry
);
1705 rhythmdb_entry_unref (entry
);
1709 rb_entry_view_row_deleted_cb (GtkTreeModel
*model
,
1713 RhythmDBEntry
*entry
= rhythmdb_query_model_tree_path_to_entry (RHYTHMDB_QUERY_MODEL (model
), path
);
1715 rb_debug ("row deleted");
1716 g_signal_emit (G_OBJECT (view
), rb_entry_view_signals
[ENTRY_DELETED
], 0, entry
);
1717 rhythmdb_entry_unref (entry
);
1721 rb_entry_view_rows_reordered_cb (GtkTreeModel
*model
,
1727 GList
*selected_rows
;
1730 gboolean scrolled
= FALSE
;
1732 rb_debug ("rows reordered");
1734 model_size
= gtk_tree_model_iter_n_children (model
, NULL
);
1736 /* check if a selected row was moved; if so, we'll
1737 * need to move the selection too.
1739 selected_rows
= gtk_tree_selection_get_selected_rows (view
->priv
->selection
,
1741 for (i
= selected_rows
; i
!= NULL
; i
= i
->next
) {
1742 GtkTreePath
*path
= (GtkTreePath
*)i
->data
;
1743 gint index
= gtk_tree_path_get_indices (path
)[0];
1745 if (order
[index
] != index
) {
1746 GtkTreePath
*newpath
;
1747 gtk_tree_selection_unselect_path (view
->priv
->selection
, path
);
1749 for (newindex
= 0; newindex
< model_size
; newindex
++) {
1750 if (order
[newindex
] == index
) {
1751 newpath
= gtk_tree_path_new_from_indices (newindex
, -1);
1752 gtk_tree_selection_select_path (view
->priv
->selection
, newpath
);
1754 GtkTreeViewColumn
*col
;
1755 GtkTreeView
*treeview
= GTK_TREE_VIEW (view
->priv
->treeview
);
1757 col
= gtk_tree_view_get_column (treeview
, 0);
1758 gtk_tree_view_scroll_to_cell (treeview
, newpath
, col
, TRUE
, 0.5, 0.0);
1761 gtk_tree_path_free (newpath
);
1769 g_list_foreach (selected_rows
, (GFunc
) gtk_tree_path_free
, NULL
);
1770 g_list_free (selected_rows
);
1772 gtk_widget_queue_draw (GTK_WIDGET (view
));
1776 rb_entry_view_select_all (RBEntryView
*view
)
1778 gtk_tree_selection_select_all (view
->priv
->selection
);
1782 rb_entry_view_select_none (RBEntryView
*view
)
1784 gtk_tree_selection_unselect_all (view
->priv
->selection
);
1788 rb_entry_view_select_entry (RBEntryView
*view
,
1789 RhythmDBEntry
*entry
)
1796 rb_entry_view_select_none (view
);
1798 if (rhythmdb_query_model_entry_to_iter (view
->priv
->model
,
1800 gtk_tree_selection_select_iter (view
->priv
->selection
, &iter
);
1805 rb_entry_view_scroll_to_entry (RBEntryView
*view
,
1806 RhythmDBEntry
*entry
)
1810 if (rhythmdb_query_model_entry_to_iter (view
->priv
->model
,
1812 rb_entry_view_scroll_to_iter (view
, &iter
);
1817 rb_entry_view_scroll_to_iter (RBEntryView
*view
,
1822 /* It's possible to we can be asked to scroll the play queue's entry
1823 * view to the playing entry before the view has ever been displayed.
1824 * This will result in gtk+ warnings, so we avoid it in this case.
1826 if (!GTK_WIDGET_REALIZED (view
))
1829 path
= gtk_tree_model_get_path (GTK_TREE_MODEL (view
->priv
->model
), iter
);
1830 gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (view
->priv
->treeview
), path
,
1831 gtk_tree_view_get_column (GTK_TREE_VIEW (view
->priv
->treeview
), 0),
1833 gtk_tree_view_set_cursor (GTK_TREE_VIEW (view
->priv
->treeview
), path
,
1834 gtk_tree_view_get_column (GTK_TREE_VIEW (view
->priv
->treeview
), 0), FALSE
);
1836 gtk_tree_path_free (path
);
1840 rb_entry_view_get_entry_visible (RBEntryView
*view
,
1841 RhythmDBEntry
*entry
)
1844 gboolean realized
, visible
;
1846 if (view
->priv
->playing_model
!= view
->priv
->model
)
1849 rb_entry_view_entry_is_visible (view
, entry
, &realized
, &visible
,
1851 return realized
&& visible
;
1855 rb_entry_view_get_entry_contained (RBEntryView
*view
,
1856 RhythmDBEntry
*entry
)
1860 return rhythmdb_query_model_entry_to_iter (view
->priv
->model
,
1865 rb_entry_view_entry_is_visible (RBEntryView
*view
,
1866 RhythmDBEntry
*entry
,
1877 g_return_if_fail (entry
!= NULL
);
1879 if (!GTK_WIDGET_REALIZED (view
))
1884 if (!rhythmdb_query_model_entry_to_iter (view
->priv
->model
,
1888 path
= gtk_tree_model_get_path (GTK_TREE_MODEL (view
->priv
->model
), iter
);
1889 gtk_tree_view_get_cell_area (GTK_TREE_VIEW (view
->priv
->treeview
),
1891 gtk_tree_view_get_column (GTK_TREE_VIEW (view
->priv
->treeview
), 0),
1894 gtk_tree_path_free (path
);
1896 *visible
= (rect
.y
!= 0 && rect
.height
!= 0);
1900 rb_entry_view_enable_drag_source (RBEntryView
*view
,
1901 const GtkTargetEntry
*targets
,
1904 g_return_if_fail (view
!= NULL
);
1906 rb_tree_dnd_add_drag_source_support (GTK_TREE_VIEW (view
->priv
->treeview
),
1907 GDK_BUTTON1_MASK
| GDK_BUTTON3_MASK
,
1908 targets
, n_targets
, GDK_ACTION_COPY
);
1912 rb_entry_view_sort_key_changed_cb (GConfClient
* client
,
1917 RBEntryView
*view
= user_data
;
1919 g_return_if_fail (RB_IS_ENTRY_VIEW (view
));
1921 rb_entry_view_set_sorting_type (view
, eel_gconf_get_string (view
->priv
->sorting_key
));
1925 rb_entry_view_columns_config_changed_cb (GConfClient
* client
,
1930 RBEntryView
*view
= user_data
;
1932 g_return_if_fail (RB_IS_ENTRY_VIEW (view
));
1934 rb_entry_view_sync_columns_visible (view
);
1938 propid_from_name (const char *name
)
1940 GEnumClass
*prop_class
= g_type_class_ref (RHYTHMDB_TYPE_PROP_TYPE
);
1944 ev
= g_enum_get_value_by_name (prop_class
, name
);
1953 set_column_visibility (guint propid
,
1954 GtkTreeViewColumn
*column
,
1955 GList
*visible_props
)
1959 if (g_object_get_qdata (G_OBJECT (column
),
1960 rb_entry_view_column_always_visible
) == GINT_TO_POINTER (1))
1963 visible
= (g_list_find (visible_props
, GINT_TO_POINTER (propid
)) != NULL
);
1965 gtk_tree_view_column_set_visible (column
, visible
);
1969 rb_entry_view_sync_columns_visible (RBEntryView
*view
)
1972 GList
*visible_properties
= NULL
;
1973 char *config
= eel_gconf_get_string (CONF_UI_COLUMNS_SETUP
);
1975 g_return_if_fail (view
!= NULL
);
1976 g_return_if_fail (config
!= NULL
);
1978 items
= g_strsplit (config
, ",", 0);
1979 if (items
!= NULL
) {
1981 for (i
= 0; items
[i
] != NULL
&& *(items
[i
]); i
++) {
1982 int value
= propid_from_name (items
[i
]);
1984 if ((value
>= 0) && (value
< RHYTHMDB_NUM_PROPERTIES
))
1985 visible_properties
= g_list_prepend (visible_properties
, GINT_TO_POINTER (value
));
1990 g_hash_table_foreach (view
->priv
->propid_column_map
, (GHFunc
) set_column_visibility
, visible_properties
);
1992 g_list_free (visible_properties
);
1997 rb_entry_view_set_state (RBEntryView
*view
,
1998 RBEntryViewState state
)
2000 g_return_if_fail (RB_IS_ENTRY_VIEW (view
));
2001 g_object_set (view
, "playing-state", state
, NULL
);
2005 rb_entry_view_grab_focus (GtkWidget
*widget
)
2007 RBEntryView
*view
= RB_ENTRY_VIEW (widget
);
2009 gtk_widget_grab_focus (GTK_WIDGET (view
->priv
->treeview
));
2013 rb_entry_view_emit_row_changed (RBEntryView
*view
,
2014 RhythmDBEntry
*entry
)
2019 if (!rhythmdb_query_model_entry_to_iter (view
->priv
->model
, entry
, &iter
))
2022 path
= gtk_tree_model_get_path (GTK_TREE_MODEL (view
->priv
->model
),
2024 gtk_tree_model_row_changed (GTK_TREE_MODEL (view
->priv
->model
),
2026 gtk_tree_path_free (path
);
2031 rb_entry_view_set_fixed_column_width (RBEntryView
*view
,
2032 GtkTreeViewColumn
*column
,
2033 GtkCellRenderer
*renderer
,
2034 const gchar
**strings
)
2039 while (strings
[i
] != NULL
) {
2041 g_object_set (renderer
, "text", strings
[i
], NULL
);
2042 gtk_cell_renderer_get_size (renderer
,
2043 view
->priv
->treeview
,
2048 if (width
> max_width
)
2054 /* include some arbitrary amount of padding, just to be safeish */
2055 gtk_tree_view_column_set_fixed_width (column
, max_width
+ 5);
2059 rb_entry_view_get_time_date_column_sample ()
2061 static const char *sample
= NULL
;
2063 * Currently, Japanese is the only translation that uses
2064 * anything other than %Y, %m ,%d, %H, and %M to format dates.
2065 * It uses %B (month name) and %e (days), and the values for
2066 * the month name appear to all consist of the month number
2067 * followed by a single character, so they're of consistent
2068 * width. So, this approach should work for every locale.
2070 * Midnight on September 30th, 2000 is the widest date/time I
2071 * can think of. 2000-09-30 00:00.
2074 if (sample
== NULL
) {
2075 /* s m h d M Y dw dY x */
2076 struct tm someday
= { 0, 0, 0, 30, 9, 100, 6, 274, 0};
2078 /* Translators: Please keep the translated date format
2079 * compact, and avoid variable-width items such as month and
2080 * day names wherever possible. This allows us to disable
2081 * column autosizing, which makes the Rhythmbox UI much faster.
2083 sample
= eel_strdup_strftime (_("%Y-%m-%d %H:%M"), &someday
);
2089 rb_entry_view_resort_model (RBEntryView
*view
)
2091 struct RBEntryViewColumnSortData
*sort_data
;
2093 g_assert (view
->priv
->sorting_column
);
2094 sort_data
= g_hash_table_lookup (view
->priv
->column_sort_data_map
,
2095 view
->priv
->sorting_column
);
2096 g_assert (sort_data
);
2098 rhythmdb_query_model_set_sort_order (view
->priv
->model
,
2102 (view
->priv
->sorting_order
== GTK_SORT_DESCENDING
));
2105 /* This should really be standard. */
2106 #define ENUM_ENTRY(NAME, DESC) { NAME, "" #NAME "", DESC }
2109 rb_entry_view_column_get_type (void)
2111 static GType etype
= 0;
2114 static const GEnumValue values
[] = {
2115 ENUM_ENTRY (RB_ENTRY_VIEW_COL_TRACK_NUMBER
, "Track Number"),
2116 ENUM_ENTRY (RB_ENTRY_VIEW_COL_TITLE
, "Title"),
2117 ENUM_ENTRY (RB_ENTRY_VIEW_COL_ARTIST
, "Artist"),
2118 ENUM_ENTRY (RB_ENTRY_VIEW_COL_ALBUM
, "Album"),
2119 ENUM_ENTRY (RB_ENTRY_VIEW_COL_GENRE
, "Genre"),
2120 ENUM_ENTRY (RB_ENTRY_VIEW_COL_DURATION
, "Duration"),
2121 ENUM_ENTRY (RB_ENTRY_VIEW_COL_QUALITY
, "Quality"),
2122 ENUM_ENTRY (RB_ENTRY_VIEW_COL_RATING
, "Rating"),
2123 ENUM_ENTRY (RB_ENTRY_VIEW_COL_PLAY_COUNT
, "Play Count"),
2124 ENUM_ENTRY (RB_ENTRY_VIEW_COL_YEAR
, "Year"),
2125 ENUM_ENTRY (RB_ENTRY_VIEW_COL_LAST_PLAYED
, "Last Played"),
2126 ENUM_ENTRY (RB_ENTRY_VIEW_COL_FIRST_SEEN
, "First Seen"),
2127 ENUM_ENTRY (RB_ENTRY_VIEW_COL_LAST_SEEN
, "Last Seen"),
2128 ENUM_ENTRY (RB_ENTRY_VIEW_COL_LOCATION
, "Location"),
2129 ENUM_ENTRY (RB_ENTRY_VIEW_COL_ERROR
, "Error"),
2133 etype
= g_enum_register_static ("RBEntryViewColumn", values
);
2140 rb_entry_view_state_get_type (void)
2142 static GType etype
= 0;
2145 static const GEnumValue values
[] = {
2146 ENUM_ENTRY (RB_ENTRY_VIEW_NOT_PLAYING
, "Not Playing"),
2147 ENUM_ENTRY (RB_ENTRY_VIEW_PLAYING
, "Playing"),
2148 ENUM_ENTRY (RB_ENTRY_VIEW_PAUSED
, "Paused"),
2152 etype
= g_enum_register_static ("RBEntryViewState", values
);