source: branches/help/WINDOW/AW_file.cxx

Last change on this file was 18730, checked in by westram, 3 years ago
  • remove trailing whitespace from c source.
  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 33.3 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 = NULp;
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 NULp;
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(NULp),
181          pwd(strdup(pwd_)),
182          pwdx(NULp),
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");
200        def_dir    = GBS_string_eval(awar_prefix, "*=*/directory");
201        def_filter = GBS_string_eval(awar_prefix, "*=*/filter");
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 NULp if no suffix found)
248    GB_CSTR dot = strrchr(fullpath, '.');
249    if (!dot) return NULp;
250
251    GB_CSTR lslash = strrchr(fullpath, '/');
252    if (lslash && lslash>dot) return NULp; // 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, NULp, NULp);
264
265    // remove dots and spaces from suffix:
266    while (suffix[0] == '.' || suffix[0] == ' ') ++suffix;
267    if (!suffix[0]) suffix = NULp;
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; 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 = NULp;
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
496inline bool contains_wildcards(const char *name) { return strpbrk(name, "?*") != NULp; }
497
498void File_selection::fill() {
499    AW_root *aw_root = awr;
500    filelist->clear();
501
502    char *filter  = aw_root->awar(def_filter)->read_string();
503    char *name    = aw_root->awar(def_name)->read_string();
504
505    const char *name_only = NULp;
506    {
507        char *slash = strrchr(name, '/');
508        name_only   = slash ? slash+1 : name;
509    }
510
511    StrArray dirs;
512    {
513        char *diru = aw_root->awar(def_dir)->read_string();
514        if (dirdisp == MULTI_DIRS) {
515            ConstStrArray cdirs;
516            GBT_split_string(cdirs, diru, ":", true);
517            for (unsigned i = 0; i<cdirs.size(); ++i) dirs.put(strdup(cdirs[i]));
518        }
519        else {
520            if (name[0] == '/' && AW_is_dir(name)) {
521                dirs.put(strdup(name));
522                name_only = "";
523            }
524            else {
525                char *fulldir = AW_unfold_path(pwd, diru);
526                dirs.put(fulldir);
527            }
528        }
529        free(diru);
530    }
531
532    filled_by_wildcard = contains_wildcards(name_only);
533
534    if (dirdisp == ANY_DIR) {
535        aw_assert(dirs.size() == 1);
536        const char *fulldir    = dirs[0];
537
538        DuplicateLinkFilter unDup;
539        unDup.register_directory(fulldir);
540
541        if (filled_by_wildcard) {
542            if (leave_wildcards) {
543                filelist->insert(GBS_global_string("  ALL '%s' in '%s'", name_only, fulldir), name);
544            }
545            else {
546                filelist->insert(GBS_global_string("  ALL '%s' in+below '%s'", name_only, fulldir), name);
547            }
548        }
549        else {
550            filelist->insert(GBS_global_string("  CONTENTS OF '%s'", fulldir), fulldir);
551            if (filter[0]) {
552                filelist->insert(GBS_global_string("!  Find all         (*%s)", filter), "*");
553            }
554        }
555
556        if (strcmp("/", fulldir)) {
557            filelist->insert("! \'PARENT DIR\'      (..)", "..");
558        }
559        if (show_subdirs) {
560            show_soft_link(filelist, pwd, unDup);
561
562            if (pwdx) {        // additional directories
563                char *start = pwdx;
564                while (start) {
565                    char *multiple = strchr(start, '^');
566                    if (multiple) {
567                        multiple[0] = 0;
568                        show_soft_link(filelist, start, unDup);
569                        multiple[0] = '^';
570                        start       = multiple+1;
571                    }
572                    else {
573                        show_soft_link(filelist, start, unDup);
574                        start = NULp;
575                    }
576                }
577            }
578
579            show_soft_link(filelist, "HOME", unDup);
580            show_soft_link(filelist, "PWD", unDup);
581            show_soft_link(filelist, "ARB_WORKDIR", unDup);
582
583            filelist->insert("!  Sub-directories  (shown)", GBS_global_string("%s?hide?", name));
584        }
585        else {
586            filelist->insert("!  Sub-directories  (hidden)", GBS_global_string("%s?show?", name));
587        }
588        filelist->insert(GBS_global_string("!  Hidden           (%s)", show_hidden ? "shown" : "not shown"), GBS_global_string("%s?dot?", name));
589    }
590    else {
591        aw_assert(dirdisp == MULTI_DIRS);
592    }
593
594    static const char *order_name[DIR_SORT_ORDERS] = { "alpha", "date", "size" };
595
596    filelist->insert(GBS_global_string("!  Sort order       (%s)", order_name[sort_order]), GBS_global_string("%s?sort?", name));
597
598    bool insert_dirs = dirdisp == ANY_DIR && show_subdirs;
599
600    searchTime.reset(); // limits time spent in fill_recursive
601    for (unsigned i = 0; i<dirs.size(); ++i) {
602        const char *fulldir = dirs[i];
603        if (filled_by_wildcard) {
604            if (leave_wildcards) {
605                fill_recursive(fulldir, strlen(fulldir)+1, name_only, false, insert_dirs);
606            }
607            else {
608                if (dirdisp == ANY_DIR) { // recursive wildcarded search
609                    fill_recursive(fulldir, strlen(fulldir)+1, name_only, true, false);
610                }
611                else {
612                    char *mask = GBS_global_string_copy("%s*%s", name_only, filter);
613                    fill_recursive(fulldir, strlen(fulldir)+1, mask, false, false);
614                    free(mask);
615                }
616            }
617        }
618        else {
619            char *mask = GBS_global_string_copy("*%s", filter);
620
621            fill_recursive(fulldir, strlen(fulldir)+1, mask, false, insert_dirs);
622            free(mask);
623        }
624    }
625
626    if (!searchTime.finished_in_time()) {
627        filelist->insert(GBS_global_string("!  Find aborted    (after %.1fs; click to search longer)", searchTime.allowed_duration()), GBS_global_string("%s?inctime?", name));
628    }
629
630    if (sort_order == SORT_SIZE) {
631        filelist->sortCustom(cmpBySize);
632    }
633    else {
634        filelist->sort(false, false);
635    }
636    format_columns();
637    filelist->insert_default("", "");
638    filelist->update();
639
640    if (filled_by_wildcard && !leave_wildcards) { // avoid returning wildcarded filename (if !leave_wildcards)
641        aw_root->awar(def_name)->write_string("");
642    }
643
644    free(name);
645    free(filter);
646}
647
648static const char *detectBrowserCommand(const char *fname) {
649    // if fname ends with '?word?'
650    // -> returns ptr to 'word'
651    //    NULp otherwise
652
653    const char *qm1 = NULp;
654    const char *qm2 = NULp;
655    const char *qm3 = ARB_strchrnul(fname, '?');
656
657    while (qm3[0]) {
658        qm1 = qm2;
659        qm2 = qm3;
660        qm3 = ARB_strchrnul(qm2+1, '?');
661    }
662
663    if (qm1 && qm1[0]) {
664        aw_assert(qm2 && qm2[0]);
665        if (!qm2[1]) { // 2nd ? at EOS
666            int cmdlen = qm2-qm1-1;
667            if (cmdlen>=1) return qm1+1;
668        }
669    }
670    return NULp;
671}
672
673void File_selection::filename_changed(bool post_filter_change_HACK) {
674    AW_root    *aw_root = awr;
675    const char *fname   = aw_root->awar(def_name)->read_string();
676
677#if defined(TRACE_FILEBOX)
678    printf("fileselection_filename_changed_cb:\n"
679           "- fname   ='%s'\n", fname);
680#endif // TRACE_FILEBOX
681
682    if (fname[0]) {
683        const char *browser_command = detectBrowserCommand(fname);
684        if (browser_command) {
685            char *browser_command_copy = ARB_strndup(browser_command, strlen(browser_command)-1);
686            char *fname_no_cmd         = ARB_strpartdup(fname, browser_command-2);
687
688            aw_root->awar(def_name)->write_string(fname_no_cmd); // re-write w/o browser_command
689            execute_browser_command(browser_command_copy);
690            trigger_refresh();
691
692            free(fname_no_cmd);
693            free(browser_command_copy);
694        }
695        else if (dirdisp != MULTI_DIRS) {
696            char *newName = NULp;
697            char *dir     = aw_root->awar(def_dir)->read_string();
698
699#if defined(TRACE_FILEBOX)
700            printf("- dir     ='%s'\n", dir);
701#endif // TRACE_FILEBOX
702
703            if (fname[0] == '/' || fname[0] == '~') {
704                newName = strdup(GB_canonical_path(fname));
705            }
706            else {
707                if (dir[0]) {
708                    if (dir[0] == '/') {
709                        newName = strdup(GB_concat_full_path(dir, fname));
710                    }
711                    else {
712                        char *fulldir = NULp;
713
714                        if (dir[0] == '.') fulldir = AW_unfold_path(pwd, dir);
715                        else fulldir               = strdup(dir);
716
717                        newName = strdup(GB_concat_full_path(fulldir, fname));
718                        free(fulldir);
719                    }
720                }
721                else {
722                    newName = AW_unfold_path(pwd, fname);
723                }
724            }
725
726            // Allow to select symbolic links for files (i.e. do not automatically switch to link-target;
727            // doing so made it impossible to load quicksaves done versus a master DB).
728            if (newName && strcmp(fname, newName) != 0) {
729                if (!GB_is_directory(fname) && !GB_is_directory(newName)) {
730                    if (GB_is_link(fname) ) freenull(newName); // do not follow symlink!
731                }
732            }
733
734            if (newName) {
735#if defined(TRACE_FILEBOX)
736                printf("- newName ='%s'\n", newName);
737#endif // TRACE_FILEBOX
738
739                if (AW_is_dir(newName)) {
740                    aw_root->awar(def_name)->write_string("");
741                    aw_assert(dirdisp != MULTI_DIRS); // overwriting content unwanted if displaying MULTI_DIRS
742                    aw_root->awar(def_dir)->write_string(newName);
743                    aw_root->awar(def_name)->write_string("");
744                }
745                else {
746                    char *lslash = strrchr(newName, '/');
747                    if (lslash) {
748                        if (lslash == newName) { // root directory
749                            aw_assert(dirdisp != MULTI_DIRS); // overwriting content unwanted if displaying MULTI_DIRS
750                            aw_root->awar(def_dir)->write_string("/"); // write directory part
751                        }
752                        else {
753                            lslash[0] = 0;
754                            aw_assert(dirdisp != MULTI_DIRS); // overwriting content unwanted if displaying MULTI_DIRS
755                            aw_root->awar(def_dir)->write_string(newName); // write directory part
756                            lslash[0] = '/';
757                        }
758                    }
759
760                    // now check the correct suffix :
761                    {
762                        char *filter = aw_root->awar(def_filter)->read_string();
763                        if (filter[0]) {
764                            char *pfilter = strrchr(filter, '.');
765                            pfilter       = pfilter ? pfilter+1 : filter;
766
767                            char *suffix = (char*)get_suffix(newName); // cast ok, since get_suffix points into newName
768
769                            if (!suffix || strcmp(suffix, pfilter) != 0) {
770                                if (suffix && post_filter_change_HACK) {
771                                    if (suffix[-1] == '.') suffix[-1] = 0;
772                                }
773                                freeset(newName, set_suffix(newName, pfilter));
774                            }
775                        }
776                        free(filter);
777                    }
778
779                    if (strcmp(newName, fname) != 0) {
780                        aw_root->awar(def_name)->write_string(newName); // loops back if changed !!!
781                    }
782                }
783            }
784            free(dir);
785
786            if (strchr(fname, '*')) { // wildcard -> search for suffix
787                trigger_refresh();
788            }
789
790            free(newName);
791        }
792    }
793}
794
795static bool avoid_multi_refresh = false;
796
797static void fill_fileselection_cb(AW_root*, File_selection *cbs) {
798    if (!avoid_multi_refresh) {
799        LocallyModify<bool> flag(avoid_multi_refresh, true);
800        cbs->fill();
801    }
802}
803static void fileselection_filename_changed_cb(AW_root*, File_selection *cbs) {
804    if (!avoid_multi_refresh) {
805        LocallyModify<bool> flag(avoid_multi_refresh, true);
806        cbs->filename_changed(false);
807        cbs->fill();
808    }
809    else {
810        cbs->filename_changed(false);
811    }
812}
813static void fileselection_filter_changed_cb(AW_root*, File_selection *cbs) {
814    if (!avoid_multi_refresh) {
815        LocallyModify<bool> flag(avoid_multi_refresh, true);
816        cbs->filename_changed(true);
817        cbs->fill();
818    }
819    else {
820        cbs->filename_changed(true);
821    }
822}
823
824void File_selection::bind_callbacks() {
825    awr->awar(def_name)  ->add_callback(makeRootCallback(fileselection_filename_changed_cb, this));
826    awr->awar(def_dir)   ->add_callback(makeRootCallback(fill_fileselection_cb, this));
827    awr->awar(def_filter)->add_callback(makeRootCallback(fileselection_filter_changed_cb, this));
828}
829
830#define SELBOX_AUTOREFRESH_FREQUENCY 3000 // refresh every XXX ms
831
832struct selbox_autorefresh_info {
833    unsigned long            modtime;
834    File_selection          *acbs;
835    selbox_autorefresh_info *next;
836};
837static selbox_autorefresh_info *autorefresh_info = NULp;
838
839static unsigned autorefresh_selboxes(AW_root *) {
840    selbox_autorefresh_info *check = autorefresh_info;
841
842    while (check) {
843        GB_ULONG mtime = check->acbs->get_newest_dir_modtime();
844        if (mtime != check->modtime) {
845            check->modtime = mtime;
846            check->acbs->trigger_refresh();
847        }
848        check = check->next;
849    }
850
851    // refresh again and again and again..
852    return SELBOX_AUTOREFRESH_FREQUENCY;
853}
854
855static void selbox_install_autorefresh(AW_root *aw_root, File_selection *acbs) {
856    if (!autorefresh_info) {    // when installing first selbox
857        aw_root->add_timed_callback(SELBOX_AUTOREFRESH_FREQUENCY, makeTimedCallback(autorefresh_selboxes));
858        // @@@ replace timer callback with AW_add_inotification!
859    }
860
861    selbox_autorefresh_info *install = new selbox_autorefresh_info;
862
863    install->acbs    = acbs;
864    install->modtime = acbs->get_newest_dir_modtime();
865
866    install->next    = autorefresh_info;
867    autorefresh_info = install;
868}
869
870void AW_create_fileselection(AW_window *aws, const char *awar_prefix, const char *at_prefix, const char *pwd, DirDisplay disp_dirs, bool allow_wildcards) {
871    /*! Create a file selection box, this box needs 3 AWARS:
872     *
873     * 1. "$awar_prefix/filter"
874     * 2. "$awar_prefix/directory"
875     * 3. "$awar_prefix/file_name"
876     *
877     * (Note: The function AW_create_fileselection_awars() can be used to create them)
878     *
879     * the "$awar_prefix/file_name" contains the full filename
880     * Use AW_get_selected_fullname() to read it.
881     *
882     * The items are placed at
883     *
884     * 1. "$at_prefix""filter"
885     * 2. "$at_prefix""box"
886     * 3. "$at_prefix""file_name"
887     *
888     * if disp_dirs == ANY_DIR, then show directories and files
889     * if disp_dirs == MULTI_DIRS, then only show files, but from multiple directories
890     *
891     * pwd is the name of a 'shell environment variable' which indicates the base directory
892     * (e.g. 'PWD' or 'ARBHOME')
893     */
894
895    AW_root        *aw_root = aws->get_root();
896    File_selection *acbs    = new File_selection(aw_root, awar_prefix, pwd, disp_dirs, allow_wildcards);
897
898    acbs->create_gui_elements(aws, at_prefix);
899
900    fill_fileselection_cb(NULp, acbs);
901    fileselection_filename_changed_cb(NULp, acbs);    // this fixes the path name
902
903    selbox_install_autorefresh(aw_root, acbs);
904}
905
906char *AW_get_selected_fullname(AW_root *awr, const char *awar_prefix) { // @@@ add flag to select whether wildcards are allowed
907    char *file = awr->awar(GBS_global_string("%s/file_name", awar_prefix))->read_string();
908    if (file[0] != '/') {
909        // if name w/o directory was entered by hand (or by default) then append the directory :
910
911        char    *awar_dir_name = GBS_global_string_copy("%s/directory", awar_prefix);
912        AW_awar *awar_dir      = awr->awar_no_error(awar_dir_name);
913
914        if (!awar_dir) {
915            // file selection box was not active (happens e.g. for print tree)
916            awar_dir = awr->awar_string(awar_dir_name, GB_getcwd());
917        }
918
919        aw_assert(awar_dir);
920
921        char *dir = awar_dir->read_string();
922        if (!dir[0]) {          // empty -> fillin current dir
923            awar_dir->write_string(GB_getcwd());
924            freeset(dir, awar_dir->read_string());
925        }
926
927        char *full = strdup(GB_concat_full_path(dir, file));
928
929        free(dir);
930        free(file);
931
932        file = full;
933
934        free(awar_dir_name);
935    }
936
937    return file;
938}
939
940void AW_set_selected_fullname(AW_root *awr, const char *awar_prefix, const char *to_fullname) {
941    awr->awar(GBS_global_string("%s/file_name", awar_prefix))->write_string(to_fullname);
942}
943
944void AW_refresh_fileselection(AW_root *awr, const char *awar_prefix) {
945    // call optionally to force instant refresh
946    // (automatic refresh is done every SELBOX_AUTOREFRESH_FREQUENCY)
947
948    awr->awar(GBS_global_string("%s/directory", awar_prefix))->touch();
949}
950
951// --------------------------------------------------------------------------------
952
953#ifdef UNIT_TESTS
954#include <test_unit.h>
955
956#define TEST_EXPECT_EQUAL_DUPPED(cs1, cs2)                              \
957    do {                                                                \
958        char *s1, *s2;                                                  \
959        TEST_EXPECT_EQUAL(s1 = (cs1), s2 = (cs2));                      \
960        free(s1);                                                       \
961        free(s2);                                                       \
962    } while(0)
963
964void TEST_detectBrowserCommand() {
965    TEST_EXPECT_EQUAL(detectBrowserCommand("hello"),           NULp);
966    TEST_EXPECT_EQUAL(detectBrowserCommand("/p/2.f"),          NULp);
967    TEST_EXPECT_EQUAL(detectBrowserCommand("/p/2.f?cmd?"),     "cmd?");
968    TEST_EXPECT_EQUAL(detectBrowserCommand("/p/2.f?cmd?x"),    NULp);
969    TEST_EXPECT_EQUAL(detectBrowserCommand("/p/2.f?cmd?2nd?"), "2nd?");
970    TEST_EXPECT_EQUAL(detectBrowserCommand("/p/2.f??"),        NULp);
971    TEST_EXPECT_EQUAL(detectBrowserCommand("/p/2.f???"),       NULp);
972    TEST_EXPECT_EQUAL(detectBrowserCommand("/p/2.f?cmd"),      NULp);
973}
974
975void TEST_path_unfolding() {
976    const char *currDir = GB_getcwd();
977    {
978        gb_getenv_hook old = GB_install_getenv_hook(expand_symbolic_directories);
979
980        TEST_EXPECT_EQUAL(GB_getenv("PWD"), currDir);
981        TEST_EXPECT_EQUAL(GB_getenv("ARBHOME"), GB_getenvARBHOME());
982        TEST_EXPECT_NULL(GB_getenv("ARB_NONEXISTING_ENVAR"));
983
984        GB_install_getenv_hook(old);
985    }
986
987    TEST_EXPECT_EQUAL_DUPPED(AW_unfold_path("PWD", "/usr"),                strdup("/usr"));
988    TEST_EXPECT_EQUAL_DUPPED(AW_unfold_path("PWD", "../tests"),            strdup(GB_path_in_ARBHOME("UNIT_TESTER/tests")));
989    TEST_EXPECT_EQUAL_DUPPED(AW_unfold_path("ARB_NONEXISTING_ENVAR", "."), strdup(currDir));
990}
991TEST_PUBLISH(TEST_path_unfolding);
992
993#endif // UNIT_TESTS
994
995
Note: See TracBrowser for help on using the repository browser.