r1542: Option to switch automatically between Large and Small icons (Stephen Watson).
[rox-filer.git] / ROX-Filer / src / display.c
blob94326c76d426c315d5d56221a3e2b8a4bda3e1c7
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;
82 /* GC for drawing colour filenames */
83 static GdkGC *type_gc = NULL;
85 typedef struct _Template Template;
87 struct _Template {
88 GdkRectangle icon;
89 GdkRectangle leafname;
90 GdkRectangle details;
93 #define SHOW_RECT(ite, template) \
94 g_print("%s: %dx%d+%d+%d %dx%d+%d+%d\n", \
95 item->leafname, \
96 (template)->leafname.width, (template)->leafname.height,\
97 (template)->leafname.x, (template)->leafname.y, \
98 (template)->icon.width, (template)->icon.height, \
99 (template)->icon.x, (template)->icon.y)
101 /* Static prototypes */
102 static void fill_template(GdkRectangle *area, CollectionItem *item,
103 FilerWindow *filer_window, Template *template);
104 static void huge_template(GdkRectangle *area, CollectionItem *colitem,
105 FilerWindow *filer_window, Template *template);
106 static void large_template(GdkRectangle *area, CollectionItem *colitem,
107 FilerWindow *filer_window, Template *template);
108 static void small_template(GdkRectangle *area, CollectionItem *colitem,
109 FilerWindow *filer_window, Template *template);
110 static void huge_full_template(GdkRectangle *area, CollectionItem *colitem,
111 FilerWindow *filer_window, Template *template);
112 static void large_full_template(GdkRectangle *area, CollectionItem *colitem,
113 FilerWindow *filer_window, Template *template);
114 static void small_full_template(GdkRectangle *area, CollectionItem *colitem,
115 FilerWindow *filer_window, Template *template);
116 static void draw_item(GtkWidget *widget,
117 CollectionItem *item,
118 GdkRectangle *area,
119 FilerWindow *filer_window);
120 static gboolean test_point(Collection *collection,
121 int point_x, int point_y,
122 CollectionItem *item,
123 int width, int height,
124 FilerWindow *filer_window);
125 static void display_details_set(FilerWindow *filer_window, DetailsType details);
126 static void display_style_set(FilerWindow *filer_window, DisplayStyle style);
127 static void options_changed(void);
128 static char *details(FilerWindow *filer_window, DirItem *item);
129 static void draw_string(GtkWidget *widget,
130 PangoLayout *layout,
131 GdkRectangle *area, /* Area available on screen */
132 int width, /* Width of the full string */
133 GtkStateType selection_state,
134 gboolean selected,
135 gboolean box);
137 enum {
138 SORT_BY_NAME = 0,
139 SORT_BY_TYPE = 1,
140 SORT_BY_DATE = 2,
141 SORT_BY_SIZE = 3,
144 /****************************************************************
145 * EXTERNAL INTERFACE *
146 ****************************************************************/
148 void display_init()
150 option_add_int(&o_intelligent_sort, "display_intelligent_sort", 1);
151 option_add_int(&o_display_dirs_first, "display_dirs_first", FALSE);
152 option_add_int(&o_display_size, "display_size", LARGE_ICONS);
153 option_add_int(&o_display_details, "display_details", DETAILS_NONE);
154 option_add_int(&o_display_sort_by, "display_sort_by", SORT_BY_NAME);
155 option_add_int(&o_large_width, "display_large_width", 89);
156 option_add_int(&o_small_width, "display_small_width", 250);
157 option_add_int(&o_display_show_hidden, "display_show_hidden", FALSE);
158 option_add_int(&o_display_show_thumbs, "display_show_thumbs", FALSE);
159 option_add_int(&o_display_inherit_options,
160 "display_inherit_options", FALSE);
162 option_add_notify(options_changed);
165 /* A template contains the locations of the three rectangles (for the icon,
166 * name and extra details).
167 * Fill in the empty 'template' with the rectanges for this item.
169 static void fill_template(GdkRectangle *area, CollectionItem *colitem,
170 FilerWindow *filer_window, Template *template)
172 DisplayStyle style = filer_window->display_style;
173 ViewData *view = (ViewData *) colitem->view_data;
175 if (view->details)
177 template->details.width = view->details_width;
178 template->details.height = view->details_height;
180 if (style == SMALL_ICONS)
181 small_full_template(area, colitem,
182 filer_window, template);
183 else if (style == LARGE_ICONS)
184 large_full_template(area, colitem,
185 filer_window, template);
186 else
187 huge_full_template(area, colitem,
188 filer_window, template);
190 else
192 if (style == HUGE_ICONS)
193 huge_template(area, colitem, filer_window, template);
194 else if (style == LARGE_ICONS)
195 large_template(area, colitem, filer_window, template);
196 else
197 small_template(area, colitem, filer_window, template);
201 /* Return the size needed for this item */
202 void calc_size(FilerWindow *filer_window, CollectionItem *colitem,
203 int *width, int *height)
205 int pix_width, pix_height;
206 int w;
207 DisplayStyle style = filer_window->display_style;
208 ViewData *view = (ViewData *) colitem->view_data;
210 if (filer_window->details_type == DETAILS_NONE)
212 if (style == HUGE_ICONS)
214 if (view->image)
216 if (!view->image->huge_pixbuf)
217 pixmap_make_huge(view->image);
218 pix_width = view->image->huge_width;
219 pix_height = view->image->huge_height;
221 else
223 pix_width = HUGE_WIDTH * 3 / 2;
224 pix_height = HUGE_HEIGHT * 3 / 2;
226 *width = MAX(pix_width, view->name_width) + 4;
227 *height = view->name_height + pix_height + 4;
229 else if (style == SMALL_ICONS)
231 w = MIN(view->name_width, o_small_width.int_value);
232 *width = SMALL_WIDTH + 12 + w;
233 *height = MAX(view->name_height, SMALL_HEIGHT) + 4;
235 else
237 if (view->image)
238 pix_width = view->image->width;
239 else
240 pix_width = ICON_WIDTH;
241 *width = MAX(pix_width, view->name_width) + 4;
242 *height = view->name_height + ICON_HEIGHT + 2;
245 else
247 w = view->details_width;
248 if (style == HUGE_ICONS)
250 *width = HUGE_WIDTH + 12 + MAX(w, view->name_width);
251 *height = HUGE_HEIGHT - 4;
253 else if (style == SMALL_ICONS)
255 int text_height;
257 *width = SMALL_WIDTH + view->name_width + 12 + w;
258 text_height = MAX(view->name_height,
259 view->details_height);
260 *height = MAX(text_height, SMALL_HEIGHT) + 4;
262 else
264 *width = ICON_WIDTH + 12 + MAX(w, view->name_width);
265 *height = ICON_HEIGHT;
270 /* Draw this icon (including any symlink or mount symbol) inside the
271 * given rectangle.
273 void draw_huge_icon(GtkWidget *widget,
274 GdkRectangle *area,
275 DirItem *item,
276 MaskedPixmap *image,
277 gboolean selected)
279 int width, height;
280 int image_x;
281 int image_y;
283 if (!image)
284 return;
286 width = image->huge_width;
287 height = image->huge_height;
288 image_x = area->x + ((area->width - width) >> 1);
289 image_y = MAX(0, area->height - height - 6);
291 gdk_pixbuf_render_to_drawable_alpha(
292 selected ? image->huge_pixbuf_lit
293 : image->huge_pixbuf,
294 widget->window,
295 0, 0, /* src */
296 image_x, area->y + image_y, /* dest */
297 width, height,
298 GDK_PIXBUF_ALPHA_FULL, 128, /* (unused) */
299 GDK_RGB_DITHER_NORMAL, 0, 0);
301 if (item->flags & ITEM_FLAG_SYMLINK)
303 gdk_pixbuf_render_to_drawable_alpha(im_symlink->pixbuf,
304 widget->window,
305 0, 0, /* src */
306 image_x, area->y + 2, /* dest */
307 -1, -1,
308 GDK_PIXBUF_ALPHA_FULL, 128, /* (unused) */
309 GDK_RGB_DITHER_NORMAL, 0, 0);
311 else if (item->flags & ITEM_FLAG_MOUNT_POINT)
313 MaskedPixmap *mp = item->flags & ITEM_FLAG_MOUNTED
314 ? im_mounted
315 : im_unmounted;
317 gdk_pixbuf_render_to_drawable_alpha(mp->pixbuf,
318 widget->window,
319 0, 0, /* src */
320 image_x, area->y + 2, /* dest */
321 -1, -1,
322 GDK_PIXBUF_ALPHA_FULL, 128, /* (unused) */
323 GDK_RGB_DITHER_NORMAL, 0, 0);
327 /* Draw this icon (including any symlink or mount symbol) inside the
328 * given rectangle.
330 void draw_large_icon(GtkWidget *widget,
331 GdkRectangle *area,
332 DirItem *item,
333 MaskedPixmap *image,
334 gboolean selected)
336 int width;
337 int height;
338 int image_x;
339 int image_y;
341 if (!image)
342 return;
344 width = MIN(image->width, ICON_WIDTH);
345 height = MIN(image->height, ICON_HEIGHT);
346 image_x = area->x + ((area->width - width) >> 1);
347 image_y = MAX(0, area->height - height - 6);
349 gdk_pixbuf_render_to_drawable_alpha(
350 selected ? image->pixbuf_lit : image->pixbuf,
351 widget->window,
352 0, 0, /* src */
353 image_x, area->y + image_y, /* dest */
354 width, height,
355 GDK_PIXBUF_ALPHA_FULL, 128, /* (unused) */
356 GDK_RGB_DITHER_NORMAL, 0, 0);
358 if (item->flags & ITEM_FLAG_SYMLINK)
360 gdk_pixbuf_render_to_drawable_alpha(im_symlink->pixbuf,
361 widget->window,
362 0, 0, /* src */
363 image_x, area->y + 2, /* dest */
364 -1, -1,
365 GDK_PIXBUF_ALPHA_FULL, 128, /* (unused) */
366 GDK_RGB_DITHER_NORMAL, 0, 0);
368 else if (item->flags & ITEM_FLAG_MOUNT_POINT)
370 MaskedPixmap *mp = item->flags & ITEM_FLAG_MOUNTED
371 ? im_mounted
372 : im_unmounted;
374 gdk_pixbuf_render_to_drawable_alpha(mp->pixbuf,
375 widget->window,
376 0, 0, /* src */
377 image_x, area->y + 2, /* dest */
378 -1, -1,
379 GDK_PIXBUF_ALPHA_FULL, 128, /* (unused) */
380 GDK_RGB_DITHER_NORMAL, 0, 0);
384 /* The sort functions aren't called from outside, but they are
385 * passed as arguments to display_set_sort_fn().
388 #define IS_A_DIR(item) (item->base_type == TYPE_DIRECTORY && \
389 !(item->flags & ITEM_FLAG_APPDIR))
391 #define SORT_DIRS \
392 if (o_display_dirs_first.int_value) { \
393 gboolean id1 = IS_A_DIR(i1); \
394 gboolean id2 = IS_A_DIR(i2); \
395 if (id1 && !id2) return -1; \
396 if (id2 && !id1) return 1; \
399 int sort_by_name(const void *item1, const void *item2)
401 const DirItem *i1 = (DirItem *) ((CollectionItem *) item1)->data;
402 const DirItem *i2 = (DirItem *) ((CollectionItem *) item2)->data;
403 char *n1 = i1->leafname_collate;
404 char *n2 = i2->leafname_collate;
406 SORT_DIRS;
408 if (!o_intelligent_sort.int_value)
409 return strcmp(i1->leafname, i2->leafname);
411 /* The following code was copied from PicoGUI (was LGPL) */
413 /* Sort the files, in a way that should make sense to users.
414 * Case is ignored, punctuation is ignored. If the file contains
415 * numbers, the numbers are sorted numerically.
417 while (*n1 && *n2)
419 char c1 = *n1, c2 = *n2;
421 /* If they are both numbers, sort them numerically */
422 if (isdigit(c1) && isdigit(c2))
424 char *p;
425 unsigned long u1,u2;
426 u1 = strtoul(n1, &p, 10);
427 n1 = p;
428 u2 = strtoul(n2, &p, 10);
429 n2 = p;
430 if (u1 < u2)
431 return -1;
432 else if (u1 > u2)
433 return 1;
434 continue;
437 /* Do a case-insensitive asciibetical sort */
438 c1 = tolower(c1);
439 c2 = tolower(c2);
440 if (c1 < c2)
441 return -1;
442 else if (c1 > c2)
443 return 1;
444 n1++;
445 n2++;
448 /* Compare length */
449 if (*n1)
450 return 1; /* First string is longer, so comes after */
451 if (*n2)
452 return -1;
454 /* If the strings are equal at the end of this, fallback to a straight
455 * ASCII sort so at least it's deterministic.
457 return strcmp(i1->leafname, i2->leafname);
460 int sort_by_type(const void *item1, const void *item2)
462 const DirItem *i1 = (DirItem *) ((CollectionItem *) item1)->data;
463 const DirItem *i2 = (DirItem *) ((CollectionItem *) item2)->data;
464 MIME_type *m1, *m2;
466 int diff = i1->base_type - i2->base_type;
468 if (!diff)
469 diff = (i1->flags & ITEM_FLAG_APPDIR)
470 - (i2->flags & ITEM_FLAG_APPDIR);
471 if (diff)
472 return diff > 0 ? 1 : -1;
474 m1 = i1->mime_type;
475 m2 = i2->mime_type;
477 if (m1 && m2)
479 diff = strcmp(m1->media_type, m2->media_type);
480 if (!diff)
481 diff = strcmp(m1->subtype, m2->subtype);
483 else if (m1 || m2)
484 diff = m1 ? 1 : -1;
485 else
486 diff = 0;
488 if (diff)
489 return diff > 0 ? 1 : -1;
491 return sort_by_name(item1, item2);
494 int sort_by_date(const void *item1, const void *item2)
496 const DirItem *i1 = (DirItem *) ((CollectionItem *) item1)->data;
497 const DirItem *i2 = (DirItem *) ((CollectionItem *) item2)->data;
499 SORT_DIRS;
501 return i1->mtime > i2->mtime ? -1 :
502 i1->mtime < i2->mtime ? 1 :
503 sort_by_name(item1, item2);
506 int sort_by_size(const void *item1, const void *item2)
508 const DirItem *i1 = (DirItem *) ((CollectionItem *) item1)->data;
509 const DirItem *i2 = (DirItem *) ((CollectionItem *) item2)->data;
511 SORT_DIRS;
513 return i1->size > i2->size ? -1 :
514 i1->size < i2->size ? 1 :
515 sort_by_name(item1, item2);
518 /* Make the items as small as possible */
519 void shrink_grid(FilerWindow *filer_window)
521 int i;
522 Collection *col = filer_window->collection;
523 int width = MIN_ITEM_WIDTH;
524 int height = SMALL_HEIGHT;
526 for (i = 0; i < col->number_of_items; i++)
528 int w, h;
530 calc_size(filer_window, &col->items[i], &w, &h);
531 if (w > width)
532 width = w;
533 if (h > height)
534 height = h;
537 collection_set_item_size(col, width, height);
540 void display_set_sort_fn(FilerWindow *filer_window,
541 int (*fn)(const void *a, const void *b))
543 if (filer_window->sort_fn == fn)
544 return;
546 filer_window->sort_fn = fn;
548 collection_qsort(filer_window->collection,
549 filer_window->sort_fn);
552 void display_set_layout(FilerWindow *filer_window,
553 DisplayStyle style,
554 DetailsType details)
556 g_return_if_fail(filer_window != NULL);
558 display_style_set(filer_window, style);
559 display_details_set(filer_window, details);
561 shrink_grid(filer_window);
563 if (o_filer_auto_resize.int_value != RESIZE_NEVER)
564 filer_window_autosize(filer_window, TRUE);
567 /* Set the 'Show Thumbnails' flag for this window */
568 void display_set_thumbs(FilerWindow *filer_window, gboolean thumbs)
570 if (filer_window->show_thumbs == thumbs)
571 return;
573 filer_window->show_thumbs = thumbs;
575 display_update_views(filer_window);
577 gtk_widget_queue_draw(GTK_WIDGET(filer_window->collection));
579 if (!thumbs)
580 filer_cancel_thumbnails(filer_window);
582 filer_set_title(filer_window);
584 filer_create_thumbs(filer_window);
587 /* Set the 'Show Hidden' flag for this window */
588 void display_set_hidden(FilerWindow *filer_window, gboolean hidden)
590 if (filer_window->show_hidden == hidden)
591 return;
593 filer_window->show_hidden = hidden;
595 filer_detach_rescan(filer_window); /* (updates titlebar) */
598 /* Highlight (wink or cursor) this item in the filer window. If the item
599 * isn't already there but we're scanning then highlight it if it
600 * appears later.
602 void display_set_autoselect(FilerWindow *filer_window, const gchar *leaf)
604 Collection *col;
605 int i;
607 g_return_if_fail(filer_window != NULL);
608 col = filer_window->collection;
610 for (i = 0; i < col->number_of_items; i++)
612 DirItem *item = (DirItem *) col->items[i].data;
614 if (strcmp(item->leafname, leaf) == 0)
616 if (col->cursor_item != -1)
617 collection_set_cursor_item(col, i);
618 else
619 collection_wink_item(col, i);
620 leaf = NULL;
621 goto out;
625 out:
626 g_free(filer_window->auto_select);
627 if (leaf)
628 filer_window->auto_select = g_strdup(leaf);
629 else
630 filer_window->auto_select = NULL;
633 gboolean display_is_truncated(FilerWindow *filer_window, int i)
635 Template template;
636 Collection *collection = filer_window->collection;
637 CollectionItem *colitem = &collection->items[i];
638 int col = i % collection->columns;
639 int row = i / collection->columns;
640 GdkRectangle area;
641 ViewData *view = (ViewData *) colitem->view_data;
643 if (filer_window->display_style == LARGE_ICONS ||
644 filer_window->display_style == HUGE_ICONS)
645 return FALSE; /* These wrap rather than truncate */
647 area.x = col * collection->item_width;
648 area.y = row * collection->item_height;
649 area.height = collection->item_height;
651 if (col == collection->columns - 1)
652 area.width = GTK_WIDGET(collection)->allocation.width - area.x;
653 else
654 area.width = collection->item_width;
656 fill_template(&area, colitem, filer_window, &template);
658 return template.leafname.width < view->name_width;
661 /* Change the icon size (wraps) */
662 void display_change_size(FilerWindow *filer_window, gboolean bigger)
664 DisplayStyle new;
666 g_return_if_fail(filer_window != NULL);
668 switch (filer_window->display_style)
670 case LARGE_ICONS:
671 new = bigger ? HUGE_ICONS : SMALL_ICONS;
672 break;
673 case HUGE_ICONS:
674 new = bigger ? SMALL_ICONS : LARGE_ICONS;
675 break;
676 default:
677 new = bigger ? LARGE_ICONS : HUGE_ICONS;
678 break;
681 display_set_layout(filer_window, new, filer_window->details_type);
684 ViewData *display_create_viewdata(FilerWindow *filer_window, DirItem *item)
686 ViewData *view;
688 view = g_new(ViewData, 1);
690 view->layout = NULL;
691 view->details = NULL;
692 view->image = NULL;
694 display_update_view(filer_window, item, view, TRUE);
696 return view;
699 void display_free_colitem(Collection *collection, CollectionItem *colitem)
701 ViewData *view = (ViewData *) colitem->view_data;
703 if (!view)
704 return;
706 if (view->layout)
708 g_object_unref(G_OBJECT(view->layout));
709 view->layout = NULL;
711 if (view->details)
712 g_object_unref(G_OBJECT(view->details));
714 if (view->image)
715 g_object_unref(view->image);
717 g_free(view);
720 /* Recalculate all the ViewData structs for this window.
721 * Useful when the display style has changed.
723 void display_update_views(FilerWindow *filer_window)
725 Collection *collection = filer_window->collection;
726 int i;
728 for (i = 0; i < collection->number_of_items; i++)
730 CollectionItem *ci = &collection->items[i];
732 display_update_view(filer_window, (DirItem *) ci->data,
733 (ViewData *) ci->view_data, TRUE);
737 /****************************************************************
738 * INTERNAL FUNCTIONS *
739 ****************************************************************/
741 static void options_changed(void)
743 GList *next;
745 for (next = all_filer_windows; next; next = next->next)
747 FilerWindow *filer_window = (FilerWindow *) next->data;
749 if (o_large_width.has_changed || o_small_width.has_changed)
751 /* Recreate PangoLayout */
752 display_update_views(filer_window);
755 if (o_intelligent_sort.has_changed ||
756 o_display_dirs_first.has_changed)
758 collection_qsort(filer_window->collection,
759 filer_window->sort_fn);
761 shrink_grid(filer_window);
762 gtk_widget_queue_draw(filer_window->window);
766 static void huge_template(GdkRectangle *area, CollectionItem *colitem,
767 FilerWindow *filer_window, Template *template)
769 int col_width = filer_window->collection->item_width;
770 int text_x, text_y;
771 ViewData *view = (ViewData *) colitem->view_data;
772 MaskedPixmap *image = view->image;
774 if (image)
776 if (!image->huge_pixbuf)
777 pixmap_make_huge(image);
778 template->icon.width = image->huge_width;
779 template->icon.height = image->huge_height;
781 else
783 template->icon.width = HUGE_WIDTH * 3 / 2;
784 template->icon.height = HUGE_HEIGHT;
787 template->leafname.width = view->name_width;
788 template->leafname.height = view->name_height;
790 text_x = area->x + ((col_width - template->leafname.width) >> 1);
791 text_y = area->y + area->height - template->leafname.height;
793 template->leafname.x = text_x;
794 template->leafname.y = text_y;
796 template->icon.x = area->x + ((col_width - template->icon.width) >> 1);
797 template->icon.y = template->leafname.y - template->icon.height - 2;
800 static void large_template(GdkRectangle *area, CollectionItem *colitem,
801 FilerWindow *filer_window, Template *template)
803 int col_width = filer_window->collection->item_width;
804 int iwidth, iheight;
805 int image_x;
806 int image_y;
807 ViewData *view = (ViewData *) colitem->view_data;
808 MaskedPixmap *image = view->image;
810 int text_x, text_y;
812 if (image)
814 iwidth = MIN(image->width, ICON_WIDTH);
815 iheight = MIN(image->height + 6, ICON_HEIGHT);
817 else
819 iwidth = ICON_WIDTH;
820 iheight = ICON_HEIGHT;
822 image_x = area->x + ((col_width - iwidth) >> 1);
824 template->leafname.width = view->name_width;
825 template->leafname.height = view->name_height;
827 text_x = area->x + ((col_width - template->leafname.width) >> 1);
828 text_y = area->y + ICON_HEIGHT + 2;
830 template->leafname.x = text_x;
831 template->leafname.y = text_y;
833 image_y = text_y - iheight;
834 image_y = MAX(area->y, image_y);
836 template->icon.x = image_x;
837 template->icon.y = image_y;
838 template->icon.width = iwidth;
839 template->icon.height = MIN(ICON_HEIGHT, iheight);
842 static void small_template(GdkRectangle *area, CollectionItem *colitem,
843 FilerWindow *filer_window, Template *template)
845 int text_x = area->x + SMALL_WIDTH + 4;
846 int low_text_y;
847 int max_text_width = area->width - SMALL_WIDTH - 4;
848 ViewData *view = (ViewData *) colitem->view_data;
850 low_text_y = area->y + area->height / 2 - view->name_height / 2;
852 template->leafname.x = text_x;
853 template->leafname.y = low_text_y;
854 template->leafname.width = MIN(max_text_width, view->name_width);
855 template->leafname.height = view->name_height;
857 template->icon.x = area->x;
858 template->icon.y = area->y + 1;
859 template->icon.width = SMALL_WIDTH;
860 template->icon.height = SMALL_HEIGHT;
863 static void huge_full_template(GdkRectangle *area, CollectionItem *colitem,
864 FilerWindow *filer_window, Template *template)
866 int max_text_width = area->width - HUGE_WIDTH - 4;
867 ViewData *view = (ViewData *) colitem->view_data;
868 MaskedPixmap *image = view->image;
870 if (image)
872 if (!image->huge_pixbuf)
873 pixmap_make_huge(image);
874 template->icon.width = image->huge_width;
875 template->icon.height = image->huge_height;
877 else
879 template->icon.width = HUGE_WIDTH * 3 / 2;
880 template->icon.height = HUGE_HEIGHT;
883 template->icon.x = area->x + (HUGE_WIDTH - template->icon.width) / 2;
884 template->icon.y = area->y + (area->height - template->icon.height) / 2;
886 template->leafname.x = area->x + HUGE_WIDTH + 4;
887 template->leafname.y = area->y + area->height / 2
888 - (view->name_height + 2 + view->details_height) / 2;
889 template->leafname.width = MIN(max_text_width, view->name_width);
890 template->leafname.height = view->name_height;
892 if (!image)
893 return; /* Not scanned yet */
895 template->details.x = template->leafname.x;
896 template->details.y = template->leafname.y + view->name_height + 2;
899 static void large_full_template(GdkRectangle *area, CollectionItem *colitem,
900 FilerWindow *filer_window, Template *template)
902 int max_text_width = area->width - ICON_WIDTH - 4;
903 ViewData *view = (ViewData *) colitem->view_data;
904 MaskedPixmap *image = view->image;
906 if (image)
908 template->icon.width = image->width;
909 template->icon.height = image->height;
911 else
913 template->icon.width = ICON_WIDTH;
914 template->icon.height = ICON_HEIGHT;
917 template->icon.x = area->x + (ICON_WIDTH - template->icon.width) / 2;
918 template->icon.y = area->y + (area->height - template->icon.height) / 2;
921 template->leafname.x = area->x + ICON_WIDTH + 4;
922 template->leafname.y = area->y + area->height / 2
923 - (view->name_height + 2 + view->details_height) / 2;
924 template->leafname.width = MIN(max_text_width, view->name_width);
925 template->leafname.height = view->name_height;
927 if (!image)
928 return; /* Not scanned yet */
930 template->details.x = template->leafname.x;
931 template->details.y = template->leafname.y + view->name_height + 2;
934 static void small_full_template(GdkRectangle *area, CollectionItem *colitem,
935 FilerWindow *filer_window, Template *template)
937 int col_width = filer_window->collection->item_width;
938 ViewData *view = (ViewData *) colitem->view_data;
940 small_template(area, colitem, filer_window, template);
942 if (!view->image)
943 return; /* Not scanned yet */
945 template->details.x = area->x + col_width - template->details.width;
946 template->details.y = area->y + area->height / 2 - \
947 view->details_height / 2;
950 #define INSIDE(px, py, area) \
951 (px >= area.x && py >= area.y && \
952 px <= area.x + area.width && py <= area.y + area.height)
954 static gboolean test_point(Collection *collection,
955 int point_x, int point_y,
956 CollectionItem *colitem,
957 int width, int height,
958 FilerWindow *filer_window)
960 Template template;
961 GdkRectangle area;
962 ViewData *view = (ViewData *) colitem->view_data;
964 area.x = 0;
965 area.y = 0;
966 area.width = width;
967 area.height = height;
969 fill_template(&area, colitem, filer_window, &template);
971 return INSIDE(point_x, point_y, template.leafname) ||
972 INSIDE(point_x, point_y, template.icon) ||
973 (view->details && INSIDE(point_x, point_y, template.details));
976 static void draw_small_icon(GtkWidget *widget,
977 GdkRectangle *area,
978 DirItem *item,
979 MaskedPixmap *image,
980 gboolean selected)
982 int width, height, image_x, image_y;
984 if (!image)
985 return;
987 if (!image->sm_pixbuf)
988 pixmap_make_small(image);
990 width = MIN(image->sm_width, SMALL_WIDTH);
991 height = MIN(image->sm_height, SMALL_HEIGHT);
992 image_x = area->x + ((area->width - width) >> 1);
993 image_y = MAX(0, SMALL_HEIGHT - image->sm_height);
995 gdk_pixbuf_render_to_drawable_alpha(
996 selected ? image->sm_pixbuf_lit : image->sm_pixbuf,
997 widget->window,
998 0, 0, /* src */
999 image_x, area->y + image_y, /* dest */
1000 width, height,
1001 GDK_PIXBUF_ALPHA_FULL, 128, /* (unused) */
1002 GDK_RGB_DITHER_NORMAL, 0, 0);
1004 if (item->flags & ITEM_FLAG_SYMLINK)
1006 gdk_pixbuf_render_to_drawable_alpha(im_symlink->pixbuf,
1007 widget->window,
1008 0, 0, /* src */
1009 image_x, area->y + 8, /* dest */
1010 -1, -1,
1011 GDK_PIXBUF_ALPHA_FULL, 128, /* (unused) */
1012 GDK_RGB_DITHER_NORMAL, 0, 0);
1014 else if (item->flags & ITEM_FLAG_MOUNT_POINT)
1016 MaskedPixmap *mp = item->flags & ITEM_FLAG_MOUNTED
1017 ? im_mounted
1018 : im_unmounted;
1020 gdk_pixbuf_render_to_drawable_alpha(mp->pixbuf,
1021 widget->window,
1022 0, 0, /* src */
1023 image_x + 2, area->y + 2, /* dest */
1024 -1, -1,
1025 GDK_PIXBUF_ALPHA_FULL, 128, /* (unused) */
1026 GDK_RGB_DITHER_NORMAL, 0, 0);
1030 /* Return a new string giving details of this item, or NULL if details
1031 * are not being displayed. If details are not yet available, return
1032 * a string of the right length.
1034 static char *details(FilerWindow *filer_window, DirItem *item)
1036 mode_t m = item->mode;
1037 guchar *buf = NULL;
1038 gboolean scanned = item->base_type != TYPE_UNKNOWN;
1040 if (filer_window->details_type == DETAILS_NONE)
1041 return NULL;
1043 if (scanned && item->lstat_errno)
1044 buf = g_strdup_printf(_("lstat(2) failed: %s"),
1045 g_strerror(item->lstat_errno));
1046 else if (filer_window->details_type == DETAILS_SUMMARY)
1048 gchar *time;
1050 if (!scanned)
1051 return g_strdup("XXXX ---,---,---/--"
1052 #ifdef S_ISVTX
1054 #endif
1055 " 12345678 12345678 "
1056 "1234M 00:00:00 01 Mmm Yyyy");
1058 time = pretty_time(&item->mtime);
1060 buf = g_strdup_printf("%s %s %-8.8s %-8.8s %s %s",
1061 item->flags & ITEM_FLAG_APPDIR? "App " :
1062 S_ISDIR(m) ? "Dir " :
1063 S_ISCHR(m) ? "Char" :
1064 S_ISBLK(m) ? "Blck" :
1065 S_ISLNK(m) ? "Link" :
1066 S_ISSOCK(m) ? "Sock" :
1067 S_ISFIFO(m) ? "Pipe" : "File",
1068 pretty_permissions(m),
1069 user_name(item->uid),
1070 group_name(item->gid),
1071 format_size_aligned(item->size),
1072 time);
1073 g_free(time);
1075 else if (filer_window->details_type == DETAILS_TYPE)
1077 MIME_type *type = item->mime_type;
1079 if (!scanned)
1080 return g_strdup("(application/octet-stream)");
1082 buf = g_strdup_printf("(%s/%s)",
1083 type->media_type, type->subtype);
1085 else if (filer_window->details_type == DETAILS_TIMES)
1087 guchar *ctime, *mtime, *atime;
1089 ctime = pretty_time(&item->ctime);
1090 mtime = pretty_time(&item->mtime);
1091 atime = pretty_time(&item->atime);
1093 buf = g_strdup_printf("a[%s] c[%s] m[%s]", atime, ctime, mtime);
1094 g_free(ctime);
1095 g_free(mtime);
1096 g_free(atime);
1098 else if (filer_window->details_type == DETAILS_PERMISSIONS)
1100 if (!scanned)
1101 return g_strdup("---,---,---/--"
1102 #ifdef S_ISVTX
1104 #endif
1105 " 12345678 12345678");
1107 buf = g_strdup_printf("%s %-8.8s %-8.8s",
1108 pretty_permissions(m),
1109 user_name(item->uid),
1110 group_name(item->gid));
1112 else
1114 if (!scanned)
1116 if (filer_window->display_style == SMALL_ICONS)
1117 return g_strdup("12345M");
1118 else
1119 return g_strdup("12345 bytes");
1122 if (item->base_type != TYPE_DIRECTORY)
1124 if (filer_window->display_style == SMALL_ICONS)
1125 buf = g_strdup(format_size_aligned(item->size));
1126 else
1127 buf = g_strdup(format_size(item->size));
1129 else
1130 buf = g_strdup("-");
1133 return buf;
1136 static void draw_item(GtkWidget *widget,
1137 CollectionItem *colitem,
1138 GdkRectangle *area,
1139 FilerWindow *filer_window)
1141 DirItem *item = (DirItem *) colitem->data;
1142 gboolean selected = colitem->selected;
1143 Template template;
1144 ViewData *view = (ViewData *) colitem->view_data;
1146 g_return_if_fail(view != NULL);
1148 fill_template(area, colitem, filer_window, &template);
1150 /* Set up GC for coloured file types */
1151 if (!type_gc)
1152 type_gc = gdk_gc_new(widget->window);
1154 gdk_gc_set_foreground(type_gc, type_get_colour(item,
1155 &widget->style->fg[GTK_STATE_NORMAL]));
1157 if (template.icon.width <= SMALL_WIDTH &&
1158 template.icon.height <= SMALL_HEIGHT)
1160 draw_small_icon(widget, &template.icon,
1161 item, view->image, selected);
1163 else if (template.icon.width <= ICON_WIDTH &&
1164 template.icon.height <= ICON_HEIGHT)
1166 draw_large_icon(widget, &template.icon,
1167 item, view->image, selected);
1169 else
1171 draw_huge_icon(widget, &template.icon,
1172 item, view->image, selected);
1175 draw_string(widget, view->layout,
1176 &template.leafname,
1177 view->name_width,
1178 filer_window->selection_state,
1179 selected, TRUE);
1180 if (view->details)
1181 draw_string(widget, view->details,
1182 &template.details,
1183 template.details.width,
1184 filer_window->selection_state,
1185 selected, TRUE);
1188 /* Note: Call shrink_grid after this */
1189 static void display_details_set(FilerWindow *filer_window, DetailsType details)
1191 if (filer_window->details_type == details)
1192 return;
1193 filer_window->details_type = details;
1194 display_update_views(filer_window);
1196 gtk_widget_queue_draw(GTK_WIDGET(filer_window->collection));
1199 /* Note: Call shrink_grid after this */
1200 static void display_style_set(FilerWindow *filer_window, DisplayStyle style)
1202 if (filer_window->display_style == style)
1203 return;
1205 filer_window->display_style = style;
1207 display_update_views(filer_window);
1209 collection_set_functions(filer_window->collection,
1210 (CollectionDrawFunc) draw_item,
1211 (CollectionTestFunc) test_point,
1212 filer_window);
1215 void display_update_view(FilerWindow *filer_window,
1216 DirItem *item,
1217 ViewData *view,
1218 gboolean update_name_layout)
1220 DisplayStyle style = filer_window->display_style;
1221 int w, h;
1222 int wrap_width = -1;
1223 char *str;
1224 static PangoFontDescription *monospace = NULL;
1226 if (!monospace)
1227 monospace = pango_font_description_from_string("monospace");
1229 if (view->details)
1231 g_object_unref(G_OBJECT(view->details));
1232 view->details = NULL;
1235 str = details(filer_window, item);
1236 if (str)
1238 PangoAttrList *list;
1239 int perm_offset = -1;
1241 view->details = gtk_widget_create_pango_layout(
1242 filer_window->window, str);
1243 g_free(str);
1245 pango_layout_set_font_description(view->details, monospace);
1246 pango_layout_get_size(view->details, &w, &h);
1247 view->details_width = w / PANGO_SCALE;
1248 view->details_height = h / PANGO_SCALE;
1250 if (filer_window->details_type == DETAILS_SUMMARY)
1251 perm_offset = 5;
1252 else if (filer_window->details_type == DETAILS_PERMISSIONS)
1253 perm_offset = 0;
1254 if (perm_offset > -1)
1256 PangoAttribute *attr;
1258 attr = pango_attr_underline_new(PANGO_UNDERLINE_SINGLE);
1260 perm_offset += 4 * applicable(item->uid, item->gid);
1261 attr->start_index = perm_offset;
1262 attr->end_index = perm_offset + 3;
1264 list = pango_attr_list_new();
1265 pango_attr_list_insert(list, attr);
1266 pango_layout_set_attributes(view->details, list);
1270 if (view->image)
1272 g_object_unref(view->image);
1273 view->image = NULL;
1276 if (filer_window->show_thumbs && item->base_type == TYPE_FILE &&
1277 strcmp(item->mime_type->media_type, "image") == 0)
1279 gchar *path;
1281 path = make_path(filer_window->real_path, item->leafname)->str;
1283 view->image = g_fscache_lookup_full(pixmap_cache, path,
1284 FSCACHE_LOOKUP_ONLY_NEW, NULL);
1287 if (!view->image)
1289 view->image = item->image;
1290 if (view->image)
1291 g_object_ref(view->image);
1294 if (view->layout && update_name_layout)
1296 g_object_unref(G_OBJECT(view->layout));
1297 view->layout = NULL;
1300 if (view->layout)
1302 /* Do nothing */
1304 else if (g_utf8_validate(item->leafname, -1, NULL))
1306 view->layout = gtk_widget_create_pango_layout(
1307 filer_window->window, item->leafname);
1309 else
1311 PangoAttrList *list;
1312 PangoAttribute *attr;
1313 gchar *utf8;
1315 utf8 = to_utf8(item->leafname);
1316 view->layout = gtk_widget_create_pango_layout(
1317 filer_window->window, utf8);
1318 g_free(utf8);
1320 attr = pango_attr_foreground_new(0xffff, 0, 0);
1321 attr->start_index = 0;
1322 attr->end_index = 1000;
1323 list = pango_attr_list_new();
1324 pango_attr_list_insert(list, attr);
1325 pango_layout_set_attributes(view->layout, list);
1328 if (filer_window->details_type == DETAILS_NONE)
1330 if (style == HUGE_ICONS)
1331 wrap_width = HUGE_WRAP * PANGO_SCALE;
1332 else if (style == LARGE_ICONS)
1333 wrap_width = o_large_width.int_value * PANGO_SCALE;
1336 if (wrap_width != -1)
1337 pango_layout_set_width(view->layout, wrap_width);
1339 pango_layout_get_size(view->layout, &w, &h);
1340 view->name_width = w / PANGO_SCALE;
1341 view->name_height = h / PANGO_SCALE;
1344 /* 'box' renders a background box if the string is also selected */
1345 static void draw_string(GtkWidget *widget,
1346 PangoLayout *layout,
1347 GdkRectangle *area, /* Area available on screen */
1348 int width, /* Width of the full string */
1349 GtkStateType selection_state,
1350 gboolean selected,
1351 gboolean box)
1353 GdkGC *gc = selected
1354 ? widget->style->fg_gc[selection_state]
1355 : type_gc;
1357 if (selected && box)
1358 gtk_paint_flat_box(widget->style, widget->window,
1359 selection_state, GTK_SHADOW_NONE,
1360 NULL, widget, "text",
1361 area->x, area->y,
1362 MIN(width, area->width),
1363 area->height);
1365 if (width > area->width)
1367 gdk_gc_set_clip_origin(gc, 0, 0);
1368 gdk_gc_set_clip_rectangle(gc, area);
1371 gdk_draw_layout(widget->window, gc, area->x, area->y, layout);
1373 if (width > area->width)
1375 static GdkGC *red_gc = NULL;
1377 if (!red_gc)
1379 gboolean success;
1380 GdkColor red = {0, 0xffff, 0, 0};
1382 red_gc = gdk_gc_new(widget->window);
1383 gdk_colormap_alloc_colors(
1384 gtk_widget_get_colormap(widget),
1385 &red, 1, FALSE, TRUE, &success);
1386 gdk_gc_set_foreground(red_gc, &red);
1388 gdk_draw_rectangle(widget->window, red_gc, TRUE,
1389 area->x + area->width - 1, area->y,
1390 1, area->height);
1391 gdk_gc_set_clip_rectangle(gc, NULL);