// // "$Id: fl_file_chooser.cxx,v 1.10.2.4 2000/06/03 08:37:08 bill Exp $" // // File chooser widget for the Fast Light Tool Kit (FLTK). // // Copyright 1998-2000 by Bill Spitzak and others. // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Library General Public // License as published by the Free Software Foundation; either // version 2 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Library General Public License for more details. // // You should have received a copy of the GNU Library General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 // USA. // // Please report all bugs and problems to "fltk-bugs@easysw.com". // #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static void default_callback(const char*) {} static void (*current_callback)(const char*) = default_callback; void fl_file_chooser_callback(void (*cb)(const char*)) { current_callback = cb ? cb : default_callback; } // "File Chooser Browser" widget: class FCB : public Fl_Browser_ { void* item_first() const ; void* item_next(void*) const ; void* item_prev(void*) const ; int item_height(const dirent*, int) const ; int item_height(void*) const ; int item_width(const dirent*) const ; int item_width(void*) const ; int item_quick_height(void*) const ; int incr_height() const ; void item_draw(void*, int, int, int, int) const ; int checkdir(const dirent*, char*) const ; void draw(); void clear_prev(); public: char listed[FL_PATH_MAX];// current dir & starname int dirend; // points after last / before starname int nameend; // length to trailing '*' or '\0' const char* pattern; // default pattern dirent** list; // the file names dirent** last; // pointer after end of list const char* message; // message if no file names char preved[FL_PATH_MAX];// directory listed in prev dirent** prev; // cached list of another directory dirent** prev_last; // end of that list int prev_count; FCB(int x, int y, int w, int h) : Fl_Browser_(x, y, w, h, 0) { type(FL_HOLD_BROWSER); listed[0] = 0; dirend = nameend = 1; pattern = 0; list = prev = 0; message = 0; } // ~FCB nyi void clear(); void set(const char*); int get(char*); }; // "File Chooser Window" widget: class FCW : public Fl_Window { public: int handle(int); Fl_Input input; Fl_Button* ok_button; Fl_Button* cancel_button; Fl_Button* normal_button; FCB browser; FCW(); }; /* Files are marked as being directories by replacing the trailing null with a '/' if it is a directory, a '\001' if it is *not* a directory. An item has height (and is thus selectable) if it is either a directory or if it matches the pattern. Quick-height assummes all unknown files are directories, and thus saves the time needed to do a stat(). */ // return pointer to last character: static const char* end_of_name(const dirent* d) { #if HAVE_DIRENT_H const char* e; for (e = d->d_name; ;e++) switch (*e) { case 0: case 1: case '/': return e; } #else // warning: clobbers byte after end of name return d->d_name + d->d_namelen; #endif } // return true if item is directory, when given pointer to last character: int FCB::checkdir(const dirent* d, char* e) const { if (*e == 1) return 0; if (*e == '/') return 1; char buf[FL_PATH_MAX]; memcpy(buf, listed, dirend); memcpy(buf+dirend, d->d_name, e-d->d_name); *(buf+dirend+(e-d->d_name)) = 0; if (filename_isdir(buf)) { *e = '/'; return 1; } else { *e = 1; return 0; } } void* FCB::item_first() const {return list;} void* FCB::item_next(void* p) const { if ((dirent**)p+1 >= last) return 0; return (dirent**)p+1; } void* FCB::item_prev(void* p) const { if ((dirent**)p <= list) return 0; return ((dirent**)p)-1; } #ifdef _MSC_VER #pragma optimize("a",off) // without this it does not change *e #endif static int ido_matching(const dirent* p, const char* e, const char* n) { // replace / or 1 at end with 0 and do match, then put back. yukko int save = *e; *(char*)e = 0; int r = filename_match(p->d_name, n); *(char*)e = save; return(r); } #ifdef _MSC_VER #pragma optimize("",on) #endif int FCB::incr_height() const {return textsize()+2;} int FCB::item_height(const dirent* p, int slow) const { const char* e = end_of_name(p); if (listed[dirend]) { // if (p->d_name[0]=='.' && listed[dirend]!='.') return 0; if (listed[nameend-1]=='/') { if (slow ? !checkdir(p, (char*)e) : *e==1) return 0; ((char*)listed)[nameend-1] = 0; int r = ido_matching(p, e, listed+dirend); ((char*)listed)[nameend-1] = '/'; if (!r) return 0; } else { if (!ido_matching(p, e, listed+dirend)) return 0; } } else { if (p->d_name[0]=='.') return 0; if (pattern && (slow ? !checkdir(p, (char*)e) : *e==1) && !ido_matching(p, e, pattern)) return 0; } return textsize()+2; } int FCB::item_height(void* x) const { return item_height(*(const dirent**)x, 1); } int FCB::item_quick_height(void* x) const { return item_height(*(const dirent**)x, 0); } void FCB::item_draw(void* v, int x, int y, int, int h) const { const dirent* p = *(const dirent**)v; const char* e = end_of_name(p); if (checkdir(p, (char*)e)) e++; if (v == selection()) fl_color(contrast(textcolor(), selection_color())); else fl_color(textcolor()); fl_font(textfont(), textsize()); fl_draw(p->d_name, e-p->d_name, x+4, y+h-3); } int FCB::item_width(const dirent* p) const { const char* e = end_of_name(p); if (*e == '/') e++; fl_font(textfont(), textsize()); return (int)fl_width(p->d_name, e-p->d_name)+4; } int FCB::item_width(void* x) const { return item_width(*(const dirent**)x); } // "get" the current value by copying the name of the selected file // or if none are selected, by copying as many common letters as // possible of the matched file list: int FCB::get(char* buf) { dirent** q = (dirent**)selection(); // the file to copy from int n = 0; // number of letters if (q) { // a file is selected const char* e = end_of_name(*q); n = e - (*q)->d_name; if (*e == '/') n++; } else { // do filename completion for (q = list; q < last && !item_height(*q, 0); q++); if (q < last) { const char* e = end_of_name(*q); n = e - (*q)->d_name; if (*e == '/') n++; for (dirent** r = q+1; n && r < last; r++) { if (!item_height(*r, 0)) continue; int i; #ifdef _WIN32 for (i=0; id_name[i])==tolower((*r)->d_name[i]); i++) {} #else for (i=0; id_name[i]==(*r)->d_name[i]; i++) {} #endif n = i; } } } if (n) { memcpy(buf, listed, dirend); memcpy(buf+dirend, (*q)->d_name, n); buf[dirend+n]=0; } return n; } // "set" the current value by changing the directory being listed and // changing the highlighted item, if possible: void FCB::set(const char* buf) { int bufdirend; int ispattern = 0; const char* c = buf; for (bufdirend=0; *c;) switch(*c++) { case '?': case '[': case '*': case '{': ispattern = 1; goto BREAK; #if defined(WIN32) || defined(__EMX__) && !defined(__CYGWIN__) case '\\': #endif case '/': bufdirend=c-buf; break; } #if defined(WIN32) || defined(__EMX__) && !defined(__CYGWIN__) if ((!bufdirend) && isalpha(buf[0]) && (buf[1]==':')) bufdirend = 2; #endif BREAK: int bufend = strlen(buf); if (bufend<=bufdirend) ispattern = 1; // if directory is different, change list to xxx/ : if (bufdirend != dirend || strncmp(buf, listed, bufdirend)) { if (prev && preved[bufdirend]==0 && !strncmp(buf, preved, bufdirend)) { strcpy(preved, listed); preved[dirend] = 0; dirent** t; t = prev; prev = list; list = t; t = prev_last; prev_last = last; last = t; strcpy(listed, buf); dirend = nameend = bufdirend; message = 0; } else { if (list) { clear_prev(); strcpy(preved, listed); preved[dirend]=0; prev = list; prev_last = last; } list = last = 0; message = "reading..."; redraw(); Fl::flush(); redraw(); strcpy(listed, buf); dirend = nameend = bufdirend; listed[dirend] = listed[dirend+1] = 0; int n = filename_list(dirend ? listed : ".", &list); if (n < 0) { if (errno==ENOENT) message = "No such directory"; else message = strerror(errno); n = 0; list = 0; } else message = 0; last = list+n; } if (list && last <= list+2) message = "Empty directory"; new_list(); } dirent** q = 0; // will point to future selection int any = 0; // true if any names shown // do we match one item in the previous list? if (!ispattern && bufend >= nameend) { for (q = list; ; q++) { if (q >= last) {q = 0; break;} if (item_height(*q, 0)==0) continue; any = 1; const char* a = (*q)->d_name; const char* b = buf+bufdirend; #if defined(WIN32) && !defined(__CYGWIN__) while (*b && tolower(*a)==tolower(*b)) {a++; b++;} #else while (*b && *a==*b) {a++; b++;} #endif if (!*b && (*a==0 || /* *a=='/' ||*/ *a==1)) break; } } // no, change the list pattern to the new text + a star: if (!q) { strcpy(listed+dirend, buf+bufdirend); nameend = bufend; if (!ispattern) {listed[nameend]='*'; listed[nameend+1]=0;} any = 0; // search again for an exact match: for (q = list; ; q++) { if (q >= last) {q = 0; break;} if (item_height(*q, 0)==0) continue; any = 1; const char* a = (*q)->d_name; const char* b = buf+bufdirend; #if defined(WIN32) && !defined(__CYGWIN__) while (*b && tolower(*a)==tolower(*b)) {a++; b++;} #else while (*b && *a==*b) {a++; b++;} #endif if (!*b && (*a==0 || /* *a=='/' ||*/ *a==1)) break; } new_list(); } if (any) message = 0; else if (!message) message = "No matching files"; select_only(q); if (q) current_callback(buf); } void FCB::draw() { if (!message) { Fl_Browser_::draw(); if (full_height() > 0) return; message = "No matching files"; } Fl_Boxtype b = box(); if (!b) b = FL_DOWN_BOX; draw_box(b,color()); fl_color(FL_INACTIVE_COLOR); fl_font(textfont(), textsize()); fl_draw(message, x()+7, y()+3, w(), h()-3, FL_ALIGN_TOP_LEFT); // insure scrollbars are redrawn if error message goes away: scrollbar.redraw(); hscrollbar.redraw(); } void FCB::clear_prev() { if (prev) { for (dirent**p=prev_last-1; p>=prev; p--) free((void*)*p); free((void*)prev); prev = prev_last = 0; } } void FCB::clear() { if (list) { for (dirent**p=last-1; p>=list; p--) free((void*)*p); free((void*)list); list = last = 0; } clear_prev(); listed[0] = 0; dirend = 1; } //////////////////////////////////////////////////////////////// static void fcb_cb(Fl_Widget*, void* v) { FCW* w = (FCW*)v; char buf[FL_PATH_MAX]; if (w->browser.get(buf)) { w->input.value(buf); w->input.position(10000); // w->input.position(10000, w->browser.dirend); if (Fl::event_button()==1) { if (Fl::event_clicks()) w->ok_button->do_callback(); else w->browser.set(buf); } else { current_callback(buf); } } } static void tab_cb(Fl_Widget*, void* v) { FCW* w = (FCW*)v; char buf[FL_PATH_MAX]; if (w->browser.get(buf)) { w->input.value(buf); w->input.position(10000); w->browser.set(buf); } } #if defined(WIN32) || defined(__EMX__) && !defined(__CYGWIN__) // ':' needs very special handling! static inline int isdirsep(char c) {return c=='/' || c=='\\';} #else #define isdirsep(c) ((c)=='/') #endif static void input_cb(Fl_Widget*, void* v) { FCW* w = (FCW*)v; const char* buf = w->input.value(); char localbuf[FL_PATH_MAX]; if (buf[0] && isdirsep(buf[w->input.size()-1]) && filename_expand(localbuf, buf)) { buf = localbuf; w->input.value(localbuf); w->input.position(10000); } w->browser.set(buf); } static void up_cb(Fl_Widget*, void* v) { // the .. button FCW* w = (FCW*)v; char* p; char* newname; char buf[FL_PATH_MAX]; p = w->browser.listed+w->browser.dirend-1; // point right before last '/' if (p < w->browser.listed) newname = "../"; // go up from current directory else { for (; p>w->browser.listed; p--) if (isdirsep(*(p-1))) break; if (isdirsep(*p) || *p=='.' && (isdirsep(p[1]) || p[1]=='.' && isdirsep(p[2]))) { p = w->browser.listed+w->browser.dirend; memcpy(buf, w->browser.listed, p-w->browser.listed); strcpy(buf+(p-w->browser.listed), "../"); } else { memcpy(buf, w->browser.listed, p-w->browser.listed); buf[p-w->browser.listed] = 0; } newname = buf; } w->input.value(newname); w->input.position(10000); w->browser.set(newname); } static void dir_cb(Fl_Widget* obj, void* v) { // directory buttons FCW* w = (FCW*)v; const char* p = obj->label(); if (*p=='&') p++; char buf[FL_PATH_MAX]; char* q; for (q=buf; *p && *p!=' '; *q++ = *p++); *q = 0; filename_expand(buf, buf); w->input.value(buf); w->input.position(10000); w->browser.set(buf); } static void working_cb(Fl_Widget*, void* v) { // directory buttons FCW*w = (FCW*)v; char buf[FL_PATH_MAX]; filename_absolute(buf, ""); w->input.value(buf); w->input.position(10000); w->browser.set(buf); } static void files_cb(Fl_Widget* obj, void* v) { // file pattern buttons FCW* w = (FCW*)v; char buf[FL_PATH_MAX]; strcpy(buf, w->input.value()); char* q = buf+w->browser.dirend; if (obj != w->normal_button) { // tack on first word of label const char* p = obj->label(); if (*p=='&') p++; for (; *p && *p!=' '; *q++ = *p++); } *q = 0; w->input.value(buf); w->input.position(10000, w->browser.dirend); w->browser.set(buf); } /*----------------------- The Main Routine ----------------------*/ #define HEIGHT_BOX (4*WIDTH_SPC+HEIGHT_BUT+HEIGHT_INPUT+HEIGHT_BROWSER) #define HEIGHT_BUT 23 #define HEIGHT_INPUT 23 #define HEIGHT_BROWSER (9*HEIGHT_BUT+2) // must be > buttons*HEIGHT_BUT #define WIDTH_BOX (3*WIDTH_SPC+WIDTH_BUT+WIDTH_BROWSER) #define WIDTH_BROWSER 350 #define WIDTH_BUT 125 #define WIDTH_OK 60 #define WIDTH_SPC 5 int FCW::handle(int event) { if (Fl_Window::handle(event)) return 1; if (event==FL_KEYBOARD && Fl::event_key()==FL_Tab) { tab_cb(this, this); return 1; } return 0; } // set this to make extra directory-jumping button: const char* fl_file_chooser_button; extern const char* fl_ok; extern const char* fl_cancel; FCW::FCW() : Fl_Window(WIDTH_BOX, HEIGHT_BOX), input(WIDTH_SPC, HEIGHT_BOX-HEIGHT_BUT-2*WIDTH_SPC-HEIGHT_INPUT, WIDTH_BOX-2*WIDTH_SPC, HEIGHT_INPUT, 0), browser(2*WIDTH_SPC+WIDTH_BUT, WIDTH_SPC, WIDTH_BROWSER, HEIGHT_BROWSER) { int but_y = WIDTH_SPC; input.callback(input_cb, this); input.when(FL_WHEN_CHANGED); // add(browser); browser.callback(fcb_cb, this); begin(); Fl_Widget* obj; obj = ok_button = new Fl_Return_Button( WIDTH_BOX-2*(WIDTH_SPC+WIDTH_OK), HEIGHT_BOX-WIDTH_SPC-HEIGHT_BUT, WIDTH_OK, HEIGHT_BUT, fl_ok); obj = cancel_button = new Fl_Button( WIDTH_BOX-WIDTH_SPC-WIDTH_OK, HEIGHT_BOX-WIDTH_SPC-HEIGHT_BUT, WIDTH_OK, HEIGHT_BUT, fl_cancel); cancel_button->shortcut("^["); obj=new Fl_Button(WIDTH_SPC,but_y,WIDTH_BUT,HEIGHT_BUT, "&Up one directory"); obj->callback(up_cb, this); but_y += HEIGHT_BUT; obj = new Fl_Button(WIDTH_SPC, but_y, WIDTH_BUT, HEIGHT_BUT, "&~/ Home"); obj->callback(dir_cb, this); but_y += HEIGHT_BUT; obj = new Fl_Button(WIDTH_SPC, but_y, WIDTH_BUT, HEIGHT_BUT, "&/ Root"); obj->callback(dir_cb, this); but_y += HEIGHT_BUT; obj=new Fl_Button(WIDTH_SPC, but_y, WIDTH_BUT, HEIGHT_BUT, "&Current dir"); obj->callback(working_cb, this); but_y += HEIGHT_BUT; if (fl_file_chooser_button) { obj=new Fl_Button(WIDTH_SPC,but_y,WIDTH_BUT,HEIGHT_BUT,fl_file_chooser_button); obj->callback(dir_cb, this); but_y += HEIGHT_BUT; } normal_button = new Fl_Button(WIDTH_SPC, but_y, WIDTH_BUT, HEIGHT_BUT, ""); normal_button->callback(files_cb, this); but_y += HEIGHT_BUT; obj = new Fl_Button(WIDTH_SPC,but_y, WIDTH_BUT, HEIGHT_BUT, "* &All files"); obj->callback(files_cb, this); but_y += HEIGHT_BUT; obj = new Fl_Button(WIDTH_SPC,but_y,WIDTH_BUT,HEIGHT_BUT, ". &Hidden files"); obj->callback(files_cb, this); but_y += HEIGHT_BUT; obj = new Fl_Button(WIDTH_SPC,but_y,WIDTH_BUT,HEIGHT_BUT, "*/ &Directories"); obj->callback(files_cb, this); but_y += HEIGHT_BUT; resizable(new Fl_Box(browser.x(), but_y, ok_button->x()-browser.x(), browser.y()+browser.h()-but_y)); // add(input); // put last for better draw() speed end(); set_modal(); } char* fl_file_chooser(const char* message, const char* pat, const char* fname) { static FCW* f; if (!f) f = new FCW(); f->ok_button->label(fl_ok); f->cancel_button->label(fl_cancel); if (pat && !*pat) pat = 0; if (fname && *fname) { f->input.value(fname); } else if (f->browser.pattern != pat && (!pat || !f->browser.pattern || strcmp(pat,f->browser.pattern))) { // if pattern is different, remove name but leave old directory: const char* p = f->input.value(); const char* q = filename_name(p); f->input.value(p, q-p); } f->browser.pattern = pat; f->normal_button->label(pat ? pat : "visible files"); f->browser.set(f->input.value()); f->input.position(10000, f->browser.dirend); f->label(message); f->hotspot(f); f->show(); int ok = 0; for (;;) { Fl::wait(); Fl_Widget* o = Fl::readqueue(); if (o == f->ok_button) {ok = 1; break;} else if (o == f->cancel_button || o == f) break; } f->hide(); f->browser.clear(); if (!ok) return 0; const char* r = f->input.value(); const char* p; for (p=r+f->browser.dirend; *p; p++) if (*p=='*' || *p=='?' || *p=='[' || *p=='{') return 0; return (char*)r; } // // End of "$Id: fl_file_chooser.cxx,v 1.10.2.4 2000/06/03 08:37:08 bill Exp $". //