1 /* fpick.c
2 Copyright (C) 2007-2020 Mark Tyler and Dmitry Groshev
3
4 This file is part of mtPaint.
5
6 mtPaint is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 3 of the License, or
9 (at your option) any later version.
10
11 mtPaint is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with mtPaint in the file COPYING.
18 */
19
20 #include "global.h"
21 #undef _
22 #define _(X) X
23
24 #include "mygtk.h"
25 #include "fpick.h"
26 #include "vcode.h"
27
28 #ifdef U_FPICK_MTPAINT /* mtPaint fpicker */
29
30 #include "inifile.h"
31 #include "memory.h"
32 #include "png.h" // Needed by canvas.h
33 #include "canvas.h"
34 #include "mainwindow.h"
35 #include "icons.h"
36
37 #define FP_KEY "mtPaint.fpick"
38
39 #define FPICK_ICON_UP 0
40 #define FPICK_ICON_HOME 1
41 #define FPICK_ICON_DIR 2
42 #define FPICK_ICON_HIDDEN 3
43 #define FPICK_ICON_CASE 4
44 #define FPICK_ICON_TOT 5
45
46 #define FPICK_COMBO_ITEMS 16
47
48 enum {
49 COL_FILE = 0,
50 COL_NAME,
51 COL_SIZE,
52 COL_TYPE,
53 COL_TIME,
54 COL_NOCASE,
55 COL_CASE,
56
57 COL_MAX
58 };
59
60 #define RELREF(X) ((char *)&X + X)
61
62 // ------ Main Data Structure ------
63
64 typedef struct {
65 char *title;
66 int flags;
67 int entry_f;
68 int allow_files, allow_dirs; // Allow the user to select files/directories
69 int show_hidden;
70 int cnt, cntx, idx;
71 int fsort; // Sort column/direction of list
72 int *fcols, *fmap;
73 char *cdir, **cpp, *cp[FPICK_COMBO_ITEMS + 1];
74 void **combo, **list;
75 void **hbox, **entry;
76 void **ok, **cancel;
77 memx2 files;
78 char fname[PATHBUF];
79 char txt_directory[PATHBUF]; // Current directory - Normal C string
80 char txt_mask[PATHTXT]; // Filter mask - UTF8 in GTK+2
81 char combo_items[FPICK_COMBO_ITEMS][PATHTXT]; // UTF8 in GTK+2
82 } fpick_dd;
83
84 static int case_insensitive;
85
86 static void fpick_btn(fpick_dd *dt, void **wdata, int what, void **where);
87
88 #if GTK_MAJOR_VERSION == 1
89
90 #include <fnmatch.h>
91 #define fpick_fnmatch(mask, str) !fnmatch(mask, str, FNM_PATHNAME)
92
93 #elif (GTK_MAJOR_VERSION == 2) && (GTK2VERSION < 4) /* GTK+ 2.0/2.2 */
94 #define fpick_fnmatch(mask, str) wjfnmatch(mask, str, TRUE)
95
96 #else
97 #define HAVE_FILEFILTER
98 #endif
99
filter_dir(fpick_dd * dt,const char * pattern)100 static void filter_dir(fpick_dd *dt, const char *pattern)
101 {
102 #ifdef HAVE_FILEFILTER
103 GtkFileFilter *filt = gtk_file_filter_new();
104 GtkFileFilterInfo info;
105 gtk_file_filter_add_pattern(filt, pattern);
106 info.contains = GTK_FILE_FILTER_DISPLAY_NAME;
107 #endif
108 int *cols = dt->fcols, *map = dt->fmap;
109 int i, cnt = dt->cnt;
110 char *s;
111
112 for (i = 0; i < cnt; i++ , cols += COL_MAX)
113 {
114 s = RELREF(cols[COL_NAME]);
115 /* Filter files, let directories pass */
116 if (pattern[0] && (s[0] == 'F'))
117 {
118 #ifdef HAVE_FILEFILTER
119 info.display_name = s + 1;
120 if (!gtk_file_filter_filter(filt, &info)) continue;
121 #else
122 if (!fpick_fnmatch(pattern, s + 1)) continue;
123 #endif
124 }
125 *map++ = i;
126 }
127 dt->cntx = map - dt->fmap;
128
129 #if GTK_MAJOR_VERSION == 3
130 g_object_ref_sink(filt);
131 g_object_unref(filt);
132 #elif defined HAVE_FILEFILTER
133 gtk_object_sink(GTK_OBJECT(filt));
134 #endif
135 }
136
137 static fpick_dd *mdt;
138 static int cmp_rows(const void *f1, const void *f2);
139
fpick_sort(fpick_dd * dt)140 static void fpick_sort(fpick_dd *dt)
141 {
142 if (dt->cntx <= 0) return; // Nothing to do
143 /* Sort row map */
144 mdt = dt;
145 qsort(dt->fmap, dt->cntx, sizeof(dt->fmap[0]), cmp_rows);
146 }
147
148 /* *** A WORD OF WARNING ***
149 * Collating string comparison functions consider letter case only *AFTER*
150 * letter itself for any (usable) value of LC_COLLATE except LC_COLLATE=C, and
151 * in that latter case, order by character codepoint which frequently is
152 * unrelated to alphabetical ordering. And on GTK+1 with multibyte charset,
153 * casefolding will break down horribly, too.
154 * What this means in practice: don't expect anything sane out of alphabetical
155 * sorting outside of strict ASCII, and don't expect anything sane out of
156 * case-sensitive sorting at all. - WJ */
157
158 #if GTK_MAJOR_VERSION == 1
159
160 /* Returns a string which can be used as key for case-insensitive sort;
161 * input string is in locale encoding in GTK+1, in UTF-8 in GTK+2 */
isort_key(char * src)162 static char *isort_key(char *src)
163 {
164 char *s;
165 s = g_strdup(src);
166 g_strdown(s);
167 // !!! Consider replicating g_utf8_collate_key(), based on strxfrm()
168 return (s);
169 }
170
171 /* "strkeycmp" is for sort keys, "strcollcmp" for displayable strings */
172 #define strkeycmp strcoll
173 #define strcollcmp strcoll
174
175 #else /* if GTK_MAJOR_VERSION >= 2 */
176
isort_key(char * src)177 static char *isort_key(char *src)
178 {
179 char *s;
180 src = g_utf8_casefold(src, -1);
181 s = g_utf8_collate_key(src, -1);
182 g_free(src);
183 return (s);
184 }
185
186 #define strkeycmp strcmp
187 #define strcollcmp g_utf8_collate
188
189 #endif
190
191 /* !!! Expects that "txt" points to PATHBUF-sized buffer */
fpick_cleanse_path(char * txt)192 static void fpick_cleanse_path(char *txt) // Clean up null terminated path
193 {
194 char *src, *dest;
195
196 #ifdef WIN32
197 // Unify path separators
198 reseparate(txt);
199 #endif
200 // Expand home directory
201 if ((txt[0] == '~') && (txt[1] == DIR_SEP))
202 {
203 src = file_in_homedir(NULL, txt + 2, PATHBUF);
204 strncpy0(txt, src, PATHBUF - 1);
205 free(src);
206 }
207 // Remove multiple consecutive occurences of DIR_SEP
208 if ((dest = src = strstr(txt, DIR_SEP_STR DIR_SEP_STR)))
209 {
210 while (*src)
211 {
212 if (*src == DIR_SEP) while (src[1] == DIR_SEP) src++;
213 *dest++ = *src++;
214 }
215 *dest++ = '\0';
216 }
217 }
218
219
cmp_rows(const void * f1,const void * f2)220 static int cmp_rows(const void *f1, const void *f2)
221 {
222 static const signed char sort_order[] =
223 { COL_NAME, COL_TIME, COL_SIZE, -1 };
224 int *r1, *r2;
225 char *s1, *s2;
226 int d, c, bits, lvl;
227
228 r1 = mdt->fcols + *(int *)f1 * COL_MAX;
229 r2 = mdt->fcols + *(int *)f2 * COL_MAX;
230
231 /* "/ .." Directory always goes first, other dirs next, files last;
232 * and their type IDs are ordered to reflect that */
233 s1 = RELREF(r1[COL_NAME]);
234 s2 = RELREF(r2[COL_NAME]);
235 if ((d = s1[0] - s2[0])) return (d);
236
237 bits = lvl = 0;
238 c = abs(mdt->fsort) - 1 + COL_NAME;
239
240 while (c >= 0)
241 {
242 if (bits & (1 << c))
243 {
244 c = sort_order[lvl++];
245 continue;
246 }
247 bits |= 1 << c;
248 s1 = RELREF(r1[c]);
249 s2 = RELREF(r2[c]);
250 switch (c)
251 {
252 case COL_TYPE:
253 if ((d = strcollcmp(s1, s2))) break;
254 continue;
255 case COL_SIZE:
256 if ((d = strcmp(s1, s2))) break;
257 continue;
258 case COL_TIME:
259 if ((d = strcmp(s2, s1))) break; // Newest first
260 continue;
261 default:
262 case COL_NAME:
263 c = case_insensitive ? COL_NOCASE : COL_CASE;
264 continue;
265 case COL_NOCASE:
266 case COL_CASE:
267 if ((d = strkeycmp(s1, s2))) break;
268 c = COL_CASE;
269 continue;
270 }
271 break;
272 }
273 return (mdt->fsort < 0 ? -d : d);
274 }
275
276
277 /* Register directory in combo */
fpick_directory_new(fpick_dd * dt,char * name)278 static void fpick_directory_new(fpick_dd *dt, char *name)
279 {
280 char *dest, **cpp, txt[PATHTXT];
281 int i;
282
283 gtkuncpy(txt, name, PATHTXT);
284
285 /* Does this text already exist in the list? */
286 cpp = dt->cpp;
287 for (i = 0 ; i < FPICK_COMBO_ITEMS - 1; i++)
288 if (!strcmp(txt, cpp[i])) break;
289 dest = cpp[i];
290 memmove(cpp + 1, cpp, i * sizeof(*cpp)); // Shuffle items down as needed
291 memcpy(cpp[0] = dest, txt, PATHTXT); // Add item to list
292
293 dt->cdir = txt;
294 cmd_reset(dt->combo, dt);
295 }
296
297 #ifdef WIN32
298
299 #include <ctype.h>
300 #define WIN32_LEAN_AND_MEAN
301 #include <windows.h>
302
scan_drives(fpick_dd * dt,int cdrive)303 static void scan_drives(fpick_dd *dt, int cdrive)
304 {
305 static const unsigned char dmap[COL_MAX] = { 1, 0, 4, 4, 4, 1, 1 };
306 memx2 mem = dt->files;
307 char *cp, *dest, buf[PATHBUF]; // More than enough for 26 4-char strings
308 int i, j, *tc;
309
310 mem.here = 0;
311 getmemx2(&mem, 8000); // default size
312 mem.here += getmemx2(&mem, 26 * ((COL_MAX + 1) * sizeof(int) + 5)); // minimum size
313
314 /* Get the current drive letter */
315 if (!cdrive)
316 {
317 if (GetCurrentDirectory(sizeof(buf), buf) && (buf[1] == ':'))
318 cdrive = buf[0];
319 }
320 cdrive = toupper(cdrive);
321 /* Get all drives */
322 GetLogicalDriveStrings(sizeof(buf), buf);
323
324 tc = dt->fcols = (void *)mem.buf;
325 dest = (void *)(tc + 26 * (COL_MAX + 1));
326 dt->idx = -1;
327 for (i = 0 , cp = buf; *cp; i++ , cp += strlen(cp) + 1)
328 {
329 for (j = 0; j < COL_MAX; j++)
330 {
331 *tc = dest + dmap[j] - (char *)tc;
332 tc++;
333 }
334 strcpy(dest, "DC:\\"); // 'D' is type flag
335 if ((dest[1] = toupper(cp[0])) == cdrive) dt->idx = i;
336 dest += 5;
337 }
338 dt->cnt = i;
339
340 // Setup mapping array
341 dt->fmap = tc;
342 for (j = 0; j < i; j++) *tc++ = j;
343 dt->cntx = i;
344
345 dt->files = mem;
346 }
347
fpick_scan_drives(fpick_dd * dt)348 static void fpick_scan_drives(fpick_dd *dt) // Scan drives, populate widgets
349 {
350 int cdrive = 0;
351
352 /* Get the current drive letter */
353 if (dt->txt_directory[1] == ':') cdrive = dt->txt_directory[0];
354
355 dt->txt_directory[0] = '\0';
356 cmd_setv(dt->combo, "", ENTRY_VALUE); // Just clear it
357
358 scan_drives(dt, cdrive);
359 cmd_reset(dt->list, dt);
360 }
361
362 #endif
363
364 #define MAX_DIR_FILES (16 * 1024 * 1024) /* 16+ million is when to say "Enough" */
365
scan_dir(fpick_dd * dt,DIR * dp,char * select)366 static void scan_dir(fpick_dd *dt, DIR *dp, char *select)
367 {
368 char full_name[PATHBUF], txt_size[64], txt_date[64], tmp_txt[64];
369 char *nm, *src, *dest, *dir = dt->txt_directory;
370 memx2 mem = dt->files;
371 struct tm *lt;
372 struct dirent *ep;
373 struct stat buf;
374 int *tc;
375 int subdir, tf, n, l = strlen(dir), cnt = 0;
376
377 mem.here = 0;
378 getmemx2(&mem, 8000); // default size
379
380 dt->idx = -1;
381 if (strcmp(dir, DIR_SEP_STR)) // Have a parent dir to move to?
382 {
383 // Field #0 - original name
384 addstr(&mem, "..", 0);
385 // Field #1 - type flag (space) + name in GUI encoding
386 addstr(&mem, " " DIR_SEP_STR " ..", 0);
387 // Fields #2-4 empty for all dirs
388 // Fields #5-6 are empty strings, to keep this sorted first
389 addchars(&mem, 0, 3 + 2);
390 cnt++;
391 }
392
393 while ((cnt < MAX_DIR_FILES) && (ep = readdir(dp)))
394 {
395 wjstrcat(full_name, PATHBUF, dir, l, ep->d_name, NULL);
396
397 // Error getting file details
398 if (stat(full_name, &buf) < 0) continue;
399
400 if (!dt->show_hidden && (ep->d_name[0] == '.')) continue;
401
402 #ifdef WIN32
403 subdir = S_ISDIR(buf.st_mode);
404 #else
405 subdir = (ep->d_type == DT_DIR) || S_ISDIR(buf.st_mode);
406 #endif
407 tf = 'F'; // Type flag: 'D' for dir, 'F' for file
408 if (subdir)
409 {
410 if (!dt->allow_dirs) continue;
411 // Don't look at '.' or '..'
412 if (!strcmp(ep->d_name, ".") || !strcmp(ep->d_name, ".."))
413 continue;
414 tf = 'D';
415 }
416 else if (!dt->allow_files) continue;
417
418 /* Remember which row has matching name */
419 if (select && !strcmp(ep->d_name, select)) dt->idx = cnt;
420
421 cnt++;
422
423 // Field #0 - original name
424 addstr(&mem, ep->d_name, 0);
425 // Field #1 - type flag + name in GUI encoding
426 addchars(&mem, tf, 1);
427 nm = gtkuncpy(NULL, ep->d_name, 0);
428 addstr(&mem, nm, 0);
429 // Fields #2-4 empty for dirs
430 if (subdir) addchars(&mem, 0, 3);
431 else
432 {
433 // Field #2 - file size
434 #ifdef WIN32
435 n = snprintf(tmp_txt, 64, "%I64u", (unsigned long long)buf.st_size);
436 #else
437 n = snprintf(tmp_txt, 64, "%llu", (unsigned long long)buf.st_size);
438 #endif
439 memset(txt_size, ' ', 20);
440 dest = txt_size + 20; *dest-- = '\0';
441 for (src = tmp_txt + n - 1; src - tmp_txt > 2; )
442 {
443 *dest-- = *src--;
444 *dest-- = *src--;
445 *dest-- = *src--;
446 *dest-- = ',';
447 }
448 while (src - tmp_txt >= 0) *dest-- = *src--;
449 addstr(&mem, txt_size, 0);
450 // Field #3 - file type (extension)
451 src = strrchr(nm, '.');
452 if (src && (src != nm) && src[1])
453 {
454 #if GTK_MAJOR_VERSION == 1
455 g_strup(src = g_strdup(src + 1));
456 #else
457 src = g_utf8_strup(src + 1, -1);
458 #endif
459 addstr(&mem, src, 0);
460 g_free(src);
461 }
462 else addchars(&mem, 0, 1);
463 // Field #4 - file modification time
464 strcpy(txt_date, " "); // to sort failed files after dirs
465 lt = localtime(&buf.st_mtime);
466 /* !!! localtime() can fail and return NULL if given
467 * "wrong" input value */
468 if (lt) strftime(txt_date, 60, "%Y-%m-%d %H:%M.%S", lt);
469 addstr(&mem, txt_date, 0);
470 }
471 // Field #5 - case-insensitive sort key
472 src = isort_key(nm);
473 addstr(&mem, src, 0);
474 g_free(src);
475 // Field #6 - alphabetic (case-sensitive) sort key
476 #if GTK_MAJOR_VERSION == 1
477 addstr(&mem, nm, 0);
478 #else /* if GTK_MAJOR_VERSION >= 2 */
479 src = g_utf8_collate_key(nm, -1);
480 addstr(&mem, src, 0);
481 g_free(src);
482 #endif
483 g_free(nm);
484 }
485 dt->cnt = cnt;
486
487 /* Now add index array and mapping arrays to all this */
488 l = (~(unsigned)mem.here + 1) & (sizeof(int) - 1);
489 n = cnt * COL_MAX;
490 getmemx2(&mem, l + (n + cnt) * sizeof(int));
491 // Fill index array
492 tc = dt->fcols = (void *)(mem.buf + mem.here + l);
493 src = mem.buf;
494 while (n-- > 0)
495 {
496 *tc = src - (char *)tc;
497 tc++;
498 src += strlen(src) + 1;
499 }
500 // Setup mapping array
501 dt->fmap = tc;
502
503 dt->files = mem;
504 }
505
506 /* Scan directory, populate widgets; return 1 if success, 0 if total failure,
507 * -1 if failed with original dir and scanned a different one */
fpick_scan_directory(fpick_dd * dt,char * name,char * select)508 static int fpick_scan_directory(fpick_dd *dt, char *name, char *select)
509 {
510 DIR *dp;
511 char *cp, *parent = NULL;
512 char full_name[PATHBUF];
513 int len, fail, res = 1;
514
515
516 strncpy0(full_name, name, PATHBUF - 1);
517 len = strlen(full_name);
518 /* Ensure the invariant */
519 if (!len || (full_name[len - 1] != DIR_SEP))
520 full_name[len++] = DIR_SEP , full_name[len] = 0;
521 /* Step up the path till a searchable dir is found */
522 fail = 0;
523 while (!(dp = opendir(full_name)))
524 {
525 res = -1; // Remember that original path was invalid
526 full_name[len - 1] = 0;
527 cp = strrchr(full_name, DIR_SEP);
528 // Try to go one level up
529 if (cp) len = cp - full_name + 1;
530 // No luck - restart with current dir
531 else if (!fail++)
532 {
533 getcwd(full_name, PATHBUF - 1);
534 len = strlen(full_name);
535 full_name[len++] = DIR_SEP;
536 }
537 // If current dir hasn't helped either, give up
538 else return (0);
539 full_name[len] = 0;
540 }
541
542 /* If we're going up the path and want to show from where */
543 if (!select)
544 {
545 if (!strncmp(dt->txt_directory, full_name, len) &&
546 dt->txt_directory[len])
547 {
548 cp = strchr(dt->txt_directory + len, DIR_SEP); // Guaranteed
549 parent = dt->txt_directory + len;
550 select = parent = g_strndup(parent, cp - parent);
551 }
552 }
553 /* If we've nothing to show */
554 else if (!select[0]) select = NULL;
555
556 strncpy(dt->txt_directory, full_name, PATHBUF);
557 fpick_directory_new(dt, full_name); // Register directory in combo
558
559 scan_dir(dt, dp, select);
560 g_free(parent);
561 closedir(dp);
562 filter_dir(dt, dt->txt_mask);
563
564 cmd_reset(dt->list, dt);
565
566 return (res);
567 }
568
fpick_enter_dir_via_list(fpick_dd * dt,char * name)569 static void fpick_enter_dir_via_list(fpick_dd *dt, char *name)
570 {
571 char ndir[PATHBUF], *c;
572 int l;
573
574 strncpy(ndir, dt->txt_directory, PATHBUF);
575 l = strlen(ndir);
576 if (!strcmp(name, "..")) // Go to parent directory
577 {
578 if (l && (ndir[l - 1] == DIR_SEP)) ndir[--l] = '\0';
579 c = strrchr(ndir, DIR_SEP);
580 if (c) *c = '\0';
581 else /* Already in root directory */
582 {
583 #ifdef WIN32
584 fpick_scan_drives(dt);
585 #endif
586 return;
587 }
588 }
589 else strnncat(ndir, name, PATHBUF);
590 fpick_cleanse_path(ndir);
591 fpick_scan_directory(dt, ndir, NULL); // Enter new directory
592 }
593
fpick_ok(fpick_dd * dt)594 static void fpick_ok(fpick_dd *dt)
595 {
596 int *rp = dt->fcols + dt->idx * COL_MAX;
597 char *txt_name = RELREF(rp[COL_FILE]), *txt_size = RELREF(rp[COL_SIZE]);
598
599 /* Directory selected */
600 if (!txt_size[0]) fpick_enter_dir_via_list(dt, txt_name);
601 /* File selected */
602 else fpick_btn(dt, NULL, op_EVT_OK, NULL);
603 }
604
fpick_select(fpick_dd * dt,void ** wdata,int what,void ** where)605 static void fpick_select(fpick_dd *dt, void **wdata, int what, void **where)
606 {
607 int *rp = dt->fcols + dt->idx * COL_MAX;
608 char *txt_name = RELREF(rp[COL_FILE]), *txt_size = RELREF(rp[COL_SIZE]);
609
610 // File selected
611 if (txt_size[0]) cmd_setv(dt->entry, txt_name, PATH_VALUE);
612 }
613
614 /* Return 1 if changed directory, 0 if directory was the same, -1 if tried
615 * to change but failed */
fpick_enter_dirname(fpick_dd * dt,const char * name,int l)616 static int fpick_enter_dirname(fpick_dd *dt, const char *name, int l)
617 {
618 char txt[PATHBUF], *ctxt;
619 int res = 0;
620
621 if (name) name = g_strndup(name, l);
622 else
623 {
624 cmd_read(dt->combo, dt);
625 name = g_strdup(dt->cdir);
626 }
627 gtkncpy(txt, name, PATHBUF);
628
629 fpick_cleanse_path(txt); // Path might have been entered manually
630
631 if (strcmp(txt, dt->txt_directory) &&
632 // Only do something if the directory is new
633 ((res = fpick_scan_directory(dt, txt, NULL)) <= 0))
634 { // Directory doesn't exist so tell user
635 ctxt = g_strdup_printf(__("Could not access directory %s"), name);
636 alert_box(_("Error"), ctxt, NULL);
637 g_free(ctxt);
638 res = res < 0 ? 1 : -1;
639 }
640 g_free((char *)name);
641 return (res);
642 }
643
fpick_combo_changed(fpick_dd * dt,void ** wdata,int what,void ** where)644 static void fpick_combo_changed(fpick_dd *dt, void **wdata, int what, void **where)
645 {
646 fpick_enter_dirname(dt, NULL, 0);
647 }
648
649 typedef struct {
650 char *title, *what;
651 void **xw; // parent widget-map
652 void **cancel, **delete, **rename, **create;
653 void **res;
654 int dir;
655 char fname[PATHBUF];
656 } fdialog_dd;
657
658 #define WBbase fdialog_dd
659 static void *fdialog_code[] = {
660 ONTOP(xw), DIALOGpm(title),
661 BORDER(LABEL, 8),
662 WLABELp(what),
663 XPENTRY(fname, PATHBUF), FOCUS,
664 WDONE, // vbox
665 BORDER(BUTTON, 2),
666 REF(cancel), CANCELBTN(_("Cancel"), dialog_event),
667 UNLESSx(dir, 1),
668 REF(delete), BUTTON(_("Delete"), dialog_event),
669 REF(rename), OKBTN(_("Rename"), dialog_event),
670 ENDIF(1),
671 IFx(dir, 1),
672 REF(create), OKBTN(_("Create"), dialog_event),
673 ENDIF(1),
674 RAISED, WDIALOG(res)
675 };
676 #undef WBbase
677
fpick_file_dialog(fpick_dd * dt,void ** wdata,int what,void ** where,void * xdata)678 static void fpick_file_dialog(fpick_dd *dt, void **wdata, int what, void **where,
679 void *xdata)
680 {
681 fdialog_dd tdata, *ddt;
682 char fnm[PATHBUF], *tmp, *fname = NULL, *snm = NULL;
683 void **dd;
684 int uninit_(l), res, row = (int)xdata;
685
686
687 memset(&tdata, 0, sizeof(tdata));
688 tdata.xw = wdata;
689 if (row >= 0) /* Doing things to selected file */
690 {
691 fname = RELREF(dt->fcols[COL_FILE + dt->idx * COL_MAX]);
692 if (!strcmp(fname, "..")) return; // Up-dir
693 #ifdef WIN32
694 if (fname[1] == ':') return; // Drive
695 #endif
696
697 snprintf(tdata.title = fnm, sizeof(fnm),
698 "%s / %s", __("Delete"), __("Rename"));
699 tdata.what = _("Enter the new filename");
700 strncpy0(tdata.fname, fname, PATHBUF);
701 }
702 else
703 {
704 tdata.title = _("Create Directory");
705 tdata.what = _("Enter the name of the new directory");
706 tdata.dir = TRUE;
707 }
708 dd = run_create(fdialog_code, &tdata, sizeof(tdata)); // run dialog
709
710 /* Retrieve results */
711 run_query(dd);
712 ddt = GET_DDATA(dd);
713 where = origin_slot(ddt->res);
714 res = where == ddt->delete ? 2 : where == ddt->rename ? 3 :
715 where == ddt->create ? 4 : 1;
716
717 if (res > 1)
718 {
719 l = strlen(dt->txt_directory);
720 wjstrcat(fnm, PATHBUF, dt->txt_directory, l, ddt->fname, NULL);
721 // The source name SHOULD NOT get truncated, ever
722 if (fname) snm = g_strconcat(dt->txt_directory, fname, NULL);
723 }
724 run_destroy(dd);
725
726 tmp = NULL;
727 if (res == 2) // Delete file or directory
728 {
729 char *ts = g_strdup_printf(__("Do you really want to delete \"%s\" ?"),
730 RELREF(dt->fcols[COL_NAME + dt->idx * COL_MAX]) + 1);
731 int r = alert_box(_("Warning"), ts, _("No"), _("Yes"), NULL);
732 g_free(ts);
733 if (r == 2)
734 {
735 if (remove(snm)) tmp = _("Unable to delete");
736 }
737 else res = 1;
738 }
739 else if (res == 3) // Rename file or directory
740 {
741 if (rename(snm, fnm)) tmp = _("Unable to rename");
742 }
743 else if (res == 4) // Create directory
744 {
745 #ifdef WIN32
746 if (mkdir(fnm))
747 #else
748 if (mkdir(fnm, 0777))
749 #endif
750 tmp = _("Unable to create directory");
751 }
752 g_free(snm);
753
754 if (tmp) alert_box(_("Error"), tmp, NULL);
755 else if (res > 1)
756 {
757 if (row >= 0) /* Deleted/renamed a file - move down */
758 {
759 if (++row >= dt->cntx) row = dt->cntx - 2;
760 row = dt->fmap[row];
761 row = COL_FILE + row * COL_MAX;
762 // !!! "fcols" will get overwritten
763 strncpy0(tmp = fnm, RELREF(dt->fcols[row]), PATHBUF);
764 }
765 else tmp = fnm + l; /* Created a directory - move to it */
766
767 fpick_scan_directory(dt, dt->txt_directory, tmp);
768 }
769 }
770
fpick_wildcard(fpick_dd * dt,int button)771 static int fpick_wildcard(fpick_dd *dt, int button)
772 {
773 char ctxt[PATHTXT];
774 char *ds, *nm, *mask = dt->txt_mask;
775
776 cmd_peekv(dt->entry, ctxt, PATHTXT, PATH_RAW);
777 /* Presume filename if called by user pressing "OK", pattern otherwise */
778 if (button)
779 {
780 /* If user had changed directory in the combo */
781 if (fpick_enter_dirname(dt, NULL, 0)) return (FALSE);
782 /* If file entry is hidden anyway */
783 if (!dt->allow_files) return (TRUE);
784 /* Filename must have some chars and no wildcards in it */
785 if (ctxt[0] && !ctxt[strcspn(ctxt, "?*")]) return (TRUE);
786 }
787
788 /* Do we have directory in here? */
789 ds = strrchr(ctxt, DIR_SEP);
790 #ifdef WIN32 /* Allow '/' separator too */
791 if ((nm = strrchr(ds ? ds : ctxt, '/'))) ds = nm;
792 #endif
793
794 /* Store filename pattern */
795 nm = ds ? ds + 1 : ctxt;
796 strncpy0(mask, nm, PATHTXT - 1);
797 if (mask[0] && !strchr(mask, '*'))
798 {
799 /* Add a '*' at end if one isn't present in string */
800 int l = strlen(mask);
801 mask[l++] = '*';
802 mask[l] = '\0';
803 }
804
805 /* Have directory - enter it */
806 if (ds && (fpick_enter_dirname(dt, ctxt, ds + 1 - ctxt) > 0))
807 { // Opened a new dir - skip redisplay
808 cmd_setv(dt->entry, nm, PATH_RAW); // Cut dir off
809 }
810 else
811 { /* Redisplay only files that match pattern */
812 filter_dir(dt, mask);
813 cmd_reset(dt->list, dt);
814 }
815
816 /* Don't let pattern pass as filename */
817 return (FALSE);
818 }
819
820 /* "Tab completion" for entry field, like in GtkFileSelection */
fpick_entry_key(fpick_dd * dt,void ** wdata,int what,void ** where,key_ext * keydata)821 static int fpick_entry_key(fpick_dd *dt, void **wdata, int what, void **where,
822 key_ext *keydata)
823 {
824 if (keydata->key != KEY(Tab)) return (FALSE);
825 fpick_wildcard(dt, FALSE);
826 return (TRUE);
827 }
828
829 // !!! May get NULLs in "wdata" and "where" when called from other handlers
fpick_btn(fpick_dd * dt,void ** wdata,int what,void ** where)830 static void fpick_btn(fpick_dd *dt, void **wdata, int what, void **where)
831 {
832 if (what == op_EVT_CANCEL) do_evt_1_d(dt->cancel);
833 else if (fpick_wildcard(dt, TRUE)) do_evt_1_d(dt->ok);
834 }
835
set_fname(fpick_dd * dt,char * name,int raw)836 static void set_fname(fpick_dd *dt, char *name, int raw)
837 {
838 char txt[PATHTXT];
839
840 if (!raw)
841 {
842 /* Ensure that path is absolute */
843 resolve_path(txt, PATHBUF, name);
844 /* Separate the filename */
845 name = strrchr(txt, DIR_SEP);
846 *name++ = '\0';
847 // Scan directory, populate boxes if successful
848 if (!fpick_scan_directory(dt, txt, "")) return;
849 }
850 cmd_setv(dt->entry, name, raw ? PATH_RAW : PATH_VALUE);
851 }
852
853 /* Store things to inifile */
fpick_destroy(fpick_dd * dt,void ** wdata)854 static void fpick_destroy(fpick_dd *dt, void **wdata)
855 {
856 char txt[64], buf[PATHBUF];
857 int i;
858
859 /* Remember recently used directories */
860 for (i = 0; i < FPICK_COMBO_ITEMS; i++)
861 {
862 gtkncpy(buf, dt->cpp[i], PATHBUF);
863 sprintf(txt, "fpick_dir_%i", i);
864 inifile_set(txt, buf);
865 }
866
867 inifile_set_gboolean("fpick_case_insensitive", case_insensitive);
868 inifile_set_gboolean("fpick_show_hidden", dt->show_hidden );
869 }
870
fpick_iconbar_click(fpick_dd * dt,void ** wdata,int what,void ** where)871 static void fpick_iconbar_click(fpick_dd *dt, void **wdata, int what, void **where)
872 {
873 char fnm[PATHBUF];
874 int id = TOOL_ID(where);
875
876 cmd_read(where, dt);
877 switch (id)
878 {
879 case FPICK_ICON_UP:
880 fpick_enter_dir_via_list(dt, "..");
881 break;
882 case FPICK_ICON_HOME:
883 cmd_read(dt->entry, dt);
884 file_in_homedir(fnm, dt->fname, PATHBUF);
885 set_fname(dt, fnm, FALSE);
886 break;
887 case FPICK_ICON_DIR:
888 fpick_file_dialog(dt, wdata, 0, NULL, (void *)(-1));
889 break;
890 case FPICK_ICON_HIDDEN:
891 fpick_scan_directory(dt, dt->txt_directory, "");
892 break;
893 case FPICK_ICON_CASE:
894 cmd_setv(dt->list, (void *)dt->fsort, LISTC_SORT);
895 break;
896 }
897 }
898
fpick_get_filename(GtkWidget * fp,char * buf,int len,int raw)899 void fpick_get_filename(GtkWidget *fp, char *buf, int len, int raw)
900 {
901 fpick_dd *dt = GET_DDATA(get_wdata(fp, FP_KEY));
902 if (raw) cmd_peekv(dt->entry, buf, len, PATH_RAW);
903 else
904 {
905 cmd_read(dt->entry, dt);
906 snprintf(buf, len, "%s%s", dt->txt_directory, dt->fname);
907 }
908 }
909
fpick_set_filename(GtkWidget * fp,char * name,int raw)910 void fpick_set_filename(GtkWidget *fp, char *name, int raw)
911 {
912 set_fname(GET_DDATA(get_wdata(fp, FP_KEY)), name, raw);
913 }
914
915 #define WBbase fpick_dd
916 static void *fpick_code[] = {
917 IDENT(FP_KEY),
918 WPWHEREVER, WINDOWpm(title), EVENT(DESTROY, fpick_destroy),
919 HBOXP,
920 // ------- Combo Box -------
921 REF(combo), COMBOENTRY(cdir, cpp, fpick_combo_changed),
922 // ------- Toolbar -------
923 TOOLBAR(fpick_iconbar_click),
924 TBBUTTON(_("Up"), XPM_ICON(up), FPICK_ICON_UP),
925 TBBUTTON(_("Home"), XPM_ICON(home), FPICK_ICON_HOME),
926 TBBUTTON(_("Create New Directory"), XPM_ICON(newdir), FPICK_ICON_DIR),
927 TBTOGGLE(_("Show Hidden Files"), XPM_ICON(hidden), FPICK_ICON_HIDDEN,
928 show_hidden),
929 TBTOGGLEv(_("Case Insensitive Sort"), XPM_ICON(case), FPICK_ICON_CASE,
930 case_insensitive),
931 WDONE, WDONE,
932 // ------- File List -------
933 XHBOXP,
934 XSCROLL(1, 2), // auto/always
935 WLIST,
936 NRFILECOLUMNDax(_("Name"), COL_NAME, 250, 0, "fpick_col1"),
937 NRTXTCOLUMNDaxx(_("Size"), COL_SIZE, 64, 2, "fpick_col2", "8,888,888,888"),
938 NRTXTCOLUMNDax(_("Type"), COL_TYPE, 80, 2, "fpick_col3"),
939 NRTXTCOLUMNDax(_("Modified"), COL_TIME, 150, 1, "fpick_col4"),
940 COLUMNDATA(fcols, COL_MAX * sizeof(int)),
941 REF(list), LISTCX(idx, cntx, fsort, fmap, fpick_select, fpick_file_dialog),
942 EVENT(OK, fpick_ok), EVENT(CHANGE, fpick_sort),
943 UNLESS(entry_f), FOCUS,
944 CLEANUP(files.buf),
945 WDONE,
946 // ------- Extra widget section -------
947 REF(hbox), HBOXPr, WDONE,
948 // ------- Entry Box -------
949 HBOXP,
950 REF(entry), XPENTRY(fname, PATHBUF),
951 EVENT(KEY, fpick_entry_key), EVENT(OK, fpick_btn),
952 UNLESS(allow_files), HIDDEN, IF(entry_f), FOCUS,
953 WDONE,
954 // ------- Buttons -------
955 HBOX,
956 MINWIDTH(110), EBUTTON(_("OK"), fpick_btn),
957 MINWIDTH(110), ECANCELBTN(_("Cancel"), fpick_btn),
958 WEND
959 };
960 #undef WBbase
961
fpick(GtkWidget ** box,char * title,int flags,void ** r)962 GtkWidget *fpick(GtkWidget **box, char *title, int flags, void **r)
963 {
964 fpick_dd tdata, *dt;
965 void **res;
966 int i;
967
968 memset(&tdata, 0, sizeof(tdata));
969 tdata.title = title;
970 tdata.flags = flags;
971 tdata.ok = NEXT_SLOT(r);
972 tdata.cancel = SLOT_N(r, 2);
973
974 tdata.entry_f = tdata.flags & FPICK_ENTRY;
975
976 tdata.fsort = 1; // By name column, ascending
977
978 case_insensitive = inifile_get_gboolean("fpick_case_insensitive", TRUE );
979
980 tdata.show_hidden = inifile_get_gboolean("fpick_show_hidden", FALSE );
981 tdata.allow_files = !(tdata.flags & FPICK_DIRS_ONLY);
982 tdata.allow_dirs = TRUE;
983
984 // Pointers can't yet be properly prepared, so the combo is created empty
985 tdata.cdir = "";
986 tdata.cpp = tdata.cp;
987
988 res = run_create(fpick_code, &tdata, sizeof(tdata));
989 dt = GET_DDATA(res);
990
991 for (i = 0; i < FPICK_COMBO_ITEMS; i++)
992 {
993 char txt[64];
994 sprintf(txt, "fpick_dir_%i", i);
995 gtkuncpy(dt->cp[i] = dt->combo_items[i],
996 inifile_get(txt, ""), PATHTXT);
997 }
998 dt->cpp = dt->cp;
999 cmd_reset(dt->combo, dt);
1000
1001 *box = dt->hbox[0];
1002 return (GET_REAL_WINDOW(res));
1003 }
1004
1005 #endif /* mtPaint fpicker */
1006
1007
1008
1009
1010 #ifdef U_FPICK_GTKFILESEL /* GtkFileSelection based dialog */
1011
fpick(GtkWidget ** box,char * title,int flags,void ** r)1012 GtkWidget *fpick(GtkWidget **box, char *title, int flags, void **r)
1013 {
1014 #if GTK_MAJOR_VERSION == 1
1015 GtkAccelGroup* ag = gtk_accel_group_new();
1016 #endif
1017 GtkWidget *fp;
1018 GtkFileSelection *fs;
1019
1020 fp = gtk_file_selection_new(__(title));
1021 fs = GTK_FILE_SELECTION(fp);
1022 if ( flags & FPICK_DIRS_ONLY )
1023 {
1024 gtk_widget_hide(GTK_WIDGET(fs->selection_entry));
1025 gtk_widget_set_sensitive(GTK_WIDGET(fs->file_list),
1026 FALSE); // Don't let the user select files
1027 }
1028
1029 gtk_signal_connect_object(GTK_OBJECT(fs->ok_button), "clicked",
1030 GTK_SIGNAL_FUNC(do_evt_1_d), (gpointer)NEXT_SLOT(r));
1031 gtk_signal_connect_object(GTK_OBJECT(fs->cancel_button), "clicked",
1032 GTK_SIGNAL_FUNC(do_evt_1_d), (gpointer)SLOT_N(r, 2));
1033
1034 *box = pack(fs->main_vbox, gtk_hbox_new(FALSE, 0));
1035 gtk_widget_show(*box);
1036
1037 #if GTK_MAJOR_VERSION == 1 /* No builtin accelerators - add our own */
1038 gtk_widget_add_accelerator(fs->cancel_button,
1039 "clicked", ag, KEY(Escape), 0, (GtkAccelFlags)0);
1040 gtk_window_add_accel_group(GTK_WINDOW(fp), ag);
1041 #endif
1042 return (fp);
1043 }
1044
fpick_get_filename(GtkWidget * fp,char * buf,int len,int raw)1045 void fpick_get_filename(GtkWidget *fp, char *buf, int len, int raw)
1046 {
1047 char *fname = (char *)gtk_entry_get_text(GTK_ENTRY(
1048 GTK_FILE_SELECTION(fp)->selection_entry));
1049 if (raw) strncpy0(buf, fname, len);
1050 else
1051 {
1052 #ifdef WIN32 /* Widget returns filename in UTF8 */
1053 gtkncpy(buf, gtk_file_selection_get_filename(GTK_FILE_SELECTION(fp)), len);
1054 #else
1055 strncpy0(buf, gtk_file_selection_get_filename(GTK_FILE_SELECTION(fp)), len);
1056 #endif
1057 /* Make sure directory paths end with DIR_SEP */
1058 if (fname[0]) return;
1059 raw = strlen(buf);
1060 if (!raw || (buf[raw - 1] != DIR_SEP))
1061 {
1062 if (raw > len - 2) raw = len - 2;
1063 buf[raw] = DIR_SEP;
1064 buf[raw + 1] = '\0';
1065 }
1066 }
1067 }
1068
fpick_set_filename(GtkWidget * fp,char * name,int raw)1069 void fpick_set_filename(GtkWidget *fp, char *name, int raw)
1070 {
1071 if (raw) gtk_entry_set_text(GTK_ENTRY(
1072 GTK_FILE_SELECTION(fp)->selection_entry), name);
1073 #ifdef WIN32 /* Widget wants filename in UTF8 */
1074 else
1075 {
1076 name = gtkuncpy(NULL, name, 0);
1077 gtk_file_selection_set_filename(GTK_FILE_SELECTION(fp), name);
1078 g_free(name);
1079 }
1080 #else
1081 else gtk_file_selection_set_filename(GTK_FILE_SELECTION(fp), name);
1082 #endif
1083 }
1084
1085 #endif /* GtkFileSelection based dialog */
1086