1 /*
2 * * Copyright (C) 2006-2011 Anders Brander <anders@brander.dk>,
3 * * Anders Kvist <akv@lnxbx.dk> and Klaus Post <klauspost@gmail.com>
4 *
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU General Public License
7 * as published by the Free Software Foundation; either version 2
8 * of the License, or (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 */
19
20 #include <gtk/gtk.h>
21 #include "rs-dir-selector.h"
22
23 enum {
24 COL_NAME = 0,
25 COL_PATH,
26 NUM_COLS
27 };
28
29 struct _RSDirSelector {
30 GtkScrolledWindow parent;
31 GtkWidget *view;
32 gchar *root;
33 };
34
35 G_DEFINE_TYPE (RSDirSelector, rs_dir_selector, GTK_TYPE_SCROLLED_WINDOW);
36
37 static void realize(GtkWidget *widget, gpointer data);
38 static void dir_selector_add_element(GtkTreeStore *treestore, GtkTreeIter *iter, const gchar *name, const gchar *path);
39 static void row_activated(GtkTreeView *view, GtkTreePath *path, GtkTreeViewColumn *col, gpointer user_data);
40 static void row_expanded(GtkTreeView *view, GtkTreeIter *iter, GtkTreePath *path, gpointer user_data);
41 static void row_collapsed(GtkTreeView *view, GtkTreeIter *iter, GtkTreePath *path, gpointer user_data);
42 static void move_cursor(GtkTreeView *view, GtkMovementStep movement, gint direction, gpointer user_data);
43 static GtkTreeModel *create_and_fill_model (const gchar *root);
44 static gboolean directory_contains_directories(const gchar *filepath);
45
46 enum {
47 DIRECTORY_ACTIVATED_SIGNAL,
48 LAST_SIGNAL
49 };
50
51 static guint signals[LAST_SIGNAL] = { 0 };
52
53 /**
54 * Class initializer
55 */
56 static void
rs_dir_selector_class_init(RSDirSelectorClass * klass)57 rs_dir_selector_class_init(RSDirSelectorClass *klass)
58 {
59 GtkWidgetClass *widget_class;
60 GtkObjectClass *object_class;
61 widget_class = GTK_WIDGET_CLASS(klass);
62 object_class = GTK_OBJECT_CLASS(klass);
63
64 signals[DIRECTORY_ACTIVATED_SIGNAL] = g_signal_new ("directory-activated",
65 G_TYPE_FROM_CLASS (klass),
66 G_SIGNAL_RUN_FIRST,
67 0,
68 NULL,
69 NULL,
70 g_cclosure_marshal_VOID__STRING,
71 G_TYPE_NONE,
72 1,
73 G_TYPE_STRING);
74 }
75
76 /**
77 * Instance initialization
78 */
79 static void
rs_dir_selector_init(RSDirSelector * selector)80 rs_dir_selector_init(RSDirSelector *selector)
81 {
82 GtkTreeViewColumn *col;
83 GtkCellRenderer *renderer;
84 GtkScrolledWindow *scroller = GTK_SCROLLED_WINDOW(selector);
85
86 g_object_set (G_OBJECT (selector), "hadjustment", NULL, "vadjustment", NULL, NULL);
87 gtk_scrolled_window_set_policy (scroller, GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
88
89 selector->view = gtk_tree_view_new();
90 col = gtk_tree_view_column_new();
91 gtk_tree_view_append_column(GTK_TREE_VIEW(selector->view), col);
92 renderer = gtk_cell_renderer_text_new();
93 gtk_tree_view_column_pack_start(col, renderer, TRUE);
94 gtk_tree_view_column_add_attribute (col, renderer, "text",
95 COL_NAME);
96
97 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(selector->view), FALSE);
98
99 g_signal_connect(selector->view, "row-activated", G_CALLBACK(row_activated), selector);
100 g_signal_connect(selector->view, "row-expanded", G_CALLBACK(row_expanded), NULL);
101 g_signal_connect(selector->view, "row-collapsed", G_CALLBACK(row_collapsed), NULL);
102 g_signal_connect(selector->view, "move-cursor", G_CALLBACK(move_cursor), NULL);
103
104 gtk_tree_selection_set_mode(gtk_tree_view_get_selection(GTK_TREE_VIEW(selector->view)),
105 GTK_SELECTION_SINGLE);
106
107 rs_dir_selector_set_root(selector, "/");
108
109 gtk_signal_connect(GTK_OBJECT(selector), "realize", G_CALLBACK(realize), NULL);
110
111 gtk_container_add (GTK_CONTAINER (scroller), selector->view);
112 return;
113 }
114
115 static void
realize(GtkWidget * widget,gpointer data)116 realize(GtkWidget *widget, gpointer data)
117 {
118 RSDirSelector *selector = RS_DIR_SELECTOR(widget);
119 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(selector->view));
120
121 if (!selection)
122 return;
123
124 if (gtk_tree_selection_count_selected_rows(selection) == 1)
125 {
126 GList *selected = gtk_tree_selection_get_selected_rows(selection, NULL);
127
128 gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(selector->view), g_list_nth_data(selected, 0), NULL, TRUE, 0.2, 0.0);
129
130 g_list_foreach (selected, (GFunc) gtk_tree_path_free, NULL);
131 g_list_free (selected);
132 }
133 }
134
135 /**
136 * Adds an element to treestore
137 * @param treestore A GtkTreeStore
138 * @param iter A GtkTreeIter
139 * @param name A gchar
140 * @param path A gchar
141 */
142 static void
dir_selector_add_element(GtkTreeStore * treestore,GtkTreeIter * iter,const gchar * name,const gchar * path)143 dir_selector_add_element(GtkTreeStore *treestore, GtkTreeIter *iter, const gchar *name, const gchar *path)
144 {
145 GtkTreeIter this, child;
146
147 gtk_tree_store_append(treestore, &this, iter);
148 gtk_tree_store_set(treestore, &this,
149 COL_NAME, name,
150 COL_PATH, path,
151 -1);
152 if (path && directory_contains_directories(path))
153 {
154 gtk_tree_store_append(treestore, &child, &this);
155 gtk_tree_store_set(treestore, &child,
156 -1);
157 }
158 }
159
160 /**
161 * Callback after activating a row
162 * @param view A GtkTreeView
163 * @param path A GtkTreePath
164 * @param col A GtkTreeViewColumn
165 * @param user_data A gpointer
166 */
167 static void
row_activated(GtkTreeView * view,GtkTreePath * path,GtkTreeViewColumn * col,gpointer user_data)168 row_activated(GtkTreeView *view, GtkTreePath *path, GtkTreeViewColumn *col, gpointer user_data)
169 {
170 GtkTreeModel *model;
171 GtkTreeIter iter;
172 gchar *filepath;
173
174 model = gtk_tree_view_get_model(view);
175 gtk_tree_model_get_iter(model, &iter, path);
176 gtk_tree_model_get(GTK_TREE_MODEL(model), &iter,
177 COL_PATH, &filepath,
178 -1);
179 g_signal_emit (G_OBJECT (user_data), signals[DIRECTORY_ACTIVATED_SIGNAL], 0, filepath);
180 }
181
182 /**
183 * Callback after expanding a row
184 * @param view A GtkTreeView
185 * @param iter A GtkTreeIter
186 * @param path A GtkTreePath
187 * @param user_data A gpointer
188 */
189 static void
row_expanded(GtkTreeView * view,GtkTreeIter * iter,GtkTreePath * path,gpointer user_data)190 row_expanded(GtkTreeView *view, GtkTreeIter *iter, GtkTreePath *path, gpointer user_data)
191 {
192 GtkTreeModel *model;
193 GtkTreeIter empty;
194 gchar *filepath;
195 gchar *file;
196 GDir *dir;
197 GString *gs = NULL;
198
199 /* Set busy cursor */
200 GdkCursor* cursor = gdk_cursor_new(GDK_WATCH);
201 gdk_window_set_cursor(gtk_widget_get_toplevel(GTK_WIDGET(view))->window, cursor);
202 gdk_cursor_unref(cursor);
203 gdk_flush();
204
205 model = gtk_tree_view_get_model(view);
206 gtk_tree_model_iter_children(GTK_TREE_MODEL(model),
207 &empty, iter);
208 gtk_tree_model_get(GTK_TREE_MODEL(model), iter,
209 COL_PATH, &filepath,
210 -1);
211
212 dir = g_dir_open(filepath, 0, NULL);
213
214 if (dir)
215 {
216 while ((file = (gchar *) g_dir_read_name(dir)))
217 {
218 gs = g_string_new(filepath);
219 g_string_append(gs, file);
220 g_string_append(gs, "/");
221 if (g_file_test(gs->str, G_FILE_TEST_IS_DIR))
222 {
223 if (file[0] == '.')
224 {
225 /* Fixme: If hidden files should be shown too */
226 }
227 else
228 {
229 dir_selector_add_element(GTK_TREE_STORE(model), iter, file, gs->str);
230 }
231 }
232 g_string_free(gs, TRUE);
233 }
234 g_dir_close(dir);
235 g_free(filepath);
236 }
237 gdk_window_set_cursor(gtk_widget_get_toplevel(GTK_WIDGET(view))->window, NULL);
238 gtk_tree_store_remove(GTK_TREE_STORE(model), &empty);
239 }
240
241 /**
242 * Callback after collapsing a row
243 * @param view A GtkTreeView
244 * @param iter A GtkTreeIter
245 * @param path A GtkTreePath
246 * @param user_data A gpointer
247 */
248 static void
row_collapsed(GtkTreeView * view,GtkTreeIter * iter,GtkTreePath * path,gpointer user_data)249 row_collapsed(GtkTreeView *view, GtkTreeIter *iter, GtkTreePath *path, gpointer user_data)
250 {
251 GtkTreeModel *model;
252 GtkTreeIter child;
253
254 model = gtk_tree_view_get_model(view);
255 while (gtk_tree_model_iter_children(GTK_TREE_MODEL(model), &child, iter))
256 {
257 gtk_tree_store_remove(GTK_TREE_STORE(model), &child);
258 }
259 dir_selector_add_element(GTK_TREE_STORE(model), iter, NULL, NULL);
260 }
261
262 /**
263 * Callback after moving cursor
264 * @param view A GtkTreeView
265 * @param iter A GtkMovementStep
266 * @param path A gint
267 * @param user_data A gpointer
268 */
269 static void
move_cursor(GtkTreeView * view,GtkMovementStep movement,gint direction,gpointer user_data)270 move_cursor(GtkTreeView *view, GtkMovementStep movement, gint direction, gpointer user_data)
271 {
272 if (movement == GTK_MOVEMENT_VISUAL_POSITIONS)
273 {
274 GtkTreePath *path;
275 gtk_tree_view_get_cursor(view, &path, NULL);
276 if (direction == 1) /* RIGHT */
277 {
278 gtk_tree_view_expand_row(view, path, FALSE);
279 }
280 else if (direction == -1) /* LEFT */
281 {
282 gtk_tree_view_collapse_row(view, path);
283 }
284 gtk_tree_path_free(path);
285 }
286 }
287
288 /**
289 * Creates a GtkTreeStore and fills it with data
290 * @param root A gchar
291 * @return A GtkTreeModel
292 */
293 static GtkTreeModel *
create_and_fill_model(const gchar * root)294 create_and_fill_model (const gchar *root)
295 {
296 GtkTreeStore *treestore;
297
298 treestore = gtk_tree_store_new(NUM_COLS, G_TYPE_STRING, G_TYPE_STRING);
299 dir_selector_add_element(treestore, NULL, root, root);
300 return GTK_TREE_MODEL(treestore);
301 }
302
303 /**
304 * Creates a GtkWidget
305 * @return A GtkWidget
306 */
307 GtkWidget *
rs_dir_selector_new()308 rs_dir_selector_new()
309 {
310 return g_object_new (RS_DIR_SELECTOR_TYPE_WIDGET, NULL);
311 }
312
313 /**
314 * Sets root
315 * @param root A gchar
316 */
317 void
rs_dir_selector_set_root(RSDirSelector * selector,const gchar * root)318 rs_dir_selector_set_root(RSDirSelector *selector, const gchar *root)
319 {
320 GtkTreeModel *model;
321 GtkTreeSortable *sortable;
322
323 model = create_and_fill_model(root);
324
325 sortable = GTK_TREE_SORTABLE(model);
326 gtk_tree_sortable_set_sort_column_id(sortable,
327 COL_NAME,
328 GTK_SORT_ASCENDING);
329
330 gtk_tree_view_set_model(GTK_TREE_VIEW(selector->view), model);
331
332 g_object_unref(model); /* destroy model automatically with view */
333 }
334
335 /**
336 * Expands to path
337 * @param path to expand
338 */
339 void
rs_dir_selector_expand_path(RSDirSelector * selector,const gchar * expand)340 rs_dir_selector_expand_path(RSDirSelector *selector, const gchar *expand)
341 {
342 GtkTreeView *view = GTK_TREE_VIEW(selector->view);
343 GtkTreeModel *model = gtk_tree_view_get_model(view);
344 GtkTreePath *path = gtk_tree_path_new_first();
345 GtkTreeIter iter;
346 gchar *filepath = NULL;
347 GString *gs;
348
349 if (g_path_is_absolute(expand))
350 {
351 gs = g_string_new(expand);
352 }
353 else
354 {
355 gs = g_string_new(g_get_current_dir());
356 g_string_append(gs, G_DIR_SEPARATOR_S);
357 g_string_append(gs, expand);
358 }
359
360 g_string_append(gs, G_DIR_SEPARATOR_S);
361
362 while (gtk_tree_model_get_iter(model, &iter, path))
363 {
364 gtk_tree_model_get(model, &iter, COL_PATH, &filepath, -1);
365
366 if (filepath && g_str_has_prefix(gs->str, filepath))
367 {
368 gtk_tree_view_expand_row(GTK_TREE_VIEW(view), path, FALSE);
369 gtk_tree_path_down(path);
370 }
371 else
372 {
373 gtk_tree_path_next(path);
374 }
375 }
376
377 g_string_free(gs, TRUE);
378
379 if (GTK_WIDGET_REALIZED(GTK_WIDGET(selector)))
380 gtk_tree_view_scroll_to_cell(view, path, NULL, TRUE, 0.2, 0.0);
381 else
382 {
383 /* Save this, realize() will catch it later */
384 GtkTreeSelection *selection = gtk_tree_view_get_selection(view);
385 if (gtk_tree_model_get_iter(model, &iter, path))
386 gtk_tree_selection_select_iter(selection, &iter);
387 }
388
389 gtk_tree_path_free(path);
390 }
391
392 static gboolean
directory_contains_directories(const gchar * filepath)393 directory_contains_directories(const gchar *filepath)
394 {
395 GDir *dir;
396 GString *gs = NULL;
397 gchar *file;
398
399 dir = g_dir_open(filepath, 0, NULL);
400
401 if (dir)
402 {
403 while ((file = (gchar *) g_dir_read_name(dir)))
404 {
405 gs = g_string_new(filepath);
406 g_string_append(gs, file);
407 g_string_append(gs, "/");
408
409 if (file[0] == '.')
410 {
411 /* Fixme: If hidden files should be shown too */
412 }
413 else
414 {
415 if (g_file_test(gs->str, G_FILE_TEST_IS_DIR))
416 {
417 g_dir_close(dir);
418 g_string_free(gs, TRUE);
419 return TRUE;
420 }
421 }
422 g_string_free(gs, TRUE);
423 }
424 g_dir_close(dir);
425 }
426
427 return FALSE;
428 }
429