No issues found
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
2 *
3 * Copyright (C) 2003,2004 Colin Walters <walters@verbum.org>
4 * Copyright (C) 2010 Jonathan Matthew <jonathan@d14n.org>
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License as
8 * published by the Free Software Foundation; either version 2 of the
9 * License, or (at your option) any later version.
10 *
11 * The Rhythmbox authors hereby grant permission for non-GPL compatible
12 * GStreamer plugins to be used and distributed together with GStreamer
13 * and Rhythmbox. This permission is above and beyond the permissions granted
14 * by the GPL license by which Rhythmbox is covered. If you modify this code
15 * you may extend this exception to your version of the code, but you are not
16 * obligated to do so. If you do not wish to do so, delete this exception
17 * statement from your version.
18 *
19 * This program is distributed in the hope that it will be useful,
20 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
22 * General Public License for more details.
23 *
24 * You should have received a copy of the GNU General Public
25 * License along with this program; if not, write to the
26 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
27 * Boston, MA 02110-1301 USA.
28 *
29 */
30
31 #include "config.h"
32
33 #include <unistd.h>
34 #include <string.h>
35
36 #include <glib/gi18n.h>
37 #include <gdk/gdk.h>
38 #include <gdk/gdkkeysyms.h>
39 #include <gtk/gtk.h>
40
41 #include "rb-display-page-group.h"
42 #include "rb-display-page-tree.h"
43 #include "rb-display-page-model.h"
44 #include "rb-debug.h"
45 #include "rb-stock-icons.h"
46 #include "rb-marshal.h"
47 #include "rb-cell-renderer-pixbuf.h"
48 #include "gossip-cell-renderer-expander.h"
49 #include "rb-tree-dnd.h"
50 #include "rb-util.h"
51 #include "rb-auto-playlist-source.h"
52 #include "rb-static-playlist-source.h"
53
54 /**
55 * SECTION:rb-display-page-tree
56 * @short_description: widget displaying the tree of #RBDisplayPage instances
57 *
58 * The display page tree widget is a GtkTreeView backed by a GtkListStore
59 * containing the display page instances (sources and other things). Display
60 * pages include sources, such as the library and playlists, and other things
61 * like the visualization display.
62 *
63 * Display pages are shown in the list with an icon and the name. The current
64 * playing source is displayed in bold.
65 *
66 * Sources are divided into groups - library, stores, playlists, devices,
67 * network shares. Groups are displayed as headers, with expanders for hiding
68 * and showing the sources in the group. Sources themselves may also have
69 * child sources, such as playlists on portable audio players.
70 */
71
72 struct _RBDisplayPageTreePrivate
73 {
74 GtkWidget *treeview;
75 GtkCellRenderer *title_renderer;
76 GtkCellRenderer *expander_renderer;
77
78 RBDisplayPageModel *page_model;
79 GtkTreeSelection *selection;
80
81 int child_source_count;
82 GtkTreeViewColumn *main_column;
83
84 RBShell *shell;
85
86 GList *expand_rows;
87 GtkTreeRowReference *expand_select_row;
88 guint expand_rows_id;
89
90 GSettings *settings;
91 };
92
93
94 enum
95 {
96 PROP_0,
97 PROP_SHELL,
98 PROP_MODEL
99 };
100
101 enum
102 {
103 SELECTED,
104 DROP_RECEIVED,
105 LAST_SIGNAL
106 };
107
108 static guint signals[LAST_SIGNAL] = { 0 };
109
110 G_DEFINE_TYPE (RBDisplayPageTree, rb_display_page_tree, GTK_TYPE_SCROLLED_WINDOW)
111
112
113 static gboolean
114 retrieve_expander_state (RBDisplayPageTree *display_page_tree, RBDisplayPageGroup *group)
115 {
116 char **groups;
117 char *id;
118 gboolean collapsed;
119
120 groups = g_settings_get_strv (display_page_tree->priv->settings, "collapsed-groups");
121 g_object_get (group, "id", &id, NULL);
122 collapsed = rb_str_in_strv (id, (const char **)groups);
123 g_free (id);
124 g_strfreev (groups);
125
126 return (collapsed == FALSE);
127 }
128
129 static void
130 store_expander_state (RBDisplayPageTree *display_page_tree, RBDisplayPageGroup *group, gboolean expanded)
131 {
132 char **newgroups = NULL;
133 char **groups;
134 char *id;
135 int num;
136 int i;
137 int p;
138
139 groups = g_settings_get_strv (display_page_tree->priv->settings, "collapsed-groups");
140 g_object_get (group, "id", &id, NULL);
141
142 num = g_strv_length (groups);
143 p = 0;
144 if (rb_str_in_strv (id, (const char **)groups) && expanded) {
145 newgroups = g_new0(char *, num);
146 for (i = 0; i < num; i++) {
147 if (g_strcmp0 (groups[i], id) != 0) {
148 newgroups[p++] = g_strdup (groups[i]);
149 }
150 }
151 } else if (expanded == FALSE) {
152 newgroups = g_new0(char *, num + 2);
153 for (i = 0; i < num; i++) {
154 newgroups[i] = g_strdup (groups[i]);
155 }
156 newgroups[i] = g_strdup (id);
157 }
158
159 if (newgroups != NULL) {
160 g_settings_set_strv (display_page_tree->priv->settings, "collapsed-groups", (const char * const *)newgroups);
161 g_strfreev (newgroups);
162 }
163 g_strfreev (groups);
164 g_free (id);
165 }
166
167 static void
168 set_cell_background (RBDisplayPageTree *display_page_tree,
169 GtkCellRenderer *cell,
170 gboolean is_group,
171 gboolean is_active)
172 {
173 GdkRGBA color;
174
175 g_return_if_fail (display_page_tree != NULL);
176 g_return_if_fail (cell != NULL);
177
178 gtk_style_context_get_color (gtk_widget_get_style_context (GTK_WIDGET (display_page_tree)),
179 GTK_STATE_SELECTED,
180 &color);
181
182 if (!is_group) {
183 if (is_active) {
184 /* Here we take the current theme colour and add it to
185 * the colour for white and average the two. This
186 * gives a colour which is inline with the theme but
187 * slightly whiter.
188 */
189 color.red = (color.red + 1.0) / 2;
190 color.green = (color.green + 1.0) / 2;
191 color.blue = (color.blue + 1.0) / 2;
192
193 g_object_set (cell,
194 "cell-background-rgba", &color,
195 NULL);
196 } else {
197 g_object_set (cell,
198 "cell-background-rgba", NULL,
199 NULL);
200 }
201 } else {
202 /* don't set background for group heading */
203 }
204 }
205
206 static void
207 indent_level1_cell_data_func (GtkTreeViewColumn *tree_column,
208 GtkCellRenderer *cell,
209 GtkTreeModel *model,
210 GtkTreeIter *iter,
211 RBDisplayPageTree *display_page_tree)
212 {
213 GtkTreePath *path;
214 int depth;
215
216 path = gtk_tree_model_get_path (model, iter);
217 depth = gtk_tree_path_get_depth (path);
218 gtk_tree_path_free (path);
219 g_object_set (cell,
220 "text", " ",
221 "visible", depth > 1,
222 NULL);
223 }
224
225 static void
226 indent_level2_cell_data_func (GtkTreeViewColumn *tree_column,
227 GtkCellRenderer *cell,
228 GtkTreeModel *model,
229 GtkTreeIter *iter,
230 RBDisplayPageTree *display_page_tree)
231 {
232 GtkTreePath *path;
233 int depth;
234
235 path = gtk_tree_model_get_path (model, iter);
236 depth = gtk_tree_path_get_depth (path);
237 gtk_tree_path_free (path);
238 g_object_set (cell,
239 "text", " ",
240 "visible", depth > 2,
241 NULL);
242 }
243
244 static void
245 pixbuf_cell_data_func (GtkTreeViewColumn *tree_column,
246 GtkCellRenderer *cell,
247 GtkTreeModel *model,
248 GtkTreeIter *iter,
249 RBDisplayPageTree *display_page_tree)
250 {
251 RBDisplayPage *page;
252 GdkPixbuf *pixbuf;
253
254 gtk_tree_model_get (model, iter,
255 RB_DISPLAY_PAGE_MODEL_COLUMN_PAGE, &page,
256 -1);
257 g_object_get (page, "pixbuf", &pixbuf, NULL);
258
259 if (pixbuf == NULL) {
260 g_object_set (cell,
261 "visible", FALSE,
262 "pixbuf", NULL,
263 NULL);
264 } else {
265 g_object_set (cell,
266 "visible", TRUE,
267 "pixbuf", pixbuf,
268 NULL);
269 g_object_unref (pixbuf);
270 }
271
272 set_cell_background (display_page_tree, cell, RB_IS_DISPLAY_PAGE_GROUP (page), FALSE);
273 g_object_unref (page);
274 }
275
276 static void
277 title_cell_data_func (GtkTreeViewColumn *column,
278 GtkCellRenderer *renderer,
279 GtkTreeModel *tree_model,
280 GtkTreeIter *iter,
281 RBDisplayPageTree *display_page_tree)
282 {
283 RBDisplayPage *page;
284 char *name;
285 gboolean playing;
286
287 gtk_tree_model_get (GTK_TREE_MODEL (display_page_tree->priv->page_model), iter,
288 RB_DISPLAY_PAGE_MODEL_COLUMN_PAGE, &page,
289 RB_DISPLAY_PAGE_MODEL_COLUMN_PLAYING, &playing,
290 -1);
291
292 g_object_get (page, "name", &name, NULL);
293
294 g_object_set (renderer,
295 "text", name,
296 "weight", playing ? PANGO_WEIGHT_BOLD : PANGO_WEIGHT_NORMAL,
297 NULL);
298
299 set_cell_background (display_page_tree, renderer, RB_IS_DISPLAY_PAGE_GROUP (page), FALSE);
300
301 g_free (name);
302 g_object_unref (page);
303 }
304
305 static void
306 expander_cell_data_func (GtkTreeViewColumn *column,
307 GtkCellRenderer *cell,
308 GtkTreeModel *model,
309 GtkTreeIter *iter,
310 RBDisplayPageTree *display_page_tree)
311 {
312 RBDisplayPage *page;
313
314 if (gtk_tree_model_iter_has_child (model, iter)) {
315 GtkTreePath *path;
316 gboolean row_expanded;
317
318 path = gtk_tree_model_get_path (model, iter);
319 row_expanded = gtk_tree_view_row_expanded (GTK_TREE_VIEW (display_page_tree->priv->treeview),
320 path);
321 gtk_tree_path_free (path);
322
323 g_object_set (cell,
324 "visible", TRUE,
325 "expander-style", row_expanded ? GTK_EXPANDER_EXPANDED : GTK_EXPANDER_COLLAPSED,
326 NULL);
327 } else {
328 g_object_set (cell, "visible", FALSE, NULL);
329 }
330
331 gtk_tree_model_get (GTK_TREE_MODEL (display_page_tree->priv->page_model), iter,
332 RB_DISPLAY_PAGE_MODEL_COLUMN_PAGE, &page,
333 -1);
334 set_cell_background (display_page_tree, cell, RB_IS_DISPLAY_PAGE_GROUP (page), FALSE);
335 g_object_unref (page);
336 }
337
338 static void
339 row_activated_cb (GtkTreeView *treeview,
340 GtkTreePath *path,
341 GtkTreeViewColumn *column,
342 RBDisplayPageTree *display_page_tree)
343 {
344 GtkTreeModel *model;
345 GtkTreeIter iter;
346 RBDisplayPage *page;
347
348 model = gtk_tree_view_get_model (treeview);
349
350 g_return_if_fail (gtk_tree_model_get_iter (model, &iter, path));
351
352 gtk_tree_model_get (model, &iter,
353 RB_DISPLAY_PAGE_MODEL_COLUMN_PAGE, &page,
354 -1);
355
356 if (page != NULL) {
357 rb_debug ("page %p activated", page);
358 rb_display_page_activate (page);
359 g_object_unref (page);
360 }
361 }
362
363 static void
364 update_expanded_state (RBDisplayPageTree *display_page_tree,
365 GtkTreeIter *iter,
366 gboolean expanded)
367 {
368 GtkTreeModel *model;
369 RBDisplayPage *page;
370
371 model = gtk_tree_view_get_model (GTK_TREE_VIEW (display_page_tree->priv->treeview));
372 gtk_tree_model_get (model, iter,
373 RB_DISPLAY_PAGE_MODEL_COLUMN_PAGE, &page,
374 -1);
375 if (RB_IS_DISPLAY_PAGE_GROUP (page)) {
376 store_expander_state (display_page_tree, RB_DISPLAY_PAGE_GROUP (page), expanded);
377 }
378 }
379
380 static void
381 row_expanded_cb (GtkTreeView *treeview,
382 GtkTreeIter *iter,
383 GtkTreePath *path,
384 RBDisplayPageTree *display_page_tree)
385 {
386 update_expanded_state (display_page_tree, iter, TRUE);
387 }
388
389 static void
390 row_collapsed_cb (GtkTreeView *treeview,
391 GtkTreeIter *iter,
392 GtkTreePath *path,
393 RBDisplayPageTree *display_page_tree)
394 {
395 update_expanded_state (display_page_tree, iter, FALSE);
396 }
397
398 static void
399 drop_received_cb (RBDisplayPageModel *model,
400 RBDisplayPage *page,
401 GtkTreeViewDropPosition pos,
402 GtkSelectionData *data,
403 RBDisplayPageTree *display_page_tree)
404 {
405 rb_debug ("drop received");
406 g_signal_emit (display_page_tree, signals[DROP_RECEIVED], 0, page, data);
407 }
408
409 static gboolean
410 expand_rows_cb (RBDisplayPageTree *display_page_tree)
411 {
412 GList *l;
413 rb_debug ("expanding %d rows", g_list_length (display_page_tree->priv->expand_rows));
414 display_page_tree->priv->expand_rows_id = 0;
415
416 for (l = display_page_tree->priv->expand_rows; l != NULL; l = l->next) {
417 GtkTreePath *path;
418 path = gtk_tree_row_reference_get_path (l->data);
419 if (path != NULL) {
420 gtk_tree_view_expand_to_path (GTK_TREE_VIEW (display_page_tree->priv->treeview), path);
421 if (l->data == display_page_tree->priv->expand_select_row) {
422 GtkTreeSelection *selection;
423 GtkTreeIter iter;
424
425 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (display_page_tree->priv->treeview));
426 if (gtk_tree_model_get_iter (GTK_TREE_MODEL (display_page_tree->priv->page_model), &iter, path)) {
427 rb_debug ("selecting one of the expanded rows");
428 gtk_tree_selection_select_iter (selection, &iter);
429 }
430 }
431 gtk_tree_path_free (path);
432 }
433 }
434
435 rb_list_destroy_free (display_page_tree->priv->expand_rows, (GDestroyNotify) gtk_tree_row_reference_free);
436 display_page_tree->priv->expand_rows = NULL;
437 return FALSE;
438 }
439
440 static void
441 model_row_inserted_cb (GtkTreeModel *model,
442 GtkTreePath *path,
443 GtkTreeIter *iter,
444 RBDisplayPageTree *display_page_tree)
445 {
446 gboolean expand = FALSE;
447 if (gtk_tree_path_get_depth (path) == 2) {
448 GtkTreeIter group_iter;
449 expand = TRUE;
450 if (gtk_tree_model_iter_parent (model, &group_iter, iter)) {
451 gboolean loaded;
452 RBDisplayPage *page;
453 RBDisplayPageGroupCategory category;
454
455 gtk_tree_model_get (model, &group_iter,
456 RB_DISPLAY_PAGE_MODEL_COLUMN_PAGE, &page,
457 -1);
458 g_object_get (page, "loaded", &loaded, "category", &category, NULL);
459 if (category == RB_DISPLAY_PAGE_GROUP_CATEGORY_TRANSIENT || loaded == FALSE) {
460 expand = retrieve_expander_state (display_page_tree, RB_DISPLAY_PAGE_GROUP (page));
461 }
462 g_object_unref (page);
463 }
464 } else if (gtk_tree_path_get_depth (path) == 1) {
465 RBDisplayPage *page;
466
467 gtk_tree_model_get (model, iter,
468 RB_DISPLAY_PAGE_MODEL_COLUMN_PAGE, &page,
469 -1);
470 expand = retrieve_expander_state (display_page_tree, RB_DISPLAY_PAGE_GROUP (page));
471 }
472
473 if (expand) {
474 display_page_tree->priv->expand_rows = g_list_append (display_page_tree->priv->expand_rows,
475 gtk_tree_row_reference_new (model, path));
476 if (display_page_tree->priv->expand_rows_id == 0) {
477 display_page_tree->priv->expand_rows_id = g_idle_add ((GSourceFunc)expand_rows_cb, display_page_tree);
478 }
479 }
480
481 gtk_tree_view_columns_autosize (GTK_TREE_VIEW (display_page_tree->priv->treeview));
482 }
483
484 static gboolean
485 emit_show_popup (GtkTreeView *treeview,
486 RBDisplayPageTree *display_page_tree)
487 {
488 GtkTreeIter iter;
489 RBDisplayPage *page;
490
491 if (!gtk_tree_selection_get_selected (gtk_tree_view_get_selection (treeview),
492 NULL, &iter))
493 return FALSE;
494
495 gtk_tree_model_get (GTK_TREE_MODEL (display_page_tree->priv->page_model),
496 &iter,
497 RB_DISPLAY_PAGE_MODEL_COLUMN_PAGE, &page,
498 -1);
499 if (page == NULL)
500 return FALSE;
501
502 g_return_val_if_fail (RB_IS_DISPLAY_PAGE (page), FALSE);
503
504 rb_display_page_show_popup (page);
505 g_object_unref (page);
506 return TRUE;
507 }
508
509 static gboolean
510 button_press_cb (GtkTreeView *treeview,
511 GdkEventButton *event,
512 RBDisplayPageTree *display_page_tree)
513 {
514 GtkTreeIter iter;
515 GtkTreePath *path;
516 gboolean res;
517
518 if (event->button != 3) {
519 return FALSE;
520 }
521
522 res = gtk_tree_view_get_path_at_pos (treeview,
523 event->x,
524 event->y,
525 &path,
526 NULL,
527 NULL,
528 NULL);
529 if (! res) {
530 /* pointer is over empty space */
531 GtkUIManager *uimanager;
532 g_object_get (display_page_tree->priv->shell, "ui-manager", &uimanager, NULL);
533 rb_gtk_action_popup_menu (uimanager, "/DisplayPageTreePopup");
534 g_object_unref (uimanager);
535 return TRUE;
536 }
537
538 res = gtk_tree_model_get_iter (GTK_TREE_MODEL (display_page_tree->priv->page_model),
539 &iter,
540 path);
541 gtk_tree_path_free (path);
542 if (res) {
543 gtk_tree_selection_select_iter (gtk_tree_view_get_selection (treeview), &iter);
544 }
545
546 return emit_show_popup (treeview, display_page_tree);
547 }
548
549 static gboolean
550 key_release_cb (GtkTreeView *treeview,
551 GdkEventKey *event,
552 RBDisplayPageTree *display_page_tree)
553 {
554 GtkTreeIter iter;
555 RBDisplayPage *page;
556 gboolean res;
557
558 /* F2 = rename playlist */
559 if (event->keyval != GDK_KEY_F2) {
560 return FALSE;
561 }
562
563 if (!gtk_tree_selection_get_selected (display_page_tree->priv->selection, NULL, &iter)) {
564 return FALSE;
565 }
566
567 gtk_tree_model_get (GTK_TREE_MODEL (display_page_tree->priv->page_model),
568 &iter,
569 RB_DISPLAY_PAGE_MODEL_COLUMN_PAGE, &page,
570 -1);
571 if (page == NULL || RB_IS_SOURCE (page) == FALSE) {
572 return FALSE;
573 }
574
575 res = FALSE;
576 if (rb_source_can_rename (RB_SOURCE (page))) {
577 rb_display_page_tree_edit_source_name (display_page_tree, RB_SOURCE (page));
578 res = TRUE;
579 }
580
581 g_object_unref (page);
582 return res;
583 }
584
585 static gboolean
586 popup_menu_cb (GtkTreeView *treeview,
587 RBDisplayPageTree *display_page_tree)
588 {
589 return emit_show_popup (treeview, display_page_tree);
590 }
591
592
593 /**
594 * rb_display_page_tree_edit_source_name:
595 * @display_page_tree: the #RBDisplayPageTree
596 * @source: the #RBSource to edit
597 *
598 * Initiates editing of the name of the specified source. The row for the source
599 * is selected and given input focus, allowing the user to edit the name.
600 * source_name_edited_cb is called when the user finishes editing.
601 */
602 void
603 rb_display_page_tree_edit_source_name (RBDisplayPageTree *display_page_tree,
604 RBSource *source)
605 {
606 GtkTreeIter iter;
607 GtkTreePath *path;
608
609 g_assert (rb_display_page_model_find_page (display_page_tree->priv->page_model,
610 RB_DISPLAY_PAGE (source),
611 &iter));
612 path = gtk_tree_model_get_path (GTK_TREE_MODEL (display_page_tree->priv->page_model),
613 &iter);
614 gtk_tree_view_expand_to_path (GTK_TREE_VIEW (display_page_tree->priv->treeview), path);
615
616 /* Make cell editable just for the moment.
617 We'll turn it off once editing is done. */
618 g_object_set (display_page_tree->priv->title_renderer, "editable", TRUE, NULL);
619
620 gtk_tree_view_set_cursor_on_cell (GTK_TREE_VIEW (display_page_tree->priv->treeview),
621 path, display_page_tree->priv->main_column,
622 display_page_tree->priv->title_renderer,
623 TRUE);
624 gtk_tree_path_free (path);
625 }
626
627 /**
628 * rb_display_page_tree_select:
629 * @display_page_tree: the #RBDisplayPageTree
630 * @page: the #RBDisplayPage to select
631 *
632 * Selects the specified page in the tree. This will result in the 'selected'
633 * signal being emitted.
634 */
635 void
636 rb_display_page_tree_select (RBDisplayPageTree *display_page_tree,
637 RBDisplayPage *page)
638 {
639 GtkTreeIter iter;
640 GtkTreePath *path;
641 GList *l;
642 gboolean defer = FALSE;
643
644 g_assert (rb_display_page_model_find_page (display_page_tree->priv->page_model,
645 page,
646 &iter));
647
648 /* if this is a path we're trying to expand to, wait until we've done that first */
649 path = gtk_tree_model_get_path (GTK_TREE_MODEL (display_page_tree->priv->page_model), &iter);
650 for (l = display_page_tree->priv->expand_rows; l != NULL; l = l->next) {
651 GtkTreePath *expand_path;
652
653 expand_path = gtk_tree_row_reference_get_path (l->data);
654 if (expand_path != NULL) {
655 defer = (gtk_tree_path_compare (expand_path, path) == 0);
656 gtk_tree_path_free (expand_path);
657 }
658
659 if (defer) {
660 display_page_tree->priv->expand_select_row = l->data;
661 break;
662 }
663 }
664
665 if (defer == FALSE) {
666 gtk_tree_selection_select_iter (display_page_tree->priv->selection, &iter);
667 }
668
669 gtk_tree_path_free (path);
670 }
671
672 /**
673 * rb_display_page_tree_toggle_expanded:
674 * @display_page_tree: the #RBDisplayPageTree
675 * @page: the #RBDisplayPage to toggle
676 *
677 * If @page is expanded (children visible), collapses it, otherwise expands it.
678 */
679 void
680 rb_display_page_tree_toggle_expanded (RBDisplayPageTree *display_page_tree,
681 RBDisplayPage *page)
682 {
683 GtkTreeIter iter;
684 GtkTreePath *path;
685
686 g_assert (rb_display_page_model_find_page (display_page_tree->priv->page_model,
687 page,
688 &iter));
689 path = gtk_tree_model_get_path (GTK_TREE_MODEL (display_page_tree->priv->page_model),
690 &iter);
691 if (gtk_tree_view_row_expanded (GTK_TREE_VIEW (display_page_tree->priv->treeview), path)) {
692 rb_debug ("collapsing page %p", page);
693 gtk_tree_view_collapse_row (GTK_TREE_VIEW (display_page_tree->priv->treeview), path);
694 g_object_set (display_page_tree->priv->expander_renderer,
695 "expander-style",
696 GTK_EXPANDER_COLLAPSED,
697 NULL);
698 } else {
699 rb_debug ("expanding page %p", page);
700 gtk_tree_view_expand_row (GTK_TREE_VIEW (display_page_tree->priv->treeview), path, FALSE);
701 g_object_set (display_page_tree->priv->expander_renderer,
702 "expander-style",
703 GTK_EXPANDER_EXPANDED,
704 NULL);
705 }
706
707 gtk_tree_path_free (path);
708 }
709
710 static gboolean
711 selection_check_cb (GtkTreeSelection *selection,
712 GtkTreeModel *model,
713 GtkTreePath *path,
714 gboolean currently_selected,
715 RBDisplayPageTree *display_page_tree)
716 {
717 GtkTreeIter iter;
718 gboolean result = TRUE;
719
720 if (currently_selected) {
721 /* do anything? */
722 } else if (gtk_tree_model_get_iter (model, &iter, path)) {
723 RBDisplayPage *page;
724 gtk_tree_model_get (model, &iter, RB_DISPLAY_PAGE_MODEL_COLUMN_PAGE, &page, -1);
725
726 /* figure out if page can be selected */
727 result = rb_display_page_selectable (page);
728
729 g_object_unref (page);
730 }
731 return result;
732 }
733
734 static void
735 selection_changed_cb (GtkTreeSelection *selection,
736 RBDisplayPageTree *display_page_tree)
737 {
738 GtkTreeIter iter;
739 GtkTreeModel *model;
740 RBDisplayPage *page;
741
742 if (!gtk_tree_selection_get_selected (display_page_tree->priv->selection, &model, &iter))
743 return;
744
745 gtk_tree_model_get (model,
746 &iter,
747 RB_DISPLAY_PAGE_MODEL_COLUMN_PAGE, &page,
748 -1);
749 if (page == NULL)
750 return;
751 g_signal_emit (display_page_tree, signals[SELECTED], 0, page);
752 g_object_unref (page);
753 }
754
755 static void
756 source_name_edited_cb (GtkCellRendererText *renderer,
757 const char *pathstr,
758 const char *text,
759 RBDisplayPageTree *display_page_tree)
760 {
761 GtkTreePath *path;
762 GtkTreeIter iter;
763 RBDisplayPage *page;
764
765 if (text[0] == '\0')
766 return;
767
768 path = gtk_tree_path_new_from_string (pathstr);
769 g_return_if_fail (gtk_tree_model_get_iter (GTK_TREE_MODEL (display_page_tree->priv->page_model), &iter, path));
770 gtk_tree_path_free (path);
771
772 gtk_tree_model_get (GTK_TREE_MODEL (display_page_tree->priv->page_model),
773 &iter,
774 RB_DISPLAY_PAGE_MODEL_COLUMN_PAGE, &page,
775 -1);
776 if (page == NULL || RB_IS_SOURCE (page) == FALSE) {
777 g_object_set (renderer, "editable", FALSE, NULL);
778 return;
779 }
780
781 g_object_set (page, "name", text, NULL);
782 g_object_unref (page);
783 }
784
785 static gboolean
786 display_page_search_equal_func (GtkTreeModel *model,
787 gint column,
788 const char *key,
789 GtkTreeIter *iter,
790 RBDisplayPageTree *display_page_tree)
791 {
792 RBDisplayPage *page;
793 gboolean result = TRUE;
794 char *folded_key;
795 char *name;
796 char *folded_name;
797
798 gtk_tree_model_get (model,
799 iter,
800 RB_DISPLAY_PAGE_MODEL_COLUMN_PAGE, &page,
801 -1);
802 g_object_get (page, "name", &name, NULL);
803
804 folded_key = rb_search_fold (key);
805 folded_name = rb_search_fold (name);
806
807 if (folded_key != NULL && folded_name != NULL) {
808 result = (strncmp (folded_key, folded_name, strlen (folded_key)) != 0);
809 }
810
811 g_free (folded_key);
812 g_free (folded_name);
813 g_free (name);
814 g_object_unref (page);
815 return result;
816 }
817
818 /**
819 * rb_display_page_tree_new:
820 * @shell: the #RBShell instance
821 *
822 * Creates the display page tree widget.
823 *
824 * Return value: the display page tree widget.
825 */
826 RBDisplayPageTree *
827 rb_display_page_tree_new (RBShell *shell)
828 {
829 return RB_DISPLAY_PAGE_TREE (g_object_new (RB_TYPE_DISPLAY_PAGE_TREE,
830 "hadjustment", NULL,
831 "vadjustment", NULL,
832 "hscrollbar_policy", GTK_POLICY_AUTOMATIC,
833 "vscrollbar_policy", GTK_POLICY_AUTOMATIC,
834 "shadow_type", GTK_SHADOW_IN,
835 "shell", shell,
836 NULL));
837 }
838
839 static void
840 impl_set_property (GObject *object,
841 guint prop_id,
842 const GValue *value,
843 GParamSpec *pspec)
844 {
845 RBDisplayPageTree *display_page_tree = RB_DISPLAY_PAGE_TREE (object);
846 switch (prop_id)
847 {
848 case PROP_SHELL:
849 display_page_tree->priv->shell = g_value_get_object (value);
850 break;
851 default:
852 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
853 break;
854 }
855 }
856
857 static void
858 impl_get_property (GObject *object,
859 guint prop_id,
860 GValue *value,
861 GParamSpec *pspec)
862 {
863 RBDisplayPageTree *display_page_tree = RB_DISPLAY_PAGE_TREE (object);
864 switch (prop_id)
865 {
866 case PROP_SHELL:
867 g_value_set_object (value, display_page_tree->priv->shell);
868 break;
869 case PROP_MODEL:
870 g_value_set_object (value, display_page_tree->priv->page_model);
871 break;
872 default:
873 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
874 break;
875 }
876 }
877
878 static void
879 impl_finalize (GObject *object)
880 {
881 RBDisplayPageTree *display_page_tree = RB_DISPLAY_PAGE_TREE (object);
882
883 g_object_unref (display_page_tree->priv->page_model);
884
885 if (display_page_tree->priv->expand_rows_id != 0) {
886 g_source_remove (display_page_tree->priv->expand_rows_id);
887 display_page_tree->priv->expand_rows_id = 0;
888 }
889
890 rb_list_destroy_free (display_page_tree->priv->expand_rows, (GDestroyNotify) gtk_tree_row_reference_free);
891
892 G_OBJECT_CLASS (rb_display_page_tree_parent_class)->finalize (object);
893 }
894
895 static void
896 impl_constructed (GObject *object)
897 {
898 RBDisplayPageTree *display_page_tree;
899
900 RB_CHAIN_GOBJECT_METHOD (rb_display_page_tree_parent_class, constructed, object);
901 display_page_tree = RB_DISPLAY_PAGE_TREE (object);
902
903 gtk_container_add (GTK_CONTAINER (display_page_tree), display_page_tree->priv->treeview);
904
905 display_page_tree->priv->settings = g_settings_new ("org.gnome.rhythmbox.display-page-tree");
906 }
907
908 static void
909 rb_display_page_tree_init (RBDisplayPageTree *display_page_tree)
910 {
911 GtkCellRenderer *renderer;
912
913 display_page_tree->priv =
914 G_TYPE_INSTANCE_GET_PRIVATE (display_page_tree,
915 RB_TYPE_DISPLAY_PAGE_TREE,
916 RBDisplayPageTreePrivate);
917
918 display_page_tree->priv->page_model = rb_display_page_model_new ();
919 g_signal_connect_object (display_page_tree->priv->page_model,
920 "drop-received",
921 G_CALLBACK (drop_received_cb),
922 display_page_tree, 0);
923 g_signal_connect_object (display_page_tree->priv->page_model,
924 "row-inserted",
925 G_CALLBACK (model_row_inserted_cb),
926 display_page_tree, 0);
927
928 display_page_tree->priv->treeview = gtk_tree_view_new_with_model (GTK_TREE_MODEL (display_page_tree->priv->page_model));
929
930 g_object_set (display_page_tree->priv->treeview,
931 "headers-visible", FALSE,
932 "reorderable", TRUE,
933 "enable-search", TRUE,
934 "search-column", RB_DISPLAY_PAGE_MODEL_COLUMN_PAGE,
935 NULL);
936 gtk_tree_view_set_search_equal_func (GTK_TREE_VIEW (display_page_tree->priv->treeview),
937 (GtkTreeViewSearchEqualFunc) display_page_search_equal_func,
938 display_page_tree,
939 NULL);
940
941 rb_display_page_model_set_dnd_targets (display_page_tree->priv->page_model,
942 GTK_TREE_VIEW (display_page_tree->priv->treeview));
943
944 g_signal_connect_object (display_page_tree->priv->treeview,
945 "row_activated",
946 G_CALLBACK (row_activated_cb),
947 display_page_tree, 0);
948 g_signal_connect_object (display_page_tree->priv->treeview,
949 "row-collapsed",
950 G_CALLBACK (row_collapsed_cb),
951 display_page_tree, 0);
952 g_signal_connect_object (display_page_tree->priv->treeview,
953 "row-expanded",
954 G_CALLBACK (row_expanded_cb),
955 display_page_tree, 0);
956 g_signal_connect_object (display_page_tree->priv->treeview,
957 "button_press_event",
958 G_CALLBACK (button_press_cb),
959 display_page_tree, 0);
960 g_signal_connect_object (display_page_tree->priv->treeview,
961 "key_release_event",
962 G_CALLBACK (key_release_cb),
963 display_page_tree, 0);
964
965 g_signal_connect_object (display_page_tree->priv->treeview,
966 "popup_menu",
967 G_CALLBACK (popup_menu_cb),
968 display_page_tree, 0);
969
970 display_page_tree->priv->main_column = gtk_tree_view_column_new ();
971 gtk_tree_view_column_set_clickable (display_page_tree->priv->main_column, FALSE);
972
973 gtk_tree_view_append_column (GTK_TREE_VIEW (display_page_tree->priv->treeview),
974 display_page_tree->priv->main_column);
975
976 /* Set up the indent level1 column */
977 renderer = gtk_cell_renderer_text_new ();
978 gtk_tree_view_column_pack_start (display_page_tree->priv->main_column, renderer, FALSE);
979 gtk_tree_view_column_set_cell_data_func (display_page_tree->priv->main_column,
980 renderer,
981 (GtkTreeCellDataFunc) indent_level1_cell_data_func,
982 display_page_tree,
983 NULL);
984 g_object_set (renderer,
985 "xpad", 0,
986 "visible", FALSE,
987 NULL);
988
989 /* Set up the indent level2 column */
990 renderer = gtk_cell_renderer_text_new ();
991 gtk_tree_view_column_pack_start (display_page_tree->priv->main_column, renderer, FALSE);
992 gtk_tree_view_column_set_cell_data_func (display_page_tree->priv->main_column,
993 renderer,
994 (GtkTreeCellDataFunc) indent_level2_cell_data_func,
995 display_page_tree,
996 NULL);
997 g_object_set (renderer,
998 "xpad", 0,
999 "visible", FALSE,
1000 NULL);
1001
1002 /* Set up the pixbuf column */
1003 renderer = gtk_cell_renderer_pixbuf_new ();
1004 gtk_tree_view_column_pack_start (display_page_tree->priv->main_column, renderer, FALSE);
1005 gtk_tree_view_column_set_cell_data_func (display_page_tree->priv->main_column,
1006 renderer,
1007 (GtkTreeCellDataFunc) pixbuf_cell_data_func,
1008 display_page_tree,
1009 NULL);
1010
1011 g_object_set (renderer,
1012 "xpad", 8,
1013 "ypad", 1,
1014 "visible", FALSE,
1015 NULL);
1016
1017
1018 /* Set up the name column */
1019 renderer = gtk_cell_renderer_text_new ();
1020 g_object_set (renderer, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
1021 gtk_tree_view_column_pack_start (display_page_tree->priv->main_column, renderer, TRUE);
1022 gtk_tree_view_column_set_cell_data_func (display_page_tree->priv->main_column,
1023 renderer,
1024 (GtkTreeCellDataFunc) title_cell_data_func,
1025 display_page_tree,
1026 NULL);
1027 g_signal_connect_object (renderer, "edited", G_CALLBACK (source_name_edited_cb), display_page_tree, 0);
1028
1029 g_object_set (display_page_tree->priv->treeview, "show-expanders", FALSE, NULL);
1030 display_page_tree->priv->title_renderer = renderer;
1031
1032 /* Expander */
1033 renderer = gossip_cell_renderer_expander_new ();
1034 gtk_tree_view_column_pack_end (display_page_tree->priv->main_column, renderer, FALSE);
1035 gtk_tree_view_column_set_cell_data_func (display_page_tree->priv->main_column,
1036 renderer,
1037 (GtkTreeCellDataFunc) expander_cell_data_func,
1038 display_page_tree,
1039 NULL);
1040 display_page_tree->priv->expander_renderer = renderer;
1041
1042 display_page_tree->priv->selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (display_page_tree->priv->treeview));
1043 g_signal_connect_object (display_page_tree->priv->selection,
1044 "changed",
1045 G_CALLBACK (selection_changed_cb),
1046 display_page_tree,
1047 0);
1048 gtk_tree_selection_set_select_function (display_page_tree->priv->selection,
1049 (GtkTreeSelectionFunc) selection_check_cb,
1050 display_page_tree,
1051 NULL);
1052 }
1053
1054 static void
1055 rb_display_page_tree_class_init (RBDisplayPageTreeClass *class)
1056 {
1057 GObjectClass *o_class;
1058
1059 o_class = (GObjectClass *) class;
1060
1061 o_class->constructed = impl_constructed;
1062 o_class->finalize = impl_finalize;
1063 o_class->set_property = impl_set_property;
1064 o_class->get_property = impl_get_property;
1065
1066 /**
1067 * RBDisplayPageTree:shell:
1068 *
1069 * The #RBShell instance
1070 */
1071 g_object_class_install_property (o_class,
1072 PROP_SHELL,
1073 g_param_spec_object ("shell",
1074 "RBShell",
1075 "RBShell object",
1076 RB_TYPE_SHELL,
1077 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
1078
1079 /**
1080 * RBDisplayPageTree:model:
1081 *
1082 * The #GtkTreeModel for the display page tree
1083 */
1084 g_object_class_install_property (o_class,
1085 PROP_MODEL,
1086 g_param_spec_object ("model",
1087 "GtkTreeModel",
1088 "GtkTreeModel object",
1089 GTK_TYPE_TREE_MODEL,
1090 G_PARAM_READABLE));
1091 /**
1092 * RBDisplayPageTree::selected:
1093 * @tree: the #RBDisplayPageTree
1094 * @page: the newly selected #RBDisplayPage
1095 *
1096 * Emitted when a page is selected from the tree
1097 */
1098 signals[SELECTED] =
1099 g_signal_new ("selected",
1100 G_OBJECT_CLASS_TYPE (o_class),
1101 G_SIGNAL_RUN_LAST,
1102 G_STRUCT_OFFSET (RBDisplayPageTreeClass, selected),
1103 NULL, NULL,
1104 g_cclosure_marshal_VOID__OBJECT,
1105 G_TYPE_NONE,
1106 1,
1107 G_TYPE_OBJECT);
1108
1109 /**
1110 * RBDisplayPageTree::drop-received:
1111 * @tree: the #RBDisplayPageTree
1112 * @page: the #RBDisplagePage receiving the drop
1113 * @data: the drop data
1114 *
1115 * Emitted when a drag and drop to the tree completes.
1116 */
1117 signals[DROP_RECEIVED] =
1118 g_signal_new ("drop-received",
1119 G_OBJECT_CLASS_TYPE (o_class),
1120 G_SIGNAL_RUN_LAST,
1121 G_STRUCT_OFFSET (RBDisplayPageTreeClass, drop_received),
1122 NULL, NULL,
1123 rb_marshal_VOID__POINTER_POINTER,
1124 G_TYPE_NONE,
1125 2,
1126 G_TYPE_POINTER, G_TYPE_POINTER);
1127
1128 g_type_class_add_private (class, sizeof (RBDisplayPageTreePrivate));
1129 }