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_dispose (GObject
*object
);
45 static void rb_property_view_finalize (GObject
*object
);
46 static void rb_property_view_set_property (GObject
*object
,
50 static void rb_property_view_get_property (GObject
*object
,
54 static GObject
* rb_property_view_constructor (GType type
, guint n_construct_properties
,
55 GObjectConstructParam
*construct_properties
);
56 static void rb_property_view_row_activated_cb (GtkTreeView
*treeview
,
58 GtkTreeViewColumn
*column
,
59 RBPropertyView
*view
);
60 static void rb_property_view_selection_changed_cb (GtkTreeSelection
*selection
,
61 RBPropertyView
*view
);
62 static void rb_property_view_pre_row_deleted_cb (RhythmDBPropertyModel
*model
,
63 RBPropertyView
*view
);
64 static void rb_property_view_post_row_deleted_cb (GtkTreeModel
*model
,
66 RBPropertyView
*view
);
67 static gboolean
rb_property_view_popup_menu_cb (GtkTreeView
*treeview
,
68 RBPropertyView
*view
);
69 static gboolean
rb_property_view_button_press_cb (GtkTreeView
*tree
,
70 GdkEventButton
*event
,
71 RBPropertyView
*view
);
73 struct RBPropertyViewPrivate
77 RhythmDBPropType propid
;
79 RhythmDBPropertyModel
*prop_model
;
84 GtkTreeSelection
*selection
;
87 gboolean handling_row_deletion
;
90 #define RB_PROPERTY_VIEW_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), RB_TYPE_PROPERTY_VIEW, RBPropertyViewPrivate))
112 static guint rb_property_view_signals
[LAST_SIGNAL
] = { 0 };
114 G_DEFINE_TYPE (RBPropertyView
, rb_property_view
, GTK_TYPE_SCROLLED_WINDOW
)
117 rb_property_view_class_init (RBPropertyViewClass
*klass
)
119 GObjectClass
*object_class
= G_OBJECT_CLASS (klass
);
121 object_class
->dispose
= rb_property_view_dispose
;
122 object_class
->finalize
= rb_property_view_finalize
;
123 object_class
->constructor
= rb_property_view_constructor
;
125 object_class
->set_property
= rb_property_view_set_property
;
126 object_class
->get_property
= rb_property_view_get_property
;
128 g_object_class_install_property (object_class
,
130 g_param_spec_object ("db",
134 G_PARAM_READWRITE
| G_PARAM_CONSTRUCT_ONLY
));
136 g_object_class_install_property (object_class
,
138 g_param_spec_enum ("prop",
141 RHYTHMDB_TYPE_PROP_TYPE
,
143 G_PARAM_READWRITE
| G_PARAM_CONSTRUCT_ONLY
));
145 g_object_class_install_property (object_class
,
147 g_param_spec_string ("title",
151 G_PARAM_READWRITE
| G_PARAM_CONSTRUCT_ONLY
));
152 g_object_class_install_property (object_class
,
154 g_param_spec_object ("property-model",
156 "RhythmDBPropertyModel",
157 RHYTHMDB_TYPE_PROPERTY_MODEL
,
159 g_object_class_install_property (object_class
,
161 g_param_spec_boolean ("draggable",
165 G_PARAM_READWRITE
| G_PARAM_CONSTRUCT_ONLY
));
167 rb_property_view_signals
[PROPERTY_ACTIVATED
] =
168 g_signal_new ("property-activated",
169 G_OBJECT_CLASS_TYPE (object_class
),
171 G_STRUCT_OFFSET (RBPropertyViewClass
, property_activated
),
173 g_cclosure_marshal_VOID__STRING
,
178 rb_property_view_signals
[PROPERTY_SELECTED
] =
179 g_signal_new ("property-selected",
180 G_OBJECT_CLASS_TYPE (object_class
),
182 G_STRUCT_OFFSET (RBPropertyViewClass
, property_selected
),
184 g_cclosure_marshal_VOID__STRING
,
189 rb_property_view_signals
[PROPERTIES_SELECTED
] =
190 g_signal_new ("properties-selected",
191 G_OBJECT_CLASS_TYPE (object_class
),
193 G_STRUCT_OFFSET (RBPropertyViewClass
, properties_selected
),
195 g_cclosure_marshal_VOID__POINTER
,
200 rb_property_view_signals
[SELECTION_RESET
] =
201 g_signal_new ("property-selection-reset",
202 G_OBJECT_CLASS_TYPE (object_class
),
204 G_STRUCT_OFFSET (RBPropertyViewClass
, selection_reset
),
206 g_cclosure_marshal_VOID__VOID
,
209 rb_property_view_signals
[SHOW_POPUP
] =
210 g_signal_new ("show_popup",
211 G_OBJECT_CLASS_TYPE (object_class
),
213 G_STRUCT_OFFSET (RBPropertyViewClass
, show_popup
),
215 g_cclosure_marshal_VOID__VOID
,
219 g_type_class_add_private (klass
, sizeof (RBPropertyViewPrivate
));
223 rb_property_view_init (RBPropertyView
*view
)
225 view
->priv
= RB_PROPERTY_VIEW_GET_PRIVATE (view
);
229 rb_property_view_set_model_internal (RBPropertyView
*view
,
230 RhythmDBPropertyModel
*model
)
232 if (view
->priv
->prop_model
!= NULL
) {
233 g_signal_handlers_disconnect_by_func (view
->priv
->prop_model
,
234 G_CALLBACK (rb_property_view_pre_row_deleted_cb
),
236 g_signal_handlers_disconnect_by_func (view
->priv
->prop_model
,
237 G_CALLBACK (rb_property_view_post_row_deleted_cb
),
239 g_object_unref (view
->priv
->prop_model
);
242 view
->priv
->prop_model
= model
;
244 if (view
->priv
->prop_model
!= NULL
) {
247 g_object_ref (view
->priv
->prop_model
);
249 gtk_tree_view_set_model (GTK_TREE_VIEW (view
->priv
->treeview
),
250 GTK_TREE_MODEL (view
->priv
->prop_model
));
252 g_signal_connect_object (view
->priv
->prop_model
,
254 G_CALLBACK (rb_property_view_pre_row_deleted_cb
),
257 g_signal_connect_object (view
->priv
->prop_model
,
259 G_CALLBACK (rb_property_view_post_row_deleted_cb
),
263 g_signal_handlers_block_by_func (view
->priv
->selection
,
264 G_CALLBACK (rb_property_view_selection_changed_cb
),
267 gtk_tree_selection_unselect_all (view
->priv
->selection
);
269 if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (view
->priv
->prop_model
), &iter
))
270 gtk_tree_selection_select_iter (view
->priv
->selection
, &iter
);
272 g_signal_handlers_unblock_by_func (view
->priv
->selection
,
273 G_CALLBACK (rb_property_view_selection_changed_cb
),
279 rb_property_view_dispose (GObject
*object
)
281 RBPropertyView
*view
;
283 g_return_if_fail (object
!= NULL
);
284 g_return_if_fail (RB_IS_PROPERTY_VIEW (object
));
286 view
= RB_PROPERTY_VIEW (object
);
288 rb_property_view_set_model_internal (view
, NULL
);
290 G_OBJECT_CLASS (rb_property_view_parent_class
)->dispose (object
);
294 rb_property_view_finalize (GObject
*object
)
296 RBPropertyView
*view
;
298 g_return_if_fail (object
!= NULL
);
299 g_return_if_fail (RB_IS_PROPERTY_VIEW (object
));
301 view
= RB_PROPERTY_VIEW (object
);
303 g_free (view
->priv
->title
);
305 G_OBJECT_CLASS (rb_property_view_parent_class
)->finalize (object
);
309 rb_property_view_set_property (GObject
*object
,
314 RBPropertyView
*view
= RB_PROPERTY_VIEW (object
);
318 view
->priv
->db
= g_value_get_object (value
);
321 view
->priv
->propid
= g_value_get_enum (value
);
324 view
->priv
->title
= g_value_dup_string (value
);
327 rb_property_view_set_model_internal (view
, g_value_get_object (value
));
330 view
->priv
->draggable
= g_value_get_boolean (value
);
333 G_OBJECT_WARN_INVALID_PROPERTY_ID (object
, prop_id
, pspec
);
339 rb_property_view_get_property (GObject
*object
,
344 RBPropertyView
*view
= RB_PROPERTY_VIEW (object
);
348 g_value_set_object (value
, view
->priv
->db
);
351 g_value_set_enum (value
, view
->priv
->propid
);
354 g_value_set_string (value
, view
->priv
->title
);
357 g_value_set_object (value
, view
->priv
->prop_model
);
360 g_value_set_boolean (value
, view
->priv
->draggable
);
363 G_OBJECT_WARN_INVALID_PROPERTY_ID (object
, prop_id
, pspec
);
369 rb_property_view_new (RhythmDB
*db
,
373 RBPropertyView
*view
;
375 view
= RB_PROPERTY_VIEW (g_object_new (RB_TYPE_PROPERTY_VIEW
,
378 "hscrollbar_policy", GTK_POLICY_AUTOMATIC
,
379 "vscrollbar_policy", GTK_POLICY_ALWAYS
,
380 "shadow_type", GTK_SHADOW_IN
,
387 g_return_val_if_fail (view
->priv
!= NULL
, NULL
);
393 rb_property_view_set_selection_mode (RBPropertyView
*view
,
394 GtkSelectionMode mode
)
396 g_return_if_fail (RB_IS_PROPERTY_VIEW (view
));
397 g_return_if_fail (mode
== GTK_SELECTION_SINGLE
|| mode
== GTK_SELECTION_MULTIPLE
);
399 gtk_tree_selection_set_mode (gtk_tree_view_get_selection (GTK_TREE_VIEW (view
->priv
->treeview
)),
404 rb_property_view_reset (RBPropertyView
*view
)
406 RhythmDBPropertyModel
*model
;
408 g_return_if_fail (RB_IS_PROPERTY_VIEW (view
));
410 model
= rhythmdb_property_model_new (view
->priv
->db
, view
->priv
->propid
);
412 rb_property_view_set_model_internal (view
, model
);
413 g_object_unref (model
);
416 RhythmDBPropertyModel
*
417 rb_property_view_get_model (RBPropertyView
*view
)
419 RhythmDBPropertyModel
*model
;
421 g_return_val_if_fail (RB_IS_PROPERTY_VIEW (view
), NULL
);
423 model
= view
->priv
->prop_model
;
429 rb_property_view_set_model (RBPropertyView
*view
,
430 RhythmDBPropertyModel
*model
)
432 g_return_if_fail (RB_IS_PROPERTY_VIEW (view
));
434 rb_property_view_set_model_internal (view
, model
);
438 rb_property_view_pre_row_deleted_cb (RhythmDBPropertyModel
*model
,
439 RBPropertyView
*view
)
441 view
->priv
->handling_row_deletion
= TRUE
;
442 rb_debug ("pre row deleted");
446 rb_property_view_post_row_deleted_cb (GtkTreeModel
*model
,
448 RBPropertyView
*view
)
450 view
->priv
->handling_row_deletion
= FALSE
;
451 rb_debug ("post row deleted");
452 if (gtk_tree_selection_count_selected_rows (view
->priv
->selection
) == 0) {
453 GtkTreeIter first_iter
;
454 rb_debug ("no rows selected, signalling reset");
455 if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (view
->priv
->prop_model
), &first_iter
)) {
456 g_signal_handlers_block_by_func (G_OBJECT (view
->priv
->selection
),
457 G_CALLBACK (rb_property_view_selection_changed_cb
),
459 gtk_tree_selection_select_iter (view
->priv
->selection
, &first_iter
);
460 g_signal_emit (G_OBJECT (view
), rb_property_view_signals
[SELECTION_RESET
], 0);
461 g_signal_handlers_unblock_by_func (G_OBJECT (view
->priv
->selection
),
462 G_CALLBACK (rb_property_view_selection_changed_cb
),
469 rb_property_view_get_num_properties (RBPropertyView
*view
)
471 g_return_val_if_fail (RB_IS_PROPERTY_VIEW (view
), 0);
473 return gtk_tree_model_iter_n_children (GTK_TREE_MODEL (view
->priv
->prop_model
),
478 rb_property_view_cell_data_func (GtkTreeViewColumn
*column
,
479 GtkCellRenderer
*renderer
,
480 GtkTreeModel
*tree_model
,
482 RBPropertyView
*view
)
489 gtk_tree_model_get (GTK_TREE_MODEL (tree_model
), iter
,
490 RHYTHMDB_PROPERTY_MODEL_COLUMN_TITLE
, &title
,
491 RHYTHMDB_PROPERTY_MODEL_COLUMN_PRIORITY
, &is_all
,
492 RHYTHMDB_PROPERTY_MODEL_COLUMN_NUMBER
, &number
, -1);
498 nodes
= gtk_tree_model_iter_n_children (GTK_TREE_MODEL (tree_model
), NULL
);
499 /* Subtract one for the All node */
502 switch (view
->priv
->propid
) {
503 case RHYTHMDB_PROP_ARTIST
:
504 fmt
= ngettext ("All %d artist (%d)", "All %d artists (%d)", nodes
);
506 case RHYTHMDB_PROP_ALBUM
:
507 fmt
= ngettext ("All %d album (%d)", "All %d albums (%d)", nodes
);
509 case RHYTHMDB_PROP_GENRE
:
510 fmt
= ngettext ("All %d genre (%d)", "All %d genres (%d)", nodes
);
513 fmt
= _("All %d (%d)");
517 str
= g_strdup_printf (fmt
, nodes
, number
);
519 str
= g_strdup_printf (_("%s (%d)"), title
, number
);
522 g_object_set (G_OBJECT (renderer
), "text", str
,
523 "weight", G_UNLIKELY (is_all
) ? PANGO_WEIGHT_BOLD
: PANGO_WEIGHT_NORMAL
,
530 rb_property_view_constructor (GType type
,
531 guint n_construct_properties
,
532 GObjectConstructParam
*construct_properties
)
534 GtkTreeViewColumn
*column
;
535 GtkCellRenderer
*renderer
;
537 RBPropertyView
*view
;
538 RBPropertyViewClass
*klass
;
540 klass
= RB_PROPERTY_VIEW_CLASS (g_type_class_peek (RB_TYPE_PROPERTY_VIEW
));
542 view
= RB_PROPERTY_VIEW (G_OBJECT_CLASS (rb_property_view_parent_class
)->
543 constructor (type
, n_construct_properties
, construct_properties
));
545 view
->priv
->prop_model
= rhythmdb_property_model_new (view
->priv
->db
, view
->priv
->propid
);
546 view
->priv
->treeview
= GTK_WIDGET (gtk_tree_view_new_with_model (GTK_TREE_MODEL (view
->priv
->prop_model
)));
548 if (view
->priv
->draggable
)
549 rhythmdb_property_model_enable_drag (view
->priv
->prop_model
,
550 GTK_TREE_VIEW (view
->priv
->treeview
));
552 g_signal_connect_object (G_OBJECT (view
->priv
->treeview
),
554 G_CALLBACK (rb_property_view_row_activated_cb
),
558 view
->priv
->selection
= gtk_tree_view_get_selection (GTK_TREE_VIEW (view
->priv
->treeview
));
559 g_signal_connect_object (G_OBJECT (view
->priv
->selection
),
561 G_CALLBACK (rb_property_view_selection_changed_cb
),
564 g_signal_connect_object (G_OBJECT (view
->priv
->treeview
),
566 G_CALLBACK (rb_property_view_popup_menu_cb
),
570 g_signal_connect_object (G_OBJECT (view
->priv
->treeview
),
571 "button_press_event",
572 G_CALLBACK (rb_property_view_button_press_cb
),
576 gtk_container_add (GTK_CONTAINER (view
), view
->priv
->treeview
);
578 gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (view
->priv
->treeview
), TRUE
);
579 gtk_tree_selection_set_mode (view
->priv
->selection
, GTK_SELECTION_SINGLE
);
581 column
= gtk_tree_view_column_new ();
582 renderer
= gtk_cell_renderer_text_new ();
583 gtk_tree_view_column_pack_start (column
, renderer
, TRUE
);
584 gtk_tree_view_column_set_cell_data_func (column
, renderer
,
585 (GtkTreeCellDataFunc
) rb_property_view_cell_data_func
,
587 gtk_tree_view_column_set_title (column
, view
->priv
->title
);
588 gtk_tree_view_column_set_sizing (column
, GTK_TREE_VIEW_COLUMN_FIXED
);
589 gtk_tree_view_append_column (GTK_TREE_VIEW (view
->priv
->treeview
),
592 return G_OBJECT (view
);
596 rb_property_view_row_activated_cb (GtkTreeView
*treeview
,
598 GtkTreeViewColumn
*column
,
599 RBPropertyView
*view
)
605 rb_debug ("row activated");
606 g_return_if_fail (gtk_tree_model_get_iter (GTK_TREE_MODEL (view
->priv
->prop_model
),
609 gtk_tree_model_get (GTK_TREE_MODEL (view
->priv
->prop_model
), &iter
,
610 RHYTHMDB_PROPERTY_MODEL_COLUMN_TITLE
, &val
,
611 RHYTHMDB_PROPERTY_MODEL_COLUMN_PRIORITY
, &is_all
, -1);
613 rb_debug ("emitting property activated");
614 g_signal_emit (G_OBJECT (view
), rb_property_view_signals
[PROPERTY_ACTIVATED
], 0,
615 is_all
? NULL
: val
);
621 rb_property_view_set_selection (RBPropertyView
*view
,
624 g_return_if_fail (RB_IS_PROPERTY_VIEW (view
));
626 view
->priv
->handling_row_deletion
= TRUE
;
628 gtk_tree_selection_unselect_all (view
->priv
->selection
);
630 for (; vals
; vals
= vals
->next
) {
633 if (rhythmdb_property_model_iter_from_string (view
->priv
->prop_model
, vals
->data
, &iter
)) {
636 gtk_tree_selection_select_iter (view
->priv
->selection
, &iter
);
637 path
= gtk_tree_model_get_path (GTK_TREE_MODEL (view
->priv
->prop_model
), &iter
);
639 gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (view
->priv
->treeview
),
642 gtk_tree_path_free (path
);
648 view
->priv
->handling_row_deletion
= FALSE
;
649 rb_property_view_selection_changed_cb (view
->priv
->selection
, view
);
653 rb_property_view_selection_changed_cb (GtkTreeSelection
*selection
,
654 RBPropertyView
*view
)
656 char *selected_prop
= NULL
;
657 gboolean is_all
= TRUE
;
661 if (view
->priv
->handling_row_deletion
)
664 rb_debug ("selection changed");
665 if (gtk_tree_selection_get_mode (selection
) == GTK_SELECTION_MULTIPLE
) {
666 GList
*selected_rows
, *tem
;
667 GList
*selected_properties
= NULL
;
669 selected_rows
= gtk_tree_selection_get_selected_rows (view
->priv
->selection
, &model
);
670 for (tem
= selected_rows
; tem
; tem
= tem
->next
) {
671 g_assert (gtk_tree_model_get_iter (model
, &iter
, tem
->data
));
672 gtk_tree_model_get (model
, &iter
,
673 RHYTHMDB_PROPERTY_MODEL_COLUMN_TITLE
, &selected_prop
,
674 RHYTHMDB_PROPERTY_MODEL_COLUMN_PRIORITY
, &is_all
, -1);
676 g_list_free (selected_properties
);
677 selected_properties
= NULL
;
680 selected_properties
= g_list_prepend (selected_properties
,
681 g_strdup (selected_prop
));
684 g_list_foreach (selected_rows
, (GFunc
) gtk_tree_path_free
, NULL
);
685 g_list_free (selected_rows
);
688 g_signal_handlers_block_by_func (G_OBJECT (view
->priv
->selection
),
689 G_CALLBACK (rb_property_view_selection_changed_cb
),
691 gtk_tree_selection_unselect_all (selection
);
692 if (gtk_tree_model_get_iter_first (model
, &iter
))
693 gtk_tree_selection_select_iter (selection
, &iter
);
694 g_signal_handlers_unblock_by_func (G_OBJECT (view
->priv
->selection
),
695 G_CALLBACK (rb_property_view_selection_changed_cb
),
698 g_signal_emit (G_OBJECT (view
), rb_property_view_signals
[PROPERTIES_SELECTED
], 0,
699 selected_properties
);
700 rb_list_deep_free (selected_properties
);
702 if (gtk_tree_selection_get_selected (view
->priv
->selection
, &model
, &iter
)) {
703 gtk_tree_model_get (model
, &iter
,
704 RHYTHMDB_PROPERTY_MODEL_COLUMN_TITLE
, &selected_prop
,
705 RHYTHMDB_PROPERTY_MODEL_COLUMN_PRIORITY
, &is_all
, -1);
706 g_signal_emit (G_OBJECT (view
), rb_property_view_signals
[PROPERTY_SELECTED
], 0,
707 is_all
? NULL
: selected_prop
);
711 g_free (selected_prop
);
715 rb_property_view_popup_menu_cb (GtkTreeView
*treeview
,
716 RBPropertyView
*view
)
718 g_signal_emit (G_OBJECT (view
), rb_property_view_signals
[SHOW_POPUP
], 0);
723 rb_property_view_append_column_custom (RBPropertyView
*view
,
724 GtkTreeViewColumn
*column
)
726 g_return_if_fail (RB_IS_PROPERTY_VIEW (view
));
728 gtk_tree_view_append_column (GTK_TREE_VIEW (view
->priv
->treeview
), column
);
732 rb_property_view_button_press_cb (GtkTreeView
*tree
,
733 GdkEventButton
*event
,
734 RBPropertyView
*view
)
737 if (event
->button
== 3) {
738 GtkTreeSelection
*selection
;
741 selection
= gtk_tree_view_get_selection (GTK_TREE_VIEW (view
->priv
->treeview
));
743 gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (view
->priv
->treeview
), event
->x
, event
->y
, &path
, NULL
, NULL
, NULL
);
745 gtk_tree_selection_unselect_all (selection
);
752 model
= gtk_tree_view_get_model (GTK_TREE_VIEW (view
->priv
->treeview
));
753 if (gtk_tree_model_get_iter (model
, &iter
, path
)) {
754 gtk_tree_model_get (model
, &iter
,
755 RHYTHMDB_PROPERTY_MODEL_COLUMN_TITLE
, &val
, -1);
756 lst
= g_list_prepend (lst
, (gpointer
) val
);
757 rb_property_view_set_selection (view
, lst
);
761 g_signal_emit (G_OBJECT (view
), rb_property_view_signals
[SHOW_POPUP
], 0);
769 rb_property_view_set_search_func (RBPropertyView
*view
,
770 GtkTreeViewSearchEqualFunc func
,
772 GtkDestroyNotify notify
)
774 g_return_if_fail (RB_IS_PROPERTY_VIEW (view
));
776 gtk_tree_view_set_search_equal_func (GTK_TREE_VIEW (view
->priv
->treeview
),