1 /*
2 * xed-file-browser-view.c - Xed plugin providing easy file access
3 * from the sidepanel
4 *
5 * Copyright (C) 2006 - Jesse van den Kieboom <jesse@icecrew.nl>
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2, or (at your option)
10 * any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
20 */
21
22 #include <string.h>
23 #include <glib-object.h>
24 #include <gio/gio.h>
25 #include <gtk/gtk.h>
26 #include <gdk/gdkkeysyms.h>
27
28 #include "xed-file-browser-store.h"
29 #include "xed-file-bookmarks-store.h"
30 #include "xed-file-browser-view.h"
31 #include "xed-file-browser-marshal.h"
32 #include "xed-file-browser-enum-types.h"
33
34 struct _XedFileBrowserViewPrivate
35 {
36 GtkTreeViewColumn *column;
37 GtkCellRenderer *pixbuf_renderer;
38 GtkCellRenderer *text_renderer;
39
40 GtkTreeModel *model;
41 GtkTreeRowReference *editable;
42
43 GtkTreePath *double_click_path[2]; /* Both clicks in a double click need to be on the same row */
44 GtkTreePath *hover_path;
45 GdkCursor *hand_cursor;
46 gboolean ignore_release;
47 gboolean selected_on_button_down;
48 gint drag_button;
49 gboolean drag_started;
50
51 gboolean restore_expand_state;
52 gboolean is_refresh;
53 GHashTable * expand_state;
54 };
55
56 /* Properties */
57 enum
58 {
59 PROP_0,
60
61 PROP_RESTORE_EXPAND_STATE
62 };
63
64 /* Signals */
65 enum
66 {
67 ERROR,
68 FILE_ACTIVATED,
69 DIRECTORY_ACTIVATED,
70 BOOKMARK_ACTIVATED,
71 NUM_SIGNALS
72 };
73
74 static guint signals[NUM_SIGNALS] = { 0 };
75
76 static const GtkTargetEntry drag_source_targets[] = {
77 { "text/uri-list", 0, 0 }
78 };
79
80 G_DEFINE_DYNAMIC_TYPE_EXTENDED (XedFileBrowserView,
81 xed_file_browser_view,
82 GTK_TYPE_TREE_VIEW,
83 0,
84 G_ADD_PRIVATE_DYNAMIC (XedFileBrowserView))
85
86 static void on_cell_edited (GtkCellRendererText *cell,
87 gchar *path,
88 gchar *new_text,
89 XedFileBrowserView *tree_view);
90
91 static void on_begin_refresh (XedFileBrowserStore *model,
92 XedFileBrowserView *view);
93 static void on_end_refresh (XedFileBrowserStore *model,
94 XedFileBrowserView *view);
95
96 static void on_unload (XedFileBrowserStore *model,
97 GFile *location,
98 XedFileBrowserView *view);
99
100 static void on_row_inserted (XedFileBrowserStore *model,
101 GtkTreePath *path,
102 GtkTreeIter *iter,
103 XedFileBrowserView *view);
104
105 static void
xed_file_browser_view_finalize(GObject * object)106 xed_file_browser_view_finalize (GObject *object)
107 {
108 XedFileBrowserView *obj = XED_FILE_BROWSER_VIEW(object);
109
110 if (obj->priv->hand_cursor)
111 {
112 g_object_unref (obj->priv->hand_cursor);
113 }
114
115 if (obj->priv->hover_path)
116 {
117 gtk_tree_path_free (obj->priv->hover_path);
118 }
119
120 if (obj->priv->expand_state)
121 {
122 g_hash_table_destroy (obj->priv->expand_state);
123 obj->priv->expand_state = NULL;
124 }
125
126 G_OBJECT_CLASS (xed_file_browser_view_parent_class)->finalize (object);
127 }
128
129 static void
add_expand_state(XedFileBrowserView * view,GFile * location)130 add_expand_state (XedFileBrowserView *view,
131 GFile *location)
132 {
133 if (!location)
134 {
135 return;
136 }
137
138 if (view->priv->expand_state)
139 {
140 g_hash_table_insert (view->priv->expand_state, location, g_object_ref (location));
141 }
142 }
143
144 static void
remove_expand_state(XedFileBrowserView * view,GFile * location)145 remove_expand_state (XedFileBrowserView *view,
146 GFile *location)
147 {
148 if (!location)
149 {
150 return;
151 }
152
153 if (view->priv->expand_state)
154 {
155 g_hash_table_remove (view->priv->expand_state, location);
156 }
157 }
158
159 static void
row_expanded(GtkTreeView * tree_view,GtkTreeIter * iter,GtkTreePath * path)160 row_expanded (GtkTreeView *tree_view,
161 GtkTreeIter *iter,
162 GtkTreePath *path)
163 {
164 XedFileBrowserView *view = XED_FILE_BROWSER_VIEW (tree_view);
165
166 if (GTK_TREE_VIEW_CLASS (xed_file_browser_view_parent_class)->row_expanded)
167 {
168 GTK_TREE_VIEW_CLASS (xed_file_browser_view_parent_class)->row_expanded (tree_view, iter, path);
169 }
170
171 if (!XED_IS_FILE_BROWSER_STORE (view->priv->model))
172 {
173 return;
174 }
175
176 if (view->priv->restore_expand_state)
177 {
178 GFile *location;
179
180 gtk_tree_model_get (view->priv->model, iter, XED_FILE_BROWSER_STORE_COLUMN_LOCATION, &location, -1);
181
182 add_expand_state (view, location);
183
184 if (location)
185 {
186 g_object_unref (location);
187 }
188 }
189
190 _xed_file_browser_store_iter_expanded (XED_FILE_BROWSER_STORE (view->priv->model), iter);
191 }
192
193 static void
row_collapsed(GtkTreeView * tree_view,GtkTreeIter * iter,GtkTreePath * path)194 row_collapsed (GtkTreeView *tree_view,
195 GtkTreeIter *iter,
196 GtkTreePath *path)
197 {
198 XedFileBrowserView *view = XED_FILE_BROWSER_VIEW (tree_view);
199
200 if (GTK_TREE_VIEW_CLASS (xed_file_browser_view_parent_class)->row_collapsed)
201 {
202 GTK_TREE_VIEW_CLASS (xed_file_browser_view_parent_class)->row_collapsed (tree_view, iter, path);
203 }
204
205 if (!XED_IS_FILE_BROWSER_STORE (view->priv->model))
206 {
207 return;
208 }
209
210 if (view->priv->restore_expand_state)
211 {
212 GFile *location;
213
214 gtk_tree_model_get (view->priv->model, iter, XED_FILE_BROWSER_STORE_COLUMN_LOCATION, &location, -1);
215
216 remove_expand_state (view, location);
217
218 if (location)
219 {
220 g_object_unref (location);
221 }
222 }
223
224 _xed_file_browser_store_iter_collapsed (XED_FILE_BROWSER_STORE (view->priv->model), iter);
225 }
226
227 static void
directory_activated(XedFileBrowserView * view,GtkTreeIter * iter)228 directory_activated (XedFileBrowserView *view,
229 GtkTreeIter *iter)
230 {
231 xed_file_browser_store_set_virtual_root (XED_FILE_BROWSER_STORE (view->priv->model), iter);
232 }
233
234 static void
activate_selected_files(XedFileBrowserView * view)235 activate_selected_files (XedFileBrowserView *view)
236 {
237 GtkTreeView *tree_view = GTK_TREE_VIEW (view);
238 GtkTreeSelection *selection = gtk_tree_view_get_selection (tree_view);
239 GList *rows, *row;
240 GtkTreePath *directory = NULL;
241 GtkTreePath *path;
242 GtkTreeIter iter;
243 XedFileBrowserStoreFlag flags;
244
245 rows = gtk_tree_selection_get_selected_rows (selection, &view->priv->model);
246
247 for (row = rows; row; row = row->next)
248 {
249 path = (GtkTreePath *)(row->data);
250
251 /* Get iter from path */
252 if (!gtk_tree_model_get_iter (view->priv->model, &iter, path))
253 {
254 continue;
255 }
256
257 gtk_tree_model_get (view->priv->model, &iter, XED_FILE_BROWSER_STORE_COLUMN_FLAGS, &flags, -1);
258
259 if (FILE_IS_DIR (flags))
260 {
261 if (directory == NULL)
262 {
263 directory = path;
264 }
265
266 }
267 else if (!FILE_IS_DUMMY (flags))
268 {
269 g_signal_emit (view, signals[FILE_ACTIVATED], 0, &iter);
270 }
271 }
272
273 if (directory != NULL)
274 {
275 if (gtk_tree_model_get_iter (view->priv->model, &iter, directory))
276 {
277 g_signal_emit (view, signals[DIRECTORY_ACTIVATED], 0, &iter);
278 }
279 }
280
281 g_list_foreach (rows, (GFunc)gtk_tree_path_free, NULL);
282 g_list_free (rows);
283 }
284
285 static void
activate_selected_bookmark(XedFileBrowserView * view)286 activate_selected_bookmark (XedFileBrowserView *view)
287 {
288 GtkTreeView *tree_view = GTK_TREE_VIEW (view);
289 GtkTreeSelection *selection = gtk_tree_view_get_selection (tree_view);
290 GtkTreeIter iter;
291
292 if (gtk_tree_selection_get_selected (selection, &view->priv->model, &iter))
293 {
294 g_signal_emit (view, signals[BOOKMARK_ACTIVATED], 0, &iter);
295 }
296 }
297
298 static void
activate_selected_items(XedFileBrowserView * view)299 activate_selected_items (XedFileBrowserView *view)
300 {
301 if (XED_IS_FILE_BROWSER_STORE (view->priv->model))
302 {
303 activate_selected_files (view);
304 }
305 else if (XED_IS_FILE_BOOKMARKS_STORE (view->priv->model))
306 {
307 activate_selected_bookmark (view);
308 }
309 }
310
311 static void
row_activated(GtkTreeView * tree_view,GtkTreePath * path,GtkTreeViewColumn * column)312 row_activated (GtkTreeView *tree_view,
313 GtkTreePath *path,
314 GtkTreeViewColumn *column)
315 {
316 GtkTreeSelection *selection = gtk_tree_view_get_selection (tree_view);
317
318 /* Make sure the activated row is the only one selected */
319 gtk_tree_selection_unselect_all (selection);
320 gtk_tree_selection_select_path (selection, path);
321
322 activate_selected_items (XED_FILE_BROWSER_VIEW (tree_view));
323 }
324
325 static void
toggle_hidden_filter(XedFileBrowserView * view)326 toggle_hidden_filter (XedFileBrowserView *view)
327 {
328 XedFileBrowserStoreFilterMode mode;
329
330 if (XED_IS_FILE_BROWSER_STORE (view->priv->model))
331 {
332 mode = xed_file_browser_store_get_filter_mode (XED_FILE_BROWSER_STORE (view->priv->model));
333 mode ^= XED_FILE_BROWSER_STORE_FILTER_MODE_HIDE_HIDDEN;
334 xed_file_browser_store_set_filter_mode (XED_FILE_BROWSER_STORE (view->priv->model), mode);
335 }
336 }
337
338 static gboolean
button_event_modifies_selection(GdkEventButton * event)339 button_event_modifies_selection (GdkEventButton *event)
340 {
341 return (event->state & (GDK_CONTROL_MASK | GDK_SHIFT_MASK)) != 0;
342 }
343
344 static void
drag_begin(GtkWidget * widget,GdkDragContext * context)345 drag_begin (GtkWidget *widget,
346 GdkDragContext *context)
347 {
348 XedFileBrowserView *view = XED_FILE_BROWSER_VIEW (widget);
349
350 view->priv->drag_button = 0;
351 view->priv->drag_started = TRUE;
352
353 /* Chain up */
354 GTK_WIDGET_CLASS (xed_file_browser_view_parent_class)->drag_begin (widget, context);
355 }
356
357 static void
did_not_drag(XedFileBrowserView * view,GdkEventButton * event)358 did_not_drag (XedFileBrowserView *view,
359 GdkEventButton *event)
360 {
361 GtkTreeView *tree_view;
362 GtkTreeSelection *selection;
363 GtkTreePath *path;
364
365 tree_view = GTK_TREE_VIEW (view);
366 selection = gtk_tree_view_get_selection (tree_view);
367
368 if (gtk_tree_view_get_path_at_pos (tree_view, event->x, event->y, &path, NULL, NULL, NULL))
369 {
370 if ((event->button == 1 || event->button == 2)
371 && ((event->state & GDK_CONTROL_MASK) != 0 ||
372 (event->state & GDK_SHIFT_MASK) == 0)
373 && view->priv->selected_on_button_down)
374 {
375 if (!button_event_modifies_selection (event))
376 {
377 gtk_tree_selection_unselect_all (selection);
378 gtk_tree_selection_select_path (selection, path);
379 }
380 else
381 {
382 gtk_tree_selection_unselect_path (selection, path);
383 }
384 }
385
386 gtk_tree_path_free (path);
387 }
388 }
389
390 static gboolean
button_release_event(GtkWidget * widget,GdkEventButton * event)391 button_release_event (GtkWidget *widget,
392 GdkEventButton *event)
393 {
394 XedFileBrowserView *view = XED_FILE_BROWSER_VIEW (widget);
395
396 if (event->button == view->priv->drag_button)
397 {
398 view->priv->drag_button = 0;
399
400 if (!view->priv->drag_started && !view->priv->ignore_release)
401 {
402 did_not_drag (view, event);
403 }
404 }
405
406 /* Chain up */
407 return GTK_WIDGET_CLASS (xed_file_browser_view_parent_class)->button_release_event (widget, event);
408 }
409
410 static gboolean
button_press_event(GtkWidget * widget,GdkEventButton * event)411 button_press_event (GtkWidget *widget,
412 GdkEventButton *event)
413 {
414 int double_click_time;
415 static int click_count = 0;
416 static guint32 last_click_time = 0;
417 XedFileBrowserView *view;
418 GtkTreeView *tree_view;
419 GtkTreeSelection *selection;
420 GtkTreePath *path;
421 int expander_size;
422 int horizontal_separator;
423 gboolean on_expander;
424 gboolean call_parent;
425 gboolean selected;
426 GtkWidgetClass *widget_parent = GTK_WIDGET_CLASS(xed_file_browser_view_parent_class);
427
428 tree_view = GTK_TREE_VIEW (widget);
429 view = XED_FILE_BROWSER_VIEW (widget);
430 selection = gtk_tree_view_get_selection (tree_view);
431
432 /* Get double click time */
433 g_object_get (G_OBJECT (gtk_widget_get_settings (widget)),
434 "gtk-double-click-time", &double_click_time,
435 NULL);
436
437 /* Determine click count */
438 if (event->time - last_click_time < double_click_time)
439 {
440 click_count++;
441 }
442 else
443 {
444 click_count = 0;
445 }
446
447 last_click_time = event->time;
448
449 view->priv->ignore_release = FALSE;
450 call_parent = TRUE;
451
452 if (gtk_tree_view_get_path_at_pos (tree_view, event->x, event->y, &path, NULL, NULL, NULL))
453 {
454 /* Keep track of path of last click so double clicks only happen
455 * on the same item */
456 if ((event->button == 1 || event->button == 2) && event->type == GDK_BUTTON_PRESS)
457 {
458 if (view->priv->double_click_path[1])
459 {
460 gtk_tree_path_free (view->priv->double_click_path[1]);
461 }
462
463 view->priv->double_click_path[1] = view->priv->double_click_path[0];
464 view->priv->double_click_path[0] = gtk_tree_path_copy (path);
465 }
466
467 if (event->type == GDK_2BUTTON_PRESS)
468 {
469 /* Chain up, must be before activating the selected
470 items because this will cause the view to grab focus */
471 widget_parent->button_press_event (widget, event);
472
473 if (view->priv->double_click_path[1] &&
474 gtk_tree_path_compare (view->priv->double_click_path[0], view->priv->double_click_path[1]) == 0)
475 {
476 activate_selected_items (view);
477 }
478 }
479 else
480 {
481 /* We're going to filter out some situations where
482 * we can't let the default code run because all
483 * but one row would be would be deselected. We don't
484 * want that; we want the right click menu or single
485 * click to apply to everything that's currently selected. */
486 selected = gtk_tree_selection_path_is_selected (selection, path);
487
488 if (event->button == 3 && selected)
489 {
490 call_parent = FALSE;
491 }
492
493 if ((event->button == 1 || event->button == 2) &&
494 ((event->state & GDK_CONTROL_MASK) != 0 ||
495 (event->state & GDK_SHIFT_MASK) == 0))
496 {
497 gtk_widget_style_get (widget,
498 "expander-size", &expander_size,
499 "horizontal-separator", &horizontal_separator,
500 NULL);
501 on_expander = (event->x <= horizontal_separator / 2 + gtk_tree_path_get_depth (path) * expander_size);
502
503 view->priv->selected_on_button_down = selected;
504
505 if (selected)
506 {
507 call_parent = on_expander || gtk_tree_selection_count_selected_rows (selection) == 1;
508 view->priv->ignore_release = call_parent;
509 }
510 else if ((event->state & GDK_CONTROL_MASK) != 0)
511 {
512 call_parent = FALSE;
513 gtk_tree_selection_select_path (selection, path);
514 }
515 else
516 {
517 view->priv->ignore_release = on_expander;
518 }
519 }
520
521 if (call_parent)
522 {
523 /* Chain up */
524 widget_parent->button_press_event (widget, event);
525 }
526 else if (selected)
527 {
528 gtk_widget_grab_focus (widget);
529 }
530
531 if ((event->button == 1 || event->button == 2) &&
532 event->type == GDK_BUTTON_PRESS)
533 {
534 view->priv->drag_started = FALSE;
535 view->priv->drag_button = event->button;
536 }
537 }
538
539 gtk_tree_path_free (path);
540 }
541 else
542 {
543 if ((event->button == 1 || event->button == 2) && event->type == GDK_BUTTON_PRESS)
544 {
545 if (view->priv->double_click_path[1])
546 {
547 gtk_tree_path_free (view->priv->double_click_path[1]);
548 }
549
550 view->priv->double_click_path[1] = view->priv->double_click_path[0];
551 view->priv->double_click_path[0] = NULL;
552 }
553
554 gtk_tree_selection_unselect_all (selection);
555 /* Chain up */
556 widget_parent->button_press_event (widget, event);
557 }
558
559 /* We already chained up if nescessary, so just return TRUE */
560 return TRUE;
561 }
562
563 static gboolean
key_press_event(GtkWidget * widget,GdkEventKey * event)564 key_press_event (GtkWidget *widget,
565 GdkEventKey *event)
566 {
567 XedFileBrowserView *view;
568 guint modifiers;
569 gboolean handled;
570
571 view = XED_FILE_BROWSER_VIEW (widget);
572 handled = FALSE;
573
574 modifiers = gtk_accelerator_get_default_mod_mask ();
575
576 switch (event->keyval)
577 {
578 case GDK_KEY_space:
579 if (event->state & GDK_CONTROL_MASK)
580 {
581 handled = FALSE;
582 break;
583 }
584 if (!gtk_widget_has_focus (widget))
585 {
586 handled = FALSE;
587 break;
588 }
589
590 activate_selected_items (view);
591 handled = TRUE;
592 break;
593
594 case GDK_KEY_Return:
595 case GDK_KEY_KP_Enter:
596 activate_selected_items (view);
597 handled = TRUE;
598 break;
599
600 case GDK_KEY_h:
601 if ((event->state & modifiers) == GDK_CONTROL_MASK)
602 {
603 toggle_hidden_filter (view);
604 handled = TRUE;
605 break;
606 }
607
608 default:
609 handled = FALSE;
610 }
611
612 /* Chain up */
613 if (!handled)
614 {
615 return GTK_WIDGET_CLASS (xed_file_browser_view_parent_class)->key_press_event (widget, event);
616 }
617
618 return TRUE;
619 }
620
621 static void
fill_expand_state(XedFileBrowserView * view,GtkTreeIter * iter)622 fill_expand_state (XedFileBrowserView *view,
623 GtkTreeIter *iter)
624 {
625 GtkTreePath * path;
626 GtkTreeIter child;
627
628 if (!gtk_tree_model_iter_has_child (view->priv->model, iter))
629 {
630 return;
631 }
632
633 path = gtk_tree_model_get_path (view->priv->model, iter);
634
635 if (gtk_tree_view_row_expanded (GTK_TREE_VIEW (view), path))
636 {
637 GFile *location;
638
639 gtk_tree_model_get (view->priv->model,
640 iter,
641 XED_FILE_BROWSER_STORE_COLUMN_LOCATION,
642 &location,
643 -1);
644
645 add_expand_state (view, location);
646
647 if (location)
648 {
649 g_object_unref (location);
650 }
651 }
652
653 if (gtk_tree_model_iter_children (view->priv->model, &child, iter))
654 {
655 do
656 {
657 fill_expand_state (view, &child);
658 } while (gtk_tree_model_iter_next (view->priv->model, &child));
659 }
660
661 gtk_tree_path_free (path);
662 }
663
664 static void
uninstall_restore_signals(XedFileBrowserView * tree_view,GtkTreeModel * model)665 uninstall_restore_signals (XedFileBrowserView *tree_view,
666 GtkTreeModel *model)
667 {
668 g_signal_handlers_disconnect_by_func (model, on_begin_refresh, tree_view);
669 g_signal_handlers_disconnect_by_func (model, on_end_refresh, tree_view);
670 g_signal_handlers_disconnect_by_func (model, on_unload, tree_view);
671 g_signal_handlers_disconnect_by_func (model, on_row_inserted, tree_view);
672 }
673
674 static void
install_restore_signals(XedFileBrowserView * tree_view,GtkTreeModel * model)675 install_restore_signals (XedFileBrowserView *tree_view,
676 GtkTreeModel *model)
677 {
678 g_signal_connect (model, "begin-refresh",
679 G_CALLBACK (on_begin_refresh), tree_view);
680 g_signal_connect (model, "end-refresh",
681 G_CALLBACK (on_end_refresh), tree_view);
682 g_signal_connect (model, "unload",
683 G_CALLBACK (on_unload), tree_view);
684 g_signal_connect_after (model, "row-inserted",
685 G_CALLBACK (on_row_inserted), tree_view);
686 }
687
688 static void
set_restore_expand_state(XedFileBrowserView * view,gboolean state)689 set_restore_expand_state (XedFileBrowserView *view,
690 gboolean state)
691 {
692 if (state == view->priv->restore_expand_state)
693 {
694 return;
695 }
696
697 if (view->priv->expand_state)
698 {
699 g_hash_table_destroy (view->priv->expand_state);
700 view->priv->expand_state = NULL;
701 }
702
703 if (state)
704 {
705 view->priv->expand_state = g_hash_table_new_full (g_file_hash,
706 (GEqualFunc)g_file_equal,
707 g_object_unref,
708 NULL);
709
710 if (view->priv->model && XED_IS_FILE_BROWSER_STORE (view->priv->model))
711 {
712 fill_expand_state (view, NULL);
713
714 install_restore_signals (view, view->priv->model);
715 }
716 }
717 else if (view->priv->model && XED_IS_FILE_BROWSER_STORE (view->priv->model))
718 {
719 uninstall_restore_signals (view, view->priv->model);
720 }
721
722 view->priv->restore_expand_state = state;
723 }
724
725 static void
get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)726 get_property (GObject *object,
727 guint prop_id,
728 GValue *value,
729 GParamSpec *pspec)
730 {
731 XedFileBrowserView *obj = XED_FILE_BROWSER_VIEW (object);
732
733 switch (prop_id)
734 {
735 case PROP_RESTORE_EXPAND_STATE:
736 g_value_set_boolean (value, obj->priv->restore_expand_state);
737 break;
738 default:
739 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
740 break;
741 }
742 }
743
744 static void
set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)745 set_property (GObject *object,
746 guint prop_id,
747 const GValue *value,
748 GParamSpec *pspec)
749 {
750 XedFileBrowserView *obj = XED_FILE_BROWSER_VIEW (object);
751
752 switch (prop_id)
753 {
754 case PROP_RESTORE_EXPAND_STATE:
755 set_restore_expand_state (obj, g_value_get_boolean (value));
756 break;
757 default:
758 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
759 break;
760 }
761 }
762
763 static void
xed_file_browser_view_class_init(XedFileBrowserViewClass * klass)764 xed_file_browser_view_class_init (XedFileBrowserViewClass *klass)
765 {
766 GObjectClass *object_class = G_OBJECT_CLASS (klass);
767 GtkTreeViewClass *tree_view_class = GTK_TREE_VIEW_CLASS (klass);
768 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
769
770 object_class->finalize = xed_file_browser_view_finalize;
771 object_class->get_property = get_property;
772 object_class->set_property = set_property;
773
774 /* Event handlers */
775 widget_class->button_press_event = button_press_event;
776 widget_class->button_release_event = button_release_event;
777 widget_class->drag_begin = drag_begin;
778 widget_class->key_press_event = key_press_event;
779
780 /* Tree view handlers */
781 tree_view_class->row_activated = row_activated;
782 tree_view_class->row_expanded = row_expanded;
783 tree_view_class->row_collapsed = row_collapsed;
784
785 /* Default handlers */
786 klass->directory_activated = directory_activated;
787
788 g_object_class_install_property (object_class, PROP_RESTORE_EXPAND_STATE,
789 g_param_spec_boolean ("restore-expand-state",
790 "Restore Expand State",
791 "Restore expanded state of loaded directories",
792 FALSE,
793 G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
794
795 signals[ERROR] =
796 g_signal_new ("error",
797 G_OBJECT_CLASS_TYPE (object_class),
798 G_SIGNAL_RUN_LAST,
799 G_STRUCT_OFFSET (XedFileBrowserViewClass,
800 error), NULL, NULL,
801 xed_file_browser_marshal_VOID__UINT_STRING,
802 G_TYPE_NONE, 2, G_TYPE_UINT, G_TYPE_STRING);
803 signals[FILE_ACTIVATED] =
804 g_signal_new ("file-activated",
805 G_OBJECT_CLASS_TYPE (object_class),
806 G_SIGNAL_RUN_LAST,
807 G_STRUCT_OFFSET (XedFileBrowserViewClass,
808 file_activated), NULL, NULL,
809 g_cclosure_marshal_VOID__BOXED,
810 G_TYPE_NONE, 1, GTK_TYPE_TREE_ITER);
811 signals[DIRECTORY_ACTIVATED] =
812 g_signal_new ("directory-activated",
813 G_OBJECT_CLASS_TYPE (object_class),
814 G_SIGNAL_RUN_LAST,
815 G_STRUCT_OFFSET (XedFileBrowserViewClass,
816 directory_activated), NULL, NULL,
817 g_cclosure_marshal_VOID__BOXED,
818 G_TYPE_NONE, 1, GTK_TYPE_TREE_ITER);
819 signals[BOOKMARK_ACTIVATED] =
820 g_signal_new ("bookmark-activated",
821 G_OBJECT_CLASS_TYPE (object_class),
822 G_SIGNAL_RUN_LAST,
823 G_STRUCT_OFFSET (XedFileBrowserViewClass,
824 bookmark_activated), NULL, NULL,
825 g_cclosure_marshal_VOID__BOXED,
826 G_TYPE_NONE, 1, GTK_TYPE_TREE_ITER);
827 }
828
829 static void
xed_file_browser_view_class_finalize(XedFileBrowserViewClass * klass)830 xed_file_browser_view_class_finalize (XedFileBrowserViewClass *klass)
831 {
832 /* dummy function - used by G_DEFINE_DYNAMIC_TYPE */
833 }
834
835 static void
cell_data_cb(GtkTreeViewColumn * tree_column,GtkCellRenderer * cell,GtkTreeModel * tree_model,GtkTreeIter * iter,XedFileBrowserView * obj)836 cell_data_cb (GtkTreeViewColumn *tree_column,
837 GtkCellRenderer *cell,
838 GtkTreeModel *tree_model,
839 GtkTreeIter *iter,
840 XedFileBrowserView *obj)
841 {
842 GtkTreePath *path;
843 PangoUnderline underline = PANGO_UNDERLINE_NONE;
844 gboolean editable = FALSE;
845
846 path = gtk_tree_model_get_path (tree_model, iter);
847
848 if (XED_IS_FILE_BROWSER_STORE (tree_model))
849 {
850 if (obj->priv->editable != NULL && gtk_tree_row_reference_valid (obj->priv->editable))
851 {
852 GtkTreePath *edpath = gtk_tree_row_reference_get_path (obj->priv->editable);
853
854 editable = edpath && gtk_tree_path_compare (path, edpath) == 0;
855 }
856 }
857
858 gtk_tree_path_free (path);
859 g_object_set (cell, "editable", editable, "underline", underline, NULL);
860 }
861
862 static void
xed_file_browser_view_init(XedFileBrowserView * obj)863 xed_file_browser_view_init (XedFileBrowserView *obj)
864 {
865 obj->priv = xed_file_browser_view_get_instance_private (obj);
866
867 obj->priv->column = gtk_tree_view_column_new ();
868
869 obj->priv->pixbuf_renderer = gtk_cell_renderer_pixbuf_new ();
870 gtk_tree_view_column_pack_start (obj->priv->column, obj->priv->pixbuf_renderer, FALSE);
871 gtk_tree_view_column_add_attribute (obj->priv->column,
872 obj->priv->pixbuf_renderer,
873 "pixbuf",
874 XED_FILE_BROWSER_STORE_COLUMN_ICON);
875
876 obj->priv->text_renderer = gtk_cell_renderer_text_new ();
877 gtk_tree_view_column_pack_start (obj->priv->column, obj->priv->text_renderer, TRUE);
878 gtk_tree_view_column_add_attribute (obj->priv->column,
879 obj->priv->text_renderer,
880 "text",
881 XED_FILE_BROWSER_STORE_COLUMN_NAME);
882
883 g_signal_connect (obj->priv->text_renderer, "edited",
884 G_CALLBACK (on_cell_edited), obj);
885
886 gtk_tree_view_append_column (GTK_TREE_VIEW (obj), obj->priv->column);
887 gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (obj), FALSE);
888
889 gtk_tree_view_enable_model_drag_source (GTK_TREE_VIEW (obj),
890 GDK_BUTTON1_MASK,
891 drag_source_targets,
892 G_N_ELEMENTS (drag_source_targets),
893 GDK_ACTION_COPY);
894 }
895
896 static gboolean
bookmarks_separator_func(GtkTreeModel * model,GtkTreeIter * iter,gpointer user_data)897 bookmarks_separator_func (GtkTreeModel *model,
898 GtkTreeIter *iter,
899 gpointer user_data)
900 {
901 guint flags;
902
903 gtk_tree_model_get (model, iter, XED_FILE_BOOKMARKS_STORE_COLUMN_FLAGS, &flags, -1);
904
905 return (flags & XED_FILE_BOOKMARKS_STORE_IS_SEPARATOR);
906 }
907
908 /* Public */
909 GtkWidget *
xed_file_browser_view_new(void)910 xed_file_browser_view_new (void)
911 {
912 XedFileBrowserView *obj = XED_FILE_BROWSER_VIEW (g_object_new (XED_TYPE_FILE_BROWSER_VIEW, NULL));
913
914 return GTK_WIDGET (obj);
915 }
916
917 void
xed_file_browser_view_set_model(XedFileBrowserView * tree_view,GtkTreeModel * model)918 xed_file_browser_view_set_model (XedFileBrowserView *tree_view,
919 GtkTreeModel *model)
920 {
921 GtkTreeSelection *selection;
922
923 if (tree_view->priv->model == model)
924 {
925 return;
926 }
927
928 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (tree_view));
929
930 if (XED_IS_FILE_BOOKMARKS_STORE (model))
931 {
932 gtk_tree_selection_set_mode (selection, GTK_SELECTION_BROWSE);
933 gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW
934 (tree_view),
935 bookmarks_separator_func,
936 NULL, NULL);
937 gtk_tree_view_column_set_cell_data_func (tree_view->priv->
938 column,
939 tree_view->priv->
940 text_renderer,
941 (GtkTreeCellDataFunc)
942 cell_data_cb,
943 tree_view, NULL);
944 }
945 else
946 {
947 gtk_tree_selection_set_mode (selection, GTK_SELECTION_MULTIPLE);
948 gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW
949 (tree_view), NULL,
950 NULL, NULL);
951 gtk_tree_view_column_set_cell_data_func (tree_view->priv->
952 column,
953 tree_view->priv->
954 text_renderer,
955 (GtkTreeCellDataFunc)
956 cell_data_cb,
957 tree_view, NULL);
958
959 if (tree_view->priv->restore_expand_state)
960 {
961 install_restore_signals (tree_view, model);
962 }
963
964 }
965
966 if (tree_view->priv->hover_path != NULL)
967 {
968 gtk_tree_path_free (tree_view->priv->hover_path);
969 tree_view->priv->hover_path = NULL;
970 }
971
972 if (XED_IS_FILE_BROWSER_STORE (tree_view->priv->model))
973 {
974 if (tree_view->priv->restore_expand_state)
975 {
976 uninstall_restore_signals (tree_view, tree_view->priv->model);
977 }
978 }
979
980 tree_view->priv->model = model;
981 gtk_tree_view_set_model (GTK_TREE_VIEW (tree_view), model);
982 }
983
984 void
xed_file_browser_view_start_rename(XedFileBrowserView * tree_view,GtkTreeIter * iter)985 xed_file_browser_view_start_rename (XedFileBrowserView *tree_view,
986 GtkTreeIter *iter)
987 {
988 guint flags;
989 GtkTreeRowReference *rowref;
990 GtkTreePath *path;
991
992 g_return_if_fail (XED_IS_FILE_BROWSER_VIEW (tree_view));
993 g_return_if_fail (XED_IS_FILE_BROWSER_STORE (tree_view->priv->model));
994 g_return_if_fail (iter != NULL);
995
996 gtk_tree_model_get (tree_view->priv->model, iter, XED_FILE_BROWSER_STORE_COLUMN_FLAGS, &flags, -1);
997
998 if (!(FILE_IS_DIR (flags) || !FILE_IS_DUMMY (flags)))
999 {
1000 return;
1001 }
1002
1003 path = gtk_tree_model_get_path (tree_view->priv->model, iter);
1004 rowref = gtk_tree_row_reference_new (tree_view->priv->model, path);
1005
1006 /* Start editing */
1007 gtk_widget_grab_focus (GTK_WIDGET (tree_view));
1008
1009 if (gtk_tree_path_up (path))
1010 {
1011 gtk_tree_view_expand_to_path (GTK_TREE_VIEW (tree_view), path);
1012 }
1013
1014 gtk_tree_path_free (path);
1015 tree_view->priv->editable = rowref;
1016
1017 gtk_tree_view_set_cursor (GTK_TREE_VIEW (tree_view),
1018 gtk_tree_row_reference_get_path (tree_view->priv->editable),
1019 tree_view->priv->column, TRUE);
1020
1021 gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (tree_view),
1022 gtk_tree_row_reference_get_path (tree_view->priv->editable),
1023 tree_view->priv->column,
1024 FALSE, 0.0, 0.0);
1025 }
1026
1027 void
xed_file_browser_view_set_restore_expand_state(XedFileBrowserView * tree_view,gboolean restore_expand_state)1028 xed_file_browser_view_set_restore_expand_state (XedFileBrowserView *tree_view,
1029 gboolean restore_expand_state)
1030 {
1031 g_return_if_fail (XED_IS_FILE_BROWSER_VIEW (tree_view));
1032
1033 set_restore_expand_state (tree_view, restore_expand_state);
1034 g_object_notify (G_OBJECT (tree_view), "restore-expand-state");
1035 }
1036
1037 /* Signal handlers */
1038 static void
on_cell_edited(GtkCellRendererText * cell,gchar * path,gchar * new_text,XedFileBrowserView * tree_view)1039 on_cell_edited (GtkCellRendererText *cell,
1040 gchar *path,
1041 gchar *new_text,
1042 XedFileBrowserView *tree_view)
1043 {
1044 GtkTreePath * treepath;
1045 GtkTreeIter iter;
1046 gboolean ret;
1047 GError * error = NULL;
1048
1049 gtk_tree_row_reference_free (tree_view->priv->editable);
1050 tree_view->priv->editable = NULL;
1051
1052 if (new_text == NULL || *new_text == '\0')
1053 {
1054 return;
1055 }
1056
1057 treepath = gtk_tree_path_new_from_string (path);
1058 ret = gtk_tree_model_get_iter (GTK_TREE_MODEL (tree_view->priv->model), &iter, treepath);
1059 gtk_tree_path_free (treepath);
1060
1061 if (ret)
1062 {
1063 if (xed_file_browser_store_rename (XED_FILE_BROWSER_STORE (tree_view->priv->model), &iter, new_text, &error))
1064 {
1065 treepath = gtk_tree_model_get_path (GTK_TREE_MODEL (tree_view->priv->model), &iter);
1066 gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (tree_view), treepath, NULL, FALSE, 0.0, 0.0);
1067 gtk_tree_path_free (treepath);
1068 }
1069 else
1070 {
1071 if (error)
1072 {
1073 g_signal_emit (tree_view, signals[ERROR], 0, error->code, error->message);
1074 g_error_free (error);
1075 }
1076 }
1077 }
1078 }
1079
1080 static void
on_begin_refresh(XedFileBrowserStore * model,XedFileBrowserView * view)1081 on_begin_refresh (XedFileBrowserStore *model,
1082 XedFileBrowserView *view)
1083 {
1084 /* Store the refresh state, so we can handle unloading of nodes while
1085 refreshing properly */
1086 view->priv->is_refresh = TRUE;
1087 }
1088
1089 static void
on_end_refresh(XedFileBrowserStore * model,XedFileBrowserView * view)1090 on_end_refresh (XedFileBrowserStore *model,
1091 XedFileBrowserView *view)
1092 {
1093 /* Store the refresh state, so we can handle unloading of nodes while
1094 refreshing properly */
1095 view->priv->is_refresh = FALSE;
1096 }
1097
1098 static void
on_unload(XedFileBrowserStore * model,GFile * location,XedFileBrowserView * view)1099 on_unload (XedFileBrowserStore *model,
1100 GFile *location,
1101 XedFileBrowserView *view)
1102 {
1103 /* Don't remove the expand state if we are refreshing */
1104 if (!view->priv->restore_expand_state || view->priv->is_refresh)
1105 {
1106 return;
1107 }
1108
1109 remove_expand_state (view, location);
1110 }
1111
1112 static void
restore_expand_state(XedFileBrowserView * view,XedFileBrowserStore * model,GtkTreeIter * iter)1113 restore_expand_state (XedFileBrowserView *view,
1114 XedFileBrowserStore *model,
1115 GtkTreeIter *iter)
1116 {
1117 GFile *location;
1118
1119 gtk_tree_model_get (GTK_TREE_MODEL (model),
1120 iter,
1121 XED_FILE_BROWSER_STORE_COLUMN_LOCATION,
1122 &location,
1123 -1);
1124
1125 if (location)
1126 {
1127 GtkTreePath *path;
1128
1129 path = gtk_tree_model_get_path (GTK_TREE_MODEL (model), iter);
1130
1131 if (g_hash_table_lookup (view->priv->expand_state, location))
1132 {
1133 gtk_tree_view_expand_row (GTK_TREE_VIEW (view), path, FALSE);
1134 }
1135
1136 gtk_tree_path_free (path);
1137 g_object_unref (location);
1138 }
1139 }
1140
1141 static void
on_row_inserted(XedFileBrowserStore * model,GtkTreePath * path,GtkTreeIter * iter,XedFileBrowserView * view)1142 on_row_inserted (XedFileBrowserStore *model,
1143 GtkTreePath *path,
1144 GtkTreeIter *iter,
1145 XedFileBrowserView *view)
1146 {
1147 GtkTreeIter parent;
1148 GtkTreePath * copy;
1149
1150 if (gtk_tree_model_iter_has_child (GTK_TREE_MODEL (model), iter))
1151 {
1152 restore_expand_state (view, model, iter);
1153 }
1154
1155 copy = gtk_tree_path_copy (path);
1156
1157 if (gtk_tree_path_up (copy) &&
1158 (gtk_tree_path_get_depth (copy) != 0) &&
1159 gtk_tree_model_get_iter (GTK_TREE_MODEL (model), &parent, copy))
1160 {
1161 restore_expand_state (view, model, &parent);
1162 }
1163
1164 gtk_tree_path_free (copy);
1165 }
1166
1167 void
_xed_file_browser_view_register_type(GTypeModule * type_module)1168 _xed_file_browser_view_register_type (GTypeModule *type_module)
1169 {
1170 xed_file_browser_view_register_type (type_module);
1171 }
1172
1173 // ex:ts=8:noet:
1174