hythmbox-2.98/widgets/rb-property-view.c

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 }