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