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)
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
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 */
31 #include <sys/param.h>
38 #include <gdk/gdkkeysyms.h>
44 #include "collection.h"
46 #include "gui_support.h"
56 #include "minibuffer.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)
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
;
90 GdkRectangle leafname
;
94 #define SHOW_RECT(ite, template) \
95 g_print("%s: %dx%d+%d+%d %dx%d+%d+%d\n", \
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
,
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
,
137 int len
, /* -1 for whole string */
139 GdkRectangle
*area
, /* Area available on screen */
140 int width
, /* Width of the full string */
141 GtkStateType selection_state
,
152 /****************************************************************
153 * EXTERNAL INTERFACE *
154 ****************************************************************/
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",
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
;
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);
194 huge_full_template(area
, colitem
,
195 filer_window
, template);
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);
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
;
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
)
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
;
230 pix_width
= HUGE_WIDTH
* 3 / 2;
231 pix_height
= HUGE_HEIGHT
* 3 / 2;
234 *width
= MAX(pix_width
, view
->name_width
) + 4;
235 *height
= view
->name_height
+ pix_height
+ 4;
237 *width
= MAX(pix_width
, view
->split_width
) + 4;
238 *height
= view
->split_height
+ pix_height
+ 4;
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;
250 pix_width
= view
->image
->width
;
252 pix_width
= HUGE_WIDTH
* 3 / 2;
254 *width
= MAX(pix_width
, view
->name_width
) + 4;
255 *height
= view
->name_height
+ ICON_HEIGHT
+ 2;
257 *width
= MAX(pix_width
, view
->split_width
) + 4;
258 *height
= view
->split_height
+ ICON_HEIGHT
+ 2;
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
)
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;
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
290 void draw_huge_icon(GtkWidget
*widget
,
299 GdkGC
*gc
= selected
? widget
->style
->white_gc
300 : widget
->style
->black_gc
;
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
,
314 0, 0, /* Source x,y */
315 image_x
, area
->y
+ image_y
, /* Dest x,y */
320 gdk_gc_set_function(gc
, GDK_INVERT
);
321 gdk_draw_rectangle(widget
->window
,
323 TRUE
, image_x
, area
->y
+ image_y
,
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 */
337 else if (item
->flags
& ITEM_FLAG_MOUNT_POINT
)
339 MaskedPixmap
*mp
= item
->flags
& ITEM_FLAG_MOUNTED
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 */
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
358 void draw_large_icon(GtkWidget
*widget
,
368 GdkGC
*gc
= selected
? widget
->style
->white_gc
369 : widget
->style
->black_gc
;
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
,
383 0, 0, /* Source x,y */
384 image_x
, area
->y
+ image_y
, /* Dest x,y */
389 gdk_gc_set_function(gc
, GDK_INVERT
);
390 gdk_draw_rectangle(widget
->window
,
392 TRUE
, image_x
, area
->y
+ image_y
,
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 */
406 else if (item
->flags
& ITEM_FLAG_MOUNT_POINT
)
408 MaskedPixmap
*mp
= item
->flags
& ITEM_FLAG_MOUNTED
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 */
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))
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
;
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.
459 char c1
= *n1
, c2
= *n2
;
461 /* Skip punctuation */
473 /* If they are both numbers, sort them numerically */
474 if (isdigit(c1
) && isdigit(c2
))
478 u1
= strtoul(n1
, &p
, 10);
480 u2
= strtoul(n2
, &p
, 10);
489 /* Otherwise, do a case-insensitive asciibetical sort */
502 return 1; /* First string is longer, so comes after */
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
;
518 int diff
= i1
->base_type
- i2
->base_type
;
521 diff
= (i1
->flags
& ITEM_FLAG_APPDIR
)
522 - (i2
->flags
& ITEM_FLAG_APPDIR
);
524 return diff
> 0 ? 1 : -1;
531 diff
= strcmp(m1
->media_type
, m2
->media_type
);
533 diff
= strcmp(m1
->subtype
, m2
->subtype
);
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
;
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
;
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
)
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
++)
582 calc_size(filer_window
, &col
->items
[i
], &w
, &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
)
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
,
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
)
625 filer_window
->show_thumbs
= thumbs
;
627 update_views(filer_window
);
630 filer_window
->collection
->paint_level
= PAINT_CLEAR
;
632 gtk_widget_queue_clear(GTK_WIDGET(filer_window
->collection
));
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
)
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
659 void display_set_autoselect(FilerWindow
*filer_window
, guchar
*leaf
)
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
);
676 collection_wink_item(col
, i
);
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
)
692 Collection
*collection
= filer_window
->collection
;
693 CollectionItem
*colitem
= &collection
->items
[i
];
694 int col
= i
% collection
->columns
;
695 int row
= i
/ collection
->columns
;
698 ViewData
*view
= (ViewData
*) colitem
->view_data
;
701 scroll
= collection
->vadj
->value
; /* (round to int) */
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
;
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
)
727 g_return_if_fail(filer_window
!= NULL
);
729 switch (filer_window
->display_style
)
732 new = bigger
? HUGE_ICONS
: SMALL_ICONS
;
735 new = bigger
? SMALL_ICONS
: LARGE_ICONS
;
738 new = bigger
? LARGE_ICONS
: HUGE_ICONS
;
742 display_set_layout(filer_window
, new, filer_window
->details_type
);
745 ViewData
*display_create_viewdata(FilerWindow
*filer_window
, DirItem
*item
)
749 view
= g_new(ViewData
, 1);
754 view
->details
= NULL
;
757 display_update_view(filer_window
, item
, view
);
762 void display_free_colitem(Collection
*collection
, CollectionItem
*colitem
)
764 ViewData
*view
= (ViewData
*) colitem
->view_data
;
772 g_object_unref(G_OBJECT(view
->layout
));
776 g_object_unref(G_OBJECT(view
->details
));
780 pixmap_unref(view
->image
);
785 /****************************************************************
786 * INTERNAL FUNCTIONS *
787 ****************************************************************/
789 static void options_changed(void)
791 GList
*next
= all_filer_windows
;
795 FilerWindow
*filer_window
= (FilerWindow
*) next
->data
;
798 if (o_large_width
.has_changed
|| o_small_width
.has_changed
)
800 /* Recreate PangoLayout */
801 update_views(filer_window
);
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
);
818 static void huge_template(GdkRectangle
*area
, CollectionItem
*colitem
,
819 FilerWindow
*filer_window
, Template
*template)
821 int col_width
= filer_window
->collection
->item_width
;
823 ViewData
*view
= (ViewData
*) colitem
->view_data
;
824 MaskedPixmap
*image
= view
->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
;
835 template->icon
.width
= HUGE_WIDTH
* 3 / 2;
836 template->icon
.height
= HUGE_HEIGHT
;
840 template->leafname
.width
= view
->name_width
;
841 template->leafname
.height
= view
->name_height
;
843 template->leafname
.width
= view
->split_width
;
844 template->leafname
.height
= view
->split_height
;
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
;
863 ViewData
*view
= (ViewData
*) colitem
->view_data
;
864 MaskedPixmap
*image
= view
->image
;
870 iwidth
= MIN(image
->width
, ICON_WIDTH
);
871 iheight
= MIN(image
->height
+ 6, ICON_HEIGHT
);
876 iheight
= ICON_HEIGHT
;
878 image_x
= area
->x
+ ((col_width
- iwidth
) >> 1);
881 template->leafname
.width
= view
->name_width
;
882 template->leafname
.height
= view
->name_height
;
884 template->leafname
.width
= view
->split_width
;
885 template->leafname
.height
= view
->split_height
;
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;
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
;
932 if (!image
->huge_pixmap
)
933 pixmap_make_huge(image
);
934 template->icon
.width
= image
->huge_width
;
935 template->icon
.height
= image
->huge_height
;
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
;
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
;
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);
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
)
1011 ViewData
*view
= (ViewData
*) colitem
->view_data
;
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
,
1028 MaskedPixmap
*image
,
1031 GdkGC
*gc
= selected
? widget
->style
->white_gc
1032 : widget
->style
->black_gc
;
1033 int width
, height
, image_x
, image_y
;
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
,
1051 0, 0, /* Source x,y */
1052 image_x
, area
->y
+ image_y
, /* Dest x,y */
1057 gdk_gc_set_function(gc
, GDK_INVERT
);
1058 gdk_draw_rectangle(widget
->window
,
1060 TRUE
, image_x
, area
->y
+ image_y
,
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 */
1074 else if (item
->flags
& ITEM_FLAG_MOUNT_POINT
)
1076 MaskedPixmap
*mp
= item
->flags
& ITEM_FLAG_MOUNTED
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
,
1086 0, 0, /* Source x,y */
1087 image_x
+ 2, area
->y
+ 2, /* Dest x,y */
1091 gdk_gc_set_clip_mask(gc
, NULL
);
1092 gdk_gc_set_clip_origin(gc
, 0, 0);
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
;
1105 if (item
->base_type
== TYPE_UNKNOWN
)
1108 w
= fixed_width
* strlen(string
);
1115 filer_window
->selection_state
,
1118 if (item
->lstat_errno
)
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
,
1130 ? widget
->style
->fg_gc
[
1131 filer_window
->selection_state
]
1134 area
->x
- 1 + fixed_width
* perm_offset
,
1135 area
->y
+ area
->height
- 1,
1136 fixed_width
* 3 + 1, 1);
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
;
1149 gboolean scanned
= item
->base_type
!= TYPE_UNKNOWN
;
1151 if (filer_window
->details_type
== DETAILS_NONE
)
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
)
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
;
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
);
1199 else if (filer_window
->details_type
== DETAILS_PERMISSIONS
)
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
));
1213 if (filer_window
->display_style
== SMALL_ICONS
)
1214 return g_strdup("12345M");
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
));
1224 buf
= g_strdup(format_size(item
->size
));
1227 buf
= g_strdup("-");
1233 static void draw_item(GtkWidget
*widget
,
1234 CollectionItem
*colitem
,
1236 FilerWindow
*filer_window
)
1238 DirItem
*item
= (DirItem
*) colitem
->data
;
1239 gboolean selected
= colitem
->selected
;
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 */
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
);
1268 draw_huge_icon(widget
, &template.icon
,
1269 item
, view
->image
, selected
);
1273 draw_string(widget
, view
->layout
,
1276 filer_window
->selection_state
,
1279 draw_string(widget
, view
->details
,
1281 template.details
.width
,
1282 filer_window
->selection_state
,
1285 if (view
->split_pos
)
1288 guchar
*bot
= item
->leafname
+ view
->split_pos
;
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
;
1300 item
->leafname
, view
->split_pos
,
1302 template.leafname
.width
,
1303 filer_window
->selection_state
,
1305 rec
.y
+= rec
.height
;
1310 MAX(w
, template.leafname
.width
),
1311 filer_window
->selection_state
,
1320 filer_window
->selection_state
,
1324 draw_details(filer_window
, item
,
1326 template.details
.width
,
1327 selected
, view
->details
);
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
;
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
)
1353 filer_window
->details_type
= details
;
1354 update_views(filer_window
);
1357 filer_window
->collection
->paint_level
= PAINT_CLEAR
;
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
)
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
,
1378 void display_update_view(FilerWindow
*filer_window
,
1382 DisplayStyle style
= filer_window
->display_style
;
1384 int wrap_width
= -1;
1387 static PangoFontDescription
*monospace
= NULL
;
1390 monospace
= pango_font_description_from_string("monospace");
1394 g_object_unref(G_OBJECT(view
->details
));
1395 view
->details
= NULL
;
1398 str
= details(filer_window
, item
);
1401 PangoAttrList
*list
;
1402 PangoAttribute
*attr
;
1403 int perm_offset
= -1;
1405 view
->details
= gtk_widget_create_pango_layout(
1406 filer_window
->window
, 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
)
1418 else if (filer_window
->details_type
== DETAILS_PERMISSIONS
)
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
);
1432 int font_height
= item_font
->ascent
+ item_font
->descent
;
1434 g_free(view
->details
);
1435 view
->details
= details(filer_window
, item
);
1438 view
->details_width
= fixed_width
* strlen(view
->details
);
1439 view
->details_height
= fixed_font
->ascent
+ fixed_font
->descent
;
1445 pixmap_unref(view
->image
);
1449 if (filer_window
->show_thumbs
&& item
->base_type
== TYPE_FILE
&&
1450 strcmp(item
->mime_type
->media_type
, "image") == 0)
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
);
1462 view
->image
= item
->image
;
1463 pixmap_ref(view
->image
);
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
;
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
)
1504 view
->split_height
= font_height
;
1505 view
->split_width
= view
->name_width
;
1506 view
->split_pos
= 0;
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;
1524 /* 'box' renders a background box if the string is also selected */
1525 static void draw_string(GtkWidget
*widget
,
1527 PangoLayout
*layout
,
1531 int len
, /* -1 for whole string */
1533 GdkRectangle
*area
, /* Area available on screen */
1534 int width
, /* Width of the full string */
1535 GtkStateType selection_state
,
1539 GdkGC
*gc
= selected
1540 ? widget
->style
->fg_gc
[selection_state
]
1543 if (selected
&& box
)
1544 gtk_paint_flat_box(widget
->style
, widget
->window
,
1545 selection_state
, GTK_SHADOW_NONE
,
1546 NULL
, widget
, "text",
1548 MIN(width
, area
->width
),
1551 if (width
> area
->width
)
1553 gdk_gc_set_clip_origin(gc
, 0, 0);
1554 gdk_gc_set_clip_rectangle(gc
, area
);
1558 gdk_draw_layout(widget
->window
, gc
, area
->x
, area
->y
, layout
);
1561 len
= strlen(string
);
1562 gdk_draw_text(widget
->window
,
1565 area
->x
, area
->y
+ font
->ascent
,
1569 if (width
> area
->width
)
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
,
1579 gdk_gc_set_clip_rectangle(gc
, NULL
);