fltk/src/fl_file_chooser.cxx
Bill Spitzak b0cdb25d3c Fixes for 1.0.8 I found:
Fixed hardware overlays.  The problem was the new fl_clipped() code,
which tests against the current window size.  The hardware overlay
code did not set the current window when drawing the overlay.  I
needed hardware overlay for DD's code, I'm not sure if these fixes are
good enough to enable this in our general release.  Hardware overlay
still only works on SGI Irix.

Some patches to turn off the MSVC++ -Oa (assumme no aliasing)
optimization flag.  Suprisingly this only broke a few parts of fltk,
or at least these are the only ones I found.

Does not unmap child windows when the main window is iconized.  This
reduces flashing when the window is deiconized.

Fl::key() is set to zero by all events except key down/up.  This will
allow you to reliably test if an event or callback was produced by a
keystroke.  Fixes the bug posted about stopping Escape from closing
the window.

User defined cursors on OpenGL windows slowed down NT a *LOT*.  Some
attempts to fix this by turning off the cursor while drawing the
window.

Filename completion in the file chooser works better on NT.  Typing
TAB fixes the case of everything you typed to match the shortest name
that can be completed.


git-svn-id: file:///fltk/svn/fltk/branches/branch-1.0@1158 ea41ed52-d2ee-0310-a9c1-e6b18d33e121
2000-06-03 08:37:09 +00:00

637 lines
18 KiB
C++

//
// "$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 <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;
}
#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; i<n && tolower((*q)->d_name[i])==tolower((*r)->d_name[i]); i++) {}
#else
for (i=0; i<n && (*q)->d_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 $".
//