r805: Window resizing options work again.
[rox-filer.git] / ROX-Filer / src / filer.c
blobdc7ee4ce655481b0aae182dce1a9b1e2aa5a6a22
1 /*
2 * $Id$
4 * ROX-Filer, filer for the ROX desktop project
5 * Copyright (C) 2001, the ROX-Filer team.
7 * This program is free software; you can redistribute it and/or modify it
8 * under the terms of the GNU General Public License as published by the Free
9 * Software Foundation; either version 2 of the License, or (at your option)
10 * any later version.
12 * This program is distributed in the hope that it will be useful, but WITHOUT
13 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
14 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
15 * more details.
17 * You should have received a copy of the GNU General Public License along with
18 * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
19 * Place, Suite 330, Boston, MA 02111-1307 USA
22 /* filer.c - code for handling filer windows */
24 #include "config.h"
26 #include <stdlib.h>
27 #include <stdio.h>
28 #include <string.h>
29 #include <errno.h>
30 #include <time.h>
31 #include <ctype.h>
32 #include <math.h>
33 #include <tree.h>
35 #include <gtk/gtk.h>
36 #include <gdk/gdkx.h>
37 #include <gdk/gdkkeysyms.h>
38 #include "collection.h"
40 #include "global.h"
42 #include "main.h"
43 #include "support.h"
44 #include "gui_support.h"
45 #include "filer.h"
46 #include "pixmaps.h"
47 #include "menu.h"
48 #include "dnd.h"
49 #include "dir.h"
50 #include "run.h"
51 #include "mount.h"
52 #include "type.h"
53 #include "options.h"
54 #include "minibuffer.h"
55 #include "icon.h"
56 #include "toolbar.h"
57 #include "bind.h"
58 #include "appinfo.h"
60 #define PANEL_BORDER 2
62 FilerWindow *window_with_focus = NULL;
63 GList *all_filer_windows = NULL;
65 static FilerWindow *window_with_selection = NULL;
67 /* Item we are about to display a tooltip for */
68 static DirItem *tip_item = NULL;
69 static GtkWidget *tip_widget = NULL;
70 static gint tip_timeout = 0;
71 static time_t tip_time = 0; /* Time tip widget last closed */
73 /* Static prototypes */
74 static void attach(FilerWindow *filer_window);
75 static void detach(FilerWindow *filer_window);
76 static void filer_window_destroyed(GtkWidget *widget,
77 FilerWindow *filer_window);
78 static gint focus_in(GtkWidget *widget,
79 GdkEventFocus *event,
80 FilerWindow *filer_window);
81 static void add_item(FilerWindow *filer_window, DirItem *item);
82 static void update_display(Directory *dir,
83 DirAction action,
84 GPtrArray *items,
85 FilerWindow *filer_window);
86 static void set_scanning_display(FilerWindow *filer_window, gboolean scanning);
87 static gboolean may_rescan(FilerWindow *filer_window, gboolean warning);
88 static gboolean minibuffer_show_cb(FilerWindow *filer_window);
89 static FilerWindow *find_filer_window(char *path, FilerWindow *diff);
90 static void filer_set_title(FilerWindow *filer_window);
91 static gint coll_button_release(GtkWidget *widget,
92 GdkEventButton *event,
93 FilerWindow *filer_window);
94 static gint coll_button_press(GtkWidget *widget,
95 GdkEventButton *event,
96 FilerWindow *filer_window);
97 static gint coll_motion_notify(GtkWidget *widget,
98 GdkEventMotion *event,
99 FilerWindow *filer_window);
100 static void perform_action(FilerWindow *filer_window, GdkEventButton *event);
101 static void filer_add_widgets(FilerWindow *filer_window);
102 static void filer_add_signals(FilerWindow *filer_window);
103 static void filer_tooltip_prime(FilerWindow *filer_window, DirItem *item);
104 static void show_tooltip(guchar *text);
105 static void filer_size_for(FilerWindow *filer_window,
106 int w, int h, int n, gboolean allow_shrink);
108 static void set_unique(guchar *unique);
110 static GdkCursor *busy_cursor = NULL;
111 static GdkCursor *crosshair = NULL;
113 gboolean o_unique_filer_windows = FALSE;
115 void filer_init(void)
117 option_add_int("filer_size_limit", 75, NULL);
118 option_add_int("filer_auto_resize", RESIZE_ALWAYS, NULL);
119 option_add_int("filer_unique_windows", o_unique_filer_windows,
120 set_unique);
122 busy_cursor = gdk_cursor_new(GDK_WATCH);
123 crosshair = gdk_cursor_new(GDK_CROSSHAIR);
126 static void set_unique(guchar *unique)
128 o_unique_filer_windows = atoi(unique);
131 static gboolean if_deleted(gpointer item, gpointer removed)
133 int i = ((GPtrArray *) removed)->len;
134 DirItem **r = (DirItem **) ((GPtrArray *) removed)->pdata;
135 char *leafname = ((DirItem *) item)->leafname;
137 while (i--)
139 if (strcmp(leafname, r[i]->leafname) == 0)
140 return TRUE;
143 return FALSE;
146 static void update_item(FilerWindow *filer_window, DirItem *item)
148 int i;
149 char *leafname = item->leafname;
150 int old_w = filer_window->collection->item_width;
151 int old_h = filer_window->collection->item_height;
152 int w, h;
154 if (leafname[0] == '.' && filer_window->show_hidden == FALSE)
155 return;
157 calc_size(filer_window, item, &w, &h);
158 if (w > old_w || h > old_h)
159 collection_set_item_size(filer_window->collection,
160 MAX(old_w, w),
161 MAX(old_h, h));
163 i = collection_find_item(filer_window->collection, item, dir_item_cmp);
165 if (i >= 0)
166 collection_draw_item(filer_window->collection, i, TRUE);
167 else
168 g_warning("Failed to find '%s'\n", item->leafname);
171 /* Resize the filer window to w x h pixels, plus border (not clamped) */
172 static void filer_window_set_size(FilerWindow *filer_window,
173 int w, int h,
174 gboolean allow_shrink)
176 g_return_if_fail(filer_window != NULL);
178 if (filer_window->scrollbar)
179 w += filer_window->scrollbar->allocation.width;
181 if (o_toolbar != TOOLBAR_NONE)
182 h += filer_window->toolbar_frame->allocation.height;
184 if (GTK_WIDGET_VISIBLE(filer_window->window))
186 gint x, y;
187 GtkRequisition *req = &filer_window->window->requisition;
189 w = MAX(req->width, w);
190 h = MAX(req->height, h);
191 gdk_window_get_position(filer_window->window->window,
192 &x, &y);
193 if (!allow_shrink)
195 gint old_w, old_h;
197 gdk_window_get_size(filer_window->window->window,
198 &old_w, &old_h);
199 w = MAX(w, old_w);
200 h = MAX(h, old_h);
202 if (w == old_w && h == old_h)
203 return;
206 if (x + w > screen_width || y + h > screen_height)
208 if (x + w > screen_width)
209 x = screen_width - w - 4;
210 if (y + h > screen_height)
211 y = screen_height - h - 4;
212 gdk_window_move_resize(filer_window->window->window,
213 x, y, w, h);
215 else
216 gdk_window_resize(filer_window->window->window, w, h);
218 else
219 gtk_window_set_default_size(GTK_WINDOW(filer_window->window),
220 w, h);
223 /* Resize the window to fit the items currently in the Directory.
224 * This should be used once the Directory has been fully scanned, otherwise
225 * the window will appear too small.
226 * When opening a Directory that hasn't yet been scanned use the DIR_NAMES
227 * instead for a quick estimate.
229 * This should all work unless you open a directory, get DIR_NAMES, and then
230 * open it again in other window before the items get added. Oh well.
232 void filer_window_autosize(FilerWindow *filer_window, gboolean allow_shrink)
234 Collection *collection = filer_window->collection;
235 int n;
237 if (!filer_window->directory->have_scanned)
238 return;
240 /* If any items are queued to be added, add them now.
241 * (this adds items that have been stat()d but not added to the
242 * Collection, but not items only in recheck_list).
244 dir_merge_new(filer_window->directory);
246 n = collection->number_of_items;
247 n = MAX(n, 2);
249 #if 0
250 g_print("[ actual size = %d x %d ]\n",
251 collection->item_width,
252 collection->item_height);
253 #endif
255 filer_size_for(filer_window,
256 collection->item_width,
257 collection->item_height,
258 n, allow_shrink);
261 /* Choose a good size for this window, assuming n items of size (w, h) */
262 static void filer_size_for(FilerWindow *filer_window,
263 int w, int h, int n, gboolean allow_shrink)
265 int x;
266 int rows, cols;
267 int max_x, max_rows;
268 const float r = 2.5;
269 int t = 0;
270 int size_limit;
272 size_limit = option_get_int("filer_size_limit");
274 if (o_toolbar != TOOLBAR_NONE)
275 t = filer_window->toolbar_frame->allocation.height;
277 max_x = (size_limit * screen_width) / 100;
278 max_rows = (size_limit * screen_height) / (h * 100);
280 /* Aim for a size where
281 * x = r(y + t + h), (1)
282 * unless that's too wide.
284 * t = toolbar height
285 * r = desired (width / height) ratio
287 * Want to display all items:
288 * (x/w)(y/h) = n
289 * => xy = nwh
290 * => x(x/r - t - h) = nwh (from 1)
291 * => xx - x.rt - hr(1 - nw) = 0
292 * => 2x = rt +/- sqrt(rt.rt + 4hr(nw - 1))
293 * Now,
294 * 4hr(nw - 1) > 0
295 * so
296 * sqrt(rt.rt + ...) > rt
298 * So, the +/- must be +:
300 * => x = (rt + sqrt(rt.rt + 4hr(nw - 1))) / 2
302 * ( + w - 1 to round up)
304 x = (r * t + sqrt(r*r*t*t + 4*h*r * (n*w - 1))) / 2 + w - 1;
306 /* Limit x */
307 if (x > max_x)
308 x = max_x;
310 cols = x / w;
311 cols = MAX(cols, 1);
313 /* Choose rows to display all items given our chosen x.
314 * Don't make the window *too* big!
316 rows = (n + cols - 1) / cols;
317 if (rows > max_rows)
318 rows = max_rows;
320 filer_window_set_size(filer_window,
321 w * MAX(cols, 1),
322 h * (MAX(rows, 1) + 1),
323 allow_shrink);
326 /* Called on a timeout while scanning or when scanning ends
327 * (whichever happens first).
329 static gint open_filer_window(FilerWindow *filer_window)
331 if (filer_window->open_timeout)
333 gtk_timeout_remove(filer_window->open_timeout);
334 filer_window->open_timeout = 0;
337 if (!GTK_WIDGET_VISIBLE(filer_window->window))
339 filer_window_autosize(filer_window, TRUE);
340 gtk_widget_show(filer_window->window);
343 return FALSE;
346 /* The list of names in the directory is known, but the images for each
347 * one are not. Choose a sensible size.
349 static void filer_auto_size_names(FilerWindow *filer_window, GPtrArray *names)
351 int w, h, n;
353 if (GTK_WIDGET_VISIBLE(filer_window->window))
354 if (option_get_int("filer_auto_resize") != RESIZE_ALWAYS)
355 return;
357 display_guess_size(filer_window, names, &w, &h, &n);
359 filer_size_for(filer_window, w, h, n, TRUE);
362 static void update_display(Directory *dir,
363 DirAction action,
364 GPtrArray *items,
365 FilerWindow *filer_window)
367 int old_num;
368 int i;
369 int cursor = filer_window->collection->cursor_item;
370 char *as;
371 Collection *collection = filer_window->collection;
373 switch (action)
375 case DIR_ADD:
376 as = filer_window->auto_select;
378 old_num = collection->number_of_items;
379 for (i = 0; i < items->len; i++)
381 DirItem *item = (DirItem *) items->pdata[i];
383 add_item(filer_window, item);
385 if (cursor != -1 || !as)
386 continue;
388 if (strcmp(as, item->leafname) != 0)
389 continue;
391 cursor = collection->number_of_items - 1;
392 if (filer_window->had_cursor)
394 collection_set_cursor_item(collection,
395 cursor);
396 filer_window->mini_cursor_base = cursor;
397 filer_window->had_cursor = FALSE;
399 else
400 collection_wink_item(collection,
401 cursor);
404 if (old_num != collection->number_of_items)
405 collection_qsort(filer_window->collection,
406 filer_window->sort_fn);
407 break;
408 case DIR_REMOVE:
409 collection_delete_if(filer_window->collection,
410 if_deleted,
411 items);
412 break;
413 case DIR_START_SCAN:
414 set_scanning_display(filer_window, TRUE);
415 toolbar_update_info(filer_window);
416 break;
417 case DIR_END_SCAN:
418 if (filer_window->window->window)
419 gdk_window_set_cursor(
420 filer_window->window->window,
421 NULL);
422 shrink_grid(filer_window);
423 if (filer_window->had_cursor &&
424 collection->cursor_item == -1)
426 collection_set_cursor_item(collection, 0);
427 filer_window->had_cursor = FALSE;
429 set_scanning_display(filer_window, FALSE);
430 toolbar_update_info(filer_window);
431 open_filer_window(filer_window);
432 break;
433 case DIR_UPDATE:
434 for (i = 0; i < items->len; i++)
436 DirItem *item = (DirItem *) items->pdata[i];
438 update_item(filer_window, item);
440 collection_qsort(filer_window->collection,
441 filer_window->sort_fn);
442 break;
443 case DIR_NAMES:
444 filer_auto_size_names(filer_window, items);
445 break;
449 static void attach(FilerWindow *filer_window)
451 gdk_window_set_cursor(filer_window->window->window, busy_cursor);
452 collection_clear(filer_window->collection);
453 filer_window->scanning = TRUE;
454 dir_attach(filer_window->directory, (DirCallback) update_display,
455 filer_window);
456 filer_set_title(filer_window);
459 static void detach(FilerWindow *filer_window)
461 g_return_if_fail(filer_window->directory != NULL);
463 dir_detach(filer_window->directory,
464 (DirCallback) update_display, filer_window);
465 g_fscache_data_unref(dir_cache, filer_window->directory);
466 filer_window->directory = NULL;
469 static void filer_window_destroyed(GtkWidget *widget,
470 FilerWindow *filer_window)
472 all_filer_windows = g_list_remove(all_filer_windows, filer_window);
474 if (window_with_selection == filer_window)
475 window_with_selection = NULL;
477 if (window_with_focus == filer_window)
479 if (popup_menu)
480 gtk_menu_popdown(GTK_MENU(popup_menu));
481 window_with_focus = NULL;
484 if (filer_window->directory)
485 detach(filer_window);
487 if (filer_window->open_timeout)
489 gtk_timeout_remove(filer_window->open_timeout);
490 filer_window->open_timeout = 0;
493 g_free(filer_window->auto_select);
494 g_free(filer_window->path);
495 g_free(filer_window);
497 if (--number_of_windows < 1)
498 gtk_main_quit();
501 /* Add a single object to a directory display */
502 static void add_item(FilerWindow *filer_window, DirItem *item)
504 char *leafname = item->leafname;
505 int old_w = filer_window->collection->item_width;
506 int old_h = filer_window->collection->item_height;
507 int w, h;
509 if (leafname[0] == '.')
511 if (filer_window->show_hidden == FALSE || leafname[1] == '\0'
512 || (leafname[1] == '.' && leafname[2] == '\0'))
513 return;
516 calc_size(filer_window, item, &w, &h);
517 if (w > old_w || h > old_h)
518 collection_set_item_size(filer_window->collection,
519 MAX(old_w, w),
520 MAX(old_h, h));
522 collection_insert(filer_window->collection, item);
525 /* Returns TRUE iff the directory still exists. */
526 static gboolean may_rescan(FilerWindow *filer_window, gboolean warning)
528 Directory *dir;
530 g_return_val_if_fail(filer_window != NULL, FALSE);
532 /* We do a fresh lookup (rather than update) because the inode may
533 * have changed.
535 dir = g_fscache_lookup(dir_cache, filer_window->path);
536 if (!dir)
538 if (warning)
539 delayed_error(PROJECT, _("Directory missing/deleted"));
540 gtk_widget_destroy(filer_window->window);
541 return FALSE;
543 if (dir == filer_window->directory)
544 g_fscache_data_unref(dir_cache, dir);
545 else
547 detach(filer_window);
548 filer_window->directory = dir;
549 attach(filer_window);
552 return TRUE;
555 /* Another app has grabbed the selection */
556 static gint collection_lose_selection(GtkWidget *widget,
557 GdkEventSelection *event)
559 if (window_with_selection &&
560 window_with_selection->collection == COLLECTION(widget))
562 FilerWindow *filer_window = window_with_selection;
563 window_with_selection = NULL;
564 collection_clear_selection(filer_window->collection);
567 return FALSE;
570 /* Someone wants us to send them the selection */
571 static void selection_get(GtkWidget *widget,
572 GtkSelectionData *selection_data,
573 guint info,
574 guint time,
575 gpointer data)
577 GString *reply, *header;
578 FilerWindow *filer_window;
579 int i;
580 Collection *collection;
582 filer_window = gtk_object_get_data(GTK_OBJECT(widget), "filer_window");
584 reply = g_string_new(NULL);
585 header = g_string_new(NULL);
587 switch (info)
589 case TARGET_STRING:
590 g_string_sprintf(header, " %s",
591 make_path(filer_window->path, "")->str);
592 break;
593 case TARGET_URI_LIST:
594 g_string_sprintf(header, " file://%s%s",
595 our_host_name(),
596 make_path(filer_window->path, "")->str);
597 break;
600 collection = filer_window->collection;
601 for (i = 0; i < collection->number_of_items; i++)
603 if (collection->items[i].selected)
605 DirItem *item =
606 (DirItem *) collection->items[i].data;
608 g_string_append(reply, header->str);
609 g_string_append(reply, item->leafname);
612 /* This works, but I don't think I like it... */
613 /* g_string_append_c(reply, ' '); */
615 gtk_selection_data_set(selection_data, xa_string,
616 8, reply->str + 1, reply->len - 1);
617 g_string_free(reply, TRUE);
618 g_string_free(header, TRUE);
621 /* No items are now selected. This might be because another app claimed
622 * the selection or because the user unselected all the items.
624 static void lose_selection(Collection *collection,
625 guint time,
626 gpointer user_data)
628 FilerWindow *filer_window = (FilerWindow *) user_data;
630 if (window_with_selection == filer_window)
632 window_with_selection = NULL;
633 gtk_selection_owner_set(NULL,
634 GDK_SELECTION_PRIMARY,
635 time);
639 static void gain_selection(Collection *collection,
640 guint time,
641 gpointer user_data)
643 FilerWindow *filer_window = (FilerWindow *) user_data;
645 if (gtk_selection_owner_set(GTK_WIDGET(collection),
646 GDK_SELECTION_PRIMARY,
647 time))
649 window_with_selection = filer_window;
651 else
652 collection_clear_selection(filer_window->collection);
655 /* Open the item (or add it to the shell command minibuffer) */
656 void filer_openitem(FilerWindow *filer_window, int item_number, OpenFlags flags)
658 gboolean shift = (flags & OPEN_SHIFT) != 0;
659 gboolean close_mini = flags & OPEN_FROM_MINI;
660 gboolean close_window = (flags & OPEN_CLOSE_WINDOW) != 0;
661 GtkWidget *widget;
662 DirItem *item = (DirItem *)
663 filer_window->collection->items[item_number].data;
664 guchar *full_path;
665 gboolean wink = TRUE;
666 Directory *old_dir;
668 widget = filer_window->window;
669 if (filer_window->mini_type == MINI_SHELL)
671 minibuffer_add(filer_window, item->leafname);
672 return;
675 if (item->base_type == TYPE_DIRECTORY)
677 /* Never close a filer window when opening a directory
678 * (click on a dir or click on an app with shift).
680 if (shift || !(item->flags & ITEM_FLAG_APPDIR))
681 close_window = FALSE;
684 full_path = make_path(filer_window->path, item->leafname)->str;
685 if (shift && (item->flags & ITEM_FLAG_SYMLINK))
686 wink = FALSE;
688 old_dir = filer_window->directory;
689 if (run_diritem(full_path, item,
690 flags & OPEN_SAME_WINDOW ? filer_window : NULL,
691 shift))
693 if (old_dir != filer_window->directory)
694 return;
696 if (close_window)
697 gtk_widget_destroy(filer_window->window);
698 else
700 if (wink)
701 collection_wink_item(filer_window->collection,
702 item_number);
703 if (close_mini)
704 minibuffer_hide(filer_window);
709 static gint pointer_in(GtkWidget *widget,
710 GdkEventCrossing *event,
711 FilerWindow *filer_window)
713 may_rescan(filer_window, TRUE);
714 return FALSE;
717 static gint pointer_out(GtkWidget *widget,
718 GdkEventCrossing *event,
719 FilerWindow *filer_window)
721 filer_tooltip_prime(NULL, NULL);
722 return FALSE;
725 static gint focus_in(GtkWidget *widget,
726 GdkEventFocus *event,
727 FilerWindow *filer_window)
729 window_with_focus = filer_window;
731 return FALSE;
734 /* Move the cursor to the next selected item in direction 'dir'
735 * (+1 or -1).
737 static void next_selected(FilerWindow *filer_window, int dir)
739 Collection *collection = filer_window->collection;
740 int to_check = collection->number_of_items;
741 int item = collection->cursor_item;
743 g_return_if_fail(dir == 1 || dir == -1);
745 if (to_check > 0 && item == -1)
747 /* Cursor not currently on */
748 if (dir == 1)
749 item = 0;
750 else
751 item = collection->number_of_items - 1;
753 if (collection->items[item].selected)
754 goto found;
757 while (--to_check > 0)
759 item += dir;
761 if (item >= collection->number_of_items)
762 item = 0;
763 else if (item < 0)
764 item = collection->number_of_items - 1;
766 if (collection->items[item].selected)
767 goto found;
770 gdk_beep();
771 return;
772 found:
773 collection_set_cursor_item(collection, item);
776 static void return_pressed(FilerWindow *filer_window, GdkEventKey *event)
778 Collection *collection = filer_window->collection;
779 int item = collection->cursor_item;
780 TargetFunc cb = filer_window->target_cb;
781 gpointer data = filer_window->target_data;
782 OpenFlags flags = OPEN_SAME_WINDOW;
784 filer_target_mode(filer_window, NULL, NULL, NULL);
785 if (item < 0 || item >= collection->number_of_items)
786 return;
788 if (cb)
790 cb(filer_window, item, data);
791 return;
794 if (event->state & GDK_SHIFT_MASK)
795 flags |= OPEN_SHIFT;
797 filer_openitem(filer_window, item, flags);
800 /* Handle keys that can't be bound with the menu */
801 static gint key_press_event(GtkWidget *widget,
802 GdkEventKey *event,
803 FilerWindow *filer_window)
805 switch (event->keyval)
807 case GDK_Escape:
808 filer_target_mode(filer_window, NULL, NULL, NULL);
809 return FALSE;
810 case GDK_Return:
811 return_pressed(filer_window, event);
812 break;
813 case GDK_ISO_Left_Tab:
814 next_selected(filer_window, -1);
815 break;
816 case GDK_Tab:
817 next_selected(filer_window, 1);
818 break;
819 case GDK_BackSpace:
820 change_to_parent(filer_window);
821 break;
822 case GDK_backslash:
823 filer_tooltip_prime(NULL, NULL);
824 show_filer_menu(filer_window, (GdkEvent *) event,
825 filer_window->collection->cursor_item);
826 break;
827 default:
828 return FALSE;
831 #ifndef GTK2
832 gtk_signal_emit_stop_by_name(GTK_OBJECT(widget), "key_press_event");
833 #endif
834 return TRUE;
837 void change_to_parent(FilerWindow *filer_window)
839 char *copy;
840 char *slash;
842 if (filer_window->path[0] == '/' && filer_window->path[1] == '\0')
843 return; /* Already in the root */
845 copy = g_strdup(filer_window->path);
846 slash = strrchr(copy, '/');
848 if (slash)
850 *slash = '\0';
851 filer_change_to(filer_window,
852 *copy ? copy : "/",
853 slash + 1);
855 else
856 g_warning("No / in directory path!\n");
858 g_free(copy);
862 /* Make filer_window display path. When finished, highlight item 'from', or
863 * the first item if from is NULL. If there is currently no cursor then
864 * simply wink 'from' (if not NULL).
866 void filer_change_to(FilerWindow *filer_window, char *path, char *from)
868 char *from_dup;
869 char *real_path;
870 Directory *new_dir;
872 g_return_if_fail(filer_window != NULL);
874 filer_tooltip_prime(NULL, NULL);
876 real_path = pathdup(path);
877 new_dir = g_fscache_lookup(dir_cache, real_path);
879 if (!new_dir)
881 char *error;
883 error = g_strdup_printf(_("Directory '%s' is not accessible"),
884 real_path);
885 g_free(real_path);
886 delayed_error(PROJECT, error);
887 g_free(error);
888 return;
891 if (o_unique_filer_windows)
893 FilerWindow *fw;
895 fw = find_filer_window(real_path, filer_window);
896 if (fw)
897 gtk_widget_destroy(fw->window);
900 from_dup = from && *from ? g_strdup(from) : NULL;
902 detach(filer_window);
903 g_free(filer_window->path);
904 filer_window->path = real_path;
906 filer_window->directory = new_dir;
908 g_free(filer_window->auto_select);
909 filer_window->had_cursor = filer_window->collection->cursor_item != -1
910 || filer_window->had_cursor;
911 filer_window->auto_select = from_dup;
913 filer_set_title(filer_window);
914 collection_set_cursor_item(filer_window->collection, -1);
916 attach(filer_window);
917 if (option_get_int("filer_auto_resize") == RESIZE_ALWAYS)
918 filer_window_autosize(filer_window, TRUE);
920 if (filer_window->mini_type == MINI_PATH)
921 gtk_idle_add((GtkFunction) minibuffer_show_cb,
922 filer_window);
925 void filer_open_parent(FilerWindow *filer_window)
927 char *copy;
928 char *slash;
930 if (filer_window->path[0] == '/' && filer_window->path[1] == '\0')
931 return; /* Already in the root */
933 copy = g_strdup(filer_window->path);
934 slash = strrchr(copy, '/');
936 if (slash)
938 *slash = '\0';
939 filer_opendir(*copy ? copy : "/");
941 else
942 g_warning("No / in directory path!\n");
944 g_free(copy);
947 /* Returns a list containing the full pathname of every selected item.
948 * You must g_free() each item in the list.
950 GList *filer_selected_items(FilerWindow *filer_window)
952 Collection *collection = filer_window->collection;
953 GList *retval = NULL;
954 guchar *dir = filer_window->path;
955 int i;
957 for (i = 0; i < collection->number_of_items; i++)
959 if (collection->items[i].selected)
961 DirItem *item = (DirItem *) collection->items[i].data;
963 retval = g_list_prepend(retval,
964 g_strdup(make_path(dir, item->leafname)->str));
968 return g_list_reverse(retval);
971 int selected_item_number(Collection *collection)
973 int i;
975 g_return_val_if_fail(collection != NULL, -1);
976 g_return_val_if_fail(IS_COLLECTION(collection), -1);
977 g_return_val_if_fail(collection->number_selected == 1, -1);
979 for (i = 0; i < collection->number_of_items; i++)
980 if (collection->items[i].selected)
981 return i;
983 g_warning("selected_item: number_selected is wrong\n");
985 return -1;
988 DirItem *selected_item(Collection *collection)
990 int item;
992 item = selected_item_number(collection);
994 if (item > -1)
995 return (DirItem *) collection->items[item].data;
996 return NULL;
999 /* Append all the URIs in the selection to the string */
1000 static void create_uri_list(FilerWindow *filer_window, GString *string)
1002 Collection *collection = filer_window->collection;
1003 GString *leader;
1004 int i, num_selected;
1006 leader = g_string_new("file://");
1007 g_string_append(leader, our_host_name());
1008 g_string_append(leader, filer_window->path);
1009 if (leader->str[leader->len - 1] != '/')
1010 g_string_append_c(leader, '/');
1012 num_selected = collection->number_selected;
1014 for (i = 0; num_selected > 0; i++)
1016 if (collection->items[i].selected)
1018 DirItem *item = (DirItem *) collection->items[i].data;
1020 g_string_append(string, leader->str);
1021 g_string_append(string, item->leafname);
1022 g_string_append(string, "\r\n");
1023 num_selected--;
1027 g_string_free(leader, TRUE);
1030 /* Creates and shows a new filer window.
1031 * Returns the new filer window, or NULL on error.
1033 FilerWindow *filer_opendir(char *path)
1035 FilerWindow *filer_window;
1036 char *real_path;
1037 int i;
1039 /* Get the real pathname of the directory and copy it */
1040 real_path = pathdup(path);
1042 filer_window = g_new(FilerWindow, 1);
1043 filer_window->minibuffer = NULL;
1044 filer_window->minibuffer_label = NULL;
1045 filer_window->minibuffer_area = NULL;
1046 filer_window->temp_show_hidden = FALSE;
1047 filer_window->path = real_path;
1048 filer_window->scanning = FALSE;
1049 filer_window->had_cursor = FALSE;
1050 filer_window->auto_select = NULL;
1051 filer_window->toolbar_text = NULL;
1052 filer_window->target_cb = NULL;
1053 filer_window->mini_type = MINI_NONE;
1055 /* Finds the entry for this directory in the dir cache, creating
1056 * a new one if needed. This does not cause a scan to start,
1057 * so if a new entry is created then it will be empty.
1059 filer_window->directory = g_fscache_lookup(dir_cache,
1060 filer_window->path);
1061 if (!filer_window->directory)
1063 char *error;
1065 error = g_strdup_printf(_("Directory '%s' not found."), path);
1066 delayed_error(PROJECT, error);
1067 g_free(error);
1068 g_free(filer_window->path);
1069 g_free(filer_window);
1070 return NULL;
1073 filer_window->show_hidden = FALSE;
1074 filer_window->temp_item_selected = FALSE;
1076 i = option_get_int("display_sort_by");
1077 filer_window->sort_fn = i == 0 ? sort_by_name :
1078 i == 1 ? sort_by_type :
1079 i == 2 ? sort_by_date :
1080 sort_by_size;
1082 filer_window->flags = (FilerFlags) 0;
1083 filer_window->details_type = DETAILS_SUMMARY;
1084 filer_window->display_style = UNKNOWN_STYLE;
1086 /* Add all the user-interface elements & realise */
1087 filer_add_widgets(filer_window);
1089 /* Connect to all the signal handlers */
1090 filer_add_signals(filer_window);
1092 display_set_layout(filer_window,
1093 option_get_int("display_size"),
1094 option_get_int("display_details"));
1096 /* Open the window after a timeout, or when scanning stops.
1097 * Do this before attaching, because attach() might tell us to
1098 * stop scanning (if a scan isn't needed).
1100 filer_window->open_timeout = gtk_timeout_add(500,
1101 (GtkFunction) open_filer_window,
1102 filer_window);
1104 /* The collection is created empty and then attach() is called, which
1105 * links the filer window to the entry in the directory cache we
1106 * looked up / created above.
1108 * The attach() function will immediately callback to the filer window
1109 * to deliver a list of all known entries in the directory (so,
1110 * collection->number_of_items may be valid after the call to
1111 * attach() returns).
1113 * BUT, if the directory was not in the cache (because it hadn't been
1114 * opened it before) then the cached dir will be empty and nothing gets
1115 * added until a while later when some entries are actually available.
1118 attach(filer_window);
1120 /* Make the window visible. Update number_of_windows BEFORE destroying
1121 * the old window!
1123 number_of_windows++;
1125 /* If the user doesn't want duplicate windows then check
1126 * for an existing one and close it if found.
1129 if (o_unique_filer_windows)
1131 FilerWindow *fw;
1133 fw = find_filer_window(filer_window->path, NULL);
1135 if (fw)
1136 gtk_widget_destroy(fw->window);
1139 all_filer_windows = g_list_prepend(all_filer_windows, filer_window);
1141 return filer_window;
1144 /* This adds all the widgets to a new filer window. It is in a separate
1145 * function because filer_opendir() was getting too long...
1147 static void filer_add_widgets(FilerWindow *filer_window)
1149 GtkWidget *hbox, *vbox, *collection;
1151 /* Create the top-level window widget */
1152 filer_window->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
1153 filer_set_title(filer_window);
1155 /* The collection is the area that actually displays the files */
1156 collection = collection_new(NULL);
1158 gtk_object_set_data(GTK_OBJECT(collection),
1159 "filer_window", filer_window);
1160 filer_window->collection = COLLECTION(collection);
1162 /* Scrollbar on the right, everything else on the left */
1163 hbox = gtk_hbox_new(FALSE, 0);
1164 gtk_container_add(GTK_CONTAINER(filer_window->window), hbox);
1166 vbox = gtk_vbox_new(FALSE, 0);
1167 gtk_box_pack_start(GTK_BOX(hbox), vbox, TRUE, TRUE, 0);
1169 /* If there's a message that should go at the top of every
1170 * window (eg 'Running as root'), add it here.
1172 if (show_user_message)
1174 GtkWidget *label;
1176 label = gtk_label_new(show_user_message);
1177 gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, TRUE, 0);
1178 gtk_widget_show(label);
1181 /* Create a frame for the toolbar, but don't show it unless we actually
1182 * have a toolbar.
1183 * (allows us to change the toolbar later)
1185 filer_window->toolbar_frame = gtk_frame_new(NULL);
1186 gtk_frame_set_shadow_type(GTK_FRAME(filer_window->toolbar_frame),
1187 GTK_SHADOW_OUT);
1188 gtk_box_pack_start(GTK_BOX(vbox),
1189 filer_window->toolbar_frame, FALSE, TRUE, 0);
1191 /* If we want a toolbar, create it and put it in the frame */
1192 if (o_toolbar != TOOLBAR_NONE)
1194 GtkWidget *toolbar;
1196 toolbar = toolbar_new(filer_window);
1197 gtk_container_add(GTK_CONTAINER(filer_window->toolbar_frame),
1198 toolbar);
1199 gtk_widget_show_all(filer_window->toolbar_frame);
1202 /* Now add the area for displaying the files... */
1203 gtk_box_pack_start(GTK_BOX(vbox), collection, TRUE, TRUE, 0);
1205 /* And the minibuffer (hidden by default)... */
1206 create_minibuffer(filer_window);
1207 gtk_box_pack_start(GTK_BOX(vbox), filer_window->minibuffer_area,
1208 FALSE, TRUE, 0);
1210 /* Put the scrollbar on the left of everything else... */
1211 filer_window->scrollbar =
1212 gtk_vscrollbar_new(COLLECTION(collection)->vadj);
1213 gtk_box_pack_start(GTK_BOX(hbox),
1214 filer_window->scrollbar, FALSE, TRUE, 0);
1216 /* Connect the menu's accelerator group to the window */
1217 gtk_accel_group_attach(filer_keys, GTK_OBJECT(filer_window->window));
1219 gtk_window_set_focus(GTK_WINDOW(filer_window->window), collection);
1221 gtk_widget_show(hbox);
1222 gtk_widget_show(vbox);
1223 gtk_widget_show(filer_window->scrollbar);
1224 gtk_widget_show(collection);
1226 gtk_widget_realize(filer_window->window);
1228 filer_window_set_size(filer_window, 4, 4, TRUE);
1231 static void filer_add_signals(FilerWindow *filer_window)
1233 GtkObject *collection = GTK_OBJECT(filer_window->collection);
1234 GtkTargetEntry target_table[] =
1236 {"text/uri-list", 0, TARGET_URI_LIST},
1237 {"STRING", 0, TARGET_STRING},
1240 /* Events on the top-level window */
1241 gtk_widget_add_events(filer_window->window, GDK_ENTER_NOTIFY);
1242 gtk_signal_connect(GTK_OBJECT(filer_window->window),
1243 "enter-notify-event",
1244 GTK_SIGNAL_FUNC(pointer_in), filer_window);
1245 gtk_signal_connect(GTK_OBJECT(filer_window->window),
1246 "leave-notify-event",
1247 GTK_SIGNAL_FUNC(pointer_out), filer_window);
1248 gtk_signal_connect(GTK_OBJECT(filer_window->window), "focus_in_event",
1249 GTK_SIGNAL_FUNC(focus_in), filer_window);
1250 gtk_signal_connect(GTK_OBJECT(filer_window->window), "destroy",
1251 GTK_SIGNAL_FUNC(filer_window_destroyed), filer_window);
1253 /* Events on the collection widget */
1254 gtk_widget_set_events(GTK_WIDGET(collection),
1255 GDK_BUTTON1_MOTION_MASK | GDK_BUTTON2_MOTION_MASK |
1256 GDK_BUTTON3_MOTION_MASK | GDK_POINTER_MOTION_MASK |
1257 GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK);
1259 gtk_signal_connect(collection, "gain_selection",
1260 GTK_SIGNAL_FUNC(gain_selection), filer_window);
1261 gtk_signal_connect(collection, "lose_selection",
1262 GTK_SIGNAL_FUNC(lose_selection), filer_window);
1263 gtk_signal_connect(collection, "selection_clear_event",
1264 GTK_SIGNAL_FUNC(collection_lose_selection), NULL);
1265 gtk_signal_connect(collection, "selection_get",
1266 GTK_SIGNAL_FUNC(selection_get), NULL);
1267 gtk_selection_add_targets(GTK_WIDGET(collection), GDK_SELECTION_PRIMARY,
1268 target_table,
1269 sizeof(target_table) / sizeof(*target_table));
1271 gtk_signal_connect(collection, "key_press_event",
1272 GTK_SIGNAL_FUNC(key_press_event), filer_window);
1273 gtk_signal_connect(collection, "button-release-event",
1274 GTK_SIGNAL_FUNC(coll_button_release), filer_window);
1275 gtk_signal_connect(collection, "button-press-event",
1276 GTK_SIGNAL_FUNC(coll_button_press), filer_window);
1277 gtk_signal_connect(collection, "motion-notify-event",
1278 GTK_SIGNAL_FUNC(coll_motion_notify), filer_window);
1280 /* Drag and drop events */
1281 gtk_signal_connect(collection, "drag_data_get",
1282 GTK_SIGNAL_FUNC(drag_data_get), NULL);
1283 drag_set_dest(filer_window);
1286 static gint clear_scanning_display(FilerWindow *filer_window)
1288 if (filer_exists(filer_window))
1289 filer_set_title(filer_window);
1290 return FALSE;
1293 static void set_scanning_display(FilerWindow *filer_window, gboolean scanning)
1295 if (scanning == filer_window->scanning)
1296 return;
1297 filer_window->scanning = scanning;
1299 if (scanning)
1300 filer_set_title(filer_window);
1301 else
1302 gtk_timeout_add(300, (GtkFunction) clear_scanning_display,
1303 filer_window);
1306 /* Note that filer_window may not exist after this call. */
1307 void filer_update_dir(FilerWindow *filer_window, gboolean warning)
1309 if (may_rescan(filer_window, warning))
1310 dir_update(filer_window->directory, filer_window->path);
1313 /* Refresh the various caches even if we don't think we need to */
1314 void full_refresh(void)
1316 mount_update(TRUE);
1319 /* See whether a filer window with a given path already exists
1320 * and is different from diff.
1322 static FilerWindow *find_filer_window(char *path, FilerWindow *diff)
1324 GList *next = all_filer_windows;
1326 while (next)
1328 FilerWindow *filer_window = (FilerWindow *) next->data;
1330 if (filer_window != diff &&
1331 strcmp(path, filer_window->path) == 0)
1333 return filer_window;
1336 next = next->next;
1339 return NULL;
1342 /* This path has been mounted/umounted/deleted some files - update all dirs */
1343 void filer_check_mounted(char *path)
1345 GList *next = all_filer_windows;
1346 char *slash;
1347 int len;
1349 len = strlen(path);
1351 while (next)
1353 FilerWindow *filer_window = (FilerWindow *) next->data;
1355 next = next->next;
1357 if (strncmp(path, filer_window->path, len) == 0)
1359 char s = filer_window->path[len];
1361 if (s == '/' || s == '\0')
1362 filer_update_dir(filer_window, FALSE);
1366 slash = strrchr(path, '/');
1367 if (slash && slash != path)
1369 guchar *parent;
1371 parent = g_strndup(path, slash - path);
1373 refresh_dirs(parent);
1375 g_free(parent);
1377 else
1378 refresh_dirs("/");
1380 icons_may_update(path);
1383 /* Like minibuffer_show(), except that:
1384 * - It returns FALSE (to be used from an idle callback)
1385 * - It checks that the filer window still exists.
1387 static gboolean minibuffer_show_cb(FilerWindow *filer_window)
1389 if (filer_exists(filer_window))
1390 minibuffer_show(filer_window, MINI_PATH);
1391 return FALSE;
1394 /* TRUE iff filer_window points to an existing FilerWindow
1395 * structure.
1397 gboolean filer_exists(FilerWindow *filer_window)
1399 GList *next;
1401 for (next = all_filer_windows; next; next = next->next)
1403 FilerWindow *fw = (FilerWindow *) next->data;
1405 if (fw == filer_window)
1406 return TRUE;
1409 return FALSE;
1412 static void filer_set_title(FilerWindow *filer_window)
1414 guchar *title = NULL;
1415 guchar *scanning = filer_window->scanning ? _(" (Scanning)") : "";
1417 if (home_dir_len > 1 &&
1418 strncmp(filer_window->path, home_dir, home_dir_len) == 0)
1420 guchar sep = filer_window->path[home_dir_len];
1422 if (sep == '\0' || sep == '/')
1423 title = g_strconcat("~",
1424 filer_window->path + home_dir_len,
1425 scanning,
1426 NULL);
1429 if (!title)
1430 title = g_strconcat(filer_window->path, scanning, NULL);
1432 gtk_window_set_title(GTK_WINDOW(filer_window->window), title);
1433 g_free(title);
1436 /* Reconnect to the same directory (used when the Show Hidden option is
1437 * toggled).
1439 void filer_detach_rescan(FilerWindow *filer_window)
1441 Directory *dir = filer_window->directory;
1443 g_fscache_data_ref(dir_cache, dir);
1444 detach(filer_window);
1445 filer_window->directory = dir;
1446 attach(filer_window);
1449 static gint coll_button_release(GtkWidget *widget,
1450 GdkEventButton *event,
1451 FilerWindow *filer_window)
1453 if (dnd_motion_release(event))
1455 if (motion_buttons_pressed == 0 &&
1456 filer_window->collection->lasso_box)
1458 collection_end_lasso(filer_window->collection,
1459 event->button == 1 ? GDK_SET : GDK_INVERT);
1461 return FALSE;
1464 perform_action(filer_window, event);
1466 return FALSE;
1469 static void perform_action(FilerWindow *filer_window, GdkEventButton *event)
1471 Collection *collection = filer_window->collection;
1472 DirItem *dir_item;
1473 int item;
1474 BindAction action;
1475 gboolean press = event->type == GDK_BUTTON_PRESS;
1476 gboolean selected = FALSE;
1477 OpenFlags flags = 0;
1479 if (event->button > 3)
1480 return;
1482 item = collection_get_item(collection, event->x, event->y);
1484 if (filer_window->target_cb)
1486 dnd_motion_ungrab();
1487 if (item != -1 && press && event->button == 1)
1488 filer_window->target_cb(filer_window, item,
1489 filer_window->target_data);
1490 filer_target_mode(filer_window, NULL, NULL, NULL);
1492 return;
1495 action = bind_lookup_bev(
1496 item == -1 ? BIND_DIRECTORY : BIND_DIRECTORY_ICON,
1497 event);
1499 if (item != -1)
1501 dir_item = (DirItem *) collection->items[item].data;
1502 selected = collection->items[item].selected;
1504 else
1505 dir_item = NULL;
1507 switch (action)
1509 case ACT_CLEAR_SELECTION:
1510 collection_clear_selection(collection);
1511 break;
1512 case ACT_TOGGLE_SELECTED:
1513 collection_toggle_item(collection, item);
1514 break;
1515 case ACT_SELECT_EXCL:
1516 collection_clear_except(collection, item);
1517 break;
1518 case ACT_EDIT_ITEM:
1519 flags |= OPEN_SHIFT;
1520 /* (no break) */
1521 case ACT_OPEN_ITEM:
1522 if (event->button != 1)
1523 flags |= OPEN_CLOSE_WINDOW;
1524 else
1525 flags |= OPEN_SAME_WINDOW;
1526 if (o_new_window_on_1)
1527 flags ^= OPEN_SAME_WINDOW;
1528 if (event->type == GDK_2BUTTON_PRESS)
1529 collection_unselect_item(collection, item);
1530 dnd_motion_ungrab();
1531 filer_openitem(filer_window, item, flags);
1532 break;
1533 case ACT_POPUP_MENU:
1534 dnd_motion_ungrab();
1535 filer_tooltip_prime(NULL, NULL);
1536 show_filer_menu(filer_window, (GdkEvent *) event, item);
1537 break;
1538 case ACT_PRIME_AND_SELECT:
1539 if (!selected)
1540 collection_clear_except(collection, item);
1541 dnd_motion_start(MOTION_READY_FOR_DND);
1542 break;
1543 case ACT_PRIME_AND_TOGGLE:
1544 collection_toggle_item(collection, item);
1545 dnd_motion_start(MOTION_READY_FOR_DND);
1546 break;
1547 case ACT_PRIME_FOR_DND:
1548 dnd_motion_start(MOTION_READY_FOR_DND);
1549 break;
1550 case ACT_IGNORE:
1551 if (press && event->button < 4)
1553 if (item)
1554 collection_wink_item(collection, item);
1555 dnd_motion_start(MOTION_NONE);
1557 break;
1558 case ACT_LASSO_CLEAR:
1559 collection_clear_selection(collection);
1560 /* (no break) */
1561 case ACT_LASSO_MODIFY:
1562 collection_lasso_box(collection, event->x, event->y);
1563 break;
1564 case ACT_RESIZE:
1565 filer_window_autosize(filer_window, TRUE);
1566 break;
1567 default:
1568 g_warning("Unsupported action : %d\n", action);
1569 break;
1573 static gint coll_button_press(GtkWidget *widget,
1574 GdkEventButton *event,
1575 FilerWindow *filer_window)
1577 collection_set_cursor_item(filer_window->collection, -1);
1579 if (dnd_motion_press(widget, event))
1580 perform_action(filer_window, event);
1582 return FALSE;
1585 static gint coll_motion_notify(GtkWidget *widget,
1586 GdkEventMotion *event,
1587 FilerWindow *filer_window)
1589 Collection *collection = filer_window->collection;
1590 int i;
1592 i = collection_get_item(collection, event->x, event->y);
1593 if (i == -1)
1594 filer_tooltip_prime(NULL, NULL);
1595 else
1596 filer_tooltip_prime(filer_window,
1597 (DirItem *) collection->items[i].data);
1599 if (motion_state != MOTION_READY_FOR_DND)
1600 return FALSE;
1602 if (!dnd_motion_moved(event))
1603 return FALSE;
1605 i = collection_get_item(collection,
1606 event->x - (event->x_root - drag_start_x),
1607 event->y - (event->y_root - drag_start_y));
1608 if (i == -1)
1609 return FALSE;
1611 collection_wink_item(collection, -1);
1613 if (!collection->items[i].selected)
1615 if (event->state & GDK_BUTTON1_MASK)
1617 /* Select just this one */
1618 collection_clear_except(collection, i);
1619 filer_window->temp_item_selected = TRUE;
1621 else
1623 if (collection->number_selected == 0)
1624 filer_window->temp_item_selected = TRUE;
1625 collection_select_item(collection, i);
1629 g_return_val_if_fail(collection->number_selected > 0, TRUE);
1631 if (collection->number_selected == 1)
1633 DirItem *item = (DirItem *) collection->items[i].data;
1635 drag_one_item(widget, event,
1636 make_path(filer_window->path, item->leafname)->str,
1637 item);
1639 else
1641 GString *uris;
1643 uris = g_string_new(NULL);
1644 create_uri_list(filer_window, uris);
1645 drag_selection(widget, event, uris->str);
1646 g_string_free(uris, TRUE);
1649 return FALSE;
1652 /* Puts the filer window into target mode. When an item is chosen,
1653 * fn(filer_window, item, data) is called. 'reason' will be displayed
1654 * on the toolbar while target mode is active.
1656 * Use fn == NULL to cancel target mode.
1658 void filer_target_mode(FilerWindow *filer_window,
1659 TargetFunc fn,
1660 gpointer data,
1661 char *reason)
1663 TargetFunc old_fn = filer_window->target_cb;
1665 if (fn != old_fn)
1666 gdk_window_set_cursor(
1667 GTK_WIDGET(filer_window->collection)->window,
1668 fn ? crosshair : NULL);
1670 filer_window->target_cb = fn;
1671 filer_window->target_data = data;
1673 if (filer_window->toolbar_text == NULL)
1674 return;
1676 if (fn)
1677 gtk_label_set_text(
1678 GTK_LABEL(filer_window->toolbar_text), reason);
1679 else if (o_toolbar_info)
1681 if (old_fn)
1682 toolbar_update_info(filer_window);
1684 else
1685 gtk_label_set_text(GTK_LABEL(filer_window->toolbar_text), "");
1689 /* Draw the black border */
1690 static gint filer_tooltip_draw(GtkWidget *w)
1692 gdk_draw_rectangle(w->window, w->style->fg_gc[w->state], FALSE, 0, 0,
1693 w->allocation.width - 1, w->allocation.height - 1);
1695 return FALSE;
1698 /* When the tips window closed, record the time. If we try to open another
1699 * tip soon, it will appear more quickly.
1701 static void tip_destroyed(gpointer data)
1703 time(&tip_time);
1706 /* It's time to make the tooltip appear. If we're not over the item any
1707 * more, or the item doesn't need a tooltip, do nothing.
1709 static gboolean filer_tooltip_activate(FilerWindow *filer_window)
1711 Collection *collection;
1712 gint x, y;
1713 int i;
1714 GString *tip = NULL;
1716 g_return_val_if_fail(tip_item != NULL, 0);
1718 tip_timeout = 0;
1720 show_tooltip(NULL);
1722 if (!filer_exists(filer_window))
1723 return FALSE;
1725 collection = filer_window->collection;
1726 gdk_window_get_pointer(GTK_WIDGET(collection)->window, &x, &y, NULL);
1727 i = collection_get_item(filer_window->collection, x, y);
1728 if (i == -1 || ((DirItem *) collection->items[i].data) != tip_item)
1729 return FALSE; /* Not still under the pointer */
1731 /* OK, the filer window still exists and the pointer is still
1732 * over the same item. Do we need to show a tip?
1735 tip = g_string_new(NULL);
1737 if (display_is_truncated(filer_window, i))
1739 g_string_append(tip, tip_item->leafname);
1740 g_string_append_c(tip, '\n');
1743 if (tip_item->flags & ITEM_FLAG_APPDIR)
1745 AppInfo *info;
1746 xmlNode *node;
1748 info = appinfo_get(make_path(filer_window->path,
1749 tip_item->leafname)->str, tip_item);
1750 if (info && ((node = appinfo_get_section(info, "Summary"))))
1752 guchar *str;
1753 str = xmlNodeListGetString(node->doc,
1754 node->xmlChildrenNode, 1);
1755 g_string_append(tip, str);
1756 g_string_append_c(tip, '\n');
1757 g_free(str);
1761 if (tip->len > 1)
1763 g_string_truncate(tip, tip->len - 1);
1764 show_tooltip(tip->str);
1767 g_string_free(tip, TRUE);
1769 return FALSE;
1772 /* Display a tooltip-like widget near the pointer with 'text'. If 'text' is
1773 * NULL, close any current tooltip.
1775 static void show_tooltip(guchar *text)
1777 GtkWidget *label;
1778 int x, y, py;
1779 int w, h;
1781 if (tip_widget)
1783 gtk_widget_destroy(tip_widget);
1784 tip_widget = NULL;
1787 if (!text)
1788 return;
1790 /* Show the tip */
1791 tip_widget = gtk_window_new(GTK_WINDOW_POPUP);
1792 gtk_widget_set_app_paintable(tip_widget, TRUE);
1793 gtk_widget_set_name(tip_widget, "gtk-tooltips");
1795 gtk_signal_connect_object(GTK_OBJECT(tip_widget), "expose_event",
1796 GTK_SIGNAL_FUNC(filer_tooltip_draw),
1797 (GtkObject *) tip_widget);
1798 #ifndef GTK2
1799 gtk_signal_connect_object(GTK_OBJECT(tip_widget), "draw",
1800 GTK_SIGNAL_FUNC(filer_tooltip_draw),
1801 (GtkObject *) tip_widget);
1802 #endif
1804 label = gtk_label_new(text);
1805 gtk_misc_set_padding(GTK_MISC(label), 4, 2);
1806 gtk_container_add(GTK_CONTAINER(tip_widget), label);
1807 gtk_widget_show(label);
1808 gtk_widget_realize(tip_widget);
1810 w = tip_widget->allocation.width;
1811 h = tip_widget->allocation.height;
1812 gdk_window_get_pointer(NULL, &x, &py, NULL);
1814 x -= w / 2;
1815 y = py + 12; /* I don't know the pointer height so I use a constant */
1817 /* Now check for screen boundaries */
1818 x = CLAMP(x, 0, screen_width - w);
1819 y = CLAMP(y, 0, screen_height - h);
1821 /* And again test if pointer is over the tooltip window */
1822 if (py >= y && py <= y + h)
1823 y = py - h- 2;
1824 gtk_widget_set_uposition(tip_widget, x, y);
1825 gtk_widget_show(tip_widget);
1827 gtk_signal_connect_object(GTK_OBJECT(tip_widget), "destroy",
1828 GTK_SIGNAL_FUNC(tip_destroyed), NULL);
1829 time(&tip_time);
1832 /* Display a tooltip for 'item' after a while (if item is not NULL).
1833 * Cancel any previous tooltip.
1835 static void filer_tooltip_prime(FilerWindow *filer_window, DirItem *item)
1837 time_t now;
1839 time(&now);
1841 if (item == tip_item)
1842 return;
1844 if (tip_timeout)
1846 gtk_timeout_remove(tip_timeout);
1847 tip_timeout = 0;
1848 tip_item = NULL;
1850 if (tip_widget)
1852 gtk_widget_destroy(tip_widget);
1853 tip_widget = NULL;
1856 tip_item = item;
1857 if (filer_window && item)
1859 int delay = now - tip_time > 2 ? 1000 : 200;
1861 tip_timeout = gtk_timeout_add(delay,
1862 (GtkFunction) filer_tooltip_activate,
1863 filer_window);