"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 }