home *** CD-ROM | disk | FTP | other *** search
/ PC Pro 2002 April / pcpro0402.iso / essentials / graphics / Gimp / gimp-src-20001226.exe / src / gimp / unofficial-plug-ins / guash / guash.c < prev    next >
Encoding:
C/C++ Source or Header  |  1999-10-20  |  211.0 KB  |  8,031 lines

  1. /* The GIMP -- an image manipulation program
  2.  * Copyright (C) 1995 Spencer Kimball and Peter Mattis */
  3. #define GUASH_TIMESTAMP "Time-stamp: <1999/03/17 22:29:44 narazaki@gimp.org>"
  4. /* guash.c -- This is a plug-in/extension for the GIMP 1.X
  5.  * Guash: Gimp Users' Another SHell (pronounced like 'gouache')
  6.  * Copyright (C) 1997-1999 Shuji Narazaki <narazaki@gimp.org>
  7.  * guash uses internally for loading xv's thumbnail:
  8.  * xvpict (Copyright (C) 1997 Marco Lamberto <lm@geocities.com>)
  9.  * (slighly modified xvpict is embedded into guash for reducing communication
  10.  * overhead)
  11.  *
  12.  * This program is free software; you can redistribute it and/or modify
  13.  * it under the terms of the GNU General Public License as published by
  14.  * the Free Software Foundation; either version 2 of the License, or
  15.  * (at your option) any later version.
  16.  *
  17.  * This program is distributed in the hope that it will be useful,
  18.  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  19.  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  20.  * GNU General Public License for more details.
  21.  *
  22.  * You should have received a copy of the GNU General Public License
  23.  * along with this program; if not, write to the Free Software
  24.  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
  25.  *
  26.  * THANKS
  27.  * this release of guash was applied patches from:
  28.  *  Marco Lamberto <lm@geocities.com>
  29.  *  Vincent Hascoet <hascoet@lep.research.philips.com>
  30.  *  J"urgen Koslowski <koslowj@iti.cs.tu-bs.de>
  31.  *  meo@netads.com (Miles O'Neal)
  32.  *  Casper Maarbjerg <casper@tv1.dk>
  33.  *  Drew Ronneberg <drew@rnaworld.princeton.edu>
  34.  *  craig@pablo.ppco.com (Craig Brockmeier)
  35.  *  Colin Plumb <colin@nyx.net>
  36.  *  Shawn Houston <houston@arsc.edu>
  37.  */
  38.  
  39. #include "config.h"
  40.  
  41. #include <gtk/gtk.h>
  42. #include <gdk/gdkkeysyms.h>
  43. #include <libgimp/gimp.h>
  44. #include <stdio.h>
  45. #include <stdlib.h>
  46. #include <string.h>
  47. #include <sys/types.h>
  48. #include <sys/stat.h>
  49. #ifdef HAVE_DIRENT_H
  50. #include <dirent.h>
  51. #endif
  52. #include <fcntl.h>
  53. #ifdef HAVE_PWD_H
  54. #include <pwd.h>
  55. #endif
  56. #include <errno.h>
  57. #ifdef HAVE_UNISTD_H
  58. #include <unistd.h>
  59. #endif
  60.  
  61. #include "guash-config.h"
  62. #include "VERSION"
  63. #include "include/debug.h"
  64. #include "data/guash-banner.h"
  65. #include "data/guash-dir_icon.h"
  66. #include "data/guash-pdir_icon.h"
  67. #include "data/guash-file_icon.h"
  68.  
  69. #ifdef G_OS_WIN32
  70. #define mkdir(p, m) _mkdir (p)
  71. #define sleep(s) g_usleep (s*1000000)
  72. #define readlink(p, b, l) (-1)
  73. #endif
  74.  
  75. /* The following definition is only for narazaki@gimp.org (linux user) */
  76. #ifdef CHECK_PERFORMANCE
  77. # include <sys/times.h>
  78. # define WATCH(exp)    \
  79. { \
  80.   struct tms    _t;\
  81.   gulong    _start = (gulong) times (&_t);\
  82.   exp;\
  83.   printf ("Elapsed time: %ld msec.\n", (gulong) times (&_t) - _start);\
  84. }
  85. #else
  86. # define WATCH(exp)    exp
  87. #endif
  88.  
  89. /* CONSTANTS */
  90. #ifdef DEBUG
  91. # define PLUG_IN_NAME        "extension_guash_debug"
  92. # define PLUG_IN_NAME_SUB    "plug_in_guash_debug"
  93. # define PLUG_IN_NAME_FOR_SELECTION "extension_guash_script_fu_interface"
  94. # define SHORT_NAME        "Guash (debug)"
  95. # define MENU_PATH_AS_EXTENSION    "<Toolbox>/Xtns/Guash (debug)"
  96. # define MENU_PATH_AS_PLUG_IN    "<Image>/File/Guash (debug)"
  97. #else
  98. # define PLUG_IN_NAME        "extension_guash"
  99. # define PLUG_IN_NAME_SUB    "plug_in_guash"
  100. # define PLUG_IN_NAME_FOR_SELECTION "extension_guash_script_fu_interface"
  101. # define SHORT_NAME        "Guash"
  102. # define MENU_PATH_AS_EXTENSION    "<Toolbox>/Xtns/Guash"
  103. # define MENU_PATH_AS_PLUG_IN    "<Image>/File/Guash"
  104. #endif
  105.  
  106. #ifndef NEW_DND_API
  107. #define GUASH_DND_SIGNATURE    "Guash copy files:\n"
  108. #endif
  109. #define THUMBNAIL_DIR        "/.xvpics"
  110. #define DELETE_CORE        TRUE
  111.  
  112. #ifdef GIMP_HAVE_PARASITES
  113. #define GUASH_PARASITE_NAME    "guash-selection"
  114. #endif
  115.  
  116. /* VISUAL PARAMETERS */
  117. #define JUMP_BUTTON_WIDTH    40     /* change it if you use a large font */
  118. #define LABEL_PADDING        4     /* [pixel] from left margin */
  119. #define    SCROLLBAR_WIDTH        15    /* the width of scrollbar */
  120. #define THUMBNAIL_TSEPARATOR    2    /* v. pad between thumb. and name */
  121. #define THUMBNAIL_BORDER_WIDTH    2     /* width of selection border */
  122. #define WHITE_COLOR        { 255, 255, 255 }
  123. #define CURSOR_DEFAULT        GDK_TOP_LEFT_ARROW
  124. #define CURSOR_HAND        GDK_HAND2
  125. #define CURSOR_WAIT        GDK_WATCH
  126. #define NCOL_OF_THUMBNAIL_PANEL_MIN    4 /* minimal size (width) of panel */
  127. #define NROW_OF_THUMBNAIL_PANEL_MIN    1 /* minimal size (height) of panel */
  128.  
  129. /* VISUAL CONSTANTS */
  130. #define THUMBNAIL_WIDTH        80    /* Max width of XV thumbnail */
  131. #define THUMBNAIL_HEIGHT    60    /* Max height of XV thumbnail */
  132.  
  133. #define LABEL_WIDTH(x)        ((x) - JUMP_BUTTON_WIDTH - LABEL_PADDING)
  134. #define THUMBNAIL_THEIGHT    (the_panel.font_height + THUMBNAIL_TSEPARATOR)
  135. #define THUMBNAIL_FULLHEIGHT    (THUMBNAIL_HEIGHT + THUMBNAIL_THEIGHT)
  136. #define THUMBNAIL_FULLHEIGHT    (THUMBNAIL_HEIGHT + THUMBNAIL_THEIGHT)
  137. #define THUMBNAIL_SEPARATOR    (2 * THUMBNAIL_BORDER_WIDTH + 3)
  138.  
  139. /* OTHER CONSTANTS */
  140. #define INITIALIZATION_DELAY    100        /* [msec] */
  141. #define BANNER_UP_PERIOD    200        /* [msec] */
  142. #define INITIALIZATION_SLEEP_PERIOD    1200    /* [msec] */
  143. #define STARTUP_SLEEP_PERIOD        300    /* [msec] */
  144. #define EVENT_MASK    (GDK_EXPOSURE_MASK | GDK_BUTTON_PRESS_MASK)
  145. #define    LINE_BUF_SIZE    1024
  146. #define PATH_LENGTH    2049
  147.  
  148. #define INTERFACE        guash_interface
  149. #define    DIALOG            guash_dialog
  150. #define VALS            guash_vals
  151.  
  152. #define INDEX_TO_X(i)    ((i % the_panel.ncol)\
  153.              * (THUMBNAIL_WIDTH + THUMBNAIL_SEPARATOR)\
  154.              + THUMBNAIL_SEPARATOR)
  155. #define INDEX_TO_Y(i)    ((i / the_panel.ncol)\
  156.              * (THUMBNAIL_FULLHEIGHT + THUMBNAIL_SEPARATOR)\
  157.              + THUMBNAIL_SEPARATOR)
  158. #define VALID_POS_P(x,y)    ((y < \
  159.                   (THUMBNAIL_FULLHEIGHT + THUMBNAIL_SEPARATOR)\
  160.                   * the_panel.nrow)\
  161.                  && \
  162.                  ((x < \
  163.                    (THUMBNAIL_WIDTH + THUMBNAIL_SEPARATOR)\
  164.                    * the_panel.ncol)))
  165. #define POS_TO_INDEX(x,y)    (cwd_cache->display_page * the_panel.nthumb_in_page \
  166.                  + (y - THUMBNAIL_SEPARATOR) \
  167.                  / (THUMBNAIL_FULLHEIGHT + THUMBNAIL_SEPARATOR)\
  168.                  * the_panel.ncol \
  169.                  + x / (THUMBNAIL_WIDTH + THUMBNAIL_SEPARATOR))
  170. #define COL2WIDTH(col)    (col * (THUMBNAIL_WIDTH + THUMBNAIL_SEPARATOR) + THUMBNAIL_SEPARATOR)
  171. #define ROW2HEIGHT(row)    (row * (THUMBNAIL_FULLHEIGHT + THUMBNAIL_SEPARATOR) + THUMBNAIL_SEPARATOR)
  172. #define WIDTH2COL(w)    (w - THUMBNAIL_SEPARATOR) / (THUMBNAIL_WIDTH + THUMBNAIL_SEPARATOR)
  173. #define HEIGHT2ROW(h)    (h - THUMBNAIL_SEPARATOR) / (THUMBNAIL_FULLHEIGHT + THUMBNAIL_SEPARATOR)
  174.  
  175. /* programming stuff */
  176. #define ASSERT(predicate)    if (! (predicate)) return
  177.  
  178. /* gtkWrapper */
  179. /* gtkW is the abbreviation of gtk Wrapper */
  180. #define GTKW_ENTRY_BUFFER_SIZE    3
  181. #define GTKW_ENTRY_WIDTH    20
  182. #define GTKW_SCALE_WIDTH    200
  183. #define    GTKW_BORDER_WIDTH    0
  184.  
  185. /* gtkW type */
  186. typedef struct
  187. {
  188.   GtkWidget    *widget;
  189.   gpointer    value;
  190.   void         (*updater)();
  191. } gtkW_widget_table;
  192. gtkW_widget_table    widget_pointer[1];
  193.  
  194. /* gtkW global variables */
  195. gint    gtkW_border_width = GTKW_BORDER_WIDTH;
  196. gint    gtkW_border_height = 0;
  197. gint    gtkW_homogeneous_layout    = FALSE;
  198. gint    gtkW_frame_shadow_type = GTK_SHADOW_ETCHED_IN;
  199. gint    gtkW_align_x = GTK_FILL;
  200. gint    gtkW_align_y = GTK_FILL;
  201.  
  202. #include "include/gtkW.h"
  203.  
  204. /* define enums */
  205. /* for binding_style {BINDING_GIMP, BINDING_EMACS } I*/
  206. #define BINDING_GIMP    0
  207. #define BINDING_EMACS    1
  208. #define NBINDINGS    2    /* number of bindings */
  209.  
  210. /* for file_kind { NOT_EXIST, REGFILE, DIRECTORY, SLNKFILE, SLNKDIR } */
  211. #define NOT_EXIST    0
  212. #define REGFILE        (1 << 0)
  213. #define DIRECTORY    (1 << 1)
  214. #define SLNKFILE    (1 << 2)
  215. #define SLNKDIR        (1 << 3)
  216.  
  217. /* for directory_cache.savable { FALSE, TRUE, UNKNOWN } */
  218. #define UNKNOWN        2
  219.  
  220. /* for selection_kind { FALSE, SELECTION_IMAGE, SELECTION_DIRECTORY } */
  221. #define    SELECTION_IMAGE    1
  222. #define SELECTION_DISPLAY 2
  223.  
  224. /* for sensives { SENSITIVE_SELECTION, SENSITIVE_SCROLL } */
  225. #define SENSITIVE_SELECTION    (1 << 0)
  226. #define SENSITIVE_SCROLL    (1 << 1)
  227.  
  228. /* getters and setters for misc attributes for structs */
  229. #define INITIALIZE_ATTRIBUTES(obj)    ((obj)->attributes = 0)
  230. /* assumed each attribute is represented by 1 bit. bogus. */
  231. #define GET_ATTRIBUTE(obj, key)        (((obj)->attributes & (key)) >> (key))
  232. #define HAS_ATTRIBUTE(obj, key)        ((obj)->attributes & (key))
  233. #define HAS_NO_ATTRIBUTE(obj, key)    (! ((obj)->attributes & (key)))
  234. #define SET_ATTRIBUTE(obj, key)        ((obj)->attributes |= (key))
  235. #define RESET_ATTRIBUTE(obj, key)    ((obj)->attributes &= ~(key))
  236. #define SET_TO_ATTRIBUTE(obj, key, val)    ((val) ? SET_ATTRIBUTE(obj, key) : RESET_ATTRIBUTE(obj, key))
  237. #define COPY_ATTRIBUTE(dest, key, src)    (SET_TO_ATTRIBUTE(dest, key, HAS_ATTRIBUTE(src, key)))
  238. #define TOGGLE_ATTRIBUTE(obj, key)    (SET_TO_ATTRIBUTE(obj, key, HAS_NO_ATTRIBUTE(obj, key)))
  239.  
  240. /* attributes for VAL */
  241. #define DISPLAY_IMAGE_P        (1 << 0)
  242. #define SORT_BY_NAME_P        (1 << 1)
  243. /* SAVE_AS_XVPICT_P should be used only for save thumbnail.
  244.    And don't use to determine whether thumbnails were saved */
  245. #define SAVE_AS_XVPICT_P    (1 << 2)
  246. #define SAVE_AS_GPICT_P        (1 << 3)
  247. #define CONFIRM_P        (1 << 4)
  248. #define MONITOR_P        (1 << 5)
  249.  
  250. typedef struct
  251. {
  252.   guint    attributes;
  253.   GCHAR last_dir_name[PATH_LENGTH];
  254.   GCHAR mapping_command[LINE_BUF_SIZE];
  255. } VALS;
  256.  
  257. typedef struct
  258. {
  259.   guchar    *data;
  260.   gint        width;
  261.   gint        height;
  262.   gint        ch;
  263.   gint        size;
  264. } image_buffer;
  265.  
  266. /* attributes for Thumbnail */
  267. #define DIRECTORY_P        (1 << 0)
  268. #define SYMLINK_P        (1 << 1)
  269. #define DELETED_P        (1 << 2)
  270. #define PARENT_DIRECTORY_P    (1 << 3)
  271. #define SELECTED_P        (1 << 4)
  272.  
  273. typedef struct
  274. {
  275.   guint        attributes;        /* 5 collective attributes above */
  276.   image_buffer    *image;
  277.   GCHAR        *name;
  278.   GCHAR        *info;
  279.   gint        selection_timestamp;
  280. } Thumbnail;
  281.  
  282. typedef struct
  283. {
  284.   GdkPixmap    *pixmap;
  285.   guchar    *line_buffer;
  286.   gint        width;
  287.   gint        height;
  288.   GdkGC        *gc;
  289.   GdkColor    black;
  290.   GdkColor    white;
  291. } Pixmap_struct;
  292.  
  293. /* attributes for directory_cache */
  294. /* #define DISPLAY_IMAGE_P    shared with VALS */
  295. /* #define SORT_BY_NAME_P    shared with VALS */
  296. #define FILED_P            (1 << 2)
  297. #define DISORDERED_P        (1 << 3)
  298.  
  299. typedef struct
  300. {
  301.   GCHAR        name[PATH_LENGTH];
  302.   guint        attributes;    /* filed and purged */
  303.   gint        birth_index;
  304.   gshort    savable;    /* three values */
  305.   Thumbnail    *image;
  306.   Thumbnail    *dir;
  307.   gint        ndir;
  308.   gint        nimage;
  309.   gint        max_ndir;
  310.   gint        max_nimage;
  311.   gint        display_page;
  312.   gint        selection_timestamp;
  313.   gint        num_selection;
  314.   gint        selection_top;
  315.   gint        last_focus;
  316.   gdouble    timestamp;
  317. } directory_cache;
  318.  
  319. typedef struct
  320. {
  321.   GCHAR    key;
  322.   guint    mod;
  323. } Gtk_binding;
  324.  
  325. typedef struct
  326. {
  327.   GCHAR        *label;
  328.   void        (* command) (GtkWidget *, gpointer);
  329.   gint        sensitive;
  330.   GtkWidget    *widget;
  331.   Gtk_binding    binding[NBINDINGS];
  332. } image_command_table;
  333.  
  334. typedef struct
  335. {
  336.   GCHAR    *action;
  337.   GCHAR    *condition;
  338.   GCHAR    *behavior;
  339.   gint    flush_left;
  340. } help_table;
  341.  
  342. struct
  343. {
  344.   gint        width;
  345.   gint        height;
  346.   gint        ncol;
  347.   gint        nrow;
  348.   gint        nthumb_in_page;
  349.   gint        resized;
  350.   gint        current_page1;
  351.   gint        current_page;
  352.   gint        last_index;
  353.   gint        font_height;
  354.   GCHAR        info[256];
  355. } the_panel;
  356.  
  357. typedef struct
  358. {
  359.   gint        index;
  360.   gint        selection_timestamp;
  361.   Thumbnail    *thumbnail;
  362. } selection_iterator_entry;
  363.  
  364. typedef struct
  365. {
  366.   gint                length;
  367.   gint                unvisit_index;
  368.   selection_iterator_entry    *body;
  369. } selection_iterator;
  370.  
  371. VALS        VAL;
  372. directory_cache *cwd_cache = NULL;
  373. Thumbnail    *the_loaded_data = NULL;
  374. image_buffer    *banner;
  375. image_buffer    *dir_icon;
  376. image_buffer    *pdir_icon;
  377. image_buffer    *file_icon;
  378. Pixmap_struct    *text_buffer = NULL;
  379. GHashTable    *directory_cache_table;
  380. gint        directory_cache_max_images = 1024;
  381. gint        directory_cache_table_max_size = DIRECTORY_CACHE_TABLE_MAX_SIZE;
  382. gint        directory_cache_table_size = 0;
  383. GtkWidget    *dlg = NULL;
  384. GtkWidget    *cwd_label = NULL;
  385. GtkWidget    *thumbnail_panel = NULL;
  386. GtkWidget    *file_property = NULL;
  387. GtkWidget    *thumbnail_panel_root_menu = NULL;
  388. GtkWidget    *thumbnail_panel_selection_menu = NULL;
  389. GtkWidget    *thumbnail_panel_hidden_menu = NULL;
  390. GCHAR        **inhibit_suffix_table = NULL;
  391. gint        num_inhibit_suffix = -1;
  392. gint        selection_kind = FALSE;
  393. guchar        thumbnail_colormap[256 * 3];
  394. GCHAR        fileselector_last_pathname[PATH_LENGTH];
  395. gint        guash_is_initialized = FALSE;
  396. GCHAR        *guash_tmp_dir = NULL;
  397. gint        binding_style = BINDING_GIMP;
  398. gint        directory_monitor_interval = 0;
  399. guint        delayed_updating = 0; /* This is not a flag: a kind of countable semaphore */
  400. guint        during_buildup_thumbnail_panel = FALSE;
  401.  
  402. #define NSCROLLER    2
  403. GtkWidget    *widget_for_scroll[NSCROLLER];
  404.  
  405. #ifdef NEW_DND_API
  406. enum {
  407.   TARGET_STRING,
  408. };
  409.  
  410. static GtkTargetEntry dnd_target_table[] =
  411. {
  412.   { "STRING", 0, TARGET_STRING },
  413.   { "text/plain", 0, TARGET_STRING },
  414. };
  415. static guint dnd_n_targets = sizeof(dnd_target_table) / sizeof(dnd_target_table[0]);
  416. #else
  417. gchar        *possible_dnd_types[] = {"text/plain"};
  418. #endif
  419.  
  420. #define    NSELECTION_BUTTON    2
  421. GtkWidget    *widget_for_selecion[NSELECTION_BUTTON];
  422.  
  423. /* //HB: the old version of the macro. The new one uses INDEXED images
  424.  * and a shared color map, defined in data/guash-banner.h
  425.  */
  426. #ifndef INDEXED_HEADER
  427. #define HEADER_PIXEL(data,pixel) {\
  428.   pixel[0] = (((data[0] - 33) << 2) | ((data[1] - 33) >> 4)); \
  429.   pixel[1] = ((((data[1] - 33) & 0xF) << 4) | ((data[2] - 33) >> 2)); \
  430.   pixel[2] = ((((data[2] - 33) & 0x3) << 6) | ((data[3] - 33))); \
  431.   data += 4; }
  432. #endif
  433.  
  434. #include "include/guash-f.h"
  435.  
  436. #define MENU_SEPARATOR   { NULL, NULL, 0, NULL, \
  437.                   { { 0, 0 }, \
  438.                   { 0, 0 } \
  439.               } }
  440.  
  441. image_command_table
  442. image_root_commands [] =
  443. {
  444.   { "Next page", next_page_callback, SENSITIVE_SCROLL, NULL,
  445.     { { ' ', 0 },
  446.       { ' ', 0 }
  447.     } },
  448.   { "Prev page", prev_page_callback, SENSITIVE_SCROLL, NULL,
  449.     { { 'B', 0 },
  450.       { 'B', 0 }
  451.     } },
  452.   { "Selection All", select_all_callback, FALSE, NULL,
  453.     { { 'A', GDK_CONTROL_MASK },
  454.       { 'A', GDK_CONTROL_MASK }
  455.     } },
  456.   MENU_SEPARATOR,
  457.   { "Make new directory", fileselector_for_mkdir_callback, FALSE, NULL,
  458.     { { 'D', GDK_SHIFT_MASK },
  459.       { '+', GDK_SHIFT_MASK }
  460.     } },
  461.   { "Change directory", fileselector_for_chdir_callback, FALSE, NULL,
  462.     { { 'C', 0 },
  463.       { 'D', GDK_CONTROL_MASK }
  464.     } },
  465.   { "Change to parent directory", parent_directory_callback, FALSE, NULL,
  466.     { { '0', 0 },
  467.       { '0', 0 }
  468.     } },
  469.   { "Update", update_callback, FALSE, NULL,
  470.     { { 'R', 0 },
  471.       { 'G', 0 }
  472.     } },
  473.   { "Change display mode", toggle_display_mode_callback, FALSE, NULL,
  474.     { { 'S', GDK_SHIFT_MASK },
  475.       { 'S', GDK_SHIFT_MASK }
  476.     } },
  477.   { "Change thumbnail save mode", toggle_save_mode_callback, FALSE, NULL,
  478.     { { 'T', 0 },
  479.       { 'T', 0 }
  480.     } },
  481.   { "Change sort mode", toggle_sort_mode_callback, FALSE, NULL,
  482.     { { 'S', 0 },
  483.       { 'S', 0 }
  484.     } },
  485.   { "Remove all thumbnail files", purge_thumbnail_file_callback, FALSE, NULL,
  486.     { { 'R', GDK_MOD1_MASK },
  487.       { 'P', GDK_MOD1_MASK }
  488.     } },
  489.   MENU_SEPARATOR,
  490.   { "Help", help_callback, FALSE, NULL,
  491.     { { '?', 0 },
  492.       { 'H', GDK_CONTROL_MASK }
  493.     } },
  494.   { "Quit", gtkW_close_callback, FALSE, NULL,
  495.     { { 'Q', 0 },
  496.       { 'Q', GDK_CONTROL_MASK }
  497.     } }
  498. };
  499.  
  500. image_command_table
  501. image_selection_commands [] =
  502. {
  503.   { "Open", open_callback, SENSITIVE_SELECTION, NULL,
  504.     { { 'O', GDK_CONTROL_MASK },
  505.       { 'O', GDK_CONTROL_MASK }
  506.     } },
  507. #ifdef GIMP_HAVE_PARASITES
  508.   { "Show comment", show_comment_callback, SENSITIVE_SELECTION, NULL,
  509.     { { 'C', GDK_SHIFT_MASK },
  510.       { 'C', GDK_SHIFT_MASK }
  511.     } },
  512. #endif
  513.   { "Selection None", select_none_callback, FALSE, NULL,
  514.     { { 'A', GDK_CONTROL_MASK|GDK_SHIFT_MASK },
  515.       { 'A', GDK_CONTROL_MASK|GDK_SHIFT_MASK }
  516.     } },
  517.   MENU_SEPARATOR,
  518.   { "Copy to...", fileselector_for_copy_callback, SENSITIVE_SELECTION, NULL,
  519.     { { 'C', GDK_CONTROL_MASK },
  520.       { 'C', GDK_CONTROL_MASK }
  521.     } },
  522.   { "Move to...", fileselector_for_move_callback, SENSITIVE_SELECTION, NULL,
  523.     { { 'V', GDK_CONTROL_MASK },
  524.       { 'R', GDK_CONTROL_MASK }
  525.     } },
  526.   { "Map script-fu on...", selection_map_script_callback, SENSITIVE_SELECTION, NULL,
  527.     { { 'X', 0 },
  528.       { '!', GDK_SHIFT_MASK }
  529.     } },
  530.   { "Map unix command on...", selection_map_unix_command_callback, SENSITIVE_SELECTION, NULL,
  531.     { { 'X', GDK_SHIFT_MASK },
  532.       { '|', GDK_SHIFT_MASK }
  533.     } },
  534.   { "Delete the thumbnail files", purge_selected_thumbnail_file_callback, SENSITIVE_SELECTION, NULL,
  535.     { { 'T', GDK_CONTROL_MASK },
  536.       { 'T', GDK_MOD1_MASK }
  537.     } },
  538.   MENU_SEPARATOR,
  539.   { "Root menu", NULL, FALSE, NULL,
  540.     { { 0, 0 },
  541.       { 0, 0 }
  542.     } },
  543.   MENU_SEPARATOR,
  544.   { "Delete the selected files", delete_callback, SENSITIVE_SELECTION, NULL,
  545.     { { 'X', GDK_CONTROL_MASK },
  546.       { 'D', GDK_MOD1_MASK }
  547.     } }
  548. };
  549.  
  550. image_command_table
  551. image_hidden_commands [] =
  552. {
  553.   { "Forward image", forward_callback, FALSE, NULL,
  554.     { { 'F', 0 },
  555.       { 'F', GDK_CONTROL_MASK }
  556.     } },
  557.   { "Backward image", backward_callback, FALSE, NULL,
  558.     { { 'B', 0 },
  559.       { 'B', GDK_CONTROL_MASK }
  560.     } },
  561.   { "Down image", next_callback, FALSE, NULL,
  562.     { { 'D', 0 },
  563.       { 'N', GDK_CONTROL_MASK }
  564.     } },
  565.   { "Up image", prev_callback, FALSE, NULL,
  566.     { { 'U', 0 },
  567.       { 'P', GDK_CONTROL_MASK }
  568.     } },
  569.   { "Select then go forward", select_and_forward_callback, FALSE, NULL,
  570.     { { ' ', GDK_CONTROL_MASK },
  571.       { ' ', GDK_CONTROL_MASK }
  572.     } },
  573.   { "Jump to subdirectory1", jump_to_subdir1_callback, FALSE, NULL,
  574.     { { '1', 0 },
  575.       { '1', 0 }
  576.     } },
  577.   { "Jump to subdirectory2", jump_to_subdir2_callback, FALSE, NULL,
  578.     { { '2', 0 },
  579.       { '2', 0 }
  580.     } },
  581.   { "Jump to subdirectory3", jump_to_subdir3_callback, FALSE, NULL,
  582.     { { '3', 0 },
  583.       { '3', 0 }
  584.     } },
  585.   { "Jump to subdirectory4", jump_to_subdir4_callback, FALSE, NULL,
  586.     { { '4', 0 },
  587.       { '4', 0 }
  588.     } },
  589.   { "Jump to subdirectory5", jump_to_subdir5_callback, FALSE, NULL,
  590.     { { '5', 0 },
  591.       { '5', 0 }
  592.     } },
  593.   { "Jump to subdirectory6", jump_to_subdir6_callback, FALSE, NULL,
  594.     { { '6', 0 },
  595.       { '6', 0 }
  596.     } },
  597.   { "Jump to subdirectory7", jump_to_subdir7_callback, FALSE, NULL,
  598.     { { '7', 0 },
  599.       { '7', 0 }
  600.     } },
  601.   { "Jump to subdirectory8", jump_to_subdir8_callback, FALSE, NULL,
  602.     { { '8', 0 },
  603.       { '8', 0 }
  604.     } },
  605.   { "Jump to subdirectory9", jump_to_subdir9_callback, FALSE, NULL,
  606.     { { '9', 0 },
  607.       { '9', 0 }
  608.     } }
  609. };
  610. #undef MENU_SEPARATOR
  611.  
  612. #define DOCUMENT_SEPARATOR    { NULL, NULL, NULL, FALSE }
  613. #define CENTERING    FALSE
  614. #define ALIGN_LEFT    TRUE
  615.  
  616. help_table
  617. help_document [] =
  618. {
  619.   { NULL, "Gimp Users' Another SHell", NULL, CENTERING},
  620.   { NULL, GUASH_VERSION, NULL, CENTERING},
  621.   DOCUMENT_SEPARATOR,
  622.   { "ACTION", "CONDITION", "COMMAND", CENTERING },
  623.   /* -------------------- */
  624.   { "Left-click", "on unselected image file",
  625.     "Select only the image file", ALIGN_LEFT },
  626.   { "Left-click", "on selected image file",
  627.     "Open the selected image files", ALIGN_LEFT },
  628.   { "Left-click", "on directory",
  629.     "Jump to the directory", ALIGN_LEFT },
  630.   { "Shift + Left-click", "on image file",
  631.     "Add/delete the file to/from selection", ALIGN_LEFT },
  632.   { "Control + Left-click", "on directory & active selection",
  633.     "Copy selected files to the directory", ALIGN_LEFT },
  634.   { "Shift + Left-click", "on directory & active selection",
  635.     "Move selected files to the directory", ALIGN_LEFT },
  636.   { "Middle-click", NULL,
  637.     "Go to the next page of panel", ALIGN_LEFT },
  638.   { "Shift + Middle-click", NULL,
  639.     "Go to the pevious page of panel", ALIGN_LEFT },
  640.   { "Right-click", "no selection",
  641.     "Open root menu", ALIGN_LEFT },
  642.   { "Right-click", "active selection",
  643.     "Open selection menu", ALIGN_LEFT },
  644.   { "Shift + Right-click", NULL,
  645.     "Open root menu", ALIGN_LEFT },
  646.   DOCUMENT_SEPARATOR,
  647.   { NULL, "You can set options through your gimprc file like:", NULL, CENTERING },
  648.   { NULL, "(guash-option-string \"NEIXNA\")   ; default is \"CGIXNM\"", NULL, CENTERING },
  649.   { NULL, "Each character means (do Confirm/Not), (key-binding Gimp/Emacs), (show Images/Files), ", NULL, CENTERING },
  650.   { NULL, "(thumbnail Xv/Guash/Unsave), (sort Name/Date), (update Automatically/Manually) respectively.", NULL, CENTERING }
  651. };
  652.  
  653. #undef DOCUMENT_SEPARATOR
  654. #undef CENTERING
  655. #undef ALIGN_LEFT
  656.  
  657. GPlugInInfo PLUG_IN_INFO =
  658. {
  659.   NULL,                /* init_proc  */
  660.   NULL,                /* quit_proc */
  661.   query,            /* query_proc */
  662.   run,                /* run_proc */
  663. };
  664.  
  665. MAIN ()
  666.  
  667. static void
  668. query ()
  669. {
  670.  /* NOTE: `[] = {}' is illegal on MIPS C++ */
  671.  
  672.   static GParamDef eargs [] =
  673.   {
  674.     { PARAM_INT32, "run_mode", "Interactive, non-interactive"},
  675.     { PARAM_STRING, "directory_name", "directory name used as the default directory" },
  676.   };
  677.   static GParamDef pargs [] =
  678.   {
  679.     { PARAM_INT32, "run_mode", "Interactive, non-interactive"},
  680.     { PARAM_IMAGE, "image", "Input image" },
  681.     { PARAM_DRAWABLE, "drawable", "drawable (unused)" },
  682.   };
  683.   static GParamDef *return_vals = NULL;
  684.  
  685.   static GParamDef sargs [] =
  686.   {
  687.     { PARAM_STRING, "file_name", "the filename that defines selection for script-fu"},
  688.   };
  689.   static GParamDef srets [] =
  690.   {
  691.     { PARAM_STRING, "file_name", "the filename that defines selection for script-fu"},
  692.   };
  693.  
  694.   static int neargs = sizeof (eargs) / sizeof (eargs[0]);
  695.   static int nreturn_vals = 0;
  696.   static int npargs = sizeof (pargs) / sizeof (pargs[0]);
  697.   static int nsargs = sizeof (sargs) / sizeof (sargs[0]);
  698.   static int nsrets = sizeof (srets) / sizeof (srets[0]);
  699.  
  700.   gimp_install_procedure (PLUG_IN_NAME,
  701.               "Thumbnail-based directory browser",
  702.               "Thumbnail-based directory browser",
  703.               "Shuji Narazaki <narazaki@gimp.org>",
  704.               "Shuji Narazaki",
  705.               "1997, 1998",
  706.               MENU_PATH_AS_EXTENSION,
  707.               "",
  708.               PROC_EXTENSION,
  709.               neargs, nreturn_vals,
  710.               eargs, return_vals);
  711.   gimp_install_procedure (PLUG_IN_NAME_SUB,
  712.               "Thumbnail-based directory browser",
  713.               "Thumbnail-based directory browser",
  714.               "Shuji Narazaki <narazaki@gimp.org>",
  715.               "Shuji Narazaki",
  716.               "1997, 1998",
  717.               MENU_PATH_AS_PLUG_IN,
  718.               "",
  719.               PROC_PLUG_IN,
  720.               npargs, nreturn_vals,
  721.               pargs, return_vals);
  722.   gimp_install_procedure (PLUG_IN_NAME_FOR_SELECTION,
  723.               "Return the name of the file that defines %guash-selection",
  724.               "Return the name of the file that defines %guash-selection",
  725.               "Shuji Narazaki <narazaki@gimp.org>",
  726.               "Shuji Narazaki",
  727.               "1997, 1998",
  728.                   NULL,
  729.               "",
  730.               PROC_PLUG_IN,
  731.               nsargs, nsrets,
  732.               sargs, srets);
  733. }
  734.  
  735. static void
  736. run (char    *name,
  737.      int    nparams,
  738.      GParam    *param,
  739.      int    *nreturn_vals,
  740.      GParam    **return_vals)
  741. {
  742.   GParam    *values;
  743.   GStatusType    status = STATUS_EXECUTION_ERROR;
  744.   GRunModeType    run_mode;
  745.  
  746.   DEBUGBLOCK (N_("run\n"));
  747.  
  748.   if (strcmp (name, PLUG_IN_NAME_FOR_SELECTION) == 0)
  749.     {
  750.       GCHAR    tmp[128];
  751.       values = g_new (GParam, 2);
  752.       *nreturn_vals = 2;
  753.       *return_vals = values;
  754. #if 0
  755.       strcpy (tmp, "/tmp/guash0000.scm");
  756. #else
  757.       /* //HB: use glib function for portability */
  758.       strcpy (tmp, g_get_tmp_dir ());
  759.       strcat (tmp, G_DIR_SEPARATOR_S "guash0000.scm");
  760. #endif
  761.       /* gimp_get_data (PLUG_IN_NAME_FOR_SELECTION, &tmp); */
  762.       values[0].type = PARAM_STATUS;
  763.       values[0].data.d_status = STATUS_SUCCESS;
  764.       values[1].type = PARAM_STRING;
  765.       strcpy (tmp, param[0].data.d_string);
  766.       values[1].data.d_string = g_strdup (tmp);
  767.       DPRINT (N_("delete %s\n"), tmp);
  768.       os_delete_file (tmp);
  769.       RETURN;
  770.     }
  771.  
  772.   values = g_new (GParam, 1);
  773.   run_mode = param[0].data.d_int32;
  774.  
  775.   *nreturn_vals = 1;
  776.   *return_vals = values;
  777.  
  778.   values[0].type = PARAM_STATUS;
  779.   values[0].data.d_status = status;
  780.  
  781.   DPRINT (N_("guash is just started.\n"));
  782. #if defined(_DEBUG) && defined(G_OS_WIN32)
  783.   g_warning("Guash needs DEBUGGING!");
  784.   {
  785.     int i;
  786.     for (i = 0; i < 20; i ++)
  787.     {
  788.       Sleep(1000);
  789.       g_print(".");
  790.     }
  791.   }
  792. #endif
  793.   switch (run_mode)
  794.     {
  795.     case RUN_INTERACTIVE:
  796.     case RUN_WITH_LAST_VALS:
  797.       SET_ATTRIBUTE (&VAL, DISPLAY_IMAGE_P);
  798.       SET_ATTRIBUTE (&VAL, SAVE_AS_XVPICT_P);
  799.       SET_ATTRIBUTE (&VAL, SAVE_AS_GPICT_P);
  800.       SET_ATTRIBUTE (&VAL, SORT_BY_NAME_P);
  801.       VAL.mapping_command[0] = VAL.last_dir_name[0] = 0;
  802.       gimp_get_data (PLUG_IN_NAME, &VAL);
  803.  
  804.       if (strcmp (name, PLUG_IN_NAME_SUB) == 0)
  805.     {
  806.       GCHAR    *str = guash_get_image_filename (param[1].data.d_image);
  807.  
  808.       if (str)
  809.         {
  810.           GCHAR    *dir = pathname_get_directoryname (str);
  811.  
  812.           if (dir)
  813.         {
  814.           strcpy (VAL.last_dir_name, dir);
  815.           g_free (dir);
  816.         }
  817.           g_free (str);
  818.         }
  819.     }
  820.  
  821.       directory_cache_table = g_hash_table_new ((GHashFunc) g_str_hash,
  822.                         (GCompareFunc) g_str_equal);
  823.       if (0 < strlen (VAL.last_dir_name))
  824.     os_file_change_current_directory (VAL.last_dir_name);
  825.  
  826.       dir_icon = image_buffer_new_with_header (dir_icon_data,
  827.                            dir_icon_width,
  828.                            dir_icon_height);
  829.       pdir_icon = image_buffer_new_with_header (pdir_icon_data,
  830.                         pdir_icon_width,
  831.                         pdir_icon_height);
  832.       file_icon = image_buffer_new_with_header (file_icon_data,
  833.                         file_icon_width,
  834.                         file_icon_height);
  835.  
  836.       DPRINT (N_("icons are set\n"));
  837.       /* Let's set rational options of ps file hander for guash. */
  838.       {
  839.     GParam*        return_vals;
  840.     gint        retvals;
  841.     /* //HB: ... but first query if available */
  842.         char *proc_blurb, *proc_help, *proc_author, *proc_copyright, *proc_date;
  843.         int proc_type, nparams, nreturn_vals;
  844.         GParamDef *params1, *params2;
  845.  
  846.     if (gimp_query_procedure("file_ps_load_setargs",
  847.                               &proc_blurb, &proc_help, 
  848.                   &proc_author, &proc_copyright, &proc_date,
  849.                   &proc_type, &nparams, &nreturn_vals,
  850.                   ¶ms1, ¶ms2)) {
  851.       /* now dismiss unneded information; wouldn't it be nice 
  852.            * to don't get unneeded by setting NULL pointers?
  853.        */
  854.       g_free(proc_blurb);
  855.       g_free(proc_help);
  856.       g_free(proc_author);
  857.       g_free(proc_date);
  858.       gimp_destroy_paramdefs (params1, nparams);
  859.  
  860.       if (nreturn_vals && return_vals[0].data.d_status == STATUS_SUCCESS) {
  861.         /* first destruct old parameters */
  862.         gimp_destroy_paramdefs (params2, nreturn_vals);
  863.  
  864.         /* finally run file_ps_load */
  865.  
  866.         return_vals = gimp_run_procedure ("file_ps_load_setargs",
  867.                       &retvals,
  868.                       PARAM_INT32, 75, /* resolution */
  869.                       PARAM_INT32, THUMBNAIL_WIDTH,
  870.                       PARAM_INT32, THUMBNAIL_HEIGHT,
  871.                       PARAM_INT32, 1, /* use BBox */
  872.                       PARAM_STRING, "1", /* only 1 page */
  873.                       PARAM_INT32, 7, /* automatic color */
  874.                       PARAM_INT32, 1, /* text alpha */
  875.                       PARAM_INT32, 1,
  876.                       PARAM_END);
  877.         gimp_destroy_params (return_vals, retvals);
  878.       }
  879.     }
  880.       }
  881.  
  882.       DPRINT (N_("open dialog\n"));
  883.       DIALOG ();
  884.       DPRINT (N_("success to close the dialog\n"));
  885.  
  886.       gimp_displays_flush();
  887.       if (strcmp (name, PLUG_IN_NAME) == 0)
  888.     gimp_set_data (PLUG_IN_NAME, &VAL, sizeof (VALS));
  889.       status = STATUS_SUCCESS;
  890.       break;
  891.     case RUN_NONINTERACTIVE:
  892.       break;
  893.     }
  894.  
  895.   g_hash_table_destroy (directory_cache_table);
  896.  
  897.   image_buffer_free (banner);
  898.   image_buffer_free (dir_icon);
  899.   image_buffer_free (pdir_icon);
  900.   image_buffer_free (file_icon);
  901.  
  902.   g_free (inhibit_suffix_table);
  903.  
  904.   DPRINT (N_("success to free memory chunks\n"));
  905.  
  906.   values[0].type = PARAM_STATUS;
  907.   values[0].data.d_status = status;
  908.  
  909.   DEBUGEND;
  910. }
  911.  
  912. static guchar *
  913. indexed_to_rgb (guchar data, guchar *dest)
  914. {
  915.   guchar *color = thumbnail_colormap + (data * 3);
  916.  
  917.   *dest++ = *color++;
  918.   *dest++ = *color++;
  919.   *dest++ = *color;
  920.  
  921.   return dest;
  922. }
  923.  
  924. /*
  925.  * guash classes for packing misc functions
  926.  */
  927. static void
  928. guash_build_thumbnail_colormap ()
  929. {
  930.   gint        i;
  931.   guchar    *ptr = thumbnail_colormap;
  932.  
  933.   for (i = 0; i < 256; i++)
  934.     {
  935.       *ptr++ = (((i >> 5) & 0x07) * 255) / 7;
  936.       *ptr++ = (((i >> 2) & 0x07) * 255) / 7;
  937.       *ptr++ = (((i >> 0) & 0x03) * 255) / 3;
  938.     }
  939. }
  940.  
  941. static void
  942. guash_build_inhibit_suffix_table (GCHAR *buffer)
  943. {
  944.   GCHAR    *ptr = buffer;
  945.   GCHAR    **chunk;
  946.   gint    chunk_index = 0;
  947.   gint    chunk_size = 8;
  948.  
  949.   DEBUGBLOCK (N_("guash_build_inhibit_suffix_table (\"%s\")\n"), buffer);
  950.  
  951.   chunk = g_new (GCHAR *, chunk_size);
  952.  
  953.   while (*ptr != 0)
  954.     {
  955.       gint    index = 0;
  956.       GCHAR    *new_str;
  957.  
  958.       while ((*(ptr + index) != ':') && (*(ptr + index) != 0))
  959.     index++;
  960.  
  961.       if (0 < index)
  962.     {
  963.       new_str = g_malloc (index + 1); /*  end of str */
  964.       g_memmove (new_str, ptr, index);
  965.       new_str[index] = 0;
  966.  
  967.       if (chunk_size <= chunk_index)
  968.         {
  969.           DPRINT (N_("Exapnd chunk(%s) size: %d..."), chunk[chunk_index -1], chunk_size);
  970.           chunk_size *= 2;
  971. #ifdef GIMP_HAVE_PARASITES
  972.           /* This condition is not true one. I'd like to check glib
  973.          version. */
  974.           chunk = g_renew (GCHAR *, chunk, chunk_size);
  975. #else
  976.           chunk = g_realloc ((gpointer) chunk,
  977.                  chunk_size * sizeof (GCHAR *));
  978. #endif
  979.           DPRINT (N_("done\n"));
  980.         }
  981.       chunk[chunk_index++] = new_str;
  982.     }
  983.       if (*(ptr + index) == 0)
  984.     break;
  985.       else
  986.     ptr += index + 1;
  987.     }
  988.  
  989.   inhibit_suffix_table = chunk;
  990.   num_inhibit_suffix = chunk_index;
  991.  
  992.   RETURN;
  993. }
  994.  
  995. static gint
  996. guash_change_current_directory (GCHAR *pathname, Thumbnail *thumbnail)
  997. {
  998.   DEBUGBLOCK (N_("guash_change_directory (\"%s\")\n"), pathname);
  999.  
  1000.   if (-1 == os_file_change_current_directory (pathname))
  1001.     {
  1002.       GCHAR    tmp[LINE_BUF_SIZE];
  1003.  
  1004.       if (thumbnail != NULL)
  1005.     {
  1006.       cwd_cache->timestamp = os_file_get_modify_timestamp (cwd_cache->name);
  1007.       SET_ATTRIBUTE (thumbnail, DELETED_P);
  1008.       guash_update_cwd_cache (GC);
  1009.     }
  1010.       sprintf (tmp, "Abort: %s does not exist now", pathname);
  1011.       thumbnail_panel_set_info (tmp);
  1012.       RETURN FALSE;
  1013.     }
  1014.   else
  1015.     {
  1016.       guash_update_cwd_cache (REDRAW);
  1017.       RETURN TRUE;
  1018.     }
  1019. }
  1020.  
  1021. static gint
  1022. guash_confirm_operation (GCHAR *operation_phrase)
  1023. {
  1024.   gint    flag = TRUE;
  1025.  
  1026.   if (! selection_is_active ())
  1027.     return flag;
  1028.  
  1029.   G_ASSERT (HAS_ATTRIBUTE (directory_cache_get_nth (cwd_cache,
  1030.                             cwd_cache->selection_top),
  1031.                SELECTED_P));
  1032.  
  1033.   if (HAS_ATTRIBUTE (&VAL, CONFIRM_P))
  1034.     {
  1035.       GCHAR buffer [LINE_BUF_SIZE];
  1036.  
  1037.       if (selection_length () == 1)
  1038.     {
  1039.       Thumbnail    *selected;
  1040.  
  1041.       selected = directory_cache_get_nth (cwd_cache, cwd_cache->selection_top);
  1042.       sprintf (buffer, _(" %s the selected image: %s? \n\
  1043.  Guash is NO WARRANTY program. "),
  1044.            operation_phrase,
  1045.            selected->name);
  1046.     }
  1047.       else
  1048.     {
  1049.       sprintf (buffer, _(" %s the selected %d images? \n\
  1050.  Guash is NO WARRANTY program. "),
  1051.            operation_phrase,
  1052.            selection_length ());
  1053.     }
  1054.  
  1055.       if (! gtkW_confirmor_dialog (TRUE, buffer, FALSE))
  1056.     flag = FALSE;
  1057.     }
  1058.  
  1059.   return flag;
  1060. }
  1061.  
  1062. static gint
  1063. guash_discard_events ()
  1064. {
  1065.   gint        invalids = FALSE;
  1066.   gint        redraw = FALSE;
  1067.   GdkEvent    *e;
  1068.  
  1069.   DEBUGBLOCK (N_("guash_discard_events\n"));
  1070.   /*
  1071.    * Tue May 12 20:49:27 1998
  1072.    * gtk+ FAQ uses gtk_events_pending (), but it returns 1 anytime.
  1073.    * On the other hand, gdk_events_pending () returns zero if no events!
  1074.    * That's what I want!
  1075.    */
  1076.   while (gdk_events_pending ())
  1077.     {
  1078.       if ((e = gdk_event_get ()) != NULL)
  1079.     {
  1080.       switch (e->type)
  1081.         {
  1082.         case GDK_BUTTON_PRESS:
  1083.         case GDK_2BUTTON_PRESS:
  1084.         case GDK_3BUTTON_PRESS:
  1085.         case GDK_KEY_PRESS:
  1086.           invalids = TRUE;
  1087.           gdk_event_free (e);
  1088.           break;
  1089.         case GDK_EXPOSE:
  1090.           redraw = TRUE;
  1091.           gdk_event_free (e);
  1092.         default:
  1093.           gtk_main_iteration ();
  1094.           break;
  1095.         }
  1096.     }
  1097.     }
  1098.   if (redraw)
  1099.     gtk_widget_queue_draw (dlg);
  1100.  
  1101.   RETURN invalids;
  1102. }
  1103.  
  1104. static GCHAR *
  1105. guash_generate_unix_command (GCHAR *template, GCHAR *directory, GCHAR *filename)
  1106. {
  1107.   GCHAR    *new = NULL, *tmp;
  1108.   gint    index = -1;
  1109.   gint    directory_p = FALSE;
  1110.   gint    i = 0;
  1111.  
  1112.   for (tmp = template; *tmp; tmp++, i++)
  1113.     {
  1114.       if ((*tmp == '{') && (*(tmp + 1) == '}'))
  1115.     {
  1116.       directory_p = FALSE;
  1117.       index = i;
  1118.       break;
  1119.     }
  1120.       if ((*tmp == '%') && (*(tmp + 1) == '%'))
  1121.     {
  1122.       directory_p = TRUE;
  1123.       index = i;
  1124.       break;
  1125.     }
  1126.     }
  1127.   if (-1 < index)
  1128.     {
  1129.       if (directory_p)
  1130.     new = g_malloc (strlen (template) + strlen (directory) + 1);
  1131.       else
  1132.     new = g_malloc (strlen (template)
  1133.             + strlen (directory) + strlen (filename) + 1);
  1134.       i = index;
  1135.       g_memmove (new, template, i);
  1136.       if (directory_p)
  1137.     {
  1138.       g_memmove (new + i, directory, strlen (directory));
  1139.       i += strlen (directory);
  1140.     }
  1141.       else
  1142.     {
  1143.       g_memmove (new + i, filename, strlen (filename));
  1144.       i += strlen (filename);
  1145.     }
  1146.       g_memmove (new + i, template + index + 2, strlen (template) - index - 2);
  1147.       i += strlen (template) - index - 2;
  1148.       new[i] = 0;
  1149.  
  1150.       tmp = guash_generate_unix_command (new, directory, filename);
  1151.       g_free (new);
  1152.       return tmp;
  1153.     }
  1154.   else
  1155.     {
  1156.       return g_strdup (template);
  1157.     }
  1158. }
  1159.  
  1160. static GCHAR *
  1161. guash_get_image_filename (gint image_id)
  1162. {
  1163.   GCHAR    *path = gimp_image_get_filename (image_id);
  1164.  
  1165.   if (pathname_get_last_separator_index (path) < 0)
  1166.     {
  1167.       GCHAR    tmp[PATH_LENGTH];
  1168.       gint    index;
  1169.  
  1170.       os_file_get_current_directory (tmp);
  1171.       index = strlen (tmp);
  1172.       tmp[index] = G_DIR_SEPARATOR;
  1173.       g_memmove (tmp + index + 1, path, strlen (path) + 1);
  1174.       g_free (path);
  1175.       path = g_strdup (tmp);
  1176.     }
  1177.   return path;
  1178. }
  1179.  
  1180. static Thumbnail *
  1181. guash_build_thumbnail_from_gimage (gint32 i_id, GCHAR *pathname)
  1182. {
  1183.   gint32     d_id;
  1184.   gint        new_width, new_height;
  1185.   gint        width, height;
  1186.   GCHAR        image_type[16];
  1187.   GParam*    return_vals;
  1188.   gint        retvals;
  1189.   GCHAR        info[256];
  1190.  
  1191.   DEBUGBLOCK (N_("guash_build_thumbnail_from_gimage (%d, \"%s\")\n"),
  1192.           i_id, pathname);
  1193.  
  1194.   if (gimp_image_base_type (i_id) != RGB)
  1195.     {
  1196.       if (gimp_image_base_type (i_id) == INDEXED)
  1197.     strcpy (image_type, "Indexed file");
  1198.       else
  1199.     strcpy (image_type, "Gray file");
  1200.  
  1201.       DPRINT (N_("convert image to RGB\n"));
  1202.  
  1203.       return_vals = gimp_run_procedure ("gimp_convert_rgb",
  1204.                     &retvals,
  1205.                     PARAM_IMAGE, i_id,
  1206.                     PARAM_END);
  1207.       gimp_destroy_params (return_vals, retvals);
  1208.     }
  1209.   else
  1210.     strcpy (image_type, "RGB file");
  1211.  
  1212.   new_width = width = gimp_image_width (i_id);
  1213.   new_height = height = gimp_image_height (i_id);
  1214.  
  1215.   if (THUMBNAIL_HEIGHT < new_height)
  1216.     {
  1217.       new_width = new_width * THUMBNAIL_HEIGHT / new_height;
  1218.       new_height = THUMBNAIL_HEIGHT;
  1219.     }
  1220.   if (THUMBNAIL_WIDTH < new_width)
  1221.     {
  1222.       new_height = new_height * THUMBNAIL_WIDTH / new_width;
  1223.       new_width = THUMBNAIL_WIDTH;
  1224.     }
  1225.   if (new_width < 2) new_width = 2;
  1226.   if (new_height < 2) new_height = 2;
  1227.  
  1228.   {
  1229.     guchar    tr[] = BASE_COLOR;
  1230.     guchar    bg[3];
  1231.  
  1232.     gimp_palette_get_background (bg, bg + 1, bg + 2);
  1233.     gimp_palette_set_background (tr[0], tr[1], tr[2]);
  1234.     gimp_image_flatten (i_id);
  1235.     gimp_palette_set_background (bg[0], bg[1], bg[2]);
  1236.   }
  1237.  
  1238.   d_id = gimp_image_get_active_layer (i_id);
  1239.   G_ASSERT (0 <= d_id);
  1240.  
  1241.   if ((width != new_width) || (height != new_height))
  1242.     {
  1243.       gimp_layer_scale (d_id, new_width, new_height, 0);
  1244.       DPRINT (N_("resized to thumbnail size.\n"));
  1245.     }
  1246.  
  1247.   sprintf (info, "%dx%d %s (%d bytes)",
  1248.        width, height,
  1249.        image_type, os_file_size (pathname));
  1250.   gimp_layer_set_name (d_id, info);
  1251.  
  1252.   if (the_loaded_data->name)
  1253.     g_free (the_loaded_data->name);
  1254.   the_loaded_data->name = g_strdup (pathname_get_basename (pathname));
  1255.  
  1256.   if (the_loaded_data->info)
  1257.     g_free (the_loaded_data->info);
  1258.   {
  1259.     GCHAR    tmp[PATH_LENGTH];
  1260. #ifdef GIMP_HAVE_PARASITES
  1261.     Parasite    *para = NULL;
  1262.  
  1263.     para = gimp_image_parasite_find (i_id, "gimp-comment");
  1264.     if ((para != NULL) && (0 < para->size))
  1265.       {
  1266.     GCHAR    comment[242];
  1267.     GCHAR    *src = (GCHAR *) para->data;
  1268.     gint    i;
  1269.  
  1270.     /* maybe newline in info field in xv thumbnail causes a trouble. */
  1271.     for (i = 0; i < 241; i++)
  1272.       {
  1273.         if (!(comment[i] = *src++))
  1274.           break;
  1275.         if (comment[i] == '\n')
  1276.           {
  1277.         comment[i] = 0;
  1278.         break;
  1279.           }
  1280.       }
  1281.     DPRINT (N_("found gimp-comment parasite: %s\n"), comment);
  1282.     sprintf (tmp, "%s %s \"%s\"", the_loaded_data->name, info, comment);
  1283.       }
  1284.     else
  1285.       sprintf (tmp, "%s %s", the_loaded_data->name, info);
  1286.     if (para != NULL)
  1287.       parasite_free (para);
  1288. #else
  1289.     sprintf (tmp, "%s %s", the_loaded_data->name, info);
  1290. #endif
  1291.     the_loaded_data->info = g_strdup (tmp);
  1292.   }
  1293.  
  1294.   image_buffer_copy_drawable (the_loaded_data->image, d_id);
  1295.  
  1296.   RETURN the_loaded_data;
  1297. }
  1298.  
  1299. /* if thumbnail_p is non-FALSE, check only thumbnail */
  1300. static Thumbnail *
  1301. guash_get_image_from_file (GCHAR *pathname, gint thumbnail_p)
  1302. {
  1303.   GParam*    return_vals;
  1304.   gint        retvals;
  1305.   gint32    i_id;
  1306.  
  1307.   DEBUGBLOCK (N_("guash_get_image_from_file (\"%s\", %d)\n"),
  1308.           pathname, thumbnail_p);
  1309.   G_ASSERT (*pathname == G_DIR_SEPARATOR);
  1310.  
  1311.   if (thumbnail_p)        /* thumbnail file */
  1312.     pathname = pathname_build_thumbnail_filename (pathname);
  1313.  
  1314.   DPRINT (N_("thumbnail_file is \"%s\"\n"), pathname);
  1315.  
  1316.   if ((os_file_kind (pathname, TRUE)) == NOT_EXIST)
  1317.     {
  1318.       DPRINT (N_("%s is not found\n"), pathname);
  1319.       RETURN NULL;
  1320.     }
  1321.  
  1322.   if (thumbnail_p)
  1323.     {
  1324.       Thumbnail *thumb = load_xvpict_image (pathname);
  1325.  
  1326.       DPRINT (N_("cache is%s found\n"), (thumb == NULL) ? " not" : "");
  1327.       RETURN thumb;
  1328.     }
  1329.  
  1330.   return_vals = gimp_run_procedure ("gimp_file_load",
  1331.                     &retvals,
  1332.                     PARAM_INT32, RUN_NONINTERACTIVE,
  1333.                     PARAM_STRING, pathname,
  1334.                     PARAM_STRING, pathname,
  1335.                     PARAM_END);
  1336.   if (return_vals[0].data.d_status != STATUS_SUCCESS)
  1337.     {
  1338.       gimp_destroy_params (return_vals, retvals);
  1339.  
  1340.       DPRINT (N_("fail to load (maybe non-image file)\n"));
  1341.       RETURN NULL;
  1342.     }
  1343.   else
  1344. #if 0 
  1345.     gimp_destroy_params (return_vals, retvals);
  1346.  
  1347.   i_id = return_vals[1].data.d_image;
  1348. #else
  1349.    {
  1350.      /* //HB: bug in original source: 
  1351.       * change: first set i_id, then destroy 
  1352.       */
  1353.         i_id = return_vals[1].data.d_image;
  1354.         gimp_destroy_params (return_vals, retvals);
  1355.    }
  1356. #endif
  1357.  
  1358.   DPRINT (N_("loaded from the file\n"));
  1359.  
  1360.   /* Some file-loader returns STATUS_SUCCESS even if it fails to load !!! */
  1361.   if ((i_id < 0) || (gimp_image_get_active_layer (i_id) < 0))
  1362.     {
  1363.       DPRINT (N_("failed to loading (i_id:%d, active_layer:%d)\n"),
  1364.          i_id, gimp_image_get_active_layer (i_id));
  1365.       if (0 <= i_id)
  1366.     gimp_image_delete (i_id);
  1367.       RETURN NULL;
  1368.     }
  1369.   gimp_image_undo_disable (i_id);
  1370.   guash_build_thumbnail_from_gimage (i_id, pathname);
  1371.   gimp_image_delete (i_id);
  1372.  
  1373.   RETURN the_loaded_data;
  1374. }
  1375.  
  1376. static directory_cache *
  1377. guash_lookup_directory_cache (GCHAR *dir)
  1378. {
  1379.   return (directory_cache *) g_hash_table_lookup (directory_cache_table,
  1380.                           (gpointer) dir);
  1381. }
  1382.  
  1383. static void
  1384. guash_parse_gimprc ()
  1385. {
  1386.   GCHAR buf[LINE_BUF_SIZE];
  1387.   gint    tmp = 0;
  1388.  
  1389.   DEBUGBLOCK (N_("guash_parse_gimprc\n"));
  1390.  
  1391.   gtkW_parse_gimprc_string ("guash-option-string", buf);
  1392.   if (strlen (buf) == 6)
  1393.     {
  1394.       GCHAR    *ptr = buf;
  1395.  
  1396. #define c_l_eq_p_(C, c)    ((('a' - 'A') | (C)) == (c))
  1397.       SET_TO_ATTRIBUTE (&VAL, CONFIRM_P, c_l_eq_p_ (*ptr++, 'c'));
  1398.       binding_style = c_l_eq_p_ (*ptr++, 'e') ? BINDING_EMACS : BINDING_GIMP;
  1399.       SET_TO_ATTRIBUTE (&VAL, DISPLAY_IMAGE_P, (! c_l_eq_p_ (*ptr++, 'f')));
  1400.       SET_TO_ATTRIBUTE (&VAL, SAVE_AS_XVPICT_P, (! c_l_eq_p_ (*ptr, 'u')));
  1401. /*
  1402.  *    o an EXPERIMENTAL 16bit color thumbnail format. NO DOCUMENT.
  1403.  *    NO COMPRESSION (the size reaches to 10KB!)  THE FORMAT MIGHT BE
  1404.  *    MODIFIED/DISCARED IN FUTURE RELEASE.
  1405.  */
  1406.       SET_TO_ATTRIBUTE (&VAL, SAVE_AS_GPICT_P, c_l_eq_p_ (*ptr++, 'g'));
  1407.       SET_TO_ATTRIBUTE (&VAL, SORT_BY_NAME_P, (! c_l_eq_p_ (*ptr++, 'd')));
  1408.       SET_TO_ATTRIBUTE (&VAL, MONITOR_P, c_l_eq_p_ (*ptr, 'a'));
  1409. #undef c_l_eq_p_
  1410.     }
  1411.   else if (0 < strlen (buf))
  1412.     g_warning ("The length of guash-option-string is not 6. Ignored.");
  1413.  
  1414.   gtkW_parse_gimprc_string ("guash-inhibit-suffix", buf);
  1415.   if (*buf == 0)
  1416.     strcpy (buf, GUASH_INHIBIT_SUFFIX_TABLE_DEFAULT);
  1417.   guash_build_inhibit_suffix_table (buf);
  1418.  
  1419.   gtkW_parse_gimprc_string ("guash-mapping-command", buf);
  1420.   if (*buf == 0)
  1421.     strcpy (buf, MAPPING_COMMAND_DEFAULT);
  1422.   if ((VAL.mapping_command == NULL) || strlen (VAL.mapping_command) == 0)
  1423.     strcpy (VAL.mapping_command, buf);
  1424.  
  1425.   tmp = gtkW_parse_gimprc_gint ("guash-max-images-in-dir",
  1426.                 directory_cache_max_images);
  1427.   if (0 < tmp)
  1428.     directory_cache_max_images = CLAMP (tmp, 1, 2000);
  1429.   else
  1430.     directory_cache_max_images = -1;
  1431.  
  1432.   tmp = 0;
  1433.   tmp = gtkW_parse_gimprc_gint ("guash-directory-history-length",
  1434.                 directory_cache_table_max_size);
  1435.   if (0 <= tmp)
  1436.     directory_cache_table_max_size = CLAMP (tmp, 1, 16);
  1437.   else
  1438.     directory_cache_table_max_size = -1;
  1439.  
  1440.   directory_monitor_interval = DIRECTORY_MONITOR_INTERVAL_DEFAULT * 1000;
  1441.   tmp = 0;
  1442.   tmp = gtkW_parse_gimprc_gint ("guash-directory-monitor-interval", tmp);
  1443.   if (0 < tmp)
  1444.     directory_monitor_interval = CLAMP (tmp, 1, 200) * 1000;
  1445.  
  1446.   gtkW_parse_gimprc_string ("temp-path", buf);
  1447. #if 0
  1448.   guash_tmp_dir = *buf ? g_strdup (buf) : "/tmp";
  1449. #else
  1450.   /* //HB: use glib function for portability 
  1451.    *       Do I really need g_strdup () for it?
  1452.    */
  1453.   guash_tmp_dir = *buf ? g_strdup (buf) : g_strdup (g_get_tmp_dir ());
  1454. #endif
  1455.   if (guash_tmp_dir[strlen (guash_tmp_dir) - 1] == G_DIR_SEPARATOR)
  1456.     guash_tmp_dir[strlen (guash_tmp_dir) - 1] = 0;
  1457.  
  1458.   RETURN;
  1459. }
  1460.  
  1461. static void
  1462. guash_set_fileselector_last_value (GCHAR *pathname)
  1463. {
  1464.   if (os_file_kind (pathname, TRUE) == DIRECTORY)
  1465.     {
  1466.       strcpy (fileselector_last_pathname, pathname);
  1467.     }
  1468.   else
  1469.     {
  1470.       gint    len;
  1471.  
  1472.       strcpy (fileselector_last_pathname,
  1473.           pathname_get_directoryname (pathname));
  1474.       len = strlen (fileselector_last_pathname);
  1475.       fileselector_last_pathname[len] = G_DIR_SEPARATOR;
  1476.       fileselector_last_pathname[len + 1] = 0;
  1477.     }
  1478. }
  1479.  
  1480. static directory_cache *
  1481. guash_update_cwd_cache (gint mode)
  1482. {
  1483.   /* mode is either of
  1484.      RESCAN: to build w/o current information
  1485.      REDRAW: only to update panel
  1486.      GC: to remove unexisting files from panel
  1487.   */
  1488.   gpointer    ptr = NULL;
  1489.   GCHAR        dir[PATH_LENGTH];
  1490.  
  1491.   os_file_get_current_directory (dir);
  1492.  
  1493.   DEBUGBLOCK (N_("guash_update_cwd_cache (%d) at %s\n"), mode, dir);
  1494.  
  1495.   G_ASSERT (mode == RESCAN || mode == GC || mode == REDRAW);
  1496.  
  1497.   if ((ptr = g_hash_table_lookup (directory_cache_table, (gpointer) dir)))
  1498.     {
  1499.       cwd_cache = (directory_cache *) ptr;
  1500.  
  1501.       DPRINT (N_("found directory_cache for %s\n"), dir);
  1502.  
  1503.       g_hash_table_foreach (directory_cache_table,
  1504.                 directory_cache_table_aging,
  1505.                 NULL);
  1506.       cwd_cache->birth_index = directory_cache_table_size;
  1507.       DPRINT (N_("success to LRU ordering to directory_caches\n"));
  1508.  
  1509.       if (mode == RESCAN)
  1510.     {
  1511.       cwd_cache_update ();
  1512.     }
  1513.       else if (mode == GC)
  1514.     {
  1515.       directory_cache_garbage_collect (cwd_cache);
  1516.     }
  1517.       else
  1518.     {
  1519.       if ((HAS_NO_ATTRIBUTE (cwd_cache, DISPLAY_IMAGE_P)
  1520.            && HAS_ATTRIBUTE (&VAL, DISPLAY_IMAGE_P))
  1521.           || (HAS_ATTRIBUTE (cwd_cache, DISPLAY_IMAGE_P)
  1522.           && HAS_NO_ATTRIBUTE (&VAL, DISPLAY_IMAGE_P))
  1523.           || (HAS_ATTRIBUTE (cwd_cache, SORT_BY_NAME_P)
  1524.           && HAS_NO_ATTRIBUTE (&VAL, SORT_BY_NAME_P))
  1525.           || (HAS_NO_ATTRIBUTE (cwd_cache, SORT_BY_NAME_P)
  1526.           && HAS_ATTRIBUTE (&VAL, SORT_BY_NAME_P)))
  1527.         cwd_cache_update ();
  1528.       else
  1529.         {
  1530.           if (HAS_ATTRIBUTE (cwd_cache, DISORDERED_P))
  1531.         {
  1532.           directory_cache_garbage_collect (cwd_cache);
  1533.           DPRINT (N_("success to garbage collect\n"));
  1534.         }
  1535.           else
  1536.         thumbnail_panel_update ();
  1537.         }
  1538.     }
  1539.     }
  1540.   else
  1541.     {
  1542.       if ((0 < directory_cache_table_max_size) &&
  1543.       (directory_cache_table_max_size <= directory_cache_table_size))
  1544.     {
  1545.       directory_cache    *tmp = NULL;
  1546.  
  1547.       DPRINT (N_("recycle directory_cache\n"));
  1548.       /* First, set cwd_cache to NULL. */
  1549.       cwd_cache = NULL;
  1550.       /* Second: map recycler.
  1551.        * Recycler sets one of the oldest entries to cwd_cache
  1552.        * and sets the 1st entry of directory hashtable to tmp. */
  1553.       g_hash_table_foreach (directory_cache_table,
  1554.                 directory_cache_table_recycler,
  1555.                 (gpointer) &tmp);
  1556.       if (cwd_cache)    /* recycler worked fine, finding an entry! */
  1557.         {
  1558.           /* Then, delete it from directory_cache_table */
  1559.           g_hash_table_remove (directory_cache_table, cwd_cache->name);
  1560.           /* Finally, set the name as the new key */
  1561.           strcpy (cwd_cache->name, dir);
  1562.           directory_cache_initialize (cwd_cache);
  1563.           cwd_cache->birth_index = directory_cache_table_max_size;
  1564.         }
  1565.       else            /* should not happen! FAILSAFE path */
  1566.         {
  1567.           if (tmp == NULL)
  1568.         {
  1569.           printf ("CATASTROPHIC ERROR: CACHE RECYCLE FAILED!!!\n");
  1570.           cwd_cache = directory_cache_new (dir);
  1571.         }
  1572.           else
  1573.         {
  1574.           printf ("Warning: directory_cache recycle failed (%s)!\n", tmp->name);
  1575.           cwd_cache = tmp;
  1576.           /* tmp is born again as cwd_cache */
  1577.           cwd_cache->birth_index = directory_cache_table_size;
  1578.         }
  1579.         }
  1580.     }
  1581.       else
  1582.     {
  1583.       if (0 < directory_cache_table_max_size)
  1584.         directory_cache_table_size++;
  1585.  
  1586.       DPRINT (N_("making a new directory_cache...\n"));
  1587.       cwd_cache = directory_cache_new (dir);
  1588.       DPRINT (N_("making a new directory_cache... done\n"));
  1589.     }
  1590.       g_hash_table_insert (directory_cache_table,
  1591.                cwd_cache->name,
  1592.                (gpointer) cwd_cache);
  1593.  
  1594.       cwd_cache_update ();
  1595.     }
  1596.   directory_cache_auto_shrink (cwd_cache, FALSE);
  1597.   strcpy (VAL.last_dir_name, cwd_cache->name);
  1598.  
  1599.   RETURN cwd_cache;
  1600. }
  1601.  
  1602. static gint
  1603. guash_add_entry (GCHAR *pathname, Thumbnail *thumb)
  1604. {
  1605.   GCHAR            *dirname;
  1606.   directory_cache    *dcache;
  1607.  
  1608.   DEBUGBLOCK (N_("guash_add_entry (\"%s\", %s_thumb.)\n"), pathname,
  1609.           (thumb ? thumb->name : "no"));
  1610.  
  1611.   dirname = pathname_get_directoryname (pathname);
  1612.  
  1613.   if ((dcache = guash_lookup_directory_cache (dirname)) == NULL)
  1614.     {
  1615.       DPRINT (N_("%s was not visted\n"), dirname);
  1616.       g_free (dirname);
  1617.       DPRINT (N_("done of guash_add_entry\n"));
  1618.       RETURN FALSE;
  1619.     }
  1620.   directory_cache_update_thumbnail_for (dcache, pathname, thumb);
  1621.   SET_ATTRIBUTE (dcache, DISORDERED_P);
  1622.  
  1623.   g_free (dirname);
  1624.   RETURN (cwd_cache == dcache);
  1625. }
  1626.  
  1627. static gint
  1628. guash_copy_image_file (GCHAR *filename, GCHAR *newname)
  1629. {
  1630.   /* FIXME
  1631.      UPDATE timestamp!
  1632.  */
  1633.   if (! guash_validate_src_file (filename))
  1634.     return FALSE;
  1635.  
  1636.   if (os_file_kind (newname, TRUE) == NOT_EXIST)
  1637.     {
  1638.       GCHAR    *from_thumbnail_name;
  1639.       GCHAR    *to_thumbnail_name;
  1640.       mode_t    mode = NEW_DIRECTORY_MODE;
  1641.  
  1642.       if (os_copy_file (filename, newname) == FALSE)
  1643.     return FALSE;
  1644.  
  1645.       /* copy thumbnail */
  1646.       from_thumbnail_name = pathname_build_thumbnail_filename (filename);
  1647.       if (os_file_kind (from_thumbnail_name, TRUE) != REGFILE)
  1648.     {
  1649.       g_free (from_thumbnail_name);
  1650.       return TRUE;
  1651.     }
  1652.       to_thumbnail_name = pathname_build_thumbnail_filename (newname);
  1653.       {
  1654.     GCHAR    *to_dir_name;
  1655.  
  1656.     to_dir_name = pathname_get_directoryname (to_thumbnail_name);
  1657.  
  1658.     if (os_make_directory (to_dir_name, mode) == -1)
  1659.       if (errno != EEXIST)
  1660.         {
  1661.           g_free (to_dir_name);
  1662.           g_free (to_thumbnail_name);
  1663.           g_free (from_thumbnail_name);
  1664.           return TRUE;
  1665.         }
  1666.     if (os_file_kind (to_dir_name, TRUE) != DIRECTORY)
  1667.       {
  1668.         g_free (to_dir_name);
  1669.         g_free (to_thumbnail_name);
  1670.         g_free (from_thumbnail_name);
  1671.         return TRUE;
  1672.       }
  1673.     g_free (to_dir_name);
  1674.       }
  1675.       os_copy_file (from_thumbnail_name, to_thumbnail_name);
  1676.  
  1677.       g_free (from_thumbnail_name);
  1678.       g_free (to_thumbnail_name);
  1679.       return TRUE;
  1680.     }
  1681.   else
  1682.     return FALSE;
  1683. }
  1684.  
  1685. static gint
  1686. guash_delete_image_file (GCHAR *filename)
  1687. {
  1688.   if (! guash_validate_src_file (filename))
  1689.     return FALSE;
  1690.  
  1691.   return (os_delete_file (filename) == 0);
  1692. }
  1693.  
  1694. static gint
  1695. guash_move_image_file (GCHAR *filename, GCHAR *newname)
  1696. {
  1697.   DEBUGBLOCK (N_("guash_move_image_file (\"%s\", \"%s\")\n"),
  1698.           filename, newname);
  1699.  
  1700.   if (! guash_validate_src_file (filename))
  1701.     RETURN FALSE;
  1702.  
  1703.   if (os_file_kind (newname, TRUE) == NOT_EXIST)
  1704.     {
  1705.       if (os_rename_file (filename, newname) == 0)
  1706.     {
  1707.       GCHAR        *from_thumbnail_name = NULL;
  1708.       GCHAR        *to_thumbnail_name = NULL;
  1709.       GCHAR        *thumbnail_dir = NULL;
  1710.       mode_t    mode = NEW_DIRECTORY_MODE;
  1711.  
  1712.       from_thumbnail_name = pathname_build_thumbnail_filename (filename);
  1713.  
  1714.       if (os_file_kind (from_thumbnail_name, TRUE) != REGFILE)
  1715.         {
  1716.           g_free (from_thumbnail_name);
  1717.           RETURN TRUE;
  1718.         }
  1719.       to_thumbnail_name = pathname_build_thumbnail_filename (newname);
  1720.       thumbnail_dir = pathname_get_directoryname (to_thumbnail_name);
  1721.  
  1722.       if (os_make_directory (thumbnail_dir, mode) == -1)
  1723.         if (errno != EEXIST)
  1724.           {
  1725.         g_free (thumbnail_dir);
  1726.         g_free (to_thumbnail_name);
  1727.         g_free (from_thumbnail_name);
  1728.         RETURN TRUE;
  1729.           }
  1730.       if (os_file_kind (thumbnail_dir, TRUE) != DIRECTORY)
  1731.         {
  1732.           g_free (thumbnail_dir);
  1733.           g_free (to_thumbnail_name);
  1734.           g_free (from_thumbnail_name);
  1735.           RETURN TRUE;
  1736.         }
  1737.       g_free (thumbnail_dir);
  1738.  
  1739.       if (os_rename_file (from_thumbnail_name, to_thumbnail_name) == -1)
  1740.         {
  1741.           perror ("rename thumbnail file");
  1742.           printf ("from %s to %s\n", from_thumbnail_name, to_thumbnail_name);
  1743.         }
  1744.  
  1745.       g_free (from_thumbnail_name);
  1746.       g_free (to_thumbnail_name);
  1747.       RETURN TRUE;
  1748.     }
  1749.       else
  1750.     {
  1751.       if (errno == EXDEV)
  1752.         {
  1753.           if (guash_copy_image_file (filename, newname))
  1754.         {
  1755.           guash_delete_image_file (filename);
  1756.           RETURN TRUE;
  1757.         }
  1758.           else
  1759.         RETURN FALSE;
  1760.         }
  1761.       else
  1762.         {
  1763.           perror ("file_move");
  1764.           RETURN FALSE;
  1765.         }
  1766.     }
  1767.     }
  1768.   else
  1769.     {
  1770.       GString *message;
  1771.  
  1772.       message = g_string_new ("NOP: ");
  1773.       g_string_append (message, newname);
  1774.       g_string_append (message, " already exists");
  1775.       gtkW_message_dialog (TRUE, message->str);
  1776.       g_string_free (message, TRUE);
  1777.  
  1778.       RETURN FALSE;
  1779.     }
  1780. }
  1781.  
  1782. static gint
  1783. guash_open_image_file (GCHAR *filename)
  1784. {
  1785.   GParam*    return_vals;
  1786.   gint        retvals;
  1787.   gint        i_id;
  1788.  
  1789.   if (! guash_validate_src_file (filename))
  1790.     return -1;
  1791.  
  1792.   return_vals = gimp_run_procedure ("gimp_file_load",
  1793.                     &retvals,
  1794.                     PARAM_INT32, RUN_INTERACTIVE,
  1795.                     PARAM_STRING, filename,
  1796.                     PARAM_STRING, filename,
  1797.                     PARAM_END);
  1798.  
  1799.   if (return_vals[0].data.d_status != STATUS_SUCCESS)
  1800.     {
  1801.       gimp_destroy_params (return_vals, retvals);
  1802.       return -1;
  1803.     }
  1804.   else
  1805.     gimp_destroy_params (return_vals, retvals);
  1806.  
  1807.   i_id = return_vals[1].data.d_image;
  1808.   /* the following sequence is copied from app/fileops.c */
  1809.   /*  enable & clear all undo steps  */
  1810.   gimp_image_undo_enable (i_id);
  1811.   /*  set the image to clean  */
  1812.   gimp_image_clean_all (i_id);
  1813.   /*  display the image */
  1814.   gimp_display_new (i_id);
  1815.  
  1816.   return i_id;
  1817. }
  1818.  
  1819. static gint
  1820. guash_validate_src_file (GCHAR *filename)
  1821. {
  1822.   if (os_file_kind (filename, TRUE) == NOT_EXIST)
  1823.     {
  1824.       GCHAR    tmp[LINE_BUF_SIZE];
  1825.  
  1826.       sprintf (tmp, "%s does not exist", filename);
  1827.       gtkW_message_dialog (TRUE, tmp);
  1828.       return FALSE;
  1829.     }
  1830.   return TRUE;
  1831. }
  1832.  
  1833. /*
  1834.  * selection class: internal data structure
  1835.  */
  1836. static void
  1837. selection_show ()
  1838. {
  1839.   thumbnail_panel_draw_selection_frame (TRUE);
  1840.   thumbnail_panel_update_selection_buttons ();
  1841. }
  1842.  
  1843. static gint
  1844. selection_add (gint index)
  1845. {
  1846.   return directory_cache_update_selection (cwd_cache, SELECTION_ADD, index);
  1847. }
  1848.  
  1849. static gint
  1850. selection_add_and_show (gint index)
  1851. {
  1852.   if (selection_add (index))
  1853.     {
  1854.       selection_show ();
  1855.       return TRUE;
  1856.     }
  1857.   else
  1858.     return FALSE;
  1859. }
  1860.  
  1861. static gint
  1862. selection_delete (gint index)
  1863. {
  1864.   Thumbnail    *thumb = directory_cache_get_nth (cwd_cache, index);
  1865.  
  1866.   if (thumb && HAS_ATTRIBUTE (thumb, SELECTED_P))
  1867.     {
  1868.       thumbnail_panel_draw_selection_frame (FALSE);
  1869.       directory_cache_update_selection (cwd_cache, SELECTION_DELETE, index);
  1870.  
  1871.       if (selection_is_active ())
  1872.     thumbnail_panel_draw_selection_frame (TRUE);
  1873.       else
  1874.     thumbnail_panel_set_info (NULL);
  1875.       thumbnail_panel_update_selection_buttons ();
  1876.       return TRUE;
  1877.     }
  1878.   else
  1879.     return FALSE;
  1880. }
  1881.  
  1882. static gint
  1883. selection_is_active ()
  1884. {
  1885.   return (0 < cwd_cache->num_selection);
  1886. }
  1887.  
  1888. static gint
  1889. selection_length ()
  1890. {
  1891.   return cwd_cache->num_selection;
  1892. }
  1893.  
  1894. static gint
  1895. selection_member_p (gint index)
  1896. {
  1897.   return HAS_ATTRIBUTE (directory_cache_get_nth (cwd_cache, index), SELECTED_P);
  1898. }
  1899.  
  1900. static gint
  1901. selection_reset ()
  1902. {
  1903.   Thumbnail    *thumb;
  1904.   gint        i;
  1905.   gint        max = directory_cache_num_entry (cwd_cache);
  1906.  
  1907.   G_ASSERT (cwd_cache);
  1908.  
  1909.   DEBUGBLOCK (N_("selection_reset\n"));
  1910.  
  1911.   thumbnail_panel_draw_selection_frame (FALSE);
  1912.   DPRINT (N_("success to call thumbnail_panel_clear_selection_frame\n"));
  1913.  
  1914.   if (0 < cwd_cache->num_selection)
  1915.     {
  1916.       G_ASSERT (HAS_ATTRIBUTE (directory_cache_get_nth (cwd_cache,
  1917.                             cwd_cache->selection_top),
  1918.                    SELECTED_P));
  1919.  
  1920.       for (i = cwd_cache->selection_top; i < max; i++)
  1921.     {
  1922.       thumb = directory_cache_get_nth (cwd_cache, i);
  1923.  
  1924.       G_ASSERT (thumb);
  1925.  
  1926.       if (HAS_ATTRIBUTE (thumb, SELECTED_P))
  1927.         RESET_ATTRIBUTE (thumb, SELECTED_P);
  1928.     }
  1929.     }
  1930.   cwd_cache->num_selection = 0;
  1931.   cwd_cache->selection_top = -1;
  1932.   cwd_cache->selection_timestamp = 0;
  1933.   DPRINT (N_("success to remove all thumbnails from selection\n"));
  1934.  
  1935.   thumbnail_panel_update_selection_buttons ();
  1936.   gtk_widget_queue_draw (thumbnail_panel);
  1937.  
  1938.   cwd_cache->last_focus = 0;
  1939.   RETURN TRUE;
  1940. }
  1941.  
  1942. static gint
  1943. selection_reverse_member (gint index)
  1944. {
  1945.   gint    flag = selection_member_p (index);
  1946.  
  1947.   if (flag)
  1948.     selection_delete (index);
  1949.   else
  1950.     selection_add_and_show (index);
  1951.   return (! flag);
  1952. }
  1953.  
  1954. static gint
  1955. selection_validate_image ()
  1956. {
  1957.   if (selection_is_active ())
  1958.     return TRUE;
  1959.   else
  1960.     {
  1961.       thumbnail_panel_set_info (_("No image is selected"));
  1962.       return FALSE;
  1963.     }
  1964. }
  1965.  
  1966. static selection_iterator *
  1967. selection_make_iterator (gint mode)
  1968. {
  1969.   selection_iterator    *si;
  1970.   gint            length = selection_length ();
  1971.   gint            index = 0;
  1972.   gint            max = directory_cache_num_entry (cwd_cache);
  1973.   gint            id;
  1974.   gint            count = 0;
  1975.  
  1976.   __DEBUGBLOCK (N_("selection_make_iterator (length %d)\n"), length);
  1977.  
  1978.   if (length < 1)
  1979.     RETURN NULL;
  1980.  
  1981.   si = g_new (selection_iterator, 1);
  1982.   si->length = length;
  1983.   si->unvisit_index = 0;
  1984.   si->body = g_new (selection_iterator_entry, length);
  1985.  
  1986.   G_ASSERT (selection_is_active ());
  1987.   G_ASSERT (HAS_ATTRIBUTE (directory_cache_get_nth (cwd_cache,
  1988.                             cwd_cache->selection_top),
  1989.                SELECTED_P));
  1990.  
  1991.   for (id = cwd_cache->selection_top; id < max; id++)
  1992.     {
  1993.       Thumbnail    *thumb = directory_cache_get_nth (cwd_cache, id);
  1994.  
  1995.       G_ASSERT (thumb != NULL);
  1996.  
  1997.       if (HAS_ATTRIBUTE (thumb, SELECTED_P)
  1998.       && HAS_NO_ATTRIBUTE (thumb, DELETED_P))
  1999.     {
  2000.       selection_iterator_entry *entry = si->body + index++;
  2001.  
  2002.       entry->index = id;
  2003.       entry->selection_timestamp = thumb->selection_timestamp;
  2004.       entry->thumbnail = thumb;
  2005.       count++;
  2006.     }
  2007.     }
  2008.   DPRINT (N_("success to gather selected %d thumbnails\n"), index);
  2009.  
  2010.   G_ASSERT (count == cwd_cache->num_selection);
  2011.  
  2012.   if (mode == SELECTION_ORDER_TIMESTAMP)
  2013.     {
  2014.       qsort (si->body,
  2015.          length,
  2016.          sizeof (selection_iterator_entry),
  2017.          (sort_compare_function) selection_iterator_compare_timestamp);
  2018.       DPRINT (N_("success to sort by timestamp\n"));
  2019.     }
  2020.   RETURN si;
  2021. }
  2022.  
  2023. static gint
  2024. selection_iterator_compare_timestamp (void *a, void *b)
  2025. {
  2026.   gint    a_stamp = ((selection_iterator_entry *) a)->selection_timestamp;
  2027.   gint    b_stamp = ((selection_iterator_entry *) b)->selection_timestamp;
  2028.  
  2029.   if (a_stamp < b_stamp)
  2030.     return -1;
  2031.   else if (a_stamp == b_stamp)
  2032.     return 0;
  2033.   else
  2034.     return 1;
  2035. }
  2036.  
  2037. static selection_iterator_entry *
  2038. selection_iterator_get_next_entry (selection_iterator *si)
  2039. {
  2040.   DEBUGBLOCK (N_("selection_iterator_get_next_entry\n"));
  2041.  
  2042.   if (si == NULL)
  2043.     RETURN NULL;
  2044.  
  2045.   DPRINT (N_("unvisit_index: %d, length: %d\n"), si->unvisit_index, si->length);
  2046.  
  2047.   if (si->unvisit_index < si->length)
  2048.     {
  2049.       RETURN si->body + si->unvisit_index++;
  2050.     }
  2051.   else
  2052.     {
  2053.       DPRINT (N_("delete iterator\n"));
  2054.       g_free (si->body);
  2055.       g_free (si);
  2056.       RETURN NULL;
  2057.     }
  2058. }
  2059.  
  2060. static Thumbnail *
  2061. selection_iterator_get_next_thumbnail (selection_iterator *si)
  2062. {
  2063.   selection_iterator_entry    *next = NULL;
  2064.  
  2065.   DEBUGBLOCK (N_("selection_iterator_get_next_thumbnail\n"));
  2066.  
  2067.   next = selection_iterator_get_next_entry (si);
  2068.   DPRINT (N_("success to call selection_iterator_get_next_entry (%ld)\n"),
  2069.       (next == NULL ? 0 : (glong) next->thumbnail));
  2070.  
  2071.   RETURN (next == NULL ? NULL : next->thumbnail);
  2072. }
  2073.  
  2074. static void
  2075. selection_map_script ()
  2076. {
  2077.   gint            retvals;
  2078.   FILE            *file;
  2079.   GCHAR            script_file[PATH_LENGTH];
  2080.   selection_iterator     *iterator;
  2081.   Thumbnail        *selected;
  2082.   GCHAR            buf[LINE_BUF_SIZE];
  2083.  
  2084.   ASSERT (cwd_cache_validate ());
  2085.  
  2086.   DEBUGBLOCK (N_("selection_map_script\n"));
  2087.  
  2088.   if (os_file_kind (guash_tmp_dir, TRUE) != DIRECTORY)
  2089.     {
  2090.       gtkW_message_dialog (TRUE, "Abort execution: you don't have an appropriate directory");
  2091.       RETURN;
  2092.     }
  2093.  
  2094.   gtkW_widget_set_cursor (dlg, CURSOR_WAIT);
  2095.   gtkW_widget_set_cursor (thumbnail_panel, CURSOR_WAIT);
  2096.  
  2097.   thumbnail_panel_set_info (_("Map script-fu: wait a moment..."));
  2098.  
  2099.   sprintf (script_file, N_("%s%cguash%d.scm"), guash_tmp_dir, G_DIR_SEPARATOR, getpid ());
  2100.   DPRINT (N_("script_file is %s\n"), script_file);
  2101.  
  2102.   if ((file = fopen (script_file, "w")) == NULL)
  2103.     {
  2104.       sprintf (buf, _(" Failed to save to %s "), script_file);
  2105.       gtkW_message_dialog (TRUE, buf); /* info is too short to display filename */
  2106.     }
  2107.   fprintf (file, N_(";; This file was generated by guash automatically.\n"));
  2108.   fprintf (file, N_(";; You can delete me without trouble.\n\n"));
  2109.   fprintf (file, N_("(set! %%guash-selection\n"));
  2110.   fprintf (file, N_("  '(\n"));
  2111.  
  2112.   iterator = selection_make_iterator (SELECTION_ORDER_TIMESTAMP);
  2113.   while (NULL != (selected = selection_iterator_get_next_thumbnail (iterator)))
  2114.     {
  2115.       gint    ppos, i;
  2116.  
  2117.       fprintf (file,
  2118.            N_("    (\"%s%c%s\" \"%s\" \""),
  2119.            cwd_cache->name, G_DIR_SEPARATOR, selected->name, /* full path */
  2120.            cwd_cache->name /* directory that does not end with slash */
  2121.            );
  2122.  
  2123.       ppos = pathname_get_last_period_index (selected->name);
  2124.  
  2125.       for (i = 0; i < ppos; i++)
  2126.     fprintf (file, N_("%c"), selected->name[i]);
  2127.  
  2128.       fprintf (file, N_("\" \""));
  2129.  
  2130.       for (i = ppos + 1; i < strlen (selected->name); i++)
  2131.     fprintf (file, N_("%c"), selected->name[i]);
  2132.  
  2133.       fprintf (file, N_("\")\n"));
  2134.     }
  2135.   fprintf (file, N_("    ))\n"));
  2136.   fclose (file);
  2137.  
  2138. #ifdef GIMP_HAVE_PARASITES
  2139. #ifndef BROKEN_SCRIPT_FU
  2140.   {
  2141.     Parasite *para = NULL;
  2142.  
  2143.     /*
  2144.     if ((para = gimp_parasite_find (GUASH_PARASITE_NAME)) != NULL)
  2145.       gimp_parasite_detach (GUASH_PARASITE_NAME);
  2146.     */
  2147.      gimp_attach_new_parasite (GUASH_PARASITE_NAME,
  2148.                    FALSE,
  2149.                    strlen (script_file) + 1,
  2150.                    script_file);
  2151.   }
  2152. #endif
  2153. #endif
  2154.  
  2155.   /* And all script-fus return NULL always */
  2156. #ifndef BROKEN_SCRIPT_FU
  2157.   thumbnail_panel_set_info (_("Map script-fu: start launchar..."));
  2158.  
  2159.   gimp_run_procedure ("script-fu-map-on-guash-selection",
  2160.               &retvals,
  2161.               PARAM_INT32, RUN_INTERACTIVE,
  2162.               PARAM_STRING, "/tmp/guash",     /* the filename */
  2163.               PARAM_INT32, TRUE,        /* delete the file */
  2164.               PARAM_STRING, "gm-sample",    /* function */
  2165.               PARAM_INT32, FALSE,        /* display */
  2166.               PARAM_INT32, FALSE,        /* overwrite */
  2167.               PARAM_INT32, FALSE,        /* sort */
  2168.               PARAM_INT32, FALSE,        /* reverse order */
  2169.               PARAM_STRING, "()",        /* extra arg list */
  2170.               PARAM_END);
  2171.  
  2172.   os_delete_file (script_file);
  2173.   thumbnail_panel_set_info (_("Map script-fu: finished"));
  2174. #else
  2175.   retvals = 0;
  2176.   sprintf (buf, _("Wrote %s. Run script-fu-map-on-guash-selection with it"),
  2177.        script_file);
  2178.   thumbnail_panel_set_info (buf);
  2179. #endif
  2180.   gtkW_widget_set_cursor (dlg, CURSOR_DEFAULT);
  2181.   gtkW_widget_set_cursor (thumbnail_panel, CURSOR_HAND);
  2182.  
  2183.   RETURN;
  2184. }
  2185.  
  2186. static void
  2187. selection_map_unix_command ()
  2188. {
  2189.   selection_iterator     *iterator;
  2190.   Thumbnail        *selected;
  2191.   GCHAR            template[LINE_BUF_SIZE];
  2192.  
  2193.   ASSERT (cwd_cache_validate ());
  2194.  
  2195.   gtkW_query_box ("Guash: mapping unix command on selection",
  2196.           "Type command to map on the selected image files. \n\
  2197.  \"{}\" in the command string is replaced to each filename without directory. \n\
  2198.  \"%%\" in the command string is replaced to the directory part of each file. \n\
  2199.  - SAMPLES -\n\
  2200.  rm .xvpics/{} \n\
  2201.  uuencode {} {} > /tmp/{}.uu; chmod go-r /tmp/{}.uu",
  2202.           VAL.mapping_command, template);
  2203.   if (strlen (template) == 0)
  2204.     return;
  2205.  
  2206.   iterator = selection_make_iterator (SELECTION_ORDER_TIMESTAMP);
  2207.   while (NULL != (selected = selection_iterator_get_next_thumbnail (iterator)))
  2208.     {
  2209.       GCHAR    *command;
  2210.  
  2211.       command = guash_generate_unix_command (template, cwd_cache->name, selected->name);
  2212.       if (command)
  2213.     {
  2214.       system (command);
  2215.       g_free (command);
  2216.     }
  2217.     }
  2218.   strcpy (VAL.mapping_command, template);
  2219. }
  2220.  
  2221. static void
  2222. selection_open_files ()
  2223. {
  2224.   Thumbnail        *selected;
  2225.   selection_iterator     *iterator;
  2226.   gint            first_missing = TRUE;
  2227.  
  2228.   ASSERT (cwd_cache_validate () && selection_validate_image ());
  2229.  
  2230.   DEBUGBLOCK (N_("selection_open_files\n"));
  2231.  
  2232.   gtkW_widget_set_cursor (dlg, CURSOR_WAIT);
  2233.   gtkW_widget_set_cursor (thumbnail_panel, CURSOR_WAIT);
  2234.  
  2235.   thumbnail_panel_set_info (_("Loading..."));
  2236.  
  2237.   iterator = selection_make_iterator (SELECTION_ORDER_INDEX);
  2238.   while (NULL != (selected = selection_iterator_get_next_thumbnail (iterator)))
  2239.     {
  2240.       gint    f_kind;
  2241.  
  2242.       if (HAS_NO_ATTRIBUTE (selected, SELECTED_P))
  2243.     continue;
  2244.  
  2245.       f_kind = os_file_kind (selected->name, TRUE);
  2246.  
  2247.       if (f_kind == REGFILE)
  2248.     {
  2249.       GCHAR        filename[PATH_LENGTH];
  2250.  
  2251.       sprintf (filename, "%s%c%s", cwd_cache->name, G_DIR_SEPARATOR, selected->name);
  2252.       guash_open_image_file (filename);
  2253.     }
  2254.       else if (f_kind == NOT_EXIST)
  2255.     {
  2256.       if (first_missing)
  2257.         {
  2258.           gdk_beep ();
  2259.           thumbnail_panel_set_info (_("the selected image does not exist now!"));
  2260.           DPRINT (N_("file lost %s\n"), selected->name);
  2261.           sleep (1);
  2262.           first_missing = FALSE;
  2263.         }
  2264.     }
  2265.     }
  2266.  
  2267.   gtkW_widget_set_cursor (dlg, CURSOR_DEFAULT);
  2268.   gtkW_widget_set_cursor (thumbnail_panel, CURSOR_HAND);
  2269.  
  2270.   if (guash_discard_events ())
  2271.     thumbnail_panel_set_info (_("FYI, events during opening an image were discarded"));
  2272.   else
  2273.     {
  2274.       Thumbnail *thumb = NULL;
  2275.  
  2276.       G_ASSERT (0 < cwd_cache->last_focus);
  2277.       thumb = directory_cache_get_nth (cwd_cache, cwd_cache->last_focus);
  2278.       G_ASSERT (thumb != NULL);
  2279.       thumbnail_panel_set_info (thumb->info);
  2280.     }
  2281.  
  2282.   DEBUGEND;
  2283. }
  2284.  
  2285. static void
  2286. selection_copy_files_to (GCHAR *pathname)
  2287. {
  2288.   selection_iterator     *iterator;
  2289.   Thumbnail        *selected;
  2290.   GCHAR            info[256], first_file[LINE_BUF_SIZE];
  2291.   gint            kind = NOT_EXIST;
  2292.   gint            success = 0;
  2293.  
  2294.   ASSERT (cwd_cache_validate () && selection_validate_image ());
  2295.  
  2296.   DEBUGBLOCK (N_("selection_copy_files_to (\"%s\")\n"), pathname);
  2297.  
  2298.   iterator = selection_make_iterator (SELECTION_ORDER_INDEX);
  2299.   while (NULL != (selected = selection_iterator_get_next_thumbnail (iterator)))
  2300.     {
  2301.       kind = os_file_kind (pathname, TRUE);
  2302.  
  2303.       if ((kind == NOT_EXIST) || (kind == DIRECTORY))
  2304.     {
  2305.       GString    *name, *new;
  2306.  
  2307.       name = g_string_new (cwd_cache->name);
  2308.       g_string_append_c (name, G_DIR_SEPARATOR);
  2309.       G_ASSERT (selected->name);
  2310.       g_string_append (name, selected->name);
  2311.       new = g_string_new (pathname);
  2312.       if (kind == DIRECTORY)
  2313.         {
  2314.           if (pathname [strlen (pathname) - 1] != G_DIR_SEPARATOR)
  2315.         g_string_append_c (new, G_DIR_SEPARATOR);
  2316.           g_string_append (new, selected->name);
  2317.         }
  2318.       if (os_file_kind (new->str, TRUE) != NOT_EXIST)
  2319.         {
  2320.           sprintf (info, _("%s already exists"), new->str);
  2321.           gtkW_message_dialog (TRUE, info);
  2322.         }
  2323.       else if (guash_copy_image_file (name->str, new->str) == TRUE)
  2324.         {
  2325.           guash_add_entry (new->str, selected);
  2326.  
  2327.           if (success++ == 0)
  2328.         strcpy (first_file, selected->name);
  2329.         }
  2330.  
  2331.       g_string_free (name, TRUE);
  2332.       g_string_free (new, TRUE);
  2333.     }
  2334.       else if (kind == REGFILE)
  2335.     {
  2336.       sprintf (info, _("%s already exists"), pathname);
  2337.       gtkW_message_dialog (TRUE, info);
  2338.     }
  2339.     }
  2340.  
  2341.   if (0 < success)
  2342.     cwd_cache_update_after_file_operation (success, "copied", first_file, pathname);
  2343.   else
  2344.     thumbnail_panel_set_info ("Failed to copy");
  2345.  
  2346.   RETURN;
  2347. }
  2348.  
  2349. static void
  2350. selection_delete_files ()
  2351. {
  2352.   selection_iterator         *iterator;
  2353.   selection_iterator_entry     *entry;
  2354.   GCHAR                first_file[LINE_BUF_SIZE];
  2355.   gint                success = 0;
  2356.  
  2357.   ASSERT (cwd_cache_validate () && selection_validate_image ());
  2358.  
  2359.   DEBUGBLOCK (N_("selection_delete_files\n"));
  2360.  
  2361.   iterator = selection_make_iterator (SELECTION_ORDER_INDEX);
  2362.   while (NULL != (entry = selection_iterator_get_next_entry (iterator)))
  2363.     {
  2364.       Thumbnail    *selected = entry->thumbnail;
  2365.       GCHAR    pathname[PATH_LENGTH];
  2366.  
  2367.       sprintf (pathname, "%s%c%s", cwd_cache->name, G_DIR_SEPARATOR, selected->name);
  2368.       if (os_delete_file (pathname) == 0)
  2369.     {
  2370.       GCHAR *thumbnail_name;
  2371.  
  2372.       thumbnail_name = pathname_build_thumbnail_filename (pathname);
  2373.       os_delete_file (thumbnail_name);
  2374.       selection_delete (entry->index);
  2375.       SET_ATTRIBUTE (selected, DELETED_P);
  2376.       if (success++ == 0)
  2377.         strcpy (first_file, selected->name);
  2378.       DPRINT (N_("success to delete %s\n"), selected->name);
  2379.  
  2380.       g_free (thumbnail_name);
  2381.     }
  2382.     }
  2383.   DPRINT (N_("done and display the page %d\n"), cwd_cache->display_page);
  2384.  
  2385.   if (0 < success)
  2386.     {
  2387.       SET_ATTRIBUTE (cwd_cache, DISORDERED_P);
  2388.       cwd_cache_update_after_file_operation (success, "deleted",
  2389.                          first_file, NULL);
  2390.     }
  2391.   else
  2392.     thumbnail_panel_set_info (_("Failed to delete (permission problem?)"));
  2393.  
  2394.   DEBUGEND;
  2395. }
  2396.  
  2397. static void
  2398. selection_move_files_to (GCHAR *pathname)
  2399. {
  2400.   selection_iterator        *iterator;
  2401.   selection_iterator_entry     *entry;
  2402.   GCHAR                info[256], first_file[LINE_BUF_SIZE];
  2403.   gint                kind = NOT_EXIST;
  2404.   gint                success = 0;
  2405.  
  2406.   ASSERT (cwd_cache_validate () && selection_validate_image ());
  2407.  
  2408.   DEBUGBLOCK (N_("selection_move_files_to (\"%s\")\n"), pathname);
  2409.  
  2410.   iterator = selection_make_iterator (SELECTION_ORDER_INDEX);
  2411.   while (NULL != (entry = selection_iterator_get_next_entry (iterator)))
  2412.     {
  2413.       Thumbnail *selected = entry->thumbnail;
  2414.  
  2415.       G_ASSERT (selected != NULL);
  2416.  
  2417.       kind = os_file_kind (pathname, TRUE); /* might be overwrited */
  2418.       if ((kind == NOT_EXIST) || (kind == DIRECTORY))
  2419.     {
  2420.       GCHAR src_name[PATH_LENGTH];
  2421.       GCHAR dest_name[PATH_LENGTH];
  2422.  
  2423.       sprintf (src_name, "%s%c%s", cwd_cache->name, G_DIR_SEPARATOR, selected->name);
  2424.       if (kind == DIRECTORY)
  2425.         if (pathname [strlen (pathname) - 1] != G_DIR_SEPARATOR)
  2426.           sprintf (dest_name, "%s%c%s", pathname, G_DIR_SEPARATOR, selected->name);
  2427.         else
  2428.           sprintf (dest_name, "%s%s", pathname, selected->name);
  2429.       else
  2430.         sprintf (dest_name, "%s", pathname);
  2431.  
  2432.       DPRINT (N_("src : %s, dest: %s\n"), src_name, dest_name);
  2433.  
  2434.       if (guash_move_image_file (src_name, dest_name))
  2435.         {
  2436.           guash_add_entry (dest_name, selected);
  2437.           selection_delete (entry->index);
  2438.           SET_ATTRIBUTE (selected, DELETED_P);
  2439.           SET_ATTRIBUTE (cwd_cache, DISORDERED_P);
  2440.           if (success++ == 0)
  2441.         strcpy (first_file, selected->name);
  2442.         }
  2443.     }
  2444.       else if (kind == REGFILE)
  2445.     {
  2446.       sprintf (info, _("%s already exists"), pathname);
  2447.       gtkW_message_dialog (TRUE, info);
  2448.     }
  2449.     }
  2450.   DPRINT (N_("success to move file(s)\n"));
  2451.  
  2452.   if (0 < success)
  2453.     cwd_cache_update_after_file_operation (success, "moved", first_file, pathname);
  2454.   else
  2455.     thumbnail_panel_set_info (_("Failed to move (permission problem?)"));
  2456.  
  2457.   RETURN;
  2458. }
  2459.  
  2460. /*
  2461.  * thumbnail_panel: visible element
  2462.  */
  2463. static void
  2464. thumbnail_panel_initialize_banner ()
  2465. {
  2466.   guchar    data[] = BASE_COLOR;
  2467.   guchar    *ptr;
  2468.  
  2469.   DEBUGBLOCK (N_("thumbnail_panel_initialize_banner\n"));
  2470.  
  2471.   if (VAL.last_dir_name[0] != 0)
  2472.     {
  2473.       banner_width = the_panel.width;
  2474.       banner_height = 2;    /* for BASE_COLOR and FRAME_COLOR */
  2475.     }
  2476.  
  2477.   /* to call thumbnail_panel_clear, only the 1st line should be cleared. */
  2478.   banner = image_buffer_new (banner_width, banner_height, 3);
  2479.   for (ptr = banner->data; ptr < banner->data + 3 * banner->width;)
  2480.     {
  2481.       *ptr++ = *data;
  2482.       *ptr++ = *(data + 1);
  2483.       *ptr++ = *(data + 2);
  2484.     }
  2485.  
  2486.   thumbnail_panel_clear ();
  2487.  
  2488.   if (VAL.last_dir_name[0] == 0)
  2489.     image_buffer_set_from_header (banner,
  2490.                   banner_data,
  2491.                   banner_width, banner_height);
  2492.   RETURN;
  2493. }
  2494.  
  2495. static void
  2496. thumbnail_panel_reinitialize_banner ()
  2497. {
  2498.   guchar    base[3] = BASE_COLOR;
  2499.   guchar    frame[3] = FRAME_COLOR;
  2500.   guchar    *ptr;
  2501.  
  2502.   DEBUGBLOCK (N_("thumbnail_panel_reinitialize_banner\n"));
  2503.  
  2504.   /* Then, we use banner to clear thumbnail_panel. Thus clear the 1st row. */
  2505.   for (ptr = banner->data; ptr < banner->data + 3 * banner->width;)
  2506.     {
  2507.       *ptr++ = *base;
  2508.       *ptr++ = *(base + 1);
  2509.       *ptr++ = *(base + 2);
  2510.     }
  2511.   /* And use second row to draw frame */
  2512.   for (ptr = banner->data + 3 * banner->width; ptr < banner->data + 6 * banner->width;)
  2513.     {
  2514.       *ptr++ = *frame;
  2515.       *ptr++ = *(frame + 1);
  2516.       *ptr++ = *(frame + 2);
  2517.     }
  2518.  
  2519.   RETURN;
  2520. }
  2521.  
  2522. static void
  2523. thumbnail_panel_show_banner_step1 ()
  2524. {
  2525.   guchar    white[3] = WHITE_COLOR;
  2526.   gint        x, y;
  2527.   gint        sx, sy;
  2528.  
  2529.   DEBUGBLOCK (N_("thumbnail_panel_show_banner_step1\n"));
  2530.  
  2531.   if (VAL.last_dir_name[0] != 0)
  2532.     {
  2533.       RETURN;
  2534.     }
  2535.  
  2536.   sx = MAX (0, ((the_panel.width - banner->width) / 2));
  2537.   sy = MAX (0, ((the_panel.height - banner->height) / 2));
  2538.  
  2539.   thumbnail_panel_draw_image_buffer (banner, sx, sy, FALSE);
  2540.  
  2541.   for (y = 19; y < 24; y++)
  2542.     for (x = 101; x < 106; x++)
  2543.       gtk_preview_draw_row (GTK_PREVIEW (thumbnail_panel), white,
  2544.                 sx + x, sy + y, 1);
  2545.  
  2546.   {
  2547.     gint    i;
  2548.     guchar    color[3];
  2549.     guchar    draw[3] = DRAW_COLOR;
  2550.  
  2551.     for (i = 0; i < 3; i++)
  2552.       color[i] = (2 * white[i] + draw[i]) / 3;
  2553.  
  2554.     thumbnail_panel_draw_string (GUASH_VERSION,
  2555.                  the_panel.width / 2, sy + banner->height,
  2556.                  color, TRUE, the_panel.width);
  2557.  
  2558.     gtkW_preview_force_to_update (thumbnail_panel);
  2559.   }
  2560.  
  2561.   RETURN;
  2562. }
  2563.  
  2564. static void
  2565. thumbnail_panel_show_banner_step2 ()
  2566. {
  2567.   guchar    color[3];
  2568.   guchar    draw[3] = DRAW_COLOR;
  2569.   guchar    white[3] = WHITE_COLOR;
  2570.   gint        sx, sy,  y;
  2571.   gint        i;
  2572.  
  2573.   DEBUGBLOCK (N_("thumbnail_panel_banner_step2\n"));
  2574.  
  2575.   sx = MAX (0, ((the_panel.width - banner->width) / 2));
  2576.   sy = MAX (0, ((the_panel.height - banner->height) / 2));
  2577.  
  2578.   thumbnail_panel_clear ();
  2579.   thumbnail_panel_draw_image_buffer (banner, sx, sy, FALSE);
  2580.  
  2581.   for (y = 18; y < 24; y++)
  2582.     gtk_preview_draw_row (GTK_PREVIEW (thumbnail_panel),
  2583.               banner->data + ((y * banner->width + 100) * 3),
  2584.               sx + 100, sy + y, 6);
  2585.  
  2586.   for (i = 0; i < 3; i++)
  2587.     color[i] = (white[i] + draw[i]) / 2;
  2588.  
  2589.   thumbnail_panel_draw_string (GUASH_VERSION,
  2590.                    the_panel.width / 2, sy + banner->height,
  2591.                    color, TRUE, the_panel.width);
  2592.  
  2593.   gtkW_preview_force_to_update (thumbnail_panel);
  2594.  
  2595.   RETURN;
  2596. }
  2597.  
  2598. static void
  2599. thumbnail_panel_show_banner_step3 ()
  2600. {
  2601.   guchar    draw[3] = DRAW_COLOR;
  2602.   guchar    white[3] = WHITE_COLOR;
  2603.   gint        sx, sy, x, y;
  2604.  
  2605.   DEBUGBLOCK (N_("thumbnail_panel_show_banner_step3\n"));
  2606.  
  2607.   sx = MAX (0, ((the_panel.width - banner->width) / 2));
  2608.   sy = MAX (0, ((the_panel.height - banner->height) / 2));
  2609.  
  2610.   thumbnail_panel_draw_string (GUASH_VERSION,
  2611.                    the_panel.width / 2, sy + banner->height,
  2612.                    draw, TRUE, the_panel.width);
  2613.  
  2614.   gtkW_preview_force_to_update (thumbnail_panel);
  2615.  
  2616.   for (y = 18; y < 24; y++)
  2617.     for (x = 101; x < 106; x++)
  2618.       gtk_preview_draw_row (GTK_PREVIEW (thumbnail_panel), white,
  2619.                 sx + x, sy + y, 1);
  2620.  
  2621.   for (y = 19; y < 24; y++)
  2622.     gtk_preview_draw_row (GTK_PREVIEW (thumbnail_panel),
  2623.               banner->data + ((y * banner->width + 100) * 3),
  2624.               sx + 100 + 3, sy + y + 2, 6);
  2625.  
  2626.   gtkW_preview_force_to_update (thumbnail_panel);
  2627.  
  2628.   RETURN;
  2629. }
  2630.  
  2631. static void
  2632. thumbnail_panel_clear ()
  2633. {
  2634.   gint    x, y, scan;
  2635.  
  2636.   if (! thumbnail_panel)
  2637.     return;
  2638.  
  2639.   DEBUGBLOCK (N_("thumbnail_panel_clear()\n"));
  2640.  
  2641.   for (y = 0; y < the_panel.height; y++)
  2642.     for (x = 0; x < the_panel.width; x += scan)
  2643.       {
  2644.     scan = MIN (the_panel.width - x, banner->width);
  2645.     gtk_preview_draw_row (GTK_PREVIEW (thumbnail_panel),
  2646.                   banner->data, x, y, scan);
  2647.       }
  2648.   gtk_widget_draw (thumbnail_panel, NULL);
  2649.  
  2650.   DEBUGEND;
  2651. }
  2652.  
  2653. static void
  2654. thumbnail_panel_draw_image_buffer (image_buffer *buffer,
  2655.                    gint sx,
  2656.                    gint sy,
  2657.                    gint align_p)
  2658. {
  2659.   gint    width, height;
  2660.   gint    y;
  2661.  
  2662.   G_ASSERT (buffer);
  2663.  
  2664.   DEBUGBLOCK (N_("thumbnail_panel_draw_image_buffer (width: %d, height %d)\n"),
  2665.           buffer->width, buffer->height);
  2666.  
  2667.   if (buffer->data == NULL)
  2668.     RETURN;
  2669.  
  2670.   if (align_p)
  2671.     {
  2672.       sx += (THUMBNAIL_WIDTH - buffer->width) / 2;
  2673.       sy += (THUMBNAIL_HEIGHT - buffer->height) / 2;
  2674.     }
  2675.  
  2676.   width = MIN (the_panel.width - sx, buffer->width);
  2677.   height = MIN (the_panel.height - sy, buffer->height);
  2678.  
  2679.   if (buffer->ch == 1)
  2680.     {
  2681.       guchar    fixed_buf[THUMBNAIL_WIDTH * 3];
  2682.       guchar    *dynamic_buffer = NULL;
  2683.       guchar    *buf = NULL;
  2684.       guchar    *data = buffer->data;
  2685.  
  2686.       DPRINT (N_("draw indexed image\n"));
  2687.  
  2688.       if (buffer->width <= THUMBNAIL_WIDTH)
  2689.     buf = fixed_buf;
  2690.       else
  2691.     buf = dynamic_buffer = g_new (guchar, buffer->width * 3);
  2692.  
  2693.       for (y = 0; y < height; y++)
  2694.     {
  2695.       gint        x;
  2696.       guchar    *tmp = buf;
  2697.  
  2698.       for (x = 0; x < buffer->width; x++)
  2699.         tmp = indexed_to_rgb (*data++, tmp);
  2700.  
  2701.       gtk_preview_draw_row (GTK_PREVIEW (thumbnail_panel),
  2702.                 buf,
  2703.                 sx,  sy + y,
  2704.                 width);
  2705.     }
  2706.       if (dynamic_buffer)
  2707.     g_free (dynamic_buffer);
  2708.     }
  2709.   else
  2710.     {
  2711.       for (y = 0; y < height; y++)
  2712.     gtk_preview_draw_row (GTK_PREVIEW (thumbnail_panel),
  2713.                   buffer->data + (y * buffer->width * 3),
  2714.                   sx,  sy + y,
  2715.                   width);
  2716.  
  2717.     }
  2718.   RETURN;
  2719. }
  2720.  
  2721. static void
  2722. thumbnail_panel_draw_string (GCHAR *string,
  2723.                  gint x,
  2724.                  gint y,
  2725.                  guchar *color,
  2726.                  gint align_p,
  2727.                  gint frame_width)
  2728. {
  2729.   GtkWidget    *widget = GTK_WIDGET (thumbnail_panel);
  2730.   GdkFont    *font = widget->style->font;
  2731.   GdkImage    *image = NULL;
  2732. #ifdef DRAW_STRING_BY_CHARACTER
  2733.   GCHAR        buffer[LINE_BUF_SIZE];
  2734. #endif
  2735.   guchar    base[3] = BASE_COLOR;
  2736.   guchar    *ptr = NULL;
  2737.   gint        ox, oy, width = 0, height;
  2738.  
  2739.   DEBUGBLOCK (N_("thumbnail_panel_draw_string (\"%s\",...)\n"), string);
  2740.  
  2741.   if ((string == NULL) || (string[0] == 0))
  2742.     RETURN;
  2743.  
  2744. #ifdef DRAW_STRING_BY_CHARACTER
  2745.   {
  2746.     gint    index = strlen (string);
  2747.     gint    rest;
  2748.     gint    font_width = the_panel.font_height * 2; /* should be safe */
  2749.  
  2750.     strcpy (buffer, string);
  2751.     while (0 < (rest = (width = gdk_string_width (font, buffer)) - frame_width))
  2752.       {
  2753.     index -= MAX (rest / font_width, 1);
  2754.     buffer[index] = 0;
  2755.       }
  2756.     string = buffer;
  2757.   }
  2758. #else
  2759.   width = gdk_string_width (font, string);
  2760.   width = MIN (width, frame_width);
  2761. #endif
  2762.  
  2763.   if (align_p)
  2764.     x -= width / 2;
  2765.  
  2766.   height = the_panel.font_height;
  2767.   if (the_panel.height <= y + height)
  2768.     {
  2769.       DPRINT (N_("abort:too tall\n"));
  2770.       RETURN;
  2771.     }
  2772.  
  2773.   if (! text_buffer || (text_buffer->width < width) || (text_buffer->height < height))
  2774.     {
  2775.       if (text_buffer)
  2776.     {
  2777.       gdk_pixmap_unref (text_buffer->pixmap);
  2778.       gdk_gc_destroy (text_buffer->gc);
  2779.       g_free (text_buffer->line_buffer);
  2780.       g_free (text_buffer);
  2781.     }
  2782.       text_buffer = g_new (Pixmap_struct, 1);
  2783.       text_buffer->width = width;
  2784.       text_buffer->height = height;
  2785.       text_buffer->pixmap = gdk_pixmap_new (NULL, width, height, 1);
  2786.       text_buffer->line_buffer = g_malloc (3 * width);
  2787.       text_buffer->gc = gdk_gc_new (text_buffer->pixmap);
  2788.       gdk_gc_set_font (text_buffer->gc, font);
  2789. #if !defined (GDK_WINDOWING_WIN32)
  2790.       text_buffer->black.pixel
  2791.     = BlackPixel (((Display *) GDK_DISPLAY()),
  2792.               DefaultScreen (((Display *) GDK_DISPLAY())));
  2793. #else
  2794.       text_buffer->black.pixel = 0;
  2795.       text_buffer->black.red = 
  2796.       text_buffer->black.green = 
  2797.       text_buffer->black.blue = 0;
  2798. #endif
  2799. #if !defined (GDK_WINDOWING_WIN32) 
  2800.       text_buffer->white.pixel
  2801.     = WhitePixel (((Display *) GDK_DISPLAY()),
  2802.               DefaultScreen (((Display *) GDK_DISPLAY())));
  2803. #else
  2804.       /* //HB: ??? */
  2805.       text_buffer->white.pixel = 1;
  2806.       text_buffer->white.red = 
  2807.       text_buffer->white.green = 
  2808.       text_buffer->white.blue = 65535;
  2809. #endif
  2810.  
  2811.       DPRINT (N_("success to build text_buffer\n"));
  2812.     }
  2813.   gdk_gc_set_foreground (text_buffer->gc, &text_buffer->white);
  2814.   gdk_draw_rectangle (text_buffer->pixmap, text_buffer->gc, 1, 0, 0,
  2815.               text_buffer->width, text_buffer->height);
  2816.   gdk_gc_set_foreground (text_buffer->gc, &text_buffer->black);
  2817.  
  2818.   gdk_draw_string (text_buffer->pixmap, font, text_buffer->gc,
  2819.            0, font->ascent, string);
  2820.   DPRINT (N_("success to draw \"%s\" on text_buffer\n"), string);
  2821.  
  2822.   /* then make GdkImage */
  2823.   if (! (image = gdk_image_get (text_buffer->pixmap, 0, 0, width, height)))
  2824.     {
  2825.       DPRINT (N_("abort: fail to gdk_image_get\n"));
  2826.       RETURN;
  2827.     }
  2828.  
  2829.   for (oy = 0; oy < height; oy++)
  2830.     {
  2831.       ptr = text_buffer->line_buffer;
  2832.       for (ox = 0; ox < width; ox++)
  2833.     {
  2834.       if (gdk_image_get_pixel (image, ox, oy) == text_buffer->black.pixel)
  2835.         {
  2836.           *ptr++ = *color;
  2837.           *ptr++ = *(color + 1);
  2838.           *ptr++ = *(color + 2);
  2839.         }
  2840.       else
  2841.         {
  2842.           *ptr++ = *base;
  2843.           *ptr++ = *(base + 1);
  2844.           *ptr++ = *(base + 2);
  2845.         }
  2846.     }
  2847.       /* copy it to preview's buffer */
  2848.       gtk_preview_draw_row (GTK_PREVIEW (thumbnail_panel),
  2849.                 text_buffer->line_buffer, x, y + oy, width);
  2850.     }
  2851.  
  2852.   DPRINT (N_("success to gtk_preview_draw_row\n"));
  2853.  
  2854.   gdk_image_destroy (image);
  2855.  
  2856.   RETURN;
  2857. }
  2858.  
  2859. static gint
  2860. thumbnail_panel_draw_thumbnail (Thumbnail *thumb)
  2861. {
  2862.   guchar    draw[3] = DRAW_COLOR;
  2863.   guchar    slink[3] = SLINK_COLOR;
  2864.   guchar    *color = NULL;
  2865.   gint        sx, sy;
  2866.   gint        pdir_p;
  2867.   image_buffer    *icon = NULL;
  2868.  
  2869.   G_ASSERT (thumb);
  2870.  
  2871.   __DEBUGBLOCK (N_("thumbnail_panel_draw_thumbnail of %s\n"), thumb->name);
  2872.  
  2873.   the_panel.last_index++;
  2874.   if (the_panel.nthumb_in_page <= the_panel.last_index)
  2875.     {
  2876.       RETURN FALSE;
  2877.     }
  2878.  
  2879.   sx = INDEX_TO_X (the_panel.last_index);
  2880.   sy = INDEX_TO_Y (the_panel.last_index);
  2881.  
  2882.   pdir_p = HAS_ATTRIBUTE (thumb, PARENT_DIRECTORY_P);
  2883.   color = HAS_ATTRIBUTE (thumb, SYMLINK_P) ? slink : draw;
  2884.  
  2885.   if (HAS_ATTRIBUTE (thumb, DIRECTORY_P))
  2886.     icon = pdir_p ? pdir_icon : dir_icon;
  2887.   else
  2888.     {
  2889.       if (thumb->image &&
  2890.       (0 < thumb->image->width) && (0 < thumb->image->height))
  2891.     icon = thumb->image;
  2892.       else
  2893.     icon = file_icon;
  2894.     }
  2895.  
  2896.   thumbnail_panel_draw_image_buffer (icon, sx, sy, TRUE);
  2897.   thumbnail_panel_draw_string ((pdir_p ? "..(parent)" : thumb->name),
  2898.                    sx + THUMBNAIL_WIDTH / 2,
  2899.                    sy + THUMBNAIL_HEIGHT + THUMBNAIL_TSEPARATOR,
  2900.                    color,
  2901.                    TRUE,
  2902.                    THUMBNAIL_WIDTH);
  2903.   DPRINT (N_("success to draw_string\n"));
  2904.  
  2905.   gtkW_preview_force_to_update_partially (thumbnail_panel,
  2906.                       sx - THUMBNAIL_BORDER_WIDTH - 1,
  2907.                       sy - THUMBNAIL_BORDER_WIDTH - 1,
  2908.                       THUMBNAIL_WIDTH + THUMBNAIL_SEPARATOR,
  2909.                       THUMBNAIL_FULLHEIGHT + THUMBNAIL_SEPARATOR);
  2910.  
  2911.   DPRINT (N_("success to gtkW_preview_force_to_update_partially\n"));
  2912.  
  2913.   RETURN TRUE;
  2914. }
  2915.  
  2916. static void
  2917. thumbnail_panel_set_directory_info ()
  2918. {
  2919.   GCHAR    title[LINE_BUF_SIZE];
  2920.   GCHAR label[LINE_BUF_SIZE];
  2921.   gint    from = 0;
  2922.  
  2923.   sprintf (title, "%s: %s", SHORT_NAME, pathname_get_basename (cwd_cache->name));
  2924.   gtk_window_set_title (GTK_WINDOW (dlg), title);
  2925.  
  2926.   the_panel.current_page1 = cwd_cache->display_page + 1;
  2927.  
  2928.   if (1 < cwd_cache_npage ())
  2929.     sprintf (label,
  2930.          "%s%s (%d/%d by %s) ",
  2931.          (from == 0) ? "" : "*",
  2932.          cwd_cache->name + from,
  2933.          cwd_cache->display_page + 1,
  2934.          cwd_cache_npage (),
  2935.          (HAS_ATTRIBUTE (&VAL, SORT_BY_NAME_P) ? "name" : "date"));
  2936.   else
  2937.     sprintf (label,
  2938.          "%s%s (by %s)",
  2939.          (from == 0) ? "" : "*",
  2940.          cwd_cache->name + from,
  2941.          (HAS_ATTRIBUTE (&VAL, SORT_BY_NAME_P) ? "name" : "date"));
  2942. #ifdef GTK_HAVE_FEATURES_1_1_0
  2943.   gtk_label_set_text (GTK_LABEL (cwd_label), label);
  2944. #else
  2945.   gtk_label_set (GTK_LABEL (cwd_label), label);
  2946. #endif
  2947.   gtk_label_set_justify (GTK_LABEL (cwd_label), GTK_JUSTIFY_FILL);
  2948. }
  2949.  
  2950. static void
  2951. thumbnail_panel_set_info (GCHAR *name)
  2952. {
  2953.   G_ASSERT (GTK_IS_LABEL (file_property));
  2954. #ifdef GTK_HAVE_FEATURES_1_1_0
  2955.   gtk_label_set_text (GTK_LABEL (file_property),
  2956.               ((name == NULL) ? the_panel.info : name));
  2957. #else
  2958.   gtk_label_set (GTK_LABEL (file_property),
  2959.          ((name == NULL) ? the_panel.info : name));
  2960. #endif
  2961.   /* Since info field should be updated immediately, I use gtk_widget_draw ()
  2962.      instead of gtk_widget_queue_draw () here. */
  2963.   gtk_widget_realize (file_property);
  2964.   gtk_widget_show (file_property);
  2965.  
  2966.   gtk_widget_queue_draw (file_property);
  2967.   gtk_widget_draw (file_property, NULL);
  2968.  
  2969.   during_buildup_thumbnail_panel = TRUE;
  2970.   while (gtk_events_pending())
  2971.     gtk_main_iteration();
  2972.   during_buildup_thumbnail_panel = FALSE;
  2973.  
  2974.   gdk_flush ();
  2975. }
  2976.  
  2977. static void
  2978. thumbnail_panel_set_info_default ()
  2979. {
  2980.   if (! cwd_cache)
  2981.     the_panel.info[0] = 0;
  2982.   else if (1 == cwd_cache->ndir)
  2983.     sprintf (the_panel.info, "%d %s",
  2984.          cwd_cache->nimage,
  2985.          (HAS_ATTRIBUTE (&VAL, DISPLAY_IMAGE_P)
  2986.           ? ((1 < cwd_cache->nimage) ? _("images") : _("image"))
  2987.           : ((1 < cwd_cache->nimage) ? _("files") : _("file"))));
  2988.   else
  2989.     sprintf (the_panel.info, _("%d %s and %d %s"),
  2990.          cwd_cache->nimage,
  2991.          (HAS_ATTRIBUTE (&VAL, DISPLAY_IMAGE_P)
  2992.           ? ((1 < cwd_cache->nimage) ? _("images") : _("image"))
  2993.           : ((1 < cwd_cache->nimage) ? _("files") : _("file"))),
  2994.          (cwd_cache->ndir - 1),
  2995.          ((2 < cwd_cache->ndir) ? _("subdirectories") : _("subdirectory")));
  2996. }
  2997.  
  2998. static void
  2999. thumbnail_panel_draw_frame (gint index, gint draw_p)
  3000. {
  3001.   gint        x, y;
  3002.   gint        xi, yi;
  3003.   gint        endx;
  3004.   gint        scan = 0;
  3005.   gint        border = THUMBNAIL_BORDER_WIDTH;
  3006.   guchar     *data;
  3007.   GtkPreview    *preview = GTK_PREVIEW (thumbnail_panel);
  3008.  
  3009.   index -= cwd_cache->display_page * the_panel.nthumb_in_page;
  3010.   x = INDEX_TO_X (index);
  3011.   y = INDEX_TO_Y (index);
  3012.  
  3013.   data = banner->data + (draw_p ? 3 * banner->width : 0);
  3014.  
  3015.   /* draw top */
  3016.   endx = x + THUMBNAIL_WIDTH + border + 1;
  3017.   for (yi = y - border - 1;
  3018.        yi < y - 1;
  3019.        yi++)
  3020.     for (xi = x - border - 1;
  3021.      xi < endx;
  3022.      xi += scan)
  3023.       {
  3024.     scan = MIN (endx - xi, banner->width);
  3025.     gtk_preview_draw_row (preview, data, xi, yi, scan);
  3026.       }
  3027.   /* draw left */
  3028.   endx = x - 1;
  3029.   for (yi = y - border - 1;
  3030.        yi < y + THUMBNAIL_FULLHEIGHT + border + 1;
  3031.        yi++)
  3032.     for (xi = x - border - 1;
  3033.      xi < endx;
  3034.      xi += scan)
  3035.       {
  3036.     scan = MIN (endx - xi, banner->width);
  3037.     gtk_preview_draw_row (preview, data, xi, yi, scan);
  3038.       }
  3039.   /* draw right */
  3040.   endx = x + THUMBNAIL_WIDTH + border + 1;
  3041.   for (yi = y - border - 1;
  3042.        yi < y + THUMBNAIL_FULLHEIGHT + border + 1;
  3043.        yi++)
  3044.     for (xi = x + THUMBNAIL_WIDTH + 1;
  3045.      xi < endx;
  3046.      xi += scan)
  3047.       {
  3048.     scan = MIN (endx - xi, banner->width);
  3049.     gtk_preview_draw_row (preview, data, xi, yi, scan);
  3050.       }
  3051.   /* draw bottom */
  3052.   for (yi = y + THUMBNAIL_FULLHEIGHT + 1;
  3053.        yi < y + THUMBNAIL_FULLHEIGHT + border + 1;
  3054.        yi++)
  3055.     for (xi = x - border - 1;
  3056.      xi < endx;
  3057.      xi += scan)
  3058.       {
  3059.     scan = MIN (endx - xi, banner->width);
  3060.     gtk_preview_draw_row (preview, data, xi, yi, scan);
  3061.       }
  3062. }
  3063.  
  3064. static void
  3065. thumbnail_panel_draw_selection_frame (gint mode)
  3066. {
  3067.   gint    max, index;
  3068.   gint    flag = FALSE;
  3069.  
  3070.   max = MIN ((1 + cwd_cache->display_page) * the_panel.nthumb_in_page,
  3071.          directory_cache_num_entry (cwd_cache));
  3072.  
  3073.   for (index = cwd_cache->display_page * the_panel.nthumb_in_page;
  3074.        index < max;
  3075.        index++)
  3076.     if (HAS_ATTRIBUTE (directory_cache_get_nth (cwd_cache, index), SELECTED_P))
  3077.       {
  3078.     thumbnail_panel_draw_frame (index, mode);
  3079.     flag = TRUE;
  3080.       }
  3081.   if (flag)
  3082.     gtk_widget_queue_draw (thumbnail_panel);
  3083. }
  3084.  
  3085. static void
  3086. thumbnail_panel_finalize_update ()
  3087. {
  3088.   __DEBUGBLOCK (N_("thumbnail_panel_finalize_update\n"));
  3089.  
  3090.   the_panel.current_page1 = cwd_cache->display_page + 1;
  3091.   thumbnail_panel_set_directory_info ();
  3092.   thumbnail_panel_set_info_default ();
  3093.   if (selection_is_active ())
  3094.     {
  3095.       Thumbnail *selected;
  3096.  
  3097.       selected = directory_cache_get_nth (cwd_cache,
  3098.                       cwd_cache->selection_top);
  3099.       thumbnail_panel_draw_selection_frame (TRUE);
  3100.       thumbnail_panel_set_info (selected->info);
  3101.     }
  3102.   else
  3103.     thumbnail_panel_set_info (NULL);
  3104.   thumbnail_panel_update_sensitive_menu ();
  3105.  
  3106.   gdk_flush ();
  3107.  
  3108.   RETURN;
  3109. }
  3110.  
  3111. static void
  3112. thumbnail_panel_size_request (GtkWidget      *widget,
  3113.                   GtkRequisition *requisition)
  3114. {
  3115.   gint    ncol, nrow;
  3116.  
  3117.   g_return_if_fail (widget != NULL);
  3118.   g_return_if_fail (GTK_IS_PREVIEW (widget));
  3119.   g_return_if_fail (requisition != NULL);
  3120.  
  3121.   __DEBUGBLOCK (N_("thumbnail_panel_size_request\n"));
  3122.  
  3123.   if (! cwd_cache)
  3124.     {
  3125.       ncol = the_panel.ncol;
  3126.       nrow = the_panel.nrow;
  3127.     }
  3128.   else
  3129.     {
  3130.       ncol = NCOL_OF_THUMBNAIL_PANEL_MIN;
  3131.       nrow = NROW_OF_THUMBNAIL_PANEL_MIN;
  3132.     }
  3133.   requisition->width = COL2WIDTH (ncol);
  3134.   requisition->height = ROW2HEIGHT (nrow);
  3135.  
  3136.   RETURN;
  3137. }
  3138.  
  3139. static gint
  3140. thumbnail_panel_size_allocate (GtkWidget *widget, gpointer value)
  3141. {
  3142.   g_return_val_if_fail (widget != NULL, FALSE);
  3143.   g_return_val_if_fail (GTK_IS_DIALOG (widget), FALSE);
  3144.  
  3145.   __DEBUGBLOCK (N_("thumbnail_panel_size_allocate\n"));
  3146.  
  3147.   /* widget->allocation = *allocation;*/
  3148.   widget = thumbnail_panel;
  3149.  
  3150.   if ((the_panel.width != widget->allocation.width)
  3151.       || (the_panel.height != widget->allocation.height))
  3152.     the_panel.resized = TRUE;
  3153.  
  3154.   the_panel.width = widget->allocation.width;
  3155.   the_panel.height = widget->allocation.height;
  3156.  
  3157.   the_panel.ncol = WIDTH2COL (the_panel.width);
  3158.   the_panel.nrow = HEIGHT2ROW (the_panel.height);
  3159.   the_panel.nthumb_in_page = the_panel.ncol * the_panel.nrow;
  3160.  
  3161.   if (GTK_WIDGET_REALIZED (widget))
  3162.     {
  3163.       if (cwd_cache)
  3164.     if (((GtkAdjustment *) widget_pointer[0].widget)->upper != cwd_cache_npage () + 1)
  3165.       {
  3166.         the_panel.current_page1 = cwd_cache_npage () + 1;
  3167.         ((GtkAdjustment *) widget_pointer[0].widget)->upper
  3168.           = cwd_cache_npage () + 1;
  3169.       }
  3170.     }
  3171.   gtk_preview_size (GTK_PREVIEW (thumbnail_panel),
  3172.             the_panel.width,
  3173.             the_panel.height);
  3174.  
  3175.   RETURN FALSE;
  3176. }
  3177.  
  3178. /* this function does not check the current size of thumbnail_panel */
  3179. static gint
  3180. thumbnail_panel_update ()
  3181. {
  3182.   gint    index, max;
  3183.  
  3184.   DEBUGBLOCK (N_("thumbnail_panel_update\n"));
  3185.  
  3186.   if (! cwd_cache)
  3187.     RETURN FALSE;
  3188.  
  3189.   gtkW_widget_set_cursor (dlg, CURSOR_WAIT);
  3190.   gtkW_widget_set_cursor (thumbnail_panel, CURSOR_WAIT);
  3191.   /* from 0.99.5, this function must also check the page number, since resize
  3192.      changes the number of pages. */
  3193.   DPRINT (N_("display_page: %d, max_page: %d\n"),
  3194.       cwd_cache->display_page,
  3195.       (cwd_cache_npage () - 1));
  3196.   cwd_cache->display_page = CLAMP (cwd_cache->display_page,
  3197.                    0,
  3198.                    (cwd_cache_npage () - 1));
  3199.   the_panel.current_page1 = cwd_cache->display_page + 1;
  3200.   thumbnail_panel_clear ();
  3201.  
  3202.   max = MIN ((1 + cwd_cache->display_page) * the_panel.nthumb_in_page,
  3203.          directory_cache_num_entry (cwd_cache));
  3204.  
  3205.   the_panel.last_index = -1;
  3206.   for (index = cwd_cache->display_page * the_panel.nthumb_in_page;
  3207.        index < max;
  3208.        index++)
  3209.     thumbnail_panel_draw_thumbnail (directory_cache_get_nth (cwd_cache, index));
  3210.  
  3211.   thumbnail_panel_finalize_update ();
  3212.  
  3213.   DPRINT (N_("success to call thumbnail_panel_finalize_update\n"));
  3214.  
  3215.   gtkW_widget_set_cursor (dlg, CURSOR_DEFAULT);
  3216.   gtkW_widget_set_cursor (thumbnail_panel, CURSOR_HAND);
  3217.  
  3218.   RETURN TRUE;
  3219. }
  3220.  
  3221. static gint
  3222. thumbnail_panel_update_delayed (GtkWidget *widget)
  3223. {
  3224.   if (0 < delayed_updating)
  3225.     {
  3226.       delayed_updating = 0;
  3227.       thumbnail_panel_update ();
  3228.     }
  3229.  /* By returning FALSE, this idle_func is removed automatically. */
  3230.   return FALSE;
  3231. }
  3232.  
  3233. /* this function does not check the current size of thumbnail_panel */
  3234. static gint
  3235. thumbnail_panel_update_partially (gint index)
  3236. {
  3237.   gint    shown_first = cwd_cache->display_page * the_panel.nthumb_in_page;
  3238.   gint    unshown_first = shown_first + the_panel.nthumb_in_page;
  3239.  
  3240.   DEBUGBLOCK (N_("thumbnail_panel_update_partially\n"));
  3241.  
  3242.   if ((index < shown_first) || (unshown_first <= index))
  3243.     RETURN FALSE;
  3244.  
  3245.   if (index == shown_first)
  3246.     {
  3247.       the_panel.last_index = -1;
  3248.       thumbnail_panel_clear ();
  3249.     }
  3250.   thumbnail_panel_draw_thumbnail (directory_cache_get_nth (cwd_cache, index));
  3251.   DPRINT (N_("success to thumbnail_panel_draw_thumbnail\n"));
  3252.  
  3253.   RETURN (index == unshown_first - 1);
  3254. }
  3255.  
  3256. static void
  3257. thumbnail_panel_update_sensitive_menu ()
  3258. {
  3259.   DEBUGBLOCK (N_("thumbnail_panel_update_sensitive_menu\n"));
  3260.  
  3261.   thumbnail_panel_update_selection_buttons ();
  3262.   thumbnail_panel_update_scroller ();
  3263.  
  3264.   RETURN;
  3265. }
  3266.  
  3267. static void
  3268. thumbnail_panel_update_selection_buttons ()
  3269. {
  3270.   gint    ncommand =
  3271.     sizeof (image_selection_commands) / sizeof (image_selection_commands[0]);
  3272.   gint    flag = selection_is_active ();
  3273.   gint    i;
  3274.  
  3275.   for (i = 0; i < ncommand; i++)
  3276.     if (image_selection_commands[i].sensitive & SENSITIVE_SELECTION)
  3277.       gtk_widget_set_sensitive (image_selection_commands[i].widget, flag);
  3278.   for (i = 0 ; i < NSELECTION_BUTTON; i++)
  3279.     if (widget_for_selecion[i] != NULL)
  3280.       gtk_widget_set_sensitive (widget_for_selecion[i], flag);
  3281.  
  3282. #ifndef NEW_DND_API
  3283.   for (i = 0 ; i < NSELECTION_BUTTON; i++)
  3284.     if (widget_for_selecion[i] != NULL)
  3285.       gtk_widget_dnd_drag_set (widget_for_selecion[i],
  3286.                    flag, possible_dnd_types, 1);
  3287. #endif
  3288. }
  3289.  
  3290. static void
  3291. thumbnail_panel_update_scroller ()
  3292. {
  3293.   gint        ncommand = sizeof (image_root_commands) / sizeof (image_root_commands[0]);
  3294.   gint        i;
  3295.   gint        flags[2];
  3296.   GtkAdjustment *adjustment;
  3297.  
  3298.   DEBUGBLOCK (N_("thumbnail_panel_update_scroller display_page %d, max %d\n"),
  3299.           cwd_cache->display_page, cwd_cache_npage ());
  3300.  
  3301.   if (! cwd_cache)
  3302.     RETURN;
  3303.   if ((widget_pointer[0].widget == NULL) || (widget_pointer[0].updater == NULL))
  3304.     RETURN;
  3305.  
  3306.   flags[0] = flags[1] = TRUE;    /* 0 for back(prev), 1 for next */
  3307.   if (cwd_cache->display_page == 0)
  3308.     flags[0] = FALSE;
  3309.   if (cwd_cache_npage () <= 1 + cwd_cache->display_page)
  3310.     flags[1] = FALSE;
  3311.  
  3312.   {
  3313.     GCHAR buffer[GTKW_ENTRY_BUFFER_SIZE];
  3314.  
  3315.     sprintf (buffer, "%d", the_panel.current_page1);
  3316.     gtk_entry_set_text (GTK_ENTRY (widget_for_scroll[1]), buffer);
  3317.   }
  3318.  
  3319.   adjustment = (GtkAdjustment *) widget_pointer[0].widget;
  3320.   adjustment->value = the_panel.current_page1;
  3321.  
  3322.   if (adjustment->upper != cwd_cache_npage () + 1)
  3323.     {
  3324.       gdouble    current_page = the_panel.current_page1;
  3325.  
  3326.       DPRINT (N_("change upper from %d to %d\n"),
  3327.           (gint) adjustment->upper,
  3328.           cwd_cache_npage () + 1);
  3329.  
  3330.       adjustment->upper    = cwd_cache_npage () + 1;
  3331.       /* force update! Wed Aug 26 11:01:02 1998 */
  3332.       adjustment->value = -1;
  3333.       /* From 0.99.5 changes of the_panel.current_page1 propagates here. */
  3334.       gtk_signal_emit_by_name (GTK_OBJECT (adjustment), "changed");
  3335.  
  3336.       DPRINT (N_("adjustment->value :%d, old :%d\n"),
  3337.           (gint) adjustment->value,
  3338.           (gint) current_page);
  3339.       /* as a side-effect, value is reseted. [Mon Sep 14 15:05:45 1998] */
  3340.       adjustment->value = current_page;
  3341.       the_panel.current_page1 = current_page;
  3342.       /* dirty design! to stop the 2nd panel flush, cancel the result of
  3343.      signal_emit [Wed Sep 16 19:15:02 1998] */
  3344.       _DPRINT (N_("delayed updating %d\n"), delayed_updating);
  3345.       delayed_updating = 0;
  3346.     }
  3347.   /* The following statement is still required to update scroll-bar. */
  3348.   (widget_pointer[0].updater) (widget_pointer);
  3349.  
  3350.   for (i = 0; i < NSCROLLER; i++)
  3351.     {
  3352.       gtk_widget_set_sensitive (widget_for_scroll[i],
  3353.                 (1 < cwd_cache_npage ()));
  3354.       gtk_widget_queue_draw (widget_for_scroll[i]);
  3355.     }
  3356.   {
  3357.     gint    j = 1;
  3358.  
  3359.     for (i = 0; i < ncommand; i++)
  3360.       if (image_root_commands[i].sensitive & SENSITIVE_SCROLL)
  3361.     {
  3362.       gtk_widget_set_sensitive (image_root_commands[i].widget, flags[j]);
  3363.       j = 0;
  3364.     }
  3365.   }
  3366.  
  3367.   DPRINT (N_("display_page: %d\n"), cwd_cache->display_page);
  3368.  
  3369.   RETURN;
  3370. }
  3371.  
  3372. static void
  3373. thumbnail_panel_create_menu_for (GtkWidget *menu,
  3374.                  image_command_table *commands,
  3375.                  gint ncommand,
  3376. #ifdef GTK_HAVE_FEATURES_1_1_0
  3377.                  GtkAccelGroup *accel_group
  3378. #else
  3379.                  GtkAcceleratorTable *accelerator_table
  3380. #endif
  3381. )
  3382. {
  3383.   GtkWidget    *menu_item;
  3384.   gint    i;
  3385.  
  3386. #ifdef GTK_HAVE_FEATURES_1_1_0
  3387.   gtk_menu_set_accel_group (GTK_MENU (menu), accel_group);
  3388. #else
  3389.   gtk_menu_set_accelerator_table (GTK_MENU (menu), accelerator_table);
  3390. #endif
  3391.   for (i = 0; i < ncommand; i++)
  3392.     {
  3393.       if (commands[i].label)
  3394.     menu_item = gtk_menu_item_new_with_label (commands[i].label);
  3395.       else
  3396.     menu_item = gtk_menu_item_new ();
  3397.  
  3398.       commands[i].widget = menu_item;
  3399.       gtk_menu_append (GTK_MENU (menu), menu_item);
  3400.       if (commands[i].command)
  3401.     gtk_signal_connect (GTK_OBJECT (menu_item), "activate",
  3402.                 (GtkSignalFunc) commands[i].command,
  3403.                 (gpointer) commands[i].label);
  3404.       else if (commands[i].label)
  3405.     gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu_item),
  3406.                    thumbnail_panel_root_menu);
  3407.  
  3408.       if (commands[i].label
  3409.       && (commands[i].binding[binding_style].key != 0))
  3410. #ifdef GTK_HAVE_FEATURES_1_1_0
  3411.     gtk_widget_add_accelerator (menu_item,
  3412.                     "activate",
  3413.                     accel_group,
  3414.                     commands[i].binding[binding_style].key,
  3415.                     commands[i].binding[binding_style].mod,
  3416.                     GTK_ACCEL_VISIBLE | GTK_ACCEL_LOCKED);
  3417. #else
  3418.       gtk_widget_install_accelerator (menu_item,
  3419.                       accelerator_table,
  3420.                       "activate",
  3421.                       commands[i].binding[binding_style].key,
  3422.                       commands[i].binding[binding_style].mod);
  3423. #endif
  3424.       gtk_widget_show (menu_item);
  3425.       if (! commands[i].label)    /* separator should not focus on */
  3426.     gtk_widget_set_sensitive (menu_item, FALSE);
  3427.     }
  3428. #ifndef GTK_HAVE_FEATURES_1_1_0
  3429.   gtk_window_add_accelerator_table (GTK_WINDOW (dlg), accelerator_table);
  3430. #endif
  3431. }
  3432.  
  3433. static void
  3434. thumbnail_panel_create_menu ()
  3435. {
  3436. #ifdef GTK_HAVE_FEATURES_1_1_0
  3437.   GtkAccelGroup        *accel_group = gtk_accel_group_new ();
  3438. #else
  3439.   GtkAcceleratorTable    *accelerator_table = gtk_accelerator_table_new ();
  3440. #endif
  3441.   gint    modified = FALSE;
  3442.  
  3443.   DEBUGBLOCK (N_("thumbail_panel_create_menu\n"));
  3444.   if (! thumbnail_panel_root_menu)
  3445.     {
  3446.       thumbnail_panel_root_menu = gtk_menu_new ();
  3447.       thumbnail_panel_create_menu_for (thumbnail_panel_root_menu,
  3448.                        image_root_commands,
  3449.                        sizeof (image_root_commands)
  3450.                        / sizeof (image_root_commands[0]),
  3451. #ifdef GTK_HAVE_FEATURES_1_1_0
  3452.                        accel_group
  3453. #else
  3454.                        accelerator_table
  3455. #endif
  3456.                        );
  3457.       modified = TRUE;
  3458.     }
  3459.   if (! thumbnail_panel_selection_menu)
  3460.     {
  3461.       thumbnail_panel_selection_menu = gtk_menu_new ();
  3462.       thumbnail_panel_create_menu_for (thumbnail_panel_selection_menu,
  3463.                        image_selection_commands,
  3464.                        sizeof (image_selection_commands)
  3465.                        / sizeof (image_selection_commands[0]),
  3466. #ifdef GTK_HAVE_FEATURES_1_1_0
  3467.                        accel_group
  3468. #else
  3469.                        accelerator_table
  3470. #endif
  3471.                        );
  3472.       modified = TRUE;
  3473.     }
  3474.   if (! thumbnail_panel_hidden_menu)
  3475.     {
  3476.       thumbnail_panel_hidden_menu = gtk_menu_new ();
  3477.       thumbnail_panel_create_menu_for (thumbnail_panel_hidden_menu,
  3478.                        image_hidden_commands,
  3479.                        sizeof (image_hidden_commands)
  3480.                        / sizeof (image_hidden_commands[0]),
  3481. #ifdef GTK_HAVE_FEATURES_1_1_0
  3482.                        accel_group
  3483. #else
  3484.                        accelerator_table
  3485. #endif
  3486.                        );
  3487.       modified = TRUE;
  3488.     }
  3489.   if (modified)
  3490. #ifdef GTK_HAVE_FEATURES_1_1_0
  3491.     gtk_window_add_accel_group (GTK_WINDOW (dlg), accel_group);
  3492. #else
  3493.     gtk_window_add_accelerator_table (GTK_WINDOW (dlg),accelerator_table);
  3494. #endif
  3495.   RETURN;
  3496. }
  3497.  
  3498. static gint
  3499. thumbnail_panel_move_focus (gint offset)
  3500. {
  3501.   gint    index = -1;
  3502.   gint    new_index = -1;
  3503.   gint        new_page = cwd_cache->display_page;
  3504.   Thumbnail    *thumbnail;
  3505.  
  3506.   DEBUGBLOCK (N_("thumbnail_panel_move_focus (%d)\n"), offset);
  3507.  
  3508.   if (selection_is_active ())
  3509.     {
  3510.       index = cwd_cache->last_focus;
  3511.       if (! directory_cache_valid_image_index (cwd_cache, index))
  3512.     index = cwd_cache->selection_top;
  3513.       new_index = index + offset;
  3514.     }
  3515.   else
  3516.     if (0 < cwd_cache->nimage)
  3517.       {
  3518.     if (0 < offset)
  3519.       new_index = MAX ((cwd_cache->display_page * the_panel.nthumb_in_page),
  3520.                cwd_cache->ndir);
  3521.     else
  3522.       new_index = MIN ((1 + cwd_cache->display_page) * the_panel.nthumb_in_page -1,
  3523.                (directory_cache_num_entry (cwd_cache) -1));
  3524.       }
  3525.   if (directory_cache_valid_image_index (cwd_cache, new_index))
  3526.     {
  3527.       if (0 < index)
  3528.     selection_delete (index);
  3529.       new_page = new_index / (the_panel.ncol * the_panel.nrow);
  3530.       if (new_page != cwd_cache->display_page)
  3531.     {
  3532.       cwd_cache->display_page = new_page;
  3533.       thumbnail_panel_update ();
  3534.     }
  3535.       selection_add_and_show (new_index);
  3536.       thumbnail = directory_cache_get_nth (cwd_cache, new_index);
  3537.       thumbnail_panel_set_info (thumbnail->info);
  3538.       cwd_cache->last_focus = new_index;
  3539.     }
  3540.   else
  3541.     thumbnail_panel_set_info (NULL);
  3542.  
  3543.   thumbnail_panel_update_sensitive_menu ();
  3544.  
  3545.   RETURN new_index;
  3546. }
  3547.  
  3548. /*
  3549.  * directory_cache: internal data structure
  3550.  */
  3551. static void
  3552. directory_cache_initialize (directory_cache *dcache)
  3553. {
  3554.   INITIALIZE_ATTRIBUTES (dcache);
  3555.  
  3556.   dcache->savable = FALSE;
  3557.   dcache->ndir = 0;
  3558.   dcache->nimage = 0;
  3559.   dcache->display_page = 0;
  3560.   dcache->selection_timestamp = 0;
  3561.   dcache->num_selection = 0;
  3562.   dcache->selection_top = -1;
  3563.   dcache->last_focus = 0;
  3564.   dcache->timestamp = 0;
  3565. }
  3566.  
  3567. static directory_cache *
  3568. directory_cache_new (GCHAR *name)
  3569. {
  3570.   directory_cache     *p;
  3571.   gint            i;
  3572.  
  3573.   DEBUGBLOCK (N_("directory_cache_new\n"));
  3574.  
  3575.   p = g_new (directory_cache, 1);
  3576.   strcpy (p->name, name);
  3577.   p->birth_index = directory_cache_table_size;
  3578.  
  3579.   p->max_ndir = 2;
  3580.   p->dir = g_new (Thumbnail, p->max_ndir);
  3581.   memset (p->dir, 0, p->max_ndir * sizeof (Thumbnail));
  3582.   for (i = 0; i < p->max_ndir; i++)
  3583.     thumbnail_initialize (p->dir + i);
  3584.  
  3585.   p->max_nimage = 16;
  3586.   p->image = g_new (Thumbnail, p->max_nimage);
  3587.   memset (p->image, 0, p->max_nimage * sizeof (Thumbnail));
  3588.   for (i = 0; i < p->max_nimage; i++)
  3589.     thumbnail_initialize (p->image + i);
  3590.  
  3591.   directory_cache_initialize (p);
  3592.   p->last_focus = 0;
  3593.  
  3594.   RETURN p;
  3595. }
  3596.  
  3597. static void
  3598. directory_cache_auto_shrink (directory_cache *dcache, gint force)
  3599. {
  3600.   gint    true_last;
  3601.   gint    i;
  3602.   gint    nfree = 0;
  3603.  
  3604.   __DEBUGBLOCK (N_("directory_cache_auto_shrink (%d in %d)\n"),
  3605.         dcache->nimage, dcache->max_nimage);
  3606.  
  3607.   true_last = dcache->max_nimage - 1;
  3608.   while ((0 <= true_last) && ((dcache->image + true_last)->image == NULL))
  3609.     true_last--;
  3610.   /* now true_last is the index of image which has alloccated memory. */
  3611.  
  3612.   if (true_last < 0)
  3613.     RETURN;
  3614.  
  3615.   if (force)
  3616.     nfree = true_last - dcache->nimage + 1;
  3617.   else if (dcache->nimage * 2 < true_last)
  3618.     nfree = MAX ((true_last - dcache->nimage) / 2, 1);
  3619.  
  3620.   /* release memory in reverse order */
  3621.   DPRINTIF (0 < nfree) (N_("free %d thumbnail->images\n"), nfree);
  3622.  
  3623.   for (i = true_last; true_last - nfree < i; i--)
  3624.     thumbnail_free_image (dcache->image + i);
  3625.  
  3626.   /*
  3627.   {
  3628.     gint flag = TRUE;
  3629.  
  3630.     DPRINT (N_("seek last index...\n"));
  3631.  
  3632.     true_last = dcache->max_nimage - 1;
  3633.     while ((0 <= true_last) && flag)
  3634.       {
  3635.     if ((dcache->image + true_last)->image != NULL)
  3636.       flag = FALSE;
  3637.     else
  3638.       true_last--;
  3639.       }
  3640.     DPRINT (N_("new allocated last is %d\n"), true_last);
  3641.   }
  3642.   */
  3643.   RETURN;
  3644. }
  3645.  
  3646. static gint
  3647. directory_cache_garbage_collect (directory_cache *dcache)
  3648. {
  3649.   /* side-effect: selection is reset and thumbnail_panel is updated
  3650.    */
  3651.   gint        index;
  3652.   gint        old_ndir, old_nimage;
  3653.   gint        nsel = 0;
  3654.   Thumbnail    *chunk;
  3655.  
  3656.   DEBUGBLOCK (N_("directory_cache_garbage_collect\n"));
  3657.  
  3658.   old_ndir = dcache->ndir;
  3659.   chunk = dcache->dir;
  3660.   index = 0;
  3661.   while (index < dcache->ndir)
  3662.     {
  3663.       if (HAS_ATTRIBUTE (chunk + index, DELETED_P))
  3664.     {
  3665.       GCHAR        *name, *info;
  3666.  
  3667.       /* shift to the left */
  3668.       dcache->ndir--;
  3669.       info = chunk[index].info;
  3670.       name = chunk[index].name;
  3671.  
  3672.       g_memmove (chunk + index,
  3673.              chunk + (index + 1),
  3674.              sizeof (Thumbnail) * (dcache->ndir - index));
  3675.       /* the dubbling pointer is back to the world */
  3676.       chunk[dcache->ndir].info = info;
  3677.       chunk[dcache->ndir].name = name;
  3678.     }
  3679.       else
  3680.     index++;
  3681.     }
  3682.  
  3683.   old_nimage = dcache->nimage;
  3684.   chunk = dcache->image;
  3685.   index = 0;
  3686.   while (index < dcache->nimage)
  3687.     {
  3688.       if (HAS_ATTRIBUTE (chunk + index, DELETED_P))
  3689.     {
  3690.       image_buffer    *ibuf;
  3691.       GCHAR        *name, *info;
  3692.  
  3693.       G_ASSERT (HAS_NO_ATTRIBUTE (chunk + index, SELECTED_P));
  3694.  
  3695.       /* shift to the left */
  3696.       dcache->nimage--;
  3697.       ibuf = chunk[index].image;
  3698.       info = chunk[index].info;
  3699.       name = chunk[index].name;
  3700.  
  3701.       g_memmove (chunk + index,
  3702.              chunk + (index + 1),
  3703.              sizeof (Thumbnail) * (dcache->nimage - index));
  3704.       /* the dubbling pointer is back to the world */
  3705.       chunk[dcache->nimage].image = ibuf;
  3706.       chunk[dcache->nimage].info = info;
  3707.       chunk[dcache->nimage].name = name;
  3708.     }
  3709.       else
  3710.     {
  3711.       if (HAS_ATTRIBUTE (chunk + index, SELECTED_P))
  3712.         {
  3713.           nsel++;
  3714.           if (index < dcache->selection_top)
  3715.         dcache->selection_top = index + dcache->ndir;
  3716.         }
  3717.     index++;
  3718.     }
  3719.     }
  3720.  
  3721.   G_ASSERT (nsel == dcache->num_selection);
  3722.  
  3723.   if (dcache == cwd_cache)
  3724.     {
  3725.       if (cwd_cache_npage () <= dcache->display_page)
  3726.     dcache->display_page = cwd_cache_npage () - 1;
  3727.  
  3728.       guash_discard_events ();
  3729.       DPRINT (N_("npage: %d, display_page: %d\n"),
  3730.           cwd_cache_npage (),
  3731.           dcache->display_page);
  3732.       if (HAS_ATTRIBUTE (cwd_cache, DISORDERED_P))
  3733.     {
  3734.       directory_cache_reorder (cwd_cache);
  3735.       RESET_ATTRIBUTE (cwd_cache, DISORDERED_P);
  3736.       DPRINT (N_("success to reorder\n"));
  3737.     }
  3738.       thumbnail_panel_update ();
  3739.     }
  3740.  
  3741.   RETURN TRUE;
  3742. }
  3743.  
  3744. static Thumbnail *
  3745. directory_cache_get_image (directory_cache *dcache, GCHAR *pathname)
  3746. {
  3747.   Thumbnail    *val = NULL;
  3748.  
  3749.   G_ASSERT (dcache != NULL);
  3750.   G_ASSERT (*pathname == G_DIR_SEPARATOR);
  3751.  
  3752.   DEBUGBLOCK (N_("directory_cache_get_image (\"%s\", \"%s\")\n"),
  3753.           dcache->name, pathname);
  3754.  
  3755.   if (HAS_ATTRIBUTE (dcache, FILED_P)) /* check thumbnail */
  3756.     val = guash_get_image_from_file (pathname, TRUE);
  3757.   DPRINT (N_("success to try to load the thumbnail\n"));
  3758.  
  3759.   if ((val == NULL) && HAS_ATTRIBUTE (dcache, DISPLAY_IMAGE_P))
  3760.     {
  3761.       val = guash_get_image_from_file (pathname, FALSE);
  3762.       if (val != NULL)
  3763.     /* Now we can save image as an XV thumbnail.*/
  3764.     if (HAS_ATTRIBUTE (&VAL, SAVE_AS_XVPICT_P))
  3765.       {
  3766.         if (dcache->savable == UNKNOWN)
  3767.           directory_cache_make_thumbnail_directory (dcache);
  3768.         if (dcache->savable == TRUE)
  3769.           {
  3770.         directory_cache_save_thumbnail_to (cwd_cache, val);
  3771.         DPRINT (N_("success to save thumbnail\n"));
  3772.           }
  3773.       }
  3774.     }
  3775.   RETURN val;
  3776. }
  3777.  
  3778. static gint
  3779. directory_cache_add_directory (directory_cache *dcache,
  3780.                    GCHAR *pathname,
  3781.                    gint f_kind)
  3782. {
  3783.   Thumbnail    *thumbnail;
  3784.  
  3785.   DEBUGBLOCK (N_("directory_cache_add_directory (\"%s\", %d)\n"),
  3786.           pathname, f_kind);
  3787.  
  3788.   if ((0 < directory_cache_max_images)
  3789.       && (directory_cache_max_images < directory_cache_num_entry (dcache)))
  3790.     RETURN FALSE;
  3791.  
  3792.   if (dcache->max_ndir <= dcache->ndir)
  3793.     {
  3794.       gint    i = dcache->max_ndir;
  3795.  
  3796.       dcache->max_ndir *= 2;
  3797.       dcache->dir =
  3798.     (Thumbnail *) g_realloc ((gpointer) dcache->dir,
  3799.                  dcache->max_ndir * sizeof (Thumbnail));
  3800.  
  3801.       for (; i < dcache->max_ndir; i++)
  3802.     thumbnail_initialize (dcache->dir + i);
  3803.  
  3804.       DPRINT (N_("expand directory table to %d\n"), dcache->max_ndir);
  3805.     }
  3806.  
  3807.   thumbnail = &dcache->dir[dcache->ndir];
  3808.  
  3809.   INITIALIZE_ATTRIBUTES (thumbnail);
  3810.   RESET_ATTRIBUTE (thumbnail, DELETED_P);
  3811.   SET_ATTRIBUTE (thumbnail, DIRECTORY_P);
  3812.   SET_TO_ATTRIBUTE (thumbnail, SYMLINK_P, (f_kind == SLNKDIR));
  3813.  
  3814.   if (thumbnail->name)
  3815.     g_free (thumbnail->name);
  3816.   thumbnail->name = g_strdup (pathname);
  3817.  
  3818.   if (dcache->ndir == 0)
  3819.     SET_ATTRIBUTE (thumbnail, PARENT_DIRECTORY_P);
  3820.  
  3821.   if (thumbnail->info)
  3822.     g_free (thumbnail->info);
  3823.   thumbnail->info = NULL;
  3824.  
  3825.   thumbnail_free_image (thumbnail);
  3826.  
  3827.   dcache->ndir++;    /* increment before calling update_partially */
  3828.  
  3829.   if (dcache == cwd_cache)
  3830.     thumbnail_panel_update_partially (dcache->ndir - 1);
  3831.  
  3832.   _DPRINT (N_("success to add_directory\n"));
  3833.  
  3834.   RETURN TRUE;
  3835. }
  3836.  
  3837. static gint
  3838. directory_cache_add_image (directory_cache *dcache,
  3839.                GCHAR *pathname,
  3840.                Thumbnail *thumb,
  3841.                gint f_kind)
  3842. {
  3843.   Thumbnail    *thumbnail;
  3844.  
  3845.   DEBUGBLOCK (N_("directory_cache_add_image (\"%s\", %s, %d)\n"),
  3846.           pathname, ((thumb != NULL) ? "a thumb." : ""), f_kind);
  3847.  
  3848.   if ((0 < directory_cache_max_images)
  3849.       && (directory_cache_max_images < directory_cache_num_entry (dcache)))
  3850.     {
  3851.       DPRINT (N_("done (too many: %d)\n"), dcache->nimage);
  3852.       RETURN FALSE;
  3853.     }
  3854.  
  3855.   if (dcache->max_nimage <= dcache->nimage)
  3856.     {
  3857.       gint    i = dcache->max_nimage;
  3858.  
  3859.       DPRINT (N_("expand image table...\n"));
  3860.  
  3861.       dcache->max_nimage *= 2;
  3862.       dcache->image
  3863.     = (Thumbnail *) g_realloc ((gpointer) dcache->image,
  3864.                    dcache->max_nimage * sizeof (Thumbnail));
  3865.       for (; i < dcache->max_nimage; i++)
  3866.     thumbnail_initialize (dcache->image + i);
  3867.  
  3868.       DPRINT (N_("expand image table...done\n"));
  3869.     }
  3870.  
  3871.   thumbnail = &dcache->image[dcache->nimage];
  3872.  
  3873.   G_ASSERT (thumbnail);
  3874.  
  3875.   INITIALIZE_ATTRIBUTES (thumbnail);
  3876.  
  3877.   if (thumbnail->info)
  3878.     g_free (thumbnail->info);
  3879.  
  3880.   if (thumb != NULL)
  3881.     {
  3882.       thumbnail_copy_data (thumbnail, thumb, FALSE);
  3883.  
  3884.       if (f_kind == SLNKFILE)
  3885.     {
  3886.       GCHAR tmp[LINE_BUF_SIZE];
  3887.  
  3888.       sprintf (tmp, _("(symlink) %s"), thumb->info);
  3889.       thumbnail->info = g_strdup (tmp);
  3890.     }
  3891.       else
  3892.     thumbnail->info = g_strdup (thumb->info);
  3893.     }
  3894.   else
  3895.     {
  3896.       thumbnail_free_image (thumbnail);
  3897.       thumbnail->info = g_strdup (pathname_get_basename (pathname));
  3898.     }
  3899.  
  3900.   RESET_ATTRIBUTE (thumbnail, DIRECTORY_P);
  3901.   RESET_ATTRIBUTE (thumbnail, PARENT_DIRECTORY_P);
  3902.   SET_TO_ATTRIBUTE (thumbnail, SYMLINK_P, (f_kind == SLNKFILE));
  3903.   if (thumbnail->name)
  3904.     g_free (thumbnail->name);
  3905.   thumbnail->name = g_strdup (pathname_get_basename (pathname));
  3906.  
  3907.   dcache->nimage++;
  3908.  
  3909.   if (dcache == cwd_cache)
  3910.     thumbnail_panel_update_partially (directory_cache_num_entry (dcache) - 1);
  3911.   RETURN TRUE;
  3912. }
  3913.  
  3914. static gint
  3915. directory_cache_num_entry (directory_cache *dcache)
  3916. {
  3917.   return dcache->ndir + dcache->nimage;
  3918. }
  3919.  
  3920. static gint
  3921. directory_cache_valid_index (directory_cache *dcache, gint index)
  3922. {
  3923.   return ((0 <= index) && (index < dcache->ndir + dcache->nimage));
  3924. }
  3925.  
  3926. static gint
  3927. directory_cache_valid_image_index (directory_cache *dcache, gint index)
  3928. {
  3929.   return ((dcache->ndir <= index) && (index < dcache->ndir + dcache->nimage));
  3930. }
  3931.  
  3932. static directory_cache *
  3933. directory_cache_of (GCHAR *name)
  3934. {
  3935.   return (directory_cache *) g_hash_table_lookup (directory_cache_table,
  3936.                           (gpointer) name);
  3937. }
  3938.  
  3939. static void
  3940. directory_cache_reorder (directory_cache *dcache)
  3941. {
  3942.   int    (func)(Thumbnail *, Thumbnail *);
  3943.  
  3944.   DEBUGBLOCK (N_("directory_cache_reorder\n"));
  3945.  
  3946.   if (HAS_ATTRIBUTE (dcache, SORT_BY_NAME_P))
  3947.     {
  3948.       qsort (dcache->dir,
  3949.          dcache->ndir,
  3950.          sizeof (Thumbnail),
  3951.          (sort_compare_function) thumbnail_compare_name);
  3952.       qsort (dcache->image,
  3953.          dcache->nimage,
  3954.          sizeof (Thumbnail),
  3955.          (sort_compare_function) thumbnail_compare_name);
  3956.       DPRINT (N_("success to reorder by name\n"));
  3957.     }
  3958.  
  3959.   if (0 < dcache->num_selection)
  3960.     {
  3961.       gint    i;
  3962.       Thumbnail *thumb = dcache->image;
  3963.  
  3964.       dcache->selection_top = -1;
  3965.       for (i = 0; i < dcache->nimage; i++, thumb++)
  3966.     if (HAS_ATTRIBUTE (thumb, SELECTED_P))
  3967.       {
  3968.         dcache->selection_top = i + dcache->ndir;
  3969.         break;
  3970.       }
  3971.       DPRINT (N_("success to recompute selection_top\n"));
  3972.     }
  3973.   RETURN;
  3974. }
  3975.  
  3976. static void
  3977. directory_cache_table_aging (gpointer key,
  3978.                  gpointer value,
  3979.                  gpointer user_data)
  3980. {
  3981.   directory_cache *dcache = (directory_cache *) value;
  3982.  
  3983.   if (dcache != cwd_cache)
  3984.     {
  3985.       if (cwd_cache->birth_index < dcache->birth_index)
  3986.     dcache->birth_index--;
  3987.     }
  3988.   DPRINT (N_("birth_index:%d (cwd_cache:%d)\n"),
  3989.       dcache->birth_index,
  3990.       cwd_cache->birth_index);
  3991. }
  3992.  
  3993. static void
  3994. directory_cache_table_recycler (gpointer key,
  3995.                 gpointer value,
  3996.                 gpointer user_data)
  3997. {
  3998.   directory_cache *dcache, **fail_safe;
  3999.  
  4000.   dcache = (directory_cache *) value;
  4001.   fail_safe = (directory_cache **)user_data;
  4002.  
  4003.   /* This is for fail safe */
  4004.   if ((cwd_cache == NULL) && (*fail_safe == NULL))
  4005.     *fail_safe = dcache;
  4006.  
  4007.   /* set the oldest entry to cwd_cache */
  4008.   if ((--(dcache->birth_index) == 0) && (cwd_cache == NULL))
  4009.     cwd_cache = (directory_cache *) value;
  4010. }
  4011.  
  4012. static Thumbnail *
  4013. directory_cache_get_nth (directory_cache *dcache, gint index)
  4014. {
  4015.   G_ASSERT (index < directory_cache_num_entry (dcache));
  4016.  
  4017.   if (index < dcache->ndir)
  4018.     {
  4019.       G_ASSERT (dcache->dir);
  4020.       return dcache->dir + index;
  4021.     }
  4022.   else
  4023.     {
  4024.       G_ASSERT (dcache->image);
  4025.       return dcache->image + (index - dcache->ndir);
  4026.     }
  4027. }
  4028.  
  4029. static Thumbnail *
  4030. directory_cache_lookup (directory_cache *dcache, GCHAR *name)
  4031. {
  4032.   gint        index;
  4033.   Thumbnail    *ptr = dcache->image;
  4034.  
  4035.   for (index = 0; index < dcache->nimage; index++)
  4036.     if (strcmp ((ptr + index)->name, name) == 0)
  4037.       return (ptr + index);
  4038.   return NULL;
  4039. }
  4040.  
  4041. static gint
  4042. directory_cache_make_thumbnail_directory (directory_cache *dentry)
  4043. {
  4044.   GCHAR        *thumbnail_dir;
  4045.   mode_t    mode = NEW_DIRECTORY_MODE;
  4046.  
  4047.   DEBUGBLOCK (N_("directory_cache_make_thumbnail_directory (\"%s\")\n"),
  4048.           dentry->name);
  4049.  
  4050.   thumbnail_dir = pathname_build_thumbnail_dirname (dentry->name);
  4051.  
  4052.   if (os_make_directory (thumbnail_dir, mode) == -1)
  4053.     {
  4054.       switch (errno)
  4055.     {
  4056.     case EEXIST:
  4057.       dentry->savable = os_file_is_writable (thumbnail_dir);
  4058.       SET_ATTRIBUTE (dentry, FILED_P);
  4059.       break;
  4060.     default:
  4061.       dentry->savable = FALSE;
  4062.       RESET_ATTRIBUTE (dentry, FILED_P);
  4063.       break;
  4064.     }
  4065.       g_free (thumbnail_dir);
  4066.       RETURN FALSE;
  4067.     }
  4068.   else
  4069.     {
  4070.       dentry->savable = TRUE;
  4071.       SET_ATTRIBUTE (dentry, FILED_P);
  4072.  
  4073.       g_free (thumbnail_dir);
  4074.       RETURN TRUE;
  4075.     }
  4076. }
  4077.  
  4078. static gint
  4079. directory_cache_save_thumbnail_to (directory_cache *dcache,
  4080.                    Thumbnail *thumb)
  4081. {
  4082.   GString    *s;
  4083.  
  4084.   DEBUGBLOCK (N_("directory_cache_save_thumbnail_to (%s, %s)\n"),
  4085.           dcache->name, thumb->name);
  4086.  
  4087.   G_ASSERT (cwd_cache->savable != FALSE);
  4088.  
  4089.   s= g_string_new (pathname_build_thumbnail_dirname (dcache->name));
  4090.   g_string_append_c (s, G_DIR_SEPARATOR);
  4091.   G_ASSERT (thumb->name);
  4092.   g_string_append (s, thumb->name);
  4093.  
  4094.   dcache->savable = save_thumbnail_as_xvpict_image (s->str, thumb);
  4095.   SET_ATTRIBUTE (cwd_cache, FILED_P);
  4096.  
  4097.   g_string_free (s, TRUE);
  4098.  
  4099.   RETURN TRUE;
  4100. }
  4101.  
  4102. /* if force is TRUE, all thumbnails and directory are deleted.
  4103.  * Otherwise, only obsolete thumbnails are deleted.
  4104.  * This function returns TRUE if some thumbnail is old. Otherwise FALSE.
  4105.  */
  4106. static gint
  4107. directory_cache_delete_invalid_cache_files (directory_cache *dcache,
  4108.                         gint force)
  4109. {
  4110.   GCHAR        *path;
  4111.   DIR        *dir;
  4112.   struct dirent *dentry;
  4113.   gint        index = 0;
  4114.   gint        flag = FALSE;
  4115.  
  4116.   DEBUGBLOCK (N_("directory_cache_delete_invalid_cache_files\n"));
  4117.  
  4118.   path = pathname_build_thumbnail_dirname (dcache->name);
  4119.  
  4120.   if (os_file_kind (path, TRUE) != DIRECTORY)
  4121.     RETURN flag;
  4122.  
  4123.   dir = opendir (path);
  4124.   rewinddir (dir);
  4125.  
  4126.   for (; (dentry = readdir (dir)); index++)
  4127.     {
  4128.       GString    *file;
  4129.  
  4130.       file = g_string_new (g_strdup (path));
  4131.       g_string_append_c (file, G_DIR_SEPARATOR);
  4132.       g_string_append (file, dentry->d_name);
  4133.  
  4134.       if (os_file_kind (file->str, TRUE) == REGFILE)
  4135.     {
  4136.       if (force || (pathname_is_valid_thumbnail_filename (file->str) == FALSE))
  4137.         {
  4138.           DPRINT (N_("delete thumbnail file: %s\n"), file->str);
  4139.           os_delete_file (file->str);
  4140.           flag = TRUE;
  4141.         }
  4142.     }
  4143.       g_string_free (file, TRUE);
  4144.     }
  4145.   if (force)
  4146.     {
  4147.       if (os_delete_file (path) == 0)
  4148.     thumbnail_panel_set_info (_("Thumbnail directory was deleted"));
  4149.       else
  4150.     thumbnail_panel_set_info (_("Failed to delete thumbnail directory (permission problem?)"));
  4151.  
  4152.       RESET_ATTRIBUTE (dcache, FILED_P);
  4153.     }
  4154.  
  4155.   g_free (path);
  4156.   closedir (dir);
  4157.  
  4158.   RETURN flag;
  4159. }
  4160.  
  4161. static GtkWidget *
  4162. directory_cache_create_parents_menu ()
  4163. {
  4164.   GtkWidget    *menu;
  4165.   gint        count, index;
  4166.   GCHAR        *cwd;
  4167.   GtkWidget    *menuitem;
  4168.   GCHAR        *label = NULL;
  4169.  
  4170.   menu = gtk_menu_new ();
  4171.   cwd = cwd_cache->name;
  4172.  
  4173.   label = (GCHAR *) g_malloc (2);
  4174.   label[0] = G_DIR_SEPARATOR;
  4175.   label[1] = 0;
  4176.   menuitem = gtk_menu_item_new_with_label (label);
  4177.   gtk_menu_append (GTK_MENU (menu), menuitem);
  4178.   gtk_signal_connect (GTK_OBJECT (menuitem), "activate",
  4179.               (GtkSignalFunc) menu_change_directory_callback,
  4180.               (gpointer) label);
  4181.   gtk_widget_show (menuitem);
  4182.  
  4183.   for (count = index = 1; index < strlen (cwd); index++)
  4184.     if (cwd[index] == G_DIR_SEPARATOR)
  4185.       {
  4186.     label = (GCHAR *) g_malloc (index);
  4187.     g_memmove (label, cwd, index);
  4188.     label[index] = 0;
  4189.  
  4190.     menuitem = gtk_menu_item_new_with_label (label);
  4191.     gtk_menu_prepend (GTK_MENU (menu), menuitem);
  4192.     gtk_signal_connect (GTK_OBJECT (menuitem), "activate",
  4193.                 (GtkSignalFunc) menu_change_directory_callback,
  4194.               (gpointer) label);
  4195.     gtk_widget_show (menuitem);
  4196.     }
  4197.  
  4198.   return menu;
  4199. }
  4200.  
  4201. static void
  4202. directory_cache_create_history_menu_foreach (gpointer key,
  4203.                          gpointer value,
  4204.                          gpointer user_data)
  4205. {
  4206.   GtkWidget    *menu_item;
  4207.   GtkWidget    *menu;
  4208.  
  4209.   menu = (GtkWidget *) user_data;
  4210.  
  4211.   menu_item = gtk_menu_item_new_with_label ((GCHAR *)key);
  4212.   gtk_menu_append (GTK_MENU (menu), menu_item);
  4213.   gtk_signal_connect (GTK_OBJECT (menu_item), "activate",
  4214.               (GtkSignalFunc) menu_change_directory_callback,
  4215.               key);
  4216.   gtk_widget_show (menu_item);
  4217. }
  4218.  
  4219. static GtkWidget *
  4220. directory_cache_create_history_menu ()
  4221. {
  4222.   GtkWidget *menu;
  4223.  
  4224.   menu = gtk_menu_new ();
  4225.  
  4226.   g_hash_table_foreach (directory_cache_table,
  4227.             directory_cache_create_history_menu_foreach,
  4228.             menu);
  4229.  
  4230.   return menu;
  4231. }
  4232.  
  4233. static gint
  4234. directory_cache_update_selection (directory_cache *dcache, gint kind, gint index)
  4235. {
  4236.   Thumbnail    *thumb = directory_cache_get_nth (dcache, index);
  4237.  
  4238.   G_ASSERT (thumb);
  4239.   G_ASSERT (0 <= index);
  4240.  
  4241.   dcache->last_focus = index;
  4242.  
  4243.   if (kind == SELECTION_ADD)
  4244.     {
  4245.       if (HAS_ATTRIBUTE (thumb, SELECTED_P))
  4246.     return TRUE;
  4247.  
  4248.       SET_ATTRIBUTE (thumb, SELECTED_P);
  4249.       thumb->selection_timestamp = ++dcache->selection_timestamp;
  4250.  
  4251.       if (dcache->num_selection == 0)
  4252.     dcache->selection_top = index;
  4253.       else
  4254.     dcache->selection_top = MIN (dcache->selection_top, index);
  4255.  
  4256.       dcache->num_selection++;
  4257.       DPRINT (N_("num_selection: %d\n"), dcache->num_selection);
  4258.       return TRUE;
  4259.     }
  4260.   else if (kind == SELECTION_DELETE)
  4261.     {
  4262.       gint    i;
  4263.       gint    max = directory_cache_num_entry (dcache);
  4264.  
  4265.       if (HAS_NO_ATTRIBUTE (thumb, SELECTED_P))
  4266.     return (0 < dcache->num_selection);
  4267.  
  4268.       RESET_ATTRIBUTE (thumb, SELECTED_P);
  4269.  
  4270.       if (0 < dcache->num_selection)
  4271.     dcache->num_selection--;
  4272.       DPRINT (N_("num_selection: %d\n"), dcache->num_selection);
  4273.  
  4274.       if (dcache->num_selection == 0)
  4275.     {
  4276.       dcache->last_focus = 0;
  4277.       return FALSE;
  4278.     }
  4279.       if (dcache->selection_top == index)
  4280.     for (i = index; i < max; i++)
  4281.       {
  4282.         if (HAS_ATTRIBUTE (directory_cache_get_nth (dcache, i), SELECTED_P))
  4283.           {
  4284.         dcache->selection_top = i;
  4285.         return TRUE;
  4286.           }
  4287.       }
  4288.       return TRUE;
  4289.     }
  4290.   return FALSE;
  4291. }
  4292.  
  4293. /* name is a short name, which does not include the directory part */
  4294. static gint
  4295. directory_cache_update_thumbnail_for (directory_cache *dcache,
  4296.                       GCHAR *pathname,
  4297.                       Thumbnail *src_thumb)
  4298. {
  4299.   Thumbnail    *thumbnail, *thumb = NULL;
  4300.   gint        offset, index;
  4301.   gint        last_index;
  4302.   GCHAR        *basename = pathname_get_basename (pathname);
  4303.  
  4304.   DEBUGBLOCK (N_("directory_cache_update_thumbnail_for (\"%s\", \"%s\" with%s thumb.)\n"),
  4305.           dcache->name,
  4306.           pathname,
  4307.           (src_thumb == NULL ? "out" : ""));
  4308.  
  4309.   if (src_thumb)
  4310.     thumb = src_thumb;
  4311.  
  4312.   if ((thumbnail = directory_cache_lookup (dcache, basename)) == NULL)
  4313.     {
  4314.       DPRINT (N_("%s is a new image file\n"), basename);
  4315.  
  4316.       if ((thumb == NULL)
  4317.       && ((thumb = directory_cache_get_image (dcache, pathname)) == NULL))
  4318.     {
  4319.       if (HAS_ATTRIBUTE (dcache, DISPLAY_IMAGE_P))
  4320.         RETURN FALSE;
  4321.     }
  4322.  
  4323.       directory_cache_add_image (dcache, pathname, thumb,
  4324.                  os_file_kind (pathname, FALSE));
  4325.       SET_ATTRIBUTE (dcache, DISORDERED_P);
  4326.       RETURN TRUE;
  4327.     }
  4328.  
  4329.   if ((thumb == NULL)
  4330.       && ((thumb = directory_cache_get_image (dcache, pathname)) == NULL))
  4331.     RETURN FALSE;
  4332.  
  4333.   /* copy all data [Sun Sep 13 15:15:24 1998] */
  4334.   thumbnail_copy_data (thumbnail, thumb, TRUE);
  4335.  
  4336.   DPRINT (N_("success to copy all data\n"));
  4337.  
  4338.   if (dcache != cwd_cache)
  4339.     RETURN FALSE;
  4340.  
  4341.   offset = POS_TO_INDEX (0, 0);
  4342.   last_index = MIN (offset + the_panel.nthumb_in_page,
  4343.             directory_cache_num_entry (dcache));
  4344.   for (index = offset; index < last_index; index++)
  4345.     {
  4346.       if (directory_cache_get_nth (dcache, index) == thumbnail)
  4347.     {
  4348.       DPRINT (N_("updated thumbnail is the %dth thumb in the panel\n"),
  4349.           index);
  4350.       RETURN TRUE;
  4351.       break;
  4352.     }
  4353.     }
  4354.  
  4355.   RETURN FALSE;
  4356. }
  4357.  
  4358. /*
  4359.  * cwd_cache: special function only for cwd_cache
  4360.  */
  4361.  
  4362. static gint
  4363. cwd_cache_npage ()
  4364. {
  4365.   gint    page;
  4366.  
  4367.   page = directory_cache_num_entry (cwd_cache) / the_panel.nthumb_in_page;
  4368.   if (directory_cache_num_entry (cwd_cache) % the_panel.nthumb_in_page != 0)
  4369.     page++;
  4370.   return page;
  4371. }
  4372.  
  4373. static gint
  4374. cwd_cache_jump_to_subdirectory_index (gint nth)
  4375. {
  4376.   if (nth < cwd_cache->ndir)
  4377.     {
  4378.       Thumbnail    *thumb = directory_cache_get_nth (cwd_cache, nth);
  4379.       GCHAR    tmp[PATH_LENGTH];
  4380.  
  4381.       sprintf (tmp, "%s%c%s", cwd_cache->name, G_DIR_SEPARATOR, thumb->name);
  4382.       guash_change_current_directory (tmp, thumb);
  4383.       return TRUE;
  4384.     }
  4385.   else
  4386.     return FALSE;
  4387. }
  4388.  
  4389. static gint
  4390. cwd_cache_validate ()
  4391. {
  4392.   __DEBUGBLOCK (N_("cwd_cache_validate\n"));
  4393.  
  4394.   if (os_file_kind (cwd_cache->name, TRUE) == NOT_EXIST)
  4395.     {
  4396.       GCHAR    *new_dir;
  4397.  
  4398.       gdk_beep ();
  4399.       thumbnail_panel_set_info (_("Error: the directory does not exist now!"));
  4400.       gdk_flush ();
  4401.  
  4402.       DPRINT (N_("cwd_cache->name %s\n"), cwd_cache->name);
  4403.       sleep (1);
  4404.       new_dir = pathname_get_vaild_directoryname (cwd_cache->name);
  4405.       os_file_change_current_directory (new_dir);
  4406.       /* to delete the obsolete directory_cache, we must recycle it now.
  4407.      Thus hack the name before calling guash_update_cwd_cache. */
  4408.       strcpy (cwd_cache->name, new_dir);
  4409.       g_free (new_dir);
  4410.       guash_update_cwd_cache (RESCAN);
  4411.  
  4412.       thumbnail_panel_set_info (_("Error: the directory does not exist now! Jumped up"));
  4413.  
  4414.       RETURN FALSE;
  4415.     }
  4416.   else
  4417.     RETURN TRUE;
  4418. }
  4419.  
  4420. static gint
  4421. cwd_cache_update ()
  4422. {
  4423.   /* side-effect: selection is reset  */
  4424.   GCHAR        path[PATH_LENGTH], counter[256], *fname;
  4425.   gint        index_to_update;
  4426.   gint        index = 0;
  4427.   GCHAR        **filename_list;
  4428.   gint        nentry;
  4429.   gint        update_threshold;
  4430. #ifdef DELETE_CORE
  4431.   gint        no_core;
  4432. #endif
  4433.  
  4434.   DEBUGBLOCK (N_("cwd_cache_update\n"));
  4435.  
  4436.   gtkW_widget_set_cursor (dlg, CURSOR_WAIT);
  4437.   gtkW_widget_set_cursor (thumbnail_panel, CURSOR_WAIT);
  4438.   {
  4439.     GCHAR    *thumbnail_dir;
  4440.  
  4441.     thumbnail_dir = pathname_build_thumbnail_dirname (cwd_cache->name);
  4442.     switch (os_file_kind (thumbnail_dir, TRUE))
  4443.       {
  4444.       case NOT_EXIST:
  4445.     cwd_cache->savable = UNKNOWN;
  4446.     RESET_ATTRIBUTE (cwd_cache, FILED_P);
  4447.     break;
  4448.       case DIRECTORY:
  4449.     cwd_cache->savable = os_file_is_writable (thumbnail_dir);
  4450.     SET_ATTRIBUTE (cwd_cache, FILED_P);
  4451.     break;
  4452.       default:
  4453.     cwd_cache->savable = FALSE;
  4454.     RESET_ATTRIBUTE (cwd_cache, FILED_P);
  4455.     break;
  4456.       }
  4457.     g_free (thumbnail_dir);
  4458.   }
  4459.   /* set attributes here. The values may be used by
  4460.      thumbnail_panel_update_partially. */
  4461.   COPY_ATTRIBUTE (cwd_cache, DISPLAY_IMAGE_P, &VAL);
  4462.   COPY_ATTRIBUTE (cwd_cache, SORT_BY_NAME_P, &VAL);
  4463.  
  4464.   if (VAL.last_dir_name[0] != 0)
  4465.     {
  4466.       thumbnail_panel_clear ();
  4467.       gtkW_preview_force_to_update (thumbnail_panel);
  4468.     }
  4469.   if (HAS_ATTRIBUTE (cwd_cache, FILED_P))
  4470.     gtk_window_set_title (GTK_WINDOW (dlg),
  4471.               "Guash: scanning with saved thumbnails...");
  4472.   else
  4473.     gtk_window_set_title (GTK_WINDOW (dlg),
  4474.               "Guash: wait a minute...");
  4475. #ifdef GTK_HAVE_FEATURES_1_1_0
  4476.   gtk_label_set_text (GTK_LABEL (cwd_label), cwd_cache->name);
  4477. #else
  4478.   gtk_label_set (GTK_LABEL (cwd_label), cwd_cache->name);
  4479. #endif
  4480.   gtk_widget_draw (cwd_label, NULL);
  4481.  
  4482.   selection_reset ();
  4483.  
  4484.   cwd_cache->nimage = cwd_cache->ndir = 0;
  4485.   index_to_update = (1 + cwd_cache->display_page) * the_panel.nthumb_in_page;
  4486.  
  4487.   {
  4488.     GCHAR    *tmp;
  4489.  
  4490.     tmp = pathname_get_directoryname (cwd_cache->name);
  4491.     directory_cache_add_directory (cwd_cache, tmp, DIRECTORY);
  4492.     g_free (tmp);
  4493.   }
  4494.  
  4495.   DPRINT (N_("scandir %s\n"), cwd_cache->name);
  4496.   nentry = os_scandir (cwd_cache->name, &filename_list, &os_scandir_selector,
  4497.                HAS_ATTRIBUTE (&VAL, SORT_BY_NAME_P));
  4498.  
  4499.   /* For displaying eagarly, I use two pass scanning. */
  4500. #ifdef DELETE_CORE
  4501.   /* before the traverse, check the existance of core */
  4502.   no_core = (os_file_kind ("core", TRUE) == NOT_EXIST);
  4503.  
  4504.   DPRINTIF (! no_core) (N_("Checking core: found\n"));
  4505. #endif
  4506.  
  4507.   /* 1st path: for collecting directories */
  4508.   DPRINT (N_("Scanning directory 1st path\n"));
  4509.   thumbnail_panel_set_info ("Reading the directory.");
  4510.  
  4511.   for (index = 0; index < nentry; index++)
  4512.     {
  4513.       gint    f_kind = NOT_EXIST;
  4514.  
  4515.       fname = filename_list[index];
  4516.       if (fname[0] == '.')
  4517.     continue;
  4518.  
  4519.       f_kind = os_file_kind (fname, FALSE);
  4520.  
  4521.       switch (f_kind)
  4522.     {
  4523.     case DIRECTORY:
  4524.     case SLNKDIR:
  4525.       directory_cache_add_directory (cwd_cache, fname, f_kind);
  4526.       break;
  4527.     default:
  4528.       break;
  4529.     }
  4530.     }
  4531.   if (index_to_update < cwd_cache->ndir)
  4532.     {
  4533.       /* gtkW_preview_force_to_update (thumbnail_panel); */
  4534.       /* stop further update */
  4535.       index_to_update = -1;
  4536.     }
  4537.  
  4538.   /* 2nd path: for loading image files */
  4539.   DPRINT (N_("Scanning directory 2nd path\n"));
  4540.   thumbnail_panel_set_info (_("Reading the directory.."));
  4541.  
  4542.   update_threshold = the_panel.ncol;
  4543.   for (index = 0; index < nentry; index++)
  4544.     {
  4545.       gint    f_kind = NOT_EXIST;
  4546.       gint    no_need_to_load = FALSE;
  4547.  
  4548.       if (index == update_threshold)
  4549.     {
  4550.       sprintf (counter, _("Reading the directory (%02.0f%%)..."),
  4551.            100 * (gfloat) index / (gfloat) nentry);
  4552.       thumbnail_panel_set_info (counter);
  4553.       update_threshold += update_threshold + the_panel.nthumb_in_page / 2;
  4554.     }
  4555.  
  4556.       fname = filename_list[index];
  4557.       if (fname[0] == '.')
  4558.     continue;
  4559.       no_need_to_load = pathname_match_inhibit_suffix (fname);
  4560.       if (no_need_to_load && HAS_ATTRIBUTE (cwd_cache, DISPLAY_IMAGE_P))
  4561.     continue;
  4562.  
  4563.       sprintf (path, "%s%c%s", cwd_cache->name, G_DIR_SEPARATOR, fname);
  4564.       f_kind = os_file_kind (path, FALSE);
  4565.  
  4566.       switch (f_kind)
  4567.     {
  4568.     case REGFILE:
  4569.     case SLNKFILE:
  4570.       /* new semantics from 1.2.0 [Wed Sep  9 02:31:44 1998] */
  4571.       if ((! no_need_to_load)
  4572.           && (directory_cache_get_image (cwd_cache, path) != NULL))
  4573.         directory_cache_add_image (cwd_cache, path, the_loaded_data, f_kind);
  4574.       else if (HAS_NO_ATTRIBUTE (cwd_cache, DISPLAY_IMAGE_P))
  4575.         directory_cache_add_image (cwd_cache, path, NULL, f_kind);
  4576.       else
  4577.         {
  4578.           DPRINT (N_("non image: %s\n"), fname);
  4579.         }
  4580.       break;
  4581.     default:
  4582.       break;
  4583.     }
  4584.       if (index_to_update == directory_cache_num_entry (cwd_cache))
  4585.     {
  4586.       /* gtkW_preview_force_to_update (thumbnail_panel); */
  4587.       /* if the next entry is non-directory/image, another update is
  4588.          occurred, unless reset index_to_update. */
  4589.       index_to_update = -1;
  4590.     }
  4591.     }
  4592.   g_free (filename_list);
  4593.  
  4594.   DPRINT (N_("end of scan\n"));
  4595.  
  4596. #ifdef DELETE_CORE
  4597.   if (no_core && (os_file_kind ("core", TRUE) == REGFILE))
  4598.     {
  4599.       DPRINT (N_("unlink newly generated core file\n"));
  4600.       os_delete_file ("core");
  4601.       if (os_file_kind ("core", TRUE) == NOT_EXIST)
  4602.     DPRINT (N_("success to remove core\n"));
  4603.       else
  4604.     DPRINT (N_("fail to remove core!\n"));
  4605.     }
  4606. #endif
  4607.   cwd_cache->timestamp = os_file_get_modify_timestamp (cwd_cache->name);
  4608.  
  4609.   if (cwd_cache_npage () <= cwd_cache->display_page)
  4610.     {
  4611.       cwd_cache->display_page = cwd_cache_npage () - 1;
  4612.       thumbnail_panel_update ();
  4613.     }
  4614.   else if (directory_cache_num_entry (cwd_cache) < index_to_update)
  4615.     {
  4616.       thumbnail_panel_finalize_update ();
  4617.     }
  4618.   else
  4619.     {
  4620.       /* Then thumbnail_panel doesn't know the true number of dir & image. */
  4621.       thumbnail_panel_finalize_update ();
  4622.       if ((0 < directory_cache_max_images)
  4623.       && (directory_cache_max_images < directory_cache_num_entry (cwd_cache)))
  4624.     {
  4625.       GCHAR    buffer[256];
  4626.  
  4627.       sprintf (buffer,
  4628.            _("Warning: # of files is beyond the display limit (%d)"),
  4629.            directory_cache_max_images);
  4630.       thumbnail_panel_set_info (buffer);
  4631.       printf (buffer);
  4632.       printf (".\nCheck guash's README file to control display limit\n");
  4633.     }
  4634.       else
  4635.     thumbnail_panel_set_info (NULL);
  4636.     }
  4637.   if (guash_discard_events ())
  4638.     thumbnail_panel_set_info (_("FYI, events during directory scan were discarded"));
  4639.  
  4640.   directory_cache_auto_shrink (cwd_cache, FALSE);
  4641.  
  4642.   gtkW_widget_set_cursor (dlg, CURSOR_DEFAULT);
  4643.   gtkW_widget_set_cursor (thumbnail_panel, CURSOR_HAND);
  4644.  
  4645.   RETURN TRUE;
  4646. }
  4647.  
  4648. static gint
  4649. cwd_cache_update_after_file_operation (gint count,
  4650.                        GCHAR *operation,
  4651.                        GCHAR *first_success,
  4652.                        GCHAR *target)
  4653. {
  4654.   GCHAR    buffer[LINE_BUF_SIZE];
  4655.  
  4656.   G_ASSERT (0 < count);
  4657.  
  4658.   if (HAS_ATTRIBUTE (cwd_cache, DISORDERED_P))
  4659.     guash_update_cwd_cache (GC);
  4660.  
  4661.   if (target)
  4662.     {
  4663.       if (count == 1)
  4664.     sprintf (buffer, _("%s was %s to %s"), first_success, operation, target);
  4665.       else
  4666.     sprintf (buffer, _("%d files were %s to %s"), count, operation, target);
  4667.     }
  4668.   else
  4669.     {
  4670.       if (count == 1)
  4671.     sprintf (buffer, _("%s was %s"), first_success, operation);
  4672.       else
  4673.     sprintf (buffer, _("%d files were %s"), count, operation);
  4674.     }
  4675.  
  4676.   thumbnail_panel_set_info (buffer);
  4677.  
  4678.   cwd_cache->timestamp = os_file_get_modify_timestamp (cwd_cache->name);
  4679.  
  4680.   return TRUE;
  4681. }
  4682.  
  4683. /*
  4684.  * image_buffer
  4685.  */
  4686. static image_buffer *
  4687. image_buffer_new (gint width, gint height, gint ch)
  4688. {
  4689.   image_buffer    *new;
  4690.  
  4691.   DEBUGBLOCK (N_("image_buffer_new (%d, %d, %d)\n"), width, height, ch);
  4692.  
  4693.   new = g_new (image_buffer, 1);
  4694.  
  4695.   G_ASSERT (new);
  4696.  
  4697.   new->size = 0;
  4698.   new->data = NULL;
  4699.   image_buffer_resize (new, width, height, ch);
  4700.  
  4701.   RETURN new;
  4702. }
  4703.  
  4704. static void
  4705. image_buffer_resize (image_buffer *ibuf, gint width, gint height, gint ch)
  4706. {
  4707.   gint    size = width * height * ch;
  4708.  
  4709.   DEBUGBLOCK (N_("image_buffer_resize (%d %d %d: %d)\n"),
  4710.           width, height, ch, ibuf->size);
  4711.  
  4712.   if ((ibuf->size < size) || (! ibuf->data))
  4713.     {
  4714.       if (ibuf->data)
  4715.     g_free (ibuf->data);
  4716.       ibuf->size = size;
  4717.       ibuf->data = g_new (guchar, size);
  4718.       G_ASSERT (ibuf->data);
  4719.       memset (ibuf->data, 0, ibuf->size);
  4720.     }
  4721.   ibuf->width = width;
  4722.   ibuf->height = height;
  4723.   ibuf->ch = ch;
  4724.  
  4725.   RETURN;
  4726. }
  4727.  
  4728. static void
  4729. image_buffer_free (image_buffer *ibuf)
  4730. {
  4731.   G_ASSERT (ibuf);
  4732.  
  4733.   if (ibuf->data)
  4734.     g_free (ibuf->data);
  4735.  
  4736.   g_free (ibuf);
  4737. }
  4738.  
  4739. static image_buffer *
  4740. image_buffer_new_with_header (gchar *data, gint width, gint height)
  4741. {
  4742.   image_buffer    *new;
  4743.   guchar    *ptr;
  4744.  
  4745.   DEBUGBLOCK (N_("image_buffer_new_with_header\n"));
  4746.  
  4747.   new = image_buffer_new (width, height, 3);
  4748.   for (ptr = new->data; ptr < new->data + new->size; ptr += 3)
  4749.     HEADER_PIXEL (data, ptr);
  4750.  
  4751.   RETURN new;
  4752. }
  4753.  
  4754. static image_buffer *
  4755. image_buffer_set_from_header (image_buffer *buffer,
  4756.                   gchar *data,
  4757.                   gint width,
  4758.                   gint height)
  4759. {
  4760.   guchar    *ptr;
  4761.  
  4762.   DEBUGBLOCK (N_("image_buffer_set_from_header"));
  4763.  
  4764.   image_buffer_resize (buffer, width, height, 3);
  4765.  
  4766.   for (ptr = buffer->data; ptr < buffer->data + buffer->size; ptr += 3)
  4767.     HEADER_PIXEL (data, ptr);
  4768.  
  4769.   RETURN buffer;
  4770. }
  4771.  
  4772. static void
  4773. image_buffer_copy (image_buffer *dest, image_buffer *src)
  4774. {
  4775.   G_ASSERT (src->data);
  4776.  
  4777.   DEBUGBLOCK (N_("image_buffer_copy\n"));
  4778.  
  4779.   image_buffer_resize (dest, src->width, src->height, src->ch);
  4780.   g_memmove (dest->data, src->data, src->width * src->height * src->ch);
  4781.  
  4782.   RETURN;
  4783. }
  4784.  
  4785. static void
  4786. image_buffer_copy_drawable (image_buffer *ibuf, gint32 drawable_id)
  4787. {
  4788.   gint        x, y;
  4789.   GDrawable    *drawable;
  4790.   GPixelRgn    src_rgn;
  4791.   GCHAR        *src;
  4792.   gpointer    pr;
  4793.   gint        gap = 0;
  4794.  
  4795.   DEBUGBLOCK (N_("thumbnail_set_data_from_drawable\n"));
  4796.  
  4797.   image_buffer_resize (ibuf, THUMBNAIL_WIDTH, THUMBNAIL_HEIGHT, 3);
  4798.  
  4799.   drawable = gimp_drawable_get (drawable_id);
  4800.   G_ASSERT (drawable != NULL);
  4801.  
  4802.   ibuf->width = gimp_drawable_width (drawable_id);
  4803.   ibuf->height = gimp_drawable_height (drawable_id);
  4804.  
  4805.   gap = (gimp_drawable_has_alpha (drawable_id)) ? 1 : 0;
  4806.   gimp_pixel_rgn_init (&src_rgn, drawable, 0, 0,
  4807.                ibuf->width, ibuf->height,
  4808.                FALSE, FALSE);
  4809.   pr = gimp_pixel_rgns_register (1, &src_rgn);
  4810.  
  4811.   for (; pr != NULL; pr = gimp_pixel_rgns_process (pr))
  4812.     {
  4813.       gint    gx = src_rgn.x;
  4814.       gint    gy = src_rgn.y;
  4815.  
  4816.       for (y = 0; y < src_rgn.h; y++)
  4817.     {
  4818.       src = src_rgn.data + y * src_rgn.rowstride;
  4819.  
  4820.       for (x = 0; x < src_rgn.w; x++)
  4821.         {
  4822.           guchar    *ch = src + (x * (3 + gap));
  4823.           gint    offset = (gy + y) * ibuf->width * 3 + (gx + x) * 3;
  4824.  
  4825.           ibuf->data[offset    ] = *ch;
  4826.           ibuf->data[offset + 1] = *(ch + 1);
  4827.           ibuf->data[offset + 2] = *(ch + 2);
  4828.         }
  4829.     }
  4830.     }
  4831.  
  4832.   RETURN;
  4833. }
  4834.  
  4835. /*
  4836.  * Thumbnail
  4837.  */
  4838. static void
  4839. thumbnail_initialize (Thumbnail *thumb)
  4840. {
  4841.   G_ASSERT (thumb);
  4842.  
  4843.   INITIALIZE_ATTRIBUTES (thumb);
  4844.   thumb->image = NULL;
  4845.   thumb->name = NULL;
  4846.   thumb->info = NULL;
  4847. }
  4848.  
  4849. static gint
  4850. thumbnail_compare_name (Thumbnail *a, Thumbnail *b)
  4851. {
  4852.   return strcmp (a->name, b->name);
  4853. }
  4854.  
  4855. static void
  4856. thumbnail_copy_data (Thumbnail *dest, Thumbnail *src, gint all_p)
  4857. {
  4858.   DEBUGBLOCK (N_("thumbnail_copy_data (to %sinitialized thumb.)\n"),
  4859.           (dest->image ? "" : "un"));
  4860.  
  4861.   if (src->image != NULL)
  4862.     {
  4863.       if (! dest->image)
  4864.     {
  4865.       dest->image = image_buffer_new (THUMBNAIL_WIDTH, THUMBNAIL_HEIGHT, 1);
  4866.       DPRINT (N_("success to new\n"));
  4867.     }
  4868.       image_buffer_copy (dest->image, src->image);
  4869.       DPRINT (N_("success to copy to image_buffer\n"));
  4870.     }
  4871.   else
  4872.     {
  4873.       DPRINT (N_("src thumbnail has no image_buffer\n"));
  4874.     }
  4875.  
  4876.   if (all_p)
  4877.     {
  4878.       G_ASSERT (src->name);
  4879.  
  4880.       if (dest->name)
  4881.     g_free (dest->name);
  4882.       dest->name = g_strdup (src->name);
  4883.  
  4884.       G_ASSERT (src->info);
  4885.  
  4886.       if (dest->info)
  4887.     g_free (dest->info);
  4888.       dest->info = g_strdup (src->info);
  4889.     }
  4890.   RETURN;
  4891. }
  4892.  
  4893. static void
  4894. thumbnail_free_image (Thumbnail *thumb)
  4895. {
  4896.   DEBUGBLOCK (N_("thumbnail_free_image\n"));
  4897.   G_ASSERT (thumb);
  4898.  
  4899.   if (thumb->image)
  4900.     {
  4901.       image_buffer_free (thumb->image);
  4902.       thumb->image = NULL;
  4903.     }
  4904.   RETURN;
  4905. }
  4906.  
  4907. /*
  4908.  * filename
  4909.  */
  4910.  
  4911. /* foo/bar.baz => foo/bar.baz/.xvpics */
  4912. static GCHAR *
  4913. pathname_build_thumbnail_dirname (GCHAR *dirname)
  4914. {
  4915.   gint    dir_len, cd_len, cp_len;
  4916.   GCHAR    *thumbnail_dcname;
  4917.   GCHAR    *thumbnail_dir = THUMBNAIL_DIR;
  4918.  
  4919.   dir_len = strlen (dirname);
  4920.   cd_len = strlen (THUMBNAIL_DIR);
  4921.   cp_len = dir_len + cd_len;
  4922.   thumbnail_dcname = (GCHAR *) g_malloc (cp_len + 1);
  4923.  
  4924.   g_memmove (thumbnail_dcname, dirname, dir_len);
  4925.   g_memmove (thumbnail_dcname + dir_len, thumbnail_dir, cd_len);
  4926.   thumbnail_dcname[cp_len] = 0;
  4927.  
  4928.   return thumbnail_dcname;
  4929. }
  4930.  
  4931. static GCHAR *
  4932. pathname_build_thumbnail_filename (GCHAR *filename)
  4933. {
  4934.   gint    index;
  4935.   gint    fname_len, tname_len;
  4936.   GCHAR    *thumbnail_fcname;
  4937.   GCHAR    *thumbnail_dir = THUMBNAIL_DIR;
  4938.  
  4939.   fname_len = strlen (filename);
  4940.   tname_len = fname_len + strlen (thumbnail_dir);
  4941.   index = pathname_get_last_separator_index (filename);
  4942.  
  4943.   G_ASSERT (0 <= index);
  4944.  
  4945.   thumbnail_fcname = (GCHAR *) g_malloc (tname_len + 1);
  4946.   g_memmove (thumbnail_fcname, filename, index);
  4947.   g_memmove (thumbnail_fcname + index, thumbnail_dir, strlen (thumbnail_dir));
  4948.   g_memmove (thumbnail_fcname + (index + strlen (thumbnail_dir)),
  4949.          filename + index,
  4950.          fname_len - index);
  4951.   thumbnail_fcname [tname_len] = 0;
  4952.   return thumbnail_fcname;
  4953. }
  4954.  
  4955. static GCHAR *
  4956. pathname_get_canonical_name (GCHAR *name)
  4957. {
  4958.   GCHAR    cwd[PATH_LENGTH];
  4959.   GCHAR    *dir;
  4960.  
  4961.   dir = g_malloc (PATH_LENGTH);
  4962.  
  4963.   os_file_get_current_directory (cwd);
  4964.   if (os_file_change_current_directory (name) == -1)
  4965.     {
  4966.       g_free (dir);
  4967.       return NULL;
  4968.     }
  4969.   os_file_get_current_directory (dir);
  4970.   os_file_change_current_directory (cwd);
  4971.  
  4972.   return dir;
  4973. }
  4974.  
  4975. static gint
  4976. pathname_get_last_period_index (GCHAR *pathname)
  4977. {
  4978.   gint    index;
  4979.   gint    pname_len;
  4980.  
  4981.   pname_len = strlen (pathname);
  4982.  
  4983.   for (index = pname_len - 2; 0 <= index; index--)
  4984.     if (pathname[index] == '.') break;
  4985.  
  4986.   return index;
  4987. }
  4988.  
  4989. static gint
  4990. pathname_get_last_separator_index (GCHAR *pathname)
  4991. {
  4992.   gint    index;
  4993.   gint    pname_len;
  4994.  
  4995.   G_ASSERT (pathname);
  4996.   pname_len = strlen (pathname);
  4997.  
  4998.   for (index = pname_len - 2; 0 <= index; index--)
  4999.     if (pathname[index] == G_DIR_SEPARATOR) break;
  5000.  
  5001.   return index;
  5002. }
  5003.  
  5004. /* /foo/bar/baz => /foo/bar
  5005.   /foo/bar/baz/ => /foo/bar/baz (impossible case)
  5006. */
  5007. static GCHAR *
  5008. pathname_get_directoryname (GCHAR *pathname)
  5009. {
  5010.   gint    pname_len, slash_pos;
  5011.   GCHAR    *dirname;
  5012.  
  5013.   __DEBUGBLOCK (N_("pathname_get_directoryname (\"%s\")\n"), pathname);
  5014.   G_ASSERT (pathname);
  5015.  
  5016.   pname_len = strlen (pathname);
  5017.   slash_pos = pathname_get_last_separator_index (pathname);
  5018.  
  5019.   if (slash_pos <= 0)
  5020.     RETURN ((strcmp (pathname, G_DIR_SEPARATOR_S) == 0) ? NULL : g_strdup (G_DIR_SEPARATOR_S));
  5021.  
  5022.   dirname = (GCHAR *) g_malloc (slash_pos + 1);
  5023.   g_memmove (dirname, pathname, slash_pos);
  5024.   dirname[slash_pos] = 0;
  5025.  
  5026.   RETURN dirname;
  5027. }
  5028.  
  5029. static GCHAR *
  5030. pathname_get_vaild_directoryname (GCHAR *pathname)
  5031. {
  5032.   GCHAR    *dirname;
  5033.  
  5034.   DEBUGBLOCK (N_("pathname_get_vaild_directoryname (\"%s\")\n"), pathname);
  5035.   G_ASSERT (pathname);
  5036.  
  5037.   dirname = pathname_get_directoryname (pathname);
  5038.   while (os_file_kind (dirname, TRUE) != DIRECTORY)
  5039.     {
  5040.       GCHAR *tmp;
  5041.  
  5042.       tmp = pathname_get_vaild_directoryname (dirname);
  5043.       g_free (dirname);
  5044.       dirname = tmp;
  5045.     }
  5046.   RETURN dirname;
  5047. }
  5048.  
  5049. /* foo/bar.baz => bar.baz */
  5050. static GCHAR *
  5051. pathname_get_basename (GCHAR *pathname)
  5052. {
  5053.   return pathname + pathname_get_last_separator_index (pathname) + 1;
  5054. }
  5055.  
  5056. /* pathname_is_valid_thumbnail_filename return nil if:
  5057.    1. thumbnail is older than the corresponding image file, or
  5058.    2. thumbnail file does not exist */
  5059. static gint
  5060. pathname_is_valid_thumbnail_filename (GCHAR *thumbnail_name)
  5061. {
  5062.   GCHAR        *file_name;
  5063.   struct stat    image_f, thumbnail_f;
  5064.   gint        stripper;
  5065.   gint        last_slash, parent_slash;
  5066.  
  5067.   stripper = strlen (THUMBNAIL_DIR);
  5068.   file_name = (GCHAR *) g_malloc (strlen (thumbnail_name) - stripper + 1);
  5069.   last_slash = pathname_get_last_separator_index (thumbnail_name);
  5070.   parent_slash = last_slash - strlen (THUMBNAIL_DIR);
  5071.   /* /.../dir/  */
  5072.   g_memmove (file_name, thumbnail_name, parent_slash);
  5073.   /* /.../dir/image.file */
  5074.   g_memmove (file_name + parent_slash,
  5075.          thumbnail_name + last_slash,
  5076.          strlen (thumbnail_name) - last_slash);
  5077.   /* /.../dir/image.file + TERMINATOR */
  5078.   file_name [strlen (thumbnail_name) - stripper] = 0;
  5079.  
  5080.   if (stat (file_name, &image_f) != 0)
  5081.     return FALSE;
  5082.   if (stat (thumbnail_name, &thumbnail_f) != 0)
  5083.     return FALSE;
  5084.  
  5085.   return (image_f.st_mtime <= thumbnail_f.st_mtime);
  5086. }
  5087.  
  5088. static gint
  5089. pathname_match_inhibit_suffix (GCHAR *pathname)
  5090. {
  5091.   gint    index = 0;
  5092.   gint    pathlen = strlen (pathname);
  5093.  
  5094.   for (; index < num_inhibit_suffix; index++)
  5095.     {
  5096.       GCHAR    *suffix = inhibit_suffix_table[index];
  5097.       gint    prelen = strlen (suffix);
  5098.  
  5099.       if (pathlen < prelen)
  5100.     continue;
  5101.  
  5102.       /* the last letter check. It will be much cheap test. */
  5103.       if (suffix[prelen - 1] == pathname[pathlen - 1])
  5104.     if (strcmp (suffix, pathname + (pathlen - prelen)) == 0)
  5105.       return TRUE;
  5106.     }
  5107.   return FALSE;
  5108. }
  5109.  
  5110. /*
  5111.  * system call (mainly file operations) interface
  5112.  */
  5113. static gint
  5114. os_file_change_current_directory (GCHAR *pathname)
  5115. {
  5116. #ifdef G_OS_WIN32
  5117.   /* //HB: On windoze e.g. chdir("D:") has a special meaning 
  5118.    * which isn't meant here
  5119.    */
  5120.   if ((2 == strlen(pathname)) && (':' == pathname[1]))
  5121.     return chdir ("\\");
  5122. #endif
  5123.   return chdir (pathname);
  5124. }
  5125.  
  5126. static void
  5127. os_file_get_current_directory (GCHAR *buffer)
  5128. {
  5129.   GCHAR *dir;
  5130.  
  5131.   *buffer = 0;
  5132.  
  5133.   /* We don't use getcwd(3) on SUNOS, because, it does a popen("pwd")
  5134.    * and, if that wasn't bad enough, hangs in doing so.
  5135.    */
  5136. #if    defined (sun) && !defined (__SVR4)
  5137.   dir =  getwd (buffer);
  5138. #else    /* !sun */
  5139.   dir = getcwd (buffer, PATH_LENGTH - 1);
  5140. #endif    /* !sun */
  5141.  
  5142. #ifdef G_OS_WIN32
  5143.   /* //HB: Only the root drive gets a backslash at it's end
  5144.    * Remove it; we don't need it.
  5145.    */
  5146.   if ((strlen(buffer) == 3) && (':' == buffer[1]) && ('\\' == buffer[2]))
  5147.     buffer[2] = 0;
  5148. #endif
  5149.  
  5150.   if (!dir || !*buffer)
  5151.     {
  5152.       /* hm, should we g_error() out here?
  5153.        * this can happen if e.g. "./" has mode \0000
  5154.        */
  5155.       buffer[0] = G_DIR_SEPARATOR;
  5156.       buffer[1] = 0;
  5157.     }
  5158. }
  5159.  
  5160. static gdouble
  5161. os_file_get_modify_timestamp (GCHAR *pathname)
  5162. {
  5163.   struct stat path_stat;
  5164.  
  5165.   stat (pathname, &path_stat);
  5166.  
  5167.   return (gdouble) path_stat.st_mtime;
  5168. }
  5169.  
  5170. /* if b has larger name than a, return 1 */
  5171. /* Warning: file existance check is omitted */
  5172. static gint
  5173. os_file_alphasort (GCHAR **a, GCHAR **b)
  5174. {
  5175.   return strcmp (*a, *b);
  5176. }
  5177.  
  5178. /* if b is newer than a, return 1 This means we get reverse ordered list. */
  5179. static gint
  5180. os_file_mtimesort (GCHAR **a, GCHAR **b)
  5181. {
  5182.   struct stat a_stat;
  5183.   struct stat b_stat;
  5184.  
  5185.   stat (*a, &a_stat);
  5186.   stat (*b, &b_stat);
  5187.  
  5188.   if (a_stat.st_mtime < b_stat.st_mtime)
  5189.     return 1;
  5190.   else if (a_stat.st_mtime == b_stat.st_mtime)
  5191.     return 0;
  5192.   else
  5193.     return -1;
  5194. }
  5195.  
  5196. static gint
  5197. os_scandir_selector (GCHAR *filename)
  5198. {
  5199.   return 1;
  5200. }
  5201.  
  5202. static gint
  5203. os_scandir (GCHAR *dir_name,
  5204.         GCHAR ***filename_list,
  5205.         scandir_selector_function selector,
  5206.         gint sort_by_name_p)
  5207. {
  5208.   GCHAR        **list = NULL;
  5209.   struct dirent    *dirent_ptr;
  5210.   DIR        *directory;
  5211.   gint        entry_count = 32;
  5212.   gint        fail_p = FALSE;
  5213.   gint        i;
  5214.  
  5215.   DEBUGBLOCK (N_("os_scandir (\"%s\", ...)\n"), dir_name);
  5216.  
  5217.   *filename_list = NULL;
  5218.   if (! (directory = opendir (dir_name)))
  5219.     {
  5220.       closedir (directory);
  5221.       RETURN 0;
  5222.     }
  5223.  
  5224.   if (NULL == (list = g_new (GCHAR *, entry_count)))
  5225.     {
  5226.       closedir (directory);
  5227.       RETURN 0;
  5228.     }
  5229.  
  5230.   i = 0;
  5231.   while ((! fail_p) && (NULL != (dirent_ptr = readdir (directory))))
  5232.     {
  5233.       if (i == entry_count)
  5234.     {
  5235.       gint    new_count = entry_count * 2;
  5236.       GCHAR    **new_list = NULL;
  5237.  
  5238.       /* expand list */
  5239.       new_list = g_realloc (list, new_count * sizeof (GCHAR *));
  5240.       if (new_list == NULL)
  5241.         {
  5242.           /* too much, try allocating less */
  5243.           new_count = entry_count + 1;
  5244.           new_list = g_realloc (list, new_count * sizeof (GCHAR *));
  5245.           if (new_list == NULL)
  5246.         fail_p = TRUE;
  5247.         }
  5248.       list = new_list;
  5249.       entry_count = new_count;
  5250.     }
  5251.       /* then copy the name to the cache */
  5252.       if (NULL == (list[i] = g_malloc (strlen (dirent_ptr->d_name) + 1)))
  5253.     fail_p = TRUE;
  5254.       else
  5255.     strcpy (list[i++], dirent_ptr->d_name);
  5256.     }
  5257.   closedir (directory);
  5258.  
  5259.   if (fail_p)
  5260.     {
  5261.       while (0 < i--)
  5262.     g_free (list[i]);
  5263.       g_free (list);
  5264.  
  5265.       RETURN 0;
  5266.     }
  5267.  
  5268.   if (i < entry_count)
  5269.     {
  5270.       list = g_realloc (list, i * sizeof (GCHAR *));
  5271.       entry_count = i;
  5272.     }
  5273.  
  5274.   qsort (list,
  5275.      entry_count,
  5276.      sizeof (GCHAR *),
  5277.      (sort_compare_function) (sort_by_name_p ? &os_file_alphasort : &os_file_mtimesort));
  5278.  
  5279.   *filename_list = list;
  5280.  
  5281.   RETURN entry_count;
  5282. }
  5283.  
  5284. static gint
  5285. os_copy_file (GCHAR *filename, GCHAR *newname)
  5286. {
  5287.   gint        from;
  5288.   gint        to;
  5289.   gint        size;
  5290.   GCHAR        buffer[LINE_BUF_SIZE];
  5291.   mode_t    mode = NEW_FILE_MODE;
  5292.  
  5293.   if ((from = open (filename, O_RDONLY)) == -1)
  5294.     {
  5295.       perror ("os_copy_file: can't open source file");
  5296.       return FALSE;
  5297.     }
  5298.   if ((to = creat (newname, mode)) == -1)
  5299.     {
  5300.       close (from);
  5301.       perror ("os_copy_file: can't create destination file");
  5302.       return FALSE;
  5303.     }
  5304.   while (0 < (size = read (from, buffer, LINE_BUF_SIZE - 1)))
  5305.     write (to, buffer, size);
  5306.  
  5307.   close (from);
  5308.   close (to);
  5309.  
  5310.   return TRUE;
  5311. }
  5312.  
  5313. static gint
  5314. os_delete_file (GCHAR *filename)
  5315. {
  5316.   gint    type = os_file_kind (filename, TRUE);
  5317.  
  5318.   switch (type)
  5319.     {
  5320.     case REGFILE:
  5321.       return unlink (filename);
  5322.       break;
  5323.     case DIRECTORY:
  5324.       return rmdir (filename);
  5325.       break;
  5326.     default:
  5327.       return -1;
  5328.       break;
  5329.     }
  5330. }
  5331.  
  5332. static gint
  5333. os_file_is_writable (GCHAR *filename)
  5334. {
  5335.   struct stat    ent;
  5336.   gint        flag = FALSE;
  5337.  
  5338.   DEBUGBLOCK (N_("os_file_is_writable (\"%s\")\n"), filename);
  5339.  
  5340.   if (stat (filename, &ent) == 0)
  5341.     {
  5342. #ifdef S_IWUSR
  5343.       flag = S_IWUSR & ent.st_mode;
  5344. #else
  5345.       flag = S_IWRITE & ent.st_mode;
  5346. #endif
  5347.  
  5348. #if !defined (G_OS_WIN32)
  5349.       if (flag)
  5350.     flag = (ent.st_uid == getuid ());
  5351.       if (! flag)
  5352.     flag = (S_IWGRP & ent.st_mode) && (ent.st_gid == getgid ());
  5353.       if (! flag)
  5354.     flag = (S_IWOTH & ent.st_mode);
  5355. #else
  5356.       /* //HB: I don't know if only the M$VC compiler is bogus, but 
  5357.        * we need to return either FALSE==0 or TRUE==1
  5358.        */
  5359.       flag = (flag) ? TRUE : FALSE;
  5360. #endif
  5361.     }
  5362.  
  5363.   DPRINT (N_("%s is%s writable.\n"), filename, flag ? "" : " not");
  5364.   RETURN flag;
  5365. }
  5366.  
  5367. /* If SHRINK is non-FALSE, symbolic links are referred. */
  5368. static gint
  5369. os_file_kind (GCHAR *filename, gint shrink)
  5370. {
  5371.   struct stat    ent_sbuf;
  5372.   gint        flag = TRUE;            /*  fail safe */
  5373.   gint        symlink = FALSE;
  5374.   gint        size = 0;
  5375.   GCHAR        fname[PATH_LENGTH], tmp[PATH_LENGTH];
  5376.  
  5377.   G_ASSERT (filename);
  5378.  
  5379. #if !defined (G_OS_WIN32)
  5380.   if (*filename == G_DIR_SEPARATOR)
  5381. #else
  5382.   if ((strlen(filename) > 1) && (':' == filename[1]))
  5383. #endif
  5384.     strcpy (fname, filename);
  5385.   else
  5386.     sprintf (fname, "%s%c%s", cwd_cache->name, G_DIR_SEPARATOR, filename);
  5387.  
  5388.   while ((size = readlink (fname, tmp, PATH_LENGTH - 1)) != -1)
  5389.     {
  5390.       /* fname is a symbolic link. */
  5391.       symlink = TRUE;
  5392.       tmp[size] = 0;
  5393.       if (tmp[0] != G_DIR_SEPARATOR)
  5394.     {
  5395.       GCHAR    *dir = NULL;
  5396.       GCHAR    scratch[PATH_LENGTH];
  5397.  
  5398.       dir = pathname_get_directoryname (fname);
  5399.       sprintf (scratch, "%s%c%s", dir, G_DIR_SEPARATOR, tmp);
  5400.       strcpy (fname, scratch);
  5401.       g_free (dir);
  5402.     }
  5403.       else
  5404.     {
  5405.       strcpy (fname, tmp);
  5406.     }
  5407.     }
  5408.  
  5409.   if (stat (fname, &ent_sbuf) == 0)
  5410.     {
  5411.       /* something exists */
  5412.       if (S_ISDIR (ent_sbuf.st_mode))
  5413.     flag = DIRECTORY;
  5414.       else if (S_ISREG (ent_sbuf.st_mode))
  5415.     flag = REGFILE;
  5416.  
  5417.       if ((! shrink) && symlink)
  5418.     {
  5419.       switch (flag)
  5420.         {
  5421.         case REGFILE:
  5422.           flag = SLNKFILE;
  5423.           break;
  5424.         case DIRECTORY:
  5425.           flag = SLNKDIR;
  5426.           break;
  5427.         default:
  5428.           break;
  5429.         }
  5430.     }
  5431.     }
  5432.   else
  5433.     {
  5434.       /* error was occured */
  5435.       if (errno == ENOENT)
  5436.     flag = NOT_EXIST;
  5437.     }
  5438.   return flag;
  5439. }
  5440.  
  5441. static gint
  5442. os_file_size (GCHAR *filename)
  5443. {
  5444.   struct stat ent_sbuf;
  5445.   gint    size = 0;
  5446.   gint    f_size = -1;
  5447.   GCHAR    fname[PATH_LENGTH], tmp[PATH_LENGTH];
  5448.  
  5449.   strcpy (fname, filename);
  5450.   while ((size = readlink (fname, tmp, PATH_LENGTH - 1)) != -1)
  5451.     {
  5452.       /* fname is a symbolic link. */
  5453.       tmp[size] = 0;
  5454.       strcpy (fname, tmp);
  5455.     }
  5456.   if (stat (fname, &ent_sbuf) == 0)
  5457.     f_size = ent_sbuf.st_size;
  5458.  
  5459.   return f_size;
  5460. }
  5461.  
  5462. static gint
  5463. os_mkdir (GCHAR *pathname)
  5464. {
  5465.   GCHAR        info[256];
  5466.   mode_t    mode = NEW_DIRECTORY_MODE;
  5467.   gint        kind;
  5468.  
  5469.   kind = os_file_kind (pathname, TRUE);
  5470.  
  5471.   if (NOT_EXIST == kind)
  5472.     {
  5473.       if (os_make_directory (pathname, mode) != -1)
  5474.     {
  5475.       GCHAR    *canonical;
  5476.  
  5477.       canonical = pathname_get_canonical_name (pathname);
  5478.       if (canonical != NULL)
  5479.         {
  5480.           GCHAR        *parent;
  5481.           directory_cache    *dcache = NULL;
  5482.  
  5483.           parent = pathname_get_directoryname (canonical);
  5484.           dcache = guash_lookup_directory_cache (parent);
  5485.  
  5486.           if (dcache != NULL)
  5487.         {
  5488.           directory_cache_add_directory (dcache,
  5489.                          pathname_get_basename(canonical),
  5490.                          DIRECTORY);
  5491.           dcache->timestamp = os_file_get_modify_timestamp (dcache->name);
  5492.           SET_ATTRIBUTE (dcache, DISORDERED_P);
  5493.         }
  5494.           if (dcache == cwd_cache)
  5495.         {
  5496.           guash_update_cwd_cache (REDRAW);
  5497.         }
  5498.  
  5499.           g_free (parent);
  5500.         }
  5501.       else
  5502.         printf ("os_mkdir: can't move to the directory\n");
  5503.       g_free (canonical);
  5504.     }
  5505.     }
  5506.   else
  5507.     {
  5508.       sprintf (info, _("%s already exists"), pathname);
  5509.       thumbnail_panel_set_info (info);
  5510.     }
  5511.   return (NOT_EXIST == kind);
  5512. }
  5513.  
  5514. static gint
  5515. os_make_directory (GCHAR *pathname, gint mode)
  5516. {
  5517.   return mkdir (pathname, mode);
  5518. }
  5519.  
  5520. static gint
  5521. os_rename_file (GCHAR *filename, GCHAR *newname)
  5522. {
  5523.   return rename (filename, newname);
  5524. }
  5525.  
  5526. /* dialog stuff */
  5527. static int
  5528. DIALOG (gint restart)
  5529. {
  5530.   GtkWidget    *hbox;
  5531.   GtkWidget    *ps_box;
  5532.   GtkWidget    *thumbnail_panel_frame = NULL;
  5533.   GtkWidget    *button;
  5534.   gchar        **argv;
  5535.   gint        argc;
  5536.  
  5537.   DEBUGBLOCK (N_("DIALOG (building dialog window)\n"));
  5538.  
  5539.   argc = 1;
  5540.   argv = g_new (gchar *, 1);
  5541.   argv[0] = g_strdup (PLUG_IN_NAME);
  5542.   gdk_set_use_xshm (gimp_use_xshm ());
  5543.  
  5544.   /* Initialize Gtk toolkit */
  5545.   gtk_set_locale ();
  5546.  
  5547. #ifdef HAVE_LC_MESSAGES
  5548.   setlocale(LC_MESSAGES, "");
  5549. #endif
  5550. #if defined(ENABLE_NLS) & defined(LOCALEDIR)
  5551.   bindtextdomain(TEXT_DOMAIN, LOCALEDIR);
  5552.   textdomain(TEXT_DOMAIN);
  5553. #endif
  5554.  
  5555.   gtk_init (&argc, &argv);
  5556.   gtk_rc_parse (gimp_gtkrc ());
  5557.  
  5558.   dlg = gtk_dialog_new ();
  5559.   gtk_window_set_wmclass (GTK_WINDOW (dlg), "Guash", "Gimp");
  5560.   gtk_window_set_title (GTK_WINDOW (dlg), "Guash: initializing...");
  5561.   gtk_window_position (GTK_WINDOW (dlg), GTK_WIN_POS_MOUSE);
  5562.   gtk_signal_connect (GTK_OBJECT (dlg), "destroy",
  5563.               (GtkSignalFunc) gtkW_close_callback, NULL);
  5564.   /* handle the wm close signal */
  5565.   gtk_signal_connect (GTK_OBJECT (dlg), "delete_event",
  5566.               (GtkSignalFunc) gtkW_close_callback, NULL);
  5567.  
  5568.   /* Hey, don't remove the following sentence!
  5569.      Desired behavior of Cursor keys depends on it, magically... */
  5570.   gtk_signal_connect (GTK_OBJECT (dlg), "key_press_event",
  5571.               (GtkSignalFunc) cursor_event_handler,
  5572.               NULL);
  5573.   /* set geometric parameters */
  5574. #ifdef GTK_HAVE_FEATURES_1_1_0
  5575.   the_panel.font_height = gdk_string_height (GTK_WIDGET (dlg)->style->font, "yX");
  5576. #else
  5577. #ifdef THUMBNAIL_FONT_SIZE
  5578.   the_panel.font_height = THUMBNAIL_FONT_SIZE;
  5579. #else
  5580.   the_panel.font_height = 18;
  5581. #endif
  5582. #endif
  5583.   the_panel.ncol = gtkW_parse_gimprc_gint ("guash-ncol",
  5584.                        NCOL_OF_THUMBNAIL_PANEL_DEFAULT);
  5585.   the_panel.ncol = CLAMP (the_panel.ncol, NCOL_OF_THUMBNAIL_PANEL_MIN, 10);
  5586.   the_panel.nrow = gtkW_parse_gimprc_gint ("guash-nrow",
  5587.                        NROW_OF_THUMBNAIL_PANEL_DEFAULT);
  5588.   the_panel.nrow = CLAMP (the_panel.nrow, NROW_OF_THUMBNAIL_PANEL_MIN, 10);
  5589.   the_panel.nthumb_in_page = the_panel.ncol * the_panel.nrow;
  5590.   the_panel.width = COL2WIDTH (the_panel.ncol);
  5591.   the_panel.height = ROW2HEIGHT (the_panel.nrow);
  5592.  
  5593.   /* Action Area */
  5594.   gtk_container_border_width (GTK_CONTAINER (GTK_DIALOG (dlg)->action_area), 0);
  5595.   gtk_box_set_spacing (GTK_BOX (GTK_DIALOG (dlg)->action_area), 0);
  5596.  
  5597.   widget_for_selecion[0]
  5598.     = button
  5599. #ifdef NEW_DND_API
  5600.     = gtk_button_new_with_label ("Copy/Move");
  5601. #else
  5602.     = gtk_button_new_with_label ("Drag copy");
  5603. #endif
  5604.   GTK_CONTAINER (button)->border_width = 0;
  5605.   gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dlg)->action_area), button,
  5606.               TRUE, TRUE, 0);
  5607.   gtk_widget_show (button);
  5608.   gtk_widget_realize (button);
  5609.   gtk_signal_connect (GTK_OBJECT (button),
  5610.               "clicked",
  5611.               GTK_SIGNAL_FUNC (dnd_drag_button_callback),
  5612.               button);
  5613. #ifdef NEW_DND_API
  5614.   gtk_signal_connect (GTK_OBJECT (button),
  5615.               "drag_data_get",
  5616.               GTK_SIGNAL_FUNC (dnd_drag_request_callback),
  5617.               NULL);
  5618.   gtk_drag_source_set (button, GDK_BUTTON1_MASK | GDK_BUTTON3_MASK,
  5619.                dnd_target_table, dnd_n_targets,
  5620.                GDK_ACTION_COPY | GDK_ACTION_MOVE);
  5621.   gtk_signal_connect (GTK_OBJECT (button),
  5622.               "drag_data_delete",
  5623.               GTK_SIGNAL_FUNC (dnd_source_delete_data),
  5624.               NULL);
  5625. #else
  5626.   gtk_signal_connect (GTK_OBJECT (button),
  5627.               "drag_request_event",
  5628.               GTK_SIGNAL_FUNC (dnd_drag_request_callback),
  5629.               NULL);
  5630.  
  5631.   gtk_widget_dnd_drag_set (button, FALSE, possible_dnd_types, 1);
  5632. #endif
  5633.   gtk_widget_set_sensitive (button, 0);
  5634.  
  5635. #if defined(USE_UPDATE_BUTTON) & (USE_UPDATE_BUTTON == 1)
  5636.   button = gtk_button_new_with_label ("Update");
  5637.   GTK_CONTAINER (button)->border_width = 0;
  5638.   gtk_signal_connect (GTK_OBJECT (button), "clicked",
  5639.               (GtkSignalFunc) update_callback, dlg);
  5640.   gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dlg)->action_area), button,
  5641.               TRUE, TRUE, 0);
  5642.   gtk_widget_show (button);
  5643. #else
  5644. #ifdef NEW_DND_API
  5645.   widget_for_selecion[1]
  5646.     = button
  5647.     = gtk_button_new_with_label ("Move");
  5648.   GTK_CONTAINER (button)->border_width = 0;
  5649.   gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dlg)->action_area), button,
  5650.               TRUE, TRUE, 0);
  5651.   gtk_widget_show (button);
  5652.   gtk_widget_realize (button);
  5653.   gtk_signal_connect (GTK_OBJECT (button),
  5654.               "clicked",
  5655.               GTK_SIGNAL_FUNC (dnd_drag_button_callback),
  5656.               button);
  5657.   gtk_signal_connect (GTK_OBJECT (button),
  5658.               "drag_data_get",
  5659.               GTK_SIGNAL_FUNC (dnd_drag_request_callback),
  5660.               NULL);
  5661.   gtk_drag_source_set (button, GDK_BUTTON1_MASK | GDK_BUTTON3_MASK,
  5662.                dnd_target_table, dnd_n_targets,
  5663.                GDK_ACTION_MOVE);
  5664.   gtk_signal_connect (GTK_OBJECT (button),
  5665.               "drag_data_delete",
  5666.               GTK_SIGNAL_FUNC (dnd_source_delete_data),
  5667.               NULL);
  5668.   gtk_widget_set_sensitive (button, 0);
  5669. #endif
  5670. #endif
  5671.  
  5672.   button = gtk_button_new_with_label ("Help");
  5673.   GTK_CONTAINER (button)->border_width = 0;
  5674.   gtk_signal_connect (GTK_OBJECT (button), "clicked",
  5675.               (GtkSignalFunc) help_callback, dlg);
  5676.   gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dlg)->action_area), button,
  5677.               TRUE, TRUE, 0);
  5678.   gtk_widget_show (button);
  5679.  
  5680.   button = gtk_button_new_with_label ("Close");
  5681.   GTK_CONTAINER (button)->border_width = 0;
  5682.   gtk_signal_connect_object (GTK_OBJECT (button), "clicked",
  5683.                  (GtkSignalFunc) gtkW_close_callback,
  5684.                  GTK_OBJECT(dlg));
  5685.   gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dlg)->action_area), button,
  5686.               TRUE, TRUE, 0);
  5687.   gtk_widget_show (button);
  5688.  
  5689.   /* top row */
  5690.   hbox = gtkW_hbox_new ((GTK_DIALOG (dlg)->vbox), FALSE, FALSE);
  5691.  
  5692.   cwd_label = gtk_label_new ("Gimp Users' Another SHell");
  5693.   gtk_widget_set_usize (cwd_label,
  5694.             LABEL_WIDTH (COL2WIDTH(NCOL_OF_THUMBNAIL_PANEL_MIN)),
  5695.             0);
  5696.   gtk_box_pack_start (GTK_BOX (hbox), cwd_label, TRUE, TRUE, LABEL_PADDING);
  5697.   gtk_misc_set_alignment (GTK_MISC (cwd_label), 0.0, 0.4);
  5698.   gtk_widget_show (cwd_label);
  5699.  
  5700.   button = gtk_button_new_with_label ("Jump");
  5701.   gtk_signal_connect (GTK_OBJECT (button), "clicked",
  5702.               GTK_SIGNAL_FUNC (directory_jump_callback),
  5703.               NULL);
  5704.   gtk_box_pack_end (GTK_BOX (hbox), button, FALSE, FALSE, 0);
  5705.   gtk_widget_set_usize (button, JUMP_BUTTON_WIDTH, 0);
  5706.   gtk_widget_show (button);
  5707.  
  5708.   gtk_widget_show (hbox);
  5709.  
  5710.   /* 2nd row */
  5711.   ps_box = gtkW_hbox_new ((GTK_DIALOG (dlg)->vbox), TRUE, TRUE);
  5712.  
  5713.   gdk_set_use_xshm (gimp_use_xshm ());
  5714.   gtk_preview_set_gamma (gimp_gamma ());
  5715.   {
  5716.     guchar *color_cube;
  5717.     color_cube = gimp_color_cube ();
  5718.     gtk_preview_set_color_cube (color_cube[0], color_cube[1],
  5719.                 color_cube[2], color_cube[3]);
  5720.   }
  5721.   {
  5722.     GtkPreviewInfo *info;
  5723.  
  5724.     info = gtk_preview_get_info ();
  5725.     if ( info->visual->type != GDK_VISUAL_PSEUDO_COLOR)
  5726.       {
  5727.     /* rule of thumb: An extension should not share cmap with the gimp. */
  5728.     gtk_preview_set_install_cmap (gimp_install_cmap ());
  5729.       }
  5730.     gtk_widget_set_default_colormap (gtk_preview_get_cmap ());
  5731.     gtk_preview_reset ();
  5732.     gtk_widget_set_default_visual (gtk_preview_get_visual ());
  5733.   }
  5734.  
  5735.   thumbnail_panel = gtk_preview_new (GTK_PREVIEW_COLOR);
  5736.   gtk_preview_size (GTK_PREVIEW (thumbnail_panel),
  5737.             the_panel.width,
  5738.             the_panel.height);
  5739.   gtk_preview_set_expand (GTK_PREVIEW (thumbnail_panel), TRUE);
  5740.   GTK_WIDGET_CLASS (GTK_OBJECT (thumbnail_panel)->klass)->size_request
  5741.     = thumbnail_panel_size_request;
  5742.   gtk_signal_connect_after (GTK_OBJECT (dlg), "size_allocate",
  5743.                 (GtkSignalFunc) thumbnail_panel_size_allocate,
  5744.                 NULL);
  5745.   GTK_WIDGET_CLASS (GTK_OBJECT (thumbnail_panel)->klass)->key_press_event
  5746.     = cursor_event_handler;
  5747.   gtk_widget_set_events (thumbnail_panel, EVENT_MASK);
  5748.   gtk_signal_connect (GTK_OBJECT (thumbnail_panel), "event",
  5749.               (GtkSignalFunc) preview_event_handler,
  5750.               NULL);
  5751.   gtk_widget_show (thumbnail_panel);
  5752.  
  5753.   thumbnail_panel_frame = gtkW_frame_new (NULL, NULL);
  5754.   gtk_container_border_width (GTK_CONTAINER (thumbnail_panel_frame), 0);
  5755.   gtk_container_add (GTK_CONTAINER (thumbnail_panel_frame), thumbnail_panel);
  5756.   gtk_widget_show (thumbnail_panel_frame);
  5757.  
  5758.   gtkW_ivscroll_entry_new (&widget_for_scroll[0],
  5759.                &widget_for_scroll[1],
  5760.                (GtkSignalFunc) gtkW_iscroll_update,
  5761.                (GtkSignalFunc) gtkW_ientry_update,
  5762.                &the_panel.current_page1,
  5763.                1, 5, 1, &widget_pointer[0]);
  5764.   gtk_widget_set_usize (widget_for_scroll[0], SCROLLBAR_WIDTH, 0);
  5765.   gtk_widget_set_usize (widget_for_scroll[1], JUMP_BUTTON_WIDTH, 0);
  5766.   gtk_widget_show (widget_for_scroll[0]);
  5767.   gtk_widget_show (widget_for_scroll[1]);
  5768.  
  5769.   gtk_box_pack_start (GTK_BOX (ps_box), thumbnail_panel_frame, TRUE, TRUE, 0);
  5770.   gtk_box_pack_start (GTK_BOX (ps_box), widget_for_scroll[0], FALSE, FALSE, 0);
  5771.  
  5772.   gtk_widget_show (ps_box);
  5773.  
  5774. #ifdef NEW_DND_API
  5775. #if 0
  5776.   /* drag side */
  5777.   gtk_drag_source_set (thumbnail_panel, GDK_BUTTON1_MASK | GDK_BUTTON3_MASK,
  5778.                dnd_target_table, dnd_n_targets,
  5779.                GDK_ACTION_COPY | GDK_ACTION_MOVE);
  5780.   gtk_signal_connect (GTK_OBJECT (thumbnail_panel),
  5781.               "drag_data_get",
  5782.               GTK_SIGNAL_FUNC (dnd_drag_request_callback),
  5783.               NULL);
  5784.   gtk_signal_connect (GTK_OBJECT (thumbnail_panel),
  5785.               "drag_data_delete",
  5786.               GTK_SIGNAL_FUNC (dnd_source_delete_data),
  5787.               NULL);
  5788. #endif
  5789.   /* drop size */
  5790.   gtk_drag_dest_set (thumbnail_panel,
  5791.              GTK_DEST_DEFAULT_ALL,
  5792.              dnd_target_table, dnd_n_targets,
  5793.              GDK_ACTION_COPY | GDK_ACTION_MOVE);
  5794.   gtk_signal_connect (GTK_OBJECT (thumbnail_panel),
  5795.               "drag_drop",
  5796.               GTK_SIGNAL_FUNC (dnd_drop),
  5797.               thumbnail_panel);
  5798.   gtk_signal_connect (GTK_OBJECT (thumbnail_panel),
  5799.               "drag_data_received",
  5800.               GTK_SIGNAL_FUNC (dnd_data_received),
  5801.               thumbnail_panel);
  5802. #else
  5803.   gtk_signal_connect (GTK_OBJECT (thumbnail_panel),
  5804.               "drop_data_available_event",
  5805.               GTK_SIGNAL_FUNC (dnd_drop),
  5806.               thumbnail_panel);
  5807.   gtk_widget_dnd_drop_set (thumbnail_panel, TRUE, possible_dnd_types, 1, FALSE);
  5808. #endif
  5809.  
  5810.   /* 3rd row */
  5811.   hbox = gtkW_hbox_new ((GTK_DIALOG (dlg)->vbox), FALSE, FALSE);
  5812.  
  5813.   file_property = gtk_label_new ("Gimp Users' Another SHell: initializing...");
  5814.   gtk_widget_set_usize (file_property,
  5815.             LABEL_WIDTH (COL2WIDTH (NCOL_OF_THUMBNAIL_PANEL_MIN)),
  5816.             0);
  5817.   gtk_box_pack_start (GTK_BOX (hbox), file_property, TRUE, TRUE, LABEL_PADDING);
  5818.   gtk_misc_set_alignment (GTK_MISC (file_property), 0.0, 0.4);
  5819.   gtk_label_set_justify (GTK_LABEL (file_property), GTK_JUSTIFY_FILL);
  5820.   gtk_widget_show (file_property);
  5821.  
  5822.   gtk_box_pack_end (GTK_BOX (hbox), widget_for_scroll[1], FALSE, FALSE, 0);
  5823.   gtk_widget_show (hbox);
  5824.  
  5825.   gtk_window_set_title (GTK_WINDOW (dlg), SHORT_NAME);
  5826.   gtk_widget_show (dlg);
  5827.  
  5828.   DPRINT (N_("Setting timer\n"));
  5829.   guash_is_initialized = FALSE;
  5830.   gtk_timeout_add (INITIALIZATION_DELAY, timer_initialize_guash, NULL);
  5831.   gtk_timeout_add (((VAL.last_dir_name[0] == 0) ?
  5832.             INITIALIZATION_SLEEP_PERIOD : STARTUP_SLEEP_PERIOD),
  5833.            timer_start_guash,
  5834.            NULL);
  5835.  
  5836.   DEBUGEND;
  5837.  
  5838.   gtk_main ();
  5839.   gdk_flush ();
  5840.   return 1;
  5841. }
  5842.  
  5843. static gint
  5844. timer_initialize_guash (gpointer data)
  5845. {
  5846.   DEBUGBLOCK (N_("timer_initialize_guash\n"));
  5847.  
  5848.   if (guash_is_initialized == TRUE)
  5849.     {
  5850.       if ((VAL.last_dir_name[0] == 0) && (cwd_cache == NULL))
  5851.     thumbnail_panel_show_banner_step2 ();
  5852.       return FALSE;
  5853.     }
  5854.  
  5855.   thumbnail_panel_initialize_banner ();
  5856.   thumbnail_panel_show_banner_step1 ();
  5857.  
  5858.   guash_parse_gimprc ();
  5859.   thumbnail_panel_create_menu ();
  5860.   guash_build_thumbnail_colormap ();
  5861.  
  5862.   guash_is_initialized = TRUE;
  5863.   gtk_timeout_add (BANNER_UP_PERIOD, timer_initialize_guash, NULL);
  5864.  
  5865.   RETURN FALSE;
  5866. }
  5867.  
  5868. static gint
  5869. timer_start_guash (gpointer data)
  5870. {
  5871.   gint    flag = FALSE;
  5872.  
  5873.   DEBUGBLOCK (N_("timer_start_guash\n"));
  5874.  
  5875.   if (guash_is_initialized == FALSE)
  5876.     {
  5877.       DPRINT (N_("not yet initialized. resleeping\n"));
  5878.       gtk_timeout_add (STARTUP_SLEEP_PERIOD, timer_start_guash, NULL);
  5879.       return FALSE;
  5880.     }
  5881.   if (VAL.last_dir_name[0] == 0)
  5882.     thumbnail_panel_show_banner_step3 ();
  5883.  
  5884.   thumbnail_panel_reinitialize_banner ();
  5885.  
  5886.   flag = (strlen (VAL.last_dir_name) == 0);
  5887.  
  5888.   the_loaded_data = g_new (Thumbnail, 1);
  5889.   thumbnail_initialize (the_loaded_data);
  5890.   the_loaded_data->image
  5891.     = image_buffer_new (THUMBNAIL_WIDTH, THUMBNAIL_HEIGHT, 3);
  5892.   INITIALIZE_ATTRIBUTES (the_loaded_data);
  5893.  
  5894.   guash_update_cwd_cache (REDRAW);
  5895.  
  5896.   if (flag)
  5897.     gimp_set_data (PLUG_IN_NAME, &VAL, sizeof (VALS));
  5898.  
  5899.   if (HAS_ATTRIBUTE (&VAL, MONITOR_P) && (0 < directory_monitor_interval))
  5900.     gtk_timeout_add (directory_monitor_interval,
  5901.              timer_directory_monitor,
  5902.              NULL);
  5903.   RETURN FALSE;
  5904. }
  5905.  
  5906. static gint
  5907. timer_directory_monitor ()
  5908. {
  5909.   gint        *image_ids;
  5910.   gint        num_images, index;
  5911.   gint        update_p = FALSE;
  5912.  
  5913.   DEBUGBLOCK (N_("timer_directory_monitor\n"));
  5914.  
  5915.   if (HAS_NO_ATTRIBUTE (&VAL, DISPLAY_IMAGE_P))
  5916.     {
  5917.       gtk_timeout_add (directory_monitor_interval,
  5918.                timer_directory_monitor,
  5919.                NULL);
  5920.       RETURN FALSE;
  5921.     }
  5922.  
  5923.   image_ids = gimp_query_images (&num_images);
  5924.  
  5925.   for (index = 0; index < num_images; index++)
  5926.     {
  5927.       GCHAR    *file_name = guash_get_image_filename (image_ids[index]);
  5928.  
  5929.       if (file_name && (os_file_kind (file_name, TRUE) == REGFILE))
  5930.     {
  5931.       GCHAR            *path = NULL;
  5932.       GCHAR            *thumbnail_fname = NULL;
  5933.       directory_cache    *dcache = NULL;
  5934.  
  5935.       path = pathname_get_directoryname (file_name);
  5936.       dcache = directory_cache_of (path);
  5937.       thumbnail_fname = pathname_build_thumbnail_filename (file_name);
  5938.  
  5939.       if (dcache
  5940.           && (dcache->savable == TRUE)
  5941.           && HAS_ATTRIBUTE (dcache, FILED_P)
  5942.           && (dcache->timestamp < os_file_get_modify_timestamp (file_name))
  5943.           && (! pathname_is_valid_thumbnail_filename (thumbnail_fname)))
  5944.         {
  5945.           os_delete_file (thumbnail_fname);
  5946.           if (directory_cache_update_thumbnail_for (cwd_cache, file_name, NULL))
  5947.         {
  5948.           if (cwd_cache == dcache)
  5949.             update_p = TRUE;
  5950.         }
  5951.         }
  5952.       g_free (path);
  5953.     }
  5954.       g_free (file_name);
  5955.     }
  5956.  
  5957.   if ((cwd_cache->savable == TRUE)
  5958.       && (during_buildup_thumbnail_panel == FALSE)
  5959.       && HAS_ATTRIBUTE (cwd_cache, FILED_P)
  5960.       && (cwd_cache->timestamp < os_file_get_modify_timestamp (cwd_cache->name)))
  5961.     thumbnail_panel_set_info (_("FYI, the directory is just updated"));
  5962.  
  5963.   if (update_p)
  5964.     {
  5965.       thumbnail_panel_update ();
  5966.       thumbnail_panel_set_info (_("Directory monitor found updated file(s)"));
  5967.     }
  5968.  
  5969.   gtk_timeout_add (directory_monitor_interval, timer_directory_monitor, NULL);
  5970.  
  5971.   RETURN FALSE;
  5972. }
  5973.  
  5974. static gint
  5975. about_dialog ()
  5976. {
  5977.   GtkWidget    *a_dlg;
  5978.   GtkWidget    *button;
  5979.   GtkWidget    *frame;
  5980.   GtkWidget    *hbox;
  5981.   GtkWidget    *table;
  5982.   GtkWidget    *hseparator;
  5983.   gint        index, i, nhelp;
  5984.   gint        align;
  5985.  
  5986.   nhelp = sizeof (help_document) / sizeof (help_document[0]);
  5987.  
  5988.   a_dlg = gtk_dialog_new ();
  5989.   gtk_window_set_wmclass (GTK_WINDOW (a_dlg), "Guash", "Gimp");
  5990.   gtk_window_set_title (GTK_WINDOW (a_dlg), "Guash help");
  5991.   gtk_window_position (GTK_WINDOW (a_dlg), GTK_WIN_POS_MOUSE);
  5992.   gtk_container_border_width (GTK_CONTAINER (GTK_DIALOG (a_dlg)->action_area),
  5993.                   gtkW_border_width);
  5994.  
  5995.   button = gtk_button_new_with_label (GUASH_TIMESTAMP);
  5996.   GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT);
  5997.   gtk_signal_connect_object (GTK_OBJECT (button), "clicked",
  5998.                  (GtkSignalFunc) gtk_widget_destroy,
  5999.                  GTK_OBJECT(a_dlg));
  6000.   gtk_box_pack_start (GTK_BOX (GTK_DIALOG (a_dlg)->action_area), button,
  6001.               TRUE, TRUE, 0);
  6002.   gtk_widget_grab_default (button);
  6003.   gtk_widget_show (button);
  6004.  
  6005.   hbox = gtkW_hbox_new ((GTK_DIALOG (a_dlg)->vbox), FALSE, TRUE);
  6006.   frame = gtkW_frame_new (hbox, NULL);
  6007.  
  6008.   table = gtkW_table_new (frame, nhelp + 2, 3);
  6009.   index = 0;
  6010.   align = gtkW_align_x;
  6011.   gtkW_align_x = GTK_EXPAND|GTK_FILL;
  6012.  
  6013.   for (i = 0; i < nhelp; i++, index++)
  6014.     {
  6015.       if (help_document[i].action)
  6016.     {
  6017.       gtkW_table_add_label (table, help_document[i].action,
  6018.                 0, 1, index, help_document[i].flush_left);
  6019.       if (help_document[i].condition)
  6020.         gtkW_table_add_label (table, help_document[i].condition,
  6021.                   1, 2, index, help_document[i].flush_left);
  6022.       gtkW_table_add_label (table, help_document[i].behavior,
  6023.                 2, 3, index, help_document[i].flush_left);
  6024.     }
  6025.       else
  6026.     {
  6027.       if (help_document[i].condition)
  6028.         {
  6029.           gtkW_table_add_label (table, help_document[i].condition,
  6030.                     0, 3, index, FALSE);
  6031.         }
  6032.       else            /* hseparator */
  6033.         {
  6034.           hseparator = gtk_hseparator_new ();
  6035.           gtk_table_attach (GTK_TABLE (table), hseparator,
  6036.                 0, 3, index, index + 1,
  6037.                 GTK_FILL, GTK_FILL,
  6038.                 gtkW_border_width, 2 * gtkW_border_width);
  6039.           gtk_widget_show (hseparator);
  6040.         }
  6041.     }
  6042.     }
  6043.   gtkW_align_x = align;
  6044.   gtk_widget_show (a_dlg);
  6045.  
  6046.   gdk_flush ();
  6047.   return 1;
  6048. }
  6049.  
  6050. /*
  6051.  * callbacks
  6052.  */
  6053.  
  6054. static void
  6055. menu_change_directory_callback (GtkWidget *widget, gpointer data)
  6056. {
  6057.   if (data != NULL)
  6058.     guash_change_current_directory ((GCHAR *) data, NULL);
  6059. }
  6060.  
  6061. static void
  6062. directory_jump_callback (GtkWidget *widget, gpointer data)
  6063. {
  6064.   GtkWidget    *jump_menu, *parents_menu, *menu_item;
  6065.  
  6066.   jump_menu = gtk_menu_new ();
  6067.  
  6068.   parents_menu = gtk_menu_item_new_with_label ("Parents");
  6069.   gtk_menu_append (GTK_MENU (jump_menu), parents_menu);
  6070.   gtk_menu_item_set_submenu (GTK_MENU_ITEM (parents_menu),
  6071.                  directory_cache_create_parents_menu ());
  6072.   gtk_widget_show (parents_menu);
  6073.  
  6074.   if (1 < directory_cache_table_size)
  6075.     {
  6076.       GtkWidget *history_menu;
  6077.  
  6078.       history_menu = gtk_menu_item_new_with_label ("History");
  6079.       gtk_menu_append (GTK_MENU (jump_menu), history_menu);
  6080.       gtk_menu_item_set_submenu (GTK_MENU_ITEM (history_menu),
  6081.                  directory_cache_create_history_menu ());
  6082.       gtk_widget_show (history_menu);
  6083.     }
  6084.  
  6085.   menu_item = gtk_menu_item_new_with_label ("To ...");
  6086.   gtk_menu_append (GTK_MENU (jump_menu), menu_item);
  6087.   gtk_signal_connect (GTK_OBJECT (menu_item), "activate",
  6088.               (GtkSignalFunc) fileselector_for_chdir_callback,
  6089.               NULL);
  6090.   gtk_widget_show (menu_item);
  6091.  
  6092.   menu_item = gtk_menu_item_new_with_label ("Close menu");
  6093.   gtk_menu_append (GTK_MENU (jump_menu), menu_item);
  6094.   gtk_signal_connect (GTK_OBJECT (menu_item), "activate",
  6095.               (GtkSignalFunc) NULL,
  6096.               NULL);
  6097.   gtk_widget_show (menu_item);
  6098.  
  6099.   gtk_menu_popup (GTK_MENU (jump_menu), NULL, NULL, NULL, NULL, 1, 0);
  6100. }
  6101.  
  6102. static void
  6103. parent_directory_callback (GtkWidget *widget, gpointer data)
  6104. {
  6105.   guash_change_current_directory ("..", NULL);
  6106. }
  6107.  
  6108. static void
  6109. forward_callback (GtkWidget *widget, gpointer data)
  6110. {
  6111.   thumbnail_panel_move_focus (1);
  6112. }
  6113.  
  6114. static void
  6115. select_and_forward_callback (GtkWidget *widget, gpointer data)
  6116. {
  6117.   gint    index = -1;
  6118.   Thumbnail    *thumbnail;
  6119.  
  6120.   if (selection_is_active ())
  6121.     {
  6122.       index = cwd_cache->last_focus + 1;
  6123.  
  6124.       if (! directory_cache_valid_index (cwd_cache, index))
  6125.     index = -1;
  6126.     }
  6127.   else
  6128.     if (0 < cwd_cache->nimage)
  6129.       index = cwd_cache->ndir;
  6130.  
  6131.   if (directory_cache_valid_image_index (cwd_cache, index))
  6132.     {
  6133.       selection_add_and_show (index);
  6134.       thumbnail = directory_cache_get_nth (cwd_cache, index);
  6135.       thumbnail_panel_set_info (thumbnail->info);
  6136.       cwd_cache->last_focus = index;
  6137.     }
  6138.   else
  6139.     thumbnail_panel_set_info (NULL);
  6140.  
  6141.   thumbnail_panel_update_sensitive_menu ();
  6142. }
  6143.  
  6144. static void
  6145. backward_callback (GtkWidget *widget, gpointer data)
  6146. {
  6147.   thumbnail_panel_move_focus (-1);
  6148. }
  6149.  
  6150. static void
  6151. next_callback (GtkWidget *widget, gpointer data)
  6152. {
  6153.   thumbnail_panel_move_focus (the_panel.ncol);
  6154. }
  6155.  
  6156. static void
  6157. prev_callback (GtkWidget *widget, gpointer data)
  6158. {
  6159.   thumbnail_panel_move_focus (- the_panel.ncol);
  6160. }
  6161.  
  6162. static void
  6163. next_page_callback (GtkWidget *widget, gpointer data)
  6164. {
  6165.   if (cwd_cache->display_page + 1 < cwd_cache_npage ())
  6166.     {
  6167.       cwd_cache->display_page++;
  6168.       thumbnail_panel_update ();
  6169.     }
  6170. }
  6171.  
  6172. static void
  6173. prev_page_callback (GtkWidget *widget, gpointer data)
  6174. {
  6175.   if (0 < cwd_cache->display_page)
  6176.     {
  6177.       cwd_cache->display_page--;
  6178.       thumbnail_panel_update ();
  6179.     }
  6180. }
  6181.  
  6182. static void
  6183. show_comment_callback (GtkWidget *widget, gpointer data)
  6184. {
  6185.   if (directory_cache_valid_image_index (cwd_cache, cwd_cache->last_focus))
  6186.     {
  6187.       Thumbnail *selected;
  6188.       GCHAR    *str;
  6189.  
  6190.       selected = directory_cache_get_nth (cwd_cache, cwd_cache->last_focus);
  6191.  
  6192.       if (selected == NULL)
  6193.     return;
  6194.  
  6195.       str = selected->info;
  6196.       /* OK. search the beginning of comment part in str */
  6197.       while (*str)
  6198.     if (*str++ == ' ')
  6199.       {
  6200.         if ((*str == 0) || (*str == '"'))
  6201.           break;
  6202.       }
  6203.       if (*str == 0)
  6204.     str = _("no comment");
  6205. #ifdef TRANSLATE_PHOTOSHOP_TM
  6206. #warning "Enable translation of the Photoshop comment (in show_comment_callback)"
  6207.       else if (! g_strncasecmp ("\"File written by Adobe Photoshop\250", str, 33))
  6208.     str[32] = ' ';
  6209. #endif
  6210.       thumbnail_panel_set_info (str);
  6211.     }
  6212. }
  6213.  
  6214. static void
  6215. open_callback (GtkWidget *widget, gpointer data)
  6216. {
  6217.   selection_open_files ();
  6218.   guash_discard_events ();
  6219. }
  6220.  
  6221. static void
  6222. update_callback (GtkWidget *widget, gpointer data)
  6223. {
  6224.   ASSERT (cwd_cache_validate ());
  6225.  
  6226.   DEBUGBLOCK (N_("update_callback\n"));
  6227.  
  6228.   directory_cache_delete_invalid_cache_files (cwd_cache, FALSE);
  6229.   cwd_cache_update ();
  6230.  
  6231.   if (HAS_NO_ATTRIBUTE (cwd_cache, DISPLAY_IMAGE_P))
  6232.     thumbnail_panel_set_info ("FYI, thumbnails are not updated in `display all files' mode");
  6233.  
  6234.   RETURN;
  6235. }
  6236.  
  6237. static void
  6238. toggle_display_mode_callback (GtkWidget *widget, gpointer data)
  6239. {
  6240.   ASSERT (cwd_cache_validate ());
  6241.  
  6242.   TOGGLE_ATTRIBUTE (&VAL, DISPLAY_IMAGE_P);
  6243.   guash_update_cwd_cache (RESCAN);
  6244.   thumbnail_panel_set_info (HAS_ATTRIBUTE (&VAL, DISPLAY_IMAGE_P)
  6245.                 ? _("Changed to `display only image' mode")
  6246.                 : _("Changed to `display all files' mode"));
  6247. }
  6248.  
  6249. /* ASSERT (cwd_cache_validate ()); */
  6250.  
  6251. static void
  6252. toggle_save_mode_callback (GtkWidget *widget, gpointer data)
  6253. {
  6254.   GCHAR *message;
  6255.  
  6256.   if (HAS_ATTRIBUTE (&VAL, SAVE_AS_XVPICT_P)
  6257.       && HAS_ATTRIBUTE (&VAL, SAVE_AS_GPICT_P))
  6258.     {
  6259.       RESET_ATTRIBUTE (&VAL, SAVE_AS_GPICT_P);
  6260.       message = "Changed to `save thumbnails in xvpict format' mode";
  6261.     }
  6262.   else if (HAS_ATTRIBUTE (&VAL, SAVE_AS_XVPICT_P)
  6263.        && HAS_NO_ATTRIBUTE (&VAL, SAVE_AS_GPICT_P))
  6264.     {
  6265.       RESET_ATTRIBUTE (&VAL, SAVE_AS_XVPICT_P);
  6266.       message = "Changed to `unsave thumbnails' mode";
  6267.     }
  6268.   else
  6269.     {
  6270.       SET_ATTRIBUTE (&VAL, SAVE_AS_XVPICT_P);
  6271.       SET_ATTRIBUTE (&VAL, SAVE_AS_GPICT_P);
  6272.       message = "Changed to `save thumbnails in 16bit format' mode";
  6273.     }
  6274.  
  6275.   thumbnail_panel_set_info (_(message));
  6276. }
  6277.  
  6278. static void
  6279. toggle_sort_mode_callback (GtkWidget *widget, gpointer data)
  6280. {
  6281.   ASSERT (cwd_cache_validate ());
  6282.  
  6283.   TOGGLE_ATTRIBUTE (&VAL, SORT_BY_NAME_P);
  6284.   guash_update_cwd_cache (RESCAN);
  6285.   thumbnail_panel_set_info (HAS_ATTRIBUTE (&VAL, SORT_BY_NAME_P)
  6286.                 ? _("Changed to `sort by name' mode")
  6287.                 : _("Changed to `sort by date' mode"));
  6288. }
  6289.  
  6290. static void
  6291. purge_selected_thumbnail_file_callback (GtkWidget *widget, gpointer data)
  6292. {
  6293.   selection_iterator    *iterator;
  6294.   Thumbnail        *selected;
  6295.   GCHAR            tmp[PATH_LENGTH];
  6296.  
  6297.   iterator = selection_make_iterator (SELECTION_ORDER_INDEX);
  6298.   while (NULL != (selected = selection_iterator_get_next_thumbnail (iterator)))
  6299.     {
  6300.       GCHAR    *thumbnail_filename = NULL;
  6301.  
  6302.       sprintf (tmp, "%s%c%s", cwd_cache->name, G_DIR_SEPARATOR, selected->name);
  6303.       thumbnail_filename = pathname_build_thumbnail_filename (tmp);
  6304.  
  6305.       if (pathname_is_valid_thumbnail_filename (thumbnail_filename))
  6306.     os_delete_file (thumbnail_filename);
  6307.       g_free (thumbnail_filename);
  6308.     }
  6309.   thumbnail_panel_set_info ("The thumbnail file of the selected image were deleted");
  6310. }
  6311.  
  6312. static void
  6313. purge_thumbnail_file_callback (GtkWidget *widget, gpointer data)
  6314. {
  6315.   ASSERT (cwd_cache_validate ());
  6316.  
  6317.   directory_cache_delete_invalid_cache_files (cwd_cache, TRUE);
  6318. }
  6319.  
  6320. static void
  6321. help_callback (GtkWidget *widget, gpointer client_data)
  6322. {
  6323.   about_dialog ();
  6324. }
  6325.  
  6326. static void
  6327. fileselector_for_copy_callback (GtkWidget *widget, gpointer client_data)
  6328. {
  6329.   GtkWidget *filesel;
  6330.  
  6331.   ASSERT (cwd_cache_validate () && selection_validate_image ());
  6332.  
  6333.   filesel = gtk_file_selection_new ("Copy selected images to");
  6334.   gtk_window_position (GTK_WINDOW (filesel), GTK_WIN_POS_MOUSE);
  6335.  
  6336.   gtk_signal_connect (GTK_OBJECT (GTK_FILE_SELECTION (filesel)->ok_button),
  6337.               "clicked", (GtkSignalFunc) copy_callback,
  6338.               filesel);
  6339.  
  6340.   gtk_signal_connect_object (GTK_OBJECT (GTK_FILE_SELECTION (filesel)->cancel_button),
  6341.                  "clicked", (GtkSignalFunc) gtk_widget_destroy,
  6342.                  GTK_OBJECT (GTK_WINDOW (filesel)));
  6343.  
  6344.   if (selection_is_active ())
  6345.     {
  6346.       Thumbnail    *selected;
  6347.       GCHAR    tmp[PATH_LENGTH];
  6348.  
  6349.       selected = directory_cache_get_nth (cwd_cache, cwd_cache->selection_top);
  6350.  
  6351.       sprintf (tmp, "%s%c%s", cwd_cache->name, G_DIR_SEPARATOR, selected->name);
  6352.       gtk_file_selection_set_filename (GTK_FILE_SELECTION (filesel), tmp);
  6353.     }
  6354.   else if (strlen (VAL.last_dir_name) > 0)
  6355.     {
  6356.       gtk_file_selection_set_filename (GTK_FILE_SELECTION (filesel),
  6357.                        VAL.last_dir_name);
  6358.     }
  6359.   else
  6360.     {
  6361.       gtk_file_selection_set_filename (GTK_FILE_SELECTION (filesel),
  6362.                        cwd_cache->name);
  6363.     }
  6364.   gtk_widget_show (filesel);
  6365. }
  6366.  
  6367. static void
  6368. fileselector_for_move_callback (GtkWidget *widget, gpointer client_data)
  6369. {
  6370.   GtkWidget *filesel;
  6371.  
  6372.   ASSERT (cwd_cache_validate () && selection_validate_image ());
  6373.  
  6374.   if (guash_confirm_operation ("Move") == FALSE)
  6375.     return;
  6376.  
  6377.   filesel = gtk_file_selection_new ("Move selected images to");
  6378.   gtk_window_position (GTK_WINDOW (filesel), GTK_WIN_POS_MOUSE);
  6379.  
  6380.   gtk_signal_connect (GTK_OBJECT (GTK_FILE_SELECTION (filesel)->ok_button),
  6381.               "clicked", (GtkSignalFunc) move_callback,
  6382.               filesel);
  6383.  
  6384.   gtk_signal_connect_object (GTK_OBJECT (GTK_FILE_SELECTION (filesel)->cancel_button),
  6385.                  "clicked", (GtkSignalFunc) gtk_widget_destroy,
  6386.                  GTK_OBJECT (GTK_WINDOW (filesel)));
  6387.  
  6388.   if (fileselector_last_pathname)
  6389.     gtk_file_selection_set_filename (GTK_FILE_SELECTION (filesel),
  6390.                      fileselector_last_pathname);
  6391.   gtk_widget_show (filesel);
  6392. }
  6393.  
  6394. static void
  6395. fileselector_for_chdir_callback (GtkWidget *widget, gpointer client_data)
  6396. {
  6397.   GtkWidget *filesel;
  6398.  
  6399.   ASSERT (cwd_cache_validate ());
  6400.  
  6401.   filesel = gtk_file_selection_new ("Change directory");
  6402.   gtk_window_position (GTK_WINDOW (filesel), GTK_WIN_POS_MOUSE);
  6403.  
  6404.   gtk_signal_connect (GTK_OBJECT (GTK_FILE_SELECTION (filesel)->ok_button),
  6405.               "clicked", (GtkSignalFunc) chdir_callback,
  6406.               filesel);
  6407.  
  6408.   gtk_signal_connect_object (GTK_OBJECT (GTK_FILE_SELECTION (filesel)->cancel_button),
  6409.                  "clicked", (GtkSignalFunc) gtk_widget_destroy,
  6410.                  GTK_OBJECT (GTK_WINDOW (filesel)));
  6411.   {
  6412.     GCHAR    tmp[PATH_LENGTH];
  6413.  
  6414.     sprintf (tmp, "%s%c.", cwd_cache->name, G_DIR_SEPARATOR);
  6415.     gtk_file_selection_set_filename (GTK_FILE_SELECTION (filesel), tmp);
  6416.   }
  6417.  
  6418.   gtk_widget_show (filesel);
  6419. }
  6420.  
  6421. static void
  6422. fileselector_for_mkdir_callback (GtkWidget *widget, gpointer client_data)
  6423. {
  6424.   GtkWidget *filesel;
  6425.  
  6426.   ASSERT (cwd_cache_validate ());
  6427.  
  6428.   filesel = gtk_file_selection_new ("New directory");
  6429.   gtk_window_position (GTK_WINDOW (filesel), GTK_WIN_POS_MOUSE);
  6430.  
  6431.   {
  6432.     GCHAR    tmp[PATH_LENGTH];
  6433.  
  6434.     sprintf (tmp, "%s%c.", cwd_cache->name, G_DIR_SEPARATOR);
  6435.     gtk_file_selection_set_filename (GTK_FILE_SELECTION (filesel), tmp);
  6436.   }
  6437.  
  6438.   gtk_signal_connect (GTK_OBJECT (GTK_FILE_SELECTION (filesel)->ok_button),
  6439.               "clicked", (GtkSignalFunc) mkdir_callback,
  6440.               filesel);
  6441.  
  6442.   gtk_signal_connect_object (GTK_OBJECT (GTK_FILE_SELECTION (filesel)->cancel_button),
  6443.                  "clicked", (GtkSignalFunc) gtk_widget_destroy,
  6444.                  GTK_OBJECT (GTK_WINDOW (filesel)));
  6445.  
  6446.   gtk_widget_show (filesel);
  6447. }
  6448.  
  6449. static void
  6450. copy_callback (GtkWidget *widget, gpointer data)
  6451. {
  6452.   GCHAR    *pathname, *tmp;
  6453.  
  6454.   ASSERT (cwd_cache_validate () && selection_validate_image ());
  6455.  
  6456.   tmp = gtk_file_selection_get_filename (GTK_FILE_SELECTION (data));
  6457.   pathname = (GCHAR *) g_malloc (strlen (tmp) + 1);
  6458.   strcpy (pathname, tmp);
  6459.   guash_set_fileselector_last_value (pathname);
  6460.  
  6461.   gtk_widget_destroy (GTK_WIDGET (data));
  6462.  
  6463.   selection_copy_files_to (pathname);
  6464.  
  6465.   g_free (pathname);
  6466. }
  6467.  
  6468. static void
  6469. move_callback (GtkWidget *widget, gpointer data)
  6470. {
  6471.   GCHAR    *pathname, *tmp;
  6472.  
  6473.   ASSERT (cwd_cache_validate () && selection_validate_image ());
  6474.  
  6475.   tmp = gtk_file_selection_get_filename (GTK_FILE_SELECTION (data));
  6476.   pathname = (GCHAR *) g_malloc (strlen (tmp) + 1);
  6477.   strcpy (pathname, tmp);
  6478.   guash_set_fileselector_last_value (pathname);
  6479.  
  6480.   gtk_widget_destroy (GTK_WIDGET (data));
  6481.  
  6482.   selection_move_files_to (pathname);
  6483.  
  6484.   g_free (pathname);
  6485. }
  6486.  
  6487. static void
  6488. delete_callback (GtkWidget *widget, gpointer data)
  6489. {
  6490.   ASSERT (cwd_cache_validate () && selection_validate_image ());
  6491.  
  6492.   if (HAS_ATTRIBUTE (&VAL, CONFIRM_P))
  6493.     {
  6494.       GCHAR buffer [LINE_BUF_SIZE];
  6495.  
  6496.       if (selection_length () == 1)
  6497.     {
  6498.       Thumbnail    *selected;
  6499.  
  6500.       selected = directory_cache_get_nth (cwd_cache, cwd_cache->selection_top);
  6501.       sprintf (buffer, _(" Do you want to delete the selected image: %s? "),
  6502.            selected->name);
  6503.     }
  6504.       else
  6505.     {
  6506.       sprintf (buffer, _(" Do you want to delete the selected %d images? "),
  6507.            selection_length ());
  6508.     }
  6509.       if (! gtkW_confirmor_dialog (TRUE, buffer, FALSE))
  6510.     return;
  6511.     }
  6512.   selection_delete_files ();
  6513.   guash_discard_events ();
  6514. }
  6515.  
  6516. static void
  6517. select_all_callback (GtkWidget *widget, gpointer data)
  6518. {
  6519.   gint    index;
  6520.   gint    max = directory_cache_num_entry (cwd_cache);
  6521.  
  6522.   for (index = cwd_cache->ndir; index < max; index++)
  6523.     selection_add (index);
  6524.   selection_show ();
  6525.   thumbnail_panel_set_info (NULL);
  6526. }
  6527.  
  6528. static void
  6529. select_none_callback (GtkWidget *widget, gpointer data)
  6530. {
  6531.   selection_reset ();
  6532.   thumbnail_panel_set_info (NULL);
  6533. }
  6534.  
  6535. static void
  6536. chdir_callback (GtkWidget *widget, gpointer data)
  6537. {
  6538.   GCHAR    *pathname, *tmp;
  6539.   gint    f_kind;
  6540.  
  6541.   ASSERT (cwd_cache_validate ());
  6542.  
  6543.   tmp = gtk_file_selection_get_filename (GTK_FILE_SELECTION (data));
  6544.   pathname = (GCHAR *) g_malloc (strlen (tmp) + 1);
  6545.   strcpy (pathname, tmp);
  6546.   guash_set_fileselector_last_value (pathname);
  6547.  
  6548.   gtk_widget_destroy (GTK_WIDGET (data));
  6549.  
  6550.   tmp = pathname;
  6551.   f_kind = os_file_kind (tmp, TRUE);
  6552.   while ((f_kind == NOT_EXIST) || (f_kind == REGFILE))
  6553.     {
  6554.       tmp = pathname_get_directoryname (tmp);
  6555.       f_kind = os_file_kind (tmp, TRUE);
  6556.     }
  6557.  
  6558.   switch (os_file_kind (tmp, TRUE))
  6559.     {
  6560.     case DIRECTORY:
  6561.       guash_change_current_directory (tmp, NULL);
  6562.       break;
  6563.     default:
  6564.       thumbnail_panel_set_info (_("Can't move there"));
  6565.       break;
  6566.     }
  6567.   g_free (pathname);
  6568. }
  6569.  
  6570. static void
  6571. mkdir_callback (GtkWidget *widget, gpointer data)
  6572. {
  6573.   GCHAR    *pathname, *tmp;
  6574.  
  6575.   ASSERT (cwd_cache_validate ());
  6576.  
  6577.   tmp = gtk_file_selection_get_filename (GTK_FILE_SELECTION (data));
  6578.   pathname = (GCHAR *) g_malloc (strlen (tmp) + 1);
  6579.   strcpy (pathname, tmp);
  6580.   gtk_widget_destroy (GTK_WIDGET (data));
  6581.  
  6582.   os_mkdir (pathname);
  6583.  
  6584.   /* guash_set_fileselector_last_value should be called after making the directory */
  6585.   guash_set_fileselector_last_value (pathname);
  6586.  
  6587.   g_free (pathname);
  6588. }
  6589.  
  6590. static void
  6591. selection_map_script_callback (GtkWidget *widget, gpointer data)
  6592. {
  6593.   ASSERT (cwd_cache_validate () && selection_validate_image ());
  6594.  
  6595.   selection_map_script ();
  6596. }
  6597.  
  6598. static void
  6599. selection_map_unix_command_callback (GtkWidget *widget, gpointer data)
  6600. {
  6601.   ASSERT (cwd_cache_validate () && selection_validate_image ());
  6602.  
  6603.   selection_map_unix_command ();
  6604. }
  6605.  
  6606. static void
  6607. jump_to_subdir1_callback (GtkWidget *widget, gpointer data)
  6608. {
  6609.   cwd_cache_jump_to_subdirectory_index (1);
  6610. }
  6611.  
  6612. static void
  6613. jump_to_subdir2_callback (GtkWidget *widget, gpointer data)
  6614. {
  6615.   cwd_cache_jump_to_subdirectory_index (2);
  6616. }
  6617.  
  6618. static void
  6619. jump_to_subdir3_callback (GtkWidget *widget, gpointer data)
  6620. {
  6621.   cwd_cache_jump_to_subdirectory_index (3);
  6622. }
  6623.  
  6624. static void
  6625. jump_to_subdir4_callback (GtkWidget *widget, gpointer data)
  6626. {
  6627.   cwd_cache_jump_to_subdirectory_index (4);
  6628. }
  6629.  
  6630. static void
  6631. jump_to_subdir5_callback (GtkWidget *widget, gpointer data)
  6632. {
  6633.   cwd_cache_jump_to_subdirectory_index (5);
  6634. }
  6635.  
  6636. static void
  6637. jump_to_subdir6_callback (GtkWidget *widget, gpointer data)
  6638. {
  6639.   cwd_cache_jump_to_subdirectory_index (6);
  6640. }
  6641.  
  6642. static void
  6643. jump_to_subdir7_callback (GtkWidget *widget, gpointer data)
  6644. {
  6645.   cwd_cache_jump_to_subdirectory_index (7);
  6646. }
  6647.  
  6648. static void
  6649. jump_to_subdir8_callback (GtkWidget *widget, gpointer data)
  6650. {
  6651.   cwd_cache_jump_to_subdirectory_index (8);
  6652. }
  6653.  
  6654. static void
  6655. jump_to_subdir9_callback (GtkWidget *widget, gpointer data)
  6656. {
  6657.   cwd_cache_jump_to_subdirectory_index (9);
  6658. }
  6659.  
  6660. static gint
  6661. preview_event_handler (GtkWidget *widget, GdkEvent *event)
  6662. {
  6663.   GdkEventButton *bevent;
  6664.   gint        x, y, index;
  6665.  
  6666.   DEBUGBLOCK (N_("preview_event_handler\n"));
  6667.  
  6668.   gtk_widget_get_pointer (widget, &x, &y);
  6669.  
  6670.   bevent = (GdkEventButton *) event;
  6671.   index = (cwd_cache && VALID_POS_P (x, y)) ? (POS_TO_INDEX (x, y)) : -1;
  6672.  
  6673.   switch (event->type)
  6674.     {
  6675.     case GDK_BUTTON_PRESS:
  6676.       _DPRINT (N_("GDK_BUTTON PRESS\n"));
  6677.       if (during_buildup_thumbnail_panel == TRUE)
  6678.     break;
  6679.       switch (bevent->button)
  6680.     {
  6681.     case 1:
  6682.       if (directory_cache_valid_index (cwd_cache, index))
  6683.         {
  6684.           Thumbnail *thumbnail = directory_cache_get_nth (cwd_cache, index);
  6685.  
  6686.           if (HAS_ATTRIBUTE (thumbnail, DIRECTORY_P))
  6687.         {
  6688.           GCHAR    tmp[PATH_LENGTH];
  6689.           GCHAR    *ptr;
  6690.  
  6691.           if (HAS_ATTRIBUTE (thumbnail, PARENT_DIRECTORY_P))
  6692. //#ifndef NATIVE_WIN32
  6693. #if 1
  6694.             ptr = thumbnail->name;
  6695. #else
  6696.                     ptr = "..";
  6697. #endif
  6698.           else
  6699.             {
  6700.               sprintf (tmp, "%s%c%s", cwd_cache->name, G_DIR_SEPARATOR, thumbnail->name);
  6701.               ptr = tmp;
  6702.             }
  6703.  
  6704.           if (selection_is_active ())
  6705.             {
  6706.               if (bevent->state & GDK_CONTROL_MASK)
  6707.             {
  6708.               selection_copy_files_to (ptr);
  6709.             }
  6710.               else if (bevent->state & GDK_SHIFT_MASK)
  6711.             {
  6712.               if (guash_confirm_operation ("Move"))
  6713.                 selection_move_files_to (ptr);
  6714.             }
  6715.               else
  6716.             {
  6717.               guash_change_current_directory (ptr, thumbnail);
  6718.             }
  6719.             }
  6720.           else
  6721.             {
  6722.               WATCH (guash_change_current_directory (ptr, thumbnail));
  6723.             }
  6724.         }
  6725.           else
  6726.         {
  6727.           if (bevent->state & GDK_SHIFT_MASK)
  6728.             {
  6729.               selection_reverse_member (index);
  6730.               if (selection_is_active ())
  6731.             thumbnail_panel_set_info (thumbnail->info);
  6732.               else
  6733.             thumbnail_panel_set_info (NULL);
  6734.             }
  6735.           else
  6736.             {
  6737.               if (selection_is_active () && selection_member_p (index))
  6738.             selection_open_files ();
  6739.               else
  6740.             {
  6741.               selection_reset ();
  6742.               selection_add_and_show (index);
  6743.               thumbnail_panel_set_info (thumbnail->info);
  6744.             }
  6745.             }
  6746.         }
  6747.         }
  6748.       else
  6749.         {
  6750.           thumbnail_panel_set_info (NULL);
  6751.         }
  6752.       break;
  6753.     case 2:
  6754.       if (bevent->state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK))
  6755.         {
  6756.           if (0 < cwd_cache->display_page)
  6757.         cwd_cache->display_page--;
  6758.           else
  6759.         cwd_cache->display_page = cwd_cache_npage () - 1;
  6760.         }
  6761.       else
  6762.         {
  6763.           if (cwd_cache->display_page + 1 < cwd_cache_npage ())
  6764.         cwd_cache->display_page++;
  6765.           else if (0 < cwd_cache->display_page)
  6766.         cwd_cache->display_page = 0;
  6767.         }
  6768.       thumbnail_panel_update ();
  6769.       break;
  6770.     case 3:
  6771.       if (bevent->state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK))
  6772.         gtk_menu_popup (GTK_MENU (thumbnail_panel_root_menu),
  6773.                 NULL, NULL, NULL, NULL, 3, bevent->time);
  6774.       else if (selection_is_active ())
  6775.         gtk_menu_popup (GTK_MENU (thumbnail_panel_selection_menu),
  6776.                 NULL, NULL, NULL, NULL, 3, bevent->time);
  6777.       else
  6778.         gtk_menu_popup (GTK_MENU (thumbnail_panel_root_menu),
  6779.                 NULL, NULL, NULL, NULL, 3, bevent->time);
  6780.       break;
  6781.     default:
  6782.       thumbnail_panel_update ();
  6783.       break;
  6784.     }
  6785.       break;
  6786.     case GDK_EXPOSE:
  6787.       _DPRINT (N_("GDK_EXPOSE\n"));
  6788.       /* current string render works only if the window is on the top. Thus
  6789.      we need repaint when the window receives GDK_EXPOSE.
  6790.      Sun Aug 30 00:48:40 1998 */
  6791.       if (the_panel.resized && (during_buildup_thumbnail_panel == FALSE))
  6792.     {
  6793.       the_panel.resized = FALSE;
  6794.       DPRINT (N_("resized: rendering...\n"));
  6795.       thumbnail_panel_update ();
  6796.       DPRINT (N_("resized: rendering... done\n"));
  6797.       gtk_widget_draw (thumbnail_panel, NULL);
  6798.     }
  6799.       /* from 0.99.5 [Sat Feb 28 06:30:45 1998] gtk_widget_show (dlg); */
  6800.       gdk_flush ();
  6801.       break;
  6802.     default:
  6803.       break;
  6804.     }
  6805.   _DPRINT (N_("end of preview_event_handler\n"));
  6806.  
  6807.   RETURN FALSE;
  6808. }
  6809.  
  6810. static gint
  6811. cursor_event_handler (GtkWidget *widget, GdkEventKey *event)
  6812. {
  6813.   GdkEventKey    *kevent;
  6814.  
  6815.   kevent = (GdkEventKey *) event;
  6816.  
  6817.   switch (kevent->keyval)
  6818.     {
  6819.     case GDK_Left:
  6820.       thumbnail_panel_move_focus (-1);
  6821.       break;
  6822.     case GDK_Right:
  6823.       thumbnail_panel_move_focus (1);
  6824.       break;
  6825.     case GDK_Up:
  6826.       thumbnail_panel_move_focus (- the_panel.ncol);
  6827.       break;
  6828.     case GDK_Down:
  6829.       thumbnail_panel_move_focus (the_panel.ncol);
  6830.       break;
  6831.     default:
  6832. #ifdef GTK_HAVE_FEATURES_1_1_0
  6833.       gtk_accel_groups_activate (GTK_OBJECT (thumbnail_panel),
  6834.                  kevent->keyval,
  6835.                  kevent->state);
  6836. #endif
  6837.       break;
  6838.     }
  6839.   gtk_widget_grab_focus (thumbnail_panel);
  6840.   return FALSE;
  6841. }
  6842.  
  6843. static void
  6844. dnd_drag_button_callback (GtkWidget *widget, GdkEvent *event)
  6845. {
  6846.   thumbnail_panel_set_info (_("You failed to copy/move. Don't click but drag the button"));
  6847. }
  6848.  
  6849. #ifdef NEW_DND_API
  6850. static void
  6851. dnd_drag_request_callback (GtkWidget          *widget,
  6852.                GdkDragContext     *context,
  6853.                GtkSelectionData   *selection_data,
  6854.                guint               info,
  6855.                guint               time,
  6856.                gpointer            data)
  6857. #else
  6858. static void
  6859. dnd_drag_request_callback (GtkWidget *widget, GdkEvent *event)
  6860. #endif
  6861. {
  6862.   selection_iterator    *iterator;
  6863.   Thumbnail        *selected;
  6864.   GString         *str;
  6865.  
  6866.   ASSERT (cwd_cache_validate () && selection_validate_image ());
  6867.  
  6868.   DEBUGBLOCK (N_("dnd_drag_request_callback\n"));
  6869.  
  6870. #ifdef NEW_DND_API
  6871.   str = g_string_new ("");
  6872. #else
  6873.   str = g_string_new (GUASH_DND_SIGNATURE);
  6874. #endif
  6875.  
  6876.   iterator = selection_make_iterator (SELECTION_ORDER_INDEX);
  6877.   while (NULL != (selected = selection_iterator_get_next_thumbnail (iterator)))
  6878.     {
  6879.       g_string_append (str, cwd_cache->name);
  6880.       g_string_append_c (str, G_DIR_SEPARATOR);
  6881.       g_string_append (str, selected->name);
  6882.       g_string_append_c (str, '\n');
  6883.     }
  6884.  
  6885. #ifdef NEW_DND_API
  6886.   DPRINT (N_("set data in dnd_drag_request_callback\n"));
  6887.   gtk_selection_data_set (selection_data,
  6888.               selection_data->target,
  6889.               8, str->str, strlen (str->str) + 1);
  6890. #else
  6891.   gtk_widget_dnd_data_set (widget, event, str->str, strlen (str->str) + 1);
  6892. #endif
  6893.   g_string_free (str, TRUE);
  6894.  
  6895.   RETURN;
  6896. }
  6897.  
  6898. #ifdef NEW_DND_API
  6899. static void
  6900. dnd_source_delete_data  (GtkWidget          *widget,
  6901.              GdkDragContext     *context,
  6902.              gpointer            data)
  6903. {
  6904.   DEBUGBLOCK (N_("dnd_source_delete_data\n"));
  6905.  
  6906.   if (context->action == GDK_ACTION_MOVE)
  6907.     {
  6908.       DPRINT (N_("action is GDK_ACTION_MOVE: delete the selected image\n"));
  6909.       selection_delete_files ();
  6910.     }
  6911.  
  6912.   RETURN;
  6913. }
  6914.  
  6915. static void
  6916. dnd_data_received  (GtkWidget          *widget,
  6917.             GdkDragContext     *context,
  6918.             gint                x,
  6919.             gint                y,
  6920.             GtkSelectionData   *data,
  6921.             guint               info,
  6922.             guint               time)
  6923.  
  6924. {
  6925.   GCHAR    *ptr = NULL;
  6926.   GCHAR    dest[PATH_LENGTH];
  6927.   GCHAR    *subdir_p = NULL;
  6928.   gint    index;
  6929.  
  6930.   ASSERT (cwd_cache_validate ());
  6931.  
  6932.   DEBUGBLOCK (N_("dnd_data_received\n"));
  6933.  
  6934.   ptr = (GCHAR *) data->data;
  6935.   index = VALID_POS_P (x, y) ? POS_TO_INDEX (x, y): -1;
  6936.  
  6937.   if (directory_cache_valid_index (cwd_cache, index))
  6938.     {
  6939.       Thumbnail    *thumbnail = directory_cache_get_nth (cwd_cache, index);
  6940.  
  6941.       if (HAS_ATTRIBUTE (thumbnail, DIRECTORY_P))
  6942.     {
  6943.       if (HAS_ATTRIBUTE (thumbnail, PARENT_DIRECTORY_P))
  6944.         {
  6945.           sprintf (dest, "%s", thumbnail->name);
  6946.           subdir_p = "the parent directory";
  6947.         }
  6948.       else
  6949.         {
  6950.           sprintf (dest, "%s%c%s", cwd_cache->name, G_DIR_SEPARATOR, thumbnail->name);
  6951.           subdir_p = thumbnail->name;
  6952.         }
  6953.     }
  6954.       else
  6955.     strcpy (dest, cwd_cache->name);
  6956.     }
  6957.   else
  6958.     strcpy (dest, cwd_cache->name);
  6959.  
  6960.   {
  6961.     GCHAR    *message;
  6962.  
  6963.     if (subdir_p == NULL)
  6964.       message = "current directory";
  6965.     else
  6966.       message = subdir_p;
  6967.  
  6968.     if (dnd_copy_files_to (ptr, dest, message, (subdir_p == NULL)))
  6969.       {
  6970.     if (context->action == GDK_ACTION_MOVE)
  6971.       gtk_drag_finish (context, TRUE, TRUE, time);
  6972.     else
  6973.       gtk_drag_finish (context, TRUE, FALSE, time);
  6974.       }
  6975.     else
  6976.       {
  6977.     context->action = GDK_ACTION_COPY;
  6978.     gtk_drag_finish (context, FALSE, FALSE, time);
  6979.       }
  6980.   }
  6981.   RETURN;
  6982. }
  6983.  
  6984. static gboolean
  6985. dnd_drop (GtkWidget        *widget,
  6986.       GdkDragContext    *context,
  6987.       gint            x,
  6988.       gint            y,
  6989.       guint            time)
  6990. {
  6991.   DEBUGBLOCK (N_("dnd_drop\n"));
  6992.   RETURN FALSE;
  6993.  
  6994.   DPRINTIF (context->targets) (N_("set data\n"));
  6995.  
  6996.   if (context->targets)
  6997.     gtk_drag_get_data (widget, context,
  6998.                GPOINTER_TO_INT (context->targets->data),
  6999.                time);
  7000.   RETURN FALSE;
  7001. }
  7002.  
  7003. #else
  7004. static void
  7005. dnd_drop (GtkWidget *widget, GdkEvent *event)
  7006. {
  7007.   GCHAR    *ptr = NULL;
  7008.   GCHAR    dest[PATH_LENGTH];
  7009.   GCHAR    *subdir_p = NULL;
  7010.   gint    x, y, index;
  7011.  
  7012.   ASSERT (cwd_cache_validate ());
  7013.  
  7014.   DEBUGBLOCK (N_("dnd_drop\n"));
  7015.  
  7016.   gtk_widget_get_pointer (widget, &x, &y);
  7017.  
  7018.   ptr = (GCHAR *)event->dropdataavailable.data;
  7019.  
  7020.   if (strlen (ptr) < strlen (GUASH_DND_SIGNATURE))
  7021.     RETURN;
  7022.   if (strncmp (ptr, GUASH_DND_SIGNATURE, strlen (GUASH_DND_SIGNATURE)) != 0)
  7023.     RETURN;
  7024.   ptr += strlen (GUASH_DND_SIGNATURE);
  7025.   index = VALID_POS_P (x, y) ? POS_TO_INDEX (x, y): -1;
  7026.  
  7027.   if (directory_cache_valid_index (cwd_cache, index))
  7028.     {
  7029.       Thumbnail    *thumbnail = directory_cache_get_nth (cwd_cache, index);
  7030.  
  7031.       if (HAS_ATTRIBUTE (thumbnail, DIRECTORY_P))
  7032.     {
  7033.       sprintf (dest, "%s%c%s", cwd_cache->name, G_DIR_SEPARATOR, thumbnail->name);
  7034.       subdir_p = thumbnail->name;
  7035.     }
  7036.       else
  7037.     strcpy (dest, cwd_cache->name);
  7038.     }
  7039.   else
  7040.     strcpy (dest, cwd_cache->name);
  7041.  
  7042.   {
  7043.     GCHAR    *message;
  7044.  
  7045.     if (subdir_p == NULL)
  7046.       message = "current directory";
  7047.     else
  7048.       message = subdir_p;
  7049.  
  7050.     dnd_copy_files_to (ptr, dest, message, (subdir_p == NULL));
  7051.   }
  7052.   RETURN;
  7053. }
  7054. #endif
  7055.  
  7056. static gint
  7057. dnd_copy_files_to (GCHAR *buffer, GCHAR *pathname, GCHAR *dir, gint cwd_p)
  7058. {
  7059.   GCHAR            info[LINE_BUF_SIZE];
  7060.   GCHAR            first_file[LINE_BUF_SIZE];
  7061.   GCHAR            *ptr = buffer;
  7062.   gint            kind = NOT_EXIST;
  7063.   gint            success = 0;
  7064.   gint            fail = FALSE;
  7065.   directory_cache    *dcache = NULL;
  7066.  
  7067.   DEBUGBLOCK (N_("dnd_copy_files_to (\"...\", \"%s\", \"%s\", %d)\n"),
  7068.           pathname, dir, cwd_p);
  7069.  
  7070.   dcache = guash_lookup_directory_cache (pathname);
  7071.  
  7072.   while (*ptr)
  7073.     {
  7074.       gint    index = 0;
  7075.  
  7076.       kind = os_file_kind (pathname, TRUE);
  7077.       while ((ptr[index] != 0) && (ptr[index] != '\n'))
  7078.     index++;
  7079.  
  7080.       if ((kind == NOT_EXIST) || (kind == DIRECTORY))
  7081.     {
  7082.       GCHAR    name[PATH_LENGTH];
  7083.       GCHAR    new[PATH_LENGTH];
  7084.       GCHAR    *file_basename;
  7085.  
  7086.       g_memmove (name, ptr, index);
  7087.       name[index] = 0;
  7088.       DPRINT (N_("name: %s\n"), name);
  7089.       file_basename = pathname_get_basename (name);
  7090.  
  7091.       if (kind == DIRECTORY)
  7092.         {
  7093.           if (pathname [strlen (pathname) - 1] != G_DIR_SEPARATOR)
  7094.         sprintf (new, "%s%c%s", pathname, G_DIR_SEPARATOR, file_basename);
  7095.           else
  7096.         sprintf (new, "%s%s", pathname, file_basename);
  7097.         }
  7098.       else
  7099.         sprintf (new, "%s", pathname);
  7100.  
  7101.       DPRINT (N_("destination: %s\n"), new);
  7102.       /* try to copy `name' to `new->str' */
  7103.       if (os_file_kind (new, TRUE) != NOT_EXIST)
  7104.         {
  7105.           fail = TRUE;
  7106.           sprintf (info, _("%s already exists"), new);
  7107.           gtkW_message_dialog (TRUE, info);
  7108.         }
  7109.       else if (guash_copy_image_file (name, new) == TRUE)
  7110.         {
  7111.           if (success++ == 0)
  7112.         strcpy (first_file, file_basename);
  7113.           if (dcache != NULL)
  7114.         directory_cache_update_thumbnail_for (dcache, name, NULL);
  7115.         }
  7116.       else
  7117.         fail = TRUE;
  7118.     }
  7119.       else if (kind == REGFILE)
  7120.     {
  7121.       fail = TRUE;
  7122.       sprintf (info, _("%s already exists"), pathname);
  7123.       gtkW_message_dialog (TRUE, info);
  7124.     }
  7125.       if (ptr[index] == 0)
  7126.     break;
  7127.       else
  7128.     ptr += index + 1;
  7129.     }
  7130.  
  7131.   if (0 < success)
  7132.     {
  7133.       if (dcache != NULL)
  7134.     dcache->timestamp = os_file_get_modify_timestamp (dcache->name);
  7135.       cwd_cache_update_after_file_operation (success, "copied", first_file, dir);
  7136.     }
  7137.   else
  7138.     thumbnail_panel_set_info (_("Failed to copy"));
  7139.  
  7140.   RETURN (! fail);
  7141. }
  7142.  
  7143. /*
  7144.  * start of GtkW lib
  7145.  */
  7146. static void
  7147. gtkW_close_callback (GtkWidget *widget,
  7148.              gpointer   data)
  7149. {
  7150.   gtk_main_quit ();
  7151. }
  7152.  
  7153. static void
  7154. gtkW_message_dialog (gint gtk_was_initialized, GCHAR *message)
  7155. {
  7156.   GtkWidget    *dlg;
  7157.   GtkWidget    *table;
  7158.   GtkWidget    *label;
  7159.   gchar        **argv;
  7160.   gint        argc;
  7161.  
  7162.   if (! gtk_was_initialized)
  7163.     {
  7164.       argc = 1;
  7165.       argv = g_new (gchar *, 1);
  7166.       argv[0] = g_strdup (PLUG_IN_NAME);
  7167.       gtk_init (&argc, &argv);
  7168.       gtk_rc_parse (gimp_gtkrc ());
  7169.     }
  7170.  
  7171.   dlg = gtkW_message_dialog_new (PLUG_IN_NAME);
  7172.   gtk_window_set_wmclass (GTK_WINDOW (dlg), "Guash", "Gimp");
  7173.  
  7174.   table = gtkW_table_new (GTK_DIALOG (dlg)->vbox, 1, 1);
  7175.  
  7176.   label = gtk_label_new (message);
  7177.   gtk_table_attach (GTK_TABLE (table), label, 0, 1, 0, 1, GTK_FILL|GTK_EXPAND,
  7178.             0, 0, 0);
  7179.  
  7180.   gtk_widget_show (label);
  7181.   gtk_widget_show (dlg);
  7182.  
  7183.   gtk_main ();
  7184.   gdk_flush ();
  7185. }
  7186.  
  7187. static GtkWidget *
  7188. gtkW_message_dialog_new (GCHAR *name)
  7189. {
  7190.   GtkWidget *dlg, *button;
  7191.  
  7192.   dlg = gtk_dialog_new ();
  7193.   gtk_window_set_title (GTK_WINDOW (dlg), name);
  7194.   gtk_window_position (GTK_WINDOW (dlg), GTK_WIN_POS_MOUSE);
  7195.   gtk_signal_connect (GTK_OBJECT (dlg), "destroy",
  7196.               (GtkSignalFunc) gtkW_close_callback, NULL);
  7197.   gtk_container_border_width (GTK_CONTAINER (GTK_DIALOG (dlg)->action_area),
  7198.                   gtkW_border_width);
  7199.  
  7200.   /* Action Area */
  7201.   button = gtk_button_new_with_label ("OK");
  7202.   GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT);
  7203.   gtk_signal_connect_object (GTK_OBJECT (button), "clicked",
  7204.                  (GtkSignalFunc) gtk_widget_destroy,
  7205.                  GTK_OBJECT (dlg));
  7206.   gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dlg)->action_area), button,
  7207.               TRUE, TRUE, 0);
  7208.   gtk_widget_grab_default (button);
  7209.   gtk_widget_show (button);
  7210.  
  7211.   return dlg;
  7212. }
  7213.  
  7214. static gint
  7215. gtkW_confirmor_dialog (gint gtk_was_initialized, GCHAR *message, gint default_value)
  7216. {
  7217.   GtkWidget    *dlg;
  7218.   GtkWidget    *table;
  7219.   GtkWidget    *label;
  7220.   gtkW_widget_table    wtable;
  7221.   gchar        **argv;
  7222.   gint        argc;
  7223.  
  7224.   if (! gtk_was_initialized)
  7225.     {
  7226.       argc = 1;
  7227.       argv = g_new (gchar *, 1);
  7228.       argv[0] = g_strdup (PLUG_IN_NAME);
  7229.       gtk_init (&argc, &argv);
  7230.       gtk_rc_parse (gimp_gtkrc ());
  7231.     }
  7232.  
  7233.   dlg = gtkW_confirmor_new (PLUG_IN_NAME, default_value, &wtable);
  7234.  
  7235.   table = gtkW_table_new (GTK_DIALOG (dlg)->vbox, 1, 1);
  7236.  
  7237.   label = gtk_label_new (message);
  7238.   gtk_table_attach (GTK_TABLE (table), label, 0, 1, 0, 1, GTK_FILL|GTK_EXPAND,
  7239.             0, 0, 0);
  7240.  
  7241.   gtk_widget_show (label);
  7242.   gtk_widget_show (dlg);
  7243.  
  7244.   gtk_main ();
  7245.   gdk_flush ();
  7246.   return (gint) wtable.value;
  7247. }
  7248.  
  7249. static GtkWidget *
  7250. gtkW_confirmor_new (GCHAR *name, gint default_value, gtkW_widget_table *table)
  7251. {
  7252.   GtkWidget    *dlg, *button;
  7253.  
  7254.   dlg = gtk_dialog_new ();
  7255.   gtk_window_set_title (GTK_WINDOW (dlg), name);
  7256.   gtk_window_position (GTK_WINDOW (dlg), GTK_WIN_POS_MOUSE);
  7257.   gtk_signal_connect (GTK_OBJECT (dlg), "destroy",
  7258.               (GtkSignalFunc) gtkW_close_callback, NULL);
  7259.   gtk_container_border_width (GTK_CONTAINER (GTK_DIALOG (dlg)->action_area),
  7260.                   gtkW_border_width);
  7261.   table->widget = dlg;
  7262.  
  7263.   /* Action Area */
  7264.   button = gtk_button_new_with_label ("Yes");
  7265.   gtk_signal_connect (GTK_OBJECT (button), "clicked",
  7266.               (GtkSignalFunc) gtkW_confirmor_yes,
  7267.               (gpointer) table);
  7268.   gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dlg)->action_area), button,
  7269.               TRUE, TRUE, 0);
  7270.   GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT);
  7271.   if (default_value == TRUE)
  7272.     {
  7273.       gtk_widget_grab_default (button);
  7274.     }
  7275.   gtk_widget_show (button);
  7276.  
  7277.   button = gtk_button_new_with_label ("No");
  7278.   gtk_signal_connect (GTK_OBJECT (button), "clicked",
  7279.               (GtkSignalFunc) gtkW_confirmor_no,
  7280.               (gpointer) table);
  7281.   gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dlg)->action_area), button,
  7282.               TRUE, TRUE, 0);
  7283.   GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT);
  7284.   if (default_value == FALSE)
  7285.     {
  7286.       gtk_widget_grab_default (button);
  7287.     }
  7288.   gtk_widget_show (button);
  7289.  
  7290.   return dlg;
  7291. }
  7292.  
  7293. static void
  7294. gtkW_confirmor_yes (GtkWidget *widget, gpointer data)
  7295. {
  7296.   gtkW_widget_table *table;
  7297.  
  7298.   table = (gtkW_widget_table *)data;
  7299.   table->value = (gpointer) TRUE;
  7300.   gtk_widget_destroy ((GtkWidget *) (table->widget));
  7301. }
  7302.  
  7303. static void
  7304. gtkW_confirmor_no (GtkWidget *widget, gpointer data)
  7305. {
  7306.   gtkW_widget_table *table;
  7307.  
  7308.   table = (gtkW_widget_table *)data;
  7309.   table->value = (gpointer) FALSE;
  7310.   gtk_widget_destroy ((GtkWidget *) (table->widget));
  7311. }
  7312.  
  7313. static GtkWidget *
  7314. gtkW_frame_new (GtkWidget *parent, GCHAR *name)
  7315. {
  7316.   GtkWidget *frame;
  7317.  
  7318.   frame = gtk_frame_new (name);
  7319.   gtk_frame_set_shadow_type (GTK_FRAME (frame), gtkW_frame_shadow_type);
  7320.   gtk_container_border_width (GTK_CONTAINER (frame), gtkW_border_width);
  7321.   if (parent != NULL)
  7322.     gtk_box_pack_start (GTK_BOX(parent), frame, TRUE, TRUE, 0);
  7323.   gtk_widget_show (frame);
  7324.  
  7325.   return frame;
  7326. }
  7327.  
  7328. static GtkWidget *
  7329. gtkW_hbox_new (GtkWidget *parent, gint expand, gint fill)
  7330. {
  7331.   GtkWidget    *hbox;
  7332.  
  7333.   hbox = gtk_hbox_new (FALSE, 2);
  7334.   gtk_container_border_width (GTK_CONTAINER (hbox), gtkW_border_width);
  7335.   if (parent)
  7336.     {
  7337.       gtk_box_pack_start (GTK_BOX (parent), hbox, expand, fill,
  7338.               gtkW_border_width);
  7339.       /* gtk_container_add (GTK_CONTAINER (parent), hbox); */
  7340.     }
  7341.   gtk_widget_show (hbox);
  7342.  
  7343.   return hbox;
  7344. }
  7345.  
  7346. static GtkWidget *
  7347. gtkW_table_new (GtkWidget *parent, gint col, gint row)
  7348. {
  7349.   GtkWidget    *table;
  7350.  
  7351.   table = gtk_table_new (col,row, FALSE);
  7352.   gtk_container_border_width (GTK_CONTAINER (table), gtkW_border_width);
  7353.   gtk_container_add (GTK_CONTAINER (parent), table);
  7354.   gtk_widget_show (table);
  7355.  
  7356.   return table;
  7357. }
  7358.  
  7359. static GtkWidget *
  7360. gtkW_table_add_label (GtkWidget    *table,
  7361.               GCHAR    *text,
  7362.               gint    x0,
  7363.               gint    x1,
  7364.               gint    y,
  7365.               gint    flush_left)
  7366. {
  7367.   GtkWidget *label;
  7368.  
  7369.   label = gtk_label_new (text);
  7370.   if (flush_left)
  7371.     gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.0);
  7372.   gtk_table_attach (GTK_TABLE(table), label, x0, x1, y, y+1,
  7373.             gtkW_align_x, gtkW_align_y, 5, 0);
  7374.   gtk_widget_show (label);
  7375.  
  7376.   return label;
  7377. }
  7378.  
  7379. static void
  7380. gtkW_iscroll_entry_change_value (gtkW_widget_table *wtable)
  7381. {
  7382.   GtkWidget    *entry;
  7383.   gchar        buffer[32];
  7384.   GtkAdjustment *adjustment = (GtkAdjustment *) (wtable->widget);
  7385.  
  7386.   if (! cwd_cache)
  7387.     return;
  7388.   /*  adustment->value is double, that is not precise to hold long interger. */
  7389.   adjustment->value = * (gint *) (wtable->value);
  7390.   gtk_signal_emit_by_name (GTK_OBJECT (adjustment), "value_changed");
  7391.   entry = gtk_object_get_user_data (GTK_OBJECT (adjustment));
  7392.   if (entry)
  7393.     {
  7394.       sprintf (buffer, "%d", (gint) adjustment->value);
  7395.       gtk_entry_set_text (GTK_ENTRY (entry), buffer);
  7396.     }
  7397. }
  7398.  
  7399. static void
  7400. gtkW_iscroll_update (GtkAdjustment *adjustment,
  7401.              gpointer       data)
  7402. {
  7403.   GtkWidget    *entry;
  7404.   gchar        buffer[32];
  7405.   gint        *val;
  7406.  
  7407.   if (! cwd_cache)
  7408.     return;
  7409.   val = data;
  7410.   if (*val != (gint) adjustment->value)
  7411.     {
  7412.       *val = adjustment->value;
  7413.  
  7414.       entry = gtk_object_get_user_data (GTK_OBJECT (adjustment));
  7415.       if (entry)
  7416.     {
  7417.       sprintf (buffer, "%d", (int) adjustment->value);
  7418.       gtk_entry_set_text (GTK_ENTRY (entry), buffer);
  7419.     }
  7420.       cwd_cache->display_page = (gint) adjustment->value - 1;
  7421.  
  7422.       if (delayed_updating == 0)
  7423.     delayed_updating = gtk_idle_add ((GtkFunction) thumbnail_panel_update_delayed, NULL);
  7424.     }
  7425. }
  7426.  
  7427. static void
  7428. gtkW_ivscroll_entry_new (GtkWidget    **scroll,
  7429.              GtkWidget    **entry,
  7430.              GtkSignalFunc    scroll_update,
  7431.              GtkSignalFunc    entry_update,
  7432.              gint        *value,
  7433.              gdouble    min,
  7434.              gdouble    max,
  7435.              gdouble    step,
  7436.              gpointer    widget_entry)
  7437. {
  7438.   GtkObject    *adjustment;
  7439.   GCHAR        buffer[GTKW_ENTRY_BUFFER_SIZE];
  7440.  
  7441.   adjustment = gtk_adjustment_new (*value, min, max, step, step, step);
  7442.   gtk_signal_connect (GTK_OBJECT (adjustment), "value_changed",
  7443.               (GtkSignalFunc) scroll_update, value);
  7444.  
  7445.   *scroll = gtk_vscrollbar_new (GTK_ADJUSTMENT (adjustment));
  7446.   gtk_range_set_update_policy (GTK_RANGE (*scroll), GTK_UPDATE_CONTINUOUS);
  7447.  
  7448.   *entry = gtk_entry_new ();
  7449.   gtk_object_set_user_data (GTK_OBJECT (*entry), adjustment);
  7450.   gtk_object_set_user_data (GTK_OBJECT (adjustment), *entry);
  7451.   gtk_widget_set_usize (*entry, GTKW_ENTRY_WIDTH, 0);
  7452.  
  7453.   sprintf (buffer, "%d", *value);
  7454.   gtk_entry_set_text (GTK_ENTRY (*entry), buffer);
  7455.   gtk_signal_connect (GTK_OBJECT (*entry), "changed",
  7456.               (GtkSignalFunc) entry_update, value);
  7457.  
  7458.   if (widget_entry)
  7459.     {
  7460.       gtkW_widget_table *tentry = (gtkW_widget_table *) widget_entry;
  7461.  
  7462.       tentry->widget = (GtkWidget *) adjustment;
  7463.       tentry->updater = gtkW_iscroll_entry_change_value;
  7464.       tentry->value = value;
  7465.     }
  7466. }
  7467.  
  7468. static void
  7469. gtkW_ientry_update (GtkWidget *widget,
  7470.             gpointer   data)
  7471. {
  7472.   GtkAdjustment *adjustment;
  7473.   gint        val, new_val;
  7474.  
  7475.   if (! cwd_cache)
  7476.     return;
  7477.  
  7478.   val = cwd_cache->display_page + 1;
  7479.   new_val = atoi (gtk_entry_get_text (GTK_ENTRY (widget)));
  7480.  
  7481.   if (val != new_val)
  7482.     {
  7483.       adjustment = gtk_object_get_user_data (GTK_OBJECT (widget));
  7484.  
  7485.       if ((new_val >= adjustment->lower) &&
  7486.       (new_val <= adjustment->upper))
  7487.     {
  7488.       adjustment->value = new_val;
  7489.       cwd_cache->display_page = (gint) new_val - 1;
  7490.       gtk_signal_emit_by_name (GTK_OBJECT (adjustment), "value_changed");
  7491.     }
  7492.     }
  7493. }
  7494.  
  7495. static void
  7496. gtkW_query_box (GCHAR *title, GCHAR *message, GCHAR *initial, GCHAR *data)
  7497. {
  7498.   GtkWidget *qbox;
  7499.  
  7500.   qbox = gtkW_query_string_box_new (title, message, initial, (gpointer) data);
  7501.   gtk_main ();
  7502.   gdk_flush ();
  7503. }
  7504.  
  7505. static void
  7506. gtkW_preview_force_to_update (GtkWidget *widget)
  7507. {
  7508.   GtkPreview    *preview = GTK_PREVIEW (widget);
  7509.  
  7510.   DEBUGBLOCK (N_("gtkW_preview_force_to_update\n"));
  7511.  
  7512.   gtk_preview_put (GTK_PREVIEW (widget),
  7513.            widget->window, widget->style->black_gc,
  7514.            (widget->allocation.width - preview->buffer_width) / 2,
  7515.            (widget->allocation.height - preview->buffer_height) / 2,
  7516.            0, 0,
  7517.            widget->allocation.width, widget->allocation.height);
  7518.   gdk_flush ();
  7519.  
  7520.   RETURN;
  7521. }
  7522.  
  7523. static void
  7524. gtkW_preview_force_to_update_partially (GtkWidget *widget,
  7525.                     gint x, gint y,
  7526.                     gint width, gint height)
  7527. {
  7528.   GdkRectangle current_row;
  7529.  
  7530.   current_row.x = x;
  7531.   current_row.y = y;
  7532.   current_row.width = width;
  7533.   current_row.height = height;
  7534.  
  7535.   gtk_widget_draw (widget, ¤t_row);
  7536. }
  7537.  
  7538. /* copied from gimp/app/interface.c */
  7539. /*
  7540.  *  A text string query box
  7541.  */
  7542. typedef struct _QueryBox QueryBox;
  7543.  
  7544. struct _QueryBox
  7545. {
  7546.   GtkWidget *qbox;
  7547.   GtkWidget *entry;
  7548.   gpointer data;
  7549. };
  7550.  
  7551. static GtkWidget *
  7552. gtkW_query_string_box_new (char        *title,
  7553.                char        *message,
  7554.                char        *initial,
  7555.                gpointer     data)
  7556. {
  7557.   QueryBox  *query_box;
  7558.   GtkWidget *qbox;
  7559.   GtkWidget *vbox;
  7560.   GtkWidget *label;
  7561.   GtkWidget *entry;
  7562.   GtkWidget *button;
  7563.  
  7564.   query_box = g_new (QueryBox, 1);
  7565.  
  7566.   qbox = gtk_dialog_new ();
  7567.   gtk_window_set_title (GTK_WINDOW (qbox), title);
  7568.   gtk_window_position (GTK_WINDOW (qbox), GTK_WIN_POS_MOUSE);
  7569.   gtk_signal_connect (GTK_OBJECT (qbox), "delete_event",
  7570.               (GtkSignalFunc) gtkW_query_box_delete_callback,
  7571.               query_box);
  7572.   gtk_container_border_width (GTK_CONTAINER (GTK_DIALOG (qbox)->action_area),
  7573.                   gtkW_border_height);
  7574.  
  7575.   button = gtk_button_new_with_label ("OK");
  7576.   GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT);
  7577.   gtk_signal_connect (GTK_OBJECT (button), "clicked",
  7578.                       (GtkSignalFunc) gtkW_query_box_ok_callback,
  7579.                       query_box);
  7580.   gtk_box_pack_start (GTK_BOX (GTK_DIALOG (qbox)->action_area), button, TRUE, TRUE, 0);
  7581.   gtk_widget_grab_default (button);
  7582.   gtk_widget_show (button);
  7583.  
  7584.   button = gtk_button_new_with_label ("Cancel");
  7585.   GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT);
  7586.   gtk_signal_connect (GTK_OBJECT (button), "clicked",
  7587.                       (GtkSignalFunc) gtkW_query_box_cancel_callback,
  7588.                       query_box);
  7589.   gtk_box_pack_start (GTK_BOX (GTK_DIALOG (qbox)->action_area), button, TRUE, TRUE, 0);
  7590.   gtk_widget_show (button);
  7591.  
  7592.   vbox = gtk_vbox_new (FALSE, 1);
  7593.   gtk_container_border_width (GTK_CONTAINER (vbox), gtkW_border_width);
  7594.   gtk_container_add (GTK_CONTAINER (GTK_DIALOG (qbox)->vbox), vbox);
  7595.   gtk_widget_show (vbox);
  7596.  
  7597.   label = gtk_label_new (message);
  7598.   gtk_box_pack_start (GTK_BOX (vbox), label, TRUE, FALSE, 0);
  7599.   gtk_widget_show (label);
  7600.  
  7601.   entry = gtk_entry_new ();
  7602.   gtk_box_pack_start (GTK_BOX (vbox), entry, TRUE, TRUE, 0);
  7603.   if (initial)
  7604.     gtk_entry_set_text (GTK_ENTRY (entry), initial);
  7605.   gtk_widget_show (entry);
  7606.  
  7607.   query_box->qbox = qbox;
  7608.   query_box->entry = entry;
  7609.   query_box->data = data;
  7610.  
  7611.   gtk_widget_show (qbox);
  7612.  
  7613.   return qbox;
  7614. }
  7615.  
  7616. static gint
  7617. gtkW_query_box_delete_callback (GtkWidget *w,
  7618.                 GdkEvent  *e,
  7619.                 gpointer   client_data)
  7620. {
  7621.   gtkW_query_box_cancel_callback (w, client_data);
  7622.   return FALSE;
  7623. }
  7624.  
  7625. static void
  7626. gtkW_query_box_cancel_callback (GtkWidget *w, gpointer   client_data)
  7627. {
  7628.   QueryBox *query_box;
  7629.  
  7630.   query_box = (QueryBox *) client_data;
  7631.  
  7632.   *((GCHAR *) query_box->data) = 0;
  7633.   /*  Destroy the box  */
  7634.   gtk_widget_destroy (query_box->qbox);
  7635.   g_free (query_box);
  7636.   gtk_main_quit ();
  7637. }
  7638.  
  7639. static void
  7640. gtkW_query_box_ok_callback (GtkWidget *w, gpointer   client_data)
  7641. {
  7642.   QueryBox *query_box;
  7643.   char *string;
  7644.  
  7645.   query_box = (QueryBox *) client_data;
  7646.  
  7647.   /*  Get the entry data  */
  7648.   string = g_strdup (gtk_entry_get_text (GTK_ENTRY (query_box->entry)));
  7649.  
  7650.   /*  Call the user defined callback  */
  7651.   /* (* query_box->callback) (w, query_box->data, (gpointer) string); */
  7652.   strcpy ((GCHAR *)query_box->data, string);
  7653.   /*  Destroy the box  */
  7654.   gtk_widget_destroy (query_box->qbox);
  7655.   g_free (query_box);
  7656.   gtk_main_quit ();
  7657. }
  7658.  
  7659. static gint
  7660. gtkW_parse_gimprc_gint (GCHAR *name, gint default_value)
  7661. {
  7662.   GParam    *return_vals;
  7663.   gint        nreturn_vals;
  7664.   gint        val = default_value;
  7665.  
  7666.   return_vals = gimp_run_procedure ("gimp_gimprc_query",
  7667.                     &nreturn_vals,
  7668.                     PARAM_STRING, name,
  7669.                     PARAM_END);
  7670.   if (return_vals[0].data.d_status == STATUS_SUCCESS)
  7671.     val = atoi (return_vals[1].data.d_string);
  7672.   gimp_destroy_params (return_vals, nreturn_vals);
  7673.  
  7674.   return val;
  7675. }
  7676.  
  7677. static GCHAR *
  7678. gtkW_parse_gimprc_string (GCHAR *name, GCHAR *result)
  7679. {
  7680.   GParam    *return_vals;
  7681.   gint        nreturn_vals;
  7682.  
  7683.   return_vals = gimp_run_procedure ("gimp_gimprc_query",
  7684.                     &nreturn_vals,
  7685.                     PARAM_STRING, name,
  7686.                     PARAM_END);
  7687.   if (return_vals[0].data.d_status == STATUS_SUCCESS)
  7688.     strcpy (result, return_vals[1].data.d_string);
  7689.   else
  7690.     *result = 0;
  7691.   gimp_destroy_params (return_vals, nreturn_vals);
  7692.  
  7693.   return result;
  7694. }
  7695.  
  7696. static void
  7697. gtkW_widget_set_cursor (GtkWidget *widget, GdkCursorType cursortype)
  7698. {
  7699.   GdkCursor *cursor = NULL;
  7700.  
  7701.   cursor = gdk_cursor_new (cursortype);
  7702.   gdk_window_set_cursor (widget->window, cursor);
  7703.   gdk_cursor_destroy (cursor);
  7704. }
  7705. /* gtkW ends here */
  7706.  
  7707. static gint
  7708. save_thumbnail_as_xvpict_image (char *filename, Thumbnail *thumb)
  7709. {
  7710.   FILE        *dest;
  7711.   gint        i_width = thumb->image->width;
  7712.   gint        i_height = thumb->image->height;
  7713.   gint        npixel = i_width * i_height;
  7714.  
  7715.   DEBUGBLOCK (N_("save_thumbnail_as_xvpict_image (\"%s\"\n"), filename);
  7716.  
  7717.   if ((dest = fopen (filename, "wb")) == NULL)
  7718.     RETURN FALSE;
  7719.   fprintf (dest, "P7 332\n");
  7720.   fprintf (dest, "#XVVERSION:Version 3.10a  Rev: 12/29/94 (faked by The GIMP/GUASH%s)\n",
  7721.        HAS_ATTRIBUTE (&VAL, SAVE_AS_GPICT_P)? " 2.0" : " 1.0");
  7722.   fprintf (dest, "#IMGINFO:%s\n", thumb->info + strlen (thumb->name) + 1);
  7723.   fprintf (dest, "#END_OF_COMMENTS\n");
  7724.   fprintf (dest, "%d %d 255\n", i_width, i_height);
  7725.   if (thumb->image->ch == 3)
  7726.     {
  7727.       guchar    *ch, val;
  7728.       gint rerr=0, gerr=0, berr=0;
  7729.  
  7730.       if (HAS_NO_ATTRIBUTE (&VAL, SAVE_AS_GPICT_P))
  7731.     {
  7732.       for (ch = thumb->image->data;
  7733.            ch < thumb->image->data + npixel * 3;
  7734.            )
  7735.         {
  7736.           gint32 r, g, b;
  7737.  
  7738.           r = *(ch++) + rerr;
  7739.           r = CLAMP (r, 0, 255);
  7740.           g = *(ch++) + gerr;
  7741.           g = CLAMP (g, 0, 255);
  7742.           b = *(ch++) + berr;
  7743.           b = CLAMP (b, 0, 255);
  7744.  
  7745.           fprintf (dest, "%c", ((r>>5)<<5) | ((g>>5)<<2) | (b>>6));
  7746.  
  7747.           rerr = r - ( (r>>5) * 255 ) / 7;
  7748.           gerr = g - ( (g>>5) * 255 ) / 7;
  7749.           berr = b - ( (b>>6) * 255 ) / 3;
  7750.         }
  7751.     }
  7752.       else
  7753.     {
  7754.       guchar    diff_buf[THUMBNAIL_WIDTH * THUMBNAIL_HEIGHT];
  7755.       guchar    *diff_ptr = diff_buf;
  7756.       guchar    tmp;
  7757.       gint        limit = npixel;
  7758.  
  7759.       DPRINT (N_("save as gpict\n"));
  7760.       for (ch = thumb->image->data;
  7761.            ch < thumb->image->data + limit * 3;
  7762.            )
  7763.         {
  7764.           val = *ch & 0xE0;
  7765.           tmp = (*ch++ & 0x1C) << 3;
  7766.           val |= (*ch & 0xE0) >> 3;
  7767.           tmp |= *ch++ & 0x18;
  7768.           val |= (*ch & 0xC0) >> 6;
  7769.           tmp |= (*ch++ & 0x38) >> 3;
  7770.  
  7771.           fprintf (dest, "%c", val);
  7772.           /* build difference datum */
  7773.           *diff_ptr++ = tmp;
  7774.         }
  7775.       /* save the rest pixels */
  7776.       for (ch = thumb->image->data + limit * 3;
  7777.            ch < thumb->image->data + npixel * 3;
  7778.            )
  7779.         {
  7780.           /* can't combine into a expression. evaluation order problem? */
  7781.           val = (*(ch++) & 0xE0);
  7782.           val |= ((*(ch++) & 0xE0) >> 3);
  7783.           val |= ((*(ch++) & 0xC0) >> 6);
  7784.           fprintf (dest, "%c", val);
  7785.         }
  7786.       {
  7787.         guchar    *compress = NULL;
  7788.         gint    clen;
  7789.  
  7790.         clen = compress_string (diff_buf, npixel, &compress);
  7791.         /* then save difference data */
  7792.         if (0 < clen)
  7793.           fwrite (compress, clen, 1, dest);
  7794.         else
  7795.           fwrite (diff_buf, npixel, 1, dest);
  7796.         g_free (compress);
  7797.       }
  7798.     }
  7799.     }
  7800.   else
  7801.     fwrite (thumb->image->data, i_width * i_height, 1, dest);
  7802.   fclose (dest);
  7803.   RETURN TRUE;
  7804. }
  7805.  
  7806. static Thumbnail *
  7807. load_xvpict_image (char *filename)
  7808. {
  7809.   FILE    *fd;
  7810.   gint     width, height, size;
  7811.   GCHAR    w_buf[4], h_buf[4];
  7812.   GCHAR    comment[LINE_BUF_SIZE];
  7813.   guchar gpict_buffer[THUMBNAIL_WIDTH * THUMBNAIL_HEIGHT + 3];
  7814.  
  7815.   DEBUGBLOCK (N_("load_xvpict_image (\"%s\")\n"), filename);
  7816.  
  7817.   fd = fopen (filename, "rb");
  7818.   if (!fd)
  7819.     RETURN NULL;
  7820.  
  7821.   {
  7822.     gint    flag = TRUE;
  7823.     GCHAR    buffer[LINE_BUF_SIZE];
  7824.  
  7825.     while (flag)
  7826.       {
  7827.     if (fgets (buffer, LINE_BUF_SIZE -1, fd) == NULL)
  7828.       return NULL;
  7829. #if defined (_MSC_VER)
  7830.     /* //HB: The MSVCRT library function does NOT eliminate the 0x0A in binary mode!
  7831.      */
  7832.     if (0x0A == buffer[ strlen (buffer) - 1 ])
  7833.       buffer[strlen(buffer)-1] = 0x0;
  7834. #endif
  7835.     if (g_strncasecmp (buffer, "#IMGINFO", 8) == 0)
  7836.       strcpy (comment, buffer);
  7837.     else if (g_strncasecmp (buffer, "#END_OF_COMMENTS", 16) == 0)
  7838.       flag = FALSE;
  7839.       }
  7840.   }
  7841.  
  7842.   /* read the last line of comment that contains info on image size */
  7843.   if (fscanf (fd, "%s %s", w_buf, h_buf) != 2)
  7844.     {
  7845.       fclose (fd);
  7846.       RETURN NULL;
  7847.     }
  7848.   width = atoi (w_buf);
  7849.   height = atoi (h_buf);
  7850.   size = width * height;
  7851.  
  7852.   /* assure the position in the file!!!
  7853.      Wed Jul  8 23:21:09 1998 -> reimplement Sun Jul 19 03:17:33 1998
  7854.   */
  7855.   while (fgetc (fd) != '\n');
  7856.  
  7857.   {
  7858.     GCHAR    tmp[LINE_BUF_SIZE];
  7859.     GCHAR    *basename = pathname_get_basename (filename);
  7860.  
  7861.     if (the_loaded_data->name)
  7862.       g_free (the_loaded_data->name);
  7863.     the_loaded_data->name = g_strdup (basename);
  7864.  
  7865.     comment[strlen (comment) - 1] = 0; /* chop newline */
  7866.  
  7867.     sprintf (tmp, "%s %s", basename, comment + 9);
  7868.     if (the_loaded_data->info)
  7869.       g_free (the_loaded_data->info);
  7870.     the_loaded_data->info = g_strdup (tmp);
  7871.   }
  7872.   image_buffer_resize (the_loaded_data->image, width, height, 1);
  7873.   if (1 != fread (the_loaded_data->image->data, size, 1, fd))
  7874.     {
  7875.       fclose (fd);
  7876.       DPRINT (N_("fail to load thumbnail\n"));
  7877.       RETURN NULL;
  7878.     }
  7879.  
  7880.   if (HAS_ATTRIBUTE (&VAL, SAVE_AS_GPICT_P)
  7881.       && (1 == fread (gpict_buffer, size, 1, fd)))
  7882.     {
  7883.       guchar    dynamic_buffer[THUMBNAIL_WIDTH * THUMBNAIL_HEIGHT];
  7884.       guchar    diff[4];
  7885.       guchar    *ch = NULL;
  7886.       guchar    *data = NULL;
  7887.       gint    i;
  7888.  
  7889.       DPRINT (N_("guash extended thumbnail format\n"));
  7890.  
  7891.       g_memmove (dynamic_buffer, the_loaded_data->image->data, size);
  7892.       image_buffer_resize (the_loaded_data->image, width, height, 3);
  7893.  
  7894.       data = dynamic_buffer;
  7895.       ch = the_loaded_data->image->data;
  7896.  
  7897.       for (i = 0; i < size; i++)
  7898.     {
  7899.       *ch++ = *data & 0xE0;
  7900.       *ch++ = (*data & 0x1C) << 3;
  7901.       *ch++ = (*data++ & 0x03) << 6;
  7902.     }
  7903.  
  7904.       data = the_loaded_data->image->data;
  7905.       diff[0] = 0;
  7906.       diff[1] = gpict_buffer[0];
  7907.       diff[2] = gpict_buffer[1];
  7908.       diff[3] = gpict_buffer[2];
  7909.       for (ch = gpict_buffer; ch < gpict_buffer + size;)
  7910.     {
  7911.       gint i;
  7912.  
  7913.       for (i = 0; i < 4; i++)
  7914.         {
  7915.           gint    shift = 2 * (3 - i);
  7916.           gint    index = (*ch >> shift) & 0x03;
  7917.           guchar    val = diff[index];
  7918.  
  7919.           val = *ch++;
  7920.           *data |= (val & 0xC0) >> 3;
  7921.           *data = (guchar) (*data * 0xFF / 0xFC);
  7922.           data++;
  7923.           *data |= val & 0x18;
  7924.           *data = (guchar) (*data * 0xFF / 0xF8);
  7925.           data++;
  7926.           *data |= (val & 0x07) << 3;
  7927.           *data = (guchar) (*data * 0xFF / 0xFC);
  7928.           data++;
  7929.         }
  7930.     }
  7931.     }
  7932.   fclose (fd);
  7933.  
  7934.   RETURN the_loaded_data;
  7935. }
  7936.  
  7937. /* I GAVE UP!
  7938.  * gpict is the hidden feature of 2.0.0
  7939.  * I tried to implement some simple but high compression algorithm in vain.
  7940.  * The current gpict requires x2 file size, 16bit quality, x3 heap memory.
  7941.  * Is this acceptable??
  7942.  * I might delete the codes in 2.1.0.... Or never put it in public....
  7943.  */
  7944. static gint
  7945. compress_string (guchar *str, gint length, guchar **dest)
  7946. {
  7947.   gint        hist[256];
  7948.   gint        i, m[3];
  7949.   gint        clen = (gint) (length / 4) * 4;
  7950.   guchar    *ch, *buf;
  7951.  
  7952.   return -1;
  7953.  
  7954.   DEBUGBLOCK (N_("compress_string\n"));
  7955.  
  7956.   if (length < 4)
  7957.     RETURN -1;
  7958.  
  7959.   for (i = 0; i < 256; i++)
  7960.     {
  7961.       hist[i] = hist[i] = 0;
  7962.     }
  7963.   for (i = 0; i < length; i++)
  7964.     {
  7965.       hist[(gint) str[i]]++;
  7966.       hist[str[i] & 0x29]++;
  7967.     }
  7968.  
  7969.   m[0] = m[1] = m[2] = 0;
  7970.   for (i = 0; i < 256; i++)
  7971.     if (hist[m[2]] < hist[i])
  7972.       {
  7973.     if (hist[m[0]] < hist[i])
  7974.       {
  7975.         m[2] = m[1];
  7976.         m[1] = m[0];
  7977.         m[0] = i;
  7978.       }
  7979.     else if (hist[m[1]] < hist[i])
  7980.       {
  7981.         m[2] = m[1];
  7982.         m[1] = i;
  7983.       }
  7984.     else
  7985.       m[2] = i;
  7986.       }
  7987.  
  7988.   DPRINT (N_("1st letter: %d/%d (ratio: %f)\n"),
  7989.       hist[m[0]], length, (gint) hist[m[0]] / (gdouble) length);
  7990.   DPRINT (N_("2nd letter: %d/%d (ratio: %f)\n"),
  7991.       hist[m[1]], length, (gint) hist[m[1]] / (gdouble) length);
  7992.   DPRINT (N_("3rd letter: %d/%d (ratio: %f)\n"),
  7993.       hist[m[2]], length, (gint) hist[m[2]] / (gdouble) length);
  7994.   DPRINT (N_("cover rate: %d/%d (ration: %f)\n"),
  7995.       hist[m[0]] + hist[m[1]] + hist[m[2]],
  7996.       length, (gint) (hist[m[0]] + hist[m[1]] + hist[m[2]]) / (gdouble) length);
  7997.  
  7998.   buf = g_new (guchar, 3 + clen);
  7999.   buf[0] = hist[m[0]];
  8000.   buf[1] = hist[m[1]];
  8001.   buf[2] = hist[m[2]];
  8002.  
  8003.   for (ch = buf, i = 0; i < clen * 4; ch++)
  8004.     {
  8005.       gint p;
  8006.  
  8007.       *ch = 0;
  8008.       for (p = 0; p < 4; p++)
  8009.     {
  8010.       gint shift = 2 * (3 - p);
  8011.       if (m[0] == str[i])
  8012.         *ch |= 0x01 << shift;
  8013.       else if (m[1] == str[i])
  8014.         *ch |= 0x10 << shift;
  8015.       else if (m[2] == str[i])
  8016.         *ch |= 0x11 << shift;
  8017.       i++;
  8018.     }
  8019.     }
  8020.  
  8021.   *dest = buf;
  8022.   RETURN 3 + clen;
  8023. }
  8024.  
  8025. /*
  8026.  * Local variables:
  8027.  * compile-command: "gimptool --install guash.c"
  8028.  * End:
  8029.  */
  8030. /* guash.c ends here */
  8031.