fltk/src/fl_file_chooser.cxx
Michael R Sweet 4b561b6e90 Updated copyright notices for all of the 1.0.x files.
Updated the configure script for *BSD and GCC 2.95 (-fno-exceptions)

Added install rule to documentation directory.

Dumped old packages directory; added traditional RPM spec file and EPM
list file (that replace all of the packages stuff)

The FLUID man page is now "fluid.1" for the formatted page and "fluid.man"
for the non-formatted page, since only IRIX uses pack'd formatted man pages.

Whew!


git-svn-id: file:///fltk/svn/fltk/branches/branch-1.0@1090 ea41ed52-d2ee-0310-a9c1-e6b18d33e121
2000-04-25 22:17:00 +00:00

627 lines
18 KiB
C++

//
// "$Id: fl_file_chooser.cxx,v 1.10.2.3 2000/04/25 22:16:41 mike 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 <config.h>
#include <FL/fl_file_chooser.H>
#include <FL/Fl.H>
#include <FL/Fl_Window.H>
#include <FL/Fl_Box.H>
#include <FL/Fl_Button.H>
#include <FL/Fl_Return_Button.H>
#include <FL/Fl_Browser_.H>
#include <FL/Fl_Input.H>
#include <FL/fl_draw.H>
#include <FL/filename.H>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <ctype.h>
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;
}
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);
}
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;
for (i=0; i<n && (*q)->d_name[i]==(*r)->d_name[i]; i++);
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.3 2000/04/25 22:16:41 mike Exp $".
//