No issues found
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
2 *
3 * Copyright (C) 2003 Colin Walters <walters@verbum.org>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * The Rhythmbox authors hereby grant permission for non-GPL compatible
11 * GStreamer plugins to be used and distributed together with GStreamer
12 * and Rhythmbox. This permission is above and beyond the permissions granted
13 * by the GPL license by which Rhythmbox is covered. If you modify this code
14 * you may extend this exception to your version of the code, but you are not
15 * obligated to do so. If you do not wish to do so, delete this exception
16 * statement from your version.
17 *
18 * This program is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 * GNU General Public License for more details.
22 *
23 * You should have received a copy of the GNU General Public License
24 * along with this program; if not, write to the Free Software
25 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
26 *
27 */
28
29 #include "config.h"
30
31 #include <string.h>
32 #include <stdlib.h>
33
34 #include <glib/gi18n.h>
35 #include <gdk/gdkkeysyms.h>
36 #include <gtk/gtk.h>
37
38 #include "rb-property-view.h"
39 #include "rb-dialog.h"
40 #include "rb-debug.h"
41 #include "rhythmdb.h"
42 #include "rhythmdb-property-model.h"
43 #include "rb-stock-icons.h"
44 #include "rb-util.h"
45
46 static void rb_property_view_class_init (RBPropertyViewClass *klass);
47 static void rb_property_view_init (RBPropertyView *view);
48 static void rb_property_view_dispose (GObject *object);
49 static void rb_property_view_finalize (GObject *object);
50 static void rb_property_view_set_property (GObject *object,
51 guint prop_id,
52 const GValue *value,
53 GParamSpec *pspec);
54 static void rb_property_view_get_property (GObject *object,
55 guint prop_id,
56 GValue *value,
57 GParamSpec *pspec);
58 static void rb_property_view_constructed (GObject *object);
59 static void rb_property_view_row_activated_cb (GtkTreeView *treeview,
60 GtkTreePath *path,
61 GtkTreeViewColumn *column,
62 RBPropertyView *view);
63 static void rb_property_view_selection_changed_cb (GtkTreeSelection *selection,
64 RBPropertyView *view);
65 static void rb_property_view_pre_row_deleted_cb (RhythmDBPropertyModel *model,
66 RBPropertyView *view);
67 static void rb_property_view_post_row_deleted_cb (GtkTreeModel *model,
68 GtkTreePath *path,
69 RBPropertyView *view);
70 static gboolean rb_property_view_popup_menu_cb (GtkTreeView *treeview,
71 RBPropertyView *view);
72 static gboolean rb_property_view_button_press_cb (GtkTreeView *tree,
73 GdkEventButton *event,
74 RBPropertyView *view);
75
76 struct RBPropertyViewPrivate
77 {
78 RhythmDB *db;
79
80 RhythmDBPropType propid;
81
82 RhythmDBPropertyModel *prop_model;
83
84 char *title;
85
86 GtkWidget *treeview;
87 GtkTreeSelection *selection;
88
89 gboolean draggable;
90 gboolean handling_row_deletion;
91 };
92
93 #define RB_PROPERTY_VIEW_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), RB_TYPE_PROPERTY_VIEW, RBPropertyViewPrivate))
94
95 /**
96 * SECTION:rb-property-view
97 * @short_description: a #GtkTreeView backed by a #RhythmDBPropertyModel
98 *
99 * A simple #GtkTreeView that displays the contents of a #RhythmDBPropertyModel.
100 * The first row in the tree view displays the total number of properties and entries,
101 * in the form "All 473 artists (6241)". Each subsequent row in the tree view
102 * displays a property value and the number of entries from the #RhythmDBQueryModel
103 * with that value.
104 *
105 * The property view itself creates a single column, but additional columns can be
106 * added.
107 */
108
109 enum
110 {
111 PROPERTY_SELECTED,
112 PROPERTIES_SELECTED,
113 PROPERTY_ACTIVATED,
114 SELECTION_RESET,
115 SHOW_POPUP,
116 LAST_SIGNAL
117 };
118
119 enum
120 {
121 PROP_0,
122 PROP_DB,
123 PROP_PROP,
124 PROP_TITLE,
125 PROP_MODEL,
126 PROP_DRAGGABLE,
127 };
128
129 static guint rb_property_view_signals[LAST_SIGNAL] = { 0 };
130
131 G_DEFINE_TYPE (RBPropertyView, rb_property_view, GTK_TYPE_SCROLLED_WINDOW)
132
133 static void
134 rb_property_view_class_init (RBPropertyViewClass *klass)
135 {
136 GObjectClass *object_class = G_OBJECT_CLASS (klass);
137
138 object_class->dispose = rb_property_view_dispose;
139 object_class->finalize = rb_property_view_finalize;
140 object_class->constructed = rb_property_view_constructed;
141
142 object_class->set_property = rb_property_view_set_property;
143 object_class->get_property = rb_property_view_get_property;
144
145 /**
146 * RBPropertyView:db:
147 *
148 * #RhythmDB instance
149 */
150 g_object_class_install_property (object_class,
151 PROP_DB,
152 g_param_spec_object ("db",
153 "RhythmDB",
154 "RhythmDB database",
155 RHYTHMDB_TYPE,
156 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
157
158 /**
159 * RBPropertyView:prop:
160 *
161 * The property that is displayed in this view
162 */
163 g_object_class_install_property (object_class,
164 PROP_PROP,
165 g_param_spec_enum ("prop",
166 "PropertyId",
167 "RhythmDBPropType",
168 RHYTHMDB_TYPE_PROP_TYPE,
169 RHYTHMDB_PROP_TYPE,
170 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
171
172 /**
173 * RBPropertyView:title:
174 *
175 * The title displayed in the header of the property view
176 */
177 g_object_class_install_property (object_class,
178 PROP_TITLE,
179 g_param_spec_string ("title",
180 "title",
181 "title",
182 "",
183 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
184 /**
185 * RBPropertyView:property-model:
186 *
187 * The #RhythmDBPropertyModel backing the view.
188 */
189 g_object_class_install_property (object_class,
190 PROP_MODEL,
191 g_param_spec_object ("property-model",
192 "property model",
193 "RhythmDBPropertyModel",
194 RHYTHMDB_TYPE_PROPERTY_MODEL,
195 G_PARAM_READWRITE));
196 /**
197 * RBPropertyView:draggable:
198 *
199 * Whether the property view acts as a data source for drag and drop operations.
200 */
201 g_object_class_install_property (object_class,
202 PROP_DRAGGABLE,
203 g_param_spec_boolean ("draggable",
204 "draggable",
205 "is a drag source",
206 TRUE,
207 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
208
209 /**
210 * RBPropertyView::property-activated:
211 * @view: the #RBPropertyView
212 * @name: the property value that was activated
213 *
214 * Emitted when a row in a property view is activated by double clicking.
215 */
216 rb_property_view_signals[PROPERTY_ACTIVATED] =
217 g_signal_new ("property-activated",
218 G_OBJECT_CLASS_TYPE (object_class),
219 G_SIGNAL_RUN_LAST,
220 G_STRUCT_OFFSET (RBPropertyViewClass, property_activated),
221 NULL, NULL,
222 g_cclosure_marshal_VOID__STRING,
223 G_TYPE_NONE,
224 1,
225 G_TYPE_STRING);
226
227 /**
228 * RBPropertyView::property-selected:
229 * @view: the #RBPropertyView
230 * @name: the property value that has been selected
231 *
232 * Emitted when an individual property value becomes selected. This is only
233 * emitted for single-selection property views. For multiple-selection views,
234 * use the properties-selected signal.
235 */
236 rb_property_view_signals[PROPERTY_SELECTED] =
237 g_signal_new ("property-selected",
238 G_OBJECT_CLASS_TYPE (object_class),
239 G_SIGNAL_RUN_LAST,
240 G_STRUCT_OFFSET (RBPropertyViewClass, property_selected),
241 NULL, NULL,
242 g_cclosure_marshal_VOID__STRING,
243 G_TYPE_NONE,
244 1,
245 G_TYPE_STRING);
246
247 /**
248 * RBPropertyView::properties-selected:
249 * @view: the #RBPropertyView
250 * @properties: a list containing the selected property values
251 *
252 * Emitted when the set of selected property values changes. This is only
253 * emitted for multiple selection property views. For single-selection views,
254 * use the property-selected signal.
255 */
256 rb_property_view_signals[PROPERTIES_SELECTED] =
257 g_signal_new ("properties-selected",
258 G_OBJECT_CLASS_TYPE (object_class),
259 G_SIGNAL_RUN_LAST,
260 G_STRUCT_OFFSET (RBPropertyViewClass, properties_selected),
261 NULL, NULL,
262 g_cclosure_marshal_VOID__POINTER,
263 G_TYPE_NONE,
264 1,
265 G_TYPE_POINTER);
266
267 /**
268 * RBPropertyView::property-selection-reset:
269 * @view: the #RBPropertyView
270 *
271 * Emitted when the selection is reset. At this point, no property values
272 * are selected.
273 */
274 rb_property_view_signals[SELECTION_RESET] =
275 g_signal_new ("property-selection-reset",
276 G_OBJECT_CLASS_TYPE (object_class),
277 G_SIGNAL_RUN_LAST,
278 G_STRUCT_OFFSET (RBPropertyViewClass, selection_reset),
279 NULL, NULL,
280 g_cclosure_marshal_VOID__VOID,
281 G_TYPE_NONE,
282 0);
283
284 /**
285 * RBPropertyView::show-popup:
286 * @view: the #RBPropertyView
287 *
288 * Emitted when a popup menu should be displayed for the property view.
289 * The source containing the property view should connect a handler to
290 * this signal that * displays an appropriate popup.
291 */
292 rb_property_view_signals[SHOW_POPUP] =
293 g_signal_new ("show_popup",
294 G_OBJECT_CLASS_TYPE (object_class),
295 G_SIGNAL_RUN_LAST,
296 G_STRUCT_OFFSET (RBPropertyViewClass, show_popup),
297 NULL, NULL,
298 g_cclosure_marshal_VOID__VOID,
299 G_TYPE_NONE,
300 0);
301
302 g_type_class_add_private (klass, sizeof (RBPropertyViewPrivate));
303 }
304
305 static void
306 rb_property_view_init (RBPropertyView *view)
307 {
308 view->priv = RB_PROPERTY_VIEW_GET_PRIVATE (view);
309 }
310
311 static void
312 rb_property_view_set_model_internal (RBPropertyView *view,
313 RhythmDBPropertyModel *model)
314 {
315 if (view->priv->prop_model != NULL) {
316 g_signal_handlers_disconnect_by_func (view->priv->prop_model,
317 G_CALLBACK (rb_property_view_pre_row_deleted_cb),
318 view);
319 g_signal_handlers_disconnect_by_func (view->priv->prop_model,
320 G_CALLBACK (rb_property_view_post_row_deleted_cb),
321 view);
322 g_object_unref (view->priv->prop_model);
323 }
324
325 view->priv->prop_model = model;
326
327 if (view->priv->prop_model != NULL) {
328 GtkTreeIter iter;
329
330 g_object_ref (view->priv->prop_model);
331
332 gtk_tree_view_set_model (GTK_TREE_VIEW (view->priv->treeview),
333 GTK_TREE_MODEL (view->priv->prop_model));
334
335 g_signal_connect_object (view->priv->prop_model,
336 "pre-row-deletion",
337 G_CALLBACK (rb_property_view_pre_row_deleted_cb),
338 view,
339 0);
340 g_signal_connect_object (view->priv->prop_model,
341 "row_deleted",
342 G_CALLBACK (rb_property_view_post_row_deleted_cb),
343 view,
344 G_CONNECT_AFTER);
345
346 g_signal_handlers_block_by_func (view->priv->selection,
347 G_CALLBACK (rb_property_view_selection_changed_cb),
348 view);
349
350 gtk_tree_selection_unselect_all (view->priv->selection);
351
352 if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (view->priv->prop_model), &iter))
353 gtk_tree_selection_select_iter (view->priv->selection, &iter);
354
355 g_signal_handlers_unblock_by_func (view->priv->selection,
356 G_CALLBACK (rb_property_view_selection_changed_cb),
357 view);
358 }
359 }
360
361 static void
362 rb_property_view_dispose (GObject *object)
363 {
364 RBPropertyView *view;
365
366 g_return_if_fail (object != NULL);
367 g_return_if_fail (RB_IS_PROPERTY_VIEW (object));
368
369 view = RB_PROPERTY_VIEW (object);
370
371 rb_property_view_set_model_internal (view, NULL);
372
373 G_OBJECT_CLASS (rb_property_view_parent_class)->dispose (object);
374 }
375
376 static void
377 rb_property_view_finalize (GObject *object)
378 {
379 RBPropertyView *view;
380
381 g_return_if_fail (object != NULL);
382 g_return_if_fail (RB_IS_PROPERTY_VIEW (object));
383
384 view = RB_PROPERTY_VIEW (object);
385
386 g_free (view->priv->title);
387
388 G_OBJECT_CLASS (rb_property_view_parent_class)->finalize (object);
389 }
390
391 static void
392 rb_property_view_set_property (GObject *object,
393 guint prop_id,
394 const GValue *value,
395 GParamSpec *pspec)
396 {
397 RBPropertyView *view = RB_PROPERTY_VIEW (object);
398
399 switch (prop_id) {
400 case PROP_DB:
401 view->priv->db = g_value_get_object (value);
402 break;
403 case PROP_PROP:
404 view->priv->propid = g_value_get_enum (value);
405 break;
406 case PROP_TITLE:
407 view->priv->title = g_value_dup_string (value);
408 break;
409 case PROP_MODEL:
410 rb_property_view_set_model_internal (view, g_value_get_object (value));
411 break;
412 case PROP_DRAGGABLE:
413 view->priv->draggable = g_value_get_boolean (value);
414 break;
415 default:
416 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
417 break;
418 }
419 }
420
421 static void
422 rb_property_view_get_property (GObject *object,
423 guint prop_id,
424 GValue *value,
425 GParamSpec *pspec)
426 {
427 RBPropertyView *view = RB_PROPERTY_VIEW (object);
428
429 switch (prop_id) {
430 case PROP_DB:
431 g_value_set_object (value, view->priv->db);
432 break;
433 case PROP_PROP:
434 g_value_set_enum (value, view->priv->propid);
435 break;
436 case PROP_TITLE:
437 g_value_set_string (value, view->priv->title);
438 break;
439 case PROP_MODEL:
440 g_value_set_object (value, view->priv->prop_model);
441 break;
442 case PROP_DRAGGABLE:
443 g_value_set_boolean (value, view->priv->draggable);
444 break;
445 default:
446 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
447 break;
448 }
449 }
450
451 /**
452 * rb_property_view_new:
453 * @db: #RhythmDB instance
454 * @propid: property ID to be displayed in the property view
455 * @title: title of the property view
456 *
457 * Creates a new #RBPropertyView displaying the specified property.
458 *
459 * Return value: new property view instance
460 */
461 RBPropertyView *
462 rb_property_view_new (RhythmDB *db,
463 guint propid,
464 const char *title)
465 {
466 RBPropertyView *view;
467
468 view = RB_PROPERTY_VIEW (g_object_new (RB_TYPE_PROPERTY_VIEW,
469 "hadjustment", NULL,
470 "vadjustment", NULL,
471 "hscrollbar_policy", GTK_POLICY_AUTOMATIC,
472 "vscrollbar_policy", GTK_POLICY_AUTOMATIC,
473 "hexpand", TRUE,
474 "vexpand", TRUE,
475 "shadow_type", GTK_SHADOW_IN,
476 "db", db,
477 "prop", propid,
478 "title", title,
479 "draggable", TRUE,
480 NULL));
481
482 g_return_val_if_fail (view->priv != NULL, NULL);
483
484 return view;
485 }
486
487 /**
488 * rb_property_view_set_selection_mode:
489 * @view: a #RBPropertyView
490 * @mode: the new #GtkSelectionMode for the property view
491 *
492 * Sets the selection mode (single or multiple) for the property view>
493 * The default selection mode is single.
494 */
495 void
496 rb_property_view_set_selection_mode (RBPropertyView *view,
497 GtkSelectionMode mode)
498 {
499 g_return_if_fail (RB_IS_PROPERTY_VIEW (view));
500 g_return_if_fail (mode == GTK_SELECTION_SINGLE || mode == GTK_SELECTION_MULTIPLE);
501
502 gtk_tree_selection_set_mode (gtk_tree_view_get_selection (GTK_TREE_VIEW (view->priv->treeview)),
503 mode);
504 }
505
506 /**
507 * rb_property_view_reset:
508 * @view: a #RBPropertyView
509 *
510 * Clears the selection in the property view.
511 */
512 void
513 rb_property_view_reset (RBPropertyView *view)
514 {
515 RhythmDBPropertyModel *model;
516
517 g_return_if_fail (RB_IS_PROPERTY_VIEW (view));
518
519 model = rhythmdb_property_model_new (view->priv->db, view->priv->propid);
520
521 rb_property_view_set_model_internal (view, model);
522 g_object_unref (model);
523 }
524
525 /**
526 * rb_property_view_get_model:
527 * @view: a #RBPropertyView
528 *
529 * Returns the #RhythmDBPropertyModel backing the view; no reference is taken
530 *
531 * Return value: (transfer none): property model
532 */
533 RhythmDBPropertyModel *
534 rb_property_view_get_model (RBPropertyView *view)
535 {
536 RhythmDBPropertyModel *model;
537
538 g_return_val_if_fail (RB_IS_PROPERTY_VIEW (view), NULL);
539
540 model = view->priv->prop_model;
541
542 return model;
543 }
544
545 /**
546 * rb_property_view_set_model:
547 * @view: a #RBPropertyView
548 * @model: the new #RhythmDBPropertyModel for the property view
549 *
550 * Replaces the model backing the property view.
551 */
552 void
553 rb_property_view_set_model (RBPropertyView *view,
554 RhythmDBPropertyModel *model)
555 {
556 g_return_if_fail (RB_IS_PROPERTY_VIEW (view));
557
558 rb_property_view_set_model_internal (view, model);
559 }
560
561 static void
562 rb_property_view_pre_row_deleted_cb (RhythmDBPropertyModel *model,
563 RBPropertyView *view)
564 {
565 view->priv->handling_row_deletion = TRUE;
566 rb_debug ("pre row deleted");
567 }
568
569 static void
570 rb_property_view_post_row_deleted_cb (GtkTreeModel *model,
571 GtkTreePath *path,
572 RBPropertyView *view)
573 {
574 view->priv->handling_row_deletion = FALSE;
575 rb_debug ("post row deleted");
576 if (gtk_tree_selection_count_selected_rows (view->priv->selection) == 0) {
577 GtkTreeIter first_iter;
578 rb_debug ("no rows selected, signalling reset");
579 if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (view->priv->prop_model), &first_iter)) {
580 g_signal_handlers_block_by_func (G_OBJECT (view->priv->selection),
581 G_CALLBACK (rb_property_view_selection_changed_cb),
582 view);
583 gtk_tree_selection_select_iter (view->priv->selection, &first_iter);
584 g_signal_emit (G_OBJECT (view), rb_property_view_signals[SELECTION_RESET], 0);
585 g_signal_handlers_unblock_by_func (G_OBJECT (view->priv->selection),
586 G_CALLBACK (rb_property_view_selection_changed_cb),
587 view);
588 }
589 }
590 }
591
592 /**
593 * rb_property_view_get_num_properties:
594 * @view: a #RBPropertyView
595 *
596 * Returns the number of property values present in the view.
597 *
598 * Return value: number of properties
599 */
600 guint
601 rb_property_view_get_num_properties (RBPropertyView *view)
602 {
603 g_return_val_if_fail (RB_IS_PROPERTY_VIEW (view), 0);
604
605 return gtk_tree_model_iter_n_children (GTK_TREE_MODEL (view->priv->prop_model),
606 NULL)-1;
607 }
608
609 static void
610 rb_property_view_cell_data_func (GtkTreeViewColumn *column,
611 GtkCellRenderer *renderer,
612 GtkTreeModel *tree_model,
613 GtkTreeIter *iter,
614 RBPropertyView *view)
615 {
616 char *title;
617 char *str;
618 guint number;
619 gboolean is_all;
620
621 gtk_tree_model_get (GTK_TREE_MODEL (tree_model), iter,
622 RHYTHMDB_PROPERTY_MODEL_COLUMN_TITLE, &title,
623 RHYTHMDB_PROPERTY_MODEL_COLUMN_PRIORITY, &is_all,
624 RHYTHMDB_PROPERTY_MODEL_COLUMN_NUMBER, &number, -1);
625
626 if (is_all) {
627 int nodes;
628 const char *fmt;
629
630 nodes = gtk_tree_model_iter_n_children (GTK_TREE_MODEL (tree_model), NULL);
631 /* Subtract one for the All node */
632 nodes--;
633
634 switch (view->priv->propid) {
635 case RHYTHMDB_PROP_ARTIST:
636 fmt = ngettext ("%d artist (%d)", "All %d artists (%d)", nodes);
637 break;
638 case RHYTHMDB_PROP_ALBUM:
639 fmt = ngettext ("%d album (%d)", "All %d albums (%d)", nodes);
640 break;
641 case RHYTHMDB_PROP_GENRE:
642 fmt = ngettext ("%d genre (%d)", "All %d genres (%d)", nodes);
643 break;
644 default:
645 fmt = ngettext ("%d (%d)", "All %d (%d)", nodes);
646 break;
647 }
648
649 str = g_strdup_printf (fmt, nodes, number);
650 } else {
651 str = g_strdup_printf (_("%s (%d)"), title, number);
652 }
653
654 g_object_set (G_OBJECT (renderer), "text", str,
655 "weight", G_UNLIKELY (is_all) ? PANGO_WEIGHT_BOLD : PANGO_WEIGHT_NORMAL,
656 NULL);
657 g_free (str);
658 g_free (title);
659 }
660
661 static void
662 rb_property_view_constructed (GObject *object)
663 {
664 GtkTreeViewColumn *column;
665 GtkCellRenderer *renderer;
666 RBPropertyView *view;
667
668 RB_CHAIN_GOBJECT_METHOD (rb_property_view_parent_class, constructed, object);
669
670 view = RB_PROPERTY_VIEW (object);
671
672 view->priv->prop_model = rhythmdb_property_model_new (view->priv->db, view->priv->propid);
673 view->priv->treeview = GTK_WIDGET (gtk_tree_view_new_with_model (GTK_TREE_MODEL (view->priv->prop_model)));
674
675 if (view->priv->draggable)
676 rhythmdb_property_model_enable_drag (view->priv->prop_model,
677 GTK_TREE_VIEW (view->priv->treeview));
678
679 g_signal_connect_object (G_OBJECT (view->priv->treeview),
680 "row_activated",
681 G_CALLBACK (rb_property_view_row_activated_cb),
682 view,
683 0);
684
685 view->priv->selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view->priv->treeview));
686 g_signal_connect_object (G_OBJECT (view->priv->selection),
687 "changed",
688 G_CALLBACK (rb_property_view_selection_changed_cb),
689 view,
690 0);
691 g_signal_connect_object (G_OBJECT (view->priv->treeview),
692 "popup_menu",
693 G_CALLBACK (rb_property_view_popup_menu_cb),
694 view,
695 0);
696
697 g_signal_connect_object (G_OBJECT (view->priv->treeview),
698 "button_press_event",
699 G_CALLBACK (rb_property_view_button_press_cb),
700 view,
701 0);
702
703 gtk_container_add (GTK_CONTAINER (view), view->priv->treeview);
704
705 gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (view->priv->treeview), TRUE);
706 gtk_tree_selection_set_mode (view->priv->selection, GTK_SELECTION_SINGLE);
707
708 column = gtk_tree_view_column_new ();
709 renderer = gtk_cell_renderer_text_new ();
710 gtk_tree_view_column_pack_start (column, renderer, TRUE);
711 gtk_tree_view_column_set_cell_data_func (column, renderer,
712 (GtkTreeCellDataFunc) rb_property_view_cell_data_func,
713 view, NULL);
714 gtk_tree_view_column_set_title (column, view->priv->title);
715 gtk_tree_view_column_set_sizing (column, GTK_TREE_VIEW_COLUMN_FIXED);
716 gtk_tree_view_append_column (GTK_TREE_VIEW (view->priv->treeview),
717 column);
718 }
719
720 static void
721 rb_property_view_row_activated_cb (GtkTreeView *treeview,
722 GtkTreePath *path,
723 GtkTreeViewColumn *column,
724 RBPropertyView *view)
725 {
726 GtkTreeIter iter;
727 char *val;
728 gboolean is_all;
729
730 rb_debug ("row activated");
731 g_return_if_fail (gtk_tree_model_get_iter (GTK_TREE_MODEL (view->priv->prop_model),
732 &iter, path));
733
734 gtk_tree_model_get (GTK_TREE_MODEL (view->priv->prop_model), &iter,
735 RHYTHMDB_PROPERTY_MODEL_COLUMN_TITLE, &val,
736 RHYTHMDB_PROPERTY_MODEL_COLUMN_PRIORITY, &is_all, -1);
737
738 rb_debug ("emitting property activated");
739 g_signal_emit (G_OBJECT (view), rb_property_view_signals[PROPERTY_ACTIVATED], 0,
740 is_all ? NULL : val);
741
742 g_free (val);
743 }
744
745 /**
746 * rb_property_view_set_selection:
747 * @view: a #RBPropertyView
748 * @vals: (element-type utf8): the values to be selected
749 *
750 * Replaces the selection in the property view. All values in the list
751 * that are present in the view will be selected, and the view will be
752 * scrolled to show the last value selected.
753 */
754 void
755 rb_property_view_set_selection (RBPropertyView *view,
756 const GList *vals)
757 {
758 g_return_if_fail (RB_IS_PROPERTY_VIEW (view));
759
760 view->priv->handling_row_deletion = TRUE;
761
762 gtk_tree_selection_unselect_all (view->priv->selection);
763
764 for (; vals ; vals = vals->next) {
765 GtkTreeIter iter;
766
767 if (rhythmdb_property_model_iter_from_string (view->priv->prop_model, vals->data, &iter)) {
768 GtkTreePath *path;
769
770 gtk_tree_selection_select_iter (view->priv->selection, &iter);
771 path = gtk_tree_model_get_path (GTK_TREE_MODEL (view->priv->prop_model), &iter);
772 if (path != NULL) {
773 gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (view->priv->treeview),
774 path, NULL, TRUE,
775 0.5, 0.0);
776 gtk_tree_path_free (path);
777 }
778
779 }
780 }
781
782 view->priv->handling_row_deletion = FALSE;
783 rb_property_view_selection_changed_cb (view->priv->selection, view);
784 }
785
786 /**
787 * rb_property_view_get_selection:
788 * @view: a #RBPropertyView
789 *
790 * Returns a #GList containing the selected property values. The list must
791 * be freed by the caller.
792 *
793 * Return value: (element-type utf8) (transfer full): list of selected values
794 */
795 GList *
796 rb_property_view_get_selection (RBPropertyView *view)
797 {
798 gboolean is_all = TRUE;
799 GtkTreeModel *model;
800 GtkTreeIter iter;
801 GList *selected_rows, *tem;
802 GList *selected_properties = NULL;
803
804 selected_rows = gtk_tree_selection_get_selected_rows (view->priv->selection, &model);
805 for (tem = selected_rows; tem; tem = tem->next) {
806 char *selected_prop = NULL;
807
808 g_assert (gtk_tree_model_get_iter (model, &iter, tem->data));
809 gtk_tree_model_get (model, &iter,
810 RHYTHMDB_PROPERTY_MODEL_COLUMN_TITLE, &selected_prop,
811 RHYTHMDB_PROPERTY_MODEL_COLUMN_PRIORITY, &is_all, -1);
812 if (is_all) {
813 rb_list_deep_free (selected_properties);
814 selected_properties = NULL;
815 break;
816 }
817 selected_properties = g_list_prepend (selected_properties,
818 selected_prop);
819 }
820
821 g_list_foreach (selected_rows, (GFunc) gtk_tree_path_free, NULL);
822 g_list_free (selected_rows);
823
824 return selected_properties;
825 }
826
827 static void
828 rb_property_view_selection_changed_cb (GtkTreeSelection *selection,
829 RBPropertyView *view)
830 {
831 char *selected_prop = NULL;
832 gboolean is_all = TRUE;
833 GtkTreeModel *model;
834 GtkTreeIter iter;
835
836 if (view->priv->handling_row_deletion)
837 return;
838
839 rb_debug ("selection changed");
840 if (gtk_tree_selection_get_mode (selection) == GTK_SELECTION_MULTIPLE) {
841 GList *selected_rows, *tem;
842 GList *selected_properties = NULL;
843
844 selected_rows = gtk_tree_selection_get_selected_rows (view->priv->selection, &model);
845 for (tem = selected_rows; tem; tem = tem->next) {
846 g_assert (gtk_tree_model_get_iter (model, &iter, tem->data));
847 gtk_tree_model_get (model, &iter,
848 RHYTHMDB_PROPERTY_MODEL_COLUMN_TITLE, &selected_prop,
849 RHYTHMDB_PROPERTY_MODEL_COLUMN_PRIORITY, &is_all, -1);
850 if (is_all) {
851 g_list_free (selected_properties);
852 selected_properties = NULL;
853 break;
854 }
855 selected_properties = g_list_prepend (selected_properties,
856 g_strdup (selected_prop));
857 }
858
859 g_list_foreach (selected_rows, (GFunc) gtk_tree_path_free, NULL);
860 g_list_free (selected_rows);
861
862 if (is_all) {
863 g_signal_handlers_block_by_func (G_OBJECT (view->priv->selection),
864 G_CALLBACK (rb_property_view_selection_changed_cb),
865 view);
866 gtk_tree_selection_unselect_all (selection);
867 if (gtk_tree_model_get_iter_first (model, &iter))
868 gtk_tree_selection_select_iter (selection, &iter);
869 g_signal_handlers_unblock_by_func (G_OBJECT (view->priv->selection),
870 G_CALLBACK (rb_property_view_selection_changed_cb),
871 view);
872 }
873 g_signal_emit (G_OBJECT (view), rb_property_view_signals[PROPERTIES_SELECTED], 0,
874 selected_properties);
875 rb_list_deep_free (selected_properties);
876 } else {
877 if (gtk_tree_selection_get_selected (view->priv->selection, &model, &iter)) {
878 gtk_tree_model_get (model, &iter,
879 RHYTHMDB_PROPERTY_MODEL_COLUMN_TITLE, &selected_prop,
880 RHYTHMDB_PROPERTY_MODEL_COLUMN_PRIORITY, &is_all, -1);
881 g_signal_emit (G_OBJECT (view), rb_property_view_signals[PROPERTY_SELECTED], 0,
882 is_all ? NULL : selected_prop);
883 } else {
884 if (gtk_tree_model_get_iter_first (model, &iter))
885 gtk_tree_selection_select_iter (selection, &iter);
886 g_signal_emit (G_OBJECT (view), rb_property_view_signals[PROPERTY_SELECTED], 0,
887 NULL);
888 }
889 }
890
891 g_free (selected_prop);
892 }
893
894 static gboolean
895 rb_property_view_popup_menu_cb (GtkTreeView *treeview,
896 RBPropertyView *view)
897 {
898 g_signal_emit (G_OBJECT (view), rb_property_view_signals[SHOW_POPUP], 0);
899 return TRUE;
900 }
901
902 /**
903 * rb_property_view_append_column_custom:
904 * @view: a #RBPropertyView
905 * @column: a #GtkTreeViewColumn to append to the view
906 *
907 * Appends a custom created column to the view.
908 */
909 void
910 rb_property_view_append_column_custom (RBPropertyView *view,
911 GtkTreeViewColumn *column)
912 {
913 g_return_if_fail (RB_IS_PROPERTY_VIEW (view));
914
915 gtk_tree_view_append_column (GTK_TREE_VIEW (view->priv->treeview), column);
916 }
917
918 static gboolean
919 rb_property_view_button_press_cb (GtkTreeView *tree,
920 GdkEventButton *event,
921 RBPropertyView *view)
922 {
923
924 if (event->button == 3) {
925 GtkTreeSelection *selection;
926 GtkTreePath *path;
927
928 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view->priv->treeview));
929
930 gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (view->priv->treeview), event->x, event->y, &path, NULL, NULL, NULL);
931 if (path == NULL) {
932 gtk_tree_selection_unselect_all (selection);
933 } else {
934 GtkTreeModel *model;
935 GtkTreeIter iter;
936 char *val;
937 GList *lst = NULL;
938
939 model = gtk_tree_view_get_model (GTK_TREE_VIEW (view->priv->treeview));
940 if (gtk_tree_model_get_iter (model, &iter, path)) {
941 gtk_tree_model_get (model, &iter,
942 RHYTHMDB_PROPERTY_MODEL_COLUMN_TITLE, &val, -1);
943 lst = g_list_prepend (lst, (gpointer) val);
944 rb_property_view_set_selection (view, lst);
945 g_free (val);
946 }
947 }
948 g_signal_emit (G_OBJECT (view), rb_property_view_signals[SHOW_POPUP], 0);
949 return TRUE;
950 }
951
952 return FALSE;
953 }
954
955 /**
956 * rb_property_view_set_search_func:
957 * @view: a #RBPropertyView
958 * @func: tree view search function to use for this view
959 * @func_data: data to pass to the search function
960 * @notify: function to call to dispose of the data
961 *
962 * Sets the compare function for the interactive search capabilities.
963 * The function must return FALSE when the search key string matches
964 * the row it is passed.
965 */
966 void
967 rb_property_view_set_search_func (RBPropertyView *view,
968 GtkTreeViewSearchEqualFunc func,
969 gpointer func_data,
970 GDestroyNotify notify)
971 {
972 g_return_if_fail (RB_IS_PROPERTY_VIEW (view));
973
974 gtk_tree_view_set_search_equal_func (GTK_TREE_VIEW (view->priv->treeview),
975 func, func_data,
976 notify);
977 }