r1219: Internal changes to the options system. See text at start of options.c.
[rox-filer.git] / ROX-Filer / src / display.c
bloba553b58ca2d7412cbaa1b4c1728e6b321306c2aa
1 /*
2 * $Id$
4 * ROX-Filer, filer for the ROX desktop project
5 * Copyright (C) 2002, 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 /* display.c - code for arranging and displaying file items */
24 #include "config.h"
26 #include <stdlib.h>
27 #include <stdio.h>
28 #include <math.h>
29 #include <string.h>
30 #include <errno.h>
31 #include <sys/param.h>
32 #include <unistd.h>
33 #include <time.h>
34 #include <ctype.h>
36 #include <gtk/gtk.h>
37 #include <gdk/gdkx.h>
38 #include <gdk/gdkkeysyms.h>
40 #include "global.h"
42 #include "main.h"
43 #include "display.h"
44 #include "collection.h"
45 #include "support.h"
46 #include "gui_support.h"
47 #include "filer.h"
48 #include "pixmaps.h"
49 #include "menu.h"
50 #include "dnd.h"
51 #include "run.h"
52 #include "mount.h"
53 #include "type.h"
54 #include "options.h"
55 #include "action.h"
56 #include "minibuffer.h"
57 #include "dir.h"
58 #include "diritem.h"
59 #include "fscache.h"
61 #define ROW_HEIGHT_SMALL 20
62 #define ROW_HEIGHT_FULL_INFO 44
63 #define MIN_ITEM_WIDTH 64
65 #define MIN_TRUNCATE 0
66 #define MAX_TRUNCATE 250
68 #define HUGE_WRAP (1.5 * o_large_width.int_value)
70 /* Options bits */
71 static Option o_intelligent_sort;
72 static Option o_display_dirs_first;
73 Option o_display_size;
74 Option o_display_details;
75 Option o_display_sort_by;
76 static Option o_large_width;
77 static Option o_small_width;
78 Option o_display_show_hidden;
79 Option o_display_show_thumbs;
80 Option o_display_inherit_options;
83 /* GC for drawing colour filenames */
84 static GdkGC *type_gc = NULL;
86 typedef struct _Template Template;
88 struct _Template {
89 GdkRectangle icon;
90 GdkRectangle leafname;
91 GdkRectangle details;
94 #define SHOW_RECT(ite, template) \
95 g_print("%s: %dx%d+%d+%d %dx%d+%d+%d\n", \
96 item->leafname, \
97 (template)->leafname.width, (template)->leafname.height,\
98 (template)->leafname.x, (template)->leafname.y, \
99 (template)->icon.width, (template)->icon.height, \
100 (template)->icon.x, (template)->icon.y)
102 /* Static prototypes */
103 static void fill_template(GdkRectangle *area, CollectionItem *item,
104 FilerWindow *filer_window, Template *template);
105 static void huge_template(GdkRectangle *area, CollectionItem *colitem,
106 FilerWindow *filer_window, Template *template);
107 static void large_template(GdkRectangle *area, CollectionItem *colitem,
108 FilerWindow *filer_window, Template *template);
109 static void small_template(GdkRectangle *area, CollectionItem *colitem,
110 FilerWindow *filer_window, Template *template);
111 static void huge_full_template(GdkRectangle *area, CollectionItem *colitem,
112 FilerWindow *filer_window, Template *template);
113 static void large_full_template(GdkRectangle *area, CollectionItem *colitem,
114 FilerWindow *filer_window, Template *template);
115 static void small_full_template(GdkRectangle *area, CollectionItem *colitem,
116 FilerWindow *filer_window, Template *template);
117 static void draw_item(GtkWidget *widget,
118 CollectionItem *item,
119 GdkRectangle *area,
120 FilerWindow *filer_window);
121 static gboolean test_point(Collection *collection,
122 int point_x, int point_y,
123 CollectionItem *item,
124 int width, int height,
125 FilerWindow *filer_window);
126 static void display_details_set(FilerWindow *filer_window, DetailsType details);
127 static void display_style_set(FilerWindow *filer_window, DisplayStyle style);
128 static void options_changed(void);
129 static char *details(FilerWindow *filer_window, DirItem *item);
130 static void update_views(FilerWindow *filer_window);
131 static void draw_string(GtkWidget *widget,
132 #ifdef GTK2
133 PangoLayout *layout,
134 #else
135 GdkFont *font,
136 char *string,
137 int len, /* -1 for whole string */
138 #endif
139 GdkRectangle *area, /* Area available on screen */
140 int width, /* Width of the full string */
141 GtkStateType selection_state,
142 gboolean selected,
143 gboolean box);
145 enum {
146 SORT_BY_NAME = 0,
147 SORT_BY_TYPE = 1,
148 SORT_BY_DATE = 2,
149 SORT_BY_SIZE = 3,
152 /****************************************************************
153 * EXTERNAL INTERFACE *
154 ****************************************************************/
156 void display_init()
158 option_add_int(&o_intelligent_sort, "display_intelligent_sort", 1);
159 option_add_int(&o_display_dirs_first, "display_dirs_first", FALSE);
160 option_add_int(&o_display_size, "display_size", LARGE_ICONS);
161 option_add_int(&o_display_details, "display_details", DETAILS_NONE);
162 option_add_int(&o_display_sort_by, "display_sort_by", SORT_BY_NAME);
163 option_add_int(&o_large_width, "display_large_width", 89);
164 option_add_int(&o_small_width, "display_small_width", 250);
165 option_add_int(&o_display_show_hidden, "display_show_hidden", FALSE);
166 option_add_int(&o_display_show_thumbs, "display_show_thumbs", FALSE);
167 option_add_int(&o_display_inherit_options,"display_inherit_options",
168 FALSE);
169 option_add_notify(options_changed);
172 /* A template contains the locations of the three rectangles (for the icon,
173 * name and extra details).
174 * Fill in the empty 'template' with the rectanges for this item.
176 static void fill_template(GdkRectangle *area, CollectionItem *colitem,
177 FilerWindow *filer_window, Template *template)
179 DisplayStyle style = filer_window->display_style;
180 ViewData *view = (ViewData *) colitem->view_data;
182 if (view->details)
184 template->details.width = view->details_width;
185 template->details.height = view->details_height;
187 if (style == SMALL_ICONS)
188 small_full_template(area, colitem,
189 filer_window, template);
190 else if (style == LARGE_ICONS)
191 large_full_template(area, colitem,
192 filer_window, template);
193 else
194 huge_full_template(area, colitem,
195 filer_window, template);
197 else
199 if (style == HUGE_ICONS)
200 huge_template(area, colitem, filer_window, template);
201 else if (style == LARGE_ICONS)
202 large_template(area, colitem, filer_window, template);
203 else
204 small_template(area, colitem, filer_window, template);
208 /* Return the size needed for this item */
209 void calc_size(FilerWindow *filer_window, CollectionItem *colitem,
210 int *width, int *height)
212 int pix_width, pix_height;
213 int w;
214 DisplayStyle style = filer_window->display_style;
215 ViewData *view = (ViewData *) colitem->view_data;
217 if (filer_window->details_type == DETAILS_NONE)
219 if (style == HUGE_ICONS)
221 if (view->image)
223 if (!view->image->huge_pixmap)
224 pixmap_make_huge(view->image);
225 pix_width = view->image->huge_width;
226 pix_height = view->image->huge_height;
228 else
230 pix_width = HUGE_WIDTH * 3 / 2;
231 pix_height = HUGE_HEIGHT * 3 / 2;
233 #ifdef GTK2
234 *width = MAX(pix_width, view->name_width) + 4;
235 *height = view->name_height + pix_height + 4;
236 #else
237 *width = MAX(pix_width, view->split_width) + 4;
238 *height = view->split_height + pix_height + 4;
239 #endif
241 else if (style == SMALL_ICONS)
243 w = MIN(view->name_width, o_small_width.int_value);
244 *width = SMALL_WIDTH + 12 + w;
245 *height = MAX(view->name_height, SMALL_HEIGHT) + 4;
247 else
249 if (view->image)
250 pix_width = view->image->width;
251 else
252 pix_width = HUGE_WIDTH * 3 / 2;
253 #ifdef GTK2
254 *width = MAX(pix_width, view->name_width) + 4;
255 *height = view->name_height + ICON_HEIGHT + 2;
256 #else
257 *width = MAX(pix_width, view->split_width) + 4;
258 *height = view->split_height + ICON_HEIGHT + 2;
259 #endif
262 else
264 w = view->details_width;
265 if (style == HUGE_ICONS)
267 *width = HUGE_WIDTH + 12 + MAX(w, view->name_width);
268 *height = HUGE_HEIGHT - 4;
270 else if (style == SMALL_ICONS)
272 int text_height;
274 *width = SMALL_WIDTH + view->name_width + 12 + w;
275 text_height = MAX(view->name_height,
276 view->details_height);
277 *height = MAX(text_height, SMALL_HEIGHT) + 4;
279 else
281 *width = ICON_WIDTH + 12 + MAX(w, view->name_width);
282 *height = ICON_HEIGHT;
287 /* Draw this icon (including any symlink or mount symbol) inside the
288 * given rectangle.
290 void draw_huge_icon(GtkWidget *widget,
291 GdkRectangle *area,
292 DirItem *item,
293 MaskedPixmap *image,
294 gboolean selected)
296 int width, height;
297 int image_x;
298 int image_y;
299 GdkGC *gc = selected ? widget->style->white_gc
300 : widget->style->black_gc;
301 if (!image)
302 return;
304 width = image->huge_width;
305 height = image->huge_height;
306 image_x = area->x + ((area->width - width) >> 1);
308 gdk_gc_set_clip_mask(gc, image->huge_mask);
310 image_y = MAX(0, area->height - height - 6);
311 gdk_gc_set_clip_origin(gc, image_x, area->y + image_y);
312 gdk_draw_pixmap(widget->window, gc,
313 image->huge_pixmap,
314 0, 0, /* Source x,y */
315 image_x, area->y + image_y, /* Dest x,y */
316 width, height);
318 if (selected)
320 gdk_gc_set_function(gc, GDK_INVERT);
321 gdk_draw_rectangle(widget->window,
323 TRUE, image_x, area->y + image_y,
324 width, height);
325 gdk_gc_set_function(gc, GDK_COPY);
328 if (item->flags & ITEM_FLAG_SYMLINK)
330 gdk_gc_set_clip_origin(gc, image_x, area->y + 2);
331 gdk_gc_set_clip_mask(gc, im_symlink->mask);
332 gdk_draw_pixmap(widget->window, gc, im_symlink->pixmap,
333 0, 0, /* Source x,y */
334 image_x, area->y + 2, /* Dest x,y */
335 -1, -1);
337 else if (item->flags & ITEM_FLAG_MOUNT_POINT)
339 MaskedPixmap *mp = item->flags & ITEM_FLAG_MOUNTED
340 ? im_mounted
341 : im_unmounted;
343 gdk_gc_set_clip_origin(gc, image_x, area->y + 2);
344 gdk_gc_set_clip_mask(gc, mp->mask);
345 gdk_draw_pixmap(widget->window, gc, mp->pixmap,
346 0, 0, /* Source x,y */
347 image_x, area->y + 2, /* Dest x,y */
348 -1, -1);
351 gdk_gc_set_clip_mask(gc, NULL);
352 gdk_gc_set_clip_origin(gc, 0, 0);
355 /* Draw this icon (including any symlink or mount symbol) inside the
356 * given rectangle.
358 void draw_large_icon(GtkWidget *widget,
359 GdkRectangle *area,
360 DirItem *item,
361 MaskedPixmap *image,
362 gboolean selected)
364 int width;
365 int height;
366 int image_x;
367 int image_y;
368 GdkGC *gc = selected ? widget->style->white_gc
369 : widget->style->black_gc;
370 if (!image)
371 return;
373 width = MIN(image->width, ICON_WIDTH);
374 height = MIN(image->height, ICON_HEIGHT);
375 image_x = area->x + ((area->width - width) >> 1);
377 gdk_gc_set_clip_mask(gc, image->mask);
379 image_y = MAX(0, area->height - height - 6);
380 gdk_gc_set_clip_origin(gc, image_x, area->y + image_y);
381 gdk_draw_pixmap(widget->window, gc,
382 image->pixmap,
383 0, 0, /* Source x,y */
384 image_x, area->y + image_y, /* Dest x,y */
385 width, height);
387 if (selected)
389 gdk_gc_set_function(gc, GDK_INVERT);
390 gdk_draw_rectangle(widget->window,
392 TRUE, image_x, area->y + image_y,
393 width, height);
394 gdk_gc_set_function(gc, GDK_COPY);
397 if (item->flags & ITEM_FLAG_SYMLINK)
399 gdk_gc_set_clip_origin(gc, image_x, area->y + 2);
400 gdk_gc_set_clip_mask(gc, im_symlink->mask);
401 gdk_draw_pixmap(widget->window, gc, im_symlink->pixmap,
402 0, 0, /* Source x,y */
403 image_x, area->y + 2, /* Dest x,y */
404 -1, -1);
406 else if (item->flags & ITEM_FLAG_MOUNT_POINT)
408 MaskedPixmap *mp = item->flags & ITEM_FLAG_MOUNTED
409 ? im_mounted
410 : im_unmounted;
412 gdk_gc_set_clip_origin(gc, image_x, area->y + 2);
413 gdk_gc_set_clip_mask(gc, mp->mask);
414 gdk_draw_pixmap(widget->window, gc, mp->pixmap,
415 0, 0, /* Source x,y */
416 image_x, area->y + 2, /* Dest x,y */
417 -1, -1);
420 gdk_gc_set_clip_mask(gc, NULL);
421 gdk_gc_set_clip_origin(gc, 0, 0);
424 /* The sort functions aren't called from outside, but they are
425 * passed as arguments to display_set_sort_fn().
428 #define IS_A_DIR(item) (item->base_type == TYPE_DIRECTORY && \
429 !(item->flags & ITEM_FLAG_APPDIR))
431 #define SORT_DIRS \
432 if (o_display_dirs_first.int_value) { \
433 gboolean id1 = IS_A_DIR(i1); \
434 gboolean id2 = IS_A_DIR(i2); \
435 if (id1 && !id2) return -1; \
436 if (id2 && !id1) return 1; \
439 int sort_by_name(const void *item1, const void *item2)
441 const DirItem *i1 = (DirItem *) ((CollectionItem *) item1)->data;
442 const DirItem *i2 = (DirItem *) ((CollectionItem *) item2)->data;
443 char *n1 = i1->leafname;
444 char *n2 = i2->leafname;
446 SORT_DIRS;
448 if (!o_intelligent_sort.int_value)
449 return strcmp(i1->leafname, i2->leafname);
451 /* The following code was copied from PicoGUI (was LGPL) */
453 /* Sort the files, in a way that should make sense to users.
454 * Case is ignored, punctuation is ignored. If the file contains
455 * numbers, the numbers are sorted numerically.
457 while (*n1 && *n2)
459 char c1 = *n1, c2 = *n2;
461 /* Skip punctuation */
462 if (!isalnum(c1))
464 n1++;
465 continue;
467 if (!isalnum(c2))
469 n2++;
470 continue;
473 /* If they are both numbers, sort them numerically */
474 if (isdigit(c1) && isdigit(c2))
476 char *p;
477 unsigned long u1,u2;
478 u1 = strtoul(n1, &p, 10);
479 n1 = p;
480 u2 = strtoul(n2, &p, 10);
481 n2 = p;
482 if (u1 < u2)
483 return -1;
484 else if (u1 > u2)
485 return 1;
486 continue;
489 /* Otherwise, do a case-insensitive asciibetical sort */
490 c1 = tolower(c1);
491 c2 = tolower(c2);
492 if (c1 < c2)
493 return -1;
494 else if (c1 > c2)
495 return 1;
496 n1++;
497 n2++;
500 /* Compare length */
501 if (*n1)
502 return 1; /* First string is longer, so comes after */
503 if (*n2)
504 return -1;
506 /* If the strings are equal at the end of this, fallback to a straight
507 * ASCII sort so at least it's deterministic.
509 return strcmp(i1->leafname, i2->leafname);
512 int sort_by_type(const void *item1, const void *item2)
514 const DirItem *i1 = (DirItem *) ((CollectionItem *) item1)->data;
515 const DirItem *i2 = (DirItem *) ((CollectionItem *) item2)->data;
516 MIME_type *m1, *m2;
518 int diff = i1->base_type - i2->base_type;
520 if (!diff)
521 diff = (i1->flags & ITEM_FLAG_APPDIR)
522 - (i2->flags & ITEM_FLAG_APPDIR);
523 if (diff)
524 return diff > 0 ? 1 : -1;
526 m1 = i1->mime_type;
527 m2 = i2->mime_type;
529 if (m1 && m2)
531 diff = strcmp(m1->media_type, m2->media_type);
532 if (!diff)
533 diff = strcmp(m1->subtype, m2->subtype);
535 else if (m1 || m2)
536 diff = m1 ? 1 : -1;
537 else
538 diff = 0;
540 if (diff)
541 return diff > 0 ? 1 : -1;
543 return sort_by_name(item1, item2);
546 int sort_by_date(const void *item1, const void *item2)
548 const DirItem *i1 = (DirItem *) ((CollectionItem *) item1)->data;
549 const DirItem *i2 = (DirItem *) ((CollectionItem *) item2)->data;
551 SORT_DIRS;
553 return i1->mtime > i2->mtime ? -1 :
554 i1->mtime < i2->mtime ? 1 :
555 sort_by_name(item1, item2);
558 int sort_by_size(const void *item1, const void *item2)
560 const DirItem *i1 = (DirItem *) ((CollectionItem *) item1)->data;
561 const DirItem *i2 = (DirItem *) ((CollectionItem *) item2)->data;
563 SORT_DIRS;
565 return i1->size > i2->size ? -1 :
566 i1->size < i2->size ? 1 :
567 sort_by_name(item1, item2);
570 /* Make the items as small as possible */
571 void shrink_grid(FilerWindow *filer_window)
573 int i;
574 Collection *col = filer_window->collection;
575 int width = MIN_ITEM_WIDTH;
576 int height = SMALL_HEIGHT;
578 for (i = 0; i < col->number_of_items; i++)
580 int w, h;
582 calc_size(filer_window, &col->items[i], &w, &h);
583 if (w > width)
584 width = w;
585 if (h > height)
586 height = h;
589 collection_set_item_size(filer_window->collection, width, height);
592 void display_set_sort_fn(FilerWindow *filer_window,
593 int (*fn)(const void *a, const void *b))
595 if (filer_window->sort_fn == fn)
596 return;
598 filer_window->sort_fn = fn;
600 collection_qsort(filer_window->collection,
601 filer_window->sort_fn);
604 void display_set_layout(FilerWindow *filer_window,
605 DisplayStyle style,
606 DetailsType details)
608 g_return_if_fail(filer_window != NULL);
610 display_style_set(filer_window, style);
611 display_details_set(filer_window, details);
613 shrink_grid(filer_window);
615 if (o_filer_auto_resize.int_value != RESIZE_NEVER)
616 filer_window_autosize(filer_window, TRUE);
619 /* Set the 'Show Thumbnails' flag for this window */
620 void display_set_thumbs(FilerWindow *filer_window, gboolean thumbs)
622 if (filer_window->show_thumbs == thumbs)
623 return;
625 filer_window->show_thumbs = thumbs;
627 update_views(filer_window);
629 #ifndef GTK2
630 filer_window->collection->paint_level = PAINT_CLEAR;
631 #endif
632 gtk_widget_queue_clear(GTK_WIDGET(filer_window->collection));
634 if (!thumbs)
635 filer_cancel_thumbnails(filer_window);
637 filer_set_title(filer_window);
639 filer_create_thumbs(filer_window);
642 /* Set the 'Show Hidden' flag for this window */
643 void display_set_hidden(FilerWindow *filer_window, gboolean hidden)
645 if (filer_window->show_hidden == hidden)
646 return;
648 filer_window->show_hidden = hidden;
650 filer_detach_rescan(filer_window);
652 filer_set_title(filer_window);
655 /* Highlight (wink or cursor) this item in the filer window. If the item
656 * isn't already there but we're scanning then highlight it if it
657 * appears later.
659 void display_set_autoselect(FilerWindow *filer_window, guchar *leaf)
661 Collection *col;
662 int i;
664 g_return_if_fail(filer_window != NULL);
665 col = filer_window->collection;
667 for (i = 0; i < col->number_of_items; i++)
669 DirItem *item = (DirItem *) col->items[i].data;
671 if (strcmp(item->leafname, leaf) == 0)
673 if (col->cursor_item != -1)
674 collection_set_cursor_item(col, i);
675 else
676 collection_wink_item(col, i);
677 leaf = NULL;
678 goto out;
682 out:
683 if (leaf)
684 leaf = g_strdup(leaf);
685 g_free(filer_window->auto_select);
686 filer_window->auto_select = leaf;
689 gboolean display_is_truncated(FilerWindow *filer_window, int i)
691 Template template;
692 Collection *collection = filer_window->collection;
693 CollectionItem *colitem = &collection->items[i];
694 int col = i % collection->columns;
695 int row = i / collection->columns;
696 int scroll = 0;
697 GdkRectangle area;
698 ViewData *view = (ViewData *) colitem->view_data;
700 #ifndef GTK2
701 scroll = collection->vadj->value; /* (round to int) */
702 #endif
704 if (filer_window->display_style == LARGE_ICONS ||
705 filer_window->display_style == HUGE_ICONS)
706 return FALSE; /* These wrap rather than truncate */
708 area.x = col * collection->item_width;
709 area.y = row * collection->item_height - scroll;
710 area.height = collection->item_height;
712 if (col == collection->columns - 1)
713 area.width = GTK_WIDGET(collection)->allocation.width - area.x;
714 else
715 area.width = collection->item_width;
717 fill_template(&area, colitem, filer_window, &template);
719 return template.leafname.width < view->name_width;
722 /* Change the icon size (wraps) */
723 void display_change_size(FilerWindow *filer_window, gboolean bigger)
725 DisplayStyle new;
727 g_return_if_fail(filer_window != NULL);
729 switch (filer_window->display_style)
731 case LARGE_ICONS:
732 new = bigger ? HUGE_ICONS : SMALL_ICONS;
733 break;
734 case HUGE_ICONS:
735 new = bigger ? SMALL_ICONS : LARGE_ICONS;
736 break;
737 default:
738 new = bigger ? LARGE_ICONS : HUGE_ICONS;
739 break;
742 display_set_layout(filer_window, new, filer_window->details_type);
745 ViewData *display_create_viewdata(FilerWindow *filer_window, DirItem *item)
747 ViewData *view;
749 view = g_new(ViewData, 1);
751 #ifdef GTK2
752 view->layout = NULL;
753 #endif
754 view->details = NULL;
755 view->image = NULL;
757 display_update_view(filer_window, item, view);
759 return view;
762 void display_free_colitem(Collection *collection, CollectionItem *colitem)
764 ViewData *view = (ViewData *) colitem->view_data;
766 if (!view)
767 return;
769 #ifdef GTK2
770 if (view->layout)
772 g_object_unref(G_OBJECT(view->layout));
773 view->layout = NULL;
775 if (view->details)
776 g_object_unref(G_OBJECT(view->details));
777 #endif
779 if (view->image)
780 pixmap_unref(view->image);
782 g_free(view);
785 /****************************************************************
786 * INTERNAL FUNCTIONS *
787 ****************************************************************/
789 static void options_changed(void)
791 GList *next = all_filer_windows;
793 while (next)
795 FilerWindow *filer_window = (FilerWindow *) next->data;
797 #ifdef GTK2
798 if (o_large_width.has_changed || o_small_width.has_changed)
800 /* Recreate PangoLayout */
801 update_views(filer_window);
803 #endif
805 if (o_intelligent_sort.has_changed ||
806 o_display_dirs_first.has_changed)
808 collection_qsort(filer_window->collection,
809 filer_window->sort_fn);
811 shrink_grid(filer_window);
812 gtk_widget_queue_draw(filer_window->window);
814 next = next->next;
818 static void huge_template(GdkRectangle *area, CollectionItem *colitem,
819 FilerWindow *filer_window, Template *template)
821 int col_width = filer_window->collection->item_width;
822 int text_x, text_y;
823 ViewData *view = (ViewData *) colitem->view_data;
824 MaskedPixmap *image = view->image;
826 if (image)
828 if (!image->huge_pixmap)
829 pixmap_make_huge(image);
830 template->icon.width = image->huge_width;
831 template->icon.height = image->huge_height;
833 else
835 template->icon.width = HUGE_WIDTH * 3 / 2;
836 template->icon.height = HUGE_HEIGHT;
839 #ifdef GTK2
840 template->leafname.width = view->name_width;
841 template->leafname.height = view->name_height;
842 #else
843 template->leafname.width = view->split_width;
844 template->leafname.height = view->split_height;
845 #endif
846 text_x = area->x + ((col_width - template->leafname.width) >> 1);
847 text_y = area->y + area->height - template->leafname.height;
849 template->leafname.x = text_x;
850 template->leafname.y = text_y;
852 template->icon.x = area->x + ((col_width - template->icon.width) >> 1);
853 template->icon.y = template->leafname.y - template->icon.height - 2;
856 static void large_template(GdkRectangle *area, CollectionItem *colitem,
857 FilerWindow *filer_window, Template *template)
859 int col_width = filer_window->collection->item_width;
860 int iwidth, iheight;
861 int image_x;
862 int image_y;
863 ViewData *view = (ViewData *) colitem->view_data;
864 MaskedPixmap *image = view->image;
866 int text_x, text_y;
868 if (image)
870 iwidth = MIN(image->width, ICON_WIDTH);
871 iheight = MIN(image->height + 6, ICON_HEIGHT);
873 else
875 iwidth = ICON_WIDTH;
876 iheight = ICON_HEIGHT;
878 image_x = area->x + ((col_width - iwidth) >> 1);
880 #ifdef GTK2
881 template->leafname.width = view->name_width;
882 template->leafname.height = view->name_height;
883 #else
884 template->leafname.width = view->split_width;
885 template->leafname.height = view->split_height;
886 #endif
887 text_x = area->x + ((col_width - template->leafname.width) >> 1);
888 text_y = area->y + ICON_HEIGHT + 2;
890 template->leafname.x = text_x;
891 template->leafname.y = text_y;
893 image_y = text_y - iheight;
894 image_y = MAX(area->y, image_y);
896 template->icon.x = image_x;
897 template->icon.y = image_y;
898 template->icon.width = iwidth;
899 template->icon.height = MIN(ICON_HEIGHT, iheight);
902 static void small_template(GdkRectangle *area, CollectionItem *colitem,
903 FilerWindow *filer_window, Template *template)
905 int text_x = area->x + SMALL_WIDTH + 4;
906 int low_text_y;
907 int max_text_width = area->width - SMALL_WIDTH - 4;
908 ViewData *view = (ViewData *) colitem->view_data;
910 low_text_y = area->y + area->height / 2 - view->name_height / 2;
912 template->leafname.x = text_x;
913 template->leafname.y = low_text_y;
914 template->leafname.width = MIN(max_text_width, view->name_width);
915 template->leafname.height = view->name_height;
917 template->icon.x = area->x;
918 template->icon.y = area->y + 1;
919 template->icon.width = SMALL_WIDTH;
920 template->icon.height = SMALL_HEIGHT;
923 static void huge_full_template(GdkRectangle *area, CollectionItem *colitem,
924 FilerWindow *filer_window, Template *template)
926 int max_text_width = area->width - HUGE_WIDTH - 4;
927 ViewData *view = (ViewData *) colitem->view_data;
928 MaskedPixmap *image = view->image;
930 if (image)
932 if (!image->huge_pixmap)
933 pixmap_make_huge(image);
934 template->icon.width = image->huge_width;
935 template->icon.height = image->huge_height;
937 else
939 template->icon.width = HUGE_WIDTH * 3 / 2;
940 template->icon.height = HUGE_HEIGHT;
943 template->icon.x = area->x + (HUGE_WIDTH - template->icon.width) / 2;
944 template->icon.y = area->y + (area->height - template->icon.height) / 2;
946 template->leafname.x = area->x + HUGE_WIDTH + 4;
947 template->leafname.y = area->y + area->height / 2
948 - (view->name_height + 2 + view->details_height) / 2;
949 template->leafname.width = MIN(max_text_width, view->name_width);
950 template->leafname.height = view->name_height;
952 if (!image)
953 return; /* Not scanned yet */
955 template->details.x = template->leafname.x;
956 template->details.y = template->leafname.y + view->name_height + 2;
959 static void large_full_template(GdkRectangle *area, CollectionItem *colitem,
960 FilerWindow *filer_window, Template *template)
962 int max_text_width = area->width - ICON_WIDTH - 4;
963 ViewData *view = (ViewData *) colitem->view_data;
965 template->icon.x = area->x;
966 template->icon.y = area->y + (area->height - ICON_HEIGHT) / 2;
967 template->icon.width = ICON_WIDTH;
968 template->icon.height = ICON_HEIGHT;
970 template->leafname.x = area->x + ICON_WIDTH + 4;
971 template->leafname.y = area->y + area->height / 2
972 - (view->name_height + 2 + view->details_height) / 2;
973 template->leafname.width = MIN(max_text_width, view->name_width);
974 template->leafname.height = view->name_height;
976 if (!view->image)
977 return; /* Not scanned yet */
979 template->details.x = template->leafname.x;
980 template->details.y = template->leafname.y + view->name_height + 2;
983 static void small_full_template(GdkRectangle *area, CollectionItem *colitem,
984 FilerWindow *filer_window, Template *template)
986 int col_width = filer_window->collection->item_width;
987 ViewData *view = (ViewData *) colitem->view_data;
989 small_template(area, colitem, filer_window, template);
991 if (!view->image)
992 return; /* Not scanned yet */
994 template->details.x = area->x + col_width - template->details.width;
995 template->details.y = area->y + area->height / 2 - \
996 view->details_height / 2;
999 #define INSIDE(px, py, area) \
1000 (px >= area.x && py >= area.y && \
1001 px < area.x + area.width && py < area.y + area.height)
1003 static gboolean test_point(Collection *collection,
1004 int point_x, int point_y,
1005 CollectionItem *colitem,
1006 int width, int height,
1007 FilerWindow *filer_window)
1009 Template template;
1010 GdkRectangle area;
1011 ViewData *view = (ViewData *) colitem->view_data;
1013 area.x = 0;
1014 area.y = 0;
1015 area.width = width;
1016 area.height = height;
1018 fill_template(&area, colitem, filer_window, &template);
1020 return INSIDE(point_x, point_y, template.leafname) ||
1021 INSIDE(point_x, point_y, template.icon) ||
1022 (view->details && INSIDE(point_x, point_y, template.details));
1025 static void draw_small_icon(GtkWidget *widget,
1026 GdkRectangle *area,
1027 DirItem *item,
1028 MaskedPixmap *image,
1029 gboolean selected)
1031 GdkGC *gc = selected ? widget->style->white_gc
1032 : widget->style->black_gc;
1033 int width, height, image_x, image_y;
1035 if (!image)
1036 return;
1038 if (!image->sm_pixmap)
1039 pixmap_make_small(image);
1041 width = MIN(image->sm_width, SMALL_WIDTH);
1042 height = MIN(image->sm_height, SMALL_HEIGHT);
1043 image_x = area->x + ((area->width - width) >> 1);
1045 gdk_gc_set_clip_mask(gc, image->sm_mask);
1047 image_y = MAX(0, SMALL_HEIGHT - image->sm_height);
1048 gdk_gc_set_clip_origin(gc, image_x, area->y + image_y);
1049 gdk_draw_pixmap(widget->window, gc,
1050 image->sm_pixmap,
1051 0, 0, /* Source x,y */
1052 image_x, area->y + image_y, /* Dest x,y */
1053 width, height);
1055 if (selected)
1057 gdk_gc_set_function(gc, GDK_INVERT);
1058 gdk_draw_rectangle(widget->window,
1060 TRUE, image_x, area->y + image_y,
1061 width, height);
1062 gdk_gc_set_function(gc, GDK_COPY);
1065 if (item->flags & ITEM_FLAG_SYMLINK)
1067 gdk_gc_set_clip_origin(gc, image_x, area->y + 8);
1068 gdk_gc_set_clip_mask(gc, im_symlink->mask);
1069 gdk_draw_pixmap(widget->window, gc, im_symlink->pixmap,
1070 0, 0, /* Source x,y */
1071 image_x, area->y + 8, /* Dest x,y */
1072 -1, -1);
1074 else if (item->flags & ITEM_FLAG_MOUNT_POINT)
1076 MaskedPixmap *mp = item->flags & ITEM_FLAG_MOUNTED
1077 ? im_mounted
1078 : im_unmounted;
1080 if (!mp->sm_pixmap)
1081 pixmap_make_small(mp);
1082 gdk_gc_set_clip_origin(gc, image_x + 2, area->y + 2);
1083 gdk_gc_set_clip_mask(gc, mp->sm_mask);
1084 gdk_draw_pixmap(widget->window, gc,
1085 mp->sm_pixmap,
1086 0, 0, /* Source x,y */
1087 image_x + 2, area->y + 2, /* Dest x,y */
1088 -1, -1);
1091 gdk_gc_set_clip_mask(gc, NULL);
1092 gdk_gc_set_clip_origin(gc, 0, 0);
1095 #ifndef GTK2
1096 /* Render the details somewhere */
1097 static void draw_details(FilerWindow *filer_window, DirItem *item,
1098 GdkRectangle *area, int width,
1099 gboolean selected, guchar *string)
1101 GtkWidget *widget = GTK_WIDGET(filer_window->collection);
1102 DetailsType type = filer_window->details_type;
1103 int w;
1105 if (item->base_type == TYPE_UNKNOWN)
1106 return;
1108 w = fixed_width * strlen(string);
1110 draw_string(widget,
1111 fixed_font,
1112 string, -1,
1113 area,
1114 width,
1115 filer_window->selection_state,
1116 selected, TRUE);
1118 if (item->lstat_errno)
1119 return;
1121 if (type == DETAILS_SUMMARY || type == DETAILS_PERMISSIONS)
1123 int perm_offset = type == DETAILS_SUMMARY ? 5 : 0;
1125 perm_offset += 4 * applicable(item->uid, item->gid);
1127 /* Underline the effective permissions */
1128 gdk_draw_rectangle(widget->window,
1129 selected
1130 ? widget->style->fg_gc[
1131 filer_window->selection_state]
1132 : type_gc,
1133 TRUE,
1134 area->x - 1 + fixed_width * perm_offset,
1135 area->y + area->height - 1,
1136 fixed_width * 3 + 1, 1);
1139 #endif
1141 /* Return a new string giving details of this item, or NULL if details
1142 * are not being displayed. If details are not yet available, return
1143 * a string of the right length.
1145 static char *details(FilerWindow *filer_window, DirItem *item)
1147 mode_t m = item->mode;
1148 guchar *buf = NULL;
1149 gboolean scanned = item->base_type != TYPE_UNKNOWN;
1151 if (filer_window->details_type == DETAILS_NONE)
1152 return NULL;
1154 if (scanned && item->lstat_errno)
1155 buf = g_strdup_printf(_("lstat(2) failed: %s"),
1156 g_strerror(item->lstat_errno));
1157 else if (filer_window->details_type == DETAILS_SUMMARY)
1159 if (!scanned)
1160 return g_strdup("XXXX ---,---,---/--- "
1161 "12345678 12345678 "
1162 "12345M 00:00:00 01 Mmm Yyyy");
1163 buf = g_strdup_printf("%s %s %-8.8s %-8.8s %s %s",
1164 item->flags & ITEM_FLAG_APPDIR? "App " :
1165 S_ISDIR(m) ? "Dir " :
1166 S_ISCHR(m) ? "Char" :
1167 S_ISBLK(m) ? "Blck" :
1168 S_ISLNK(m) ? "Link" :
1169 S_ISSOCK(m) ? "Sock" :
1170 S_ISFIFO(m) ? "Pipe" : "File",
1171 pretty_permissions(m),
1172 user_name(item->uid),
1173 group_name(item->gid),
1174 format_size_aligned(item->size),
1175 pretty_time(&item->mtime));
1177 else if (filer_window->details_type == DETAILS_TYPE)
1179 MIME_type *type = item->mime_type;
1181 if (!scanned)
1182 return g_strdup("(application/octet-stream)");
1184 buf = g_strdup_printf("(%s/%s)",
1185 type->media_type, type->subtype);
1187 else if (filer_window->details_type == DETAILS_TIMES)
1189 guchar *ctime, *mtime;
1191 ctime = g_strdup(pretty_time(&item->ctime));
1192 mtime = g_strdup(pretty_time(&item->mtime));
1194 buf = g_strdup_printf("a[%s] c[%s] m[%s]",
1195 pretty_time(&item->atime), ctime, mtime);
1196 g_free(ctime);
1197 g_free(mtime);
1199 else if (filer_window->details_type == DETAILS_PERMISSIONS)
1201 if (!scanned)
1202 return g_strdup("---,---,---/--- 12345678 12345678");
1204 buf = g_strdup_printf("%s %-8.8s %-8.8s",
1205 pretty_permissions(m),
1206 user_name(item->uid),
1207 group_name(item->gid));
1209 else
1211 if (!scanned)
1213 if (filer_window->display_style == SMALL_ICONS)
1214 return g_strdup("12345M");
1215 else
1216 return g_strdup("12345 bytes");
1219 if (item->base_type != TYPE_DIRECTORY)
1221 if (filer_window->display_style == SMALL_ICONS)
1222 buf = g_strdup(format_size_aligned(item->size));
1223 else
1224 buf = g_strdup(format_size(item->size));
1226 else
1227 buf = g_strdup("-");
1230 return buf;
1233 static void draw_item(GtkWidget *widget,
1234 CollectionItem *colitem,
1235 GdkRectangle *area,
1236 FilerWindow *filer_window)
1238 DirItem *item = (DirItem *) colitem->data;
1239 gboolean selected = colitem->selected;
1240 Template template;
1241 ViewData *view = (ViewData *) colitem->view_data;
1243 g_return_if_fail(view != NULL);
1245 fill_template(area, colitem, filer_window, &template);
1247 /* Set up GC for coloured file types */
1248 if (!type_gc)
1249 type_gc = gdk_gc_new(widget->window);
1251 gdk_gc_set_foreground(type_gc, type_get_colour(item,
1252 &widget->style->fg[GTK_STATE_NORMAL]));
1254 if (template.icon.width <= SMALL_WIDTH &&
1255 template.icon.height <= SMALL_HEIGHT)
1257 draw_small_icon(widget, &template.icon,
1258 item, view->image, selected);
1260 else if (template.icon.width <= ICON_WIDTH &&
1261 template.icon.height <= ICON_HEIGHT)
1263 draw_large_icon(widget, &template.icon,
1264 item, view->image, selected);
1266 else
1268 draw_huge_icon(widget, &template.icon,
1269 item, view->image, selected);
1272 #ifdef GTK2
1273 draw_string(widget, view->layout,
1274 &template.leafname,
1275 view->name_width,
1276 filer_window->selection_state,
1277 selected, TRUE);
1278 if (view->details)
1279 draw_string(widget, view->details,
1280 &template.details,
1281 template.details.width,
1282 filer_window->selection_state,
1283 selected, TRUE);
1284 #else
1285 if (view->split_pos)
1287 GdkRectangle rec;
1288 guchar *bot = item->leafname + view->split_pos;
1289 int w;
1291 w = gdk_string_measure(item_font, bot);
1293 rec.x = template.leafname.x;
1294 rec.y = template.leafname.y;
1295 rec.width = template.leafname.width;
1296 rec.height = item_font->ascent + item_font->descent;
1298 draw_string(widget,
1299 item_font,
1300 item->leafname, view->split_pos,
1301 &rec,
1302 template.leafname.width,
1303 filer_window->selection_state,
1304 selected, TRUE);
1305 rec.y += rec.height;
1306 draw_string(widget,
1307 item_font,
1308 bot, -1,
1309 &rec,
1310 MAX(w, template.leafname.width),
1311 filer_window->selection_state,
1312 selected, TRUE);
1314 else
1315 draw_string(widget,
1316 item_font,
1317 item->leafname, -1,
1318 &template.leafname,
1319 view->name_width,
1320 filer_window->selection_state,
1321 selected, TRUE);
1323 if (view->details)
1324 draw_details(filer_window, item,
1325 &template.details,
1326 template.details.width,
1327 selected, view->details);
1328 #endif
1331 /* Recalculate all the ViewData structs for this window.
1332 * Useful when the display style has changed.
1334 static void update_views(FilerWindow *filer_window)
1336 Collection *collection = filer_window->collection;
1337 int i;
1339 for (i = 0; i < collection->number_of_items; i++)
1341 CollectionItem *ci = &collection->items[i];
1343 display_update_view(filer_window, (DirItem *) ci->data,
1344 (ViewData *) ci->view_data);
1348 /* Note: Call shrink_grid after this */
1349 static void display_details_set(FilerWindow *filer_window, DetailsType details)
1351 if (filer_window->details_type == details)
1352 return;
1353 filer_window->details_type = details;
1354 update_views(filer_window);
1356 #ifndef GTK2
1357 filer_window->collection->paint_level = PAINT_CLEAR;
1358 #endif
1359 gtk_widget_queue_clear(GTK_WIDGET(filer_window->collection));
1362 /* Note: Call shrink_grid after this */
1363 static void display_style_set(FilerWindow *filer_window, DisplayStyle style)
1365 if (filer_window->display_style == style)
1366 return;
1368 filer_window->display_style = style;
1370 update_views(filer_window);
1372 collection_set_functions(filer_window->collection,
1373 (CollectionDrawFunc) draw_item,
1374 (CollectionTestFunc) test_point,
1375 filer_window);
1378 void display_update_view(FilerWindow *filer_window,
1379 DirItem *item,
1380 ViewData *view)
1382 DisplayStyle style = filer_window->display_style;
1383 int w, h;
1384 int wrap_width = -1;
1385 #ifdef GTK2
1386 char *str;
1387 static PangoFontDescription *monospace = NULL;
1389 if (!monospace)
1390 monospace = pango_font_description_from_string("monospace");
1392 if (view->details)
1394 g_object_unref(G_OBJECT(view->details));
1395 view->details = NULL;
1398 str = details(filer_window, item);
1399 if (str)
1401 PangoAttrList *list;
1402 PangoAttribute *attr;
1403 int perm_offset = -1;
1405 view->details = gtk_widget_create_pango_layout(
1406 filer_window->window, str);
1407 g_free(str);
1409 pango_layout_set_font_description(view->details, monospace);
1410 pango_layout_get_size(view->details, &w, &h);
1411 view->details_width = w / PANGO_SCALE;
1412 view->details_height = h / PANGO_SCALE;
1414 attr = pango_attr_underline_new(PANGO_UNDERLINE_SINGLE);
1416 if (filer_window->details_type == DETAILS_SUMMARY)
1417 perm_offset = 5;
1418 else if (filer_window->details_type == DETAILS_PERMISSIONS)
1419 perm_offset = 0;
1420 if (perm_offset > -1)
1422 perm_offset += 4 * applicable(item->uid, item->gid);
1423 attr->start_index = perm_offset;
1424 attr->end_index = perm_offset + 3;
1426 list = pango_attr_list_new();
1427 pango_attr_list_insert(list, attr);
1428 pango_layout_set_attributes(view->details, list);
1431 #else
1432 int font_height = item_font->ascent + item_font->descent;
1434 g_free(view->details);
1435 view->details = details(filer_window, item);
1436 if (view->details)
1438 view->details_width = fixed_width * strlen(view->details);
1439 view->details_height = fixed_font->ascent + fixed_font->descent;
1441 #endif
1443 if (view->image)
1445 pixmap_unref(view->image);
1446 view->image = NULL;
1449 if (filer_window->show_thumbs && item->base_type == TYPE_FILE &&
1450 strcmp(item->mime_type->media_type, "image") == 0)
1452 gchar *path;
1454 path = make_path(filer_window->path, item->leafname)->str;
1456 view->image = g_fscache_lookup_full(pixmap_cache, path,
1457 FSCACHE_LOOKUP_ONLY_NEW, NULL);
1460 if (!view->image)
1462 view->image = item->image;
1463 pixmap_ref(view->image);
1466 #ifdef GTK2
1467 if (view->layout)
1468 g_object_unref(G_OBJECT(view->layout));
1469 view->layout = gtk_widget_create_pango_layout(
1470 filer_window->window, item->leafname);
1472 if (filer_window->details_type == DETAILS_NONE)
1474 if (style == HUGE_ICONS)
1475 wrap_width = HUGE_WRAP * PANGO_SCALE;
1476 else if (style == LARGE_ICONS)
1477 wrap_width = o_large_width.int_value * PANGO_SCALE;
1480 if (wrap_width != -1)
1481 pango_layout_set_width(view->layout, wrap_width);
1483 pango_layout_get_size(view->layout, &w, &h);
1484 view->name_width = w / PANGO_SCALE;
1485 view->name_height = h / PANGO_SCALE;
1486 #else
1487 w = gdk_string_measure(item_font, item->leafname);
1488 h = item_font->ascent + item_font->descent;
1490 view->name_width = w;
1491 view->name_height = h;
1492 view->split_pos = 0;
1493 if (filer_window->details_type == DETAILS_NONE)
1495 if (style == HUGE_ICONS)
1496 wrap_width = HUGE_WRAP;
1497 else if (style == LARGE_ICONS)
1498 wrap_width = o_large_truncate;
1501 if (wrap_width == -1 || view->name_width < wrap_width)
1503 /* Don't wrap */
1504 view->split_height = font_height;
1505 view->split_width = view->name_width;
1506 view->split_pos = 0;
1508 else
1510 int top_len, bot_len, sp;
1512 sp = strlen(item->leafname) / 2;
1513 view->split_pos = sp;
1515 top_len = gdk_text_measure(item_font, item->leafname, sp);
1516 bot_len = gdk_string_measure(item_font, item->leafname + sp);
1518 view->split_width = MAX(top_len, bot_len);
1519 view->split_height = font_height * 2;
1521 #endif
1524 /* 'box' renders a background box if the string is also selected */
1525 static void draw_string(GtkWidget *widget,
1526 #ifdef GTK2
1527 PangoLayout *layout,
1528 #else
1529 GdkFont *font,
1530 char *string,
1531 int len, /* -1 for whole string */
1532 #endif
1533 GdkRectangle *area, /* Area available on screen */
1534 int width, /* Width of the full string */
1535 GtkStateType selection_state,
1536 gboolean selected,
1537 gboolean box)
1539 GdkGC *gc = selected
1540 ? widget->style->fg_gc[selection_state]
1541 : type_gc;
1543 if (selected && box)
1544 gtk_paint_flat_box(widget->style, widget->window,
1545 selection_state, GTK_SHADOW_NONE,
1546 NULL, widget, "text",
1547 area->x, area->y,
1548 MIN(width, area->width),
1549 area->height);
1551 if (width > area->width)
1553 gdk_gc_set_clip_origin(gc, 0, 0);
1554 gdk_gc_set_clip_rectangle(gc, area);
1557 #ifdef GTK2
1558 gdk_draw_layout(widget->window, gc, area->x, area->y, layout);
1559 #else
1560 if (len == -1)
1561 len = strlen(string);
1562 gdk_draw_text(widget->window,
1563 font,
1565 area->x, area->y + font->ascent,
1566 string, len);
1567 #endif
1569 if (width > area->width)
1571 if (!red_gc)
1573 red_gc = gdk_gc_new(widget->window);
1574 gdk_gc_set_foreground(red_gc, &red);
1576 gdk_draw_rectangle(widget->window, red_gc, TRUE,
1577 area->x + area->width - 1, area->y,
1578 1, area->height);
1579 gdk_gc_set_clip_rectangle(gc, NULL);