1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
3 * arch-tag: Implementation of widget to display RhythmDB properties
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.
28 #include <glib/gi18n.h>
29 #include <gdk/gdkkeysyms.h>
31 #include <libgnomevfs/gnome-vfs-utils.h>
33 #include "rb-property-view.h"
34 #include "rb-dialog.h"
37 #include "rhythmdb-property-model.h"
38 #include "rb-stock-icons.h"
39 #include "eel-gconf-extensions.h"
42 static void rb_property_view_class_init (RBPropertyViewClass
*klass
);
43 static void rb_property_view_init (RBPropertyView
*view
);
44 static void rb_property_view_finalize (GObject
*object
);
45 static void rb_property_view_set_property (GObject
*object
,
49 static void rb_property_view_get_property (GObject
*object
,
53 static GObject
* rb_property_view_constructor (GType type
, guint n_construct_properties
,
54 GObjectConstructParam
*construct_properties
);
55 static void rb_property_view_row_activated_cb (GtkTreeView
*treeview
,
57 GtkTreeViewColumn
*column
,
58 RBPropertyView
*view
);
59 static void rb_property_view_selection_changed_cb (GtkTreeSelection
*selection
,
60 RBPropertyView
*view
);
61 static void rb_property_view_pre_row_deleted_cb (RhythmDBPropertyModel
*model
,
62 RBPropertyView
*view
);
63 static void rb_property_view_post_row_deleted_cb (GtkTreeModel
*model
,
65 RBPropertyView
*view
);
66 static gboolean
rb_property_view_popup_menu_cb (GtkTreeView
*treeview
,
67 RBPropertyView
*view
);
68 static gboolean
rb_property_view_button_press_cb (GtkTreeView
*tree
,
69 GdkEventButton
*event
,
70 RBPropertyView
*view
);
72 struct RBPropertyViewPrivate
76 RhythmDBPropType propid
;
78 RhythmDBPropertyModel
*prop_model
;
83 GtkTreeSelection
*selection
;
86 gboolean handling_row_deletion
;
89 #define RB_PROPERTY_VIEW_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), RB_TYPE_PROPERTY_VIEW, RBPropertyViewPrivate))
111 static guint rb_property_view_signals
[LAST_SIGNAL
] = { 0 };
120 static const GtkTargetEntry targets_album
[] = {
121 { "text/x-rhythmbox-album", 0, TARGET_ALBUMS
},
122 { "text/uri-list", 0, TARGET_URIS
},
124 static const GtkTargetEntry targets_genre
[] = {
125 { "text/x-rhythmbox-genre", 0, TARGET_GENRE
},
126 { "text/uri-list", 0, TARGET_URIS
},
128 static const GtkTargetEntry targets_artist
[] = {
129 { "text/x-rhythmbox-artist", 0, TARGET_ARTISTS
},
130 { "text/uri-list", 0, TARGET_URIS
},
133 G_DEFINE_TYPE (RBPropertyView
, rb_property_view
, GTK_TYPE_SCROLLED_WINDOW
)
137 rb_property_view_class_init (RBPropertyViewClass
*klass
)
139 GObjectClass
*object_class
= G_OBJECT_CLASS (klass
);
141 object_class
->finalize
= rb_property_view_finalize
;
142 object_class
->constructor
= rb_property_view_constructor
;
144 object_class
->set_property
= rb_property_view_set_property
;
145 object_class
->get_property
= rb_property_view_get_property
;
147 g_object_class_install_property (object_class
,
149 g_param_spec_object ("db",
153 G_PARAM_READWRITE
| G_PARAM_CONSTRUCT_ONLY
));
155 g_object_class_install_property (object_class
,
157 g_param_spec_enum ("prop",
160 RHYTHMDB_TYPE_PROP_TYPE
,
162 G_PARAM_READWRITE
| G_PARAM_CONSTRUCT_ONLY
));
164 g_object_class_install_property (object_class
,
166 g_param_spec_string ("title",
170 G_PARAM_READWRITE
| G_PARAM_CONSTRUCT_ONLY
));
171 g_object_class_install_property (object_class
,
173 g_param_spec_object ("property-model",
175 "RhythmDBPropertyModel",
176 RHYTHMDB_TYPE_PROPERTY_MODEL
,
178 g_object_class_install_property (object_class
,
180 g_param_spec_boolean ("draggable",
184 G_PARAM_READWRITE
| G_PARAM_CONSTRUCT_ONLY
));
186 rb_property_view_signals
[PROPERTY_ACTIVATED
] =
187 g_signal_new ("property-activated",
188 G_OBJECT_CLASS_TYPE (object_class
),
190 G_STRUCT_OFFSET (RBPropertyViewClass
, property_activated
),
192 g_cclosure_marshal_VOID__STRING
,
197 rb_property_view_signals
[PROPERTY_SELECTED
] =
198 g_signal_new ("property-selected",
199 G_OBJECT_CLASS_TYPE (object_class
),
201 G_STRUCT_OFFSET (RBPropertyViewClass
, property_selected
),
203 g_cclosure_marshal_VOID__STRING
,
208 rb_property_view_signals
[PROPERTIES_SELECTED
] =
209 g_signal_new ("properties-selected",
210 G_OBJECT_CLASS_TYPE (object_class
),
212 G_STRUCT_OFFSET (RBPropertyViewClass
, properties_selected
),
214 g_cclosure_marshal_VOID__POINTER
,
219 rb_property_view_signals
[SELECTION_RESET
] =
220 g_signal_new ("property-selection-reset",
221 G_OBJECT_CLASS_TYPE (object_class
),
223 G_STRUCT_OFFSET (RBPropertyViewClass
, selection_reset
),
225 g_cclosure_marshal_VOID__VOID
,
228 rb_property_view_signals
[SHOW_POPUP
] =
229 g_signal_new ("show_popup",
230 G_OBJECT_CLASS_TYPE (object_class
),
232 G_STRUCT_OFFSET (RBPropertyViewClass
, show_popup
),
234 g_cclosure_marshal_VOID__VOID
,
239 g_type_class_add_private (klass
, sizeof (RBPropertyViewPrivate
));
243 rb_property_view_init (RBPropertyView
*view
)
245 view
->priv
= RB_PROPERTY_VIEW_GET_PRIVATE (view
);
249 rb_property_view_finalize (GObject
*object
)
251 RBPropertyView
*view
;
253 g_return_if_fail (object
!= NULL
);
254 g_return_if_fail (RB_IS_PROPERTY_VIEW (object
));
256 view
= RB_PROPERTY_VIEW (object
);
258 g_return_if_fail (view
->priv
!= NULL
);
260 g_free (view
->priv
->title
);
262 G_OBJECT_CLASS (rb_property_view_parent_class
)->finalize (object
);
267 rb_property_view_set_property (GObject
*object
,
272 RBPropertyView
*view
= RB_PROPERTY_VIEW (object
);
277 view
->priv
->db
= g_value_get_object (value
);
280 view
->priv
->propid
= g_value_get_enum (value
);
283 view
->priv
->title
= g_value_dup_string (value
);
289 if (view
->priv
->prop_model
) {
290 g_signal_handlers_disconnect_by_func (G_OBJECT (view
->priv
->prop_model
),
291 G_CALLBACK (rb_property_view_pre_row_deleted_cb
),
293 g_signal_handlers_disconnect_by_func (G_OBJECT (view
->priv
->prop_model
),
294 G_CALLBACK (rb_property_view_post_row_deleted_cb
),
296 g_object_unref (G_OBJECT (view
->priv
->prop_model
));
299 view
->priv
->prop_model
= g_value_get_object (value
);
301 if (!view
->priv
->prop_model
)
304 gtk_tree_view_set_model (GTK_TREE_VIEW (view
->priv
->treeview
),
305 GTK_TREE_MODEL (view
->priv
->prop_model
));
307 g_object_ref (G_OBJECT (view
->priv
->prop_model
));
308 g_signal_connect_object (G_OBJECT (view
->priv
->prop_model
),
310 G_CALLBACK (rb_property_view_pre_row_deleted_cb
),
313 g_signal_connect_object (G_OBJECT (view
->priv
->prop_model
),
315 G_CALLBACK (rb_property_view_post_row_deleted_cb
),
319 g_signal_handlers_block_by_func (G_OBJECT (view
->priv
->selection
),
320 G_CALLBACK (rb_property_view_selection_changed_cb
),
323 gtk_tree_selection_unselect_all (view
->priv
->selection
);
324 if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (view
->priv
->prop_model
), &iter
))
325 gtk_tree_selection_select_iter (view
->priv
->selection
, &iter
);
326 g_signal_handlers_unblock_by_func (G_OBJECT (view
->priv
->selection
),
327 G_CALLBACK (rb_property_view_selection_changed_cb
),
333 view
->priv
->draggable
= g_value_get_boolean (value
);
336 G_OBJECT_WARN_INVALID_PROPERTY_ID (object
, prop_id
, pspec
);
342 rb_property_view_get_property (GObject
*object
,
347 RBPropertyView
*view
= RB_PROPERTY_VIEW (object
);
352 g_value_set_object (value
, view
->priv
->db
);
355 g_value_set_enum (value
, view
->priv
->propid
);
358 g_value_set_string (value
, view
->priv
->title
);
361 g_value_set_object (value
, view
->priv
->prop_model
);
364 g_value_set_boolean (value
, view
->priv
->draggable
);
367 G_OBJECT_WARN_INVALID_PROPERTY_ID (object
, prop_id
, pspec
);
373 rb_property_view_new (RhythmDB
*db
,
377 RBPropertyView
*view
;
379 view
= RB_PROPERTY_VIEW (g_object_new (RB_TYPE_PROPERTY_VIEW
,
382 "hscrollbar_policy", GTK_POLICY_AUTOMATIC
,
383 "vscrollbar_policy", GTK_POLICY_ALWAYS
,
384 "shadow_type", GTK_SHADOW_IN
,
391 g_return_val_if_fail (view
->priv
!= NULL
, NULL
);
397 rb_property_view_set_selection_mode (RBPropertyView
*view
,
398 GtkSelectionMode mode
)
400 g_return_if_fail (mode
== GTK_SELECTION_SINGLE
|| mode
== GTK_SELECTION_MULTIPLE
);
401 gtk_tree_selection_set_mode (gtk_tree_view_get_selection (GTK_TREE_VIEW (view
->priv
->treeview
)),
406 rb_property_view_reset (RBPropertyView
*view
)
408 RhythmDBPropertyModel
*model
;
410 model
= rhythmdb_property_model_new (view
->priv
->db
, view
->priv
->propid
);
412 g_object_set (G_OBJECT (view
), "property-model", model
, NULL
);
413 g_object_unref (G_OBJECT (model
));
416 RhythmDBPropertyModel
*
417 rb_property_view_get_model (RBPropertyView
*view
)
419 RhythmDBPropertyModel
*model
;
421 g_object_get (G_OBJECT (view
), "property-model", &model
, NULL
);
426 rb_property_view_set_model (RBPropertyView
*view
, RhythmDBPropertyModel
*model
)
428 g_object_set (G_OBJECT (view
), "property-model", model
, NULL
);
432 rb_property_view_pre_row_deleted_cb (RhythmDBPropertyModel
*model
,
433 RBPropertyView
*view
)
435 view
->priv
->handling_row_deletion
= TRUE
;
436 rb_debug ("pre row deleted");
440 rb_property_view_post_row_deleted_cb (GtkTreeModel
*model
,
442 RBPropertyView
*view
)
444 view
->priv
->handling_row_deletion
= FALSE
;
445 rb_debug ("post row deleted");
446 if (gtk_tree_selection_count_selected_rows (view
->priv
->selection
) == 0) {
447 GtkTreeIter first_iter
;
448 rb_debug ("no rows selected, signalling reset");
449 if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (view
->priv
->prop_model
), &first_iter
)) {
450 g_signal_handlers_block_by_func (G_OBJECT (view
->priv
->selection
),
451 G_CALLBACK (rb_property_view_selection_changed_cb
),
453 gtk_tree_selection_select_iter (view
->priv
->selection
, &first_iter
);
454 g_signal_emit (G_OBJECT (view
), rb_property_view_signals
[SELECTION_RESET
], 0);
455 g_signal_handlers_unblock_by_func (G_OBJECT (view
->priv
->selection
),
456 G_CALLBACK (rb_property_view_selection_changed_cb
),
463 rb_property_view_get_num_properties (RBPropertyView
*view
)
465 return gtk_tree_model_iter_n_children (GTK_TREE_MODEL (view
->priv
->prop_model
),
470 rb_property_view_cell_data_func (GtkTreeViewColumn
*column
,
471 GtkCellRenderer
*renderer
,
472 GtkTreeModel
*tree_model
,
474 RBPropertyView
*view
)
481 gtk_tree_model_get (GTK_TREE_MODEL (tree_model
), iter
,
482 RHYTHMDB_PROPERTY_MODEL_COLUMN_TITLE
, &title
,
483 RHYTHMDB_PROPERTY_MODEL_COLUMN_PRIORITY
, &is_all
,
484 RHYTHMDB_PROPERTY_MODEL_COLUMN_NUMBER
, &number
, -1);
490 nodes
= gtk_tree_model_iter_n_children (GTK_TREE_MODEL (tree_model
), NULL
);
491 /* Subtract one for the All node */
494 switch (view
->priv
->propid
) {
495 case RHYTHMDB_PROP_ARTIST
:
496 fmt
= ngettext ("All %d artist (%d)", "All %d artists (%d)", nodes
);
498 case RHYTHMDB_PROP_ALBUM
:
499 fmt
= ngettext ("All %d album (%d)", "All %d albums (%d)", nodes
);
501 case RHYTHMDB_PROP_GENRE
:
502 fmt
= ngettext ("All %d genre (%d)", "All %d genres (%d)", nodes
);
505 fmt
= _("All %d (%d)");
509 str
= g_strdup_printf (fmt
, nodes
, number
);
511 str
= g_strdup_printf (_("%s (%d)"), title
, number
);
514 g_object_set (G_OBJECT (renderer
), "text", str
,
515 "weight", G_UNLIKELY (is_all
) ? PANGO_WEIGHT_BOLD
: PANGO_WEIGHT_NORMAL
,
522 rb_property_view_constructor (GType type
,
523 guint n_construct_properties
,
524 GObjectConstructParam
*construct_properties
)
526 GtkTreeViewColumn
*column
;
527 GtkCellRenderer
*renderer
;
529 RBPropertyView
*view
;
530 RBPropertyViewClass
*klass
;
532 klass
= RB_PROPERTY_VIEW_CLASS (g_type_class_peek (RB_TYPE_PROPERTY_VIEW
));
534 view
= RB_PROPERTY_VIEW (G_OBJECT_CLASS (rb_property_view_parent_class
)->
535 constructor (type
, n_construct_properties
, construct_properties
));
537 view
->priv
->prop_model
= rhythmdb_property_model_new (view
->priv
->db
, view
->priv
->propid
);
538 view
->priv
->treeview
= GTK_WIDGET (gtk_tree_view_new_with_model (GTK_TREE_MODEL (view
->priv
->prop_model
)));
540 if (view
->priv
->draggable
)
541 rhythmdb_property_model_enable_drag (view
->priv
->prop_model
,
542 GTK_TREE_VIEW (view
->priv
->treeview
));
544 g_signal_connect_object (G_OBJECT (view
->priv
->treeview
),
546 G_CALLBACK (rb_property_view_row_activated_cb
),
550 view
->priv
->selection
= gtk_tree_view_get_selection (GTK_TREE_VIEW (view
->priv
->treeview
));
551 g_signal_connect_object (G_OBJECT (view
->priv
->selection
),
553 G_CALLBACK (rb_property_view_selection_changed_cb
),
556 g_signal_connect_object (G_OBJECT (view
->priv
->treeview
),
558 G_CALLBACK (rb_property_view_popup_menu_cb
),
562 g_signal_connect_object (G_OBJECT (view
->priv
->treeview
),
563 "button_press_event",
564 G_CALLBACK (rb_property_view_button_press_cb
),
569 gtk_container_add (GTK_CONTAINER (view
), view
->priv
->treeview
);
571 gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (view
->priv
->treeview
), TRUE
);
572 gtk_tree_selection_set_mode (view
->priv
->selection
, GTK_SELECTION_SINGLE
);
574 column
= gtk_tree_view_column_new ();
575 renderer
= gtk_cell_renderer_text_new ();
576 gtk_tree_view_column_pack_start (column
, renderer
, TRUE
);
577 gtk_tree_view_column_set_cell_data_func (column
, renderer
,
578 (GtkTreeCellDataFunc
) rb_property_view_cell_data_func
,
580 gtk_tree_view_column_set_title (column
, view
->priv
->title
);
581 gtk_tree_view_column_set_sizing (column
, GTK_TREE_VIEW_COLUMN_FIXED
);
582 gtk_tree_view_append_column (GTK_TREE_VIEW (view
->priv
->treeview
),
585 return G_OBJECT (view
);
589 rb_property_view_row_activated_cb (GtkTreeView
*treeview
,
591 GtkTreeViewColumn
*column
,
592 RBPropertyView
*view
)
598 rb_debug ("row activated");
599 g_return_if_fail (gtk_tree_model_get_iter (GTK_TREE_MODEL (view
->priv
->prop_model
),
602 gtk_tree_model_get (GTK_TREE_MODEL (view
->priv
->prop_model
), &iter
,
603 RHYTHMDB_PROPERTY_MODEL_COLUMN_TITLE
, &val
,
604 RHYTHMDB_PROPERTY_MODEL_COLUMN_PRIORITY
, &is_all
, -1);
606 rb_debug ("emitting property activated");
607 g_signal_emit (G_OBJECT (view
), rb_property_view_signals
[PROPERTY_ACTIVATED
], 0,
608 is_all
? NULL
: val
);
612 rb_property_view_set_selection (RBPropertyView
*view
, const GList
*vals
)
614 view
->priv
->handling_row_deletion
= TRUE
;
616 gtk_tree_selection_unselect_all (view
->priv
->selection
);
618 for (; vals
; vals
= vals
->next
) {
621 if (rhythmdb_property_model_iter_from_string (view
->priv
->prop_model
, vals
->data
, &iter
)) {
624 gtk_tree_selection_select_iter (view
->priv
->selection
, &iter
);
625 path
= gtk_tree_model_get_path (GTK_TREE_MODEL (view
->priv
->prop_model
), &iter
);
627 gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (view
->priv
->treeview
),
630 gtk_tree_path_free (path
);
636 view
->priv
->handling_row_deletion
= FALSE
;
637 rb_property_view_selection_changed_cb (view
->priv
->selection
, view
);
641 rb_property_view_selection_changed_cb (GtkTreeSelection
*selection
,
642 RBPropertyView
*view
)
644 char *selected_prop
= NULL
;
645 gboolean is_all
= TRUE
;
649 if (view
->priv
->handling_row_deletion
)
652 rb_debug ("selection changed");
653 if (gtk_tree_selection_get_mode (selection
) == GTK_SELECTION_MULTIPLE
) {
654 GList
*selected_rows
, *tem
;
655 GList
*selected_properties
= NULL
;
657 selected_rows
= gtk_tree_selection_get_selected_rows (view
->priv
->selection
, &model
);
658 for (tem
= selected_rows
; tem
; tem
= tem
->next
) {
659 g_assert (gtk_tree_model_get_iter (model
, &iter
, tem
->data
));
660 gtk_tree_model_get (model
, &iter
,
661 RHYTHMDB_PROPERTY_MODEL_COLUMN_TITLE
, &selected_prop
,
662 RHYTHMDB_PROPERTY_MODEL_COLUMN_PRIORITY
, &is_all
, -1);
664 g_list_free (selected_properties
);
665 selected_properties
= NULL
;
668 selected_properties
= g_list_prepend (selected_properties
,
669 g_strdup (selected_prop
));
672 g_list_foreach (selected_rows
, (GFunc
) gtk_tree_path_free
, NULL
);
673 g_list_free (selected_rows
);
676 g_signal_handlers_block_by_func (G_OBJECT (view
->priv
->selection
),
677 G_CALLBACK (rb_property_view_selection_changed_cb
),
679 gtk_tree_selection_unselect_all (selection
);
680 if (gtk_tree_model_get_iter_first (model
, &iter
))
681 gtk_tree_selection_select_iter (selection
, &iter
);
682 g_signal_handlers_unblock_by_func (G_OBJECT (view
->priv
->selection
),
683 G_CALLBACK (rb_property_view_selection_changed_cb
),
686 g_signal_emit (G_OBJECT (view
), rb_property_view_signals
[PROPERTIES_SELECTED
], 0,
687 selected_properties
);
688 rb_list_deep_free (selected_properties
);
690 if (gtk_tree_selection_get_selected (view
->priv
->selection
, &model
, &iter
)) {
691 gtk_tree_model_get (model
, &iter
,
692 RHYTHMDB_PROPERTY_MODEL_COLUMN_TITLE
, &selected_prop
,
693 RHYTHMDB_PROPERTY_MODEL_COLUMN_PRIORITY
, &is_all
, -1);
694 g_signal_emit (G_OBJECT (view
), rb_property_view_signals
[PROPERTY_SELECTED
], 0,
695 is_all
? NULL
: selected_prop
);
699 g_free (selected_prop
);
703 rb_property_view_popup_menu_cb (GtkTreeView
*treeview
,
704 RBPropertyView
*view
)
706 g_signal_emit (G_OBJECT (view
), rb_property_view_signals
[SHOW_POPUP
], 0);
711 rb_property_view_append_column_custom (RBPropertyView
*view
,
712 GtkTreeViewColumn
*column
)
714 gtk_tree_view_append_column (GTK_TREE_VIEW (view
->priv
->treeview
), column
);
718 rb_property_view_button_press_cb (GtkTreeView
*tree
,
719 GdkEventButton
*event
,
720 RBPropertyView
*view
)
723 if (event
->button
== 3) {
724 GtkTreeSelection
*selection
;
727 selection
= gtk_tree_view_get_selection (GTK_TREE_VIEW (view
->priv
->treeview
));
729 gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (view
->priv
->treeview
), event
->x
, event
->y
, &path
, NULL
, NULL
, NULL
);
731 gtk_tree_selection_unselect_all (selection
);
738 model
= gtk_tree_view_get_model (GTK_TREE_VIEW (view
->priv
->treeview
));
739 if (gtk_tree_model_get_iter (model
, &iter
, path
)) {
740 gtk_tree_model_get (model
, &iter
, 0, &val
, -1);
741 lst
= g_list_prepend (lst
, (gpointer
) val
);
742 rb_property_view_set_selection (view
, lst
);
745 g_signal_emit (G_OBJECT (view
), rb_property_view_signals
[SHOW_POPUP
], 0);
753 rb_property_view_set_search_func (RBPropertyView
*view
,
754 GtkTreeViewSearchEqualFunc func
,
756 GtkDestroyNotify notify
)
758 gtk_tree_view_set_search_equal_func (GTK_TREE_VIEW (view
->priv
->treeview
),