We need to include some headers first:
#include <string.h> // some standard C and C++ include files
#include <stdlib.h>
#include <iostream.h>
#include <hush/event.h> // to handle hush events
#include <hush/string.h> // hush string class
#include "fileselector.h" // The fileselector class header
Options to configure the widgets:
static const char* frameopts= "-relief raised -bd 1";
static const char* entryopts= "-width 40 -relief sunken -bg white";
Options to pack the widgets:
static const char* label_pack_opts = "-side left -padx 10 -pady 10";
static const char* entry_pack_opts = "-side right -padx 10 -pady 10 "
"-fill x -expand true";
fileselector::fileselector() {
_filename[0] = _pathname[0] = _mask[0] = 0;
}
The 'real' constructor follows below.
The use of _register() is explained in the tutorial on garbage
collection.
fileselector::fileselector(const char *p, const char* options, int istop) : frame(p,0) {
_filename[0] = _pathname[0] = _mask[0] = 0;
widget* top;
if (istop) top = new toplevel(p,options);
else top = new frame(p,options);
_register(top);
build_gui(top, options); // set up GUI
bindings(); // set up bindings
mask("*"); // set default mask
dirpath("."); // set default directory
}
fileselector::fileselector(const widget *parent, const char *p, const char* options, int istop) : frame(parent, p, 0) {
_filename[0] = _pathname[0] = _mask[0] = 0;
widget* top=0; // top will be deleted automatically by parent
if (istop) top = new toplevel(parent,p,options);
else top = new frame(parent,p,options);
top->rename(); // rename top widget (see tutorial)
tk->bind(name(), this); // bind this to tcl command name
build_gui(top, options); // set up GUI
bindings(); // set up bindings
mask("*"); // set default mask
dirpath("."); // set default directory
}
The destructor. Now we have garbage collection for widgets,
it is no longer needed ...
fileselector::~fileselector() { }
const char* fileselector::get() {
grab(); // grab mouse pointer
wait(); // wait until the user has made his choise
return _filename[0] ? _filename : 0; // contains filename or null
}
const char* fileselector::mask() const { return _mask; }
void fileselector::mask(const char *m) { // set mask
strcpy(_mask,m); // save mask
_maskentry->del(); // delete entry
_maskentry->insert(_mask); // insert new mask
rescan();
}
const char* fileselector::dirpath() const { return _pathname; }
int fileselector::dirpath(const char *p) {
sprintf(buf, "cd %s", p);
if (tk->eval(buf) == OK) { // chdir
strcpy(_pathname, tk->evaluate("pwd")); // save path
_pathentry->del(); // delete entry
_pathentry->insert(_pathname); // insert new path
rescan(); // rescan directory
return OK;
} else {
sprintf(buf, "Could not chdir to %s", p);
tk->error(buf);
return ERROR;
}
}
This function does a rescan, which is needed to update the widgets after the directory path or mask has changed.
Most of the work is done by evaluating raw Tcl commands. This is a bit tricky since they use (global) Tcl variables ... The glob -nocomplain .* * command returns a list of file and directory names of the current working directory. The foreach command is used to loop over this list. Only the directories are put the listbox, so they will appear before all other files.
Another glob command is used to retrieve a list of all filenames matching the pattern in _mask. This list is sorted by the lsort command and again, we use a foreach command to insert the filenames in the listbox.
void fileselector::rescan() {
_listbox ->del(); // delete listbox
// put all directories in listbox
sprintf(buf,"foreach i [glob -nocomplain .* * ] "
"{ if [file isdirectory i }}",
_listbox->path() );
tk->eval(buf);
// put files in
sprintf(buf,"set fileselector::fill [glob -nocomplain %s]", _mask);
tk->eval(buf);
// put files from variable in listbox
sprintf(buf,"foreach i [lsort {fileselector::fill}] "
"{ if [file isfile i }}",
_listbox->path() );
tk->eval(buf);
}
We will put everything in a widget called "top". This can be a toplevel or a frame widget. Note: we need access to some of the widgets (the listbox, buttons and entries) in other member functions as well. These widget are defines as instance variables in the header file. The other widgets are defined as local variables in this function.
void fileselector::build_gui(widget* top, const char* opts) {
// Need some frames to pack things right. All need the same options.
// Three frames for the three entries and their labels
// (file, path and mask), one frame for the listbox and the
// associated scrollbar, and finally one to frame the buttons:
frame* _fileframe = new frame(top, "fileframe", frameopts);
frame* _maskframe = new frame(top, "maskframe", frameopts);
frame* _pathframe = new frame(top, "pathframe", frameopts);
frame* _boxframe = new frame(top, "boxframe", frameopts);
frame* _buttonframe = new frame(top, "buttonframe",frameopts);
// Now we can create the file, path and mask entry boxes.
_fileentry = new entry(_fileframe, "fileentry", entryopts);
_pathentry = new entry(_pathframe, "pathentry", entryopts);
_maskentry = new entry(_maskframe, "maskentry", entryopts);
// Create labels to tell user what all these entries mean:
label* _filelabel = new label(_fileframe, "filelabel", "-width 5");
label* _masklabel = new label(_maskframe, "masklabel", "-width 5");
label* _pathlabel = new label(_pathframe, "pathlabel", "-width 5");
_filelabel->text("File:");
_masklabel->text("Mask:");
_pathlabel->text("Path:");
// Create a listbox to show the files of the current dir.
// We need a scrollbar for large directories.
_listbox = new listbox(_boxframe, "box", "-width 40 -height 10");
scrollbar* _scrollbar = new scrollbar(_boxframe, "scroll");
_scrollbar->yview(_listbox);
_listbox->yscroll(_scrollbar);
// An Ok and Cancel button are pretty basic as well:
_okbutton = new button(_buttonframe, "ok");
_okbutton->text("Ok");
_cancelbutton = new button(_buttonframe, "cancel");
_cancelbutton->text("Cancel");
// Pack all widgets.
// The listbox, scrollbar and the associated frame are a
// bit special: they should fill and expand if the
// fileselector window is resized by the user.
// The entries only expand in the x dimension.
// The buttonframe is packed before the boxframe to avoid
// that the buttons disappear when the window gets to small.
// See packer tutorial for more info...
_fileframe->pack();
_maskframe->pack();
_pathframe->pack();
_buttonframe->pack("-side bottom -fill x");
_boxframe ->pack("-fill both -expand true");
_filelabel->pack(label_pack_opts);
_masklabel->pack(label_pack_opts);
_pathlabel->pack(label_pack_opts);
_fileentry->pack(entry_pack_opts);
_maskentry->pack(entry_pack_opts);
_pathentry->pack(entry_pack_opts);
_scrollbar->pack("-side right -fill y -expand false");
_listbox ->pack("-side left -fill both -expand true");
_cancelbutton->pack("-side right -padx 10 -pady 10");
_okbutton ->pack("-side left -padx 10 -pady 10");
options(opts); // process options
redirect(top); // Redirect self() to top widget
}
void fileselector::bindings() {
_cancelbutton->bind(this, "Cancel"); // cancel pressed
_okbutton->bind(this, "Ok"); // ok button pressed
_listbox->bind(this, "Select-Double" ); // double click is default binding
_listbox->bind("<Button-1>", this, "Select" ); // single click
_fileentry->bind("<Return>", this, "Ok") ; // like pressing ok
_maskentry->bind("<Return>", this, "MaskReturn") ; // new mask
_pathentry->bind("<Return>", this, "PathReturn") ; // new path
}
hush> fileselector .f # Generates event hush> ...The statement should create a new fileselector called .f
widget* fileselector::create(int argc, char* argv[]) { // create new widget
fileselector *f = new fileselector(argv[0]);
f->options(flatten(--argc, ++argv)); // process options
tk->result(f->path()); // return path to tk
_register(f); // register for deletion
return f;
}
int fileselector::operator()() {
int argc = _event->argc(); char **argv = _event->argv();
string first = argv[0];
if (first == type()) {
create(--argc, ++argv);
return OK;
}
string s = _event->arg(1);
if (s == "Select" || s == "Select-Double") {
// A select on the listbox:
_fileentry->del(); // delete old filename
int index = _listbox->selection();
char* sel = _listbox->get(index);
char* selection = new char[strlen(sel) +1];
strcpy(selection, sel);
// Perhaps the selection is a directory ...
sprintf(buf,"file isdirectory \"%s\"", selection);
int isdir = atoi(tk->evaluate(buf));
if (isdir) {
if (s == "Select-Double") dirpath(selection);
} else {
_fileentry->insert(selection);
if (s == "Select-Double") {
strcpy(_filename, _fileentry->get());
destroy();
}
}
delete selection;
} else if (s == "Cancel") {
strcpy(_filename, ""); // Empty string on cancel
destroy();
} else if (s == "Ok") {
strcpy(_filename, _fileentry->get());
destroy();
} else if (s == "MaskReturn") {
// A [Return] in the mask entry. Update mask and listbox
mask(_maskentry->get());
} else if (s == "PathReturn") {
// A [Return] in the path entry. Change directory.
dirpath(_pathentry->get());
} else if (s == "get") {
tk->result(get()); // Pass selected filename to toolkit
} else if (s == "rescan") {
rescan();
} else self()->eval(quote(--argc, ++argv));
return OK;
}
void fileselector::options(const char*) {
// not implemented ...
}
int fileselector::usage(const char* msg) const {
cerr << type() << ": " << msg << endl;
cerr << "Usage: " << endl;
cerr << type() << " <pathname> [options]" << endl;
cerr << "Or: " << endl;
cerr << "<pathname> get" << endl;
cerr << "<pathname> rescan" << endl;
return ERROR;
}