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
[] = {{ "text/uri-list", 0, 0 }};
51 struct RBEntryViewColumnSortData
53 GCompareDataFunc func
;
57 static void rb_entry_view_class_init (RBEntryViewClass
*klass
);
58 static void rb_entry_view_init (RBEntryView
*view
);
59 static GObject
*rb_entry_view_constructor (GType type
, guint n_construct_properties
,
60 GObjectConstructParam
*construct_properties
);
61 static void rb_entry_view_finalize (GObject
*object
);
62 static void rb_entry_view_set_property (GObject
*object
,
66 static void rb_entry_view_get_property (GObject
*object
,
70 static void rb_entry_view_selection_changed_cb (GtkTreeSelection
*selection
,
72 static void rb_entry_view_grab_focus (GtkWidget
*widget
);
73 static void rb_entry_view_row_activated_cb (GtkTreeView
*treeview
,
75 GtkTreeViewColumn
*column
,
77 static void rb_entry_view_row_inserted_cb (GtkTreeModel
*model
,
81 static void rb_entry_view_row_deleted_cb (GtkTreeModel
*model
,
84 static void rb_entry_view_rows_reordered_cb (GtkTreeModel
*model
,
89 static void rb_entry_view_sync_columns_visible (RBEntryView
*view
);
90 static void rb_entry_view_columns_config_changed_cb (GConfClient
* client
,
94 static void rb_entry_view_sort_key_changed_cb (GConfClient
* client
,
98 static void rb_entry_view_rated_cb (RBCellRendererRating
*cellrating
,
102 static void rb_entry_view_pixbuf_clicked_cb (RBEntryView
*view
,
104 RBCellRendererPixbuf
*cellpixbuf
);
105 static gboolean
rb_entry_view_button_press_cb (GtkTreeView
*treeview
,
106 GdkEventButton
*event
,
108 static gboolean
rb_entry_view_popup_menu_cb (GtkTreeView
*treeview
,
110 static void rb_entry_view_entry_is_visible (RBEntryView
*view
, RhythmDBEntry
*entry
,
111 gboolean
*realized
, gboolean
*visible
,
113 static void rb_entry_view_scroll_to_iter (RBEntryView
*view
,
115 static gboolean
rb_entry_view_emit_row_changed (RBEntryView
*view
,
116 RhythmDBEntry
*entry
);
117 static void rb_entry_view_playing_song_changed (RBShellPlayer
*player
,
118 RhythmDBEntry
*entry
,
121 struct RBEntryViewPrivate
124 RBShellPlayer
*shell_player
;
126 RhythmDBQueryModel
*model
;
129 GtkTreeSelection
*selection
;
131 RBEntryViewState playing_state
;
132 RhythmDBQueryModel
*playing_model
;
133 RhythmDBEntry
*playing_entry
;
134 gboolean playing_entry_in_view
;
135 guint selection_changed_id
;
137 gboolean is_drag_source
;
138 gboolean is_drag_dest
;
140 GdkPixbuf
*playing_pixbuf
;
141 GdkPixbuf
*paused_pixbuf
;
142 GdkPixbuf
*error_pixbuf
;
145 guint sorting_gconf_notification_id
;
146 GtkTreeViewColumn
*sorting_column
;
148 char *sorting_column_name
;
150 gboolean have_selection
, have_complete_selection
;
152 GHashTable
*column_key_map
;
154 guint gconf_notification_id
;
155 GHashTable
*propid_column_map
;
156 GHashTable
*column_sort_data_map
;
159 #define RB_ENTRY_VIEW_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), RB_TYPE_ENTRY_VIEW, RBEntryViewPrivate))
186 G_DEFINE_TYPE (RBEntryView
, rb_entry_view
, GTK_TYPE_SCROLLED_WINDOW
)
188 static guint rb_entry_view_signals
[LAST_SIGNAL
] = { 0 };
190 static GQuark rb_entry_view_column_always_visible
;
193 type_ahead_search_func (GtkTreeModel
*model
,
197 gpointer search_data
)
199 RhythmDBEntry
*entry
;
201 const gchar
*entry_folded
;
204 gtk_tree_model_get (model
, iter
, 0, &entry
, -1);
205 folded
= rb_search_fold (key
);
206 entry_folded
= rhythmdb_entry_get_string (entry
, RHYTHMDB_PROP_TITLE_FOLDED
);
207 rhythmdb_entry_unref (entry
);
209 if (entry_folded
== NULL
|| folded
== NULL
)
212 res
= (strstr (entry_folded
, folded
) == NULL
);
219 rb_entry_view_class_init (RBEntryViewClass
*klass
)
221 GObjectClass
*object_class
= G_OBJECT_CLASS (klass
);
222 GtkWidgetClass
*widget_class
= GTK_WIDGET_CLASS (klass
);
224 object_class
->finalize
= rb_entry_view_finalize
;
225 object_class
->constructor
= rb_entry_view_constructor
;
227 object_class
->set_property
= rb_entry_view_set_property
;
228 object_class
->get_property
= rb_entry_view_get_property
;
230 widget_class
->grab_focus
= rb_entry_view_grab_focus
;
232 g_object_class_install_property (object_class
,
234 g_param_spec_object ("db",
238 G_PARAM_READWRITE
| G_PARAM_CONSTRUCT_ONLY
));
239 g_object_class_install_property (object_class
,
241 g_param_spec_object ("shell-player",
243 "RBShellPlayer object",
244 RB_TYPE_SHELL_PLAYER
,
245 G_PARAM_READWRITE
| G_PARAM_CONSTRUCT_ONLY
));
246 g_object_class_install_property (object_class
,
248 g_param_spec_object ("model",
249 "RhythmDBQueryModel",
250 "RhythmDBQueryModel",
251 RHYTHMDB_TYPE_QUERY_MODEL
,
253 g_object_class_install_property (object_class
,
255 g_param_spec_string ("sort-key",
259 G_PARAM_READWRITE
| G_PARAM_CONSTRUCT_ONLY
));
260 g_object_class_install_property (object_class
,
262 g_param_spec_boolean ("is-drag-source",
264 "whether or not this is a drag source",
266 G_PARAM_READWRITE
| G_PARAM_CONSTRUCT_ONLY
));
267 g_object_class_install_property (object_class
,
269 g_param_spec_boolean ("is-drag-dest",
271 "whether or not this is a drag dest",
273 G_PARAM_READWRITE
| G_PARAM_CONSTRUCT_ONLY
));
274 g_object_class_install_property (object_class
,
276 g_param_spec_int ("playing-state",
278 "playback state for this entry view",
279 RB_ENTRY_VIEW_NOT_PLAYING
,
280 RB_ENTRY_VIEW_PAUSED
,
281 RB_ENTRY_VIEW_NOT_PLAYING
,
283 rb_entry_view_signals
[ENTRY_ADDED
] =
284 g_signal_new ("entry-added",
285 G_OBJECT_CLASS_TYPE (object_class
),
287 G_STRUCT_OFFSET (RBEntryViewClass
, entry_added
),
289 g_cclosure_marshal_VOID__BOXED
,
292 RHYTHMDB_TYPE_ENTRY
);
293 rb_entry_view_signals
[ENTRY_DELETED
] =
294 g_signal_new ("entry-deleted",
295 G_OBJECT_CLASS_TYPE (object_class
),
297 G_STRUCT_OFFSET (RBEntryViewClass
, entry_deleted
),
299 g_cclosure_marshal_VOID__BOXED
,
302 RHYTHMDB_TYPE_ENTRY
);
303 rb_entry_view_signals
[ENTRIES_REPLACED
] =
304 g_signal_new ("entries-replaced",
305 G_OBJECT_CLASS_TYPE (object_class
),
307 G_STRUCT_OFFSET (RBEntryViewClass
, entries_replaced
),
309 g_cclosure_marshal_VOID__VOID
,
312 rb_entry_view_signals
[ENTRY_ACTIVATED
] =
313 g_signal_new ("entry-activated",
314 G_OBJECT_CLASS_TYPE (object_class
),
316 G_STRUCT_OFFSET (RBEntryViewClass
, entry_activated
),
318 g_cclosure_marshal_VOID__BOXED
,
321 RHYTHMDB_TYPE_ENTRY
);
322 rb_entry_view_signals
[SELECTION_CHANGED
] =
323 g_signal_new ("selection-changed",
324 G_OBJECT_CLASS_TYPE (object_class
),
326 G_STRUCT_OFFSET (RBEntryViewClass
, selection_changed
),
328 g_cclosure_marshal_VOID__VOID
,
331 rb_entry_view_signals
[SHOW_POPUP
] =
332 g_signal_new ("show_popup",
333 G_OBJECT_CLASS_TYPE (object_class
),
335 G_STRUCT_OFFSET (RBEntryViewClass
, show_popup
),
337 g_cclosure_marshal_VOID__BOOLEAN
,
341 rb_entry_view_signals
[HAVE_SEL_CHANGED
] =
342 g_signal_new ("have_selection_changed",
343 G_OBJECT_CLASS_TYPE (object_class
),
345 G_STRUCT_OFFSET (RBEntryViewClass
, have_selection_changed
),
347 g_cclosure_marshal_VOID__BOOLEAN
,
351 rb_entry_view_signals
[SORT_ORDER_CHANGED
] =
352 g_signal_new ("sort-order-changed",
353 G_OBJECT_CLASS_TYPE (object_class
),
355 G_STRUCT_OFFSET (RBEntryViewClass
, sort_order_changed
),
357 g_cclosure_marshal_VOID__VOID
,
361 g_type_class_add_private (klass
, sizeof (RBEntryViewPrivate
));
363 rb_entry_view_column_always_visible
= g_quark_from_static_string ("rb_entry_view_column_always_visible");
367 rb_entry_view_init (RBEntryView
*view
)
369 GtkIconTheme
*icon_theme
;
371 view
->priv
= RB_ENTRY_VIEW_GET_PRIVATE (view
);
373 icon_theme
= gtk_icon_theme_get_default ();
375 view
->priv
->playing_pixbuf
= gtk_icon_theme_load_icon (icon_theme
,
380 view
->priv
->paused_pixbuf
= gtk_icon_theme_load_icon (icon_theme
,
385 view
->priv
->error_pixbuf
= gtk_icon_theme_load_icon (icon_theme
,
386 "stock_dialog-error",
391 view
->priv
->propid_column_map
= g_hash_table_new (NULL
, NULL
);
392 view
->priv
->column_sort_data_map
= g_hash_table_new_full (NULL
, NULL
, NULL
, g_free
);
393 view
->priv
->column_key_map
= g_hash_table_new_full (g_str_hash
, g_str_equal
, g_free
, NULL
);
397 rb_entry_view_finalize (GObject
*object
)
401 g_return_if_fail (object
!= NULL
);
402 g_return_if_fail (RB_IS_ENTRY_VIEW (object
));
404 view
= RB_ENTRY_VIEW (object
);
406 g_return_if_fail (view
->priv
!= NULL
);
408 if (view
->priv
->gconf_notification_id
> 0)
409 eel_gconf_notification_remove (view
->priv
->gconf_notification_id
);
410 if (view
->priv
->sorting_gconf_notification_id
> 0)
411 eel_gconf_notification_remove (view
->priv
->sorting_gconf_notification_id
);
413 if (view
->priv
->selection_changed_id
> 0)
414 g_source_remove (view
->priv
->selection_changed_id
);
416 g_hash_table_destroy (view
->priv
->propid_column_map
);
417 g_hash_table_destroy (view
->priv
->column_sort_data_map
);
418 g_hash_table_destroy (view
->priv
->column_key_map
);
420 if (view
->priv
->playing_pixbuf
!= NULL
)
421 g_object_unref (view
->priv
->playing_pixbuf
);
422 if (view
->priv
->paused_pixbuf
!= NULL
)
423 g_object_unref (view
->priv
->paused_pixbuf
);
424 if (view
->priv
->error_pixbuf
!= NULL
)
425 g_object_unref (view
->priv
->error_pixbuf
);
427 if (view
->priv
->playing_model
!= NULL
) {
428 g_object_unref (view
->priv
->playing_model
);
430 if (view
->priv
->model
!= NULL
) {
431 g_object_unref (view
->priv
->model
);
434 g_free (view
->priv
->sorting_key
);
435 g_free (view
->priv
->sorting_column_name
);
437 G_OBJECT_CLASS (rb_entry_view_parent_class
)->finalize (object
);
441 rb_entry_view_set_shell_player_internal (RBEntryView
*view
,
442 RBShellPlayer
*player
)
444 if (view
->priv
->shell_player
!= NULL
) {
445 g_signal_handlers_disconnect_by_func (view
->priv
->shell_player
,
446 G_CALLBACK (rb_entry_view_playing_song_changed
),
450 view
->priv
->shell_player
= player
;
452 g_signal_connect_object (view
->priv
->shell_player
,
453 "playing-song-changed",
454 G_CALLBACK (rb_entry_view_playing_song_changed
),
459 rb_entry_view_set_model_internal (RBEntryView
*view
,
460 RhythmDBQueryModel
*model
)
462 if (view
->priv
->model
!= NULL
) {
463 g_signal_handlers_disconnect_by_func (view
->priv
->model
,
464 G_CALLBACK (rb_entry_view_row_inserted_cb
),
466 g_signal_handlers_disconnect_by_func (view
->priv
->model
,
467 G_CALLBACK (rb_entry_view_row_deleted_cb
),
469 g_signal_handlers_disconnect_by_func (view
->priv
->model
,
470 G_CALLBACK (rb_entry_view_rows_reordered_cb
),
472 g_object_unref (view
->priv
->model
);
475 gtk_tree_selection_unselect_all (view
->priv
->selection
);
477 view
->priv
->model
= model
;
478 if (view
->priv
->model
!= NULL
) {
479 g_object_ref (view
->priv
->model
);
480 g_signal_connect_object (view
->priv
->model
,
482 G_CALLBACK (rb_entry_view_row_inserted_cb
),
485 g_signal_connect_object (view
->priv
->model
,
487 G_CALLBACK (rb_entry_view_row_deleted_cb
),
490 g_signal_connect_object (view
->priv
->model
,
492 G_CALLBACK (rb_entry_view_rows_reordered_cb
),
497 if (view
->priv
->sorting_column
!= NULL
) {
498 rb_entry_view_resort_model (view
);
501 if (view
->priv
->model
!= NULL
) {
502 gtk_tree_view_set_model (GTK_TREE_VIEW (view
->priv
->treeview
),
503 GTK_TREE_MODEL (view
->priv
->model
));
506 view
->priv
->have_selection
= FALSE
;
507 view
->priv
->have_complete_selection
= FALSE
;
509 g_signal_emit (G_OBJECT (view
), rb_entry_view_signals
[ENTRIES_REPLACED
], 0);
513 rb_entry_view_set_property (GObject
*object
,
518 RBEntryView
*view
= RB_ENTRY_VIEW (object
);
522 view
->priv
->db
= g_value_get_object (value
);
524 case PROP_SHELL_PLAYER
:
525 rb_entry_view_set_shell_player_internal (view
, g_value_get_object (value
));
527 case PROP_SORTING_KEY
:
528 g_free (view
->priv
->sorting_key
);
529 view
->priv
->sorting_key
= g_value_dup_string (value
);
532 rb_entry_view_set_model_internal (view
, g_value_get_object (value
));
534 case PROP_IS_DRAG_SOURCE
:
535 view
->priv
->is_drag_source
= g_value_get_boolean (value
);
537 case PROP_IS_DRAG_DEST
:
538 view
->priv
->is_drag_dest
= g_value_get_boolean (value
);
540 case PROP_PLAYING_STATE
:
541 view
->priv
->playing_state
= g_value_get_int (value
);
543 /* redraw the playing entry, as the icon will have changed */
544 if (view
->priv
->playing_entry
!= NULL
) {
545 rb_entry_view_emit_row_changed (view
, view
->priv
->playing_entry
);
549 G_OBJECT_WARN_INVALID_PROPERTY_ID (object
, prop_id
, pspec
);
555 rb_entry_view_get_property (GObject
*object
,
560 RBEntryView
*view
= RB_ENTRY_VIEW (object
);
564 g_value_set_object (value
, view
->priv
->db
);
566 case PROP_SHELL_PLAYER
:
567 g_value_set_object (value
, view
->priv
->shell_player
);
569 case PROP_SORTING_KEY
:
570 g_value_set_string (value
, view
->priv
->sorting_key
);
572 case PROP_IS_DRAG_SOURCE
:
573 g_value_set_boolean (value
, view
->priv
->is_drag_source
);
575 case PROP_IS_DRAG_DEST
:
576 g_value_set_boolean (value
, view
->priv
->is_drag_dest
);
578 case PROP_PLAYING_STATE
:
579 g_value_set_int (value
, view
->priv
->playing_state
);
582 G_OBJECT_WARN_INVALID_PROPERTY_ID (object
, prop_id
, pspec
);
588 rb_entry_view_new (RhythmDB
*db
,
589 GObject
*shell_player
,
590 const char *sort_key
,
591 gboolean is_drag_source
,
592 gboolean is_drag_dest
)
596 view
= RB_ENTRY_VIEW (g_object_new (RB_TYPE_ENTRY_VIEW
,
599 "hscrollbar_policy", GTK_POLICY_AUTOMATIC
,
600 "vscrollbar_policy", GTK_POLICY_ALWAYS
,
601 "shadow_type", GTK_SHADOW_IN
,
603 "shell-player", RB_SHELL_PLAYER (shell_player
),
604 "sort-key", sort_key
,
605 "is-drag-source", is_drag_source
,
606 "is-drag-dest", is_drag_dest
,
609 g_return_val_if_fail (view
->priv
!= NULL
, NULL
);
615 rb_entry_view_set_model (RBEntryView
*view
,
616 RhythmDBQueryModel
*model
)
618 g_object_set (view
, "model", model
, NULL
);
621 /* Sweet name, eh? */
622 struct RBEntryViewCellDataFuncData
{
624 RhythmDBPropType propid
;
628 rb_entry_view_playing_cell_data_func (GtkTreeViewColumn
*column
,
629 GtkCellRenderer
*renderer
,
630 GtkTreeModel
*tree_model
,
634 RhythmDBEntry
*entry
;
635 GdkPixbuf
*pixbuf
= NULL
;
637 entry
= rhythmdb_query_model_iter_to_entry (view
->priv
->model
, iter
);
643 if (entry
== view
->priv
->playing_entry
) {
644 switch (view
->priv
->playing_state
) {
645 case RB_ENTRY_VIEW_PLAYING
:
646 pixbuf
= view
->priv
->playing_pixbuf
;
648 case RB_ENTRY_VIEW_PAUSED
:
649 pixbuf
= view
->priv
->paused_pixbuf
;
657 if (pixbuf
== NULL
&& rhythmdb_entry_get_string (entry
, RHYTHMDB_PROP_PLAYBACK_ERROR
)) {
658 pixbuf
= view
->priv
->error_pixbuf
;
661 g_object_set (renderer
, "pixbuf", pixbuf
, NULL
);
663 rhythmdb_entry_unref (entry
);
667 rb_entry_view_rating_cell_data_func (GtkTreeViewColumn
*column
,
668 GtkCellRenderer
*renderer
,
669 GtkTreeModel
*tree_model
,
673 RhythmDBEntry
*entry
;
675 entry
= rhythmdb_query_model_iter_to_entry (view
->priv
->model
, iter
);
677 g_object_set (renderer
,
678 "rating", rhythmdb_entry_get_double (entry
, RHYTHMDB_PROP_RATING
),
681 rhythmdb_entry_unref (entry
);
685 rb_entry_view_long_cell_data_func (GtkTreeViewColumn
*column
,
686 GtkCellRenderer
*renderer
,
687 GtkTreeModel
*tree_model
,
689 struct RBEntryViewCellDataFuncData
*data
)
691 RhythmDBEntry
*entry
;
695 entry
= rhythmdb_query_model_iter_to_entry (data
->view
->priv
->model
, iter
);
697 val
= rhythmdb_entry_get_ulong (entry
, data
->propid
);
700 str
= g_strdup_printf ("%lu", val
);
704 g_object_set (renderer
, "text", str
, NULL
);
706 rhythmdb_entry_unref (entry
);
710 rb_entry_view_play_count_cell_data_func (GtkTreeViewColumn
*column
,
711 GtkCellRenderer
*renderer
,
712 GtkTreeModel
*tree_model
,
714 struct RBEntryViewCellDataFuncData
*data
)
716 RhythmDBEntry
*entry
;
720 entry
= rhythmdb_query_model_iter_to_entry (data
->view
->priv
->model
, iter
);
722 i
= rhythmdb_entry_get_ulong (entry
, data
->propid
);
726 str
= g_strdup_printf ("%ld", i
);
728 g_object_set (renderer
, "text", str
, NULL
);
732 rhythmdb_entry_unref (entry
);
736 rb_entry_view_duration_cell_data_func (GtkTreeViewColumn
*column
,
737 GtkCellRenderer
*renderer
,
738 GtkTreeModel
*tree_model
,
740 struct RBEntryViewCellDataFuncData
*data
)
742 RhythmDBEntry
*entry
;
746 entry
= rhythmdb_query_model_iter_to_entry (data
->view
->priv
->model
, iter
);
747 duration
= rhythmdb_entry_get_ulong (entry
, RHYTHMDB_PROP_DURATION
);
749 str
= rb_make_duration_string (duration
);
750 g_object_set (renderer
, "text", str
, NULL
);
752 rhythmdb_entry_unref (entry
);
756 rb_entry_view_year_cell_data_func (GtkTreeViewColumn
*column
,
757 GtkCellRenderer
*renderer
,
758 GtkTreeModel
*tree_model
,
760 struct RBEntryViewCellDataFuncData
*data
)
762 RhythmDBEntry
*entry
;
767 entry
= rhythmdb_query_model_iter_to_entry (data
->view
->priv
->model
, iter
);
768 julian
= rhythmdb_entry_get_ulong (entry
, RHYTHMDB_PROP_DATE
);
771 date
= g_date_new_julian (julian
);
772 g_date_strftime (str
, sizeof (str
), "%Y", date
);
773 g_object_set (renderer
, "text", str
, NULL
);
776 g_object_set (renderer
, "text", _("Unknown"), NULL
);
779 rhythmdb_entry_unref (entry
);
783 rb_entry_view_quality_cell_data_func (GtkTreeViewColumn
*column
,
784 GtkCellRenderer
*renderer
,
785 GtkTreeModel
*tree_model
,
787 struct RBEntryViewCellDataFuncData
*data
)
789 RhythmDBEntry
*entry
;
792 entry
= rhythmdb_query_model_iter_to_entry (data
->view
->priv
->model
, iter
);
794 bitrate
= rhythmdb_entry_get_ulong (entry
, data
->propid
);
797 char *s
= g_strdup_printf (_("%u kbps"), (guint
)bitrate
);
798 g_object_set (renderer
, "text", s
, NULL
);
801 g_object_set (renderer
, "text", _("Unknown"), NULL
);
804 rhythmdb_entry_unref (entry
);
808 rb_entry_view_location_cell_data_func (GtkTreeViewColumn
*column
,
809 GtkCellRenderer
*renderer
,
810 GtkTreeModel
*tree_model
,
812 struct RBEntryViewCellDataFuncData
*data
)
814 RhythmDBEntry
*entry
;
815 const char *location
;
818 entry
= rhythmdb_query_model_iter_to_entry (data
->view
->priv
->model
, iter
);
820 location
= rhythmdb_entry_get_string (entry
, data
->propid
);
821 str
= gnome_vfs_unescape_string_for_display (location
);
823 g_object_set (renderer
, "text", str
, NULL
);
826 rhythmdb_entry_unref (entry
);
830 rb_entry_view_string_cell_data_func (GtkTreeViewColumn
*column
,
831 GtkCellRenderer
*renderer
,
832 GtkTreeModel
*tree_model
,
834 struct RBEntryViewCellDataFuncData
*data
)
836 RhythmDBEntry
*entry
;
839 entry
= rhythmdb_query_model_iter_to_entry (data
->view
->priv
->model
, iter
);
841 str
= rhythmdb_entry_get_string (entry
, data
->propid
);
843 g_object_set (renderer
, "text", str
, NULL
);
846 rhythmdb_entry_unref (entry
);
850 rb_entry_view_sync_sorting (RBEntryView
*view
)
852 GtkTreeViewColumn
*column
;
856 direction
= GTK_SORT_ASCENDING
;
858 rb_entry_view_get_sorting_order (view
, &column_name
, &direction
);
860 if (column_name
== NULL
) {
864 column
= g_hash_table_lookup (view
->priv
->column_key_map
, column_name
);
865 if (column
== NULL
) {
866 g_free (column_name
);
870 rb_debug ("Updating EntryView sort order to %s:%d", column_name
, direction
);
872 /* remove the old sorting indicator */
873 if (view
->priv
->sorting_column
)
874 gtk_tree_view_column_set_sort_indicator (view
->priv
->sorting_column
, FALSE
);
876 /* set the sorting order and indicator of the new sorting column */
877 view
->priv
->sorting_column
= column
;
878 gtk_tree_view_column_set_sort_indicator (column
, TRUE
);
879 gtk_tree_view_column_set_sort_order (column
, direction
);
881 rb_debug ("emitting sort order changed");
882 g_signal_emit (G_OBJECT (view
), rb_entry_view_signals
[SORT_ORDER_CHANGED
], 0);
884 g_free (column_name
);
888 rb_entry_view_get_sorting_type (RBEntryView
*view
)
891 GString
*key
= g_string_new (view
->priv
->sorting_column_name
);
893 g_string_append_c (key
, ',');
895 switch (view
->priv
->sorting_order
)
897 case GTK_SORT_ASCENDING
:
898 g_string_append (key
, "ascending");
900 case GTK_SORT_DESCENDING
:
901 g_string_append (key
, "descending");
904 g_assert_not_reached ();
907 sorttype
= g_strdup(key
->str
);
908 g_string_free (key
, TRUE
);
914 rb_entry_view_set_sorting_type (RBEntryView
*view
,
915 const char *sorttype
)
919 if (!sorttype
|| !strchr (sorttype
, ',')) {
920 rb_debug ("malformed sort data: %s", (sorttype
) ? sorttype
: "(null)");
924 strs
= g_strsplit (sorttype
, ",", 0);
926 g_free (view
->priv
->sorting_column_name
);
927 view
->priv
->sorting_column_name
= g_strdup(strs
[0]);
929 if (!strcmp ("ascending", strs
[1]))
930 view
->priv
->sorting_order
= GTK_SORT_ASCENDING
;
931 else if (!strcmp ("descending", strs
[1]))
932 view
->priv
->sorting_order
= GTK_SORT_DESCENDING
;
934 g_warning ("atttempting to sort in unknown direction");
935 view
->priv
->sorting_order
= GTK_SORT_ASCENDING
;
940 rb_entry_view_sync_sorting (view
);
944 rb_entry_view_get_sorting_order (RBEntryView
*view
,
948 g_return_if_fail (RB_IS_ENTRY_VIEW (view
));
950 if (column_name
!= NULL
) {
951 *column_name
= g_strdup (view
->priv
->sorting_column_name
);
954 if (sort_order
!= NULL
) {
955 *sort_order
= view
->priv
->sorting_order
;
960 rb_entry_view_set_sorting_order (RBEntryView
*view
,
961 const char *column_name
,
964 if (column_name
== NULL
)
967 g_free (view
->priv
->sorting_column_name
);
968 view
->priv
->sorting_column_name
= g_strdup (column_name
);
969 view
->priv
->sorting_order
= sort_order
;
971 rb_entry_view_sync_sorting (view
);
975 rb_entry_view_column_clicked_cb (GtkTreeViewColumn
*column
, RBEntryView
*view
)
978 char *clicked_column
;
980 rb_debug ("sorting on column %p", column
);
982 /* identify the clicked column, and then update the sorting order */
983 clicked_column
= (char*) g_object_get_data (G_OBJECT (column
), "rb-entry-view-key");
984 sort_order
= view
->priv
->sorting_order
;
986 if (view
->priv
->sorting_column_name
987 && !strcmp(clicked_column
, view
->priv
->sorting_column_name
)
988 && (sort_order
== GTK_SORT_ASCENDING
))
989 sort_order
= GTK_SORT_DESCENDING
;
991 sort_order
= GTK_SORT_ASCENDING
;
993 rb_entry_view_set_sorting_order (view
, clicked_column
, sort_order
);
995 /* update the sort order in GConf */
996 if (view
->priv
->sorting_key
)
997 eel_gconf_set_string (view
->priv
->sorting_key
, rb_entry_view_get_sorting_type(view
));
1001 rb_entry_view_append_column (RBEntryView
*view
,
1002 RBEntryViewColumn coltype
,
1003 gboolean always_visible
)
1005 GtkTreeViewColumn
*column
;
1006 GtkCellRenderer
*renderer
= NULL
;
1007 struct RBEntryViewCellDataFuncData
*cell_data
;
1008 const char *title
= NULL
;
1009 const char *key
= NULL
;
1010 const char *strings
[4] = {0};
1011 GtkTreeCellDataFunc cell_data_func
= NULL
;
1012 GCompareDataFunc sort_func
= NULL
;
1013 RhythmDBPropType propid
;
1014 RhythmDBPropType sort_propid
= RHYTHMDB_NUM_PROPERTIES
;
1015 gboolean ellipsize
= FALSE
;
1016 gboolean resizable
= TRUE
;
1017 gint column_width
= -1;
1019 column
= gtk_tree_view_column_new ();
1021 cell_data
= g_new0 (struct RBEntryViewCellDataFuncData
, 1);
1022 cell_data
->view
= view
;
1025 case RB_ENTRY_VIEW_COL_TRACK_NUMBER
:
1026 propid
= RHYTHMDB_PROP_TRACK_NUMBER
;
1027 cell_data
->propid
= propid
;
1028 cell_data_func
= (GtkTreeCellDataFunc
) rb_entry_view_long_cell_data_func
;
1029 sort_func
= (GCompareDataFunc
) rhythmdb_query_model_track_sort_func
;
1030 title
= _("Trac_k");
1033 strings
[1] = "9999";
1035 case RB_ENTRY_VIEW_COL_TITLE
:
1036 propid
= RHYTHMDB_PROP_TITLE
;
1037 cell_data
->propid
= propid
;
1038 cell_data_func
= (GtkTreeCellDataFunc
) rb_entry_view_string_cell_data_func
;
1039 sort_propid
= RHYTHMDB_PROP_TITLE_SORT_KEY
;
1040 sort_func
= (GCompareDataFunc
) rhythmdb_query_model_string_sort_func
;
1041 title
= _("_Title");
1045 case RB_ENTRY_VIEW_COL_ARTIST
:
1046 propid
= RHYTHMDB_PROP_ARTIST
;
1047 cell_data
->propid
= propid
;
1048 cell_data_func
= (GtkTreeCellDataFunc
) rb_entry_view_string_cell_data_func
;
1049 sort_propid
= RHYTHMDB_PROP_ARTIST_SORT_KEY
;
1050 sort_func
= (GCompareDataFunc
) rhythmdb_query_model_artist_sort_func
;
1051 title
= _("Art_ist");
1055 case RB_ENTRY_VIEW_COL_ALBUM
:
1056 propid
= RHYTHMDB_PROP_ALBUM
;
1057 cell_data
->propid
= propid
;
1058 cell_data_func
= (GtkTreeCellDataFunc
) rb_entry_view_string_cell_data_func
;
1059 sort_propid
= RHYTHMDB_PROP_ALBUM_SORT_KEY
;
1060 sort_func
= (GCompareDataFunc
) rhythmdb_query_model_album_sort_func
;
1061 title
= _("_Album");
1065 case RB_ENTRY_VIEW_COL_GENRE
:
1066 propid
= RHYTHMDB_PROP_GENRE
;
1067 cell_data
->propid
= propid
;
1068 cell_data_func
= (GtkTreeCellDataFunc
) rb_entry_view_string_cell_data_func
;
1069 sort_propid
= RHYTHMDB_PROP_GENRE_SORT_KEY
;
1070 sort_func
= (GCompareDataFunc
) rhythmdb_query_model_genre_sort_func
;
1071 title
= _("_Genre");
1075 case RB_ENTRY_VIEW_COL_DURATION
:
1076 propid
= RHYTHMDB_PROP_DURATION
;
1077 cell_data
->propid
= propid
;
1078 cell_data_func
= (GtkTreeCellDataFunc
) rb_entry_view_duration_cell_data_func
;
1079 sort_propid
= cell_data
->propid
;
1080 sort_func
= (GCompareDataFunc
) rhythmdb_query_model_ulong_sort_func
;
1084 strings
[1] = "000:00";
1085 strings
[2] = _("Unknown");
1087 case RB_ENTRY_VIEW_COL_YEAR
:
1088 propid
= RHYTHMDB_PROP_DATE
;
1089 cell_data
->propid
= propid
;
1090 cell_data_func
= (GtkTreeCellDataFunc
) rb_entry_view_year_cell_data_func
;
1091 sort_propid
= cell_data
->propid
;
1092 sort_func
= (GCompareDataFunc
) rhythmdb_query_model_date_sort_func
;
1096 strings
[1] = "0000";
1097 strings
[2] = _("Unknown");
1099 case RB_ENTRY_VIEW_COL_QUALITY
:
1100 propid
= RHYTHMDB_PROP_BITRATE
;
1101 cell_data
->propid
= propid
;
1102 cell_data_func
= (GtkTreeCellDataFunc
) rb_entry_view_quality_cell_data_func
;
1103 sort_propid
= cell_data
->propid
;
1104 sort_func
= (GCompareDataFunc
) rhythmdb_query_model_ulong_sort_func
;
1105 title
= _("_Quality");
1108 strings
[1] = _("000 kbps");
1109 strings
[2] = _("Unknown");
1111 case RB_ENTRY_VIEW_COL_RATING
:
1112 propid
= RHYTHMDB_PROP_RATING
;
1113 sort_func
= (GCompareDataFunc
) rhythmdb_query_model_double_ceiling_sort_func
;
1115 gtk_icon_size_lookup (GTK_ICON_SIZE_MENU
, &column_width
, NULL
);
1116 column_width
= column_width
* 5 + 5;
1118 title
= _("_Rating");
1121 renderer
= rb_cell_renderer_rating_new ();
1122 gtk_tree_view_column_pack_start (column
, renderer
, TRUE
);
1123 gtk_tree_view_column_set_cell_data_func (column
, renderer
,
1124 (GtkTreeCellDataFunc
)
1125 rb_entry_view_rating_cell_data_func
,
1128 g_signal_connect_object (renderer
,
1130 G_CALLBACK (rb_entry_view_rated_cb
),
1134 case RB_ENTRY_VIEW_COL_PLAY_COUNT
:
1135 propid
= RHYTHMDB_PROP_PLAY_COUNT
;
1136 cell_data
->propid
= propid
;
1137 cell_data_func
= (GtkTreeCellDataFunc
) rb_entry_view_play_count_cell_data_func
;
1138 sort_propid
= cell_data
->propid
;
1139 sort_func
= (GCompareDataFunc
) rhythmdb_query_model_ulong_sort_func
;
1140 title
= _("_Play Count");
1143 strings
[1] = _("Never");
1144 strings
[2] = "9999";
1146 case RB_ENTRY_VIEW_COL_LAST_PLAYED
:
1147 propid
= RHYTHMDB_PROP_LAST_PLAYED
;
1148 cell_data
->propid
= RHYTHMDB_PROP_LAST_PLAYED_STR
;
1149 cell_data_func
= (GtkTreeCellDataFunc
) rb_entry_view_string_cell_data_func
;
1150 sort_propid
= RHYTHMDB_PROP_LAST_PLAYED
;
1151 sort_func
= (GCompareDataFunc
) rhythmdb_query_model_ulong_sort_func
;
1152 title
= _("_Last Played");
1155 strings
[1] = rb_entry_view_get_time_date_column_sample ();
1156 strings
[2] = _("Never");
1158 case RB_ENTRY_VIEW_COL_FIRST_SEEN
:
1159 propid
= RHYTHMDB_PROP_FIRST_SEEN
;
1160 cell_data
->propid
= RHYTHMDB_PROP_FIRST_SEEN_STR
;
1161 cell_data_func
= (GtkTreeCellDataFunc
) rb_entry_view_string_cell_data_func
;
1162 sort_propid
= RHYTHMDB_PROP_FIRST_SEEN
;
1163 sort_func
= (GCompareDataFunc
) rhythmdb_query_model_ulong_sort_func
;
1164 title
= _("_Date Added");
1167 strings
[1] = rb_entry_view_get_time_date_column_sample ();
1169 case RB_ENTRY_VIEW_COL_LAST_SEEN
:
1170 propid
= RHYTHMDB_PROP_LAST_SEEN
;
1171 cell_data
->propid
= RHYTHMDB_PROP_LAST_SEEN_STR
;
1172 cell_data_func
= (GtkTreeCellDataFunc
) rb_entry_view_string_cell_data_func
;
1173 sort_propid
= RHYTHMDB_PROP_LAST_SEEN
;
1174 sort_func
= (GCompareDataFunc
) rhythmdb_query_model_ulong_sort_func
;
1175 title
= _("Last _Seen");
1178 strings
[1] = rb_entry_view_get_time_date_column_sample ();
1180 case RB_ENTRY_VIEW_COL_LOCATION
:
1181 propid
= RHYTHMDB_PROP_LOCATION
;
1182 cell_data
->propid
= RHYTHMDB_PROP_LOCATION
;
1183 cell_data_func
= (GtkTreeCellDataFunc
) rb_entry_view_location_cell_data_func
;
1184 sort_propid
= RHYTHMDB_PROP_LOCATION
;
1185 sort_func
= (GCompareDataFunc
) rhythmdb_query_model_location_sort_func
;
1186 title
= _("L_ocation");
1190 case RB_ENTRY_VIEW_COL_ERROR
:
1191 propid
= RHYTHMDB_PROP_PLAYBACK_ERROR
;
1192 cell_data
->propid
= RHYTHMDB_PROP_PLAYBACK_ERROR
;
1193 cell_data_func
= (GtkTreeCellDataFunc
) rb_entry_view_string_cell_data_func
;
1199 g_assert_not_reached ();
1204 if (sort_propid
== RHYTHMDB_NUM_PROPERTIES
)
1205 sort_propid
= propid
;
1207 if (renderer
== NULL
) {
1208 renderer
= gtk_cell_renderer_text_new ();
1209 gtk_tree_view_column_pack_start (column
, renderer
, TRUE
);
1210 gtk_tree_view_column_set_cell_data_func (column
, renderer
,
1211 cell_data_func
, cell_data
, g_free
);
1217 * Columns must either be expanding (ellipsized) or have a
1218 * fixed minimum width specified. Otherwise, gtk+ gives them a
1222 g_object_set (renderer
, "ellipsize", PANGO_ELLIPSIZE_END
, NULL
);
1223 gtk_tree_view_column_set_expand (GTK_TREE_VIEW_COLUMN (column
), TRUE
);
1224 } else if (column_width
!= -1) {
1225 gtk_tree_view_column_set_fixed_width (column
, column_width
);
1227 rb_entry_view_set_fixed_column_width (view
, column
, renderer
, strings
);
1231 gtk_tree_view_column_set_resizable (column
, TRUE
);
1233 gtk_tree_view_column_set_sizing (column
, GTK_TREE_VIEW_COLUMN_FIXED
);
1234 gtk_tree_view_column_set_clickable (column
, TRUE
);
1237 g_object_set_qdata (G_OBJECT (column
),
1238 rb_entry_view_column_always_visible
,
1239 GINT_TO_POINTER (1));
1241 g_hash_table_insert (view
->priv
->propid_column_map
, GINT_TO_POINTER (propid
), column
);
1243 rb_entry_view_append_column_custom (view
, column
, title
, key
, sort_func
, GINT_TO_POINTER (sort_propid
));
1247 rb_entry_view_append_column_custom (RBEntryView
*view
,
1248 GtkTreeViewColumn
*column
,
1251 GCompareDataFunc sort_func
,
1254 rb_entry_view_insert_column_custom (view
, column
, title
, key
, sort_func
, data
, -1);
1258 rb_entry_view_insert_column_custom (RBEntryView
*view
,
1259 GtkTreeViewColumn
*column
,
1262 GCompareDataFunc sort_func
,
1266 struct RBEntryViewColumnSortData
*sortdata
;
1268 gtk_tree_view_column_set_title (column
, title
);
1269 gtk_tree_view_column_set_reorderable (column
, FALSE
);
1271 g_signal_connect_object (column
, "clicked",
1272 G_CALLBACK (rb_entry_view_column_clicked_cb
),
1275 g_object_set_data_full (G_OBJECT (column
), "rb-entry-view-key",
1276 g_strdup (key
), g_free
);
1278 rb_debug ("appending column: %p (%s)", column
, title
);
1280 gtk_tree_view_insert_column (GTK_TREE_VIEW (view
->priv
->treeview
), column
, position
);
1282 if (sort_func
!= NULL
) {
1283 sortdata
= g_new (struct RBEntryViewColumnSortData
, 1);
1284 sortdata
->func
= (GCompareDataFunc
) sort_func
;
1285 sortdata
->data
= data
;
1286 g_hash_table_insert (view
->priv
->column_sort_data_map
, column
, sortdata
);
1288 g_hash_table_insert (view
->priv
->column_key_map
, g_strdup (key
), column
);
1290 rb_entry_view_sync_columns_visible (view
);
1291 rb_entry_view_sync_sorting (view
);
1295 rb_entry_view_set_columns_clickable (RBEntryView
*view
,
1298 GList
*columns
, *tem
;
1300 columns
= gtk_tree_view_get_columns (GTK_TREE_VIEW (view
->priv
->treeview
));
1301 for (tem
= columns
; tem
; tem
= tem
->next
) {
1302 /* only columns we can sort on should be clickable */
1303 GtkTreeViewColumn
*column
= (GtkTreeViewColumn
*) tem
->data
;
1304 if (g_hash_table_lookup (view
->priv
->column_sort_data_map
, column
) != NULL
)
1305 gtk_tree_view_column_set_clickable (tem
->data
, clickable
);
1307 g_list_free (columns
);
1311 rb_entry_view_constructor (GType type
,
1312 guint n_construct_properties
,
1313 GObjectConstructParam
*construct_properties
)
1316 RBEntryViewClass
*klass
;
1317 klass
= RB_ENTRY_VIEW_CLASS (g_type_class_peek (RB_TYPE_ENTRY_VIEW
));
1319 view
= RB_ENTRY_VIEW (G_OBJECT_CLASS (rb_entry_view_parent_class
)
1320 ->constructor (type
, n_construct_properties
, construct_properties
));
1322 view
->priv
->treeview
= GTK_WIDGET (gtk_tree_view_new ());
1323 gtk_tree_view_set_fixed_height_mode (GTK_TREE_VIEW (view
->priv
->treeview
), TRUE
);
1325 gtk_tree_view_set_search_equal_func (GTK_TREE_VIEW (view
->priv
->treeview
),
1326 type_ahead_search_func
,
1329 g_signal_connect_object (view
->priv
->treeview
,
1330 "button_press_event",
1331 G_CALLBACK (rb_entry_view_button_press_cb
),
1334 g_signal_connect_object (view
->priv
->treeview
,
1336 G_CALLBACK (rb_entry_view_row_activated_cb
),
1339 g_signal_connect_object (view
->priv
->treeview
,
1341 G_CALLBACK (rb_entry_view_popup_menu_cb
),
1344 view
->priv
->selection
= gtk_tree_view_get_selection (GTK_TREE_VIEW (view
->priv
->treeview
));
1345 g_signal_connect_object (view
->priv
->selection
,
1347 G_CALLBACK (rb_entry_view_selection_changed_cb
),
1351 gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (view
->priv
->treeview
), TRUE
);
1352 gtk_tree_view_set_rules_hint (GTK_TREE_VIEW (view
->priv
->treeview
), TRUE
);
1353 gtk_tree_selection_set_mode (view
->priv
->selection
, GTK_SELECTION_MULTIPLE
);
1355 if (view
->priv
->is_drag_source
) {
1356 rb_tree_dnd_add_drag_source_support (GTK_TREE_VIEW (view
->priv
->treeview
),
1358 rb_entry_view_drag_types
,
1359 G_N_ELEMENTS (rb_entry_view_drag_types
),
1363 if (view
->priv
->is_drag_dest
) {
1364 rb_tree_dnd_add_drag_dest_support (GTK_TREE_VIEW (view
->priv
->treeview
),
1365 RB_TREE_DEST_CAN_DROP_BETWEEN
| RB_TREE_DEST_EMPTY_VIEW_DROP
,
1366 rb_entry_view_drag_types
,
1367 G_N_ELEMENTS (rb_entry_view_drag_types
),
1368 GDK_ACTION_COPY
| GDK_ACTION_MOVE
);
1371 gtk_container_add (GTK_CONTAINER (view
), view
->priv
->treeview
);
1374 GtkTreeViewColumn
*column
;
1375 GtkTooltips
*tooltip
;
1376 GtkCellRenderer
*renderer
;
1377 GtkWidget
*image_widget
;
1380 tooltip
= gtk_tooltips_new ();
1382 /* Playing icon column */
1383 column
= GTK_TREE_VIEW_COLUMN (gtk_tree_view_column_new ());
1384 renderer
= rb_cell_renderer_pixbuf_new ();
1385 gtk_tree_view_column_pack_start (column
, renderer
, TRUE
);
1386 gtk_tree_view_column_set_cell_data_func (column
, renderer
,
1387 (GtkTreeCellDataFunc
)
1388 rb_entry_view_playing_cell_data_func
,
1392 image_widget
= gtk_image_new_from_icon_name ("stock_volume-max", GTK_ICON_SIZE_MENU
);
1393 gtk_tree_view_column_set_widget (column
, image_widget
);
1394 gtk_widget_show (image_widget
);
1396 gtk_tree_view_column_set_sizing (column
, GTK_TREE_VIEW_COLUMN_FIXED
);
1397 gtk_tree_view_column_set_clickable (column
, FALSE
);
1398 gtk_icon_size_lookup (GTK_ICON_SIZE_MENU
, &width
, NULL
);
1399 gtk_tree_view_column_set_fixed_width (column
, width
+ 5);
1400 gtk_tree_view_append_column (GTK_TREE_VIEW (view
->priv
->treeview
), column
);
1401 g_signal_connect_swapped (renderer
,
1403 G_CALLBACK (rb_entry_view_pixbuf_clicked_cb
),
1406 gtk_tooltips_set_tip (GTK_TOOLTIPS (tooltip
), GTK_WIDGET (column
->button
),
1407 _("Now Playing"), NULL
);
1410 view
->priv
->gconf_notification_id
=
1411 eel_gconf_notification_add (CONF_UI_COLUMNS_SETUP
,
1412 rb_entry_view_columns_config_changed_cb
,
1414 if (view
->priv
->sorting_key
) {
1415 view
->priv
->sorting_gconf_notification_id
=
1416 eel_gconf_notification_add (view
->priv
->sorting_key
,
1417 rb_entry_view_sort_key_changed_cb
,
1421 if (view
->priv
->sorting_key
) {
1422 char *s
= eel_gconf_get_string (view
->priv
->sorting_key
);
1423 rb_entry_view_set_sorting_type (view
, s
);
1428 RhythmDBQueryModel
*query_model
;
1429 query_model
= rhythmdb_query_model_new_empty (view
->priv
->db
);
1430 rb_entry_view_set_model (view
, RHYTHMDB_QUERY_MODEL (query_model
));
1431 g_object_unref (query_model
);
1434 return G_OBJECT (view
);
1438 rb_entry_view_rated_cb (RBCellRendererRating
*cellrating
,
1439 const char *path_string
,
1444 RhythmDBEntry
*entry
;
1445 GValue value
= { 0, };
1447 g_return_if_fail (rating
>= 0 && rating
<= 5 );
1448 g_return_if_fail (path_string
!= NULL
);
1450 path
= gtk_tree_path_new_from_string (path_string
);
1451 entry
= rhythmdb_query_model_tree_path_to_entry (view
->priv
->model
, path
);
1452 gtk_tree_path_free (path
);
1454 g_value_init (&value
, G_TYPE_DOUBLE
);
1455 g_value_set_double (&value
, rating
);
1456 rhythmdb_entry_set (view
->priv
->db
, entry
, RHYTHMDB_PROP_RATING
, &value
);
1457 g_value_unset (&value
);
1459 rhythmdb_commit (view
->priv
->db
);
1461 rhythmdb_entry_unref (entry
);
1465 rb_entry_view_pixbuf_clicked_cb (RBEntryView
*view
,
1466 const char *path_string
,
1467 RBCellRendererPixbuf
*cellpixbuf
)
1470 RhythmDBEntry
*entry
;
1473 g_return_if_fail (path_string
!= NULL
);
1475 path
= gtk_tree_path_new_from_string (path_string
);
1476 entry
= rhythmdb_query_model_tree_path_to_entry (view
->priv
->model
, path
);
1478 gtk_tree_path_free (path
);
1480 error
= rhythmdb_entry_get_string (entry
, RHYTHMDB_PROP_PLAYBACK_ERROR
);
1482 rb_error_dialog (NULL
, _("Playback Error"), "%s", error
);
1485 rhythmdb_entry_unref (entry
);
1489 rb_entry_view_playing_song_changed (RBShellPlayer
*player
,
1490 RhythmDBEntry
*entry
,
1493 gboolean realized
, visible
;
1496 g_return_if_fail (RB_IS_ENTRY_VIEW (view
));
1498 if (view
->priv
->playing_entry
!= NULL
) {
1499 if (view
->priv
->playing_state
!= RB_ENTRY_VIEW_NOT_PLAYING
)
1500 rb_entry_view_emit_row_changed (view
, view
->priv
->playing_entry
);
1501 g_object_unref (view
->priv
->playing_model
);
1504 view
->priv
->playing_entry
= entry
;
1505 view
->priv
->playing_model
= view
->priv
->model
;
1506 g_object_ref (view
->priv
->playing_model
);
1508 if (view
->priv
->playing_state
!= RB_ENTRY_VIEW_NOT_PLAYING
) {
1509 if (view
->priv
->playing_entry
!= NULL
) {
1510 view
->priv
->playing_entry_in_view
=
1511 rb_entry_view_emit_row_changed (view
, view
->priv
->playing_entry
);
1514 if (view
->priv
->playing_entry
1515 && view
->priv
->playing_entry_in_view
) {
1516 rb_entry_view_entry_is_visible (view
, view
->priv
->playing_entry
,
1517 &realized
, &visible
, &iter
);
1518 if (realized
&& !visible
)
1519 rb_entry_view_scroll_to_iter (view
, &iter
);
1525 harvest_entries (GtkTreeModel
*model
,
1530 RhythmDBEntry
*entry
;
1532 gtk_tree_model_get (model
, iter
, 0, &entry
, -1);
1534 *list
= g_list_prepend (*list
, entry
);
1540 rb_entry_view_get_selected_entries (RBEntryView
*view
)
1544 gtk_tree_selection_selected_foreach (view
->priv
->selection
,
1545 (GtkTreeSelectionForeachFunc
) harvest_entries
,
1548 list
= g_list_reverse (list
);
1553 rb_entry_view_button_press_cb (GtkTreeView
*treeview
,
1554 GdkEventButton
*event
,
1557 if (event
->button
== 3) {
1559 RhythmDBEntry
*entry
;
1561 gtk_tree_view_get_path_at_pos (treeview
, event
->x
, event
->y
, &path
, NULL
, NULL
, NULL
);
1564 entry
= rhythmdb_query_model_tree_path_to_entry (view
->priv
->model
, path
);
1566 selected
= rb_entry_view_get_selected_entries (view
);
1568 if (!g_list_find (selected
, entry
))
1569 rb_entry_view_select_entry (view
, entry
);
1571 g_list_free (selected
);
1573 rhythmdb_entry_unref (entry
);
1575 g_signal_emit (G_OBJECT (view
), rb_entry_view_signals
[SHOW_POPUP
], 0, (path
!= NULL
));
1583 rb_entry_view_popup_menu_cb (GtkTreeView
*treeview
,
1586 if (gtk_tree_selection_count_selected_rows (gtk_tree_view_get_selection (treeview
)) == 0)
1589 g_signal_emit (G_OBJECT (view
), rb_entry_view_signals
[SHOW_POPUP
], 0);
1594 rb_entry_view_emit_selection_changed (RBEntryView
*view
)
1599 GDK_THREADS_ENTER ();
1600 sel_count
= gtk_tree_selection_count_selected_rows (view
->priv
->selection
);
1601 available
= (sel_count
> 0);
1603 if (available
!= view
->priv
->have_selection
) {
1606 entry_count
= gtk_tree_model_iter_n_children (GTK_TREE_MODEL (view
->priv
->model
), NULL
);
1607 view
->priv
->have_complete_selection
= (sel_count
== entry_count
);
1609 view
->priv
->have_selection
= available
;
1611 g_signal_emit (G_OBJECT (view
), rb_entry_view_signals
[HAVE_SEL_CHANGED
], 0, available
);
1614 view
->priv
->selection_changed_id
= 0;
1615 g_signal_emit (G_OBJECT (view
), rb_entry_view_signals
[SELECTION_CHANGED
], 0);
1617 GDK_THREADS_LEAVE ();
1622 rb_entry_view_selection_changed_cb (GtkTreeSelection
*selection
,
1625 if (view
->priv
->selection_changed_id
== 0)
1626 view
->priv
->selection_changed_id
= g_idle_add ((GSourceFunc
)rb_entry_view_emit_selection_changed
, view
);
1630 rb_entry_view_have_selection (RBEntryView
*view
)
1632 return view
->priv
->have_selection
;
1636 rb_entry_view_have_complete_selection (RBEntryView
*view
)
1638 return view
->priv
->have_complete_selection
;
1642 rb_entry_view_row_activated_cb (GtkTreeView
*treeview
,
1644 GtkTreeViewColumn
*column
,
1647 RhythmDBEntry
*entry
;
1649 rb_debug ("row activated");
1651 entry
= rhythmdb_query_model_tree_path_to_entry (view
->priv
->model
, path
);
1653 rb_debug ("emitting entry activated");
1654 g_signal_emit (G_OBJECT (view
), rb_entry_view_signals
[ENTRY_ACTIVATED
], 0, entry
);
1656 rhythmdb_entry_unref (entry
);
1660 rb_entry_view_row_inserted_cb (GtkTreeModel
*model
,
1665 RhythmDBEntry
*entry
= rhythmdb_query_model_tree_path_to_entry (RHYTHMDB_QUERY_MODEL (model
), path
);
1667 rb_debug ("row added");
1668 g_signal_emit (G_OBJECT (view
), rb_entry_view_signals
[ENTRY_ADDED
], 0, entry
);
1669 rhythmdb_entry_unref (entry
);
1673 rb_entry_view_row_deleted_cb (GtkTreeModel
*model
,
1677 RhythmDBEntry
*entry
= rhythmdb_query_model_tree_path_to_entry (RHYTHMDB_QUERY_MODEL (model
), path
);
1679 rb_debug ("row deleted");
1680 g_signal_emit (G_OBJECT (view
), rb_entry_view_signals
[ENTRY_DELETED
], 0, entry
);
1681 rhythmdb_entry_unref (entry
);
1685 rb_entry_view_rows_reordered_cb (GtkTreeModel
*model
,
1691 GList
*selected_rows
;
1694 gboolean scrolled
= FALSE
;
1696 rb_debug ("rows reordered");
1698 model_size
= gtk_tree_model_iter_n_children (model
, NULL
);
1700 /* check if a selected row was moved; if so, we'll
1701 * need to move the selection too.
1703 selected_rows
= gtk_tree_selection_get_selected_rows (view
->priv
->selection
,
1705 for (i
= selected_rows
; i
!= NULL
; i
= i
->next
) {
1706 GtkTreePath
*path
= (GtkTreePath
*)i
->data
;
1707 gint index
= gtk_tree_path_get_indices (path
)[0];
1709 if (order
[index
] != index
) {
1710 GtkTreePath
*newpath
;
1711 gtk_tree_selection_unselect_path (view
->priv
->selection
, path
);
1713 for (newindex
= 0; newindex
< model_size
; newindex
++) {
1714 if (order
[newindex
] == index
) {
1715 newpath
= gtk_tree_path_new_from_indices (newindex
, -1);
1716 gtk_tree_selection_select_path (view
->priv
->selection
, newpath
);
1718 GtkTreeViewColumn
*col
;
1719 GtkTreeView
*treeview
= GTK_TREE_VIEW (view
->priv
->treeview
);
1721 col
= gtk_tree_view_get_column (treeview
, 0);
1722 gtk_tree_view_scroll_to_cell (treeview
, newpath
, col
, TRUE
, 0.5, 0.0);
1725 gtk_tree_path_free (newpath
);
1733 g_list_foreach (selected_rows
, (GFunc
) gtk_tree_path_free
, NULL
);
1734 g_list_free (selected_rows
);
1736 gtk_widget_queue_draw (GTK_WIDGET (view
));
1740 rb_entry_view_select_all (RBEntryView
*view
)
1742 gtk_tree_selection_select_all (view
->priv
->selection
);
1746 rb_entry_view_select_none (RBEntryView
*view
)
1748 gtk_tree_selection_unselect_all (view
->priv
->selection
);
1752 rb_entry_view_select_entry (RBEntryView
*view
,
1753 RhythmDBEntry
*entry
)
1760 rb_entry_view_select_none (view
);
1762 if (rhythmdb_query_model_entry_to_iter (view
->priv
->model
,
1764 gtk_tree_selection_select_iter (view
->priv
->selection
, &iter
);
1769 rb_entry_view_scroll_to_entry (RBEntryView
*view
,
1770 RhythmDBEntry
*entry
)
1774 if (rhythmdb_query_model_entry_to_iter (view
->priv
->model
,
1776 rb_entry_view_scroll_to_iter (view
, &iter
);
1781 rb_entry_view_scroll_to_iter (RBEntryView
*view
,
1786 /* It's possible to we can be asked to scroll the play queue's entry
1787 * view to the playing entry before the view has ever been displayed.
1788 * This will result in gtk+ warnings, so we avoid it in this case.
1790 if (!GTK_WIDGET_REALIZED (view
))
1793 path
= gtk_tree_model_get_path (GTK_TREE_MODEL (view
->priv
->model
), iter
);
1794 gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (view
->priv
->treeview
), path
,
1795 gtk_tree_view_get_column (GTK_TREE_VIEW (view
->priv
->treeview
), 0),
1797 gtk_tree_view_set_cursor (GTK_TREE_VIEW (view
->priv
->treeview
), path
,
1798 gtk_tree_view_get_column (GTK_TREE_VIEW (view
->priv
->treeview
), 0), FALSE
);
1800 gtk_tree_path_free (path
);
1804 rb_entry_view_get_entry_visible (RBEntryView
*view
,
1805 RhythmDBEntry
*entry
)
1808 gboolean realized
, visible
;
1810 if (view
->priv
->playing_model
!= view
->priv
->model
)
1813 rb_entry_view_entry_is_visible (view
, entry
, &realized
, &visible
,
1815 return realized
&& visible
;
1819 rb_entry_view_get_entry_contained (RBEntryView
*view
,
1820 RhythmDBEntry
*entry
)
1824 return rhythmdb_query_model_entry_to_iter (view
->priv
->model
,
1829 rb_entry_view_entry_is_visible (RBEntryView
*view
,
1830 RhythmDBEntry
*entry
,
1841 g_return_if_fail (entry
!= NULL
);
1843 if (!GTK_WIDGET_REALIZED (view
))
1848 if (!rhythmdb_query_model_entry_to_iter (view
->priv
->model
,
1852 path
= gtk_tree_model_get_path (GTK_TREE_MODEL (view
->priv
->model
), iter
);
1853 gtk_tree_view_get_cell_area (GTK_TREE_VIEW (view
->priv
->treeview
),
1855 gtk_tree_view_get_column (GTK_TREE_VIEW (view
->priv
->treeview
), 0),
1858 gtk_tree_path_free (path
);
1860 *visible
= (rect
.y
!= 0 && rect
.height
!= 0);
1864 rb_entry_view_enable_drag_source (RBEntryView
*view
,
1865 const GtkTargetEntry
*targets
,
1868 g_return_if_fail (view
!= NULL
);
1870 rb_tree_dnd_add_drag_source_support (GTK_TREE_VIEW (view
->priv
->treeview
),
1871 GDK_BUTTON1_MASK
| GDK_BUTTON3_MASK
,
1872 targets
, n_targets
, GDK_ACTION_COPY
);
1876 rb_entry_view_sort_key_changed_cb (GConfClient
* client
,
1881 RBEntryView
*view
= user_data
;
1883 g_return_if_fail (RB_IS_ENTRY_VIEW (view
));
1885 rb_entry_view_set_sorting_type (view
, eel_gconf_get_string (view
->priv
->sorting_key
));
1889 rb_entry_view_columns_config_changed_cb (GConfClient
* client
,
1894 RBEntryView
*view
= user_data
;
1896 g_return_if_fail (RB_IS_ENTRY_VIEW (view
));
1898 rb_entry_view_sync_columns_visible (view
);
1902 propid_from_name (const char *name
)
1904 GEnumClass
*prop_class
= g_type_class_ref (RHYTHMDB_TYPE_PROP_TYPE
);
1908 ev
= g_enum_get_value_by_name (prop_class
, name
);
1917 set_column_visibility (guint propid
,
1918 GtkTreeViewColumn
*column
,
1919 GList
*visible_props
)
1923 if (g_object_get_qdata (G_OBJECT (column
),
1924 rb_entry_view_column_always_visible
) == GINT_TO_POINTER (1))
1927 visible
= (g_list_find (visible_props
, GINT_TO_POINTER (propid
)) != NULL
);
1929 gtk_tree_view_column_set_visible (column
, visible
);
1933 rb_entry_view_sync_columns_visible (RBEntryView
*view
)
1936 GList
*visible_properties
= NULL
;
1937 char *config
= eel_gconf_get_string (CONF_UI_COLUMNS_SETUP
);
1939 g_return_if_fail (view
!= NULL
);
1940 g_return_if_fail (config
!= NULL
);
1942 items
= g_strsplit (config
, ",", 0);
1943 if (items
!= NULL
) {
1945 for (i
= 0; items
[i
] != NULL
&& *(items
[i
]); i
++) {
1946 int value
= propid_from_name (items
[i
]);
1948 if ((value
>= 0) && (value
< RHYTHMDB_NUM_PROPERTIES
))
1949 visible_properties
= g_list_prepend (visible_properties
, GINT_TO_POINTER (value
));
1954 g_hash_table_foreach (view
->priv
->propid_column_map
, (GHFunc
) set_column_visibility
, visible_properties
);
1956 g_list_free (visible_properties
);
1961 rb_entry_view_set_state (RBEntryView
*view
,
1962 RBEntryViewState state
)
1964 g_return_if_fail (RB_IS_ENTRY_VIEW (view
));
1965 g_object_set (view
, "playing-state", state
, NULL
);
1969 rb_entry_view_grab_focus (GtkWidget
*widget
)
1971 RBEntryView
*view
= RB_ENTRY_VIEW (widget
);
1973 gtk_widget_grab_focus (GTK_WIDGET (view
->priv
->treeview
));
1977 rb_entry_view_emit_row_changed (RBEntryView
*view
,
1978 RhythmDBEntry
*entry
)
1983 if (!rhythmdb_query_model_entry_to_iter (view
->priv
->model
, entry
, &iter
))
1986 path
= gtk_tree_model_get_path (GTK_TREE_MODEL (view
->priv
->model
),
1988 gtk_tree_model_row_changed (GTK_TREE_MODEL (view
->priv
->model
),
1990 gtk_tree_path_free (path
);
1995 rb_entry_view_set_fixed_column_width (RBEntryView
*view
,
1996 GtkTreeViewColumn
*column
,
1997 GtkCellRenderer
*renderer
,
1998 const gchar
**strings
)
2003 while (strings
[i
] != NULL
) {
2005 g_object_set (renderer
, "text", strings
[i
], NULL
);
2006 gtk_cell_renderer_get_size (renderer
,
2007 view
->priv
->treeview
,
2012 if (width
> max_width
)
2018 /* include some arbitrary amount of padding, just to be safeish */
2019 gtk_tree_view_column_set_fixed_width (column
, max_width
+ 5);
2023 rb_entry_view_get_time_date_column_sample ()
2025 static const char *sample
= NULL
;
2027 * Currently, Japanese is the only translation that uses
2028 * anything other than %Y, %m ,%d, %H, and %M to format dates.
2029 * It uses %B (month name) and %e (days), and the values for
2030 * the month name appear to all consist of the month number
2031 * followed by a single character, so they're of consistent
2032 * width. So, this approach should work for every locale.
2034 * Midnight on September 30th, 2000 is the widest date/time I
2035 * can think of. 2000-09-30 00:00.
2038 if (sample
== NULL
) {
2039 /* s m h d M Y dw dY x */
2040 struct tm someday
= { 0, 0, 0, 30, 9, 100, 6, 274, 0};
2042 /* Translators: Please keep the translated date format
2043 * compact, and avoid variable-width items such as month and
2044 * day names wherever possible. This allows us to disable
2045 * column autosizing, which makes the Rhythmbox UI much faster.
2047 sample
= eel_strdup_strftime (_("%Y-%m-%d %H:%M"), &someday
);
2053 rb_entry_view_resort_model (RBEntryView
*view
)
2055 struct RBEntryViewColumnSortData
*sort_data
;
2057 g_assert (view
->priv
->sorting_column
);
2058 sort_data
= g_hash_table_lookup (view
->priv
->column_sort_data_map
,
2059 view
->priv
->sorting_column
);
2060 g_assert (sort_data
);
2062 rhythmdb_query_model_set_sort_order (view
->priv
->model
,
2066 (view
->priv
->sorting_order
== GTK_SORT_DESCENDING
));
2069 /* This should really be standard. */
2070 #define ENUM_ENTRY(NAME, DESC) { NAME, "" #NAME "", DESC }
2073 rb_entry_view_column_get_type (void)
2075 static GType etype
= 0;
2078 static const GEnumValue values
[] = {
2079 ENUM_ENTRY (RB_ENTRY_VIEW_COL_TRACK_NUMBER
, "Track Number"),
2080 ENUM_ENTRY (RB_ENTRY_VIEW_COL_TITLE
, "Title"),
2081 ENUM_ENTRY (RB_ENTRY_VIEW_COL_ARTIST
, "Artist"),
2082 ENUM_ENTRY (RB_ENTRY_VIEW_COL_ALBUM
, "Album"),
2083 ENUM_ENTRY (RB_ENTRY_VIEW_COL_GENRE
, "Genre"),
2084 ENUM_ENTRY (RB_ENTRY_VIEW_COL_DURATION
, "Duration"),
2085 ENUM_ENTRY (RB_ENTRY_VIEW_COL_QUALITY
, "Quality"),
2086 ENUM_ENTRY (RB_ENTRY_VIEW_COL_RATING
, "Rating"),
2087 ENUM_ENTRY (RB_ENTRY_VIEW_COL_PLAY_COUNT
, "Play Count"),
2088 ENUM_ENTRY (RB_ENTRY_VIEW_COL_YEAR
, "Year"),
2089 ENUM_ENTRY (RB_ENTRY_VIEW_COL_LAST_PLAYED
, "Last Played"),
2090 ENUM_ENTRY (RB_ENTRY_VIEW_COL_FIRST_SEEN
, "First Seen"),
2091 ENUM_ENTRY (RB_ENTRY_VIEW_COL_LAST_SEEN
, "Last Seen"),
2092 ENUM_ENTRY (RB_ENTRY_VIEW_COL_LOCATION
, "Location"),
2093 ENUM_ENTRY (RB_ENTRY_VIEW_COL_ERROR
, "Error"),
2097 etype
= g_enum_register_static ("RBEntryViewColumn", values
);
2104 rb_entry_view_state_get_type (void)
2106 static GType etype
= 0;
2109 static const GEnumValue values
[] = {
2110 ENUM_ENTRY (RB_ENTRY_VIEW_NOT_PLAYING
, "Not Playing"),
2111 ENUM_ENTRY (RB_ENTRY_VIEW_PLAYING
, "Playing"),
2112 ENUM_ENTRY (RB_ENTRY_VIEW_PAUSED
, "Paused"),
2116 etype
= g_enum_register_static ("RBEntryViewState", values
);