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