source: tags/ms_r16q2/WINDOW/AW_file.cxx

Last change on this file was 14504, checked in by westram, 8 years ago
  • allow to select symbolic links (to files!) in ARB filebrowser
    • needed to load named-quicksaves ("quick save as")
    • was broken by using GB_canonical_path to fix filenames
  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 32.2 KB
Line 
1// ================================================================ //
2//                                                                  //
3//   File      : AW_file.cxx                                        //
4//   Purpose   :                                                    //
5//                                                                  //
6//   Institute of Microbiology (Technical University Munich)        //
7//   http://www.arb-home.de/                                        //
8//                                                                  //
9// ================================================================ //
10
11#include "aw_file.hxx"
12#include "aw_awar.hxx"
13#include "aw_root.hxx"
14#include "aw_select.hxx"
15#include "aw_msg.hxx"
16#include "aw_question.hxx"
17
18#include <arbdbt.h>
19#include <arb_file.h>
20#include <arb_strbuf.h>
21#include <arb_misc.h>
22#include <arb_str.h>
23#include <arb_strarray.h>
24
25#include <sys/stat.h>
26#include <dirent.h>
27#include <set>
28#include <string>
29
30using std::set;
31using std::string;
32
33#if defined(DEBUG)
34// #define TRACE_FILEBOX
35#endif // DEBUG
36
37static GB_CSTR expand_symbolic_directories(const char *pwd_envar) { 
38    GB_CSTR res;
39
40    if (strcmp(pwd_envar, "PWD") == 0) {
41        res = GB_getcwd();
42    }
43    else {
44        res = NULL;
45    }
46
47    return res;
48}
49
50
51char *AW_unfold_path(const char *pwd_envar, const char *path) {
52    //! create a full path
53    gb_getenv_hook  oldHook = GB_install_getenv_hook(expand_symbolic_directories);
54    char           *result  = nulldup(GB_unfold_path(pwd_envar, path));
55    GB_install_getenv_hook(oldHook);
56    return result;
57}
58
59char *AW_extract_directory(const char *path) {
60    const char *lslash = strrchr(path, '/');
61    if (!lslash) return 0;
62
63    char *result        = strdup(path);
64    result[lslash-path] = 0;
65
66    return result;
67}
68
69// -----------------------------
70//      file selection boxes
71
72void AW_create_fileselection_awars(AW_root *awr, const char *awar_base, const char *directories, const char *filter, const char *file_name) {
73    int        base_len     = strlen(awar_base);
74    bool       has_slash    = awar_base[base_len-1] == '/';
75    char      *awar_name    = new char[base_len+30]; // use private buffer, because caller will most likely use GBS_global_string for arguments
76    AW_default default_file = AW_ROOT_DEFAULT;
77
78    sprintf(awar_name, "%s%s", awar_base, "/directory"+int(has_slash));
79    AW_awar *awar_dir = awr->awar_string(awar_name, directories, default_file);
80
81    sprintf(awar_name, "%s%s", awar_base, "/filter"   + int(has_slash));
82    AW_awar *awar_filter = awr->awar_string(awar_name, filter, default_file);
83
84    sprintf(awar_name, "%s%s", awar_base, "/file_name"+int(has_slash));
85    AW_awar *awar_filename = awr->awar_string(awar_name, file_name, default_file);
86
87#if defined(ASSERTION_USED)
88    bool is_tmp_awar = strncmp(awar_base, "tmp/", 4) == 0 || strncmp(awar_base, "/tmp/", 5) == 0;
89    aw_assert(is_tmp_awar); // you need to use a temp awar for file selections
90#endif
91
92    awar_dir->write_string(directories);
93    awar_filter->write_string(filter);
94    awar_filename->write_string(file_name);
95
96    // create all (default) directories
97    {
98        ConstStrArray dirs;
99        GBT_split_string(dirs, directories, ":", true);
100        for (unsigned i = 0; i<dirs.size(); ++i) {
101            if (!GB_is_directory(dirs[i])) {
102                fprintf(stderr, "Creating directory '%s'\n", dirs[i]);
103                GB_ERROR error = GB_create_directory(dirs[i]);
104                if (error) aw_message(GBS_global_string("Failed to create directory '%s' (Reason: %s)", dirs[i], error));
105            }
106        }
107    }
108
109    delete [] awar_name;
110}
111
112enum DirSortOrder {
113    SORT_ALPHA,
114    SORT_DATE,
115    SORT_SIZE,
116
117    DIR_SORT_ORDERS // order count
118};
119
120class LimitedTime {
121    double       max_duration;
122    time_t       start;
123    mutable bool aborted;
124
125public:
126    LimitedTime(double max_duration_seconds) : max_duration(max_duration_seconds) { reset(); }
127    void reset() {
128        time(&start);
129        aborted = false;
130    }
131    double allowed_duration() const { return max_duration; }
132    bool finished_in_time() const { return !aborted; }
133    bool available() const {
134        if (!aborted) {
135            time_t now;
136            time(&now);
137            aborted = difftime(now, start) > max_duration;
138        }
139        return !aborted;
140    }
141    void increase() { max_duration *= 2.5; }
142};
143
144class File_selection { // @@@ derive from AW_selection?
145    AW_root *awr;
146
147    AW_selection_list *filelist;
148
149    char *def_name;
150    char *def_dir;
151    char *def_filter;
152
153    char *pwd;
154    char *pwdx;                                     // additional directories
155
156    DirDisplay dirdisp;
157
158    bool leave_wildcards;
159    bool filled_by_wildcard; // last fill done with wildcard?
160
161    bool show_subdirs;  // show or hide subdirs
162    bool show_hidden;   // show or hide files/directories starting with '.'
163
164    DirSortOrder sort_order;
165
166    LimitedTime searchTime;
167
168    int shown_name_len;
169
170    void bind_callbacks();
171    void execute_browser_command(const char *browser_command);
172    void fill_recursive(const char *fulldir, int skipleft, const char *mask, bool recurse, bool showdir);
173
174    void format_columns();
175
176public:
177
178    File_selection(AW_root *aw_root, const char *awar_prefix, const char *pwd_, DirDisplay disp_dirs, bool allow_wildcards)
179        : awr(aw_root),
180          filelist(NULL),
181          pwd(strdup(pwd_)),
182          pwdx(NULL),
183          dirdisp(disp_dirs),
184          leave_wildcards(allow_wildcards),
185          filled_by_wildcard(false),
186          show_subdirs(true),
187          show_hidden(false),
188          sort_order(SORT_ALPHA),
189          searchTime(1.3)
190    {
191        {
192            char *multiple_dirs_in_pwd = strchr(pwd, '^');
193            if (multiple_dirs_in_pwd) {
194                multiple_dirs_in_pwd[0] = 0;
195                pwdx = multiple_dirs_in_pwd+1;
196            }
197        }
198
199        def_name   = GBS_string_eval(awar_prefix, "*=*/file_name", 0);
200        def_dir    = GBS_string_eval(awar_prefix, "*=*/directory", 0);
201        def_filter = GBS_string_eval(awar_prefix, "*=*/filter", 0);
202
203        aw_assert(!GB_have_error());
204
205        bind_callbacks();
206    }
207
208    void create_gui_elements(AW_window *aws, const char *at_prefix) {
209        aw_assert(!filelist);
210
211        char buffer[1024];
212        sprintf(buffer, "%sfilter", at_prefix);
213        if (aws->at_ifdef(buffer)) {
214            aws->at(buffer);
215            aws->create_input_field(def_filter, 5);
216        }
217
218        sprintf(buffer, "%sfile_name", at_prefix);
219        if (aws->at_ifdef(buffer)) {
220            aws->at(buffer);
221            aws->create_input_field(def_name, 20);
222        }
223
224        sprintf(buffer, "%sbox", at_prefix);
225        aws->at(buffer);
226        filelist = aws->create_selection_list(def_name, false);
227    }
228
229    void fill();
230
231    void filename_changed(bool post_filter_change_HACK);
232
233    GB_ULONG get_newest_dir_modtime() const {
234        ConstStrArray dirs;
235        GBT_split_string(dirs, awr->awar(def_dir)->read_char_pntr(), ":", true);
236        unsigned long maxtof = 0;
237        for (unsigned i = 0; i<dirs.size(); ++i) {
238            unsigned long tof = GB_time_of_file(dirs[i]);
239            if (tof>maxtof) maxtof = tof;
240        }
241        return maxtof;
242    }
243
244    void trigger_refresh() { awr->awar(def_dir)->touch(); }
245};
246
247static GB_CSTR get_suffix(GB_CSTR fullpath) { // returns pointer behind '.' of suffix (or NULL if no suffix found)
248    GB_CSTR dot = strrchr(fullpath, '.');
249    if (!dot) return 0;
250
251    GB_CSTR lslash = strrchr(fullpath, '/');
252    if (lslash && lslash>dot) return 0; // no . behind last /
253    return dot+1;
254}
255
256
257static char *set_suffix(const char *name, const char *suffix) {
258    // returns "name.suffix" (name may contain path information)
259    // - eliminates multiple dots
260    // - sets name to 'noname' if no name part is given
261
262    char *path, *fullname;
263    GB_split_full_path(name, &path, &fullname, NULL, NULL);
264
265    // remove dots and spaces from suffix:
266    while (suffix[0] == '.' || suffix[0] == ' ') ++suffix;
267    if (!suffix[0]) suffix = 0;
268
269    GBS_strstruct *out = GBS_stropen(FILENAME_MAX+1);
270    if (path) {
271        GBS_strcat(out, path);
272        GBS_chrcat(out, '/');
273    }
274
275    if (fullname) GBS_strcat(out, fullname);
276
277    if (GB_is_directory(GBS_mempntr(out))) {
278        // if 'out' contains a directory now, 'name' was lacking a filename
279        // (in this case it was only a directory)
280        GBS_strcat(out, "/noname"); // invent a name
281    }
282
283    if (suffix) {
284        GBS_chrcat(out, '.');
285        GBS_strcat(out, suffix);
286    }
287
288    free(path);
289    free(fullname);
290
291    return GBS_strclose(out);
292}
293
294
295inline const char *valid_path(const char *path) { return path[0] ? path : "."; }
296
297inline bool AW_is_dir(const char *path) { return GB_is_directory(valid_path(path)); }
298inline bool AW_is_file(const char *path) { return GB_is_regularfile(valid_path(path)); }
299inline bool AW_is_link(const char *path) { return GB_is_link(valid_path(path)); }
300
301void File_selection::execute_browser_command(const char *browser_command) {
302    if (strcmp(browser_command, "sort") == 0) {
303        sort_order = DirSortOrder((sort_order+1)%DIR_SORT_ORDERS);
304    }
305    else if (strcmp(browser_command, "hide") == 0) {
306        show_subdirs = false;
307    }
308    else if (strcmp(browser_command, "show") == 0) {
309        show_subdirs = true;
310    }
311    else if (strcmp(browser_command, "dot") == 0) {
312        show_hidden = !show_hidden;
313    }
314    else if (strcmp(browser_command, "inctime") == 0) {
315        searchTime.increase();
316    }
317    else {
318        aw_message(GBS_global_string("Unknown browser command '%s'", browser_command));
319    }
320}
321
322inline int entryType(const char *entry) {
323    const char *typechar = "DFL";
324    for (int i = 0; typechar[i]; ++i) {
325        if (entry[0] == typechar[i]) return i;
326    }
327    return -1;
328}
329
330void File_selection::format_columns() {
331    const int FORMATTED_TYPES = 3;
332
333    int maxlen[FORMATTED_TYPES] = { 17, 17, 17 };
334
335    for (int pass = 1; pass<=2; ++pass) {
336        AW_selection_list_iterator entry(filelist);
337        while (entry) {
338            const char *disp = entry.get_displayed();
339            int         type = entryType(disp);
340
341            if (type>=0) {
342                const char *q1 = strchr(disp, '?');
343                if (q1) {
344                    const char *q2 = strchr(q1+1, '?');
345                    if (q2) {
346                        int len = q2-q1-1;
347                        if (pass == 1) {
348                            if (maxlen[type]<len) maxlen[type] = len;
349                        }
350                        else {
351                            GBS_strstruct buf(200);
352                            buf.ncat(disp, q1-disp);
353                            buf.ncat(q1+1, len);
354                            buf.nput(' ', maxlen[type]-len);
355                            buf.cat(q2+1);
356                            entry.set_displayed(buf.get_data());
357                        }
358                    }
359                }
360            }
361            ++entry;
362        }
363    }
364}
365
366void File_selection::fill_recursive(const char *fulldir, int skipleft, const char *mask, bool recurse, bool showdir) {
367    DIR *dirp = opendir(fulldir);
368
369#if defined(TRACE_FILEBOX)
370    printf("fill_fileselection_recursive for directory '%s'\n", fulldir);
371#endif // TRACE_FILEBOX
372
373    if (!dirp) {
374        filelist->insert(GBS_global_string("x Your directory path is invalid (%s)", fulldir), "?");
375        return;
376    }
377
378    struct dirent *dp;
379    for (dp = readdir(dirp); dp != NULL; dp = readdir(dirp)) {
380        const char *entry       = dp->d_name;
381        char       *nontruepath = GBS_global_string_copy("%s/%s", fulldir, entry);
382        char       *fullname;
383
384        if (strlen(fulldir)) fullname = strdup(GB_concat_full_path(fulldir, entry));
385        else fullname                 = strdup(GB_canonical_path(entry));
386
387        if (AW_is_dir(fullname)) {
388            if (!(entry[0] == '.' && (!show_hidden || entry[1] == 0 || (entry[1] == '.' && entry[2] == 0)))) { // skip "." and ".." and dotdirs if requested
389                if (showdir) {
390                    filelist->insert(GBS_global_string("D ?%s? (%s)", entry, fullname), fullname); // '?' used in format_columns()
391                }
392                if (recurse && !AW_is_link(nontruepath)) { // don't follow links
393                    if (searchTime.available()) {
394                        fill_recursive(nontruepath, skipleft, mask, recurse, showdir);
395                    }
396                }
397            }
398        }
399        else {
400            if (GBS_string_matches(entry, mask, GB_IGNORE_CASE)) { // entry matches mask
401                if ((entry[0] != '.' || show_hidden) && AW_is_file(fullname)) { // regular existing file
402                    struct stat stt;
403
404                    stat(fullname, &stt);
405
406                    char       atime[256];
407                    struct tm *tms = localtime(&stt.st_mtime);
408                    strftime(atime, 255, "%Y/%m/%d %k:%M", tms);
409
410                    char *size     = strdup(GBS_readable_size(stt.st_size, "b"));
411                    char  typechar = AW_is_link(nontruepath) ? 'L' : 'F';
412
413                    const char *sel_entry = 0;
414                    switch (sort_order) {
415                        case SORT_ALPHA:
416                            sel_entry = GBS_global_string("%c ?%s?  %7s  %s", typechar, nontruepath+skipleft, size, atime); // '?' used in format_columns()
417                            break;
418                        case SORT_DATE:
419                            sel_entry = GBS_global_string("%c %s  %7s  %s", typechar, atime, size, nontruepath+skipleft);
420                            break;
421                        case SORT_SIZE:
422                            sel_entry = GBS_global_string("%c %7s  %s  %s", typechar, size, atime, nontruepath+skipleft);
423                            break;
424                        case DIR_SORT_ORDERS: break;
425                    }
426
427                    filelist->insert(sel_entry, nontruepath);
428                    free(size);
429                }
430            }
431        }
432        free(fullname);
433        free(nontruepath);
434    }
435
436    closedir(dirp);
437}
438
439class DuplicateLinkFilter {
440    set<string> insertedDirectories;
441
442public:
443    DuplicateLinkFilter() {}
444
445    bool not_seen_yet(const string& dir) const { return insertedDirectories.find(dir) == insertedDirectories.end(); }
446    void register_directory(const string& dir) {
447        insertedDirectories.insert(dir);
448    }
449};
450
451
452static void show_soft_link(AW_selection_list *filelist, const char *envar, DuplicateLinkFilter& unDup) {
453    // adds a soft link (e.g. ARBMACROHOME or ARB_WORKDIR) into file selection box
454    // if content of 'envar' matches 'cwd' nothing is inserted
455
456    const char *expanded_dir        = expand_symbolic_directories(envar);
457    if (!expanded_dir) expanded_dir = GB_getenv(envar);
458
459    if (expanded_dir) {
460        string edir(expanded_dir);
461
462        if (unDup.not_seen_yet(edir)) {
463            unDup.register_directory(edir);
464            const char *entry = GBS_global_string("$ %-18s(%s)", GBS_global_string("'%s'", envar), expanded_dir);
465            filelist->insert(entry, expanded_dir);
466        }
467    }
468}
469
470inline bool fileOrLink(const char *d) { return d[0] == 'F' || d[0] == 'L'; }
471inline const char *gotounit(const char *d) {
472    ++d;
473    while (d[0] == ' ') ++d;
474    while (d[0] != ' ') ++d;
475    while (d[0] == ' ') ++d;
476    return d;
477}
478static int cmpBySize(const char *disp1, const char *disp2) {
479    if (fileOrLink(disp1) && fileOrLink(disp2)) {
480        const char *u1 = gotounit(disp1);
481        const char *u2 = gotounit(disp2);
482
483        if (u1[0] != u2[0]) { // diff units
484            static const char *units = "bkMGTPEZY"; // see also ../CORE/arb_misc.cxx@Tera
485
486            const char *p1 = strchr(units, u1[0]);
487            const char *p2 = strchr(units, u2[0]);
488            if (p1 != p2) {
489                return p1-p2;
490            }
491        }
492    }
493    return ARB_stricmp(disp1, disp2);
494}
495
496void File_selection::fill() {
497    AW_root *aw_root = awr;
498    filelist->clear();
499
500    char *filter  = aw_root->awar(def_filter)->read_string();
501    char *name    = aw_root->awar(def_name)->read_string();
502
503    const char *name_only = 0;
504    {
505        char *slash = strrchr(name, '/');
506        name_only   = slash ? slash+1 : name;
507    }
508
509    StrArray dirs;
510    {
511        char *diru = aw_root->awar(def_dir)->read_string();
512        if (dirdisp == MULTI_DIRS) {
513            ConstStrArray cdirs;
514            GBT_split_string(cdirs, diru, ":", true);
515            for (unsigned i = 0; i<cdirs.size(); ++i) dirs.put(strdup(cdirs[i]));
516        }
517        else {
518            if (name[0] == '/' && AW_is_dir(name)) {
519                dirs.put(strdup(name));
520                name_only = "";
521            }
522            else {
523                char *fulldir = AW_unfold_path(pwd, diru);
524                dirs.put(fulldir);
525            }
526        }
527        free(diru);
528    }
529
530    filled_by_wildcard = strchr(name_only, '*');
531
532    if (dirdisp == ANY_DIR) {
533        aw_assert(dirs.size() == 1);
534        const char *fulldir    = dirs[0];
535
536        DuplicateLinkFilter unDup;
537        unDup.register_directory(fulldir);
538
539        if (filled_by_wildcard) {
540            if (leave_wildcards) {
541                filelist->insert(GBS_global_string("  ALL '%s' in '%s'", name_only, fulldir), name);
542            }
543            else {
544                filelist->insert(GBS_global_string("  ALL '%s' in+below '%s'", name_only, fulldir), name);
545            }
546        }
547        else {
548            filelist->insert(GBS_global_string("  CONTENTS OF '%s'", fulldir), fulldir);
549            if (filter[0]) {
550                filelist->insert(GBS_global_string("!  Find all         (*%s)", filter), "*");
551            }
552        }
553
554        if (strcmp("/", fulldir)) {
555            filelist->insert("! \'PARENT DIR\'      (..)", "..");
556        }
557        if (show_subdirs) {
558            show_soft_link(filelist, pwd, unDup);
559
560            if (pwdx) {        // additional directories
561                char *start = pwdx;
562                while (start) {
563                    char *multiple = strchr(start, '^');
564                    if (multiple) {
565                        multiple[0] = 0;
566                        show_soft_link(filelist, start, unDup);
567                        multiple[0] = '^';
568                        start       = multiple+1;
569                    }
570                    else {
571                        show_soft_link(filelist, start, unDup);
572                        start = 0;
573                    }
574                }
575            }
576
577            show_soft_link(filelist, "HOME", unDup);
578            show_soft_link(filelist, "PWD", unDup);
579            show_soft_link(filelist, "ARB_WORKDIR", unDup);
580
581            filelist->insert("!  Sub-directories  (shown)", GBS_global_string("%s?hide?", name));
582        }
583        else {
584            filelist->insert("!  Sub-directories  (hidden)", GBS_global_string("%s?show?", name));
585        }
586        filelist->insert(GBS_global_string("!  Hidden           (%s)", show_hidden ? "shown" : "not shown"), GBS_global_string("%s?dot?", name));
587    }
588    else {
589        aw_assert(dirdisp == MULTI_DIRS);
590    }
591
592    static const char *order_name[DIR_SORT_ORDERS] = { "alpha", "date", "size" };
593
594    filelist->insert(GBS_global_string("!  Sort order       (%s)", order_name[sort_order]), GBS_global_string("%s?sort?", name));
595
596    bool insert_dirs = dirdisp == ANY_DIR && show_subdirs;
597
598    searchTime.reset(); // limits time spent in fill_recursive
599    for (unsigned i = 0; i<dirs.size(); ++i) {
600        const char *fulldir = dirs[i];
601        if (filled_by_wildcard) {
602            if (leave_wildcards) {
603                fill_recursive(fulldir, strlen(fulldir)+1, name_only, false, insert_dirs);
604            }
605            else {
606                if (dirdisp == ANY_DIR) { // recursive wildcarded search
607                    fill_recursive(fulldir, strlen(fulldir)+1, name_only, true, false);
608                }
609                else {
610                    char *mask = GBS_global_string_copy("%s*%s", name_only, filter);
611                    fill_recursive(fulldir, strlen(fulldir)+1, mask, false, false);
612                    free(mask);
613                }
614            }
615        }
616        else {
617            char *mask = GBS_global_string_copy("*%s", filter);
618
619            fill_recursive(fulldir, strlen(fulldir)+1, mask, false, insert_dirs);
620            free(mask);
621        }
622    }
623
624    if (!searchTime.finished_in_time()) {
625        filelist->insert(GBS_global_string("!  Find aborted    (after %.1fs; click to search longer)", searchTime.allowed_duration()), GBS_global_string("%s?inctime?", name));
626    }
627
628    if (sort_order == SORT_SIZE) {
629        filelist->sortCustom(cmpBySize);
630    }
631    else {
632        filelist->sort(false, false);
633    }
634    format_columns();
635    filelist->insert_default("", "");
636    filelist->update();
637
638    if (filled_by_wildcard && !leave_wildcards) { // avoid returning wildcarded filename (if !leave_wildcards)
639        aw_root->awar(def_name)->write_string("");
640    }
641
642    free(name);
643    free(filter);
644}
645
646void File_selection::filename_changed(bool post_filter_change_HACK) {
647    AW_root *aw_root = awr;
648    char    *fname   = aw_root->awar(def_name)->read_string();
649
650#if defined(TRACE_FILEBOX)
651    printf("fileselection_filename_changed_cb:\n"
652           "- fname   ='%s'\n", fname);
653#endif // TRACE_FILEBOX
654
655    if (fname[0]) {
656        char *browser_command = 0;
657        {
658            // internal browser commands (e.g. '?sort?') are simply appended to the filename
659
660            char *lquestion = strrchr(fname, '?');
661            if (lquestion) {
662                lquestion[0] = 0; // remove last '?' + everything behind
663                lquestion    = strrchr(fname, '?');
664                if (lquestion) {
665                    browser_command = lquestion+1;
666                    lquestion[0]    = 0; // completely remove the browser command
667                }
668            }
669        }
670        if (browser_command) {
671            aw_root->awar(def_name)->write_string(fname); // re-write w/o browser_command
672            execute_browser_command(browser_command);
673            trigger_refresh();
674        }
675        else if (dirdisp != MULTI_DIRS) {
676            char *newName = 0;
677            char *dir     = aw_root->awar(def_dir)->read_string();
678
679#if defined(TRACE_FILEBOX)
680            printf("- dir     ='%s'\n", dir);
681#endif // TRACE_FILEBOX
682
683            if (fname[0] == '/' || fname[0] == '~') {
684                newName = strdup(GB_canonical_path(fname));
685            }
686            else {
687                if (dir[0]) {
688                    if (dir[0] == '/') {
689                        newName = strdup(GB_concat_full_path(dir, fname));
690                    }
691                    else {
692                        char *fulldir = 0;
693
694                        if (dir[0] == '.') fulldir = AW_unfold_path(pwd, dir);
695                        else fulldir               = strdup(dir);
696
697                        newName = strdup(GB_concat_full_path(fulldir, fname));
698                        free(fulldir);
699                    }
700                }
701                else {
702                    newName = AW_unfold_path(pwd, fname);
703                }
704            }
705
706            // Allow to select symbolic links for files (i.e. do not automatically switch to link-target;
707            // doing so made it impossible to load quicksaves done versus a master DB).
708            if (newName && strcmp(fname, newName) != 0) {
709                if (!GB_is_directory(fname) && !GB_is_directory(newName)) {
710                    if (GB_is_link(fname) ) freenull(newName); // do not follow symlink!
711                }
712            }
713
714            if (newName) {
715#if defined(TRACE_FILEBOX)
716                printf("- newName ='%s'\n", newName);
717#endif // TRACE_FILEBOX
718
719                if (AW_is_dir(newName)) {
720                    aw_root->awar(def_name)->write_string("");
721                    aw_assert(dirdisp != MULTI_DIRS); // overwriting content unwanted if displaying MULTI_DIRS
722                    aw_root->awar(def_dir)->write_string(newName);
723                    aw_root->awar(def_name)->write_string("");
724                }
725                else {
726                    char *lslash = strrchr(newName, '/');
727                    if (lslash) {
728                        if (lslash == newName) { // root directory
729                            aw_assert(dirdisp != MULTI_DIRS); // overwriting content unwanted if displaying MULTI_DIRS
730                            aw_root->awar(def_dir)->write_string("/"); // write directory part
731                        }
732                        else {
733                            lslash[0] = 0;
734                            aw_assert(dirdisp != MULTI_DIRS); // overwriting content unwanted if displaying MULTI_DIRS
735                            aw_root->awar(def_dir)->write_string(newName); // write directory part
736                            lslash[0] = '/';
737                        }
738                    }
739
740                    // now check the correct suffix :
741                    {
742                        char *filter = aw_root->awar(def_filter)->read_string();
743                        if (filter[0]) {
744                            char *pfilter = strrchr(filter, '.');
745                            pfilter       = pfilter ? pfilter+1 : filter;
746
747                            char *suffix = (char*)get_suffix(newName); // cast ok, since get_suffix points into newName
748
749                            if (!suffix || strcmp(suffix, pfilter) != 0) {
750                                if (suffix && post_filter_change_HACK) {
751                                    if (suffix[-1] == '.') suffix[-1] = 0;
752                                }
753                                freeset(newName, set_suffix(newName, pfilter));
754                            }
755                        }
756                        free(filter);
757                    }
758
759                    if (strcmp(newName, fname) != 0) {
760                        aw_root->awar(def_name)->write_string(newName); // loops back if changed !!!
761                    }
762                }
763            }
764            free(dir);
765
766            if (strchr(fname, '*')) { // wildcard -> search for suffix
767                trigger_refresh();
768            }
769
770            free(newName);
771        }
772    }
773
774    free(fname);
775}
776
777static bool avoid_multi_refresh = false;
778
779static void fill_fileselection_cb(AW_root*, File_selection *cbs) {
780    if (!avoid_multi_refresh) {
781        LocallyModify<bool> flag(avoid_multi_refresh, true);
782        cbs->fill();
783    }
784}
785static void fileselection_filename_changed_cb(AW_root*, File_selection *cbs) {
786    if (!avoid_multi_refresh) {
787        LocallyModify<bool> flag(avoid_multi_refresh, true);
788        cbs->filename_changed(false);
789        cbs->fill();
790    }
791    else {
792        cbs->filename_changed(false);
793    }
794}
795static void fileselection_filter_changed_cb(AW_root*, File_selection *cbs) {
796    if (!avoid_multi_refresh) {
797        LocallyModify<bool> flag(avoid_multi_refresh, true);
798        cbs->filename_changed(true);
799        cbs->fill();
800    }
801    else {
802        cbs->filename_changed(true);
803    }
804}
805
806void File_selection::bind_callbacks() {
807    awr->awar(def_name)  ->add_callback(makeRootCallback(fileselection_filename_changed_cb, this));
808    awr->awar(def_dir)   ->add_callback(makeRootCallback(fill_fileselection_cb, this));
809    awr->awar(def_filter)->add_callback(makeRootCallback(fileselection_filter_changed_cb, this));
810}
811
812#define SELBOX_AUTOREFRESH_FREQUENCY 3000 // refresh every XXX ms
813
814struct selbox_autorefresh_info {
815    unsigned long            modtime;
816    File_selection          *acbs;
817    selbox_autorefresh_info *next;
818};
819static selbox_autorefresh_info *autorefresh_info = 0;
820
821static unsigned autorefresh_selboxes(AW_root *) {
822    selbox_autorefresh_info *check = autorefresh_info;
823
824    while (check) {
825        GB_ULONG mtime = check->acbs->get_newest_dir_modtime();
826        if (mtime != check->modtime) {
827            check->modtime = mtime;
828            check->acbs->trigger_refresh();
829        }
830        check = check->next;
831    }
832
833    // refresh again and again and again..
834    return SELBOX_AUTOREFRESH_FREQUENCY;
835}
836
837static void selbox_install_autorefresh(AW_root *aw_root, File_selection *acbs) {
838    if (!autorefresh_info) {    // when installing first selbox
839        aw_root->add_timed_callback(SELBOX_AUTOREFRESH_FREQUENCY, makeTimedCallback(autorefresh_selboxes));
840    }
841
842    selbox_autorefresh_info *install = new selbox_autorefresh_info;
843
844    install->acbs    = acbs;
845    install->modtime = acbs->get_newest_dir_modtime();
846
847    install->next    = autorefresh_info;
848    autorefresh_info = install;
849}
850
851void AW_create_fileselection(AW_window *aws, const char *awar_prefix, const char *at_prefix, const char *pwd, DirDisplay disp_dirs, bool allow_wildcards) {
852    /*! Create a file selection box, this box needs 3 AWARS:
853     *
854     * 1. "$awar_prefix/filter"
855     * 2. "$awar_prefix/directory"
856     * 3. "$awar_prefix/file_name"
857     *
858     * (Note: The function AW_create_fileselection_awars() can be used to create them)
859     *
860     * the "$awar_prefix/file_name" contains the full filename
861     * Use AW_get_selected_fullname() to read it.
862     *
863     * The items are placed at
864     *
865     * 1. "$at_prefix""filter"
866     * 2. "$at_prefix""box"
867     * 3. "$at_prefix""file_name"
868     *
869     * if disp_dirs == ANY_DIR, then show directories and files
870     * if disp_dirs == MULTI_DIRS, then only show files, but from multiple directories
871     *
872     * pwd is the name of a 'shell environment variable' which indicates the base directory
873     * (e.g. 'PWD' or 'ARBHOME')
874     */
875
876    AW_root        *aw_root = aws->get_root();
877    File_selection *acbs    = new File_selection(aw_root, awar_prefix, pwd, disp_dirs, allow_wildcards);
878
879    acbs->create_gui_elements(aws, at_prefix);
880
881    fill_fileselection_cb(0, acbs);
882    fileselection_filename_changed_cb(0, acbs);    // this fixes the path name
883
884    selbox_install_autorefresh(aw_root, acbs);
885}
886
887char *AW_get_selected_fullname(AW_root *awr, const char *awar_prefix) { // @@@ add flag to select whether wildcards are allowed
888    char *file = awr->awar(GBS_global_string("%s/file_name", awar_prefix))->read_string();
889    if (file[0] != '/') {
890        // if name w/o directory was entered by hand (or by default) then append the directory :
891
892        char    *awar_dir_name = GBS_global_string_copy("%s/directory", awar_prefix);
893        AW_awar *awar_dir      = awr->awar_no_error(awar_dir_name);
894
895        if (!awar_dir) {
896            // file selection box was not active (happens e.g. for print tree)
897            awar_dir = awr->awar_string(awar_dir_name, GB_getcwd());
898        }
899
900        aw_assert(awar_dir);
901
902        char *dir = awar_dir->read_string();
903        if (!dir[0]) {          // empty -> fillin current dir
904            awar_dir->write_string(GB_getcwd());
905            freeset(dir, awar_dir->read_string());
906        }
907
908        char *full = strdup(GB_concat_full_path(dir, file));
909
910        free(dir);
911        free(file);
912
913        file = full;
914
915        free(awar_dir_name);
916    }
917
918    return file;
919}
920
921void AW_set_selected_fullname(AW_root *awr, const char *awar_prefix, const char *to_fullname) {
922    awr->awar(GBS_global_string("%s/file_name", awar_prefix))->write_string(to_fullname);
923}
924
925void AW_refresh_fileselection(AW_root *awr, const char *awar_prefix) {
926    // call optionally to force instant refresh
927    // (automatic refresh is done every SELBOX_AUTOREFRESH_FREQUENCY)
928
929    awr->awar(GBS_global_string("%s/directory", awar_prefix))->touch();
930}
931
932// --------------------------------------------------------------------------------
933
934#ifdef UNIT_TESTS
935#include <test_unit.h>
936
937#define TEST_EXPECT_EQUAL_DUPPED(cs1, cs2)                              \
938    do {                                                                \
939        char *s1, *s2;                                                  \
940        TEST_EXPECT_EQUAL(s1 = (cs1), s2 = (cs2));                      \
941        free(s1);                                                       \
942        free(s2);                                                       \
943    } while(0)
944
945void TEST_path_unfolding() {
946    const char *currDir = GB_getcwd();
947    {
948        gb_getenv_hook old = GB_install_getenv_hook(expand_symbolic_directories);
949
950        TEST_EXPECT_EQUAL(GB_getenv("PWD"), currDir);
951        TEST_EXPECT_EQUAL(GB_getenv("ARBHOME"), GB_getenvARBHOME());
952        TEST_EXPECT_NULL(GB_getenv("ARB_NONEXISTING_ENVAR"));
953
954        GB_install_getenv_hook(old);
955    }
956
957    TEST_EXPECT_EQUAL_DUPPED(AW_unfold_path("PWD", "/bin"),                strdup("/bin"));
958    TEST_EXPECT_EQUAL_DUPPED(AW_unfold_path("PWD", "../tests"),            strdup(GB_path_in_ARBHOME("UNIT_TESTER/tests")));
959    TEST_EXPECT_EQUAL_DUPPED(AW_unfold_path("ARB_NONEXISTING_ENVAR", "."), strdup(currDir));
960}
961TEST_PUBLISH(TEST_path_unfolding);
962
963#endif // UNIT_TESTS
964
965
Note: See TracBrowser for help on using the repository browser.