Bug fixed in "Open current image folder", make sure every image has
[gliv.git] / src / images_menus.c
blob7f42fc25edbd84f9aaf6f66517840eaaa486a6b1
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.
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.
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.
16 * See the COPYING file for license information.
18 * Guillaume Chazarain <guichaz@yahoo.fr>
21 /************************************
22 * The Images and Directories menus *
23 ************************************/
25 #include <string.h> /* strlen() */
27 #include "gliv.h"
28 #include "images_menus.h"
29 #include "options.h"
30 #include "menus.h"
31 #include "messages.h"
32 #include "next_image.h"
33 #include "mnemonics.h"
34 #include "files_list.h"
35 #include "loading.h"
36 #include "thumbnails.h"
37 #include "callbacks.h"
38 #include "tree.h"
39 #include "timestamp.h"
40 #include "dirty_gtk.h"
41 #include "gliv-image.h"
43 extern options_struct *options;
44 extern GlivImage *current_image;
46 /* The progress indicator. */
47 static GtkMenuItem *rebuilding_entry;
49 /* The menu entry to cancel the rebuilding. */
50 static GtkMenuItem *cancel_menu_item;
53 * The number of files in the current tree.
54 * Used also to know if we are rebuilding a menu.
56 static gint tree_count;
59 * Association between a directory and its GtkMenu.
60 * Used by Directories->"Current image directory...".
62 static GHashTable *dir_menu_hash = NULL;
64 /*** Functions for both menus. ***/
66 /* Called at menu creation time. */
67 void set_rebuilding_entry(GtkMenuItem * item)
69 rebuilding_entry = item;
72 /* Refresh the percent indicator. */
73 static void set_menu_indicator(gchar * name, gint percent)
75 static gchar *rebuilding = NULL;
77 if (name == NULL)
78 set_menu_label(rebuilding_entry, "", FALSE);
79 else {
80 gchar *label;
82 if (rebuilding == NULL)
83 /* First time. */
84 rebuilding = _("Rebuilding:");
86 label = g_strdup_printf("%s %s (%d%%)", rebuilding, name, percent);
87 set_menu_label(rebuilding_entry, label, FALSE);
89 g_free(label);
93 /* Update the percent indicator. */
94 static void set_progress(gchar * menu, gint * percent, gint number)
96 if ((*percent + 1) * tree_count <= 100 * number) {
97 *percent = (100 * number) / tree_count;
98 set_menu_indicator(menu, *percent);
102 /* We don't want to perform the signal lookup for each file. */
103 static void connect_activate(GtkMenuItem * instance, const gchar * filename)
105 static guint signal_id = 0;
106 GClosure *closure;
108 if (signal_id == 0)
109 /* First time. */
110 signal_id = g_signal_lookup("activate", G_TYPE_FROM_INSTANCE(instance));
112 closure = g_cclosure_new_swap(G_CALLBACK(menu_load),
113 (gpointer) filename, NULL);
115 g_signal_connect_closure_by_id(instance, signal_id, 0, closure, FALSE);
118 typedef enum {
119 MENU_FILE, /* From both menus. */
120 MENU_DIR, /* From the Images menu. */
121 MENU_SUBMENU /* From the Directories menu. */
122 } menu_type;
124 /* Add also a mnemonic and a thumbnail. */
125 static GtkMenuItem *add_menu_item(DirtyGtkMenu * menu,
126 tree_item * item, menu_type type)
128 const gchar *name;
129 GtkMenuItem *menu_item;
130 GtkImage *thumbnail = NULL;
132 if (options->thumbnails && type == MENU_FILE && fill_thumbnail(item))
133 thumbnail = GTK_IMAGE(gtk_image_new_from_pixbuf(item->thumb));
134 else
135 /* Simulate the do_threaded() effect of fill_thumbnail(). */
136 process_events();
138 if (type == MENU_DIR)
139 name = item->path;
140 else
141 name = item->name;
143 if (options->mnemonics && type != MENU_DIR) {
144 name = add_mnemonic(name);
146 if (thumbnail)
147 menu_item =
148 GTK_MENU_ITEM(gtk_image_menu_item_new_with_mnemonic(name));
149 else
150 menu_item = GTK_MENU_ITEM(gtk_menu_item_new_with_mnemonic(name));
152 } else {
153 if (thumbnail)
154 menu_item = GTK_MENU_ITEM(gtk_image_menu_item_new_with_label(name));
155 else
156 menu_item = GTK_MENU_ITEM(gtk_menu_item_new_with_label(name));
159 if (thumbnail)
160 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(menu_item),
161 GTK_WIDGET(thumbnail));
163 dirty_gtk_menu_append(menu, GTK_WIDGET(menu_item));
165 switch (type) {
166 case MENU_FILE:
167 connect_activate(menu_item, item->path);
168 break;
170 case MENU_DIR:
171 gtk_widget_set_sensitive(GTK_WIDGET(menu_item), FALSE);
172 break;
174 case MENU_SUBMENU:
175 break;
178 return menu_item;
181 /*** Directories menu. ***/
183 static void add_file_from_tree(DirtyGtkMenu * menu, tree_item * item)
185 static gchar *directories = NULL;
186 static gint number = 0, percent = 0;
187 gchar *dirname;
189 if (canceled_using_tree())
190 return;
192 if (menu == NULL) {
193 /* First time for this menu. */
195 if (directories == NULL)
196 /* First time. */
197 directories = _("Directories");
199 number = 0;
200 percent = 0;
201 return;
204 dirname = g_path_get_dirname(item->path);
205 g_hash_table_insert(dir_menu_hash, dirname,
206 dirty_gtk_menu_get_tearoff(menu));
207 add_menu_item(menu, item, MENU_FILE);
209 set_progress(directories, &percent, number);
210 number++;
213 static DirtyGtkMenu *add_sub_menu(DirtyGtkMenu * parent, tree_item * item)
215 DirtyGtkMenu *menu;
216 GtkMenuItem *menu_item;
218 menu_item = add_menu_item(parent, item, MENU_SUBMENU);
220 menu = dirty_gtk_menu_new();
221 gtk_menu_item_set_submenu(menu_item, GTK_WIDGET(menu->gtk_menu_shell));
223 return menu;
226 static void make_menu_from_tree_rec(GNode * tree, DirtyGtkMenu * parent)
228 tree_item *item;
229 DirtyGtkMenu *menu;
231 if (canceled_using_tree())
232 return;
234 if (G_NODE_IS_LEAF(tree)) {
235 add_file_from_tree(parent, tree->data);
236 return;
239 item = tree->data;
240 menu = add_sub_menu(parent, item);
242 push_mnemonics();
244 g_node_children_foreach(tree, G_TRAVERSE_ALL,
245 (GNodeForeachFunc) make_menu_from_tree_rec, menu);
247 dirty_gtk_menu_release(menu);
248 pop_mnemonics();
251 static gboolean open_current_dir_menu(void)
253 if (dir_menu_hash && current_image) {
254 gchar *dir = g_path_get_dirname(current_image->node->data);
255 GtkTearoffMenuItem *tearoff = g_hash_table_lookup(dir_menu_hash, dir);
257 g_free(dir);
259 if (tearoff)
260 gtk_menu_item_activate(GTK_MENU_ITEM(tearoff));
263 return FALSE;
266 static void add_current_dir_entry(DirtyGtkMenu * menu)
268 GtkMenuItem *item;
269 const gchar *label;
271 label = add_mnemonic(_("Current image directory..."));
273 item = GTK_MENU_ITEM(gtk_menu_item_new_with_mnemonic(label));
274 g_signal_connect_swapped(item, "activate",
275 G_CALLBACK(open_current_dir_menu), NULL);
277 gtk_widget_show(GTK_WIDGET(item));
278 dirty_gtk_menu_append(menu, GTK_WIDGET(item));
280 if (dir_menu_hash)
281 g_hash_table_destroy(dir_menu_hash);
283 dir_menu_hash =
284 g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
287 /*** Images menu. ***/
289 static gboolean add_file_item(GNode * tree, DirtyGtkMenu * parent_menu)
291 static gchar *images = NULL;
292 static gint number = 0, percent = 0;
293 GNode *sibling;
295 if (canceled_using_tree())
296 return TRUE;
298 if (parent_menu == NULL) {
299 /* First time for this menu. */
301 if (images == NULL)
302 /* First time. */
303 images = _("Images");
305 number = 0;
306 percent = 0;
307 return FALSE;
310 /* Check if it is the first image in its directory. */
311 sibling = g_node_prev_sibling(tree);
312 if (sibling == NULL && tree->parent != NULL)
313 add_menu_item(parent_menu, tree->parent->data, MENU_DIR);
315 add_menu_item(parent_menu, tree->data, MENU_FILE);
317 set_progress(images, &percent, number);
318 number++;
320 return FALSE;
323 /*** Menus builders. ***/
325 static DirtyGtkMenu *begin_rebuild(GtkMenuItem * root, GCallback func)
327 static DirtyGtkMenu *menu;
328 GtkWidget *rebuilder;
330 if (root != NULL)
331 gtk_menu_item_deselect(root);
333 menu = dirty_gtk_menu_new();
335 rebuilder =
336 gtk_menu_item_new_with_mnemonic(add_mnemonic(_("Rebuild this menu")));
338 g_signal_connect_swapped(rebuilder, "activate", func, NULL);
339 dirty_gtk_menu_append(menu, rebuilder);
341 gtk_menu_item_set_submenu(root, GTK_WIDGET(menu->gtk_menu_shell));
343 return menu;
346 static GNode *get_tree(void)
348 if (is_loading())
350 * There is no problem in rebuilding the images menus during
351 * a loading, it would just make the loading too long.
353 return NULL;
355 return make_tree();
358 #define REBUILD_START(name, func) \
359 do { \
360 if (root != NULL) { \
361 menu_item = root; \
362 return TRUE; \
365 if (more_recent_than_tree(timestamp)) \
366 /* The menu is already up to date. */ \
367 return TRUE; \
369 tree = get_tree(); \
370 if (tree == NULL) \
371 return FALSE; \
373 tree_count = tree_count_files(tree); \
374 gtk_widget_set_sensitive(GTK_WIDGET(cancel_menu_item), TRUE); \
375 reset_mnemonics(); \
376 menu = begin_rebuild(menu_item, G_CALLBACK(func)); \
377 set_menu_indicator(name, 0); \
378 } while (0)
380 #define REBUILD_END() \
381 do { \
382 if (root != NULL) \
383 gtk_menu_item_deselect(root); \
385 set_menu_indicator(NULL, 0); \
386 gtk_widget_set_sensitive(GTK_WIDGET(cancel_menu_item), FALSE); \
387 if (canceled_using_tree()) \
388 reset_timestamp(&timestamp); \
389 else \
390 touch(&timestamp); \
391 end_using_tree(); \
392 reset_mnemonics(); \
393 gtk_widget_show_all(GTK_WIDGET(menu_item)); \
394 dirty_gtk_menu_release(menu); \
395 tree_count = 0; \
396 return timestamp != 0; \
397 } while (0)
399 gboolean rebuild_directories(GtkMenuItem * root)
401 static GtkMenuItem *menu_item;
402 static DECLARE_TIMESTAMP(timestamp);
403 DirtyGtkMenu *menu;
404 GNode *tree, *child;
405 gchar *prefix, *old_name;
406 tree_item *item;
408 REBUILD_START(_("Directories"), rebuild_directories);
409 add_current_dir_entry(menu);
411 /* Build the menu. */
412 add_file_from_tree(NULL, NULL);
414 item = tree->data;
415 prefix = item->path;
417 if (prefix[0] == '\0' || prefix[1] == '\0') {
418 for (child = g_node_first_child(tree); child; child = child->next) {
419 item = child->data;
420 old_name = item->name;
421 item->name = g_build_filename(prefix, item->name, NULL);
423 make_menu_from_tree_rec(child, menu);
425 g_free(item->name);
426 item->name = old_name;
428 } else
429 make_menu_from_tree_rec(tree, menu);
431 REBUILD_END();
434 gboolean rebuild_images(GtkMenuItem * root)
436 static GtkMenuItem *menu_item;
437 static DECLARE_TIMESTAMP(timestamp);
438 DirtyGtkMenu *menu;
439 GNode *tree;
441 REBUILD_START(_("Images"), rebuild_images);
443 /* Build the menu. */
444 add_file_item(NULL, NULL);
445 g_node_traverse(tree, G_PRE_ORDER, G_TRAVERSE_LEAFS, -1,
446 (GNodeTraverseFunc) add_file_item, menu);
448 REBUILD_END();
451 /* Rebuild both menus. */
452 gboolean rebuild_images_menus(void)
454 return rebuild_directories(NULL) && rebuild_images(NULL);
457 void set_stop_rebuilding_menu(GtkMenuItem * item)
459 cancel_menu_item = item;
460 gtk_widget_set_sensitive(GTK_WIDGET(item), FALSE);
463 gboolean is_rebuilding_a_menu(void)
465 return tree_count != 0;