"Fossies" - the Fresh Open Source Software Archive 
Member "xzgv-0.9.2/src/main.c" (3 Sep 2017, 111224 Bytes) of package /linux/misc/old/xzgv-0.9.2.tar.gz:
As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style:
standard) with prefixed line numbers and
code folding option.
Alternatively you can here
view or
download the uninterpreted source code file.
For more information about "main.c" see the
Fossies "Dox" file reference documentation.
1 /* xzgv - picture viewer for X, with file selector.
2 * Copyright (C) 1999-2003 Russell Marks.
3 * Copyright (C) 2007 Reuben Thomas.
4 *
5 * main.c - the guts of the program (selector, viewer, etc.).
6 *
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or (at
11 * your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful, but
14 * WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
21 */
22
23 /* XXX there's really too much stuff here, much of it could/should
24 * be moved out to other files...
25 */
26
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <string.h>
30 #include <ctype.h>
31 #include <math.h> /* for pow() */
32 #include <sys/types.h>
33 #include <sys/stat.h>
34 #include <dirent.h>
35 #include <unistd.h>
36 #include <errno.h>
37
38 #include <gtk/gtk.h>
39 #include <gdk/gdkkeysyms.h>
40 #include <gdk/gdkx.h> /* needed for iconify stuff */
41 #include <gdk/gdkrgb.h>
42 #include <X11/Xlib.h> /* needed for iconify stuff */
43
44 #include "backend.h"
45 #include "resizepic.h"
46 #include "rcfile.h" /* needed for config vars */
47 #include "filedetails.h"
48 #include "gotodir.h"
49 #include "updatetn.h"
50 #include "confirm.h"
51 #include "misc.h"
52 #include "copymove.h"
53 #include "rename.h"
54 #include "help.h"
55
56 #include "dir_icon.xpm"
57 #include "dir_icon_small.xpm"
58 #include "file_icon.xpm"
59 #include "file_icon_small.xpm"
60 #include "icon-48.xpm"
61
62 #include "main.h"
63
64
65 /* number of thumbnails idle_xvpic_load() attempts to load per call.
66 * 1 is a little on the small side :-), but should keep it tolerably
67 * interactive while loading thumbnails on slower machines (I hope!).
68 */
69 #define IDLE_XVPIC_NUM_PER_CALL 1
70
71 /* row heights - normal and `thin'. I wouldn't mess about with these
72 * unless you have a really good reason to. :-)
73 */
74 #define ROW_HEIGHT_NORMAL (60+2)
75 #define ROW_HEIGHT_DIV 3
76 #define ROW_HEIGHT_THIN (20+2)
77
78 /* GTK+ border width in scrolled window (not counting scrollbars).
79 * very kludgey, but needed for calculating size to zoom to.
80 */
81 #define SW_BORDER_WIDTH 4
82
83 /* maximum no. of `past positions' in dirs to save.
84 * if it runs out of space the oldest entries are lost.
85 */
86 #define MAX_PASTPOS 256
87
88 /* limit on scaling down - entirely arbitrary */
89 #define SCALING_DOWN_LIMIT (-32)
90
91 /* for defence against render_pixmap recursive callbacks, etc.
92 * Be sure to do RECURSE_PROTECT_END before *any* possible exit
93 * (but as late as possible, of course).
94 */
95 #define RECURSE_PROTECT_START static int here=0; if(here) return; here=1
96 #define RECURSE_PROTECT_END here=0
97
98
99 GtkWidget *drawing_area,*align,*sw_for_pic;
100 GtkWidget *clist,*statusbar,*sw_for_clist;
101 GtkWidget *selector_menu,*viewer_menu;
102 GtkWidget *zoom_widget; /* widget for zoom opt on menu */
103 GtkWidget *pane;
104 guint sel_id; /* selector id for statusbar messages */
105 guint tn_id; /* `thumbnail' id for statusbar messages */
106
107 GtkWidget *mainwin;
108
109 guint8 xvpic_pal[256][3]; /* palette for thumbnails */
110
111 /* image & rendered pixmap for currently-loaded image */
112 xzgv_image *theimage=NULL;
113 GdkPixmap *thepixmap=NULL;
114
115 /* no-thumbnail icon pixmaps */
116 GdkPixmap *dir_icon,*file_icon;
117 GdkPixmap *dir_icon_small,*file_icon_small;
118 GdkBitmap *dir_icon_mask,*file_icon_mask;
119 GdkBitmap *dir_icon_small_mask,*file_icon_small_mask;
120
121 /* stuff for the idle-func thumbnail loading */
122 gint tn_idle_tag=-1; /* tag returned by gtk_idle_add() */
123 float idle_xvpic_lastadjval;
124 int idle_xvpic_jumped=0; /* true if xvpic load jumped ahead */
125 int idle_xvpic_blocked=0; /* disables idle_xvpic_load() temporarily */
126 int idle_xvpic_called=0; /* set when idle_xvpic_load is called */
127
128 int numrows=0; /* number of rows in clist */
129
130 gint zoom_resize_idle_tag=-1; /* tag for zoom-resize kludge idle func */
131
132 int listen_to_toggles=0; /* ignore fix-up toggles initially */
133 /* (see init_window()) */
134 int in_nextprev=0; /* needed to protect against recursion */
135
136 float orig_x,orig_y; /* for image dragging with mouse */
137 int ignore_drag=1; /* ignore image drags if true */
138 int next_on_release=0; /* if true, do next-pic on but1 release */
139 int current_selection=-1; /* needed for viewer's next/previous file */
140 guint cb_selection_id; /* id of cb_selection() handler */
141 int ignore_selector_input=0; /* awkward but necessary, for blocking input */
142 int hide_saved_pos; /* saved pane-split pos for auto-hide */
143 int hidden=0; /* selector hidden if true */
144 int orient_current_state=0; /* current picture orientation state */
145 int jpeg_exif_orient=0; /* orientation from Exif tag, for some JPEGs */
146
147 int cmdline_files=0; /* if true, started as `xzgv file(s)' */
148
149 int xscaling=1,yscaling=1;
150
151
152
153 struct pastpos_tag
154 {
155 int dev,inode,row;
156 } pastpos[MAX_PASTPOS];
157
158
159 /* Scary orientation stuff
160 * -----------------------
161 *
162 * There are eight possible orientations (0 is the original image):
163 * _____ _____
164 * _______ _______ | a| | b|
165 * |a | |b | | | | |
166 * | 0 | | 1 | | 4 | | 5 |
167 * |______b| |______a| |b____| |a____|
168 * _______ _______ _____ _____
169 * | b| | a| |b | |a |
170 * | 2 | | 3 | | | | |
171 * |a______| |b______| | 6 | | 7 |
172 * |____a| |____b|
173 *
174 * That gives us these changes in orientation state for each of the
175 * orientation-changing operations (rotate, mirror, flip):
176 *
177 * rot-cw rot-acw mirror flip
178 * 0 to... 4 5 3 2
179 * 1 to... 5 4 2 3
180 * 2 to... 7 6 1 0
181 * 3 to... 6 7 0 1
182 * 4 to... 1 0 7 6
183 * 5 to... 0 1 6 7
184 * 6 to... 2 3 5 4
185 * 7 to... 3 2 4 5
186 */
187
188 int orient_state_rot_cw[8] ={4,5,7,6,1,0,2,3};
189 int orient_state_rot_acw[8]={5,4,6,7,0,1,3,2};
190 int orient_state_mirror[8] ={3,2,1,0,7,6,5,4};
191 int orient_state_flip[8] ={2,3,0,1,6,7,4,5};
192
193
194
195 /* required prototypes */
196 void render_pixmap(int reset_pos);
197 void cb_nextprev_tagged_image(int next,int view);
198 gint idle_xvpic_load(int *entryp);
199 gint pic_win_resized(GtkWidget *widget,GdkEventConfigure *event);
200 void cb_scaling_double(void);
201 void cb_xscaling_double(void);
202 void cb_yscaling_double(void);
203 void cb_scaling_halve(void);
204 void cb_xscaling_halve(void);
205 void cb_yscaling_halve(void);
206 void cb_next_image(void);
207 void cb_tag_then_next(void);
208 void set_title(int include_dir);
209 void set_window_pos_and_size(void);
210
211
212
213
214 void swap_xyscaling(void)
215 {
216 int tmp=xscaling;
217
218 xscaling=yscaling;
219 yscaling=tmp;
220 }
221
222
223 /* change from one orientation state to another.
224 * (See the comment about this above.)
225 */
226 void orient_change_state(int from,int to)
227 {
228 /* the basic idea is this:
229 *
230 * - if from and to are equal, return.
231 * - if a single flip/mirror/rot will do it, use that.
232 * - otherwise, try a rotate if we know it's needed (see below).
233 * - then see if a flip/mirror does the trick.
234 * - if not, it must need flip *and* mirror.
235 */
236 int state=from;
237
238 if(from==to) return;
239
240 #define DO_FLIP backend_flip_vert(theimage)
241 #define DO_MIRROR backend_flip_horiz(theimage)
242 #define DO_ROT_CW backend_rotate_cw(theimage), \
243 swap_xyscaling()
244
245 /* try a one-step route. */
246 if(orient_state_flip[state]==to) { DO_FLIP; return; }
247 if(orient_state_mirror[state]==to) { DO_MIRROR; return; }
248 if(orient_state_rot_cw[state]==to)
249 {
250 DO_ROT_CW;
251 return;
252 }
253
254 /* nope, ok then, things get complicated.
255 * we can get any required rotate out of the way -
256 * if it's switched from portrait to landscape or vice versa, we must
257 * need one. That's if it's gone from 0..3 to 4..7 or 4..7 to 3..0.
258 */
259 if((from<4 && to>=4) || (from>=4 && to<4))
260 {
261 DO_ROT_CW;
262 state=orient_state_rot_cw[state];
263 }
264
265 /* now try a flip/mirror. */
266 if(orient_state_flip[state]==to) { DO_FLIP; return; }
267 if(orient_state_mirror[state]==to) { DO_MIRROR; return; }
268
269 /* no? Well it must need both then. */
270 DO_FLIP;
271 DO_MIRROR;
272
273 /* sanity check */
274 if(orient_state_mirror[orient_state_flip[state]]!=to)
275 fprintf(stderr,"can't happen - orient_change_state(%d,%d) failed!\n",
276 from,to);
277 }
278
279
280 /* run GTK+ stuff until events are dealt with. Normally the idle func
281 * to load thumbnails, if running, would take this opportunity to
282 * completely finish loading the thumbnails. So we disable that
283 * temporarily.
284 */
285 void do_gtk_stuff(void)
286 {
287 idle_xvpic_blocked=1;
288 idle_xvpic_called=0;
289
290 while(!idle_xvpic_called && gtk_events_pending())
291 gtk_main_iteration();
292
293 idle_xvpic_blocked=0;
294 }
295
296
297 /* small wrapper function for backend_create_image_from_file() which
298 * deals with mrf files and other oddities (currently GIF/PNG).
299 *
300 * It also copes with loading JPEGs quickly for thumbnails, hence
301 * the second arg. :-) The original width/height of the image
302 * (which is likely to differ from that of the image returned in the
303 * latter case) is returned in orig[wh]p if non-NULL.
304 */
305 xzgv_image *load_image(char *file,int for_thumbnail,
306 int *origwp,int *orighp)
307 {
308 xzgv_image *ret;
309 int origw,origh;
310
311 jpeg_exif_orient=0;
312
313 ret=backend_create_image_from_file(file); /* use backend's loader */
314 if((ret != NULL) && use_exif_orient) jpeg_exif_orient=backend_get_orientation_from_file(file);
315
316 origw=0; origh=0;
317 if(ret)
318 {
319 origw=ret->w;
320 origh=ret->h;
321 }
322
323 if(origwp) *origwp=origw;
324 if(orighp) *orighp=origh;
325
326 return(ret);
327 }
328
329
330 GtkAccelGroup *mainwin_accel_group;
331
332 GtkItemFactory *make_menu(char *base,GtkItemFactoryEntry *menu_items,
333 int num_items)
334 {
335 GtkItemFactory *item_factory;
336
337 mainwin_accel_group=gtk_accel_group_new();
338
339 item_factory=gtk_item_factory_new(GTK_TYPE_MENU,base,mainwin_accel_group);
340
341 /* make menus */
342 gtk_item_factory_create_items(item_factory,num_items,menu_items,NULL);
343
344 /* add keys to window */
345 gtk_window_add_accel_group(GTK_WINDOW(mainwin),mainwin_accel_group);
346
347 return(item_factory);
348 }
349
350
351 gint cb_quit(GtkWidget *widget)
352 {
353 gtk_main_quit();
354
355 /* stop e.g. thumbnail update */
356 mainwin=NULL;
357
358 return(TRUE);
359 }
360
361
362 /* make a row visible if it's partly/fully obscured or `offscreen'. */
363 void make_visible_if_not(int row)
364 {
365 if(gtk_clist_row_is_visible(GTK_CLIST(clist),row)!=GTK_VISIBILITY_FULL)
366 gtk_clist_moveto(GTK_CLIST(clist),row,0,0.5,0.);
367 }
368
369
370 /* moving the cursor while the clist is focused can screw up the display.
371 * Instead, we unfocus the clist (if focused), change rows, then
372 * (if it was previously focused) return focus to clist.
373 */
374 void set_focus_row(int new_row)
375 {
376 int had_focus=GTK_WIDGET_HAS_FOCUS(clist);
377
378 if(had_focus)
379 gtk_widget_grab_focus(drawing_area);
380
381 GTK_CLIST(clist)->focus_row=new_row;
382
383 if(had_focus)
384 gtk_widget_grab_focus(clist);
385 }
386
387
388 /* gets whether a row is tagged or not.
389 * Really just for convenience, and by analogy with set_tagged_state(). :-)
390 */
391 int get_tagged_state(int row)
392 {
393 struct clist_data_tag *datptr;
394
395 if(row<0 || row>=numrows) return(0);
396
397 datptr=gtk_clist_get_row_data(GTK_CLIST(clist),row);
398 return(datptr->tagged);
399 }
400
401
402 /* sets whether a row is tagged or not.
403 * tagged=0 to untag, 1 to tag, -1 to toggle.
404 */
405 void set_tagged_state(int row,int tagged)
406 {
407 /* XXX colour used for tagging should be configurable */
408 static GdkColor col={0, 0xffff,0,0}; /* red */
409 static int gotcol=0;
410 struct clist_data_tag *datptr;
411
412 datptr=gtk_clist_get_row_data(GTK_CLIST(clist),row);
413 if(datptr->isdir) return;
414
415 if(datptr)
416 {
417 if(tagged==-1)
418 datptr->tagged=!datptr->tagged;
419 else
420 datptr->tagged=tagged;
421 }
422
423 if(!gotcol)
424 backend_get_closest_colour(&col),gotcol=1;
425
426 gtk_clist_set_foreground(GTK_CLIST(clist),row,datptr->tagged?&col:NULL);
427 }
428
429
430 /* tag_file and untag_file are used when tagging from the keyboard,
431 * or from the tag/untag file menu options.
432 */
433 void cb_tag_file(void)
434 {
435 int row=GTK_CLIST(clist)->focus_row;
436
437 if(row<0) return;
438
439 set_tagged_state(row,1); /* tag */
440 if(row<numrows-1) /* move on one */
441 {
442 set_focus_row(row+1);
443 make_visible_if_not(row+1);
444 }
445 }
446
447 void cb_untag_file(void)
448 {
449 int row=GTK_CLIST(clist)->focus_row;
450
451 if(row<0) return;
452
453 set_tagged_state(row,0); /* untag */
454 if(row<numrows-1) /* move on one */
455 {
456 set_focus_row(row+1);
457 make_visible_if_not(row+1);
458 }
459 }
460
461
462 void cb_tag_all(void)
463 {
464 int f;
465
466 gtk_clist_freeze(GTK_CLIST(clist));
467
468 for(f=0;f<numrows;f++)
469 set_tagged_state(f,1);
470
471 gtk_clist_thaw(GTK_CLIST(clist));
472 }
473
474
475 void cb_untag_all(void)
476 {
477 int f;
478
479 gtk_clist_freeze(GTK_CLIST(clist));
480
481 for(f=0;f<numrows;f++)
482 set_tagged_state(f,0);
483
484 gtk_clist_thaw(GTK_CLIST(clist));
485 }
486
487
488 void cb_toggle_all(void)
489 {
490 int f;
491
492 gtk_clist_freeze(GTK_CLIST(clist));
493
494 for(f=0;f<numrows;f++)
495 set_tagged_state(f,!get_tagged_state(f));
496
497 gtk_clist_thaw(GTK_CLIST(clist));
498 }
499
500
501 void cb_back_to_clist(void)
502 {
503 RECURSE_PROTECT_START;
504
505 /* unhide selector if it was hidden (whether auto-hidden or not) */
506 if(hidden)
507 {
508 gtk_paned_set_position(GTK_PANED(pane),hide_saved_pos);
509 hidden=0;
510 }
511
512 GTK_WIDGET_SET_FLAGS(clist,GTK_CAN_FOCUS);
513 gtk_widget_grab_focus(clist);
514
515 /* XXX kludge: make sure pic is fixed in zoom mode */
516 pic_win_resized(NULL,NULL);
517
518 RECURSE_PROTECT_END;
519 }
520
521
522 void cb_hide_selector(void)
523 {
524 RECURSE_PROTECT_START;
525
526 /* this is really a toggle, so show it if it's hidden. */
527 if(hidden)
528 {
529 gtk_paned_set_position(GTK_PANED(pane),hide_saved_pos);
530 hidden=0;
531 }
532 else
533 {
534 do_gtk_stuff(); /* in case it's being done immediately after an unhide */
535 hide_saved_pos=sw_for_clist->allocation.width;
536 gtk_paned_set_position(GTK_PANED(pane),1);
537 hidden=1;
538 }
539
540 /* XXX kludge: make sure pic is fixed in zoom mode */
541 pic_win_resized(NULL,NULL);
542
543 RECURSE_PROTECT_END;
544 }
545
546
547 void cb_iconify(void)
548 {
549 XIconifyWindow(GDK_WINDOW_XDISPLAY(mainwin->window),
550 GDK_WINDOW_XWINDOW(mainwin->window),
551 XScreenNumberOfScreen(XDefaultScreenOfDisplay(
552 GDK_WINDOW_XDISPLAY(mainwin->window))));
553 }
554
555
556 gint selector_button_press(GtkWidget *widget,GdkEventButton *event)
557 {
558 int row,col;
559
560 if(ignore_selector_input)
561 {
562 gtk_signal_emit_stop_by_name(GTK_OBJECT(widget),"button_press_event");
563 return(TRUE);
564 }
565
566 /* in theory we should screen out double-clicks, in case someone does
567 * that in error. But we seem to get two single-clicks *then* a double-click
568 * (seems bizarre to me, surely it should just be single-click then double!?),
569 * meaning that the picture *might* be loaded twice, but the selection
570 * stays intact. The picture seems to only be loaded twice if the picture
571 * has completely loaded before the double-click event is received,
572 * so this probably isn't too bad, and actually works out better than
573 * screening them out in practice.
574 */
575
576 switch(event->button)
577 {
578 case 1:
579 if(event->state&GDK_CONTROL_MASK)
580 {
581 /* stop the clist widget seeing it */
582 gtk_signal_emit_stop_by_name(GTK_OBJECT(widget),"button_press_event");
583 return(TRUE); /* otherwise ignored, we do it on release */
584 }
585 break;
586
587 case 3:
588 /* move cursor to row clicked on (if any) */
589 gtk_clist_get_selection_info(GTK_CLIST(clist),
590 event->x,event->y,&row,&col);
591 cb_back_to_clist(); /* show selector and switch to it */
592 if(row>=0 && row<numrows)
593 set_focus_row(row);
594
595 /* finally we bother showing the menu :-) */
596 gtk_menu_popup(GTK_MENU(selector_menu),NULL,NULL,NULL,NULL,3,event->time);
597 return(TRUE);
598 }
599
600 return(FALSE);
601 }
602
603
604 gint selector_button_release(GtkWidget *widget,GdkEventButton *event)
605 {
606 int row,col;
607
608 if(ignore_selector_input)
609 {
610 gtk_signal_emit_stop_by_name(GTK_OBJECT(widget),"button_release_event");
611 return(TRUE);
612 }
613
614 switch(event->button)
615 {
616 case 1:
617 if(event->state&GDK_CONTROL_MASK)
618 {
619 gtk_clist_get_selection_info(GTK_CLIST(clist),
620 event->x,event->y,&row,&col);
621 if(row>=0 && row<numrows) /* sanity check :-) */
622 set_tagged_state(row,-1); /* toggle */
623 return(TRUE);
624 }
625 break;
626 }
627
628 return(FALSE);
629 }
630
631
632 /* button press on any part of the viewer
633 * (except the scrollbars, filtered out kludgily by the next routine)
634 */
635 gint viewer_button_press(GtkWidget *widget,GdkEventButton *event)
636 {
637 switch(event->button)
638 {
639 case 1: /* left button starts image drag */
640 /* but with shift, scales up */
641 if(event->state&GDK_SHIFT_MASK)
642 {
643 cb_scaling_double();
644 next_on_release=0;
645 break;
646 }
647
648 /* and with control, scales selected axis only */
649 if(event->state&GDK_CONTROL_MASK)
650 {
651 if(mouse_scale_x)
652 cb_xscaling_double();
653 else
654 cb_yscaling_double();
655 next_on_release=0;
656 break;
657 }
658
659 ignore_drag=0;
660 next_on_release=1;
661 /* set initial position */
662 orig_x=event->x_root;
663 orig_y=event->y_root;
664 break;
665
666 case 2: /* middle button is a bit like Esc (handy in auto-hide mode) */
667 if(hidden)
668 cb_back_to_clist(); /* like Esc - show and focus */
669 else
670 cb_hide_selector(); /* really toggles it */
671 break;
672
673 case 3: /* right button gives menu */
674 /* but with shift, scales down */
675 if(event->state&GDK_SHIFT_MASK)
676 {
677 cb_scaling_halve();
678 break;
679 }
680
681 /* and with control, scales down selected axis only */
682 if(event->state&GDK_CONTROL_MASK)
683 {
684 if(mouse_scale_x)
685 cb_xscaling_halve();
686 else
687 cb_yscaling_halve();
688 break;
689 }
690
691 gtk_menu_popup(GTK_MENU(viewer_menu),NULL,NULL,NULL,NULL,3,event->time);
692 break;
693 }
694
695 return(TRUE);
696 }
697
698
699 gint viewer_button_release(GtkWidget *widget,GdkEventButton *event)
700 {
701 switch(event->button)
702 {
703 case 1:
704 if(next_on_release && click_nextpic)
705 cb_next_image();
706 next_on_release=0;
707 break;
708
709 default:
710 return(FALSE);
711 }
712
713 return(TRUE);
714 }
715
716
717 /* button press on one of the image's scrollbars. Needed to override
718 * the above, as bringing up the menu by right-clicking on a scrollbar
719 * causes all mouse stuff to hang for some reason...!
720 */
721 gint viewer_sb_button_press(GtkWidget *widget,GdkEventButton *event)
722 {
723 /* doesn't have to do anything */
724 return(TRUE);
725 }
726
727
728 gint clist_sw_ebox_button_press(GtkWidget *widget,GdkEventButton *event)
729 {
730 if(event->button==1)
731 {
732 /* this is a drag on the selector, so make sure image-dragging ignores it! */
733 ignore_drag=1;
734 next_on_release=0; /* don't try to move to next image */
735 }
736
737 return(FALSE);
738 }
739
740
741 void move_pic(float xadd,float yadd)
742 {
743 GtkAdjustment *hadj,*vadj;
744 float new_x,new_y;
745
746 /* add on to adjustment, checking bounds */
747 hadj=GTK_ADJUSTMENT(gtk_scrolled_window_get_hadjustment(
748 GTK_SCROLLED_WINDOW(sw_for_pic)));
749 vadj=GTK_ADJUSTMENT(gtk_scrolled_window_get_vadjustment(
750 GTK_SCROLLED_WINDOW(sw_for_pic)));
751
752 if(xadd)
753 {
754 new_x=hadj->value+xadd;
755 if(new_x<hadj->lower) new_x=hadj->lower;
756 if(new_x>hadj->upper-hadj->page_size) new_x=hadj->upper-hadj->page_size;
757 gtk_adjustment_set_value(hadj,new_x);
758 }
759
760 if(yadd)
761 {
762 new_y=vadj->value+yadd;
763 if(new_y<vadj->lower) new_y=vadj->lower;
764 if(new_y>vadj->upper-vadj->page_size) new_y=vadj->upper-vadj->page_size;
765 gtk_adjustment_set_value(vadj,new_y);
766 }
767 }
768
769
770 gint viewer_motion(GtkWidget *widget,GdkEventMotion *event)
771 {
772 float diff_x,diff_y;
773
774 /* ignore it if the drag started on the selector */
775 if(ignore_drag) return(FALSE);
776
777 next_on_release=0;
778
779 /* ignore it if neither scrollbar is onscreen */
780 if(!GTK_SCROLLED_WINDOW(sw_for_pic)->hscrollbar_visible &&
781 !GTK_SCROLLED_WINDOW(sw_for_pic)->vscrollbar_visible)
782 return(TRUE);
783
784 /* XXX! should absorb all pending motion-notify events somehow, and
785 * only use the X/Y pos of the last of those!
786 */
787 /* have to use [xy]_root, as the window the events happen on will be moving! */
788 diff_x=orig_x-event->x_root;
789 diff_y=orig_y-event->y_root;
790 orig_x=event->x_root;
791 orig_y=event->y_root;
792
793 move_pic(diff_x,diff_y);
794
795 return(TRUE);
796 }
797
798
799 /* used by gtk_menu_popup() calls invoked from keyboard */
800 void keyboard_menu_pos(GtkMenu *menu,gint *xp,gint *yp,GtkWidget *data)
801 {
802 gdk_window_get_position(mainwin->window,xp,yp);
803 *xp+=data->allocation.x;
804 *yp+=data->allocation.y;
805 }
806
807
808 /* this may call pic_win_resized, and can inherit the recursion problem
809 * of render_pixmap, so be careful.
810 */
811 int common_key_press(GdkEventKey *event)
812 {
813 int maxpos,oldpos,pos=sw_for_clist->allocation.width;
814 int step=20;
815
816 if(event->state&GDK_CONTROL_MASK)
817 step=5;
818
819 switch(event->keyval)
820 {
821 case GDK_bracketleft: /* [ */
822 oldpos=pos;
823 pos-=step;
824 if(pos<1) pos=1;
825 if(pos!=oldpos)
826 {
827 gtk_paned_set_position(GTK_PANED(pane),pos);
828 pic_win_resized(NULL,NULL); /* XXX kludge for zoom mode */
829 }
830 return(TRUE);
831
832 case GDK_bracketright: /* ] */
833 maxpos=mainwin->allocation.width;
834 oldpos=pos;
835 pos+=step;
836 if(pos>maxpos) pos=maxpos;
837 if(pos!=oldpos)
838 {
839 gtk_paned_set_position(GTK_PANED(pane),pos);
840 pic_win_resized(NULL,NULL); /* XXX kludge for zoom mode */
841 }
842 return(TRUE);
843
844 case GDK_asciitilde: /* ~ */
845 if(pos!=default_sel_width)
846 {
847 hidden=0; /* also treat as unhide */
848 gtk_paned_set_position(GTK_PANED(pane),default_sel_width);
849 pic_win_resized(NULL,NULL); /* XXX kludge for zoom mode */
850 }
851 return(TRUE);
852
853 default:
854 return(FALSE);
855 }
856 }
857
858
859
860
861 void cb_viewer_next_tagged(void)
862 {
863 cb_nextprev_tagged_image(1,1);
864 }
865
866 void cb_viewer_prev_tagged(void)
867 {
868 cb_nextprev_tagged_image(0,1);
869 }
870
871
872 gint viewer_key_press(GtkWidget *widget,GdkEventKey *event)
873 {
874 /* this first bit is an adapted RECURSE_PROTECT_START */
875 static int here=0;
876
877 if(here)
878 {
879 /* stop the event to avoid weirdness */
880 gtk_signal_emit_stop_by_name(GTK_OBJECT(widget),"key_press_event");
881 return(TRUE);
882 }
883
884 here=1;
885
886 /* This should only handle the minimum necessary, with most things
887 * being done via accelerators for menu items.
888 *
889 * The main things to handle here are the cursors, page up/down, etc.
890 */
891
892 /* XXX these are zgv-ish for now; want that as the default, but
893 * really should have an optional mouse-reflecting mode using
894 * the adjustments' step size and page increment.
895 */
896
897 /* treat shift-cursor as page up/down/left/right */
898 if((event->state&GDK_SHIFT_MASK))
899 switch(event->keyval)
900 {
901 case GDK_Left: goto page_left;
902 case GDK_Right: goto page_right;
903 case GDK_Up: goto page_up;
904 case GDK_Down: goto page_down;
905 }
906
907 switch(event->keyval)
908 {
909 case GDK_space:
910 if((event->state&GDK_CONTROL_MASK))
911 cb_tag_then_next();
912 else
913 cb_next_image();
914 break;
915
916 case GDK_slash:
917 cb_viewer_next_tagged();
918 break;
919
920 case GDK_question:
921 cb_viewer_prev_tagged();
922 break;
923
924 /* the way this works also means that e.g. control-shift-h will
925 * move a small amount (like unmodified h), but that doesn't hurt,
926 * and I s'pose at least it's consistent. :-)
927 */
928 case GDK_Left: case GDK_H:
929 move_pic((event->state&GDK_CONTROL_MASK)?-10.:-100., 0.);
930 break;
931 case GDK_Right: case GDK_L:
932 move_pic((event->state&GDK_CONTROL_MASK)?+10.:+100., 0.);
933 break;
934 case GDK_Up: case GDK_K:
935 move_pic(0., (event->state&GDK_CONTROL_MASK)?-10.:-100.);
936 break;
937 case GDK_Down: case GDK_J:
938 move_pic(0., (event->state&GDK_CONTROL_MASK)?+10.:+100.);
939 break;
940
941 case GDK_h:
942 move_pic(-10.,0.);
943 break;
944 case GDK_l:
945 move_pic(+10.,0.);
946 break;
947 case GDK_k:
948 move_pic(0.,-10.);
949 break;
950 case GDK_j:
951 move_pic(0.,+10.);
952 break;
953
954 case GDK_Page_Up: case GDK_u:
955 page_up:
956 if(event->keyval!=GDK_u || (event->state&GDK_CONTROL_MASK))
957 move_pic(0.,-0.9*GTK_ADJUSTMENT(gtk_scrolled_window_get_vadjustment(
958 GTK_SCROLLED_WINDOW(sw_for_pic)))->page_size);
959 else
960 {
961 RECURSE_PROTECT_END;
962 return(FALSE); /* don't stop event if not handled */
963 }
964 break;
965 case GDK_Page_Down: case GDK_v:
966 page_down:
967 if(event->keyval!=GDK_v || (event->state&GDK_CONTROL_MASK))
968 move_pic(0.,+0.9*GTK_ADJUSTMENT(gtk_scrolled_window_get_vadjustment(
969 GTK_SCROLLED_WINDOW(sw_for_pic)))->page_size);
970 else
971 {
972 RECURSE_PROTECT_END;
973 return(FALSE);
974 }
975 break;
976 case GDK_minus:
977 page_left:
978 move_pic(-0.9*GTK_ADJUSTMENT(gtk_scrolled_window_get_hadjustment(
979 GTK_SCROLLED_WINDOW(sw_for_pic)))->page_size, 0.);
980 break;
981 case GDK_equal:
982 page_right:
983 move_pic(+0.9*GTK_ADJUSTMENT(gtk_scrolled_window_get_hadjustment(
984 GTK_SCROLLED_WINDOW(sw_for_pic)))->page_size, 0.);
985 break;
986
987 case GDK_Home: case GDK_a:
988 if(event->keyval!=GDK_a || (event->state&GDK_CONTROL_MASK))
989 move_pic(-32768.,-32768.); /* X window size limit is 32767x32767 */
990 else
991 {
992 RECURSE_PROTECT_END; /* don't stop event if not handled */
993 return(FALSE);
994 }
995 break;
996 case GDK_End: case GDK_e:
997 if(event->keyval!=GDK_e || (event->state&GDK_CONTROL_MASK))
998 move_pic(+32768.,+32768.);
999 else
1000 {
1001 RECURSE_PROTECT_END;
1002 return(FALSE);
1003 }
1004 break;
1005
1006 case GDK_Tab: /* also treat tab as esc */
1007 cb_back_to_clist();
1008 break;
1009
1010 case GDK_F10: case GDK_Menu:
1011 /* pop-up menu on F10 (Emacs-like) or Menu */
1012 gtk_menu_popup(GTK_MENU(viewer_menu),NULL,NULL,
1013 (GtkMenuPositionFunc)keyboard_menu_pos,sw_for_pic,
1014 3,event->time);
1015 break;
1016
1017 default:
1018 /* check for non-menu-item keys common to selector and viewer */
1019 if(!common_key_press(event))
1020 {
1021 RECURSE_PROTECT_END;
1022 return(FALSE); /* don't stop event if not handled */
1023 }
1024 }
1025
1026 /* if we handled it, stop anything else getting the event.
1027 * This is needed to handle us tabbing into the viewer and using it;
1028 * in that case, the selector still accepts focus, so (e.g.) pressing
1029 * down wouldn't work properly.
1030 */
1031 gtk_signal_emit_stop_by_name(GTK_OBJECT(widget),"key_press_event");
1032
1033 RECURSE_PROTECT_END;
1034 return(TRUE);
1035 }
1036
1037
1038 void view_focus_row_file(void)
1039 {
1040 int row;
1041
1042 /* skip it early if we're already busy */
1043 if(in_nextprev) return;
1044
1045 in_nextprev=1; /* in effect :-) */
1046
1047 /* one difference from normal clist keyboard-select behaviour;
1048 * we always select (rather than toggling), even if image was
1049 * previously selected.
1050 */
1051 row=GTK_CLIST(clist)->focus_row;
1052 if(row>=0 && row<numrows)
1053 {
1054 gtk_clist_unselect_all(GTK_CLIST(clist));
1055 /* this sets current_selection and zeroes in_nextprev too */
1056 gtk_clist_select_row(GTK_CLIST(clist),row,0);
1057 in_nextprev=0;
1058 }
1059 else
1060 in_nextprev=0;
1061 }
1062
1063
1064 void cb_nextprev_tagged_image(int next,int view)
1065 {
1066 int f,dest,row=GTK_CLIST(clist)->focus_row;
1067 struct clist_data_tag *datptr;
1068 int incr=(next?1:-1);
1069
1070 if(in_nextprev) return;
1071
1072 dest=-1;
1073 for(f=row+incr;(next && f<numrows) || (!next && f>=0);f+=incr)
1074 {
1075 datptr=gtk_clist_get_row_data(GTK_CLIST(clist),f);
1076 if(datptr && datptr->tagged)
1077 {
1078 dest=f;
1079 break;
1080 }
1081 }
1082
1083 if(dest==-1)
1084 return;
1085
1086 set_focus_row(dest);
1087 make_visible_if_not(dest);
1088 if(view)
1089 view_focus_row_file();
1090 }
1091
1092
1093 void cb_selector_next_tagged(void)
1094 {
1095 cb_nextprev_tagged_image(1,0);
1096 }
1097
1098 void cb_selector_prev_tagged(void)
1099 {
1100 cb_nextprev_tagged_image(0,0);
1101 }
1102
1103
1104 gint selector_key_press(GtkWidget *widget,GdkEventKey *event)
1105 {
1106 static int goto_next_char=0,goto_next_evtime;
1107 /* this first bit is an adapted RECURSE_PROTECT_START */
1108 static int here=0;
1109
1110 if(here || ignore_selector_input)
1111 {
1112 /* stop the event to avoid weirdness */
1113 gtk_signal_emit_stop_by_name(GTK_OBJECT(widget),"key_press_event");
1114 return(TRUE);
1115 }
1116
1117 here=1;
1118
1119 /* This is for the odd selector thing which isn't on the menus. */
1120
1121 if(goto_next_char)
1122 {
1123 /* completely ignore any shift keypress! */
1124 if(event->keyval==GDK_Shift_L || event->keyval==GDK_Shift_R)
1125 {
1126 RECURSE_PROTECT_END;
1127 return(FALSE);
1128 }
1129
1130 goto_next_char=0;
1131
1132 if(event->time-goto_next_evtime<=2000 && /* ignore if >2 secs later */
1133 event->keyval>=33 && event->keyval<=126)
1134 {
1135 int f,nofiles=1,found=0;
1136 struct clist_data_tag *datptr;
1137 char *ptr;
1138
1139 /* go to first file (not dir) which starts with that char.
1140 * if there isn't one, go to first which starts with a later
1141 * char.
1142 * also, if there aren't any files (just dirs) don't move;
1143 * otherwise, if there are no files with 1st char >=keyval,
1144 * go to last file.
1145 *
1146 * nicest way to do this would be a binary search, but that
1147 * would complicate matters; a linear search may be crude
1148 * but it's still blindingly fast. And at least a linear one
1149 * gives *predictably* useless results when not using
1150 * sort-by-name. :-)
1151 */
1152 for(f=0;f<numrows;f++)
1153 {
1154 datptr=gtk_clist_get_row_data(GTK_CLIST(clist),f);
1155 if(!datptr->isdir)
1156 {
1157 gtk_clist_get_text(GTK_CLIST(clist),f,SELECTOR_NAME_COL,&ptr);
1158 nofiles=0;
1159 if(*ptr>=event->keyval)
1160 {
1161 set_focus_row(f);
1162 found=1;
1163 break;
1164 }
1165 }
1166 }
1167
1168 /* if didn't find one >=keyval but there *are* files in the
1169 * dir, go to the last one.
1170 */
1171 if(!found && !nofiles)
1172 set_focus_row(numrows-1);
1173
1174 /* recentre on it */
1175 if(!nofiles)
1176 gtk_clist_moveto(GTK_CLIST(clist),GTK_CLIST(clist)->focus_row,0,0.5,0.);
1177 }
1178 }
1179 else
1180 {
1181 int oldrow,incdec,up;
1182 int row=GTK_CLIST(clist)->focus_row;
1183 float vpage;
1184
1185 /* if not a goto-next-char char... */
1186 switch(event->keyval)
1187 {
1188 case GDK_Return: /* select pic */
1189 case GDK_space: /* handle this too, for consistency */
1190 view_focus_row_file();
1191 break;
1192
1193 case GDK_slash:
1194 cb_selector_next_tagged();
1195 break;
1196
1197 case GDK_question:
1198 cb_selector_prev_tagged();
1199 break;
1200
1201 case GDK_apostrophe:
1202 case GDK_g:
1203 goto_next_char=1;
1204 goto_next_evtime=event->time;
1205 break;
1206
1207 case GDK_k: /* up */
1208 if(row>0)
1209 {
1210 set_focus_row(row=row-1);
1211 if(gtk_clist_row_is_visible(GTK_CLIST(clist),row)!=GTK_VISIBILITY_FULL)
1212 gtk_clist_moveto(GTK_CLIST(clist),row,0,0.,0.);
1213 }
1214 break;
1215 case GDK_j: /* down */
1216 if(row<numrows-1)
1217 {
1218 set_focus_row(row=row+1);
1219 if(gtk_clist_row_is_visible(GTK_CLIST(clist),row)!=GTK_VISIBILITY_FULL)
1220 gtk_clist_moveto(GTK_CLIST(clist),row,0,1.,0.);
1221 }
1222 break;
1223
1224 #define RET_IF_NOT_CONTROL \
1225 if(!(event->state&GDK_CONTROL_MASK)) {RECURSE_PROTECT_END;return(FALSE);}
1226
1227 case GDK_u: case GDK_v: /* ctrl-u/v, like page up/down */
1228 RET_IF_NOT_CONTROL;
1229 up=(event->keyval==GDK_u);
1230 oldrow=row;
1231 vpage=GTK_ADJUSTMENT(
1232 gtk_clist_get_vadjustment(GTK_CLIST(clist)))->page_size;
1233 incdec=(int)((vpage/
1234 (1+(thin_rows?ROW_HEIGHT_THIN:ROW_HEIGHT_NORMAL)))+0.5);
1235 /* next statement is bug-compatible with true page up/down :-)
1236 * (i.e. page up/down have no effect in a window where the list
1237 * is less than 1.5 rows high)
1238 */
1239 if(incdec>0) incdec--;
1240 row+=(up?-incdec:incdec);
1241 if(row<0) row=0;
1242 if(row>=numrows) row=numrows-1;
1243 if(row!=oldrow)
1244 {
1245 set_focus_row(row);
1246 if(gtk_clist_row_is_visible(GTK_CLIST(clist),row)!=GTK_VISIBILITY_FULL)
1247 gtk_clist_moveto(GTK_CLIST(clist),row,0,up?0.:1.,0.);
1248 }
1249 break;
1250
1251 case GDK_a: /* ctrl-a, like ctrl-home */
1252 RET_IF_NOT_CONTROL;
1253 if(numrows)
1254 set_focus_row(0),make_visible_if_not(0);
1255 break;
1256 case GDK_e: /* ctrl-e, like ctrl-end */
1257 RET_IF_NOT_CONTROL;
1258 if(numrows)
1259 set_focus_row(numrows-1),make_visible_if_not(numrows-1);
1260 break;
1261
1262 case GDK_semicolon: /* do the same as colon */
1263 case GDK_colon: /* XXX actually, menu binding seems broken? */
1264 cb_file_details();
1265 break;
1266
1267 case GDK_KP_Add:
1268 case GDK_plus: /* may be preferable on some non-US/UK keyboards, and on laptops */
1269 case GDK_0: /* last-ditch alternative for non-US/UK laptops */
1270 if(event->state&GDK_MOD1_MASK)
1271 cb_tag_all();
1272 else
1273 cb_tag_file();
1274 break;
1275
1276 case GDK_KP_Subtract:
1277 case GDK_9:
1278 if(event->state&GDK_MOD1_MASK)
1279 cb_untag_all();
1280 else
1281 cb_untag_file();
1282 break;
1283
1284 case GDK_F10: case GDK_Menu:
1285 /* pop-up menu, as for viewer */
1286 gtk_menu_popup(GTK_MENU(selector_menu),NULL,NULL,
1287 (GtkMenuPositionFunc)keyboard_menu_pos,sw_for_clist,
1288 3,event->time);
1289 break;
1290
1291 default:
1292 /* check for non-menu-item keys common to selector and viewer */
1293 if(!common_key_press(event))
1294 {
1295 RECURSE_PROTECT_END;
1296 return(FALSE); /* don't stop event if not handled */
1297 }
1298 }
1299 }
1300
1301 /* if we handled it, stop anything else getting the event. */
1302 gtk_signal_emit_stop_by_name(GTK_OBJECT(widget),"key_press_event");
1303
1304 RECURSE_PROTECT_END;
1305 return(TRUE);
1306 }
1307
1308
1309
1310 void get_zoomed_size(int *swp,int *shp)
1311 {
1312 int scrnwide=sw_for_pic->allocation.width-SW_BORDER_WIDTH;
1313 int scrnhigh=sw_for_pic->allocation.height-SW_BORDER_WIDTH;
1314 int width=theimage->w;
1315 int height=theimage->h;
1316
1317 if (!zoom_panorama)
1318 {
1319 /* try landscapey */
1320 *swp=scrnwide; *shp=(scrnwide*height)/width;
1321 if(*shp>scrnhigh)
1322 /* no, oh well portraity then */
1323 *shp=scrnhigh,*swp=(scrnhigh*width)/height;
1324 }
1325 else
1326 {
1327 if ((width/scrnwide)>(height/scrnhigh))
1328 /* pan horizontally */
1329 zoom_panorama_sb=0,*swp=(scrnhigh*width)/height,*shp=scrnhigh;
1330 else
1331 /* pan vertically */
1332 zoom_panorama_sb=1,*swp=scrnwide,*shp=(scrnwide*height)/width;
1333 }
1334 /* don't expand if it's shrink-only. */
1335 if(zoom_reduce_only && (*swp>width || *shp>height))
1336 *swp=width,*shp=height;
1337 }
1338
1339
1340 /* render pixmap from image, resize drawing area to fit, and just
1341 * generally update things. Call this to update the image after pretty
1342 * much any change at all. :-)
1343 *
1344 * NB: this calls do_gtk_stuff(), so callers sensitive to recursion
1345 * beware! (In other words, defend against recursion with something like
1346 * the in_render stuff below, or RECURSE_PROTECT_START/END.)
1347 */
1348 void render_pixmap(int reset_pos)
1349 {
1350 int sw,sh;
1351 int width,height;
1352 int scaling_up_enabled=0;
1353 static int in_render=0;
1354
1355 /* never bother if no image is loaded */
1356 if(!theimage) return;
1357
1358 if(in_render) return;
1359 in_render=1;
1360
1361 width=theimage->w;
1362 height=theimage->h;
1363
1364 sw=width; sh=height;
1365
1366 if(zoom)
1367 get_zoomed_size(&sw,&sh);
1368
1369 if(!zoom)
1370 {
1371 if(xscaling!=1 || yscaling!=1) /* other non-1:1 scales */
1372 {
1373 if(xscaling<-1) sw/=-xscaling; else sw*=xscaling;
1374 if(yscaling<-1) sh/=-yscaling; else sh*=yscaling;
1375 if(sw<1) sw=1;
1376 if(sh<1) sh=1;
1377 if(sw>32767) sw=32767;
1378 if(sh>32767) sh=32767;
1379 /* XXX could do with a combined test, to limit the resulting
1380 * image to at most N megs.
1381 */
1382 }
1383 }
1384
1385 /* so now our image will be sw x sh */
1386 if(!scaling_up_enabled)
1387 backend_render_pixmap_for_image(theimage,sw,sh);
1388
1389 /* remove any backing pixmap */
1390 gdk_window_set_back_pixmap(drawing_area->window,NULL,FALSE);
1391
1392 if(thepixmap)
1393 backend_pixmap_destroy(thepixmap),thepixmap=NULL;
1394 if(!scaling_up_enabled)
1395 thepixmap=backend_get_and_detach_pixmap(theimage);
1396
1397 /* set drawing area to size of pixmap (also generates expose event) */
1398 gtk_widget_set_usize(align,sw,sh);
1399 gtk_widget_set_usize(drawing_area,sw,sh);
1400
1401 /* go back to top-left */
1402 if(reset_pos)
1403 {
1404 gtk_adjustment_set_value(
1405 gtk_scrolled_window_get_hadjustment(GTK_SCROLLED_WINDOW(sw_for_pic)),0.);
1406 gtk_adjustment_set_value(
1407 gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(sw_for_pic)),0.);
1408 }
1409
1410 /* let scrollbars appear/disappear as needed *before* we put pixmap on.
1411 * Without this you get a nasty `bounce' effect whenever scrollbars
1412 * appear/disappear. But it can still happen when resizing. (XXX)
1413 */
1414 do_gtk_stuff();
1415
1416 /* put pixmap onto window as background. We could then free it, but
1417 * we don't - see idle_zoom_resize() for why. (Basically, we restore
1418 * the pic there after (to avoid `bounce') having removed it.)
1419 */
1420 if(thepixmap)
1421 {
1422 gdk_window_set_back_pixmap(drawing_area->window,thepixmap,FALSE);
1423 gdk_window_clear(drawing_area->window);
1424 }
1425
1426 in_render=0;
1427 }
1428
1429
1430 /* see below for what this is */
1431 void idle_zoom_resize(void)
1432 {
1433 /* make sure we won't be called again */
1434 gtk_idle_remove(zoom_resize_idle_tag);
1435 zoom_resize_idle_tag=-1;
1436
1437 if(zoom)
1438 render_pixmap(1); /* different size, render again */
1439 else
1440 {
1441 gdk_window_set_back_pixmap(drawing_area->window,thepixmap,FALSE);
1442 gdk_window_clear(drawing_area->window);
1443 }
1444 }
1445
1446
1447 /* note that in zoom mode this inherits the recursion problem
1448 * of render_pixmap due to idle_zoom_resize, so be careful.
1449 */
1450 gint pic_win_resized(GtkWidget *widget,GdkEventConfigure *event)
1451 {
1452 /* NB: this shouldn't use widget or event, as it's sometimes
1453 * called with them both being NULL (by the auto-hide stuff).
1454 */
1455
1456 /* Now also does this for non-zoomed pics in a (possibly vain)
1457 * attempt to reduce any `bounce' effect. But it should at least
1458 * save us getting bogged down when using `opaque resize' on
1459 * slower systems.
1460 */
1461 gdk_window_set_back_pixmap(drawing_area->window,NULL,FALSE);
1462
1463 if(zoom_resize_idle_tag!=-1)
1464 gtk_idle_remove(zoom_resize_idle_tag);
1465
1466 /* using resize priority gives better results if using `opaque resize',
1467 * but seems to break `normal' resizing. Not a good tradeoff. :-(
1468 */
1469 zoom_resize_idle_tag=gtk_idle_add_priority(GTK_PRIORITY_DEFAULT/*RESIZE*/,
1470 (GtkFunction)idle_zoom_resize,NULL);
1471 return(FALSE);
1472 }
1473
1474
1475 void fix_row_heights(void)
1476 {
1477 gtk_clist_set_row_height(GTK_CLIST(clist),
1478 thin_rows?ROW_HEIGHT_THIN:ROW_HEIGHT_NORMAL);
1479 }
1480
1481
1482 void set_thumbnail_column_width(void)
1483 {
1484 gtk_clist_set_column_width(GTK_CLIST(clist),SELECTOR_TN_COL,
1485 thin_rows?(80/ROW_HEIGHT_DIV+1):80);
1486 }
1487
1488
1489
1490 void same_centre(int *xp,int *yp,int newxsc,int newysc,
1491 int oldx,int oldy,int oldxsc,int oldysc)
1492 {
1493 int xa,ya,sw,sh;
1494 int width,height;
1495 int scrnwide,scrnhigh;
1496
1497 if(!theimage) return;
1498
1499 width=theimage->w;
1500 height=theimage->h;
1501 scrnwide=GTK_ADJUSTMENT(gtk_scrolled_window_get_hadjustment(
1502 GTK_SCROLLED_WINDOW(sw_for_pic)))->page_size;
1503 scrnhigh=GTK_ADJUSTMENT(gtk_scrolled_window_get_vadjustment(
1504 GTK_SCROLLED_WINDOW(sw_for_pic)))->page_size;
1505
1506 xa=ya=0;
1507 sw=oldxsc*width;
1508 sh=oldysc*height;
1509 if(sw<scrnwide) xa=(scrnwide-sw)>>1;
1510 if(sh<scrnhigh) ya=(scrnhigh-sh)>>1;
1511
1512 /* finds centre of old visible area, and makes it centre of new one */
1513 *xp=(oldx-xa+(scrnwide>>1))*newxsc/oldxsc;
1514 *yp=(oldy-ya+(scrnhigh>>1))*newysc/oldysc;
1515
1516 xa=ya=0;
1517 sw=newxsc*width;
1518 sh=newysc*height;
1519 if(sw<scrnwide) xa=(scrnwide-sw)>>1;
1520 if(sh<scrnhigh) ya=(scrnhigh-sh)>>1;
1521
1522 *xp-=(scrnwide>>1)+xa;
1523 *yp-=(scrnhigh>>1)+ya;
1524 }
1525
1526
1527 /* call this before render_pixmap() */
1528 void get_new_centre(int oldxsc,int oldysc,int newxsc,int newysc,
1529 float *xp,float *yp)
1530 {
1531 GtkAdjustment *hadj,*vadj;
1532 int oldx,oldy,x,y;
1533
1534 hadj=GTK_ADJUSTMENT(gtk_scrolled_window_get_hadjustment(
1535 GTK_SCROLLED_WINDOW(sw_for_pic)));
1536 vadj=GTK_ADJUSTMENT(gtk_scrolled_window_get_vadjustment(
1537 GTK_SCROLLED_WINDOW(sw_for_pic)));
1538
1539 oldx=(int)hadj->value;
1540 oldy=(int)vadj->value;
1541 same_centre(&x,&y,newxsc,newysc,oldx,oldy,oldxsc,oldysc);
1542
1543 *xp=(float)x; *yp=(float)y;
1544 }
1545
1546
1547 /* call this after render_pixmap() */
1548 void move_to_new_centre(float new_x,float new_y)
1549 {
1550 GtkAdjustment *hadj,*vadj;
1551
1552 hadj=GTK_ADJUSTMENT(gtk_scrolled_window_get_hadjustment(
1553 GTK_SCROLLED_WINDOW(sw_for_pic)));
1554 vadj=GTK_ADJUSTMENT(gtk_scrolled_window_get_vadjustment(
1555 GTK_SCROLLED_WINDOW(sw_for_pic)));
1556
1557 if(new_x<hadj->lower) new_x=hadj->lower;
1558 if(new_x>hadj->upper-hadj->page_size) new_x=hadj->upper-hadj->page_size;
1559 gtk_adjustment_set_value(hadj,new_x);
1560
1561 if(new_y<vadj->lower) new_y=vadj->lower;
1562 if(new_y>vadj->upper-vadj->page_size) new_y=vadj->upper-vadj->page_size;
1563 gtk_adjustment_set_value(vadj,new_y);
1564 }
1565
1566
1567 /* this inherits the same recursion problem as render_pixmap(),
1568 * so be careful.
1569 */
1570 void scaling_finish(int oldxsc,int oldysc)
1571 {
1572 float x,y;
1573
1574 /* fairly hairy... :-/ */
1575 if(oldxsc!=xscaling || oldysc!=yscaling)
1576 get_new_centre(oldxsc,oldysc,xscaling,yscaling,&x,&y);
1577 render_pixmap(0);
1578 gtk_widget_hide(drawing_area);
1579 if(oldxsc!=xscaling || oldysc!=yscaling)
1580 move_to_new_centre(x,y);
1581 gtk_widget_show(drawing_area);
1582 /* making it smaller can look very nasty before it's redrawn,
1583 * but clearing the window looks fairly nasty too, so...
1584 */
1585 if(xscaling<oldxsc || yscaling<oldysc)
1586 {
1587 gdk_window_clear(drawing_area->window);
1588 gdk_flush();
1589 }
1590 gdk_flush();
1591 }
1592
1593
1594 /* turn off zoom (if enabled) without a call to render_pixmap() */
1595 void undo_zoom(void)
1596 {
1597 if(!zoom) return;
1598
1599 listen_to_toggles=0;
1600 zoom=0;
1601 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sw_for_pic),
1602 GTK_POLICY_AUTOMATIC,GTK_POLICY_AUTOMATIC);
1603 xscaling=yscaling=1;
1604 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(zoom_widget),zoom);
1605 listen_to_toggles=1;
1606 }
1607
1608
1609
1610 /* the size test at the end means the scale-both/xscale/yscale
1611 * routines need to be atomic, so the least painful approach seems
1612 * to be to have a generic do-x-and/or-y routine for each scaling
1613 * option, then separate both/x/y routines calling that.
1614 */
1615
1616 void xy_scaling_double(int do_x,int do_y)
1617 {
1618 static int in_routine=0;
1619 int xtmp=xscaling,ytmp=yscaling,oldxsc=xscaling,oldysc=yscaling;
1620
1621 /* if there's no image, don't do anything */
1622 if (!theimage) return;
1623
1624 /* if recursed, don't bother */
1625 if(in_routine) return;
1626 in_routine=1;
1627
1628 #define SCALE(ntmp,nscaling) \
1629 do { \
1630 ntmp=(nscaling<-1?nscaling/2:nscaling*2); \
1631 if(ntmp==-1 || ntmp==0) ntmp=1; \
1632 if(ntmp>512) ntmp=512; \
1633 } while(0)
1634
1635 if(do_x) SCALE(xtmp,xscaling);
1636 if(do_y) SCALE(ytmp,yscaling);
1637
1638 #undef SCALE
1639
1640 if(theimage->w*xtmp<=32767 && theimage->h*ytmp<=32767)
1641 {
1642 undo_zoom();
1643 xscaling=xtmp;
1644 yscaling=ytmp;
1645 scaling_finish(oldxsc,oldysc);
1646 }
1647
1648 in_routine=0;
1649 }
1650
1651 void cb_scaling_double(void)
1652 {
1653 xy_scaling_double(1,1);
1654 }
1655
1656 void cb_xscaling_double(void)
1657 {
1658 xy_scaling_double(1,0);
1659 }
1660
1661 void cb_yscaling_double(void)
1662 {
1663 xy_scaling_double(0,1);
1664 }
1665
1666
1667 void xy_scaling_add(int do_x,int do_y)
1668 {
1669 static int in_routine=0;
1670 int xtmp=xscaling,ytmp=yscaling,oldxsc=xscaling,oldysc=yscaling;
1671
1672 /* if there's no image, don't do anything */
1673 if (!theimage) return;
1674
1675 /* if recursed, don't bother */
1676 if(in_routine) return;
1677 in_routine=1;
1678
1679 #define SCALE(ntmp,nscaling) \
1680 do { \
1681 ntmp=nscaling+1; \
1682 if(ntmp==-1 || ntmp==0) ntmp=1; \
1683 if(ntmp>512) ntmp=512; \
1684 } while(0)
1685
1686 if(do_x) SCALE(xtmp,xscaling);
1687 if(do_y) SCALE(ytmp,yscaling);
1688
1689 #undef SCALE
1690
1691 if(theimage->w*xtmp<=32767 && theimage->h*ytmp<=32767)
1692 {
1693 undo_zoom();
1694 xscaling=xtmp;
1695 yscaling=ytmp;
1696 scaling_finish(oldxsc,oldysc);
1697 }
1698
1699 in_routine=0;
1700 }
1701
1702 void cb_scaling_add(void)
1703 {
1704 xy_scaling_add(1,1);
1705 }
1706
1707 void cb_xscaling_add(void)
1708 {
1709 xy_scaling_add(1,0);
1710 }
1711
1712 void cb_yscaling_add(void)
1713 {
1714 xy_scaling_add(0,1);
1715 }
1716
1717
1718 void xy_scaling_halve(int do_x,int do_y)
1719 {
1720 static int in_routine=0;
1721 int xtmp=xscaling,ytmp=yscaling,oldxsc=xscaling,oldysc=yscaling;
1722
1723 /* if there's no image, don't do anything */
1724 if (!theimage) return;
1725
1726 /* if recursed, don't bother */
1727 if(in_routine) return;
1728 in_routine=1;
1729
1730 #define SCALE(ntmp,nscaling) \
1731 do { \
1732 ntmp=nscaling; \
1733 if(ntmp==1) ntmp=-1; \
1734 if(ntmp>1) ntmp/=2; else ntmp*=2; \
1735 } while(0)
1736
1737 if(do_x) SCALE(xtmp,xscaling);
1738 if(do_y) SCALE(ytmp,yscaling);
1739
1740 #undef SCALE
1741
1742 if(xtmp<SCALING_DOWN_LIMIT || ytmp<SCALING_DOWN_LIMIT)
1743 {
1744 in_routine=0;
1745 return;
1746 }
1747
1748 undo_zoom();
1749 xscaling=xtmp;
1750 yscaling=ytmp;
1751 scaling_finish(oldxsc,oldysc);
1752
1753 in_routine=0;
1754 }
1755
1756 void cb_scaling_halve(void)
1757 {
1758 xy_scaling_halve(1,1);
1759 }
1760
1761 void cb_xscaling_halve(void)
1762 {
1763 xy_scaling_halve(1,0);
1764 }
1765
1766 void cb_yscaling_halve(void)
1767 {
1768 xy_scaling_halve(0,1);
1769 }
1770
1771
1772 void xy_scaling_sub(int do_x,int do_y)
1773 {
1774 static int in_routine=0;
1775 int xtmp=xscaling,ytmp=yscaling,oldxsc=xscaling,oldysc=yscaling;
1776
1777 /* if there's no image, don't do anything */
1778 if (!theimage) return;
1779
1780 /* if recursed, don't bother */
1781 if(in_routine) return;
1782 in_routine=1;
1783
1784 #define SCALE(ntmp,nscaling) \
1785 do { \
1786 ntmp=nscaling; \
1787 if(ntmp==1) ntmp=-1; \
1788 ntmp--; \
1789 } while(0)
1790
1791 if(do_x) SCALE(xtmp,xscaling);
1792 if(do_y) SCALE(ytmp,yscaling);
1793
1794 #undef SCALE
1795
1796 if(xtmp<SCALING_DOWN_LIMIT || ytmp<SCALING_DOWN_LIMIT)
1797 {
1798 in_routine=0;
1799 return;
1800 }
1801
1802 undo_zoom();
1803 xscaling=xtmp;
1804 yscaling=ytmp;
1805 scaling_finish(oldxsc,oldysc);
1806
1807 in_routine=0;
1808 }
1809
1810 void cb_scaling_sub(void)
1811 {
1812 xy_scaling_sub(1,1);
1813 }
1814
1815 void cb_xscaling_sub(void)
1816 {
1817 xy_scaling_sub(1,0);
1818 }
1819
1820 void cb_yscaling_sub(void)
1821 {
1822 xy_scaling_sub(0,1);
1823 }
1824
1825
1826 void cb_normal(void)
1827 {
1828 static int here=0;
1829 if(here) return;
1830 here=1;
1831
1832 undo_zoom();
1833 xscaling=yscaling=1;
1834 render_pixmap(1);
1835
1836 here=0;
1837 }
1838
1839
1840 /* Any callbacks which call render_pixmap() `must' avoid recursion,
1841 * but this is most obvious with toggles like zoom, so toggles really
1842 * *must* be especially careful about this.
1843 *
1844 * (The obvious approach for toggles is to (re)use listen_to_toggles.)
1845 */
1846 void toggle_zoom(gpointer cb_data,guint cb_action,GtkWidget *widget)
1847 {
1848 if(!listen_to_toggles || in_nextprev) return;
1849 listen_to_toggles=0;
1850
1851 zoom=!zoom;
1852 if(zoom)
1853 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sw_for_pic),
1854 (zoom_panorama&&zoom_panorama_sb)?GTK_POLICY_NEVER:GTK_POLICY_AUTOMATIC,
1855 (zoom_panorama&&!zoom_panorama_sb)?GTK_POLICY_NEVER:GTK_POLICY_AUTOMATIC);
1856 else
1857 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sw_for_pic),
1858 GTK_POLICY_AUTOMATIC,
1859 GTK_POLICY_AUTOMATIC);
1860 xscaling=yscaling=1;
1861 render_pixmap(1);
1862
1863 listen_to_toggles=1;
1864 }
1865
1866
1867 void toggle_zoom_reduce(gpointer cb_data,guint cb_action,GtkWidget *widget)
1868 {
1869 if(!listen_to_toggles || in_nextprev) return;
1870 listen_to_toggles=0;
1871
1872 zoom_reduce_only=!zoom_reduce_only;
1873 render_pixmap(1);
1874
1875 listen_to_toggles=1;
1876 }
1877
1878
1879 void toggle_zoom_panorama(gpointer cb_data,guint cb_action,GtkWidget *widget)
1880 {
1881 if(!listen_to_toggles || in_nextprev) return;
1882 listen_to_toggles=0;
1883
1884 zoom_panorama=!zoom_panorama;
1885 render_pixmap(1);
1886
1887 listen_to_toggles=1;
1888 }
1889
1890
1891 void toggle_interp(gpointer cb_data,guint cb_action,GtkWidget *widget)
1892 {
1893 if(!listen_to_toggles || in_nextprev) return;
1894 listen_to_toggles=0;
1895
1896 interp=!interp;
1897
1898 listen_to_toggles=1;
1899 }
1900
1901
1902 void toggle_mouse_x(gpointer cb_data,guint cb_action,GtkWidget *widget)
1903 {
1904 if(!listen_to_toggles) return;
1905
1906 mouse_scale_x=!mouse_scale_x;
1907 }
1908
1909
1910 void toggle_hicol_dither(gpointer cb_data,guint cb_action,GtkWidget *widget)
1911 {
1912 if(!listen_to_toggles || hicol_dither==-1) return;
1913 listen_to_toggles=0;
1914
1915 hicol_dither=!hicol_dither;
1916
1917 /* if hicol_dither!=-1, we must be in 15/16-bit, so set accordingly. */
1918 backend_set_hicol_dither(hicol_dither);
1919
1920 /* dirty the image so it'll definitely redraw */
1921 if(theimage)
1922 backend_image_changed(theimage);
1923 render_pixmap(0);
1924
1925 listen_to_toggles=1;
1926 }
1927
1928
1929 void toggle_revert(gpointer cb_data,guint cb_action,GtkWidget *widget)
1930 {
1931 if(!listen_to_toggles || in_nextprev) return;
1932
1933 revert=!revert;
1934 }
1935
1936
1937 void toggle_revert_orient(gpointer cb_data,guint cb_action,GtkWidget *widget)
1938 {
1939 if(!listen_to_toggles || in_nextprev) return;
1940
1941 revert_orient=!revert_orient;
1942 }
1943
1944
1945 void toggle_exif_orient(gpointer cb_data,guint cb_action,GtkWidget *widget)
1946 {
1947 if(!listen_to_toggles || in_nextprev) return;
1948
1949 use_exif_orient=!use_exif_orient;
1950 }
1951
1952
1953 void toggle_thin_rows(gpointer cb_data,guint cb_action,GtkWidget *widget)
1954 {
1955 struct clist_data_tag *datptr;
1956 GdkPixmap *pixmap;
1957 GdkBitmap *mask;
1958 int f;
1959
1960 if(!listen_to_toggles || in_nextprev) return;
1961
1962 listen_to_toggles=0;
1963
1964 gtk_clist_freeze(GTK_CLIST(clist));
1965
1966 thin_rows=!thin_rows;
1967 fix_row_heights();
1968 set_thumbnail_column_width();
1969
1970 /* switch pixmaps (normal for small, small for normal).
1971 * This is slightly tricky as there may be a thumbnail-read ongoing.
1972 * The current state is at least consistent though (it's not actually
1973 * multi-threaded or anything :-)), so just switch all which have
1974 * pixmaps.
1975 */
1976 for(f=0;f<numrows;f++)
1977 {
1978 if(!gtk_clist_get_pixmap(GTK_CLIST(clist),f,SELECTOR_TN_COL,&pixmap,&mask))
1979 continue;
1980
1981 datptr=gtk_clist_get_row_data(GTK_CLIST(clist),f);
1982 gtk_clist_set_pixmap(GTK_CLIST(clist),f,SELECTOR_TN_COL,
1983 thin_rows?datptr->pm_small:datptr->pm_norm,
1984 thin_rows?datptr->pm_small_mask:datptr->pm_norm_mask);
1985 }
1986
1987 gtk_clist_thaw(GTK_CLIST(clist));
1988
1989 /* this is required to avoid minor redraw-related position gliches
1990 * when moving the focus row (below).
1991 */
1992 do_gtk_stuff();
1993
1994 /* the selection is quite likely to have been `lost', so
1995 * always move, even if already visible (for consistency). What we
1996 * do is force focus row to selected one (if one is selected), then
1997 * move visible window to focus row.
1998 */
1999 if(current_selection!=-1)
2000 set_focus_row(current_selection);
2001 gtk_clist_moveto(GTK_CLIST(clist),GTK_CLIST(clist)->focus_row,0,0.5,0.);
2002
2003 listen_to_toggles=1;
2004 }
2005
2006
2007 void toggle_status(gpointer cb_data,guint cb_action,GtkWidget *widget)
2008 {
2009 if(!listen_to_toggles || in_nextprev) return;
2010
2011 have_statusbar=!have_statusbar;
2012 if(have_statusbar)
2013 gtk_widget_show(statusbar);
2014 else
2015 gtk_widget_hide(statusbar);
2016 }
2017
2018
2019 void toggle_tn_msgs(gpointer cb_data,guint cb_action,GtkWidget *widget)
2020 {
2021 if(!listen_to_toggles) return;
2022
2023 tn_msgs=!tn_msgs;
2024 }
2025
2026
2027 void toggle_auto_hide(gpointer cb_data,guint cb_action,GtkWidget *widget)
2028 {
2029 if(!listen_to_toggles || in_nextprev) return;
2030 listen_to_toggles=0;
2031
2032 auto_hide=!auto_hide;
2033 if(!auto_hide && hidden)
2034 {
2035 /* restore selector to most-recently-saved size (or default) */
2036 gtk_paned_set_position(GTK_PANED(pane),hide_saved_pos);
2037 hidden=0;
2038 pic_win_resized(NULL,NULL); /* XXX kludge for zoom mode */
2039 }
2040
2041 listen_to_toggles=1;
2042 }
2043
2044
2045 void cb_show_images(gpointer cb_data,guint cb_action,GtkWidget *widget)
2046 {
2047 if(!listen_to_toggles || in_nextprev) return;
2048 listen_to_toggles=0;
2049
2050 show_images_only = !show_images_only;
2051 reinit_dir(0,1);
2052
2053 listen_to_toggles=1;
2054 }
2055
2056
2057 void cb_flip(void)
2058 {
2059 if (!theimage) return;
2060 RECURSE_PROTECT_START;
2061 backend_flip_vert(theimage);
2062 orient_current_state=orient_state_flip[orient_current_state];
2063 render_pixmap(1);
2064 RECURSE_PROTECT_END;
2065 }
2066
2067
2068 void cb_mirror(void)
2069 {
2070 if (!theimage) return;
2071 RECURSE_PROTECT_START;
2072 backend_flip_horiz(theimage);
2073 orient_current_state=orient_state_mirror[orient_current_state];
2074 render_pixmap(1);
2075 RECURSE_PROTECT_END;
2076 }
2077
2078
2079 void cb_rot_cw(void)
2080 {
2081 if (!theimage) return;
2082 RECURSE_PROTECT_START;
2083 /* swap x and y scaling, since the effect if we don't do that
2084 * is of the image mysteriously changing. :-)
2085 */
2086 backend_rotate_cw(theimage);
2087 orient_current_state=orient_state_rot_cw[orient_current_state];
2088 swap_xyscaling();
2089 render_pixmap(1);
2090 RECURSE_PROTECT_END;
2091 }
2092
2093
2094 void cb_rot_acw(void)
2095 {
2096 if (!theimage) return;
2097 RECURSE_PROTECT_START;
2098 backend_rotate_acw(theimage);
2099 orient_current_state=orient_state_rot_acw[orient_current_state];
2100 swap_xyscaling();
2101 render_pixmap(1);
2102 RECURSE_PROTECT_END;
2103 }
2104
2105
2106 void cb_normal_orient(void)
2107 {
2108 if (!theimage) return;
2109 RECURSE_PROTECT_START;
2110 if(orient_current_state!=0)
2111 {
2112 orient_change_state(orient_current_state,0);
2113 orient_current_state=0;
2114 render_pixmap(1);
2115 }
2116 RECURSE_PROTECT_END;
2117 }
2118
2119
2120 int thumbnail_read_running(void)
2121 {
2122 return(tn_idle_tag!=-1);
2123 }
2124
2125
2126 void start_thumbnail_read(void)
2127 {
2128 static int entry;
2129
2130 if(thumbnail_read_running()) return; /* don't if it's already running */
2131
2132 if(!numrows) return; /* this is surely impossible, but WTF :-) */
2133
2134 /* we pass pointer to zeroed `entry', saving the difficulty
2135 * of dealing with when to zero this in the idle function itself.
2136 * Ditto with last adjustment, though this is a bit ugly. :-)
2137 */
2138 idle_xvpic_lastadjval=gtk_clist_get_vadjustment(GTK_CLIST(clist))->value;
2139 idle_xvpic_jumped=0;
2140 entry=0;
2141 tn_idle_tag=gtk_idle_add((GtkFunction)idle_xvpic_load,&entry);
2142
2143 /* the "" is a crappy way to disable it, but it's hairy otherwise */
2144 gtk_statusbar_push(GTK_STATUSBAR(statusbar),tn_id,
2145 tn_msgs?"Reading thumbnails...":"");
2146 }
2147
2148
2149 /* stop any currently-active idle func for thumbnail reading. */
2150 void stop_thumbnail_read(void)
2151 {
2152 if(thumbnail_read_running())
2153 {
2154 gtk_statusbar_pop(GTK_STATUSBAR(statusbar),tn_id); /* remove msg */
2155 gtk_idle_remove(tn_idle_tag);
2156 tn_idle_tag=-1;
2157 }
2158 }
2159
2160
2161 /* read *currently visible* thumbnails, blocking until all have been read.
2162 * Don't use this unless you know what you're doing, it can take a while...
2163 * if checkptr isn't NULL, it aborts if *checkptr becomes NULL.
2164 */
2165 void blocking_thumbnail_read_visible(GtkWidget **checkptr)
2166 {
2167 int entry;
2168 int row=-1,col=-1;
2169
2170 if(thumbnail_read_running()) return;
2171
2172 gtk_clist_get_selection_info(GTK_CLIST(clist),0,0,&row,&col);
2173 if(row==-1) return;
2174
2175 idle_xvpic_lastadjval=gtk_clist_get_vadjustment(GTK_CLIST(clist))->value;
2176 idle_xvpic_jumped=0;
2177 entry=row;
2178
2179 while(entry!=-1 && mainwin && (!checkptr || *checkptr) &&
2180 gtk_clist_row_is_visible(GTK_CLIST(clist),entry)!=GTK_VISIBILITY_NONE)
2181 {
2182 if(mainwin && (!checkptr || *checkptr))
2183 do_gtk_stuff(); /* make sure things get updated */
2184 idle_xvpic_load(&entry);
2185 }
2186 }
2187
2188
2189 /* do the actual resorting. Also used by rename.c after renaming a file. */
2190 void resort_finish(void)
2191 {
2192 int was_reading=0;
2193 struct clist_data_tag *datptr=NULL;
2194
2195 if(thumbnail_read_running())
2196 {
2197 stop_thumbnail_read();
2198 was_reading=1;
2199 }
2200
2201 /* set focus row to selection if it exists */
2202 if(current_selection!=-1)
2203 set_focus_row(current_selection);
2204
2205 /* now we do everything in terms of the focus row.
2206 * get row data pointer (which is unique) so we can look the row up after.
2207 */
2208 datptr=gtk_clist_get_row_data(GTK_CLIST(clist),GTK_CLIST(clist)->focus_row);
2209
2210 gtk_clist_sort(GTK_CLIST(clist));
2211
2212 /* look up data, and reselect it. */
2213 if(datptr)
2214 {
2215 int tmp=gtk_clist_find_row_from_data(GTK_CLIST(clist),datptr);
2216
2217 /* ..._find_row_from_data() returns -1 on error, great for this. :-) */
2218 if(current_selection!=-1)
2219 current_selection=tmp;
2220
2221 /* not so good for this though. */
2222 set_focus_row((tmp!=-1)?tmp:0);
2223
2224 /* Hmm. Ok, one thing we have to do which can't be done entirely
2225 * in terms of the focus row is to move the selection to the right place. :-)
2226 */
2227 if(current_selection!=-1)
2228 {
2229 /* block selection handler while selecting it, so we don't reload pic! */
2230 gtk_signal_handler_block(GTK_OBJECT(clist),cb_selection_id);
2231 gtk_clist_select_row(GTK_CLIST(clist),current_selection,0);
2232 gtk_signal_handler_unblock(GTK_OBJECT(clist),cb_selection_id);
2233 }
2234 }
2235
2236 /* now deal with visibility problems. This takes the same approach
2237 * as toggle_thin_rows() - if one selected move focus row to there
2238 * (did that before the sort), and either way force focus row to middle.
2239 */
2240 gtk_clist_moveto(GTK_CLIST(clist),GTK_CLIST(clist)->focus_row,0,0.5,0.);
2241
2242 /* a thumbnail-read may have been ongoing; if so, restart it. */
2243 if(was_reading)
2244 start_thumbnail_read();
2245 }
2246
2247
2248
2249 void cb_name_order(void)
2250 {
2251 filesel_sorttype=sort_name;
2252 resort_finish();
2253 }
2254
2255 void cb_ext_order(void)
2256 {
2257 filesel_sorttype=sort_ext;
2258 resort_finish();
2259 }
2260
2261 void cb_size_order(void)
2262 {
2263 filesel_sorttype=sort_size;
2264 resort_finish();
2265 }
2266
2267 void cb_time_order(void)
2268 {
2269 filesel_sorttype=sort_time;
2270 resort_finish();
2271 }
2272
2273 void cb_mtime_type(void)
2274 {
2275 sort_timestamp_type=0; resort_finish();
2276 }
2277
2278 void cb_ctime_type(void)
2279 {
2280 sort_timestamp_type=1; resort_finish();
2281 }
2282
2283 void cb_atime_type(void)
2284 {
2285 sort_timestamp_type=2; resort_finish();
2286 }
2287
2288
2289 void cb_next_image(void)
2290 {
2291 int row;
2292
2293 /* since the implicit cb_selection call below checks for GTK+ events,
2294 * we have to protect against being recursed unexpectedly to avoid
2295 * segfaults. :-)
2296 */
2297 if(in_nextprev) return;
2298
2299 if(current_selection==-1) return; /* skip it if no current selection */
2300 if(current_selection>=numrows-1) return; /* skip if no next image */
2301
2302 in_nextprev=1;
2303
2304 row=current_selection+1;
2305 make_visible_if_not(row);
2306
2307 /* this causes a cb_selection call, which does the rest,
2308 * including zeroing in_nextprev.
2309 */
2310 set_focus_row(row);
2311 gtk_clist_select_row(GTK_CLIST(clist),row,0); /* sets current_selection */
2312 in_nextprev=0;
2313 }
2314
2315
2316 void cb_prev_image(void)
2317 {
2318 struct clist_data_tag *datptr;
2319 int row;
2320
2321 if(in_nextprev) return;
2322
2323 if(current_selection==-1) return; /* skip it if no current selection */
2324
2325 /* checking for previous image is slightly more complicated.
2326 * If current_selection is zero there's none, of course;
2327 * however, there's also none if the previous one is a dir.
2328 */
2329 if(current_selection==0) return;
2330
2331 datptr=gtk_clist_get_row_data(GTK_CLIST(clist),current_selection-1);
2332 if(datptr->isdir) return;
2333
2334 in_nextprev=1;
2335
2336 row=current_selection-1;
2337 make_visible_if_not(row);
2338
2339 /* this causes a cb_selection call, which does the rest,
2340 * including zeroing in_nextprev.
2341 */
2342 set_focus_row(row);
2343 gtk_clist_select_row(GTK_CLIST(clist),row,0); /* sets current_selection */
2344 in_nextprev=0;
2345 }
2346
2347
2348 void cb_tag_then_next(void)
2349 {
2350 /* good idea to check this early */
2351 if(in_nextprev) return;
2352
2353 /* this filters out images left in viewer after dir change,
2354 * and the case of there being no image in the viewer to tag. :-)
2355 */
2356 if(current_selection==-1) return;
2357
2358 set_tagged_state(current_selection,1); /* tag it */
2359
2360 cb_next_image();
2361 }
2362
2363
2364 void cb_help_contents(void)
2365 {
2366 help_run("Top");
2367 }
2368
2369 void cb_help_selector(void)
2370 {
2371 help_run("The File Selector");
2372 }
2373
2374 void cb_help_viewer(void)
2375 {
2376 help_run("The Viewer");
2377 }
2378
2379 void cb_help_index(void)
2380 {
2381 help_run("Concept Index");
2382 }
2383
2384 void cb_help_about(void)
2385 {
2386 help_about();
2387 }
2388
2389
2390
2391 void ditch_line(FILE *in)
2392 {
2393 int c;
2394
2395 while((c=fgetc(in))!='\n' && c!=EOF);
2396 }
2397
2398
2399 /* for text-style PNM files, i.e. P[123].
2400 * we take an extremely generous outlook - anything other than a decimal
2401 * digit is considered whitespace.
2402 * and as per p[bgp]m(5), comments can start anywhere.
2403 */
2404 int read_next_number(FILE *in)
2405 {
2406 int c,num,in_num,gotnum;
2407
2408 num=0;
2409 in_num=gotnum=0;
2410
2411 do
2412 {
2413 if(feof(in)) return(0);
2414 if((c=fgetc(in))=='#')
2415 ditch_line(in);
2416 else
2417 if(isdigit(c))
2418 num=10*num+c-49+(in_num=1);
2419 else
2420 gotnum=in_num;
2421 }
2422 while(!gotnum);
2423
2424 return(num);
2425 }
2426
2427
2428 /* xv 3:3:2 thumbnail files - these are similar to pgm raw files,
2429 * but the context in which they are used is very different; as such
2430 * we have a separate routine for loading them.
2431 * they seem to have a max. size of 80x60.
2432 */
2433 int read_xvpic(char *filename,unsigned char *bmap,int *width,int *height)
2434 {
2435 FILE *in;
2436 char buf[128];
2437 int w,h,maxval;
2438 int count;
2439
2440 *width=0; *height=0;
2441
2442 if((in=fopen(filename,"rb"))==NULL)
2443 return(0);
2444
2445 fgets(buf,sizeof(buf),in);
2446 if(strcmp(buf,"P7 332\n")!=0) {
2447 fclose(in);
2448 return(0);
2449 }
2450
2451 /* we're not worried about any comments */
2452 w=read_next_number(in);
2453 h=read_next_number(in);
2454
2455 *width=w; *height=h;
2456
2457 if(w==0 || h==0 || w>80 || h>60) {
2458 fclose(in);
2459 return(0);
2460 }
2461
2462 /* for some reason, they have a maxval...!?
2463 * we complain if it's not 255.
2464 */
2465 if((maxval=read_next_number(in))!=255) {
2466 fclose(in);
2467 return(0);
2468 }
2469
2470 count=fread(bmap,1,w*h,in);
2471 if(count!=w*h) {
2472 fclose(in);
2473 return(0);
2474 }
2475
2476 fclose(in);
2477 return(1);
2478 }
2479
2480
2481 /* get closest-colour palette
2482 * XXX will look *awful* on 8-bit, as I'm not dithering!
2483 * (actually it's not *that* bad, but it's still fairly crap)
2484 */
2485 void find_xvpic_cols(void)
2486 {
2487 int r,g,b;
2488 int n;
2489
2490 for(n=0,r=0;r<8;r++) {
2491 for(g=0;g<8;g++) {/* colours are 3:3:2 */
2492 for(b=0;b<4;b++,n++) {
2493 xvpic_pal[n][0]=r*0xff/7;
2494 xvpic_pal[n][1]=g*0xff/7;
2495 xvpic_pal[n][2]=b*0xff/3;
2496 }
2497 }
2498 }
2499 }
2500
2501
2502 GdkPixmap *xvpic2pixmap(unsigned char *xvpic,int w,int h,GdkPixmap **smallp)
2503 {
2504 GdkPixmap *pixmap,*small_pixmap;
2505 guint8 *buffer;
2506 unsigned char *ptr=xvpic;
2507 int x,y;
2508 int small_w,small_h;
2509
2510 if(w==0 || h==0) return(NULL);
2511
2512
2513 if (NULL == (pixmap=gdk_pixmap_new(mainwin->window,w,h, -1))) {
2514 return(NULL);
2515 }
2516
2517 small_w=w/ROW_HEIGHT_DIV;
2518 small_h=h/ROW_HEIGHT_DIV;
2519 if(small_w==0) small_w=1;
2520 if(small_h==0) small_h=1;
2521
2522 if((small_pixmap=gdk_pixmap_new(mainwin->window,small_w,small_h,-1))==NULL)
2523 {
2524 g_object_unref(pixmap);
2525 return(NULL);
2526 }
2527
2528 buffer = malloc (w * h * sizeof (guint8) * 3);
2529
2530 if (NULL == buffer) {
2531 /* malloc failed */
2532 g_object_unref(pixmap);
2533 g_object_unref(small_pixmap);
2534 return NULL;
2535 }
2536
2537
2538 for(y=0;y<h;y++) {
2539 for(x=0;x<w;x++) {
2540 buffer[3*(y*w + x)+0] = xvpic_pal[*ptr][0]; /* red */
2541 buffer[3*(y*w + x)+1] = xvpic_pal[*ptr][1]; /* green */
2542 buffer[3*(y*w + x)+2] = xvpic_pal[*ptr][2]; /* blue */
2543 ptr++;
2544 }
2545 }
2546
2547 gdk_draw_rgb_image(pixmap,clist->style->white_gc,0,0,w,h,
2548 GDK_RGB_DITHER_NORMAL,
2549 (guchar*)buffer, w * 3);
2550 gdk_flush();
2551
2552 /* reuse image to draw scaled-down version for thin rows */
2553
2554
2555 for(y=0;y<small_h;y++) {
2556 for(x=0;x<small_w;x++) {
2557 buffer[3*(y*w + x)+0] = xvpic_pal[xvpic[(y*w+x)*ROW_HEIGHT_DIV]][0];
2558 buffer[3*(y*w + x)+1] = xvpic_pal[xvpic[(y*w+x)*ROW_HEIGHT_DIV]][1];
2559 buffer[3*(y*w + x)+2] = xvpic_pal[xvpic[(y*w+x)*ROW_HEIGHT_DIV]][2];
2560 }
2561 }
2562
2563 gdk_draw_rgb_image(small_pixmap,clist->style->white_gc,0,0,small_w, small_h,
2564 GDK_RGB_DITHER_NORMAL,
2565 (guchar*)buffer, small_w * 3);
2566
2567 *smallp=small_pixmap;
2568
2569 if (NULL != buffer) {
2570 free (buffer);
2571 }
2572
2573 return(pixmap);
2574 }
2575
2576
2577 gint idle_xvpic_load(int *entryp)
2578 {
2579 static char buf[1024];
2580 struct clist_data_tag *datptr;
2581 char *ptr;
2582 int f,w,h;
2583 GdkPixmap *pixmap,*small_pixmap;
2584 GdkBitmap *mask;
2585 static unsigned char xvpic_data[80*60]; /* max thumbnail size */
2586 float adjval;
2587 static int prev_scanpos=0; /* if jumped, saved pos in top-to-bot scan */
2588
2589 idle_xvpic_called=1;
2590
2591 /* don't do it if it would be a bad time */
2592 if(idle_xvpic_blocked)
2593 return 0;
2594
2595 /* freeze/thaw actually *cause* flickering for this, rather than
2596 * preventing it (!), so I've not used those here.
2597 */
2598
2599 adjval=gtk_clist_get_vadjustment(GTK_CLIST(clist))->value;
2600 if(adjval!=idle_xvpic_lastadjval)
2601 {
2602 int row=-1,col=-1;
2603
2604 idle_xvpic_lastadjval=adjval;
2605
2606 /* scrollbar position has changed, jump to first visible row.
2607 * (We'll make another pass to clean things up later.)
2608 * This can greatly reduce apparent thumbnail load time for
2609 * big dirs, even though in practice if you move about a lot
2610 * it can actually increase it somewhat. :-)
2611 */
2612 gtk_clist_get_selection_info(GTK_CLIST(clist),0,0,&row,&col);
2613 if(row!=-1)
2614 {
2615 idle_xvpic_jumped++;
2616 if(idle_xvpic_jumped==1)
2617 {
2618 /* save next one to check after, but only for first
2619 * jump, not any `recursive' ones.
2620 */
2621 prev_scanpos=*entryp;
2622 }
2623 *entryp=row;
2624 }
2625 }
2626
2627 for(f=0;f<IDLE_XVPIC_NUM_PER_CALL;f++)
2628 {
2629 /* if there's already a pixmap there, skip it. */
2630 if(!gtk_clist_get_pixmap(GTK_CLIST(clist),*entryp,
2631 SELECTOR_TN_COL,&pixmap,&mask))
2632 {
2633 /* construct filename for file's (possible) thumbnail */
2634 gtk_clist_get_text(GTK_CLIST(clist),*entryp,SELECTOR_NAME_COL,&ptr);
2635 strcpy(buf,".xvpics/");
2636 strncat(buf,ptr,sizeof(buf)-8-2); /* above string is 8 chars long */
2637
2638 datptr=gtk_clist_get_row_data(GTK_CLIST(clist),*entryp);
2639
2640 /* if it's a dir, use ref to dir_icon pixmap. */
2641 if(datptr->isdir)
2642 {
2643 datptr->pm_norm=g_object_ref(dir_icon);
2644 datptr->pm_small=g_object_ref(dir_icon_small);
2645 datptr->pm_norm_mask=g_object_ref(dir_icon_mask);
2646 datptr->pm_small_mask=g_object_ref(dir_icon_small_mask);
2647 gtk_clist_set_pixmap(GTK_CLIST(clist),*entryp,SELECTOR_TN_COL,
2648 thin_rows?datptr->pm_small:datptr->pm_norm,
2649 (thin_rows?datptr->pm_small_mask:
2650 datptr->pm_norm_mask));
2651 }
2652 else
2653 {
2654 /* it's a file, try to load a thumbnail for it */
2655 if(read_xvpic(buf,xvpic_data,&w,&h) &&
2656 (pixmap=xvpic2pixmap(xvpic_data,w,h,&small_pixmap))!=NULL)
2657 {
2658 datptr->pm_norm=pixmap;
2659 datptr->pm_small=small_pixmap;
2660 datptr->pm_norm_mask=datptr->pm_small_mask=NULL;
2661 gtk_clist_set_pixmap(GTK_CLIST(clist),*entryp,SELECTOR_TN_COL,
2662 thin_rows?datptr->pm_small:datptr->pm_norm,
2663 NULL);
2664 }
2665 else
2666 {
2667 /* no thumbnail then, use ref to file_icon pixmap. */
2668 datptr->pm_norm=g_object_ref(file_icon);
2669 datptr->pm_small=g_object_ref(file_icon_small);
2670 datptr->pm_norm_mask=g_object_ref(file_icon_mask);
2671 datptr->pm_small_mask=g_object_ref(file_icon_small_mask);
2672 gtk_clist_set_pixmap(GTK_CLIST(clist),*entryp,SELECTOR_TN_COL,
2673 thin_rows?datptr->pm_small:datptr->pm_norm,
2674 (thin_rows?datptr->pm_small_mask:
2675 datptr->pm_norm_mask));
2676 }
2677 }
2678 }
2679
2680 (*entryp)++;
2681
2682 /* if we jumped, stop on first invisible row or end of list */
2683 if(idle_xvpic_jumped &&
2684 (*entryp>=numrows ||
2685 gtk_clist_row_is_visible(GTK_CLIST(clist),*entryp)==GTK_VISIBILITY_NONE))
2686 {
2687 /* we pop all jumps, as it were; we only did ..._jumped++ above
2688 * to ensure we save a single (correct! :-)) prev_scanpos.
2689 */
2690 idle_xvpic_jumped=0;
2691 *entryp=prev_scanpos;
2692 }
2693
2694 if(*entryp>=numrows)
2695 {
2696 /* can't have jump to return from, so just remove ourselves. */
2697 stop_thumbnail_read();
2698 *entryp=-1;
2699 }
2700 }
2701 return 1;
2702 }
2703
2704
2705 /* remove everything from clist, freeing pixmaps beforehand */
2706 void blast_clist(void)
2707 {
2708 int f;
2709 struct clist_data_tag *datptr;
2710
2711 if(numrows==0) return;
2712
2713 gtk_clist_freeze(GTK_CLIST(clist));
2714
2715 /* stop any `currently'-running idle func to read thumbnails
2716 * (doing this now is probably overly paranoid, but it can't hurt)
2717 */
2718 stop_thumbnail_read();
2719
2720 for(f=0;f<numrows;f++)
2721 {
2722 /* seems to free the pixmaps itself, but doesn't free the data AFAIK
2723 * (reasonable enough - the data could point to something static, etc.)
2724 * However, only one of our pixmaps (normal/small) is showing currently;
2725 * remove the other before removing the data.
2726 */
2727 datptr=gtk_clist_get_row_data(GTK_CLIST(clist),f);
2728 /* be careful - we may be halfway through thumbnail-read... */
2729 if(datptr)
2730 {
2731 if(datptr->pm_norm) g_object_unref(datptr->pm_norm);
2732 if(datptr->pm_norm_mask) g_object_unref(datptr->pm_norm_mask);
2733 if(datptr->pm_small) g_object_unref(datptr->pm_small);
2734 if(datptr->pm_small_mask) g_object_unref(datptr->pm_small_mask);
2735 free(datptr);
2736 }
2737 }
2738
2739 /* now remove all rows at once */
2740 gtk_clist_clear(GTK_CLIST(clist));
2741 numrows=0;
2742
2743 gtk_clist_thaw(GTK_CLIST(clist));
2744 }
2745
2746
2747 gint sort_cmp(GtkCList *clist,gconstpointer ptr1,gconstpointer ptr2)
2748 {
2749 GtkCListRow *row1=(GtkCListRow *)ptr1;
2750 GtkCListRow *row2=(GtkCListRow *)ptr2;
2751 char *txt1,*txt2;
2752 struct clist_data_tag *dat1,*dat2;
2753
2754 txt1=GTK_CELL_TEXT(row1->cell[SELECTOR_NAME_COL])->text;
2755 txt2=GTK_CELL_TEXT(row2->cell[SELECTOR_NAME_COL])->text;
2756 dat1=row1->data;
2757 dat2=row2->data;
2758
2759 /* directories always come first.
2760 * so, if comparing two files, use a normal comparison;
2761 * otherwise if it's two dirs, use a strcmp on the names;
2762 * otherwise it's one file and one dir, and the dir is always `less'.
2763 */
2764 if(dat1->isdir && dat2->isdir)
2765 return(strcmp(txt1,txt2)); /* both directories, use strcmp. */
2766
2767 if(!dat1->isdir && !dat2->isdir)
2768 {
2769 /* both files, use normal comparison. */
2770 int ret=0;
2771
2772 switch(filesel_sorttype)
2773 {
2774 case sort_name:
2775 ret=strcmp(txt1,txt2);
2776 break;
2777
2778 case sort_ext:
2779 ret=strcmp(txt1+dat1->extofs,txt2+dat2->extofs);
2780 break;
2781
2782 case sort_size:
2783 if(dat1->size<dat2->size)
2784 ret=-1;
2785 else
2786 if(dat1->size>dat2->size)
2787 ret=1;
2788 break;
2789
2790 case sort_time:
2791 {
2792 time_t t1,t2;
2793
2794 switch(sort_timestamp_type)
2795 {
2796 default: t1=dat1->mtime; t2=dat2->mtime; break;
2797 case 1: t1=dat1->ctime; t2=dat2->ctime; break;
2798 case 2: t1=dat1->atime; t2=dat2->atime; break;
2799 }
2800
2801 if(t1<t2)
2802 ret=-1;
2803 else
2804 if(t1>t2)
2805 ret=1;
2806 break;
2807 }
2808 }
2809
2810 /* for all equal matches on primary key, use name as secondary */
2811 if(ret==0)
2812 ret=strcmp(txt1,txt2);
2813
2814 return(ret);
2815 } /* end of if */
2816
2817 /* otherwise, one or both are dirs. */
2818
2819 if(dat1->isdir) return(-1); /* first one is dir */
2820 return(1); /* else second one is dir */
2821 }
2822
2823
2824 int clist_add_new_row(char *filename,struct stat *sbuf)
2825 {
2826 struct clist_data_tag *datptr;
2827 gchar *textarr[2];
2828 char *ptr;
2829 int row;
2830 static char* extensions[] ={".GIF", ".JPEG", ".JPG", ".PNG", ".PBM", ".PGM", ".PPM",
2831 ".PNM", ".BMP", ".TGA", ".PCX", ".MRF", ".PRF", ".XBM",
2832 ".XPM", ".TIFF", ".TIF", ".TIM", ".XWD"};
2833
2834 if (!S_ISDIR(sbuf->st_mode) && show_images_only)
2835 {
2836 int isImage = 0;
2837 int i;
2838 gchar* nameUpper = g_ascii_strup(filename, -1);
2839 for(i = 0; i < 19; ++i)
2840 {
2841 if(g_str_has_suffix(nameUpper, extensions[i]))
2842 {
2843 isImage = 1;
2844 break;
2845 }
2846 }
2847 g_free(nameUpper);
2848 if (!isImage)
2849 return(0);
2850 }
2851
2852 /* allocate data-pointer struct for row */
2853 if((datptr=malloc(sizeof(struct clist_data_tag)))==NULL)
2854 return(0);
2855
2856 /* can't use a pointer to the extension (GTK+ makes its own copy
2857 * of the filename), so has to be an offset.
2858 */
2859 if((ptr=strrchr(filename,'.'))==NULL)
2860 /* use the NUL, Luke */
2861 datptr->extofs=strlen(filename);
2862 else
2863 datptr->extofs=ptr-filename;
2864
2865 datptr->isdir=S_ISDIR(sbuf->st_mode);
2866 datptr->size=sbuf->st_size;
2867 datptr->mtime=sbuf->st_mtime;
2868 datptr->ctime=sbuf->st_ctime;
2869 datptr->atime=sbuf->st_atime;
2870 datptr->tagged=0;
2871 datptr->pm_norm=datptr->pm_small=NULL; /* no pixmaps initially */
2872 datptr->pm_norm_mask=datptr->pm_small_mask=NULL;
2873
2874 textarr[SELECTOR_TN_COL]="";
2875 textarr[SELECTOR_NAME_COL]=filename;
2876 row=gtk_clist_append(GTK_CLIST(clist),textarr);
2877 gtk_clist_set_row_data(GTK_CLIST(clist),row,datptr);
2878
2879 /* we *could* put pixmaps in place for directories right now,
2880 * rather than waiting for idle_xvpic_load() to do it. However,
2881 * this a) seems to end up being a bit flickery despite the clist
2882 * being `frozen', and b) looks rather odd. :-)
2883 */
2884
2885 return(1);
2886 }
2887
2888
2889 /* read filenames from current dir, and (eventually) load thumbnails.
2890 *
2891 * Note that this actually just sets up the filenames, and enables
2892 * an idle function which loads the xvpics (removing any already-running
2893 * one to do this, if needed).
2894 */
2895 void create_clist_from_dir(void)
2896 {
2897 DIR *dirfile;
2898 struct dirent *dent;
2899 struct stat sbuf;
2900 static char cdir[1024];
2901
2902 if((dirfile=opendir("."))==NULL)
2903 {
2904 /* we get here if we can't read the dir.
2905 * xzgv tests we have permission to access a file or dir before
2906 * selecting it, so this can only happen if it was started on the dir
2907 * from the cmdline, or if the directory has changed somehow since we
2908 * last read it.
2909 * the first reaction is to try $HOME instead.
2910 * if *that* doesn't work, we cough and die, not unreasonably. :-)
2911 */
2912 /* be sure to mention what we're doing first... :-) */
2913 if(getenv("HOME")==NULL)
2914 goto badhome;
2915 chdir(getenv("HOME"));
2916 if((dirfile=opendir("."))==NULL)
2917 {
2918 badhome:
2919 fprintf(stderr,
2920 "xzgv: $HOME is unreadable or not set. "
2921 "This is a Bad Thing. TTFN...\n");
2922 exit(1);
2923 }
2924
2925 error_dialog("xzgv warning",
2926 "Directory unreadable - jumped to home dir instead");
2927
2928 /* if moving to $HOME worked, may need to change dir in title `by hand'... */
2929 set_title(1);
2930 }
2931
2932 current_selection=-1;
2933
2934 getcwd(cdir,sizeof(cdir)-1);
2935
2936 /* originally had a `reading directory' msg here, but it's so fast
2937 * even for big dirs that it hardly seems worth it.
2938 */
2939
2940 /* remove any currently-running idle func */
2941 stop_thumbnail_read();
2942
2943 gtk_clist_freeze(GTK_CLIST(clist));
2944
2945 numrows=0;
2946 while((dent=readdir(dirfile))!=NULL)
2947 {
2948 if(dent->d_name[0]=='.' && dent->d_name[1]!='.')
2949 continue; /* skip (most) 'dot' files */
2950
2951 /* no `.' ever, and no `..' if at root. */
2952 if(strcmp(dent->d_name,".")==0 ||
2953 (strcmp(cdir,"/")==0 && strcmp(dent->d_name,"..")==0))
2954 continue;
2955
2956 /* see if it's a dir */
2957 if((stat(dent->d_name,&sbuf))==-1)
2958 {
2959 sbuf.st_mode=0;
2960 sbuf.st_size=0;
2961 sbuf.st_mtime=0;
2962 sbuf.st_ctime=0;
2963 sbuf.st_atime=0;
2964 }
2965
2966 if(clist_add_new_row(dent->d_name,&sbuf))
2967 numrows++;
2968 }
2969
2970 closedir(dirfile);
2971
2972 if(numrows)
2973 {
2974 /* sort the list (using sort_cmp) */
2975 gtk_clist_sort(GTK_CLIST(clist));
2976
2977 /* unselect the first row to give us a sane initial pos for
2978 * keyboard movement. (Doesn't seem to be necessary after sorting,
2979 * but can't hurt.)
2980 */
2981 gtk_clist_unselect_row(GTK_CLIST(clist),0,0);
2982
2983 /* setup idle function to load thumbnails. */
2984 start_thumbnail_read();
2985 }
2986
2987 gtk_clist_thaw(GTK_CLIST(clist));
2988 }
2989
2990
2991 void set_title(int include_dir)
2992 {
2993 static char buf[1024];
2994
2995 strcpy(buf,"xzgv");
2996 if(include_dir)
2997 {
2998 strcat(buf,": ");
2999 getcwd(buf+strlen(buf),sizeof(buf)-strlen(buf)-2);
3000 }
3001
3002 gtk_window_set_title(GTK_WINDOW(mainwin),buf);
3003 }
3004
3005
3006 /* add new pastpos[0], shifting down all the rest of the entries. */
3007 void new_pastpos(int row)
3008 {
3009 struct stat sbuf;
3010 int f;
3011
3012 for(f=MAX_PASTPOS-1;f>0;f--)
3013 {
3014 pastpos[f].dev =pastpos[f-1].dev;
3015 pastpos[f].inode=pastpos[f-1].inode;
3016 pastpos[f].row =pastpos[f-1].row;
3017 }
3018
3019 if(stat(".",&sbuf)==-1) return;
3020
3021 pastpos[0].dev =sbuf.st_dev;
3022 pastpos[0].inode=sbuf.st_ino;
3023 pastpos[0].row =row;
3024 }
3025
3026
3027 /* return row from pastpos[] entry matching current directory,
3028 * or if none match return 0.
3029 */
3030 int get_pastpos(void)
3031 {
3032 struct stat sbuf;
3033 int f;
3034
3035 if(stat(".",&sbuf)==-1) return(0);
3036
3037 for(f=0;f<MAX_PASTPOS;f++)
3038 if(pastpos[f].inode==sbuf.st_ino && pastpos[f].dev==sbuf.st_dev)
3039 return(pastpos[f].row);
3040
3041 return(0);
3042 }
3043
3044
3045 /* gives a simple modal dialog box with a label containing an error message.
3046 * It returns right after creating it, but since it's modal, it shouldn't
3047 * need any further consideration.
3048 */
3049 void error_dialog(char *title,char *msg)
3050 {
3051 GtkWidget *error_win;
3052 GtkWidget *vbox,*label,*action_tbl,*button;
3053
3054 error_win=gtk_dialog_new();
3055
3056 /* make a new vbox for the top part so we can get spacing more sane */
3057 vbox=gtk_vbox_new(FALSE,10);
3058 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(error_win)->vbox),
3059 vbox,TRUE,TRUE,0);
3060 gtk_widget_show(vbox);
3061
3062 gtk_container_set_border_width(GTK_CONTAINER(vbox),5);
3063 gtk_container_set_border_width(
3064 GTK_CONTAINER(GTK_DIALOG(error_win)->action_area),2);
3065
3066 gtk_window_set_title(GTK_WINDOW(error_win),title);
3067 gtk_window_set_policy(GTK_WINDOW(error_win),FALSE,TRUE,TRUE);
3068 gtk_window_set_position(GTK_WINDOW(error_win),GTK_WIN_POS_CENTER);
3069 gtk_window_set_modal(GTK_WINDOW(error_win),TRUE);
3070
3071 label=gtk_label_new(msg);
3072 gtk_box_pack_start(GTK_BOX(vbox),label,TRUE,TRUE,2);
3073 gtk_widget_show(label);
3074
3075 /* add ok button */
3076 action_tbl=gtk_table_new(1,3,TRUE);
3077 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(error_win)->action_area),
3078 action_tbl,TRUE,TRUE,0);
3079 gtk_widget_show(action_tbl);
3080
3081 button=gtk_button_new_with_label("Ok");
3082 gtk_table_attach_defaults(GTK_TABLE(action_tbl),button, 1,2, 0,1);
3083 gtk_signal_connect_object(GTK_OBJECT(button),"clicked",
3084 GTK_SIGNAL_FUNC(gtk_widget_destroy),
3085 GTK_OBJECT(error_win));
3086 gtk_widget_grab_focus(button);
3087 gtk_widget_show(button);
3088
3089 /* also allow escs (even from main window!) */
3090 gtk_widget_add_accelerator(button,"clicked",mainwin_accel_group,
3091 GDK_Escape,0,0);
3092
3093
3094 gtk_widget_show(error_win);
3095 }
3096
3097
3098 /* close file (clear viewer). */
3099 void cb_file_close(void)
3100 {
3101 gtk_clist_unselect_all(GTK_CLIST(clist));
3102 current_selection=-1;
3103 cb_back_to_clist(); /* enable selector */
3104
3105 if(theimage)
3106 {
3107 backend_image_destroy(theimage);
3108 theimage=NULL;
3109 }
3110
3111 /* ignore revert/revert_orient for this */
3112 xscaling=yscaling=1;
3113 orient_current_state=0;
3114 }
3115
3116
3117 void cb_delete_file_confirmed(void)
3118 {
3119 static char *prefix=".xvpics/";
3120 char *ptr,*tn;
3121 int row;
3122 int was_reading=0;
3123
3124 row=GTK_CLIST(clist)->focus_row;
3125 gtk_clist_get_text(GTK_CLIST(clist),row,SELECTOR_NAME_COL,&ptr);
3126
3127 /* delete the file */
3128 if(remove(ptr)==-1)
3129 {
3130 error_dialog("xzgv error","Unable to delete file!");
3131 return;
3132 }
3133
3134 cb_back_to_clist();
3135
3136 /* construct thumbnail filename early, as we're about to delete
3137 * the row containing the filename itself.
3138 */
3139 tn=malloc(strlen(prefix)+strlen(ptr)+1);
3140 if(tn)
3141 strcpy(tn,prefix),strcat(tn,ptr);
3142
3143 /* remove the row in the clist. We need to stop/restart thumbnail read
3144 * if it's running, as unexpectedly losing a row midway through could
3145 * cause problems.
3146 */
3147 if(thumbnail_read_running())
3148 {
3149 stop_thumbnail_read();
3150 was_reading=1;
3151 }
3152
3153 gtk_clist_remove(GTK_CLIST(clist),row);
3154 numrows--;
3155
3156 if(was_reading)
3157 start_thumbnail_read();
3158
3159 /* the current row could have been the selected one; correct our notion
3160 * of the selected file if so.
3161 * (XXX could also automatically `close' the file, but that could be
3162 * somewhat disturbing visually...?)
3163 */
3164 if(current_selection==row)
3165 current_selection=-1;
3166
3167 /* only now do we quit if we couldn't allocate mem for tn */
3168 if(!tn) return;
3169
3170 remove(tn); /* don't care if this fails */
3171 rmdir(".xvpics"); /* same here */
3172
3173 free(tn);
3174 }
3175
3176
3177 void cb_delete_file(void)
3178 {
3179 static char *prefix="Really delete `",*suffix="'?";
3180 struct clist_data_tag *datptr;
3181 char *ptr,*msg;
3182 int row;
3183
3184 row=GTK_CLIST(clist)->focus_row;
3185 if(row<0 || row>=numrows) return;
3186
3187 gtk_clist_get_text(GTK_CLIST(clist),row,SELECTOR_NAME_COL,&ptr);
3188 if(!ptr) return;
3189
3190 datptr=gtk_clist_get_row_data(GTK_CLIST(clist),row);
3191 if(!datptr || datptr->isdir) return;
3192
3193 msg=malloc(strlen(ptr)+strlen(prefix)+strlen(suffix)+1);
3194 if(!msg) return;
3195
3196 strcpy(msg,prefix);
3197 strcat(msg,ptr);
3198 strcat(msg,suffix);
3199
3200 /* ok, check if they're sure. If so, the above callback routine
3201 * will be called.
3202 */
3203 if(delete_single_prompt)
3204 confirmation_dialog("Delete File",msg,cb_delete_file_confirmed);
3205 else
3206 cb_delete_file_confirmed();
3207
3208 free(msg);
3209 }
3210
3211
3212 void reinit_dir(int do_pastpos,int try_to_save_cursor_pos)
3213 {
3214 int row;
3215 char *ptr,*oldname=NULL;
3216
3217 if(do_pastpos && try_to_save_cursor_pos)
3218 fprintf(stderr,"xzgv: both args to reinit_dir() set, bug alert :-)\n"),
3219 try_to_save_cursor_pos=0;
3220
3221 if(try_to_save_cursor_pos)
3222 {
3223 gtk_clist_get_text(GTK_CLIST(clist),GTK_CLIST(clist)->focus_row,
3224 SELECTOR_NAME_COL,&ptr);
3225 if(!ptr || (oldname=malloc(strlen(ptr)+1))==NULL)
3226 try_to_save_cursor_pos=0;
3227 else
3228 strcpy(oldname,ptr);
3229 }
3230
3231 blast_clist();
3232 create_clist_from_dir();
3233 set_title(1);
3234
3235 if(try_to_save_cursor_pos)
3236 {
3237 int f;
3238
3239 for(f=0;f<numrows;f++)
3240 {
3241 gtk_clist_get_text(GTK_CLIST(clist),f,SELECTOR_NAME_COL,&ptr);
3242 if(*ptr==*oldname && strcmp(ptr,oldname)==0)
3243 {
3244 /* focus and make sure it's visible */
3245 set_focus_row(f);
3246 make_visible_if_not(f);
3247 break;
3248 }
3249 }
3250
3251 free(oldname);
3252 }
3253
3254 if(do_pastpos)
3255 {
3256 row=get_pastpos();
3257
3258 if(row<numrows)
3259 {
3260 /* don't select old row, that would be annoying. Just focus it, and
3261 * put it in middle of win.
3262 */
3263 set_focus_row(row);
3264 gtk_clist_moveto(GTK_CLIST(clist),row,0,0.5,0.);
3265 }
3266 }
3267 }
3268
3269
3270 void cb_reread_dir(void)
3271 {
3272 reinit_dir(0,1); /* reread, don't do pastpos, save cursor pos */
3273 }
3274
3275
3276 void cb_copy_files(void)
3277 {
3278 cb_back_to_clist();
3279 cb_copymove_file_or_tagged_files(0);
3280 }
3281
3282
3283 void cb_move_files(void)
3284 {
3285 cb_back_to_clist();
3286 cb_copymove_file_or_tagged_files(1);
3287 }
3288
3289
3290 /* block keyboard/mouse input to selector */
3291 void selector_block(void)
3292 {
3293 /* can't do this with gtk_signal_handler_block, as that doesn't block
3294 * the native clist handlers. Need to still have the handlers, but
3295 * have them actively ignore the events.
3296 */
3297 ignore_selector_input=1;
3298 }
3299
3300 /* and unblock it */
3301 void selector_unblock(void)
3302 {
3303 ignore_selector_input=0;
3304 }
3305
3306
3307 void cb_selection(GtkWidget *clist,gint row,gint column,
3308 GdkEventButton *event,GtkScrolledWindow *sw)
3309 {
3310 char *ptr;
3311 xzgv_image *oldimage=theimage;
3312 struct clist_data_tag *datptr;
3313 int orient_lastpicexit_state=0;
3314 int old_selection=current_selection;
3315 FILE *test;
3316 static int in_routine=0;
3317
3318 /* guard against recursion (from GTK+ updates) */
3319 if(in_routine) return;
3320
3321 in_routine=1;
3322
3323 /* block mouse click/release and keys on selector while loading. */
3324 selector_block();
3325
3326 current_selection=row;
3327
3328 gtk_clist_get_text(GTK_CLIST(clist),row,SELECTOR_NAME_COL,&ptr);
3329
3330 /* don't think this can happen, but what the heck */
3331 if(!ptr)
3332 {
3333 selector_unblock();
3334 in_nextprev=in_routine=0;
3335 return;
3336 }
3337
3338 /* this definitely can't be NULL; always allocated if the row exists */
3339 datptr=gtk_clist_get_row_data(GTK_CLIST(clist),row);
3340
3341 if(!datptr) /* but it can't hurt to check :-) */
3342 {
3343 selector_unblock();
3344 in_nextprev=in_routine=0;
3345 return;
3346 }
3347
3348 /* see if the file (or dir) exists and we have permission to read it.
3349 * By, um, trying to open it. :-) (I was going to use access(2) to do
3350 * a better check than this, but apparently that's a bad idea?)
3351 */
3352 if((test=fopen(ptr,"rb"))!=NULL)
3353 fclose(test);
3354 else
3355 {
3356 /* nope; say so */
3357 error_dialog("xzgv error","Permission denied or file not found");
3358
3359 /* restore old selection state */
3360 current_selection=old_selection;
3361 if(current_selection==-1)
3362 {
3363 /* unselect, then */
3364 gtk_clist_unselect_all(GTK_CLIST(clist));
3365 }
3366 else /* a previous file was selected, reselect it (but don't reload) */
3367 {
3368 /* block selection handler while selecting it, so we don't reload pic! */
3369 gtk_signal_handler_block(GTK_OBJECT(clist),cb_selection_id);
3370 gtk_clist_select_row(GTK_CLIST(clist),current_selection,0);
3371 gtk_signal_handler_unblock(GTK_OBJECT(clist),cb_selection_id);
3372 }
3373
3374 selector_unblock();
3375 in_nextprev=in_routine=0;
3376 return;
3377 }
3378
3379 if(datptr->isdir)
3380 {
3381 /* if it's a dir, chdir to it and read files there instead. */
3382 cb_back_to_clist(); /* in case of mouse click, to show pastpos action */
3383 new_pastpos(row);
3384 chdir(ptr);
3385 reinit_dir(1,0); /* reinit and do pastpos */
3386
3387 selector_unblock();
3388 in_nextprev=in_routine=0;
3389 return;
3390 }
3391
3392 gtk_statusbar_push(GTK_STATUSBAR(statusbar),sel_id,"Reading file...");
3393 /* let GTK+ show it */
3394 do_gtk_stuff();
3395
3396 if((theimage=load_image(ptr,0,NULL,NULL))==NULL)
3397 {
3398 gtk_statusbar_pop(GTK_STATUSBAR(statusbar),sel_id);
3399
3400 theimage=oldimage; /* keep hold of the old one */
3401
3402 error_dialog("xzgv error","Couldn't load image!");
3403
3404 /* also enable the selector; the assumption is that this is nicer
3405 * than leaving them with a blank window (if they ran it on pics
3406 * from the command-line). :-) There doesn't seem any point
3407 * keeping focus on the image, anyway, especially as (in the
3408 * case of the first command-line pic failing to load) there
3409 * may not even *be* an image. (Also, it makes it very obvious
3410 * which file screwed up.)
3411 */
3412 cb_back_to_clist(); /* enable selector */
3413
3414 selector_unblock();
3415 in_nextprev=in_routine=0;
3416 return;
3417 }
3418
3419 /* reflect loading of new pic in orientation stuff */
3420 orient_lastpicexit_state=orient_current_state;
3421 orient_current_state=0;
3422
3423 if(use_exif_orient)
3424 {
3425 /* apply Exif orientation correction, then pretend it's the normal pic */
3426 orient_change_state(0,jpeg_exif_orient);
3427 orient_current_state=0;
3428 }
3429
3430 if(revert)
3431 {
3432 xscaling=yscaling=1;
3433 /* note that revert *doesn't* do interp=0 in xzgv (it does in zgv) */
3434 /* XXX should mention this in docs, may confuse zgv refugees :-) */
3435 }
3436
3437 if(revert_orient)
3438 {
3439 /* if the last state was rotated, need to swap over scales. */
3440 if(orient_lastpicexit_state>3)
3441 swap_xyscaling();
3442 }
3443 else
3444 {
3445 /* since we're effectively *restoring* the state, we need to
3446 * preserve x/yscaling which will be erroneously `corrected'.
3447 */
3448 int xsav=xscaling,ysav=yscaling;
3449
3450 orient_change_state(orient_current_state,orient_lastpicexit_state);
3451 orient_current_state=orient_lastpicexit_state;
3452 xscaling=xsav; yscaling=ysav;
3453 }
3454
3455 /* don't pointlessly render a zoomed copy if we're just about to resize
3456 * it due to auto-hide!
3457 */
3458 if(!zoom || !auto_hide || hidden)
3459 render_pixmap(1);
3460
3461 /* only now can we safely free the old image (any old pixmap
3462 * will now no longer be onscreen).
3463 */
3464 if(oldimage)
3465 backend_image_destroy(oldimage),oldimage=NULL;
3466
3467 gtk_statusbar_pop(GTK_STATUSBAR(statusbar),sel_id);
3468
3469 /* switch focus to pic */
3470 gtk_widget_grab_focus(drawing_area);
3471
3472 /* stop us allowing kybd focus (until esc/tab) */
3473 GTK_WIDGET_UNSET_FLAGS(clist,GTK_CAN_FOCUS);
3474
3475 /* hide us if auto hide is on */
3476 if(auto_hide && !hidden)
3477 cb_hide_selector();
3478
3479 /* let them use the selector again :-) */
3480 selector_unblock();
3481
3482 /* allow next/prev and calls to this again */
3483 in_nextprev=in_routine=0;
3484 }
3485
3486
3487 void set_window_pos_and_size(void)
3488 {
3489 if(fullscreen)
3490 {
3491 /* go to top-left and use full screen */
3492 gtk_widget_set_uposition(mainwin,0,0);
3493 gtk_window_set_default_size(GTK_WINDOW(mainwin),
3494 gdk_screen_width(),gdk_screen_height());
3495 }
3496 else /* normal */
3497 {
3498 if((mainwin_flags&GEOM_BITS_X_SET) &&
3499 (mainwin_flags&GEOM_BITS_Y_SET))
3500 gtk_widget_set_uposition(mainwin,mainwin_x,mainwin_y);
3501 /* we always have width/height set */
3502 gtk_window_set_default_size(GTK_WINDOW(mainwin),mainwin_w,mainwin_h);
3503 }
3504 }
3505
3506
3507 void init_window(void)
3508 {
3509 /* basic layout is like this:
3510 * (paned in window contains all this)
3511 * __________________paned_________________
3512 * v| |^| | maybe toolbar here eventually?
3513 * b|clist of pics ||| |
3514 * o| in scrolled win||| |
3515 * x|1st col xvpic, ||| pic in scrolled win |
3516 * l|2nd col fname. ||| |
3517 * | |v| |
3518 * |------------------| |
3519 * |status bar | |
3520 * `----------------------------------------'
3521 */
3522 GtkWidget *vboxl;
3523 GtkWidget *clist_sw_ebox;
3524 GtkItemFactory *selector_menu_factory,*viewer_menu_factory;
3525 GdkPixmap *icon;
3526 GdkBitmap *icon_mask;
3527 char *ptr;
3528
3529 /* selector right-button menu */
3530 static GtkItemFactoryEntry selector_menu_items[]=
3531 {
3532 /* menu path key callback cb args item type */
3533 {"/_Update Thumbnails","u", cb_update_tn, 0, NULL},
3534 {"/_Recursive Update","<alt>u", cb_update_tn_recursive,0,NULL},
3535 {"/sep1", NULL, NULL, 0, "<Separator>"},
3536 {"/_File", NULL, NULL, 0, "<Branch>"},
3537 {"/_File/_Open", NULL, view_focus_row_file, 0, NULL},
3538 {"/_File/_Details...","colon", cb_file_details,0, NULL},
3539 {"/_File/Clo_se", "<control>w", cb_file_close, 0, NULL},
3540 {"/_File/sep1", NULL, NULL, 0, "<Separator>"},
3541 {"/_File/_Copy...", "<shift>c", cb_copy_files, 0, NULL},
3542 {"/_File/_Move...", "<shift>m", cb_move_files, 0, NULL},
3543 {"/_File/_Rename file...","<control>n",cb_rename_file,0, NULL},
3544 {"/_File/De_lete file...","<control>d",cb_delete_file,0, NULL},
3545 {"/_File/sep2", NULL, NULL, 0, "<Separator>"},
3546 /* duplicate exit, as people will expect it here */
3547 {"/_File/E_xit", NULL, gtk_main_quit, 0, NULL},
3548 {"/_Tagging", NULL, NULL, 0, "<Branch>"},
3549 {"/_Tagging/_Next Tagged","slash", cb_selector_next_tagged,0,NULL},
3550 {"/_Tagging/_Previous Tagged","question",cb_selector_prev_tagged,0,NULL},
3551 {"/_Tagging/sep1", NULL, NULL, 0, "<Separator>"},
3552 {"/_Tagging/_Tag", "equal", cb_tag_file, 0, NULL},
3553 {"/_Tagging/_Untag", "minus", cb_untag_file, 0, NULL},
3554 {"/_Tagging/sep2", NULL, NULL, 0, "<Separator>"},
3555 {"/_Tagging/Tag _All","<alt>equal", cb_tag_all, 0, NULL},
3556 {"/_Tagging/U_ntag All","<alt>minus", cb_untag_all, 0, NULL},
3557 {"/_Tagging/T_oggle All","<alt>o", cb_toggle_all, 0, NULL},
3558 {"/_Directory", NULL, NULL, 0, "<Branch>"},
3559 {"/_Directory/_Change...","<shift>g", cb_goto_dir, 0, NULL},
3560 {"/_Directory/_Rescan","<control>r", cb_reread_dir, 0, NULL},
3561 {"/_Directory/sep1", NULL, NULL, 0, "<Separator>"},
3562 {"/_Directory/Images Only","<alt>i", cb_show_images, 0, "<ToggleItem>"},
3563 {"/_Directory/sep1", NULL, NULL, 0, "<Separator>"},
3564 {"/_Directory/Sort by _Name","<alt>n",cb_name_order, 0, "<RadioItem>"},
3565 {"/_Directory/Sort by _Extension","<alt>e",cb_ext_order,
3566 0,"/Directory/Sort by Name"},
3567 {"/_Directory/Sort by _Size","<alt>s",cb_size_order,
3568 0,"/Directory/Sort by Name"},
3569 {"/_Directory/Sort by Time & _Date","<alt>d",cb_time_order,
3570 0,"/Directory/Sort by Name"},
3571 {"/_Directory/Time & Date _Type",NULL,NULL, 0, "<Branch>"},
3572 {"/_Directory/Time & Date _Type/_Modification Time (mtime)",
3573 "<alt><shift>m",
3574 cb_mtime_type,0,"<RadioItem>"},
3575 {"/_Directory/Time & Date _Type/Attribute _Change Time (ctime)",
3576 "<alt><shift>c",
3577 cb_ctime_type,0,"/Directory/Time & Date Type/Modification Time (mtime)"},
3578 {"/_Directory/Time & Date _Type/_Access Time (atime)",
3579 "<alt><shift>a",
3580 cb_atime_type,0,"/Directory/Time & Date Type/Modification Time (mtime)"},
3581 {"/_Options", NULL, NULL, 0, "<Branch>"},
3582 {"/_Options/_Auto Hide", "<alt>a", toggle_auto_hide,1, "<ToggleItem>"},
3583 {"/_Options/_Status Bar", "<alt>b", toggle_status, 1, "<ToggleItem>"},
3584 {"/_Options/Thumb_nail Msgs",
3585 NULL, toggle_tn_msgs, 1, "<ToggleItem>"},
3586 {"/_Options/_Thin Rows", "v", toggle_thin_rows, 1, "<ToggleItem>"},
3587 {"/_Help", NULL, NULL, 0, "<Branch>"},
3588 {"/_Help/_Contents", "F1", cb_help_contents,0, NULL},
3589 {"/_Help/The _File Selector",NULL, cb_help_selector,0, NULL},
3590 {"/_Help/_Index", NULL, cb_help_index, 0, NULL},
3591 {"/_Help/sep1", NULL, NULL, 0, "<Separator>"},
3592 {"/_Help/_About...", NULL, cb_help_about, 0, NULL},
3593 {"/sep2", NULL, NULL, 0, "<Separator>"},
3594 {"/E_xit xzgv", "<control>q", gtk_main_quit, 0, NULL}
3595 };
3596
3597 /* viewer right-button menu */
3598 static GtkItemFactoryEntry viewer_menu_items[]=
3599 {
3600 /* menu path key callback cb args item type */
3601 {"/_Next Image", "space", cb_next_image, 0, NULL},
3602 {"/_Previous Image", "b", cb_prev_image, 0, NULL},
3603 {"/sep1", NULL, NULL, 0, "<Separator>"},
3604 {"/_Tagging/_Tag then Next","<control>space",cb_tag_then_next,0, NULL},
3605 {"/_Tagging/sep1", NULL, NULL, 0, "<Separator>"},
3606 {"/_Tagging/_Next Tagged","slash",cb_viewer_next_tagged,0, NULL},
3607 {"/_Tagging/_Previous Tagged","question",cb_viewer_prev_tagged,0,NULL},
3608 {"/_Scaling", NULL, NULL, 0, "<Branch>"},
3609 {"/_Scaling/_Normal", "n", cb_normal, 0, NULL},
3610 {"/_Scaling/_Double Scaling", "d", cb_scaling_double,0, NULL},
3611 {"/_Scaling/_Halve Scaling", "<shift>d", cb_scaling_halve,0, NULL},
3612 {"/_Scaling/_Add 1 to Scaling","s", cb_scaling_add, 0, NULL},
3613 {"/_Scaling/_Sub 1 from Scaling","<shift>s", cb_scaling_sub, 0, NULL},
3614 {"/_Scaling/sep1", NULL, NULL, 0, "<Separator>"},
3615 {"/_Scaling/_X Only/_Double Scaling", "x", cb_xscaling_double,0, NULL},
3616 {"/_Scaling/_X Only/_Halve Scaling","<shift>x",cb_xscaling_halve,0, NULL},
3617 {"/_Scaling/_X Only/_Add 1 to Scaling","<alt>x",cb_xscaling_add,0, NULL},
3618 {"/_Scaling/_X Only/_Sub 1 from Scaling","<alt><shift>x",
3619 cb_xscaling_sub,0, NULL},
3620 {"/_Scaling/_Y Only/_Double Scaling", "y", cb_yscaling_double,0, NULL},
3621 {"/_Scaling/_Y Only/_Halve Scaling","<shift>y",cb_yscaling_halve,0, NULL},
3622 {"/_Scaling/_Y Only/_Add 1 to Scaling","<alt>y",cb_yscaling_add,0, NULL},
3623 {"/_Scaling/_Y Only/_Sub 1 from Scaling","<alt><shift>y",
3624 cb_yscaling_sub,0, NULL},
3625 {"/O_rientation", NULL, NULL, 0, "<Branch>"},
3626 {"/O_rientation/_Normal","<shift>n", cb_normal_orient,0, NULL},
3627 {"/O_rientation/_Mirror (horiz)","m", cb_mirror, 0, NULL},
3628 {"/O_rientation/_Flip (vert)","f", cb_flip, 0, NULL},
3629 {"/O_rientation/_Rotate Right","r", cb_rot_cw, 0, NULL},
3630 {"/O_rientation/Rotate _Left","<shift>r",cb_rot_acw, 0, NULL},
3631 {"/_Window", NULL, NULL, 0, "<Branch>"},
3632 {"/_Window/_Hide Selector","<shift>z",cb_hide_selector,0, NULL},
3633 /* I would normally write `minimise', but it looks a bit odd and the -ize
3634 * spelling is entrenched, so I'll live with it. :-) */
3635 {"/_Window/_Minimize", "<control>z", cb_iconify, 0, NULL},
3636 {"/_Options", NULL, NULL, 0, "<Branch>"},
3637 {"/_Options/_Zoom (fit to window)","z",toggle_zoom, 1, "<ToggleItem>"},
3638 {"/_Options/When Zooming _Reduce Only","<alt>r",
3639 toggle_zoom_reduce,1, "<ToggleItem>"},
3640 {"/_Options/When Zooming _Panorama","<alt>p",
3641 toggle_zoom_panorama,1, "<ToggleItem>"},
3642 {"/_Options/_Interpolate when Scaling","i",toggle_interp,1, "<ToggleItem>"},
3643 {"/_Options/_Ctl+Click Scales X Axis","<alt>c",
3644 toggle_mouse_x, 1, "<ToggleItem>"},
3645 {"/_Options/_Dither in 15 & 16-bit","<shift>f",
3646 toggle_hicol_dither,1, "<ToggleItem>"},
3647 {"/_Options/Use _Exif Orientation",NULL,toggle_exif_orient,1, "<ToggleItem>"},
3648 {"/_Options/sep1", NULL, NULL, 0, "<Separator>"},
3649 {"/_Options/Revert _Scaling For New Pic",NULL,
3650 toggle_revert, 1, "<ToggleItem>"},
3651 {"/_Options/Revert _Orient. For New Pic",NULL,
3652 toggle_revert_orient,1, "<ToggleItem>"},
3653 {"/_Help", NULL, NULL, 0, "<Branch>"},
3654 {"/_Help/_Contents", "F1", cb_help_contents,0, NULL},
3655 {"/_Help/The _Viewer",NULL, cb_help_viewer, 0, NULL},
3656 {"/_Help/_Index", NULL, cb_help_index, 0, NULL},
3657 {"/_Help/sep1", NULL, NULL, 0, "<Separator>"},
3658 {"/_Help/_About...", NULL, cb_help_about, 0, NULL},
3659 {"/sep2", NULL, NULL, 0, "<Separator>"},
3660 {"/E_xit to Selector","Escape", cb_back_to_clist, 0, NULL}
3661 };
3662
3663
3664 gtk_widget_push_visual(gdk_rgb_get_visual());
3665 gtk_widget_push_colormap(gdk_rgb_get_cmap());
3666 mainwin=gtk_window_new(GTK_WINDOW_TOPLEVEL);
3667 gtk_widget_pop_visual();
3668 gtk_widget_pop_colormap();
3669 GTK_WIDGET_UNSET_FLAGS(mainwin,GTK_CAN_FOCUS);
3670 gtk_signal_connect(GTK_OBJECT(mainwin),"destroy",
3671 GTK_SIGNAL_FUNC(cb_quit),NULL);
3672 /* don't include dir if selector initially hidden (loading from cmdline) */
3673 set_title(!hidden);
3674
3675 set_window_pos_and_size();
3676
3677
3678 pane=gtk_hpaned_new();
3679 GTK_WIDGET_UNSET_FLAGS(pane,GTK_CAN_FOCUS);
3680 gtk_container_add(GTK_CONTAINER(mainwin),pane);
3681 gtk_widget_show(pane);
3682
3683
3684 /* right-hand side */
3685
3686 /* the drawing area used for the pic */
3687 drawing_area=gtk_drawing_area_new();
3688 GTK_WIDGET_SET_FLAGS(drawing_area,GTK_CAN_FOCUS);
3689 viewer_menu_factory=make_menu("<main>",viewer_menu_items,
3690 sizeof(viewer_menu_items)/sizeof(
3691 viewer_menu_items[0]));
3692 viewer_menu=gtk_item_factory_get_widget(viewer_menu_factory,"<main>");
3693
3694 gtk_signal_connect(GTK_OBJECT(drawing_area),"motion_notify_event",
3695 GTK_SIGNAL_FUNC(viewer_motion),NULL);
3696 gtk_signal_connect(GTK_OBJECT(drawing_area),"key_press_event",
3697 GTK_SIGNAL_FUNC(viewer_key_press),NULL);
3698
3699 /* need to ask for motion while button 1 is pressed (for drag),
3700 * keypresses, and (for scaling) expose.
3701 */
3702 gtk_widget_set_events(drawing_area,
3703 GDK_BUTTON1_MOTION_MASK|GDK_KEY_PRESS_MASK|
3704 GDK_EXPOSURE_MASK);
3705
3706 gtk_widget_show(drawing_area);
3707
3708 /* alignment to centre the DA */
3709 align=gtk_alignment_new(0.5,0.5,0.,0.);
3710 gtk_container_add(GTK_CONTAINER(align),drawing_area);
3711 gtk_widget_show(align);
3712
3713 /* scrolled window DA goes into (`inside' alignment) */
3714 sw_for_pic=gtk_scrolled_window_new(NULL,NULL);
3715 GTK_WIDGET_UNSET_FLAGS(sw_for_pic,GTK_CAN_FOCUS);
3716 gtk_container_set_border_width(GTK_CONTAINER(sw_for_pic),0);
3717 /* first `POLICY' is horiz, second is vert */
3718 if(zoom)
3719 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sw_for_pic),
3720 (zoom_panorama&&zoom_panorama_sb)?GTK_POLICY_NEVER:GTK_POLICY_AUTOMATIC,
3721 (zoom_panorama&&!zoom_panorama_sb)?GTK_POLICY_NEVER:GTK_POLICY_AUTOMATIC);
3722 else
3723 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sw_for_pic),
3724 GTK_POLICY_AUTOMATIC,
3725 GTK_POLICY_AUTOMATIC);
3726 gtk_paned_add2(GTK_PANED(pane),sw_for_pic);
3727 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(sw_for_pic),
3728 align);
3729 gtk_widget_show(sw_for_pic);
3730
3731
3732 /* left-hand side */
3733 vboxl=gtk_vbox_new(FALSE,0);
3734 GTK_WIDGET_UNSET_FLAGS(vboxl,GTK_CAN_FOCUS);
3735 gtk_paned_add1(GTK_PANED(pane),vboxl);
3736 gtk_widget_show(vboxl);
3737
3738 /* event box for scrolled window for clist (!), to make sure it has a
3739 * proper window to draw into (otherwise it screws up when pane-split pos
3740 * is near left of window). The image is ok on this count 'cos its
3741 * scrollbars are drawn to the right, i.e. off the window, and X clips
3742 * them. :-)
3743 */
3744 clist_sw_ebox=gtk_event_box_new();
3745 gtk_box_pack_start(GTK_BOX(vboxl),clist_sw_ebox,TRUE,TRUE,0);
3746
3747 /* pass on left-button motion events to viewer's image-dragging stuff,
3748 * so it doesn't stop dragging just because you drag the pointer over
3749 * the selector. This means we have to carefully ignore any drags which
3750 * start in the selector though, hence the left-button-press event
3751 * handling here.
3752 */
3753 gtk_signal_connect(GTK_OBJECT(clist_sw_ebox),"button_press_event",
3754 GTK_SIGNAL_FUNC(clist_sw_ebox_button_press),NULL);
3755 gtk_signal_connect(GTK_OBJECT(clist_sw_ebox),"motion_notify_event",
3756 GTK_SIGNAL_FUNC(viewer_motion),NULL);
3757 gtk_widget_set_events(clist_sw_ebox,
3758 GDK_BUTTON_PRESS_MASK|GDK_BUTTON1_MOTION_MASK);
3759 gtk_widget_show(clist_sw_ebox);
3760
3761 /* now the scrolled window for clist, and the clist which goes into it. */
3762 sw_for_clist=gtk_scrolled_window_new(NULL,NULL);
3763 GTK_WIDGET_UNSET_FLAGS(sw_for_clist,GTK_CAN_FOCUS);
3764 gtk_container_set_border_width(GTK_CONTAINER(sw_for_clist),0);
3765
3766 /* first `POLICY' is horiz, second is vert */
3767 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sw_for_clist),
3768 GTK_POLICY_AUTOMATIC,GTK_POLICY_AUTOMATIC);
3769
3770 gtk_container_add(GTK_CONTAINER(clist_sw_ebox),sw_for_clist);
3771 gtk_widget_show(sw_for_clist);
3772
3773 /* the clist */
3774 clist=gtk_clist_new(2);
3775 /* select only one thing at a time */
3776 gtk_clist_set_selection_mode(GTK_CLIST(clist),GTK_SELECTION_SINGLE);
3777
3778 /* selection callback - we save handler id as it needs to be blocked
3779 * in some circumstances.
3780 */
3781 cb_selection_id=gtk_signal_connect(GTK_OBJECT(clist),"select_row",
3782 GTK_SIGNAL_FUNC(cb_selection),sw_for_pic);
3783
3784 set_thumbnail_column_width(); /* set width of thumbnail column */
3785 gtk_clist_set_column_auto_resize(GTK_CLIST(clist),SELECTOR_NAME_COL,TRUE);
3786 /* set heights to thin initially; see end of routine for why */
3787 gtk_clist_set_row_height(GTK_CLIST(clist),ROW_HEIGHT_THIN);
3788 gtk_clist_set_column_justification(GTK_CLIST(clist),
3789 SELECTOR_TN_COL,GTK_JUSTIFY_CENTER);
3790 gtk_clist_set_compare_func(GTK_CLIST(clist),sort_cmp);
3791 gtk_clist_set_sort_column(GTK_CLIST(clist),SELECTOR_NAME_COL);
3792
3793 /* put in scrolled_window
3794 * (can't use ...add_with_viewport() if I want keyboard control to work)
3795 * (actually, the viewport() way seems so limited by comparison I'm
3796 * surprised the GTK+ tutorial describes it as `the' way to do it,
3797 * even if it does work for all widgets. :-/)
3798 */
3799 gtk_container_add(GTK_CONTAINER(sw_for_clist),clist);
3800
3801 /* menu stuff */
3802 selector_menu_factory=make_menu("<main>",selector_menu_items,
3803 sizeof(selector_menu_items)/sizeof(
3804 selector_menu_items[0]));
3805 selector_menu=gtk_item_factory_get_widget(selector_menu_factory,"<main>");
3806
3807 gtk_signal_connect(GTK_OBJECT(clist),"button_press_event",
3808 GTK_SIGNAL_FUNC(selector_button_press),NULL);
3809 gtk_signal_connect(GTK_OBJECT(clist),"button_release_event",
3810 GTK_SIGNAL_FUNC(selector_button_release),NULL);
3811 gtk_signal_connect(GTK_OBJECT(clist),"key_press_event",
3812 GTK_SIGNAL_FUNC(selector_key_press),NULL);
3813 /* need to ask for button press (for menu), release (for tag), and key press */
3814 gtk_widget_set_events(clist,
3815 GDK_BUTTON_PRESS_MASK|GDK_BUTTON_RELEASE_MASK|
3816 GDK_KEY_PRESS_MASK);
3817
3818 gtk_widget_show(clist);
3819
3820 /* status line */
3821 statusbar=gtk_statusbar_new();
3822 gtk_box_pack_start(GTK_BOX(vboxl),statusbar,FALSE,FALSE,0);
3823 /* get context ids */
3824 sel_id=gtk_statusbar_get_context_id(GTK_STATUSBAR(statusbar),"selector");
3825 tn_id= gtk_statusbar_get_context_id(GTK_STATUSBAR(statusbar),"thumbnails");
3826 gtk_widget_show(statusbar);
3827 if(!have_statusbar)
3828 gtk_widget_hide(statusbar);
3829
3830
3831 /* fix menu options to reflect current status */
3832 switch(filesel_sorttype)
3833 {
3834 case sort_name: ptr="<main>/Directory/Sort by Name"; break;
3835 case sort_ext: ptr="<main>/Directory/Sort by Extension"; break;
3836 case sort_size: ptr="<main>/Directory/Sort by Size"; break;
3837 default:
3838 /* sort_time */ ptr="<main>/Directory/Sort by Time & Date"; break;
3839 }
3840 gtk_check_menu_item_set_active(
3841 &(GTK_RADIO_MENU_ITEM(gtk_item_factory_get_widget(
3842 selector_menu_factory,ptr))->check_menu_item),TRUE);
3843
3844 switch(sort_timestamp_type)
3845 {
3846 default: ptr="<main>/Directory/Time & Date Type/"
3847 "Modification Time (mtime)"; break;
3848 case 1: ptr="<main>/Directory/Time & Date Type/"
3849 "Attribute Change Time (ctime)"; break;
3850 case 2: ptr="<main>/Directory/Time & Date Type/"
3851 "Access Time (atime)"; break;
3852 }
3853 gtk_check_menu_item_set_active(
3854 &(GTK_RADIO_MENU_ITEM(gtk_item_factory_get_widget(
3855 selector_menu_factory,ptr))->check_menu_item),TRUE);
3856
3857 gtk_check_menu_item_set_active(
3858 GTK_CHECK_MENU_ITEM(
3859 gtk_item_factory_get_widget(selector_menu_factory,
3860 "<main>/Options/Auto Hide")),auto_hide);
3861
3862 gtk_check_menu_item_set_active(
3863 GTK_CHECK_MENU_ITEM(
3864 gtk_item_factory_get_widget(selector_menu_factory,
3865 "<main>/Options/Status Bar")),have_statusbar);
3866
3867 gtk_check_menu_item_set_active(
3868 GTK_CHECK_MENU_ITEM(
3869 gtk_item_factory_get_widget(selector_menu_factory,
3870 "<main>/Options/Thumbnail Msgs")),tn_msgs);
3871
3872 gtk_check_menu_item_set_active(
3873 GTK_CHECK_MENU_ITEM(
3874 gtk_item_factory_get_widget(selector_menu_factory,
3875 "<main>/Options/Thin Rows")),thin_rows);
3876
3877 gtk_check_menu_item_set_active(
3878 GTK_CHECK_MENU_ITEM(
3879 gtk_item_factory_get_widget(selector_menu_factory,
3880 "<main>/Directory/Images Only")),
3881 show_images_only);
3882
3883 gtk_check_menu_item_set_active(
3884 GTK_CHECK_MENU_ITEM(
3885 gtk_item_factory_get_widget(viewer_menu_factory,
3886 "<main>/Options/When Zooming Reduce Only")),
3887 zoom_reduce_only);
3888
3889 gtk_check_menu_item_set_active(
3890 GTK_CHECK_MENU_ITEM(
3891 gtk_item_factory_get_widget(viewer_menu_factory,
3892 "<main>/Options/When Zooming Panorama")),
3893 zoom_panorama);
3894
3895 gtk_check_menu_item_set_active(
3896 GTK_CHECK_MENU_ITEM(
3897 gtk_item_factory_get_widget(viewer_menu_factory,
3898 "<main>/Options/Interpolate when Scaling")),
3899 interp);
3900
3901 gtk_check_menu_item_set_active(
3902 GTK_CHECK_MENU_ITEM(
3903 gtk_item_factory_get_widget(viewer_menu_factory,
3904 "<main>/Options/Ctl+Click Scales X Axis")),
3905 mouse_scale_x);
3906
3907 if(hicol_dither==-1)
3908 gtk_widget_set_sensitive(
3909 gtk_item_factory_get_widget(viewer_menu_factory,
3910 "<main>/Options/Dither in 15 & 16-bit"),FALSE);
3911 else
3912 gtk_check_menu_item_set_active(
3913 GTK_CHECK_MENU_ITEM(
3914 gtk_item_factory_get_widget(viewer_menu_factory,
3915 "<main>/Options/Dither in 15 & 16-bit")),
3916 hicol_dither);
3917
3918 gtk_check_menu_item_set_active(
3919 GTK_CHECK_MENU_ITEM(
3920 gtk_item_factory_get_widget(viewer_menu_factory,
3921 "<main>/Options/Revert Orient. For New Pic")),
3922 revert_orient);
3923
3924 gtk_check_menu_item_set_active(
3925 GTK_CHECK_MENU_ITEM(
3926 gtk_item_factory_get_widget(viewer_menu_factory,
3927 "<main>/Options/Revert Scaling For New Pic")),
3928 revert);
3929
3930 gtk_check_menu_item_set_active(
3931 GTK_CHECK_MENU_ITEM(
3932 gtk_item_factory_get_widget(viewer_menu_factory,
3933 "<main>/Options/Use Exif Orientation")),
3934 use_exif_orient);
3935
3936 zoom_widget=gtk_item_factory_get_widget(viewer_menu_factory,
3937 "<main>/Options/Zoom (fit to window)");
3938 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(zoom_widget),zoom);
3939
3940 /* disable thumbnail update and `thumbnail msgs' option if sel initially
3941 * hidden (loading from cmdline). Also disable go-to-dir/rescan; in theory
3942 * we *could* allow those, but it would be hairy. Another hairy one is
3943 * file rename, due to assumptions made about the file being in the current
3944 * dir. A few others are irrelevant or cause problems, too.
3945 */
3946 if(hidden)
3947 {
3948 gtk_widget_set_sensitive(
3949 gtk_item_factory_get_widget(selector_menu_factory,
3950 "<main>/Update Thumbnails"),FALSE);
3951 gtk_widget_set_sensitive(
3952 gtk_item_factory_get_widget(selector_menu_factory,
3953 "<main>/Recursive Update"),FALSE);
3954 gtk_widget_set_sensitive(
3955 gtk_item_factory_get_widget(selector_menu_factory,
3956 "<main>/File/Rename file..."),FALSE);
3957 gtk_widget_set_sensitive(
3958 gtk_item_factory_get_widget(selector_menu_factory,
3959 "<main>/Directory/Change..."),FALSE);
3960 gtk_widget_set_sensitive(
3961 gtk_item_factory_get_widget(selector_menu_factory,
3962 "<main>/Directory/Rescan"),FALSE);
3963 gtk_widget_set_sensitive(
3964 gtk_item_factory_get_widget(selector_menu_factory,
3965 "<main>/Options/Thumbnail Msgs"),FALSE);
3966 gtk_widget_set_sensitive(
3967 gtk_item_factory_get_widget(selector_menu_factory,
3968 "<main>/Options/Thin Rows"),FALSE);
3969 }
3970
3971 /* hook up an alternative quit key (q) */
3972 gtk_widget_add_accelerator(
3973 gtk_item_factory_get_widget(selector_menu_factory,
3974 "<main>/Exit xzgv"),
3975 "activate",mainwin_accel_group,
3976 GDK_q,0,0);
3977
3978
3979 /* severely hairy, but needed to allow menu to appear when a non-image
3980 * bit of the viewer window is selected. Also allows drags in non-image
3981 * bits, which is handy for really thin images.
3982 */
3983 gtk_signal_connect(GTK_OBJECT(sw_for_pic),
3984 "button_press_event",
3985 GTK_SIGNAL_FUNC(viewer_button_press),NULL);
3986
3987 /* have to carefully override this for scrollbars! */
3988 gtk_signal_connect_after(
3989 GTK_OBJECT(GTK_SCROLLED_WINDOW(sw_for_pic)->hscrollbar),
3990 "button_press_event",GTK_SIGNAL_FUNC(viewer_sb_button_press),NULL);
3991 gtk_signal_connect_after(
3992 GTK_OBJECT(GTK_SCROLLED_WINDOW(sw_for_pic)->vscrollbar),
3993 "button_press_event",GTK_SIGNAL_FUNC(viewer_sb_button_press),NULL);
3994
3995
3996 gtk_signal_connect(GTK_OBJECT(mainwin),"configure_event",
3997 GTK_SIGNAL_FUNC(pic_win_resized),NULL);
3998 /* this catches dragging across the pane splitter */
3999 gtk_signal_connect(GTK_OBJECT(mainwin),"motion_notify_event",
4000 GTK_SIGNAL_FUNC(viewer_motion),NULL);
4001 gtk_signal_connect(GTK_OBJECT(mainwin),
4002 "button_release_event",
4003 GTK_SIGNAL_FUNC(viewer_button_release),NULL);
4004 /* ask for configure and left-button drag */
4005 gtk_widget_set_events(mainwin,
4006 GDK_STRUCTURE_MASK|GDK_BUTTON1_MOTION_MASK|
4007 GDK_BUTTON_RELEASE_MASK);
4008
4009
4010 /* if hidden is set, we should hide it initially */
4011 hide_saved_pos=default_sel_width;
4012 gtk_paned_set_position(GTK_PANED(pane),hidden?1:hide_saved_pos);
4013
4014 gtk_widget_set_usize(mainwin,100,50);
4015
4016 /* initially focus clist */
4017 gtk_widget_grab_focus(clist);
4018
4019 /* make sure option toggles are acknowledged */
4020 listen_to_toggles=1;
4021
4022 gtk_widget_show(mainwin);
4023
4024
4025 /* set icon (XXX size should be configurable) */
4026 icon=gdk_pixmap_create_from_xpm_d(mainwin->window,&icon_mask,NULL,icon_48_xpm);
4027 gdk_window_set_icon(mainwin->window,NULL,icon,icon_mask);
4028
4029 if(fullscreen)
4030 {
4031 /* use mwm hints (I think) to turn off window frame */
4032 gdk_window_set_decorations(mainwin->window,0);
4033 /* also, only allow window close to happen (not resize/move/mini/maximise) */
4034 gdk_window_set_functions(mainwin->window,GDK_FUNC_CLOSE);
4035 }
4036
4037 /* adjust row heights now, which should leave filename text
4038 * auto-adjusted to be roughly centred. This is really kludgey, but
4039 * I couldn't get this to work using vertical shifts for both
4040 * thin_rows modes.
4041 */
4042 gtk_clist_set_row_height(GTK_CLIST(clist),ROW_HEIGHT_NORMAL);
4043
4044 /* that's all folks */
4045 }
4046
4047
4048 void init_icon_pixmaps(void)
4049 {
4050 /* convert #included XPMs to pixmaps. We then use refs to these pixmaps
4051 * (increasing the ref count should avoid gtk_clist_clear() freeing them).
4052 */
4053 backend_create_pixmap_from_xpm_data((const char **)dir_icon_xpm,
4054 &dir_icon,&dir_icon_mask);
4055 backend_create_pixmap_from_xpm_data((const char **)file_icon_xpm,
4056 &file_icon,&file_icon_mask);
4057
4058 backend_create_pixmap_from_xpm_data((const char **)dir_icon_small_xpm,
4059 &dir_icon_small,&dir_icon_small_mask);
4060 backend_create_pixmap_from_xpm_data((const char **)file_icon_small_xpm,
4061 &file_icon_small,&file_icon_small_mask);
4062 }
4063
4064
4065 int isdir(char *filename)
4066 {
4067 struct stat sbuf;
4068
4069 if(stat(filename,&sbuf)==-1 || !S_ISDIR(sbuf.st_mode))
4070 return(0);
4071
4072 return(1);
4073 }
4074
4075
4076 void create_clist_from_cmdline(int argsleft,int argc,char *argv[])
4077 {
4078 int f;
4079 struct stat sbuf;
4080
4081 numrows=0;
4082 for(f=argc-argsleft;f<=argc-1;f++)
4083 {
4084 /* can't use isdir() as that has different reaction to stat() failing */
4085 if(stat(argv[f],&sbuf)!=-1 && !S_ISDIR(sbuf.st_mode))
4086 {
4087 if(clist_add_new_row(argv[f],&sbuf))
4088 numrows++;
4089 }
4090 }
4091
4092 /* there may be no valid files; quit if so */
4093 if(numrows==0)
4094 {
4095 fprintf(stderr,"xzgv: cannot open files given on command line!\n");
4096 exit(1);
4097 }
4098 }
4099
4100
4101 void echo_tagged_files(void)
4102 {
4103 struct clist_data_tag *datptr;
4104 char *ptr;
4105 int f;
4106
4107 for(f=0;f<numrows;f++)
4108 {
4109 gtk_clist_get_text(GTK_CLIST(clist),f,SELECTOR_NAME_COL,&ptr);
4110 datptr=gtk_clist_get_row_data(GTK_CLIST(clist),f);
4111 if(datptr && datptr->tagged)
4112 printf("%s\n",ptr);
4113 }
4114 }
4115
4116
4117 int main(int argc,char *argv[])
4118 {
4119 int f,argsleft;
4120 int read_dir=1;
4121 int old_hidith;
4122
4123 gtk_set_locale();
4124 gtk_init(&argc,&argv);
4125 backend_init();
4126
4127 /* set hicol_dither based on current setting */
4128 hicol_dither=backend_get_hicol_dither();
4129
4130 /* force it to n/a if more than 16-bit, though */
4131 if(gdk_visual_get_best_depth()>16)
4132 hicol_dither=-1;
4133
4134 old_hidith=hicol_dither;
4135
4136 find_xvpic_cols();
4137
4138 /* blank out past-positions array */
4139 for(f=0;f<MAX_PASTPOS;f++)
4140 pastpos[f].dev=-1,pastpos[f].inode=-1;
4141
4142 get_config(); /* read config file if any */
4143 argsleft=parse_options(argc,argv); /* and command-line options */
4144
4145 /* they may have changed hicol_dither, so tell backend */
4146 if(old_hidith!=-1)
4147 backend_set_hicol_dither(hicol_dither);
4148 else
4149 hicol_dither=-1; /* if it was n/a before, it should be n/a now :-) */
4150
4151 if(argsleft==1 && isdir(argv[optind]))
4152 chdir(argv[optind]); /* change to start directory */
4153 else
4154 {
4155 if(argsleft>=1)
4156 {
4157 thin_rows=1; /* use thin rows mode (good for filenames only :-)) */
4158 hidden=1; /* hide selector (init_window() deals with this) */
4159 read_dir=0; /* don't read dir on startup */
4160 cmdline_files=1; /* needed for copymove.c to do the Right Thing */
4161 }
4162 }
4163
4164
4165 /* now actually get going */
4166 init_window();
4167
4168 init_icon_pixmaps();
4169
4170 /* read dir (unless loading pics from cmdline) */
4171 if(read_dir)
4172 {
4173 create_clist_from_dir();
4174 if(skip_parent && numrows>1) /* skip .. if they asked us to */
4175 {
4176 char *ptr;
4177
4178 /* check it's really `..' (won't be if in root dir) */
4179 gtk_clist_get_text(GTK_CLIST(clist),0,SELECTOR_NAME_COL,&ptr);
4180 if(strcmp(ptr,"..")==0)
4181 set_focus_row(1);
4182 }
4183 }
4184 else
4185 {
4186 create_clist_from_cmdline(argsleft,argc,argv);
4187 gtk_clist_set_column_width(GTK_CLIST(clist),SELECTOR_TN_COL,1);
4188 /* select first image, but make sure things are up and running first */
4189 do_gtk_stuff();
4190 gtk_clist_select_row(GTK_CLIST(clist),0,0);
4191 }
4192
4193 /* initialise thin_rows stuff (has to be after above so there's something
4194 * (or nothing :-)) in the clist). Only needed if thin_rows is initially
4195 * true, though.
4196 */
4197 if(thin_rows)
4198 fix_row_heights();
4199
4200 gtk_main();
4201
4202 if(show_tagged)
4203 echo_tagged_files();
4204
4205 exit(0);
4206 }