1 /*
2  * This program is free software; you can redistribute it and/or
3  * modify it under the terms of the GNU General Public License
4  * as published by the Free Software Foundation; either version 2
5  * of the License, or (at your option) any later version.
6  *
7  * This program is distributed in the hope that it will be useful,
8  * but WITHOUT ANY WARRANTY; without even the implied warranty of
9  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10  * GNU General Public License for more details.
11  *
12  * You should have received a copy of the GNU General Public License
13  * along with this program; if not, write to the Free Software
14  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
15  *
16  * See the COPYING file for license information.
17  *
18  * Guillaume Chazarain <guichaz@gmail.com>
19  */
20 
21 /*****************************************
22  * The thumbnails browser in a tree view *
23  *****************************************/
24 
25 #include <string.h>             /* memset() */
26 
27 #include "gliv.h"
28 #include "tree_browser.h"
29 #include "tree.h"
30 #include "thumbnails.h"
31 #include "images_menus.h"
32 #include "next_image.h"
33 #include "messages.h"
34 #include "loading.h"
35 #include "rendering.h"
36 
37 extern GlivImage *current_image;
38 
39 static GtkTreeStore *tree_store;
40 static GtkTreeView *tree_view;
41 
42 /*
43  * Association between a directory and a GtkTreePath
44  * Used when adding filenames.
45  */
46 static GHashTable *dir_path_hash = NULL;
47 
48 /* Avoid reentrance */
49 static gint disable_tree = 0;
50 
51 /*
52  * If a row is selected when we cannot load an image, we
53  * record the filename to load it when possible.
54  */
55 static const gchar *next_filename;
56 
57 /* The tree columns (they are not all displayed) */
58 enum {
59     PIXBUF_COLUMN,
60     NAME_COLUMN,
61     PATH_COLUMN,
62     N_COLUMNS
63 };
64 
make_tree_rec(GtkTreeIter * where,GNode * node)65 static tree_item *make_tree_rec(GtkTreeIter * where, GNode * node)
66 {
67     static const gchar *browser = NULL;
68     static gint percent = 0, number = 0;
69     tree_item *item, *first_item;
70     GNode *child;
71 
72     if (where == NULL) {
73         /* First time for this browser */
74         number = 0;
75         percent = 0;
76         set_progress(NULL, NULL, -1);
77 
78         if (browser == NULL)
79             /* First time */
80             browser = _("Browser");
81         return NULL;
82     }
83 
84     item = node->data;
85 
86     if (G_NODE_IS_LEAF(node)) {
87         fill_thumbnail(item);
88         gtk_tree_store_set(tree_store, where,
89                            PIXBUF_COLUMN, item->thumb,
90                            NAME_COLUMN, item->name,
91                            PATH_COLUMN, item->path, -1);
92         set_progress(browser, &percent, number);
93         number++;
94         return item;
95     }
96 
97     g_hash_table_insert(dir_path_hash, item->path,
98                         gtk_tree_model_get_path(GTK_TREE_MODEL(tree_store),
99                                                 where));
100 
101     first_item = NULL;
102     for (child = g_node_first_child(node); child; child = child->next) {
103         GtkTreeIter iter;
104         gtk_tree_store_append(tree_store, &iter, where);
105         item = make_tree_rec(&iter, child);
106         if (first_item == NULL && item != NULL && item->thumb != NULL)
107             first_item = item;
108     }
109 
110     item = node->data;
111     gtk_tree_store_set(tree_store, where,
112                        PIXBUF_COLUMN, first_item ? first_item->thumb : NULL,
113                        NAME_COLUMN, item->path,
114                        PATH_COLUMN, first_item ? first_item->path : NULL, -1);
115 
116     return first_item;
117 }
118 
load_later(void)119 void load_later(void)
120 {
121     if (next_filename != NULL && !currently_loading() && current_image->node) {
122         menu_load(next_filename);
123         if (current_image->node->data == next_filename)
124             next_filename = NULL;
125     }
126 }
127 
activate_row(GtkTreeSelection * selection)128 static void activate_row(GtkTreeSelection * selection)
129 {
130     GtkTreeIter iter;
131     GtkTreeModel *model;
132     gchar *path;
133 
134     if (!gtk_tree_selection_get_selected(selection, &model, &iter))
135         return;
136 
137     gtk_tree_model_get(model, &iter, PATH_COLUMN, &path, -1);
138     next_filename = path;
139     do_later(G_PRIORITY_DEFAULT, load_later);
140 }
141 
build_tree_browser(void)142 static gboolean build_tree_browser(void)
143 {
144     GtkTreeIter iter;
145     GNode *tree;
146 
147     tree = get_tree();
148     if (tree == NULL)
149         return FALSE;
150 
151     if (dir_path_hash != NULL)
152         g_hash_table_destroy(dir_path_hash);
153 
154     dir_path_hash = g_hash_table_new_full(g_str_hash, g_str_equal, NULL,
155                                           (GDestroyNotify) gtk_tree_path_free);
156 
157     if (tree_store == NULL)
158         /* First time */
159         tree_store = gtk_tree_store_new(N_COLUMNS,
160                                         GDK_TYPE_PIXBUF, G_TYPE_STRING,
161                                         G_TYPE_POINTER);
162     else
163         gtk_tree_store_clear(tree_store);
164 
165     gtk_tree_store_append(tree_store, &iter, NULL);
166     make_tree_rec(NULL, NULL);
167     make_tree_rec(&iter, tree);
168 
169     set_progress(NULL, NULL, 0);
170     end_using_tree();
171     return TRUE;
172 }
173 
destroy_tree_view(GtkWindow * window)174 static void destroy_tree_view(GtkWindow * window)
175 {
176     gtk_widget_destroy(GTK_WIDGET(window));
177     tree_view = NULL;
178 }
179 
show_tree_browser(void)180 void show_tree_browser(void)
181 {
182     GtkCellRenderer *name_renderer;
183     GtkTreeViewColumn *col;
184     GtkTreeSelection *selection;
185     GtkScrolledWindow *scroll;
186     GtkWindow *window;
187 
188     if (tree_view != NULL)
189         /* Currently displayed */
190         return;
191 
192     if (tree_store == NULL && !build_tree_browser())
193         return;
194 
195     tree_view =
196         GTK_TREE_VIEW(gtk_tree_view_new_with_model(GTK_TREE_MODEL(tree_store)));
197 
198     gtk_tree_view_set_enable_search(tree_view, TRUE);
199 
200     col =
201         gtk_tree_view_column_new_with_attributes(_("Thumb"),
202                                                  gtk_cell_renderer_pixbuf_new(),
203                                                  "pixbuf", PIXBUF_COLUMN, NULL);
204     gtk_tree_view_append_column(GTK_TREE_VIEW(tree_view), col);
205 
206     name_renderer = gtk_cell_renderer_text_new();
207     g_object_set(name_renderer,
208                  "ellipsize-set", TRUE,
209                  "ellipsize", PANGO_ELLIPSIZE_START, NULL);
210     col =
211         gtk_tree_view_column_new_with_attributes(_("Name"), name_renderer,
212                                                  "text", NAME_COLUMN, NULL);
213     gtk_tree_view_append_column(GTK_TREE_VIEW(tree_view), col);
214     gtk_widget_show_all(GTK_WIDGET(tree_view));
215 
216     scroll = GTK_SCROLLED_WINDOW(gtk_scrolled_window_new(NULL, NULL));
217     gtk_scrolled_window_set_policy(scroll,
218                                    GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
219     gtk_container_add(GTK_CONTAINER(scroll), GTK_WIDGET(tree_view));
220     gtk_widget_show_all(GTK_WIDGET(scroll));
221 
222     window = new_window(_("Thumbnails browser"));
223     gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(scroll));
224     gtk_window_set_default_size(window, 300, 600);
225     gtk_widget_show_all(GTK_WIDGET(window));
226 
227     selection = gtk_tree_view_get_selection(tree_view);
228     g_signal_connect(selection, "changed", G_CALLBACK(activate_row), NULL);
229 
230     g_signal_connect(window, "delete-event", G_CALLBACK(destroy_tree_view),
231                      NULL);
232 
233     highlight_current_image();
234 }
235 
highlight_current_image(void)236 void highlight_current_image(void)
237 {
238     gchar *path;
239     GtkTreePath *tree_path;
240     GtkTreeIter iter, child;
241 
242     if (tree_view == NULL || !GTK_WIDGET_VISIBLE(tree_view) ||
243         current_image->node == NULL)
244         return;
245 
246     if (next_filename) {
247         do_later(G_PRIORITY_DEFAULT, load_later);
248         return;
249     }
250 
251     path = g_strdup(current_image->node->data);
252     do {
253         gchar *new_path = g_path_get_dirname(path);
254 
255         g_free(path);
256         path = new_path;
257 
258         tree_path = g_hash_table_lookup(dir_path_hash, path);
259     } while (!tree_path && !g_str_equal(path, "/") && !g_str_equal(path, "."));
260 
261     g_free(path);
262 
263     if (tree_path == NULL)
264         return;
265 
266     if (!gtk_tree_model_get_iter(GTK_TREE_MODEL(tree_store), &iter, tree_path))
267         return;
268 
269     if (!gtk_tree_model_iter_children
270         (GTK_TREE_MODEL(tree_store), &child, &iter))
271         return;
272 
273     do {
274         GValue tree_filename;
275         memset(&tree_filename, 0, sizeof(tree_filename));
276 
277         gtk_tree_model_get_value(GTK_TREE_MODEL(tree_store), &child,
278                                  PATH_COLUMN, &tree_filename);
279 
280         if (g_value_peek_pointer(&tree_filename) == current_image->node->data) {
281             tree_path =
282                 gtk_tree_model_get_path(GTK_TREE_MODEL(tree_store), &child);
283 
284             disable_tree++;
285             gtk_tree_view_expand_to_path(tree_view, tree_path);
286             gtk_tree_view_expand_row(tree_view, tree_path, FALSE);
287             gtk_tree_view_scroll_to_cell(tree_view, tree_path,
288                                          NULL, FALSE, 0.0, 0.0);
289             gtk_tree_view_set_cursor(tree_view, tree_path, NULL, FALSE);
290             disable_tree--;
291 
292             gtk_tree_path_free(tree_path);
293             break;
294         }
295 
296         g_value_unset(&tree_filename);
297     } while (gtk_tree_model_iter_next(GTK_TREE_MODEL(tree_store), &child));
298 }
299 
cond_rebuild_tree_browser(void)300 void cond_rebuild_tree_browser(void)
301 {
302     if (dir_path_hash != NULL)
303         build_tree_browser();
304 }
305