r1996: Cope slightly better with invalid filenames in various places (reported by
[rox-filer.git] / ROX-Filer / src / display.c
blobb154c1bd3baf258ce5ee8f83515aae49c06ecbe9
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_intelligent_sort;
65 static Option o_display_dirs_first;
66 Option o_display_size;
67 Option o_display_details;
68 Option o_display_sort_by;
69 static Option o_large_width;
70 Option o_small_width;
71 Option o_display_show_hidden;
72 Option o_display_show_thumbs;
73 Option o_display_inherit_options;
75 /* Static prototypes */
76 static void display_details_set(FilerWindow *filer_window, DetailsType details);
77 static void display_style_set(FilerWindow *filer_window, DisplayStyle style);
78 static void options_changed(void);
79 static char *details(FilerWindow *filer_window, DirItem *item);
81 enum {
82 SORT_BY_NAME = 0,
83 SORT_BY_TYPE = 1,
84 SORT_BY_DATE = 2,
85 SORT_BY_SIZE = 3,
88 /****************************************************************
89 * EXTERNAL INTERFACE *
90 ****************************************************************/
92 void display_init()
94 option_add_int(&o_intelligent_sort, "display_intelligent_sort", 1);
95 option_add_int(&o_display_dirs_first, "display_dirs_first", FALSE);
96 option_add_int(&o_display_size, "display_size", LARGE_ICONS);
97 option_add_int(&o_display_details, "display_details", DETAILS_NONE);
98 option_add_int(&o_display_sort_by, "display_sort_by", SORT_BY_NAME);
99 option_add_int(&o_large_width, "display_large_width", 89);
100 option_add_int(&o_small_width, "display_small_width", 250);
101 option_add_int(&o_display_show_hidden, "display_show_hidden", FALSE);
102 option_add_int(&o_display_show_thumbs, "display_show_thumbs", FALSE);
103 option_add_int(&o_display_inherit_options,
104 "display_inherit_options", FALSE);
106 option_add_notify(options_changed);
109 /* Draw this icon (including any symlink or mount symbol) inside the
110 * given rectangle.
112 void draw_large_icon(GtkWidget *widget,
113 GdkRectangle *area,
114 DirItem *item,
115 MaskedPixmap *image,
116 gboolean selected)
118 int width;
119 int height;
120 int image_x;
121 int image_y;
123 if (!image)
124 return;
126 width = MIN(image->width, ICON_WIDTH);
127 height = MIN(image->height, ICON_HEIGHT);
128 image_x = area->x + ((area->width - width) >> 1);
129 image_y = MAX(0, area->height - height - 6);
131 gdk_pixbuf_render_to_drawable_alpha(
132 selected ? image->pixbuf_lit : image->pixbuf,
133 widget->window,
134 0, 0, /* src */
135 image_x, area->y + image_y, /* dest */
136 width, height,
137 GDK_PIXBUF_ALPHA_FULL, 128, /* (unused) */
138 GDK_RGB_DITHER_NORMAL, 0, 0);
140 if (item->flags & ITEM_FLAG_SYMLINK)
142 gdk_pixbuf_render_to_drawable_alpha(im_symlink->pixbuf,
143 widget->window,
144 0, 0, /* src */
145 image_x, area->y + 2, /* dest */
146 -1, -1,
147 GDK_PIXBUF_ALPHA_FULL, 128, /* (unused) */
148 GDK_RGB_DITHER_NORMAL, 0, 0);
150 else if (item->flags & ITEM_FLAG_MOUNT_POINT)
152 MaskedPixmap *mp = item->flags & ITEM_FLAG_MOUNTED
153 ? im_mounted
154 : im_unmounted;
156 gdk_pixbuf_render_to_drawable_alpha(mp->pixbuf,
157 widget->window,
158 0, 0, /* src */
159 image_x, area->y + 2, /* dest */
160 -1, -1,
161 GDK_PIXBUF_ALPHA_FULL, 128, /* (unused) */
162 GDK_RGB_DITHER_NORMAL, 0, 0);
166 /* The sort functions aren't called from outside, but they are
167 * passed as arguments to display_set_sort_fn().
170 #define IS_A_DIR(item) (item->base_type == TYPE_DIRECTORY && \
171 !(item->flags & ITEM_FLAG_APPDIR))
173 #define SORT_DIRS \
174 if (o_display_dirs_first.int_value) { \
175 gboolean id1 = IS_A_DIR(i1); \
176 gboolean id2 = IS_A_DIR(i2); \
177 if (id1 && !id2) return -1; \
178 if (id2 && !id1) return 1; \
181 int sort_by_name(const void *item1, const void *item2)
183 const DirItem *i1 = (DirItem *) item1;
184 const DirItem *i2 = (DirItem *) item2;
185 char *n1 = i1->leafname_collate;
186 char *n2 = i2->leafname_collate;
188 SORT_DIRS;
190 if (!o_intelligent_sort.int_value)
191 return strcmp(i1->leafname, i2->leafname);
193 /* The following code was copied from PicoGUI (was LGPL) */
195 /* Sort the files, in a way that should make sense to users.
196 * Case is ignored, punctuation is ignored. If the file contains
197 * numbers, the numbers are sorted numerically.
199 while (*n1 && *n2)
201 char c1 = *n1, c2 = *n2;
203 /* If they are both numbers, sort them numerically */
204 if (isdigit(c1) && isdigit(c2))
206 char *p;
207 unsigned long u1,u2;
208 u1 = strtoul(n1, &p, 10);
209 n1 = p;
210 u2 = strtoul(n2, &p, 10);
211 n2 = p;
212 if (u1 < u2)
213 return -1;
214 else if (u1 > u2)
215 return 1;
216 continue;
219 /* Do a case-insensitive asciibetical sort */
220 c1 = tolower(c1);
221 c2 = tolower(c2);
222 if (c1 < c2)
223 return -1;
224 else if (c1 > c2)
225 return 1;
226 n1++;
227 n2++;
230 /* Compare length */
231 if (*n1)
232 return 1; /* First string is longer, so comes after */
233 if (*n2)
234 return -1;
236 /* If the strings are equal at the end of this, fallback to a straight
237 * ASCII sort so at least it's deterministic.
239 return strcmp(i1->leafname, i2->leafname);
242 int sort_by_type(const void *item1, const void *item2)
244 const DirItem *i1 = (DirItem *) item1;
245 const DirItem *i2 = (DirItem *) item2;
246 MIME_type *m1, *m2;
248 int diff = i1->base_type - i2->base_type;
250 if (!diff)
251 diff = (i1->flags & ITEM_FLAG_APPDIR)
252 - (i2->flags & ITEM_FLAG_APPDIR);
253 if (diff)
254 return diff > 0 ? 1 : -1;
256 m1 = i1->mime_type;
257 m2 = i2->mime_type;
259 if (m1 && m2)
261 diff = strcmp(m1->media_type, m2->media_type);
262 if (!diff)
263 diff = strcmp(m1->subtype, m2->subtype);
265 else if (m1 || m2)
266 diff = m1 ? 1 : -1;
267 else
268 diff = 0;
270 if (diff)
271 return diff > 0 ? 1 : -1;
273 return sort_by_name(item1, item2);
276 int sort_by_date(const void *item1, const void *item2)
278 const DirItem *i1 = (DirItem *) item1;
279 const DirItem *i2 = (DirItem *) item2;
281 SORT_DIRS;
283 return i1->mtime > i2->mtime ? -1 :
284 i1->mtime < i2->mtime ? 1 :
285 sort_by_name(item1, item2);
288 int sort_by_size(const void *item1, const void *item2)
290 const DirItem *i1 = (DirItem *) item1;
291 const DirItem *i2 = (DirItem *) item2;
293 SORT_DIRS;
295 return i1->size > i2->size ? -1 :
296 i1->size < i2->size ? 1 :
297 sort_by_name(item1, item2);
300 void display_set_sort_fn(FilerWindow *filer_window,
301 int (*fn)(const void *a, const void *b))
303 if (filer_window->sort_fn == fn)
304 return;
306 filer_window->sort_fn = fn;
308 view_sort(filer_window->view);
311 void display_set_layout(FilerWindow *filer_window,
312 DisplayStyle style,
313 DetailsType details)
315 g_return_if_fail(filer_window != NULL);
317 display_style_set(filer_window, style);
318 display_details_set(filer_window, details);
320 /* Recreate layouts because wrapping may have changed */
321 view_style_changed(filer_window->view, VIEW_UPDATE_NAME);
323 if (o_filer_auto_resize.int_value != RESIZE_NEVER)
324 filer_window_autosize(filer_window);
327 /* Set the 'Show Thumbnails' flag for this window */
328 void display_set_thumbs(FilerWindow *filer_window, gboolean thumbs)
330 if (filer_window->show_thumbs == thumbs)
331 return;
333 filer_window->show_thumbs = thumbs;
335 view_style_changed(filer_window->view, VIEW_UPDATE_VIEWDATA);
337 if (!thumbs)
338 filer_cancel_thumbnails(filer_window);
340 filer_set_title(filer_window);
342 filer_create_thumbs(filer_window);
345 /* Set the 'Show Hidden' flag for this window */
346 void display_set_hidden(FilerWindow *filer_window, gboolean hidden)
348 if (filer_window->show_hidden == hidden)
349 return;
351 filer_window->show_hidden = hidden;
353 filer_detach_rescan(filer_window); /* (updates titlebar) */
356 /* Highlight (wink or cursor) this item in the filer window. If the item
357 * isn't already there but we're scanning then highlight it if it
358 * appears later.
360 void display_set_autoselect(FilerWindow *filer_window, const gchar *leaf)
362 gchar *new;
364 g_return_if_fail(filer_window != NULL);
365 g_return_if_fail(leaf != NULL);
367 new = g_strdup(leaf); /* leaf == old value sometimes */
369 null_g_free(&filer_window->auto_select);
371 if (view_autoselect(filer_window->view, new))
372 g_free(new);
373 else
374 filer_window->auto_select = new;
377 /* Change the icon size (wraps) */
378 void display_change_size(FilerWindow *filer_window, gboolean bigger)
380 DisplayStyle new;
382 g_return_if_fail(filer_window != NULL);
384 switch (filer_window->display_style)
386 case LARGE_ICONS:
387 new = bigger ? HUGE_ICONS : SMALL_ICONS;
388 break;
389 case HUGE_ICONS:
390 new = bigger ? SMALL_ICONS : LARGE_ICONS;
391 break;
392 default:
393 new = bigger ? LARGE_ICONS : HUGE_ICONS;
394 break;
397 display_set_layout(filer_window, new, filer_window->details_type);
400 ViewData *display_create_viewdata(FilerWindow *filer_window, DirItem *item)
402 ViewData *view;
404 view = g_new(ViewData, 1);
406 view->layout = NULL;
407 view->details = NULL;
408 view->image = NULL;
410 display_update_view(filer_window, item, view, TRUE);
412 return view;
415 /****************************************************************
416 * INTERNAL FUNCTIONS *
417 ****************************************************************/
419 static void options_changed(void)
421 GList *next;
423 for (next = all_filer_windows; next; next = next->next)
425 FilerWindow *filer_window = (FilerWindow *) next->data;
426 int flags = 0;
428 if (o_intelligent_sort.has_changed ||
429 o_display_dirs_first.has_changed)
430 view_sort(VIEW(filer_window->view));
432 if (o_large_width.has_changed || o_small_width.has_changed)
433 flags |= VIEW_UPDATE_NAME; /* Recreate PangoLayout */
435 view_style_changed(filer_window->view, flags);
439 /* Return a new string giving details of this item, or NULL if details
440 * are not being displayed. If details are not yet available, return
441 * a string of the right length.
443 static char *details(FilerWindow *filer_window, DirItem *item)
445 mode_t m = item->mode;
446 guchar *buf = NULL;
447 gboolean scanned = item->base_type != TYPE_UNKNOWN;
449 if (filer_window->details_type == DETAILS_NONE)
450 return NULL;
452 if (scanned && item->lstat_errno)
453 buf = g_strdup_printf(_("lstat(2) failed: %s"),
454 g_strerror(item->lstat_errno));
455 else if (filer_window->details_type == DETAILS_SUMMARY)
457 gchar *time;
459 if (!scanned)
460 return g_strdup("XXXX ---,---,---/--"
461 #ifdef S_ISVTX
463 #endif
464 " 12345678 12345678 "
465 "1234M 00:00:00 01 Mmm Yyyy");
467 time = pretty_time(&item->mtime);
469 buf = g_strdup_printf("%s %s %-8.8s %-8.8s %s %s",
470 item->flags & ITEM_FLAG_APPDIR? "App " :
471 S_ISDIR(m) ? "Dir " :
472 S_ISCHR(m) ? "Char" :
473 S_ISBLK(m) ? "Blck" :
474 S_ISLNK(m) ? "Link" :
475 S_ISSOCK(m) ? "Sock" :
476 S_ISFIFO(m) ? "Pipe" :
477 S_ISDOOR(m) ? "Door" :
478 "File",
479 pretty_permissions(m),
480 user_name(item->uid),
481 group_name(item->gid),
482 format_size_aligned(item->size),
483 time);
484 g_free(time);
486 else if (filer_window->details_type == DETAILS_TYPE)
488 MIME_type *type = item->mime_type;
490 if (!scanned)
491 return g_strdup("(application/octet-stream)");
493 buf = g_strdup_printf("(%s/%s)",
494 type->media_type, type->subtype);
496 else if (filer_window->details_type == DETAILS_TIMES)
498 guchar *ctime, *mtime, *atime;
500 ctime = pretty_time(&item->ctime);
501 mtime = pretty_time(&item->mtime);
502 atime = pretty_time(&item->atime);
504 buf = g_strdup_printf("a[%s] c[%s] m[%s]", atime, ctime, mtime);
505 g_free(ctime);
506 g_free(mtime);
507 g_free(atime);
509 else if (filer_window->details_type == DETAILS_PERMISSIONS)
511 if (!scanned)
512 return g_strdup("---,---,---/--"
513 #ifdef S_ISVTX
515 #endif
516 " 12345678 12345678");
518 buf = g_strdup_printf("%s %-8.8s %-8.8s",
519 pretty_permissions(m),
520 user_name(item->uid),
521 group_name(item->gid));
523 else
525 if (!scanned)
527 if (filer_window->display_style == SMALL_ICONS)
528 return g_strdup("1234M");
529 else
530 return g_strdup("1234 bytes");
533 if (item->base_type != TYPE_DIRECTORY)
535 if (filer_window->display_style == SMALL_ICONS)
536 buf = g_strdup(format_size_aligned(item->size));
537 else
538 buf = g_strdup(format_size(item->size));
540 else
541 buf = g_strdup("-");
544 return buf;
547 /* Note: Call style_changed after this */
548 static void display_details_set(FilerWindow *filer_window, DetailsType details)
550 if (filer_window->details_type == details)
551 return;
552 filer_window->details_type = details;
555 /* Note: Call style_changed after this */
556 static void display_style_set(FilerWindow *filer_window, DisplayStyle style)
558 if (filer_window->display_style == style)
559 return;
561 filer_window->display_style = style;
564 void display_update_view(FilerWindow *filer_window,
565 DirItem *item,
566 ViewData *view,
567 gboolean update_name_layout)
569 DisplayStyle style = filer_window->display_style;
570 int w, h;
571 int wrap_width = -1;
572 char *str;
573 static PangoFontDescription *monospace = NULL;
575 if (!monospace)
576 monospace = pango_font_description_from_string("monospace");
578 if (view->details)
580 g_object_unref(G_OBJECT(view->details));
581 view->details = NULL;
584 str = details(filer_window, item);
585 if (str)
587 PangoAttrList *list;
588 int perm_offset = -1;
590 view->details = gtk_widget_create_pango_layout(
591 filer_window->window, str);
592 g_free(str);
594 pango_layout_set_font_description(view->details, monospace);
595 pango_layout_get_size(view->details, &w, &h);
596 view->details_width = w / PANGO_SCALE;
597 view->details_height = h / PANGO_SCALE;
599 if (filer_window->details_type == DETAILS_SUMMARY)
600 perm_offset = 5;
601 else if (filer_window->details_type == DETAILS_PERMISSIONS)
602 perm_offset = 0;
603 if (perm_offset > -1)
605 PangoAttribute *attr;
607 attr = pango_attr_underline_new(PANGO_UNDERLINE_SINGLE);
609 perm_offset += 4 * applicable(item->uid, item->gid);
610 attr->start_index = perm_offset;
611 attr->end_index = perm_offset + 3;
613 list = pango_attr_list_new();
614 pango_attr_list_insert(list, attr);
615 pango_layout_set_attributes(view->details, list);
619 if (view->image)
621 g_object_unref(view->image);
622 view->image = NULL;
625 if (filer_window->show_thumbs && item->base_type == TYPE_FILE &&
626 strcmp(item->mime_type->media_type, "image") == 0)
628 gchar *path;
630 path = make_path(filer_window->real_path, item->leafname)->str;
632 view->image = g_fscache_lookup_full(pixmap_cache, path,
633 FSCACHE_LOOKUP_ONLY_NEW, NULL);
636 if (!view->image)
638 view->image = item->image;
639 if (view->image)
640 g_object_ref(view->image);
643 if (view->layout && update_name_layout)
645 g_object_unref(G_OBJECT(view->layout));
646 view->layout = NULL;
649 if (view->layout)
651 /* Do nothing */
653 else if (g_utf8_validate(item->leafname, -1, NULL))
655 view->layout = gtk_widget_create_pango_layout(
656 filer_window->window, item->leafname);
658 else
660 PangoAttrList *list;
661 PangoAttribute *attr;
662 gchar *utf8;
664 utf8 = to_utf8(item->leafname);
665 view->layout = gtk_widget_create_pango_layout(
666 filer_window->window, utf8);
667 g_free(utf8);
669 attr = pango_attr_foreground_new(0xffff, 0, 0);
670 attr->start_index = 0;
671 attr->end_index = 1000;
672 list = pango_attr_list_new();
673 pango_attr_list_insert(list, attr);
674 pango_layout_set_attributes(view->layout, list);
677 if (filer_window->details_type == DETAILS_NONE)
679 if (style == HUGE_ICONS)
680 wrap_width = HUGE_WRAP * PANGO_SCALE;
681 else if (style == LARGE_ICONS)
682 wrap_width = o_large_width.int_value * PANGO_SCALE;
685 if (wrap_width != -1)
686 pango_layout_set_width(view->layout, wrap_width);
688 pango_layout_get_size(view->layout, &w, &h);
689 view->name_width = w / PANGO_SCALE;
690 view->name_height = h / PANGO_SCALE;