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-cell-renderer-pixbuf.h"
46 #include "gossip-cell-renderer-expander.h"
47 #include "rb-tree-dnd.h"
48 #include "rb-util.h"
49 #include "rb-auto-playlist-source.h"
50 #include "rb-static-playlist-source.h"
51 #include "rb-play-queue-source.h"
52 #include "rb-device-source.h"
53 #include "rb-builder-helpers.h"
54 #include "rb-application.h"
55
56 /**
57 * SECTION:rb-display-page-tree
58 * @short_description: widget displaying the tree of #RBDisplayPage instances
59 *
60 * The display page tree widget is a GtkTreeView backed by a GtkListStore
61 * containing the display page instances (sources and other things). Display
62 * pages include sources, such as the library and playlists, and other things
63 * like the visualization display.
64 *
65 * Display pages are shown in the list with an icon and the name. The current
66 * playing source is displayed in bold.
67 *
68 * Sources are divided into groups - library, stores, playlists, devices,
69 * network shares. Groups are displayed as headers, with expanders for hiding
70 * and showing the sources in the group. Sources themselves may also have
71 * child sources, such as playlists on portable audio players.
72 */
73
74 struct _RBDisplayPageTreePrivate
75 {
76 GtkWidget *scrolled;
77 GtkWidget *treeview;
78 GtkCellRenderer *title_renderer;
79 GtkCellRenderer *expander_renderer;
80
81 GtkWidget *toolbar;
82 GtkWidget *add_menubutton;
83
84 RBDisplayPageModel *page_model;
85 GtkTreeSelection *selection;
86
87 int child_source_count;
88 GtkTreeViewColumn *main_column;
89
90 RBShell *shell;
91
92 GList *expand_rows;
93 GtkTreeRowReference *expand_select_row;
94 guint expand_rows_id;
95
96 GSimpleAction *remove_action;
97 GSimpleAction *eject_action;
98
99 GdkPixbuf *blank_pixbuf;
100 };
101
102
103 enum
104 {
105 PROP_0,
106 PROP_SHELL,
107 PROP_MODEL
108 };
109
110 enum
111 {
112 SELECTED,
113 DROP_RECEIVED,
114 LAST_SIGNAL
115 };
116
117 static guint signals[LAST_SIGNAL] = { 0 };
118
G_DEFINE_TYPE(RBDisplayPageTree,rb_display_page_tree,GTK_TYPE_GRID)119 G_DEFINE_TYPE (RBDisplayPageTree, rb_display_page_tree, GTK_TYPE_GRID)
120
121 static RBDisplayPage *
122 get_selected_page (RBDisplayPageTree *display_page_tree)
123 {
124 GtkTreeIter iter;
125 GtkTreeModel *model;
126 RBDisplayPage *page;
127
128 if (!gtk_tree_selection_get_selected (display_page_tree->priv->selection, &model, &iter))
129 return NULL;
130
131 gtk_tree_model_get (model,
132 &iter,
133 RB_DISPLAY_PAGE_MODEL_COLUMN_PAGE, &page,
134 -1);
135 return page;
136 }
137
138 static void
heading_cell_data_func(GtkTreeViewColumn * tree_column,GtkCellRenderer * cell,GtkTreeModel * model,GtkTreeIter * iter,RBDisplayPageTree * display_page_tree)139 heading_cell_data_func (GtkTreeViewColumn *tree_column,
140 GtkCellRenderer *cell,
141 GtkTreeModel *model,
142 GtkTreeIter *iter,
143 RBDisplayPageTree *display_page_tree)
144 {
145 RBDisplayPage *page;
146
147 gtk_tree_model_get (GTK_TREE_MODEL (display_page_tree->priv->page_model), iter,
148 RB_DISPLAY_PAGE_MODEL_COLUMN_PAGE, &page,
149 -1);
150
151
152 if (RB_IS_DISPLAY_PAGE_GROUP (page)) {
153 char *name;
154 g_object_get (page, "name", &name, NULL);
155 g_object_set (cell,
156 "text", name,
157 "visible", TRUE,
158 NULL);
159 g_free (name);
160 } else {
161 g_object_set (cell,
162 "visible", FALSE,
163 NULL);
164 }
165
166 g_object_unref (page);
167 }
168
169 static void
padding_cell_data_func(GtkTreeViewColumn * tree_column,GtkCellRenderer * cell,GtkTreeModel * model,GtkTreeIter * iter,RBDisplayPageTree * display_page_tree)170 padding_cell_data_func (GtkTreeViewColumn *tree_column,
171 GtkCellRenderer *cell,
172 GtkTreeModel *model,
173 GtkTreeIter *iter,
174 RBDisplayPageTree *display_page_tree)
175 {
176 RBDisplayPage *page;
177
178 gtk_tree_model_get (GTK_TREE_MODEL (display_page_tree->priv->page_model), iter,
179 RB_DISPLAY_PAGE_MODEL_COLUMN_PAGE, &page,
180 -1);
181 if (RB_IS_DISPLAY_PAGE_GROUP (page)) {
182 g_object_set (cell,
183 "visible", FALSE,
184 "xpad", 0,
185 "ypad", 0,
186 NULL);
187 } else {
188 g_object_set (cell,
189 "visible", TRUE,
190 "xpad", 3,
191 "ypad", 3,
192 NULL);
193 }
194
195 g_object_unref (page);
196 }
197
198 static void
padding2_cell_data_func(GtkTreeViewColumn * tree_column,GtkCellRenderer * cell,GtkTreeModel * model,GtkTreeIter * iter,RBDisplayPageTree * display_page_tree)199 padding2_cell_data_func (GtkTreeViewColumn *tree_column,
200 GtkCellRenderer *cell,
201 GtkTreeModel *model,
202 GtkTreeIter *iter,
203 RBDisplayPageTree *display_page_tree)
204 {
205 GtkTreePath *path;
206
207 path = gtk_tree_model_get_path (model, iter);
208 if (gtk_tree_path_get_depth (path) > 2) {
209 g_object_set (cell,
210 "visible", TRUE,
211 "xpad", 3,
212 "ypad", 0,
213 NULL);
214 } else {
215 g_object_set (cell,
216 "visible", FALSE,
217 "xpad", 0,
218 "ypad", 0,
219 NULL);
220 }
221 gtk_tree_path_free (path);
222 }
223
224 static void
pixbuf_cell_data_func(GtkTreeViewColumn * tree_column,GtkCellRenderer * cell,GtkTreeModel * model,GtkTreeIter * iter,RBDisplayPageTree * display_page_tree)225 pixbuf_cell_data_func (GtkTreeViewColumn *tree_column,
226 GtkCellRenderer *cell,
227 GtkTreeModel *model,
228 GtkTreeIter *iter,
229 RBDisplayPageTree *display_page_tree)
230 {
231 RBDisplayPage *page;
232 GtkTreePath *path;
233 GIcon *icon = NULL;
234
235 path = gtk_tree_model_get_path (model, iter);
236 gtk_tree_model_get (model, iter,
237 RB_DISPLAY_PAGE_MODEL_COLUMN_PAGE, &page,
238 -1);
239
240 switch (gtk_tree_path_get_depth (path)) {
241 case 1:
242 g_object_set (cell, "visible", FALSE, NULL);
243 break;
244
245 case 2:
246 case 3:
247 g_object_get (page, "icon", &icon, NULL);
248 if (icon == NULL) {
249 g_object_set (cell, "visible", TRUE, "pixbuf", display_page_tree->priv->blank_pixbuf, NULL);
250 } else {
251 g_object_set (cell, "visible", TRUE, "gicon", icon, NULL);
252 g_object_unref (icon);
253 }
254 break;
255
256 default:
257 g_object_set (cell, "visible", TRUE, "pixbuf", display_page_tree->priv->blank_pixbuf, NULL);
258 break;
259 }
260
261 gtk_tree_path_free (path);
262 g_object_unref (page);
263 }
264
265 static void
title_cell_data_func(GtkTreeViewColumn * column,GtkCellRenderer * renderer,GtkTreeModel * tree_model,GtkTreeIter * iter,RBDisplayPageTree * display_page_tree)266 title_cell_data_func (GtkTreeViewColumn *column,
267 GtkCellRenderer *renderer,
268 GtkTreeModel *tree_model,
269 GtkTreeIter *iter,
270 RBDisplayPageTree *display_page_tree)
271 {
272 RBDisplayPage *page;
273 gboolean playing;
274
275 gtk_tree_model_get (GTK_TREE_MODEL (display_page_tree->priv->page_model), iter,
276 RB_DISPLAY_PAGE_MODEL_COLUMN_PAGE, &page,
277 RB_DISPLAY_PAGE_MODEL_COLUMN_PLAYING, &playing,
278 -1);
279
280 if (RB_IS_DISPLAY_PAGE_GROUP (page)) {
281 g_object_set (renderer, "visible", FALSE, NULL);
282 } else {
283 char *name;
284 g_object_get (page, "name", &name, NULL);
285
286 g_object_set (renderer,
287 "visible", TRUE,
288 "text", name,
289 "weight", playing ? PANGO_WEIGHT_BOLD : PANGO_WEIGHT_NORMAL,
290 NULL);
291 g_free (name);
292 }
293
294 g_object_unref (page);
295 }
296
297 static void
expander_cell_data_func(GtkTreeViewColumn * column,GtkCellRenderer * cell,GtkTreeModel * model,GtkTreeIter * iter,RBDisplayPageTree * display_page_tree)298 expander_cell_data_func (GtkTreeViewColumn *column,
299 GtkCellRenderer *cell,
300 GtkTreeModel *model,
301 GtkTreeIter *iter,
302 RBDisplayPageTree *display_page_tree)
303 {
304 RBDisplayPage *page;
305
306 gtk_tree_model_get (GTK_TREE_MODEL (display_page_tree->priv->page_model), iter,
307 RB_DISPLAY_PAGE_MODEL_COLUMN_PAGE, &page,
308 -1);
309
310 if (RB_IS_DISPLAY_PAGE_GROUP (page) || gtk_tree_model_iter_has_child (model, iter) == FALSE) {
311 g_object_set (cell, "visible", FALSE, NULL);
312 } else if (gtk_tree_model_iter_has_child (model, iter)) {
313 GtkTreePath *path;
314 gboolean row_expanded;
315
316 path = gtk_tree_model_get_path (model, iter);
317 row_expanded = gtk_tree_view_row_expanded (GTK_TREE_VIEW (display_page_tree->priv->treeview),
318 path);
319 gtk_tree_path_free (path);
320
321 g_object_set (cell,
322 "visible", TRUE,
323 "expander-style", row_expanded ? GTK_EXPANDER_EXPANDED : GTK_EXPANDER_COLLAPSED,
324 NULL);
325 }
326
327 g_object_unref (page);
328 }
329
330 static void
row_activated_cb(GtkTreeView * treeview,GtkTreePath * path,GtkTreeViewColumn * column,RBDisplayPageTree * display_page_tree)331 row_activated_cb (GtkTreeView *treeview,
332 GtkTreePath *path,
333 GtkTreeViewColumn *column,
334 RBDisplayPageTree *display_page_tree)
335 {
336 GtkTreeModel *model;
337 GtkTreeIter iter;
338 RBDisplayPage *page;
339
340 model = gtk_tree_view_get_model (treeview);
341
342 g_return_if_fail (gtk_tree_model_get_iter (model, &iter, path));
343
344 gtk_tree_model_get (model, &iter,
345 RB_DISPLAY_PAGE_MODEL_COLUMN_PAGE, &page,
346 -1);
347
348 if (page != NULL) {
349 rb_debug ("page %p activated", page);
350 rb_display_page_activate (page);
351 g_object_unref (page);
352 }
353 }
354
355 static void
drop_received_cb(RBDisplayPageModel * model,RBDisplayPage * page,GtkTreeViewDropPosition pos,GtkSelectionData * data,RBDisplayPageTree * display_page_tree)356 drop_received_cb (RBDisplayPageModel *model,
357 RBDisplayPage *page,
358 GtkTreeViewDropPosition pos,
359 GtkSelectionData *data,
360 RBDisplayPageTree *display_page_tree)
361 {
362 rb_debug ("drop received");
363 g_signal_emit (display_page_tree, signals[DROP_RECEIVED], 0, page, data);
364 }
365
366 static gboolean
expand_rows_cb(RBDisplayPageTree * display_page_tree)367 expand_rows_cb (RBDisplayPageTree *display_page_tree)
368 {
369 GList *l;
370 rb_debug ("expanding %d rows", g_list_length (display_page_tree->priv->expand_rows));
371 display_page_tree->priv->expand_rows_id = 0;
372
373 for (l = display_page_tree->priv->expand_rows; l != NULL; l = l->next) {
374 GtkTreePath *path;
375 path = gtk_tree_row_reference_get_path (l->data);
376 if (path != NULL) {
377 gtk_tree_view_expand_to_path (GTK_TREE_VIEW (display_page_tree->priv->treeview), path);
378 if (l->data == display_page_tree->priv->expand_select_row) {
379 GtkTreeSelection *selection;
380 GtkTreeIter iter;
381
382 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (display_page_tree->priv->treeview));
383 if (gtk_tree_model_get_iter (GTK_TREE_MODEL (display_page_tree->priv->page_model), &iter, path)) {
384 rb_debug ("selecting one of the expanded rows");
385 gtk_tree_selection_select_iter (selection, &iter);
386 }
387 }
388 gtk_tree_path_free (path);
389 }
390 }
391
392 rb_list_destroy_free (display_page_tree->priv->expand_rows, (GDestroyNotify) gtk_tree_row_reference_free);
393 display_page_tree->priv->expand_rows = NULL;
394 return FALSE;
395 }
396
397 static void
model_row_inserted_cb(GtkTreeModel * model,GtkTreePath * path,GtkTreeIter * iter,RBDisplayPageTree * display_page_tree)398 model_row_inserted_cb (GtkTreeModel *model,
399 GtkTreePath *path,
400 GtkTreeIter *iter,
401 RBDisplayPageTree *display_page_tree)
402 {
403 display_page_tree->priv->expand_rows = g_list_append (display_page_tree->priv->expand_rows,
404 gtk_tree_row_reference_new (model, path));
405 if (display_page_tree->priv->expand_rows_id == 0) {
406 display_page_tree->priv->expand_rows_id = g_idle_add ((GSourceFunc)expand_rows_cb, display_page_tree);
407 }
408
409 gtk_tree_view_columns_autosize (GTK_TREE_VIEW (display_page_tree->priv->treeview));
410 }
411
412 static gboolean
key_release_cb(GtkTreeView * treeview,GdkEventKey * event,RBDisplayPageTree * display_page_tree)413 key_release_cb (GtkTreeView *treeview,
414 GdkEventKey *event,
415 RBDisplayPageTree *display_page_tree)
416 {
417 RBDisplayPage *page;
418 gboolean res;
419
420 /* F2 = rename playlist */
421 if (event->keyval != GDK_KEY_F2) {
422 return FALSE;
423 }
424
425 page = get_selected_page (display_page_tree);
426 if (page == NULL) {
427 return FALSE;
428 } else if (RB_IS_SOURCE (page) == FALSE) {
429 g_object_unref (page);
430 return FALSE;
431 }
432
433 res = FALSE;
434 if (rb_source_can_rename (RB_SOURCE (page))) {
435 rb_display_page_tree_edit_source_name (display_page_tree, RB_SOURCE (page));
436 res = TRUE;
437 }
438
439 g_object_unref (page);
440 return res;
441 }
442
443 /**
444 * rb_display_page_tree_edit_source_name:
445 * @display_page_tree: the #RBDisplayPageTree
446 * @source: the #RBSource to edit
447 *
448 * Initiates editing of the name of the specified source. The row for the source
449 * is selected and given input focus, allowing the user to edit the name.
450 * source_name_edited_cb is called when the user finishes editing.
451 */
452 void
rb_display_page_tree_edit_source_name(RBDisplayPageTree * display_page_tree,RBSource * source)453 rb_display_page_tree_edit_source_name (RBDisplayPageTree *display_page_tree,
454 RBSource *source)
455 {
456 GtkTreeIter iter;
457 GtkTreePath *path;
458
459 g_assert (rb_display_page_model_find_page (display_page_tree->priv->page_model,
460 RB_DISPLAY_PAGE (source),
461 &iter));
462 path = gtk_tree_model_get_path (GTK_TREE_MODEL (display_page_tree->priv->page_model),
463 &iter);
464 gtk_tree_view_expand_to_path (GTK_TREE_VIEW (display_page_tree->priv->treeview), path);
465
466 /* Make cell editable just for the moment.
467 We'll turn it off once editing is done. */
468 g_object_set (display_page_tree->priv->title_renderer, "editable", TRUE, NULL);
469
470 gtk_tree_view_set_cursor_on_cell (GTK_TREE_VIEW (display_page_tree->priv->treeview),
471 path, display_page_tree->priv->main_column,
472 display_page_tree->priv->title_renderer,
473 TRUE);
474 gtk_tree_path_free (path);
475 }
476
477 /**
478 * rb_display_page_tree_select:
479 * @display_page_tree: the #RBDisplayPageTree
480 * @page: the #RBDisplayPage to select
481 *
482 * Selects the specified page in the tree. This will result in the 'selected'
483 * signal being emitted.
484 */
485 void
rb_display_page_tree_select(RBDisplayPageTree * display_page_tree,RBDisplayPage * page)486 rb_display_page_tree_select (RBDisplayPageTree *display_page_tree,
487 RBDisplayPage *page)
488 {
489 GtkTreeIter iter;
490 GtkTreePath *path;
491 GList *l;
492 gboolean defer = FALSE;
493
494 g_assert (rb_display_page_model_find_page (display_page_tree->priv->page_model,
495 page,
496 &iter));
497
498 /* if this is a path we're trying to expand to, wait until we've done that first */
499 path = gtk_tree_model_get_path (GTK_TREE_MODEL (display_page_tree->priv->page_model), &iter);
500 for (l = display_page_tree->priv->expand_rows; l != NULL; l = l->next) {
501 GtkTreePath *expand_path;
502
503 expand_path = gtk_tree_row_reference_get_path (l->data);
504 if (expand_path != NULL) {
505 defer = (gtk_tree_path_compare (expand_path, path) == 0);
506 gtk_tree_path_free (expand_path);
507 }
508
509 if (defer) {
510 display_page_tree->priv->expand_select_row = l->data;
511 break;
512 }
513 }
514
515 if (defer == FALSE) {
516 gtk_tree_selection_select_iter (display_page_tree->priv->selection, &iter);
517 }
518
519 gtk_tree_path_free (path);
520 }
521
522 /**
523 * rb_display_page_tree_toggle_expanded:
524 * @display_page_tree: the #RBDisplayPageTree
525 * @page: the #RBDisplayPage to toggle
526 *
527 * If @page is expanded (children visible), collapses it, otherwise expands it.
528 */
529 void
rb_display_page_tree_toggle_expanded(RBDisplayPageTree * display_page_tree,RBDisplayPage * page)530 rb_display_page_tree_toggle_expanded (RBDisplayPageTree *display_page_tree,
531 RBDisplayPage *page)
532 {
533 GtkTreeIter iter;
534 GtkTreePath *path;
535
536 g_assert (rb_display_page_model_find_page (display_page_tree->priv->page_model,
537 page,
538 &iter));
539 path = gtk_tree_model_get_path (GTK_TREE_MODEL (display_page_tree->priv->page_model),
540 &iter);
541 if (gtk_tree_view_row_expanded (GTK_TREE_VIEW (display_page_tree->priv->treeview), path)) {
542 rb_debug ("collapsing page %p", page);
543 gtk_tree_view_collapse_row (GTK_TREE_VIEW (display_page_tree->priv->treeview), path);
544 g_object_set (display_page_tree->priv->expander_renderer,
545 "expander-style",
546 GTK_EXPANDER_COLLAPSED,
547 NULL);
548 } else {
549 rb_debug ("expanding page %p", page);
550 gtk_tree_view_expand_row (GTK_TREE_VIEW (display_page_tree->priv->treeview), path, FALSE);
551 g_object_set (display_page_tree->priv->expander_renderer,
552 "expander-style",
553 GTK_EXPANDER_EXPANDED,
554 NULL);
555 }
556
557 gtk_tree_path_free (path);
558 }
559
560 static gboolean
selection_check_cb(GtkTreeSelection * selection,GtkTreeModel * model,GtkTreePath * path,gboolean currently_selected,RBDisplayPageTree * display_page_tree)561 selection_check_cb (GtkTreeSelection *selection,
562 GtkTreeModel *model,
563 GtkTreePath *path,
564 gboolean currently_selected,
565 RBDisplayPageTree *display_page_tree)
566 {
567 GtkTreeIter iter;
568 gboolean result = TRUE;
569
570 if (currently_selected) {
571 /* do anything? */
572 } else if (gtk_tree_model_get_iter (model, &iter, path)) {
573 RBDisplayPage *page;
574 gtk_tree_model_get (model, &iter, RB_DISPLAY_PAGE_MODEL_COLUMN_PAGE, &page, -1);
575
576 /* figure out if page can be selected */
577 result = rb_display_page_selectable (page);
578
579 g_object_unref (page);
580 }
581 return result;
582 }
583
584 static void
selection_changed_cb(GtkTreeSelection * selection,RBDisplayPageTree * display_page_tree)585 selection_changed_cb (GtkTreeSelection *selection,
586 RBDisplayPageTree *display_page_tree)
587 {
588 RBDisplayPage *page;
589
590 page = get_selected_page (display_page_tree);
591 if (page != NULL) {
592 g_signal_emit (display_page_tree, signals[SELECTED], 0, page);
593
594 if (RB_IS_DEVICE_SOURCE (page) && rb_device_source_can_eject (RB_DEVICE_SOURCE (page))) {
595 g_simple_action_set_enabled (display_page_tree->priv->eject_action, TRUE);
596 } else {
597 g_simple_action_set_enabled (display_page_tree->priv->eject_action, FALSE);
598 }
599
600 g_simple_action_set_enabled (display_page_tree->priv->remove_action, rb_display_page_can_remove (page));
601 g_object_unref (page);
602 } else {
603 g_simple_action_set_enabled (display_page_tree->priv->remove_action, FALSE);
604 g_simple_action_set_enabled (display_page_tree->priv->eject_action, FALSE);
605 }
606 }
607
608 static void
source_name_edited_cb(GtkCellRendererText * renderer,const char * pathstr,const char * text,RBDisplayPageTree * display_page_tree)609 source_name_edited_cb (GtkCellRendererText *renderer,
610 const char *pathstr,
611 const char *text,
612 RBDisplayPageTree *display_page_tree)
613 {
614 GtkTreePath *path;
615 GtkTreeIter iter;
616 RBDisplayPage *page;
617
618 if (text[0] == '\0')
619 return;
620
621 path = gtk_tree_path_new_from_string (pathstr);
622 g_return_if_fail (gtk_tree_model_get_iter (GTK_TREE_MODEL (display_page_tree->priv->page_model), &iter, path));
623 gtk_tree_path_free (path);
624
625 gtk_tree_model_get (GTK_TREE_MODEL (display_page_tree->priv->page_model),
626 &iter,
627 RB_DISPLAY_PAGE_MODEL_COLUMN_PAGE, &page,
628 -1);
629 if (page == NULL || RB_IS_SOURCE (page) == FALSE) {
630 g_object_set (renderer, "editable", FALSE, NULL);
631 return;
632 }
633
634 g_object_set (page, "name", text, NULL);
635 g_object_unref (page);
636 }
637
638 static void
remove_action_cb(GSimpleAction * action,GVariant * parameter,gpointer user_data)639 remove_action_cb (GSimpleAction *action, GVariant *parameter, gpointer user_data)
640 {
641 RBDisplayPage *page = get_selected_page (RB_DISPLAY_PAGE_TREE (user_data));
642 if (page) {
643 rb_display_page_remove (page);
644 g_object_unref (page);
645 }
646 }
647
648 static void
eject_action_cb(GSimpleAction * action,GVariant * parameter,gpointer user_data)649 eject_action_cb (GSimpleAction *action, GVariant *parameter, gpointer user_data)
650 {
651 RBDisplayPage *page = get_selected_page (RB_DISPLAY_PAGE_TREE (user_data));
652 if (page == NULL) {
653 /* nothing */
654 } else if (RB_IS_DEVICE_SOURCE (page) && rb_device_source_can_eject (RB_DEVICE_SOURCE (page))) {
655 rb_device_source_eject (RB_DEVICE_SOURCE (page));
656 g_object_unref (page);
657 } else {
658 rb_debug ("why are we here?");
659 g_object_unref (page);
660 }
661 }
662
663
664 static gboolean
display_page_search_equal_func(GtkTreeModel * model,gint column,const char * key,GtkTreeIter * iter,RBDisplayPageTree * display_page_tree)665 display_page_search_equal_func (GtkTreeModel *model,
666 gint column,
667 const char *key,
668 GtkTreeIter *iter,
669 RBDisplayPageTree *display_page_tree)
670 {
671 RBDisplayPage *page;
672 gboolean result = TRUE;
673 char *folded_key;
674 char *name;
675 char *folded_name;
676
677 gtk_tree_model_get (model,
678 iter,
679 RB_DISPLAY_PAGE_MODEL_COLUMN_PAGE, &page,
680 -1);
681 g_object_get (page, "name", &name, NULL);
682
683 folded_key = rb_search_fold (key);
684 folded_name = rb_search_fold (name);
685
686 if (folded_key != NULL && folded_name != NULL) {
687 result = (strncmp (folded_key, folded_name, strlen (folded_key)) != 0);
688 }
689
690 g_free (folded_key);
691 g_free (folded_name);
692 g_free (name);
693 g_object_unref (page);
694 return result;
695 }
696
697 /**
698 * rb_display_page_tree_new:
699 * @shell: the #RBShell instance
700 *
701 * Creates the display page tree widget.
702 *
703 * Return value: the display page tree widget.
704 */
705 RBDisplayPageTree *
rb_display_page_tree_new(RBShell * shell)706 rb_display_page_tree_new (RBShell *shell)
707 {
708 return RB_DISPLAY_PAGE_TREE (g_object_new (RB_TYPE_DISPLAY_PAGE_TREE,
709 "shell", shell,
710 NULL));
711 }
712
713 static void
impl_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)714 impl_set_property (GObject *object,
715 guint prop_id,
716 const GValue *value,
717 GParamSpec *pspec)
718 {
719 RBDisplayPageTree *display_page_tree = RB_DISPLAY_PAGE_TREE (object);
720 switch (prop_id)
721 {
722 case PROP_SHELL:
723 display_page_tree->priv->shell = g_value_get_object (value);
724 break;
725 default:
726 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
727 break;
728 }
729 }
730
731 static void
impl_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)732 impl_get_property (GObject *object,
733 guint prop_id,
734 GValue *value,
735 GParamSpec *pspec)
736 {
737 RBDisplayPageTree *display_page_tree = RB_DISPLAY_PAGE_TREE (object);
738 switch (prop_id)
739 {
740 case PROP_SHELL:
741 g_value_set_object (value, display_page_tree->priv->shell);
742 break;
743 case PROP_MODEL:
744 g_value_set_object (value, display_page_tree->priv->page_model);
745 break;
746 default:
747 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
748 break;
749 }
750 }
751
752 static void
impl_dispose(GObject * object)753 impl_dispose (GObject *object)
754 {
755 RBDisplayPageTree *display_page_tree = RB_DISPLAY_PAGE_TREE (object);
756
757 g_clear_object (&display_page_tree->priv->blank_pixbuf);
758
759 G_OBJECT_CLASS (rb_display_page_tree_parent_class)->dispose (object);
760 }
761
762 static void
impl_finalize(GObject * object)763 impl_finalize (GObject *object)
764 {
765 RBDisplayPageTree *display_page_tree = RB_DISPLAY_PAGE_TREE (object);
766
767 g_object_unref (display_page_tree->priv->page_model);
768
769 if (display_page_tree->priv->expand_rows_id != 0) {
770 g_source_remove (display_page_tree->priv->expand_rows_id);
771 display_page_tree->priv->expand_rows_id = 0;
772 }
773
774 rb_list_destroy_free (display_page_tree->priv->expand_rows, (GDestroyNotify) gtk_tree_row_reference_free);
775
776 G_OBJECT_CLASS (rb_display_page_tree_parent_class)->finalize (object);
777 }
778
779 static void
impl_constructed(GObject * object)780 impl_constructed (GObject *object)
781 {
782 RBDisplayPageTree *display_page_tree;
783 GtkCellRenderer *renderer;
784 GtkWidget *scrolled;
785 GtkStyleContext *context;
786 GtkWidget *box;
787 GtkToolItem *tool_item;
788 GtkWidget *button;
789 GtkWidget *image;
790 GIcon *icon;
791 GMenuModel *menu;
792 GtkBuilder *builder;
793 GApplication *app;
794 GtkAccelGroup *accel_group;
795 int pixbuf_width, pixbuf_height;
796
797 GActionEntry actions[] = {
798 { "display-page-remove", remove_action_cb },
799 { "display-page-eject", eject_action_cb }
800 };
801
802 RB_CHAIN_GOBJECT_METHOD (rb_display_page_tree_parent_class, constructed, object);
803 display_page_tree = RB_DISPLAY_PAGE_TREE (object);
804
805
806 scrolled = gtk_scrolled_window_new (NULL, NULL);
807 context = gtk_widget_get_style_context (scrolled);
808 gtk_style_context_set_junction_sides (context, GTK_JUNCTION_BOTTOM);
809 g_object_set (scrolled,
810 "hscrollbar_policy", GTK_POLICY_AUTOMATIC,
811 "vscrollbar_policy", GTK_POLICY_AUTOMATIC,
812 "hexpand", TRUE,
813 "vexpand", TRUE,
814 NULL);
815 gtk_grid_attach (GTK_GRID (display_page_tree), scrolled, 0, 0, 1, 1);
816
817 display_page_tree->priv->page_model = rb_display_page_model_new ();
818 g_signal_connect_object (display_page_tree->priv->page_model,
819 "drop-received",
820 G_CALLBACK (drop_received_cb),
821 display_page_tree, 0);
822 g_signal_connect_object (display_page_tree->priv->page_model,
823 "row-inserted",
824 G_CALLBACK (model_row_inserted_cb),
825 display_page_tree, 0);
826
827 display_page_tree->priv->treeview = gtk_tree_view_new_with_model (GTK_TREE_MODEL (display_page_tree->priv->page_model));
828 gtk_style_context_add_class (gtk_widget_get_style_context (display_page_tree->priv->treeview), GTK_STYLE_CLASS_SIDEBAR);
829
830 g_object_set (display_page_tree->priv->treeview,
831 "headers-visible", FALSE,
832 "reorderable", TRUE,
833 "enable-search", TRUE,
834 "search-column", RB_DISPLAY_PAGE_MODEL_COLUMN_PAGE,
835 NULL);
836 gtk_tree_view_set_search_equal_func (GTK_TREE_VIEW (display_page_tree->priv->treeview),
837 (GtkTreeViewSearchEqualFunc) display_page_search_equal_func,
838 display_page_tree,
839 NULL);
840
841 rb_display_page_model_set_dnd_targets (display_page_tree->priv->page_model,
842 GTK_TREE_VIEW (display_page_tree->priv->treeview));
843
844 g_signal_connect_object (display_page_tree->priv->treeview,
845 "row_activated",
846 G_CALLBACK (row_activated_cb),
847 display_page_tree, 0);
848 g_signal_connect_object (display_page_tree->priv->treeview,
849 "key_release_event",
850 G_CALLBACK (key_release_cb),
851 display_page_tree, 0);
852
853 display_page_tree->priv->main_column = gtk_tree_view_column_new ();
854 gtk_tree_view_column_set_clickable (display_page_tree->priv->main_column, FALSE);
855
856 gtk_tree_view_append_column (GTK_TREE_VIEW (display_page_tree->priv->treeview),
857 display_page_tree->priv->main_column);
858
859 gtk_icon_size_lookup (RB_DISPLAY_PAGE_ICON_SIZE, &pixbuf_width, &pixbuf_height);
860 display_page_tree->priv->blank_pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, TRUE, 8, pixbuf_width, pixbuf_height);
861 gdk_pixbuf_fill (display_page_tree->priv->blank_pixbuf, 0);
862
863 /* initial padding */
864 renderer = gtk_cell_renderer_text_new ();
865 gtk_tree_view_column_pack_start (display_page_tree->priv->main_column, renderer, FALSE);
866 g_object_set (renderer, "xpad", 3, NULL);
867
868 /* headings */
869 renderer = gtk_cell_renderer_text_new ();
870 gtk_tree_view_column_pack_start (display_page_tree->priv->main_column, renderer, FALSE);
871 gtk_tree_view_column_set_cell_data_func (display_page_tree->priv->main_column,
872 renderer,
873 (GtkTreeCellDataFunc) heading_cell_data_func,
874 display_page_tree,
875 NULL);
876 g_object_set (renderer,
877 "weight", PANGO_WEIGHT_BOLD,
878 "weight-set", TRUE,
879 "ypad", 6,
880 "xpad", 0,
881 NULL);
882
883 /* icon padding */
884 renderer = gtk_cell_renderer_text_new ();
885 gtk_tree_view_column_pack_start (display_page_tree->priv->main_column, renderer, FALSE);
886 gtk_tree_view_column_set_cell_data_func (display_page_tree->priv->main_column,
887 renderer,
888 (GtkTreeCellDataFunc) padding_cell_data_func,
889 display_page_tree,
890 NULL);
891
892 /* padding for second level */
893 renderer = gtk_cell_renderer_text_new ();
894 gtk_tree_view_column_pack_start (display_page_tree->priv->main_column, renderer, FALSE);
895 gtk_tree_view_column_set_cell_data_func (display_page_tree->priv->main_column,
896 renderer,
897 (GtkTreeCellDataFunc) padding2_cell_data_func,
898 display_page_tree,
899 NULL);
900
901 /* Set up the pixbuf column */
902 renderer = gtk_cell_renderer_pixbuf_new ();
903 gtk_tree_view_column_pack_start (display_page_tree->priv->main_column, renderer, FALSE);
904 gtk_tree_view_column_set_cell_data_func (display_page_tree->priv->main_column,
905 renderer,
906 (GtkTreeCellDataFunc) pixbuf_cell_data_func,
907 display_page_tree,
908 NULL);
909 if (gtk_check_version (3, 16, 0) != NULL) {
910 g_object_set (renderer, "follow-state", TRUE, NULL);
911 }
912
913 /* Set up the name column */
914 renderer = gtk_cell_renderer_text_new ();
915 g_object_set (renderer, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
916 gtk_tree_view_column_pack_start (display_page_tree->priv->main_column, renderer, TRUE);
917 gtk_tree_view_column_set_cell_data_func (display_page_tree->priv->main_column,
918 renderer,
919 (GtkTreeCellDataFunc) title_cell_data_func,
920 display_page_tree,
921 NULL);
922 g_signal_connect_object (renderer, "edited", G_CALLBACK (source_name_edited_cb), display_page_tree, 0);
923
924 g_object_set (display_page_tree->priv->treeview, "show-expanders", FALSE, NULL);
925 display_page_tree->priv->title_renderer = renderer;
926
927 /* Expander */
928 renderer = gossip_cell_renderer_expander_new ();
929 gtk_tree_view_column_pack_end (display_page_tree->priv->main_column, renderer, FALSE);
930 gtk_tree_view_column_set_cell_data_func (display_page_tree->priv->main_column,
931 renderer,
932 (GtkTreeCellDataFunc) expander_cell_data_func,
933 display_page_tree,
934 NULL);
935 display_page_tree->priv->expander_renderer = renderer;
936
937 /* toolbar actions */
938 app = g_application_get_default ();
939 g_action_map_add_action_entries (G_ACTION_MAP (app), actions, G_N_ELEMENTS (actions), display_page_tree);
940
941 /* disable the remove and eject actions initially */
942 display_page_tree->priv->remove_action = G_SIMPLE_ACTION (g_action_map_lookup_action (G_ACTION_MAP (app), "display-page-remove"));
943 display_page_tree->priv->eject_action = G_SIMPLE_ACTION (g_action_map_lookup_action (G_ACTION_MAP (app), "display-page-eject"));
944 g_simple_action_set_enabled (display_page_tree->priv->remove_action, FALSE);
945 g_simple_action_set_enabled (display_page_tree->priv->eject_action, FALSE);
946
947 /* toolbar */
948 display_page_tree->priv->toolbar = gtk_toolbar_new ();
949 gtk_toolbar_set_style (GTK_TOOLBAR (display_page_tree->priv->toolbar), GTK_TOOLBAR_ICONS);
950 gtk_toolbar_set_icon_size (GTK_TOOLBAR (display_page_tree->priv->toolbar), GTK_ICON_SIZE_MENU);
951
952 context = gtk_widget_get_style_context (display_page_tree->priv->toolbar);
953 gtk_style_context_set_junction_sides (context, GTK_JUNCTION_TOP);
954 gtk_style_context_add_class (context, GTK_STYLE_CLASS_INLINE_TOOLBAR);
955 gtk_style_context_add_class (context, "sidebar-toolbar");
956
957 gtk_grid_attach (GTK_GRID (display_page_tree), display_page_tree->priv->toolbar, 0, 1, 1, 1);
958
959 tool_item = gtk_tool_item_new ();
960 box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
961 gtk_container_add (GTK_CONTAINER (tool_item), box);
962 gtk_toolbar_insert (GTK_TOOLBAR (display_page_tree->priv->toolbar), tool_item, -1);
963
964 display_page_tree->priv->add_menubutton = gtk_menu_button_new ();
965 icon = g_themed_icon_new_with_default_fallbacks ("list-add-symbolic");
966 image = gtk_image_new_from_gicon (icon, GTK_ICON_SIZE_MENU);
967 gtk_button_set_image (GTK_BUTTON (display_page_tree->priv->add_menubutton), image);
968 gtk_box_pack_start (GTK_BOX (box), display_page_tree->priv->add_menubutton, FALSE, FALSE, 0);
969 g_object_unref (icon);
970
971 g_object_get (display_page_tree->priv->shell, "accel-group", &accel_group, NULL);
972 gtk_widget_add_accelerator (display_page_tree->priv->add_menubutton,
973 "activate",
974 accel_group,
975 GDK_KEY_A,
976 GDK_MOD1_MASK,
977 GTK_ACCEL_VISIBLE);
978 g_object_unref (accel_group);
979
980 builder = rb_builder_load ("display-page-add-menu.ui", NULL);
981 menu = G_MENU_MODEL (gtk_builder_get_object (builder, "display-page-add-menu"));
982 rb_application_link_shared_menus (RB_APPLICATION (g_application_get_default ()), G_MENU (menu));
983 gtk_menu_button_set_menu_model (GTK_MENU_BUTTON (display_page_tree->priv->add_menubutton), menu);
984 g_object_unref (builder);
985
986 button = gtk_button_new ();
987 icon = g_themed_icon_new_with_default_fallbacks ("list-remove-symbolic");
988 image = gtk_image_new_from_gicon (icon, GTK_ICON_SIZE_MENU);
989 gtk_button_set_image (GTK_BUTTON (button), image);
990 gtk_box_pack_start (GTK_BOX (box), button, FALSE, FALSE, 0);
991 g_object_unref (icon);
992
993 gtk_actionable_set_action_name (GTK_ACTIONABLE (button), "app.display-page-remove");
994
995 /* maybe this should be a column in the tree instead.. */
996 button = gtk_button_new ();
997 icon = g_themed_icon_new_with_default_fallbacks ("media-eject-symbolic");
998 image = gtk_image_new_from_gicon (icon, GTK_ICON_SIZE_MENU);
999 gtk_button_set_image (GTK_BUTTON (button), image);
1000 gtk_box_pack_start (GTK_BOX (box), button, FALSE, FALSE, 0);
1001 g_object_unref (icon);
1002
1003 gtk_actionable_set_action_name (GTK_ACTIONABLE (button), "app.display-page-eject");
1004
1005 display_page_tree->priv->selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (display_page_tree->priv->treeview));
1006 g_signal_connect_object (display_page_tree->priv->selection,
1007 "changed",
1008 G_CALLBACK (selection_changed_cb),
1009 display_page_tree,
1010 0);
1011 gtk_tree_selection_set_select_function (display_page_tree->priv->selection,
1012 (GtkTreeSelectionFunc) selection_check_cb,
1013 display_page_tree,
1014 NULL);
1015
1016 gtk_container_add (GTK_CONTAINER (scrolled), display_page_tree->priv->treeview);
1017 }
1018
1019 static void
rb_display_page_tree_init(RBDisplayPageTree * display_page_tree)1020 rb_display_page_tree_init (RBDisplayPageTree *display_page_tree)
1021 {
1022 display_page_tree->priv =
1023 G_TYPE_INSTANCE_GET_PRIVATE (display_page_tree,
1024 RB_TYPE_DISPLAY_PAGE_TREE,
1025 RBDisplayPageTreePrivate);
1026 }
1027
1028 static void
rb_display_page_tree_class_init(RBDisplayPageTreeClass * class)1029 rb_display_page_tree_class_init (RBDisplayPageTreeClass *class)
1030 {
1031 GObjectClass *o_class;
1032
1033 o_class = (GObjectClass *) class;
1034
1035 o_class->constructed = impl_constructed;
1036 o_class->dispose = impl_dispose;
1037 o_class->finalize = impl_finalize;
1038 o_class->set_property = impl_set_property;
1039 o_class->get_property = impl_get_property;
1040
1041 /**
1042 * RBDisplayPageTree:shell:
1043 *
1044 * The #RBShell instance
1045 */
1046 g_object_class_install_property (o_class,
1047 PROP_SHELL,
1048 g_param_spec_object ("shell",
1049 "RBShell",
1050 "RBShell object",
1051 RB_TYPE_SHELL,
1052 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
1053
1054 /**
1055 * RBDisplayPageTree:model:
1056 *
1057 * The #GtkTreeModel for the display page tree
1058 */
1059 g_object_class_install_property (o_class,
1060 PROP_MODEL,
1061 g_param_spec_object ("model",
1062 "GtkTreeModel",
1063 "GtkTreeModel object",
1064 GTK_TYPE_TREE_MODEL,
1065 G_PARAM_READABLE));
1066 /**
1067 * RBDisplayPageTree::selected:
1068 * @tree: the #RBDisplayPageTree
1069 * @page: the newly selected #RBDisplayPage
1070 *
1071 * Emitted when a page is selected from the tree
1072 */
1073 signals[SELECTED] =
1074 g_signal_new ("selected",
1075 G_OBJECT_CLASS_TYPE (o_class),
1076 G_SIGNAL_RUN_LAST,
1077 G_STRUCT_OFFSET (RBDisplayPageTreeClass, selected),
1078 NULL, NULL,
1079 NULL,
1080 G_TYPE_NONE,
1081 1,
1082 G_TYPE_OBJECT);
1083
1084 /**
1085 * RBDisplayPageTree::drop-received:
1086 * @tree: the #RBDisplayPageTree
1087 * @page: the #RBDisplagePage receiving the drop
1088 * @data: the drop data
1089 *
1090 * Emitted when a drag and drop to the tree completes.
1091 */
1092 signals[DROP_RECEIVED] =
1093 g_signal_new ("drop-received",
1094 G_OBJECT_CLASS_TYPE (o_class),
1095 G_SIGNAL_RUN_LAST,
1096 G_STRUCT_OFFSET (RBDisplayPageTreeClass, drop_received),
1097 NULL, NULL,
1098 NULL,
1099 G_TYPE_NONE,
1100 2,
1101 G_TYPE_POINTER, G_TYPE_POINTER);
1102
1103 g_type_class_add_private (class, sizeof (RBDisplayPageTreePrivate));
1104 }
1105