r2227: Changes menus for new details stuff.
[rox-filer.git] / ROX-Filer / src / display.c
blobee38190dbacdd426fa765422e9cb483898f964c7
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 "support.h"
45 #include "gui_support.h"
46 #include "filer.h"
47 #include "pixmaps.h"
48 #include "menu.h"
49 #include "dnd.h"
50 #include "run.h"
51 #include "mount.h"
52 #include "type.h"
53 #include "options.h"
54 #include "action.h"
55 #include "minibuffer.h"
56 #include "dir.h"
57 #include "diritem.h"
58 #include "fscache.h"
59 #include "view_iface.h"
61 #define HUGE_WRAP (1.5 * o_large_width.int_value)
63 /* Options bits */
64 static Option o_display_dirs_first;
65 Option o_display_size;
66 Option o_display_details;
67 Option o_display_sort_by;
68 static Option o_large_width;
69 Option o_small_width;
70 Option o_display_show_hidden;
71 Option o_display_show_thumbs;
72 Option o_display_inherit_options;
74 /* Static prototypes */
75 static void display_details_set(FilerWindow *filer_window, DetailsType details);
76 static void display_style_set(FilerWindow *filer_window, DisplayStyle style);
77 static void options_changed(void);
78 static char *details(FilerWindow *filer_window, DirItem *item);
80 enum {
81 SORT_BY_NAME = 0,
82 SORT_BY_TYPE = 1,
83 SORT_BY_DATE = 2,
84 SORT_BY_SIZE = 3,
87 /****************************************************************
88 * EXTERNAL INTERFACE *
89 ****************************************************************/
91 void display_init()
93 option_add_int(&o_display_dirs_first, "display_dirs_first", FALSE);
94 option_add_int(&o_display_size, "display_size", LARGE_ICONS);
95 option_add_int(&o_display_details, "display_details", DETAILS_NONE);
96 option_add_int(&o_display_sort_by, "display_sort_by", SORT_BY_NAME);
97 option_add_int(&o_large_width, "display_large_width", 89);
98 option_add_int(&o_small_width, "display_small_width", 250);
99 option_add_int(&o_display_show_hidden, "display_show_hidden", FALSE);
100 option_add_int(&o_display_show_thumbs, "display_show_thumbs", FALSE);
101 option_add_int(&o_display_inherit_options,
102 "display_inherit_options", FALSE);
104 option_add_notify(options_changed);
107 /* Draw this icon (including any symlink or mount symbol) inside the
108 * given rectangle.
110 void draw_large_icon(GtkWidget *widget,
111 GdkRectangle *area,
112 DirItem *item,
113 MaskedPixmap *image,
114 gboolean selected)
116 int width;
117 int height;
118 int image_x;
119 int image_y;
121 if (!image)
122 return;
124 width = MIN(image->width, ICON_WIDTH);
125 height = MIN(image->height, ICON_HEIGHT);
126 image_x = area->x + ((area->width - width) >> 1);
127 image_y = MAX(0, area->height - height - 6);
129 gdk_pixbuf_render_to_drawable_alpha(
130 selected ? image->pixbuf_lit : image->pixbuf,
131 widget->window,
132 0, 0, /* src */
133 image_x, area->y + image_y, /* dest */
134 width, height,
135 GDK_PIXBUF_ALPHA_FULL, 128, /* (unused) */
136 GDK_RGB_DITHER_NORMAL, 0, 0);
138 if (item->flags & ITEM_FLAG_SYMLINK)
140 gdk_pixbuf_render_to_drawable_alpha(im_symlink->pixbuf,
141 widget->window,
142 0, 0, /* src */
143 image_x, area->y + 2, /* dest */
144 -1, -1,
145 GDK_PIXBUF_ALPHA_FULL, 128, /* (unused) */
146 GDK_RGB_DITHER_NORMAL, 0, 0);
148 else if (item->flags & ITEM_FLAG_MOUNT_POINT)
150 MaskedPixmap *mp = item->flags & ITEM_FLAG_MOUNTED
151 ? im_mounted
152 : im_unmounted;
154 gdk_pixbuf_render_to_drawable_alpha(mp->pixbuf,
155 widget->window,
156 0, 0, /* src */
157 image_x, area->y + 2, /* dest */
158 -1, -1,
159 GDK_PIXBUF_ALPHA_FULL, 128, /* (unused) */
160 GDK_RGB_DITHER_NORMAL, 0, 0);
164 void draw_small_icon(GdkWindow *window, GdkRectangle *area,
165 DirItem *item, MaskedPixmap *image, gboolean selected)
167 int width, height, image_x, image_y;
169 if (!image)
170 return;
172 if (!image->sm_pixbuf)
173 pixmap_make_small(image);
175 width = MIN(image->sm_width, SMALL_WIDTH);
176 height = MIN(image->sm_height, SMALL_HEIGHT);
177 image_x = area->x + ((area->width - width) >> 1);
178 image_y = MAX(0, SMALL_HEIGHT - image->sm_height);
180 gdk_pixbuf_render_to_drawable_alpha(
181 selected ? image->sm_pixbuf_lit : image->sm_pixbuf,
182 window,
183 0, 0, /* src */
184 image_x, area->y + image_y, /* dest */
185 width, height,
186 GDK_PIXBUF_ALPHA_FULL, 128, /* (unused) */
187 GDK_RGB_DITHER_NORMAL, 0, 0);
189 if (item->flags & ITEM_FLAG_SYMLINK)
191 gdk_pixbuf_render_to_drawable_alpha(im_symlink->pixbuf,
192 window,
193 0, 0, /* src */
194 image_x, area->y + 8, /* dest */
195 -1, -1,
196 GDK_PIXBUF_ALPHA_FULL, 128, /* (unused) */
197 GDK_RGB_DITHER_NORMAL, 0, 0);
199 else if (item->flags & ITEM_FLAG_MOUNT_POINT)
201 MaskedPixmap *mp = item->flags & ITEM_FLAG_MOUNTED
202 ? im_mounted
203 : im_unmounted;
205 gdk_pixbuf_render_to_drawable_alpha(mp->pixbuf,
206 window,
207 0, 0, /* src */
208 image_x + 2, area->y + 2, /* dest */
209 -1, -1,
210 GDK_PIXBUF_ALPHA_FULL, 128, /* (unused) */
211 GDK_RGB_DITHER_NORMAL, 0, 0);
215 /* The sort functions aren't called from outside, but they are
216 * passed as arguments to display_set_sort_fn().
219 #define IS_A_DIR(item) (item->base_type == TYPE_DIRECTORY && \
220 !(item->flags & ITEM_FLAG_APPDIR))
222 #define SORT_DIRS \
223 if (o_display_dirs_first.int_value) { \
224 gboolean id1 = IS_A_DIR(i1); \
225 gboolean id2 = IS_A_DIR(i2); \
226 if (id1 && !id2) return -1; \
227 if (id2 && !id1) return 1; \
230 int sort_by_name(const void *item1, const void *item2)
232 const DirItem *i1 = (DirItem *) item1;
233 const DirItem *i2 = (DirItem *) item2;
234 CollateKey *n1 = i1->leafname_collate;
235 CollateKey *n2 = i2->leafname_collate;
236 int retval;
238 SORT_DIRS;
240 retval = collate_key_cmp(n1, n2);
242 return retval ? retval : strcmp(i1->leafname, i2->leafname);
245 int sort_by_type(const void *item1, const void *item2)
247 const DirItem *i1 = (DirItem *) item1;
248 const DirItem *i2 = (DirItem *) item2;
249 MIME_type *m1, *m2;
251 int diff = i1->base_type - i2->base_type;
253 if (!diff)
254 diff = (i1->flags & ITEM_FLAG_APPDIR)
255 - (i2->flags & ITEM_FLAG_APPDIR);
256 if (diff)
257 return diff > 0 ? 1 : -1;
259 m1 = i1->mime_type;
260 m2 = i2->mime_type;
262 if (m1 && m2)
264 diff = strcmp(m1->media_type, m2->media_type);
265 if (!diff)
266 diff = strcmp(m1->subtype, m2->subtype);
268 else if (m1 || m2)
269 diff = m1 ? 1 : -1;
270 else
271 diff = 0;
273 if (diff)
274 return diff > 0 ? 1 : -1;
276 return sort_by_name(item1, item2);
279 int sort_by_owner(const void *item1, const void *item2)
281 const DirItem *i1 = (DirItem *) item1;
282 const DirItem *i2 = (DirItem *) item2;
284 return i1->uid < i2->uid ? -1 :
285 i1->uid > i2->uid ? 1 :
286 sort_by_name(item1, item2);
289 int sort_by_group(const void *item1, const void *item2)
291 const DirItem *i1 = (DirItem *) item1;
292 const DirItem *i2 = (DirItem *) item2;
294 return i1->gid < i2->gid ? -1 :
295 i1->gid > i2->gid ? 1 :
296 sort_by_name(item1, item2);
299 int sort_by_date(const void *item1, const void *item2)
301 const DirItem *i1 = (DirItem *) item1;
302 const DirItem *i2 = (DirItem *) item2;
304 /* SORT_DIRS; -- too confusing! */
306 return i1->mtime < i2->mtime ? -1 :
307 i1->mtime > i2->mtime ? 1 :
308 sort_by_name(item1, item2);
311 int sort_by_size(const void *item1, const void *item2)
313 const DirItem *i1 = (DirItem *) item1;
314 const DirItem *i2 = (DirItem *) item2;
316 SORT_DIRS;
318 return i1->size < i2->size ? -1 :
319 i1->size > i2->size ? 1 :
320 sort_by_name(item1, item2);
323 void display_set_sort_fn(FilerWindow *filer_window,
324 int (*fn)(const void *a, const void *b))
326 if (filer_window->sort_fn == fn)
327 return;
329 filer_window->sort_fn = fn;
331 view_sort(filer_window->view);
334 void display_set_layout(FilerWindow *filer_window,
335 DisplayStyle style,
336 DetailsType details)
338 g_return_if_fail(filer_window != NULL);
340 display_style_set(filer_window, style);
341 display_details_set(filer_window, details);
343 /* Recreate layouts because wrapping may have changed */
344 view_style_changed(filer_window->view, VIEW_UPDATE_NAME);
346 if (o_filer_auto_resize.int_value != RESIZE_NEVER)
347 filer_window_autosize(filer_window);
350 /* Set the 'Show Thumbnails' flag for this window */
351 void display_set_thumbs(FilerWindow *filer_window, gboolean thumbs)
353 if (filer_window->show_thumbs == thumbs)
354 return;
356 filer_window->show_thumbs = thumbs;
358 view_style_changed(filer_window->view, VIEW_UPDATE_VIEWDATA);
360 if (!thumbs)
361 filer_cancel_thumbnails(filer_window);
363 filer_set_title(filer_window);
365 filer_create_thumbs(filer_window);
368 /* Set the 'Show Hidden' flag for this window */
369 void display_set_hidden(FilerWindow *filer_window, gboolean hidden)
371 if (filer_window->show_hidden == hidden)
372 return;
374 filer_window->show_hidden = hidden;
376 filer_detach_rescan(filer_window); /* (updates titlebar) */
379 /* Highlight (wink or cursor) this item in the filer window. If the item
380 * isn't already there but we're scanning then highlight it if it
381 * appears later.
383 void display_set_autoselect(FilerWindow *filer_window, const gchar *leaf)
385 gchar *new;
387 g_return_if_fail(filer_window != NULL);
388 g_return_if_fail(leaf != NULL);
390 new = g_strdup(leaf); /* leaf == old value sometimes */
392 null_g_free(&filer_window->auto_select);
394 if (view_autoselect(filer_window->view, new))
395 g_free(new);
396 else
397 filer_window->auto_select = new;
400 /* Change the icon size (wraps) */
401 void display_change_size(FilerWindow *filer_window, gboolean bigger)
403 DisplayStyle new;
405 g_return_if_fail(filer_window != NULL);
407 switch (filer_window->display_style)
409 case LARGE_ICONS:
410 new = bigger ? HUGE_ICONS : SMALL_ICONS;
411 break;
412 case HUGE_ICONS:
413 new = bigger ? SMALL_ICONS : LARGE_ICONS;
414 break;
415 default:
416 new = bigger ? LARGE_ICONS : HUGE_ICONS;
417 break;
420 display_set_layout(filer_window, new, filer_window->details_type);
423 ViewData *display_create_viewdata(FilerWindow *filer_window, DirItem *item)
425 ViewData *view;
427 view = g_new(ViewData, 1);
429 view->layout = NULL;
430 view->details = NULL;
431 view->image = NULL;
433 display_update_view(filer_window, item, view, TRUE);
435 return view;
438 /****************************************************************
439 * INTERNAL FUNCTIONS *
440 ****************************************************************/
442 static void options_changed(void)
444 GList *next;
446 for (next = all_filer_windows; next; next = next->next)
448 FilerWindow *filer_window = (FilerWindow *) next->data;
449 int flags = 0;
451 if (o_display_dirs_first.has_changed)
452 view_sort(VIEW(filer_window->view));
454 if (o_large_width.has_changed || o_small_width.has_changed)
455 flags |= VIEW_UPDATE_NAME; /* Recreate PangoLayout */
457 view_style_changed(filer_window->view, flags);
461 /* Return a new string giving details of this item, or NULL if details
462 * are not being displayed. If details are not yet available, return
463 * a string of the right length.
465 static char *details(FilerWindow *filer_window, DirItem *item)
467 mode_t m = item->mode;
468 guchar *buf = NULL;
469 gboolean scanned = item->base_type != TYPE_UNKNOWN;
471 if (filer_window->details_type == DETAILS_NONE)
472 return NULL;
474 if (scanned && item->lstat_errno)
475 buf = g_strdup_printf(_("lstat(2) failed: %s"),
476 g_strerror(item->lstat_errno));
477 else if (filer_window->details_type == DETAILS_TYPE)
479 MIME_type *type = item->mime_type;
481 if (!scanned)
482 return g_strdup("(application/octet-stream)");
484 buf = g_strdup_printf("(%s/%s)",
485 type->media_type, type->subtype);
487 else if (filer_window->details_type == DETAILS_TIMES)
489 guchar *ctime, *mtime, *atime;
491 ctime = pretty_time(&item->ctime);
492 mtime = pretty_time(&item->mtime);
493 atime = pretty_time(&item->atime);
495 buf = g_strdup_printf("a[%s] c[%s] m[%s]", atime, ctime, mtime);
496 g_free(ctime);
497 g_free(mtime);
498 g_free(atime);
500 else if (filer_window->details_type == DETAILS_PERMISSIONS)
502 if (!scanned)
503 return g_strdup("---,---,---/--"
504 #ifdef S_ISVTX
506 #endif
507 " 12345678 12345678");
509 buf = g_strdup_printf("%s %-8.8s %-8.8s",
510 pretty_permissions(m),
511 user_name(item->uid),
512 group_name(item->gid));
514 else
516 if (!scanned)
518 if (filer_window->display_style == SMALL_ICONS)
519 return g_strdup("1234M");
520 else
521 return g_strdup("1234 bytes");
524 if (item->base_type != TYPE_DIRECTORY)
526 if (filer_window->display_style == SMALL_ICONS)
527 buf = g_strdup(format_size_aligned(item->size));
528 else
529 buf = g_strdup(format_size(item->size));
531 else
532 buf = g_strdup("-");
535 return buf;
538 /* Note: Call style_changed after this */
539 static void display_details_set(FilerWindow *filer_window, DetailsType details)
541 if (filer_window->details_type == details)
542 return;
543 filer_window->details_type = details;
546 /* Note: Call style_changed after this */
547 static void display_style_set(FilerWindow *filer_window, DisplayStyle style)
549 if (filer_window->display_style == style)
550 return;
552 filer_window->display_style = style;
555 /* Each displayed item has a ViewData structure with some cached information
556 * to help quickly draw the item (eg, the PangoLayout). This function updates
557 * this information.
559 void display_update_view(FilerWindow *filer_window,
560 DirItem *item,
561 ViewData *view,
562 gboolean update_name_layout)
564 DisplayStyle style = filer_window->display_style;
565 int w, h;
566 int wrap_width = -1;
567 char *str;
568 static PangoFontDescription *monospace = NULL;
570 if (!monospace)
571 monospace = pango_font_description_from_string("monospace");
573 if (view->details)
575 g_object_unref(G_OBJECT(view->details));
576 view->details = NULL;
579 str = details(filer_window, item);
580 if (str)
582 PangoAttrList *list;
583 int perm_offset = -1;
585 view->details = gtk_widget_create_pango_layout(
586 filer_window->window, str);
587 g_free(str);
589 pango_layout_set_font_description(view->details, monospace);
590 pango_layout_get_size(view->details, &w, &h);
591 view->details_width = w / PANGO_SCALE;
592 view->details_height = h / PANGO_SCALE;
594 if (filer_window->details_type == DETAILS_PERMISSIONS)
595 perm_offset = 0;
596 if (perm_offset > -1)
598 PangoAttribute *attr;
600 attr = pango_attr_underline_new(PANGO_UNDERLINE_SINGLE);
602 perm_offset += 4 * applicable(item->uid, item->gid);
603 attr->start_index = perm_offset;
604 attr->end_index = perm_offset + 3;
606 list = pango_attr_list_new();
607 pango_attr_list_insert(list, attr);
608 pango_layout_set_attributes(view->details, list);
612 if (view->image)
614 g_object_unref(view->image);
615 view->image = NULL;
618 if (filer_window->show_thumbs && item->base_type == TYPE_FILE &&
619 strcmp(item->mime_type->media_type, "image") == 0)
621 const guchar *path;
623 path = make_path(filer_window->real_path, item->leafname);
625 view->image = g_fscache_lookup_full(pixmap_cache, path,
626 FSCACHE_LOOKUP_ONLY_NEW, NULL);
629 if (!view->image)
631 view->image = item->image;
632 if (view->image)
633 g_object_ref(view->image);
636 if (view->layout && update_name_layout)
638 g_object_unref(G_OBJECT(view->layout));
639 view->layout = NULL;
642 if (view->layout)
644 /* Do nothing */
646 else if (g_utf8_validate(item->leafname, -1, NULL))
648 view->layout = gtk_widget_create_pango_layout(
649 filer_window->window, item->leafname);
651 else
653 PangoAttrList *list;
654 PangoAttribute *attr;
655 gchar *utf8;
657 utf8 = to_utf8(item->leafname);
658 view->layout = gtk_widget_create_pango_layout(
659 filer_window->window, utf8);
660 g_free(utf8);
662 attr = pango_attr_foreground_new(0xffff, 0, 0);
663 attr->start_index = 0;
664 attr->end_index = 1000;
665 list = pango_attr_list_new();
666 pango_attr_list_insert(list, attr);
667 pango_layout_set_attributes(view->layout, list);
670 if (filer_window->details_type == DETAILS_NONE)
672 if (style == HUGE_ICONS)
673 wrap_width = HUGE_WRAP * PANGO_SCALE;
674 else if (style == LARGE_ICONS)
675 wrap_width = o_large_width.int_value * PANGO_SCALE;
678 #ifdef USE_PANGO_WRAP_WORD_CHAR
679 pango_layout_set_wrap(view->layout, PANGO_WRAP_WORD_CHAR);
680 #endif
681 if (wrap_width != -1)
682 pango_layout_set_width(view->layout, wrap_width);
684 pango_layout_get_size(view->layout, &w, &h);
685 view->name_width = w / PANGO_SCALE;
686 view->name_height = h / PANGO_SCALE;