Updated Vietnamese translation
[rhythmbox.git] / widgets / rb-property-view.c
blob40214d5910793e2d68b8a6e3ac5d0cfc388c314b
1 /*
2 * arch-tag: Implementation of widget to display RhythmDB properties
4 * Copyright (C) 2003 Colin Walters <walters@verbum.org>
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
22 #include <gtk/gtktreeview.h>
24 #include <gtk/gtktreeselection.h>
25 #include <gtk/gtkcellrenderertext.h>
26 #include <gtk/gtkiconfactory.h>
27 #include <gtk/gtktooltips.h>
28 #include <gdk/gdkkeysyms.h>
29 #include <config.h>
30 #include <libgnome/gnome-i18n.h>
31 #include <libgnomevfs/gnome-vfs-utils.h>
32 #include <string.h>
33 #include <stdlib.h>
35 #include "rb-property-view.h"
36 #include "rb-dialog.h"
37 #include "rb-debug.h"
38 #include "rhythmdb.h"
39 #include "rhythmdb-property-model.h"
40 #include "rb-stock-icons.h"
41 #include "eel-gconf-extensions.h"
43 static void rb_property_view_class_init (RBPropertyViewClass *klass);
44 static void rb_property_view_init (RBPropertyView *view);
45 static void rb_property_view_finalize (GObject *object);
46 static void rb_property_view_set_property (GObject *object,
47 guint prop_id,
48 const GValue *value,
49 GParamSpec *pspec);
50 static void rb_property_view_get_property (GObject *object,
51 guint prop_id,
52 GValue *value,
53 GParamSpec *pspec);
54 static GObject * rb_property_view_constructor (GType type, guint n_construct_properties,
55 GObjectConstructParam *construct_properties);
56 static void rb_property_view_row_activated_cb (GtkTreeView *treeview,
57 GtkTreePath *path,
58 GtkTreeViewColumn *column,
59 RBPropertyView *view);
60 static void rb_property_view_selection_changed_cb (GtkTreeSelection *selection,
61 RBPropertyView *view);
62 static void rb_property_view_pre_row_deleted_cb (RhythmDBPropertyModel *model,
63 RBPropertyView *view);
64 static void rb_property_view_post_row_deleted_cb (GtkTreeModel *model,
65 GtkTreePath *path,
66 RBPropertyView *view);
68 struct RBPropertyViewPrivate
70 RhythmDB *db;
72 RhythmDBPropType propid;
74 RhythmDBPropertyModel *prop_model;
76 char *title;
78 GtkWidget *treeview;
79 GtkTreeSelection *selection;
81 gboolean handling_row_deletion;
84 enum
86 PROPERTY_SELECTED,
87 PROPERTIES_SELECTED,
88 PROPERTY_ACTIVATED,
89 SELECTION_RESET,
90 LAST_SIGNAL
93 enum
95 PROP_0,
96 PROP_DB,
97 PROP_PROP,
98 PROP_TITLE,
99 PROP_MODEL,
102 static guint rb_property_view_signals[LAST_SIGNAL] = { 0 };
104 enum {
105 TARGET_ALBUMS,
106 TARGET_GENRE,
107 TARGET_ARTISTS,
108 TARGET_URIS,
111 static const GtkTargetEntry targets_album [] = {
112 { "text/x-rhythmbox-album", 0, TARGET_ALBUMS },
113 { "text/uri-list", 0, TARGET_URIS },
115 static const GtkTargetEntry targets_genre [] = {
116 { "text/x-rhythmbox-genre", 0, TARGET_GENRE },
117 { "text/uri-list", 0, TARGET_URIS },
119 static const GtkTargetEntry targets_artist [] = {
120 { "text/x-rhythmbox-artist", 0, TARGET_ARTISTS },
121 { "text/uri-list", 0, TARGET_URIS },
124 G_DEFINE_TYPE (RBPropertyView, rb_property_view, GTK_TYPE_SCROLLED_WINDOW)
127 static void
128 rb_property_view_class_init (RBPropertyViewClass *klass)
130 GObjectClass *object_class = G_OBJECT_CLASS (klass);
132 object_class->finalize = rb_property_view_finalize;
133 object_class->constructor = rb_property_view_constructor;
135 object_class->set_property = rb_property_view_set_property;
136 object_class->get_property = rb_property_view_get_property;
138 g_object_class_install_property (object_class,
139 PROP_DB,
140 g_param_spec_object ("db",
141 "RhythmDB",
142 "RhythmDB database",
143 RHYTHMDB_TYPE,
144 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
146 g_object_class_install_property (object_class,
147 PROP_PROP,
148 g_param_spec_enum ("prop",
149 "PropertyId",
150 "RhythmDBPropType",
151 RHYTHMDB_TYPE_PROP,
152 RHYTHMDB_PROP_TYPE,
153 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
155 g_object_class_install_property (object_class,
156 PROP_TITLE,
157 g_param_spec_string ("title",
158 "title",
159 "title",
161 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
162 g_object_class_install_property (object_class,
163 PROP_MODEL,
164 g_param_spec_object ("property-model",
165 "property model",
166 "RhythmDBPropertyModel",
167 RHYTHMDB_TYPE_PROPERTY_MODEL,
168 G_PARAM_READWRITE));
170 rb_property_view_signals[PROPERTY_ACTIVATED] =
171 g_signal_new ("property-activated",
172 G_OBJECT_CLASS_TYPE (object_class),
173 G_SIGNAL_RUN_LAST,
174 G_STRUCT_OFFSET (RBPropertyViewClass, property_activated),
175 NULL, NULL,
176 g_cclosure_marshal_VOID__STRING,
177 G_TYPE_NONE,
179 G_TYPE_STRING);
181 rb_property_view_signals[PROPERTY_SELECTED] =
182 g_signal_new ("property-selected",
183 G_OBJECT_CLASS_TYPE (object_class),
184 G_SIGNAL_RUN_LAST,
185 G_STRUCT_OFFSET (RBPropertyViewClass, property_selected),
186 NULL, NULL,
187 g_cclosure_marshal_VOID__STRING,
188 G_TYPE_NONE,
190 G_TYPE_STRING);
192 rb_property_view_signals[PROPERTIES_SELECTED] =
193 g_signal_new ("properties-selected",
194 G_OBJECT_CLASS_TYPE (object_class),
195 G_SIGNAL_RUN_LAST,
196 G_STRUCT_OFFSET (RBPropertyViewClass, properties_selected),
197 NULL, NULL,
198 g_cclosure_marshal_VOID__POINTER,
199 G_TYPE_NONE,
201 G_TYPE_POINTER);
203 rb_property_view_signals[SELECTION_RESET] =
204 g_signal_new ("property-selection-reset",
205 G_OBJECT_CLASS_TYPE (object_class),
206 G_SIGNAL_RUN_LAST,
207 G_STRUCT_OFFSET (RBPropertyViewClass, selection_reset),
208 NULL, NULL,
209 g_cclosure_marshal_VOID__VOID,
210 G_TYPE_NONE,
211 0);
215 static void
216 rb_property_view_init (RBPropertyView *view)
218 view->priv = g_new0 (RBPropertyViewPrivate, 1);
221 static void
222 rb_property_view_finalize (GObject *object)
224 RBPropertyView *view;
226 g_return_if_fail (object != NULL);
227 g_return_if_fail (RB_IS_PROPERTY_VIEW (object));
229 view = RB_PROPERTY_VIEW (object);
231 g_return_if_fail (view->priv != NULL);
233 g_free (view->priv->title);
234 g_free (view->priv);
236 G_OBJECT_CLASS (rb_property_view_parent_class)->finalize (object);
240 static void
241 rb_property_view_set_property (GObject *object,
242 guint prop_id,
243 const GValue *value,
244 GParamSpec *pspec)
246 RBPropertyView *view = RB_PROPERTY_VIEW (object);
248 switch (prop_id)
250 case PROP_DB:
251 view->priv->db = g_value_get_object (value);
252 break;
253 case PROP_PROP:
254 view->priv->propid = g_value_get_enum (value);
255 break;
256 case PROP_TITLE:
257 view->priv->title = g_value_dup_string (value);
258 break;
259 case PROP_MODEL:
261 GtkTreeIter iter;
263 if (view->priv->prop_model) {
264 g_signal_handlers_disconnect_by_func (G_OBJECT (view->priv->prop_model),
265 G_CALLBACK (rb_property_view_pre_row_deleted_cb),
266 view);
267 g_signal_handlers_disconnect_by_func (G_OBJECT (view->priv->prop_model),
268 G_CALLBACK (rb_property_view_post_row_deleted_cb),
269 view);
270 g_object_unref (G_OBJECT (view->priv->prop_model));
273 view->priv->prop_model = g_value_get_object (value);
275 if (!view->priv->prop_model)
276 break;
278 gtk_tree_view_set_model (GTK_TREE_VIEW (view->priv->treeview),
279 GTK_TREE_MODEL (view->priv->prop_model));
281 g_object_ref (G_OBJECT (view->priv->prop_model));
282 g_signal_connect_object (G_OBJECT (view->priv->prop_model),
283 "pre-row-deletion",
284 G_CALLBACK (rb_property_view_pre_row_deleted_cb),
285 view,
287 g_signal_connect_object (G_OBJECT (view->priv->prop_model),
288 "row_deleted",
289 G_CALLBACK (rb_property_view_post_row_deleted_cb),
290 view,
291 G_CONNECT_AFTER);
293 g_signal_handlers_block_by_func (G_OBJECT (view->priv->selection),
294 G_CALLBACK (rb_property_view_selection_changed_cb),
295 view);
297 gtk_tree_model_get_iter_first (GTK_TREE_MODEL (view->priv->prop_model), &iter);
298 gtk_tree_selection_unselect_all (view->priv->selection);
299 gtk_tree_selection_select_iter (view->priv->selection, &iter);
300 g_signal_handlers_unblock_by_func (G_OBJECT (view->priv->selection),
301 G_CALLBACK (rb_property_view_selection_changed_cb),
302 view);
305 break;
306 default:
307 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
308 break;
312 static void
313 rb_property_view_get_property (GObject *object,
314 guint prop_id,
315 GValue *value,
316 GParamSpec *pspec)
318 RBPropertyView *view = RB_PROPERTY_VIEW (object);
320 switch (prop_id)
322 case PROP_DB:
323 g_value_set_object (value, view->priv->db);
324 break;
325 case PROP_PROP:
326 g_value_set_enum (value, view->priv->propid);
327 break;
328 case PROP_TITLE:
329 g_value_set_string (value, view->priv->title);
330 break;
331 case PROP_MODEL:
332 g_value_set_object (value, view->priv->prop_model);
333 break;
334 default:
335 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
336 break;
340 RBPropertyView *
341 rb_property_view_new (RhythmDB *db, guint propid, const char *title)
343 RBPropertyView *view;
345 view = RB_PROPERTY_VIEW (g_object_new (RB_TYPE_PROPERTY_VIEW,
346 "hadjustment", NULL,
347 "vadjustment", NULL,
348 "hscrollbar_policy", GTK_POLICY_AUTOMATIC,
349 "vscrollbar_policy", GTK_POLICY_ALWAYS,
350 "shadow_type", GTK_SHADOW_IN,
351 "db", db,
352 "prop", propid,
353 "title", title,
354 NULL));
356 g_return_val_if_fail (view->priv != NULL, NULL);
358 return view;
361 void
362 rb_property_view_set_selection_mode (RBPropertyView *view,
363 GtkSelectionMode mode)
365 g_return_if_fail (mode == GTK_SELECTION_SINGLE || mode == GTK_SELECTION_MULTIPLE);
366 gtk_tree_selection_set_mode (gtk_tree_view_get_selection (GTK_TREE_VIEW (view->priv->treeview)),
367 mode);
370 void
371 rb_property_view_reset (RBPropertyView *view)
373 RhythmDBPropertyModel *model;
375 model = rhythmdb_property_model_new (view->priv->db, view->priv->propid);
377 g_object_set (G_OBJECT (view), "property-model", model, NULL);
378 g_object_unref (G_OBJECT (model));
381 RhythmDBPropertyModel *
382 rb_property_view_get_model (RBPropertyView *view)
384 RhythmDBPropertyModel *model;
386 g_object_get (G_OBJECT (view), "property-model", &model, NULL);
387 return model;
390 void
391 rb_property_view_set_model (RBPropertyView *view, RhythmDBPropertyModel *model)
393 g_object_set (G_OBJECT (view), "property-model", model, NULL);
396 static void
397 rb_property_view_pre_row_deleted_cb (RhythmDBPropertyModel *model,
398 RBPropertyView *view)
400 view->priv->handling_row_deletion = TRUE;
401 rb_debug ("pre row deleted");
404 static void
405 rb_property_view_post_row_deleted_cb (GtkTreeModel *model,
406 GtkTreePath *path,
407 RBPropertyView *view)
409 view->priv->handling_row_deletion = FALSE;
410 rb_debug ("post row deleted");
411 if (gtk_tree_selection_count_selected_rows (view->priv->selection) == 0) {
412 GtkTreeIter first_iter;
413 rb_debug ("no rows selected, signalling reset");
414 gtk_tree_model_get_iter_first (GTK_TREE_MODEL (view->priv->prop_model),
415 &first_iter);
416 g_signal_handlers_block_by_func (G_OBJECT (view->priv->selection),
417 G_CALLBACK (rb_property_view_selection_changed_cb),
418 view);
419 gtk_tree_selection_select_iter (view->priv->selection, &first_iter);
420 g_signal_emit (G_OBJECT (view), rb_property_view_signals[SELECTION_RESET], 0);
421 g_signal_handlers_unblock_by_func (G_OBJECT (view->priv->selection),
422 G_CALLBACK (rb_property_view_selection_changed_cb),
423 view);
427 guint
428 rb_property_view_get_num_properties (RBPropertyView *view)
430 return gtk_tree_model_iter_n_children (GTK_TREE_MODEL (view->priv->prop_model),
431 NULL)-1;
434 static void
435 rb_property_view_cell_data_func (GtkTreeViewColumn *column, GtkCellRenderer *renderer,
436 GtkTreeModel *tree_model, GtkTreeIter *iter,
437 RBPropertyView *view)
439 char *str;
440 gboolean bold;
442 gtk_tree_model_get (GTK_TREE_MODEL (tree_model), iter, 0, &str,
443 1, &bold, -1);
445 g_object_set (G_OBJECT (renderer), "text", str,
446 "weight", G_UNLIKELY (bold) ? PANGO_WEIGHT_BOLD : PANGO_WEIGHT_NORMAL,
447 NULL);
448 g_free (str);
451 static GObject *
452 rb_property_view_constructor (GType type, guint n_construct_properties,
453 GObjectConstructParam *construct_properties)
455 GtkTreeViewColumn *column;
456 GtkCellRenderer *renderer;
458 RBPropertyView *view;
459 RBPropertyViewClass *klass;
461 klass = RB_PROPERTY_VIEW_CLASS (g_type_class_peek (RB_TYPE_PROPERTY_VIEW));
463 view = RB_PROPERTY_VIEW (G_OBJECT_CLASS (rb_property_view_parent_class)->
464 constructor (type, n_construct_properties, construct_properties));
466 view->priv->prop_model = rhythmdb_property_model_new (view->priv->db, view->priv->propid);
467 view->priv->treeview = GTK_WIDGET (gtk_tree_view_new_with_model (GTK_TREE_MODEL (view->priv->prop_model)));
469 rhythmdb_property_model_enable_drag (view->priv->prop_model,
470 GTK_TREE_VIEW (view->priv->treeview));
472 g_signal_connect_object (G_OBJECT (view->priv->treeview),
473 "row_activated",
474 G_CALLBACK (rb_property_view_row_activated_cb),
475 view,
478 view->priv->selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view->priv->treeview));
479 g_signal_connect_object (G_OBJECT (view->priv->selection),
480 "changed",
481 G_CALLBACK (rb_property_view_selection_changed_cb),
482 view,
485 gtk_container_add (GTK_CONTAINER (view), view->priv->treeview);
487 gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (view->priv->treeview), TRUE);
488 gtk_tree_selection_set_mode (view->priv->selection, GTK_SELECTION_SINGLE);
490 column = gtk_tree_view_column_new ();
491 renderer = gtk_cell_renderer_text_new ();
492 gtk_tree_view_column_pack_start (column, renderer, TRUE);
493 gtk_tree_view_column_set_cell_data_func (column, renderer,
494 (GtkTreeCellDataFunc) rb_property_view_cell_data_func,
495 view, NULL);
496 gtk_tree_view_column_set_title (column, view->priv->title);
497 gtk_tree_view_column_set_sizing (column, GTK_TREE_VIEW_COLUMN_FIXED);
498 gtk_tree_view_append_column (GTK_TREE_VIEW (view->priv->treeview),
499 column);
501 return G_OBJECT (view);
504 static void
505 rb_property_view_row_activated_cb (GtkTreeView *treeview,
506 GtkTreePath *path,
507 GtkTreeViewColumn *column,
508 RBPropertyView *view)
510 GtkTreeIter iter;
511 const char *val;
512 gboolean is_all;
514 rb_debug ("row activated");
515 gtk_tree_model_get_iter (GTK_TREE_MODEL (view->priv->prop_model), &iter, path);
517 gtk_tree_model_get (GTK_TREE_MODEL (view->priv->prop_model), &iter, 0,
518 &val, 1, &is_all, -1);
520 rb_debug ("emitting property activated");
521 g_signal_emit (G_OBJECT (view), rb_property_view_signals[PROPERTY_ACTIVATED], 0,
522 is_all ? NULL : val);
525 void
526 rb_property_view_set_selection (RBPropertyView *view, const GList *vals)
528 view->priv->handling_row_deletion = TRUE;
530 gtk_tree_selection_unselect_all (view->priv->selection);
532 for (; vals ; vals = vals->next) {
533 GtkTreeIter iter;
535 if (rhythmdb_property_model_iter_from_string (view->priv->prop_model, vals->data, &iter)) {
536 GtkTreePath *path;
538 gtk_tree_selection_select_iter (view->priv->selection, &iter);
539 path = gtk_tree_model_get_path (GTK_TREE_MODEL (view->priv->prop_model), &iter);
540 if (path != NULL) {
541 gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (view->priv->treeview),
542 path, NULL, TRUE,
543 0.5, 0.0);
544 gtk_tree_path_free (path);
550 view->priv->handling_row_deletion = FALSE;
551 rb_property_view_selection_changed_cb (view->priv->selection, view);
554 static void
555 rb_property_view_selection_changed_cb (GtkTreeSelection *selection,
556 RBPropertyView *view)
558 const char *selected_prop;
559 gboolean is_all;
560 GtkTreeModel *model;
561 GtkTreeIter iter;
563 if (view->priv->handling_row_deletion)
564 return;
566 rb_debug ("selection changed");
567 if (gtk_tree_selection_get_mode (selection) == GTK_SELECTION_MULTIPLE) {
568 GList *selected_rows, *tem;
569 GList *selected_properties = NULL;
571 selected_rows = gtk_tree_selection_get_selected_rows (view->priv->selection, &model);
572 for (tem = selected_rows; tem; tem = tem->next) {
573 g_assert (gtk_tree_model_get_iter (model, &iter, tem->data));
574 gtk_tree_model_get (model, &iter, 0, &selected_prop, 1, &is_all, -1);
575 if (is_all) {
576 g_list_free (selected_properties);
577 selected_properties = NULL;
578 break;
580 selected_properties = g_list_append (selected_properties,
581 g_strdup (selected_prop));
584 g_list_foreach (selected_rows, (GFunc) gtk_tree_path_free, NULL);
585 g_list_free (selected_rows);
587 if (is_all) {
588 g_signal_handlers_block_by_func (G_OBJECT (view->priv->selection),
589 G_CALLBACK (rb_property_view_selection_changed_cb),
590 view);
591 gtk_tree_selection_unselect_all (selection);
592 gtk_tree_model_get_iter_first (model, &iter);
593 gtk_tree_selection_select_iter (selection, &iter);
594 g_signal_handlers_unblock_by_func (G_OBJECT (view->priv->selection),
595 G_CALLBACK (rb_property_view_selection_changed_cb),
596 view);
598 g_signal_emit (G_OBJECT (view), rb_property_view_signals[PROPERTIES_SELECTED], 0,
599 selected_properties);
600 } else {
601 if (gtk_tree_selection_get_selected (view->priv->selection, &model, &iter)) {
602 gtk_tree_model_get (model, &iter, 0, &selected_prop, 1, &is_all, -1);
603 g_signal_emit (G_OBJECT (view), rb_property_view_signals[PROPERTY_SELECTED], 0,
604 is_all ? NULL : selected_prop);
609 GtkWidget*
610 rb_property_view_get_treeview (RBPropertyView *view)
612 return view->priv->treeview;