/*
* Copyright (c) 2005-2008 Hypertriton, Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
* USE OF THIS SOFTWARE EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include
#include
#include "file_dlg.h"
#include
#include
#include
#include
#include
#include
#include "icons.h"
AG_FileDlg *
AG_FileDlgNew(void *parent, Uint flags)
{
AG_FileDlg *fd;
fd = Malloc(sizeof(AG_FileDlg));
AG_ObjectInit(fd, &agFileDlgClass);
fd->flags |= flags;
if (flags & AG_FILEDLG_HFILL) { AG_ExpandHoriz(fd); }
if (flags & AG_FILEDLG_VFILL) { AG_ExpandVert(fd); }
if (flags & AG_FILEDLG_MULTI) { fd->tlFiles->flags |= AG_TLIST_MULTI; }
AG_ObjectAttach(parent, fd);
return (fd);
}
AG_FileDlg *
AG_FileDlgNewMRU(void *parent, const char *mruKey, Uint flags)
{
char savePath[AG_PATHNAME_MAX];
AG_FileDlg *fd;
fd = AG_FileDlgNew(parent, flags);
AG_CopyCfgString("save-path", savePath, sizeof(savePath));
AG_FileDlgSetDirectoryMRU(fd, mruKey, savePath);
return (fd);
}
void
AG_FileDlgSetOptionContainer(AG_FileDlg *fd, void *ctr)
{
AG_ObjectLock(fd);
fd->optsCtr = ctr;
AG_ObjectUnlock(fd);
}
static int
AG_FilenameCompare(const void *p1, const void *p2)
{
const char *s1 = *(const void **)p1;
const char *s2 = *(const void **)p2;
return (strcmp(s1, s2));
}
static void
RefreshListing(AG_FileDlg *fd)
{
AG_TlistItem *it;
AG_FileInfo info;
AG_Dir *dir;
char **dirs, **files;
size_t i, ndirs = 0, nfiles = 0;
if ((dir = AG_OpenDir(fd->cwd)) == NULL) {
AG_TextMsg(AG_MSG_ERROR, "%s: %s", fd->cwd, AG_GetError());
return;
}
dirs = Malloc(sizeof(char *));
files = Malloc(sizeof(char *));
AG_ObjectLock(fd->tlDirs);
AG_ObjectLock(fd->tlFiles);
for (i = 0; i < dir->nents; i++) {
char path[AG_FILENAME_MAX];
Strlcpy(path, fd->cwd, sizeof(path));
Strlcat(path, AG_PATHSEP, sizeof(path));
Strlcat(path, dir->ents[i], sizeof(path));
if (AG_FileDlgAtRoot(fd) && strcmp(dir->ents[i], "..")==0) {
continue;
}
if (AG_GetFileInfo(path, &info) == -1) {
continue;
}
if (info.type == AG_FILE_DIRECTORY) {
dirs = Realloc(dirs, (ndirs + 1) * sizeof(char *));
dirs[ndirs++] = Strdup(dir->ents[i]);
} else {
files = Realloc(files, (nfiles + 1) * sizeof(char *));
files[nfiles++] = Strdup(dir->ents[i]);
}
}
qsort(dirs, ndirs, sizeof(char *), AG_FilenameCompare);
qsort(files, nfiles, sizeof(char *), AG_FilenameCompare);
AG_TlistClear(fd->tlDirs);
AG_TlistClear(fd->tlFiles);
for (i = 0; i < ndirs; i++) {
it = AG_TlistAdd(fd->tlDirs, agIconDirectory.s, "%s", dirs[i]);
it->cat = "dir";
it->p1 = it;
Free(dirs[i]);
}
for (i = 0; i < nfiles; i++) {
it = AG_TlistAdd(fd->tlFiles, agIconDoc.s, "%s", files[i]);
it->cat = "file";
it->p1 = it;
Free(files[i]);
}
Free(dirs);
Free(files);
AG_TlistRestore(fd->tlDirs);
AG_TlistRestore(fd->tlFiles);
AG_ObjectUnlock(fd->tlFiles);
AG_ObjectUnlock(fd->tlDirs);
AG_CloseDir(dir);
}
static void
DirSelected(AG_Event *event)
{
AG_Tlist *tl = AG_SELF();
AG_FileDlg *fd = AG_PTR(1);
AG_TlistItem *ti;
AG_ObjectLock(fd);
AG_ObjectLock(tl);
if ((ti = AG_TlistSelectedItem(tl)) != NULL) {
if (AG_FileDlgSetDirectory(fd, ti->text) == -1) {
AG_TextMsg(AG_MSG_ERROR, "%s", AG_GetError());
} else {
AG_PostEvent(NULL, fd, "dir-selected", NULL);
RefreshListing(fd);
}
}
AG_ObjectUnlock(tl);
AG_ObjectUnlock(fd);
}
static void
ChooseFile(AG_FileDlg *fd, AG_Window *pwin)
{
AG_TlistItem *it;
AG_ObjectLock(fd);
if ((it = AG_TlistSelectedItem(fd->comTypes->list)) != NULL) {
AG_FileType *ft = it->p1;
if (ft->action != NULL) {
AG_PostEvent(NULL, fd, ft->action->name, "%s,%p",
fd->cfile, ft);
}
AG_PostEvent(NULL, fd, "file-chosen", "%s,%p", fd->cfile, ft);
} else {
AG_PostEvent(NULL, fd, "file-chosen", "%s,%p", fd->cfile, NULL);
}
if (fd->flags & AG_FILEDLG_CLOSEWIN) {
/* AG_PostEvent(NULL, pwin, "window-close", NULL); */
AG_ViewDetach(pwin);
}
AG_ObjectUnlock(fd);
}
static void
ReplaceFileConfirm(AG_Event *event)
{
AG_FileDlg *fd = AG_PTR(1);
AG_Window *qwin = AG_PTR(2);
AG_Window *pwin = AG_PTR(3);
ChooseFile(fd, pwin);
AG_ViewDetach(qwin);
}
static void
ReplaceFileDlg(AG_FileDlg *fd, AG_Window *pwin)
{
AG_Window *win;
AG_Button *btn;
AG_HBox *hb;
win = AG_WindowNew(AG_WINDOW_NORESIZE|AG_WINDOW_NOTITLE);
AG_WindowSetPosition(win, AG_WINDOW_CENTER, 0);
AG_LabelNew(win, 0, _("File %s exists. Overwrite?"), fd->cfile);
hb = AG_HBoxNew(win, AG_HBOX_HOMOGENOUS|AG_HBOX_HFILL);
{
AG_ButtonNewFn(hb, 0, _("Yes"),
ReplaceFileConfirm, "%p,%p,%p", fd, win, pwin);
btn = AG_ButtonNewFn(hb, 0, _("Cancel"), AGWINDETACH(win));
AG_WidgetFocus(btn);
}
// AG_WindowAttach(pwin, win);
AG_WindowShow(win);
}
int
AG_FileDlgCheckReadAccess(AG_FileDlg *fd)
{
AG_FileInfo info;
AG_ObjectLock(fd);
if (AG_GetFileInfo(fd->cfile, &info) == -1) {
goto fail;
}
if ((info.perms & AG_FILE_READABLE) == 0) {
AG_SetError(_("%s: Read permission denied"), fd->cfile);
goto fail;
}
AG_ObjectUnlock(fd);
return (0);
fail:
AG_ObjectUnlock(fd);
return (-1);
}
int
AG_FileDlgCheckWriteAccess(AG_FileDlg *fd)
{
AG_FileInfo info;
AG_ObjectLock(fd);
if (AG_GetFileInfo(fd->cfile, &info) == -1) {
goto fail;
}
if ((info.perms & AG_FILE_WRITEABLE) == 0) {
AG_SetError(_("%s: Write permission denied"), fd->cfile);
goto fail;
}
AG_ObjectUnlock(fd);
return (0);
fail:
AG_ObjectUnlock(fd);
return (-1);
}
static void
CheckAccessAndChoose(AG_FileDlg *fd)
{
AG_Window *pwin = AG_WidgetParentWindow(fd);
char *s;
for (s = &fd->cfile[0]; *s != '\0'; s++) {
if (!isspace(*s))
break;
}
if (*s == '\0')
return;
if (fd->flags & AG_FILEDLG_LOAD) {
if (AG_FileDlgCheckReadAccess(fd) == -1) {
AG_TextMsg(AG_MSG_ERROR, "%s", AG_GetError());
return;
}
} else if (fd->flags & AG_FILEDLG_SAVE) {
AG_FileInfo info;
/*
* Display a "replace file?" dialog if this file
* already exists.
*/
if (AG_GetFileInfo(fd->cfile, &info) == 0) {
if (info.perms & AG_FILE_WRITEABLE) {
ReplaceFileDlg(fd, pwin);
} else {
AG_TextMsg(AG_MSG_ERROR,
_("%s: File exists and is non-writeable"),
fd->cfile);
}
return;
}
}
ChooseFile(fd, pwin);
}
static void
FileSelected(AG_Event *event)
{
AG_Tlist *tl = AG_SELF();
AG_FileDlg *fd = AG_PTR(1);
AG_TlistItem *ti;
char *ext;
AG_ObjectLock(tl);
if ((ti = AG_TlistSelectedItem(tl)) != NULL) {
AG_FileDlgSetFilename(fd, "%s", ti->text);
AG_PostEvent(NULL, fd, "file-selected", "%s", fd->cfile);
}
AG_ObjectUnlock(tl);
if ((ext = strrchr(fd->cfile, '.')) != NULL) {
AG_ObjectLock(fd->comTypes->list);
TAILQ_FOREACH(ti, &fd->comTypes->list->items, items) {
AG_FileType *ft = ti->p1;
char *ftext;
Uint i;
for (i = 0; i < ft->nexts; i++) {
if ((ftext = strrchr(ft->exts[i], '.'))
== NULL) {
continue;
}
if (Strcasecmp(ftext, ext) == 0)
break;
}
if (i < ft->nexts) {
AG_ComboSelect(fd->comTypes, ti);
AG_PostEvent(NULL, fd->comTypes,
"combo-selected", "%p", ti);
break;
}
}
AG_ObjectUnlock(fd->comTypes->list);
}
}
static void
FileDblClicked(AG_Event *event)
{
AG_Tlist *tl = AG_SELF();
AG_FileDlg *fd = AG_PTR(1);
AG_TlistItem *itFile;
AG_ObjectLock(fd);
AG_ObjectLock(tl);
if ((itFile = AG_TlistSelectedItem(tl)) != NULL) {
AG_FileDlgSetFilename(fd, "%s", itFile->text);
if (fd->okAction != NULL) {
AG_PostEvent(NULL, fd, fd->okAction->name, "%s,%p",
fd->cfile,
AG_TlistSelectedItemPtr(fd->comTypes->list));
} else {
CheckAccessAndChoose(fd);
}
}
AG_ObjectUnlock(tl);
AG_ObjectUnlock(fd);
}
static void
PressedOK(AG_Event *event)
{
AG_FileDlg *fd = AG_PTR(1);
AG_ObjectLock(fd);
if (fd->okAction != NULL) {
AG_PostEvent(NULL, fd, fd->okAction->name, "%s", fd->cfile);
} else {
CheckAccessAndChoose(fd);
}
AG_ObjectUnlock(fd);
}
static int
ProcessFilename(char *file, size_t len)
{
char *end = &file[strlen(file)-1];
char *s;
/* Remove trailing whitespaces. */
while ((end >= file) && *end == ' ') {
*end = '\0';
end--;
}
if (file[0] == '\0')
return (-1);
/* Remove leading whitespaces. */
for (s = file; *s == ' '; s++)
;;
if (s > file) {
memmove(file, s, end-s+2);
end -= (s-file);
}
if (file[0] == '\0')
return (-1);
/* Treat the root specially. */
if (strcmp(file, AG_PATHSEP) == 0)
return (0);
/* Remove trailing path separators. */
if (*end == AG_PATHSEPCHAR) {
*end = '\0';
end--;
}
return (0);
}
static void
SetFilename(AG_FileDlg *fd, const char *file)
{
if (file[0] == '/') {
Strlcpy(fd->cfile, file, sizeof(fd->cfile));
} else {
Strlcpy(fd->cfile, fd->cwd, sizeof(fd->cfile));
if (!AG_FileDlgAtRoot(fd) &&
(fd->cfile[0] != '\0' &&
fd->cfile[strlen(fd->cfile)-1] != AG_PATHSEPCHAR)) {
Strlcat(fd->cfile, AG_PATHSEP, sizeof(fd->cfile));
}
Strlcat(fd->cfile, file, sizeof(fd->cfile));
}
}
static void
TextboxChanged(AG_Event *event)
{
char path[AG_PATHNAME_MAX];
AG_Textbox *tb = AG_SELF();
AG_FileDlg *fd = AG_PTR(1);
AG_ObjectLock(fd);
AG_TextboxCopyString(tb, path, sizeof(path));
SetFilename(fd, path);
AG_ObjectUnlock(fd);
}
static void
TextboxReturn(AG_Event *event)
{
char file[AG_PATHNAME_MAX];
AG_Textbox *tb = AG_SELF();
AG_FileDlg *fd = AG_PTR(1);
AG_FileInfo info;
int endSep;
AG_ObjectLock(fd);
AG_TextboxCopyString(tb, file, sizeof(file));
if (file[0] == '\0') {
goto out;
}
endSep = (file[strlen(file)-1]==AG_PATHSEPCHAR) ? 1 : 0;
if (ProcessFilename(file, sizeof(file)) == -1) {
goto out;
}
AG_TextboxPrintf(tb, "%s", file);
if (endSep ||
(AG_GetFileInfo(file,&info)==0 && info.type == AG_FILE_DIRECTORY)) {
if (AG_FileDlgSetDirectory(fd, file) == 0) {
RefreshListing(fd);
} else {
AG_TextMsg(AG_MSG_ERROR, "%s", AG_GetError());
goto out;
}
} else {
AG_FileDlgSetFilename(fd, file);
CheckAccessAndChoose(fd);
}
out:
AG_ObjectUnlock(fd);
}
static void
PressedCancel(AG_Event *event)
{
AG_FileDlg *fd = AG_PTR(1);
AG_Window *pwin;
AG_ObjectLock(fd);
if (fd->cancelAction != NULL) {
AG_PostEvent(NULL, fd, fd->cancelAction->name, NULL);
} else if (fd->flags & AG_FILEDLG_CLOSEWIN) {
if ((pwin = AG_WidgetParentWindow(fd)) != NULL) {
/* AG_PostEvent(NULL, pwin, "window-close", NULL); */
AG_ViewDetach(pwin);
}
}
AG_ObjectUnlock(fd);
}
static void
SelectedType(AG_Event *event)
{
AG_FileDlg *fd = AG_PTR(1);
AG_TlistItem *it = AG_PTR(2);
AG_FileType *ft = (it != NULL) ? it->p1 : TAILQ_FIRST(&fd->types);
AG_FileOption *fo;
AG_Numerical *num;
AG_Textbox *tbox;
AG_ObjectLock(fd);
if (fd->optsCtr == NULL) {
AG_ObjectUnlock(fd);
return;
}
AG_ObjectFreeChildren(fd->optsCtr);
TAILQ_FOREACH(fo, &ft->opts, opts) {
switch (fo->type) {
case AG_FILEDLG_BOOL:
AG_CheckboxNewInt(fd->optsCtr, 0, fo->descr,
&fo->data.i.val);
break;
case AG_FILEDLG_INT:
num = AG_NumericalNew(fd->optsCtr, 0, NULL, fo->descr);
AG_BindInt(num, "value", &fo->data.i.val);
AG_NumericalSetRangeInt(num, fo->data.i.min,
fo->data.i.max);
break;
case AG_FILEDLG_FLOAT:
num = AG_NumericalNew(fd->optsCtr, 0, fo->unit,
fo->descr);
AG_BindFloat(num, "value", &fo->data.flt.val);
AG_NumericalSetRangeDbl(num, fo->data.flt.min,
fo->data.flt.max);
break;
case AG_FILEDLG_DOUBLE:
num = AG_NumericalNew(fd->optsCtr, 0, fo->unit,
fo->descr);
AG_BindDouble(num, "value", &fo->data.dbl.val);
AG_NumericalSetRange(num, fo->data.dbl.min,
fo->data.dbl.max);
break;
case AG_FILEDLG_STRING:
tbox = AG_TextboxNew(fd->optsCtr, 0, fo->descr);
AG_TextboxBindUTF8(tbox, fo->data.s,
sizeof(fo->data.s));
break;
default:
break;
}
}
AG_ObjectUnlock(fd);
AG_WindowUpdate(AG_ParentWindow(fd));
}
static void
WidgetShown(AG_Event *event)
{
AG_FileDlg *fd = AG_SELF();
AG_TlistItem *it;
int w, wMax = 0, nItems = 0;
AG_WidgetFocus(fd->tbFile);
RefreshListing(fd);
AG_PostEvent(NULL, fd->comTypes, "combo-selected", "%p", NULL);
AG_COMBO_FOREACH(it, fd->comTypes) {
AG_TextSize(it->text, &w, NULL);
if (w > wMax) { wMax = w; }
nItems++;
}
AG_ComboSizeHintPixels(fd->comTypes, wMax, nItems);
}
/*
* Evaluate whether the cwd is the filesystem root. Return value is only
* valid as long as the FileDlg is locked.
*/
int
AG_FileDlgAtRoot(AG_FileDlg *fd)
{
int rv;
AG_ObjectLock(fd);
rv = (fd->cwd[0] == AG_PATHSEPCHAR && fd->cwd[1] == '\0');
AG_ObjectUnlock(fd);
return (rv);
}
/* Move to the specified directory. */
int
AG_FileDlgSetDirectory(AG_FileDlg *fd, const char *dir)
{
AG_FileInfo info;
char ncwd[AG_PATHNAME_MAX], *c;
AG_ObjectLock(fd);
if (dir[0] == '.' && dir[1] == '\0') {
if (AG_GetCWD(ncwd, sizeof(ncwd)) == -1) {
AG_SetError("%s", AG_GetError());
goto fail;
}
} else if (dir[0] == '.' && dir[1] == '.' && dir[2] == '\0') {
if (!AG_FileDlgAtRoot(fd)) {
Strlcpy(ncwd, fd->cwd, sizeof(ncwd));
if ((c = strrchr(ncwd, AG_PATHSEPCHAR)) != NULL) {
*c = '\0';
}
if (c == &ncwd[0]) {
ncwd[0] = AG_PATHSEPCHAR;
ncwd[1] = '\0';
}
}
} else if (dir[0] != AG_PATHSEPCHAR) {
Strlcpy(ncwd, fd->cwd, sizeof(ncwd));
if (!(ncwd[0] == AG_PATHSEPCHAR &&
ncwd[1] == '\0')) {
Strlcat(ncwd, AG_PATHSEP, sizeof(ncwd));
}
Strlcat(ncwd, dir, sizeof(ncwd));
} else {
Strlcpy(ncwd, dir, sizeof(ncwd));
}
if (AG_GetFileInfo(ncwd, &info) == -1) {
goto fail;
}
if (info.type != AG_FILE_DIRECTORY) {
AG_SetError(_("%s: Not a directory"), ncwd);
goto fail;
}
if ((info.perms & (AG_FILE_READABLE|AG_FILE_EXECUTABLE)) == 0) {
AG_SetError(_("%s: Permission denied"), ncwd);
goto fail;
}
if (Strlcpy(fd->cwd, ncwd, sizeof(fd->cwd)) >= sizeof(fd->cwd)) {
AG_SetError(_("Path is too large: `%s'"), ncwd);
goto fail;
}
if (fd->dirMRU != NULL) {
AG_SetCfgString(fd->dirMRU, "%s", fd->cwd);
AG_ConfigSave();
}
AG_TlistScrollToStart(fd->tlDirs);
AG_TlistScrollToStart(fd->tlFiles);
AG_ObjectUnlock(fd);
return (0);
fail:
AG_ObjectUnlock(fd);
return (-1);
}
void
AG_FileDlgSetDirectoryMRU(AG_FileDlg *fd, const char *key, const char *dflt)
{
char *s;
AG_ObjectLock(fd);
if (AG_Defined(agConfig,key) && (s = AG_GetStringDup(agConfig,key))) {
AG_FileDlgSetDirectory(fd, s);
Free(s);
} else {
AG_SetCfgString(key, "%s", dflt);
if (AG_ConfigSave() == -1) {
Verbose("Saving MRU: %s\n", AG_GetError());
}
AG_FileDlgSetDirectory(fd, dflt);
}
fd->dirMRU = Strdup(key);
AG_ObjectUnlock(fd);
}
void
AG_FileDlgSetFilename(AG_FileDlg *fd, const char *fmt, ...)
{
char file[AG_FILENAME_MAX];
va_list ap;
va_start(ap, fmt);
Vsnprintf(file, sizeof(file), fmt, ap);
va_end(ap);
AG_ObjectLock(fd);
SetFilename(fd, file);
AG_TextboxPrintf(fd->tbFile, "%s", file);
AG_TextboxSetCursorPos(fd->tbFile, -1);
AG_ObjectUnlock(fd);
}
static void
Init(void *obj)
{
AG_FileDlg *fd = obj;
fd->flags = 0;
fd->cfile[0] = '\0';
fd->dirMRU = NULL;
if (AG_GetCWD(fd->cwd, sizeof(fd->cwd)) == -1) {
fprintf(stderr, "%s: %s", fd->cwd, AG_GetError());
}
fd->optsCtr = NULL;
TAILQ_INIT(&fd->types);
fd->hPane = AG_PaneNewHoriz(fd, AG_PANE_EXPAND);
fd->tlDirs = AG_TlistNew(fd->hPane->div[0], AG_TLIST_EXPAND);
fd->tlFiles = AG_TlistNew(fd->hPane->div[1], AG_TLIST_EXPAND);
fd->lbCwd = AG_LabelNewPolled(fd, AG_LABEL_HFILL,
_("Directory: %s"), &fd->cwd[0]);
AG_LabelSizeHint(fd->lbCwd, 1,
_("Directory: XXXXXXXXXXXXX"));
fd->tbFile = AG_TextboxNew(fd, 0, _("File: "));
fd->comTypes = AG_ComboNew(fd, AG_COMBO_HFILL, _("Type: "));
AG_TlistSizeHint(fd->tlDirs, "XXXXXXXXXXXXXX", 8);
AG_TlistSizeHint(fd->tlFiles, "XXXXXXXXXXXXXXXXXX", 8);
fd->btnOk = AG_ButtonNew(fd, 0, _("OK"));
fd->btnCancel = AG_ButtonNew(fd, 0, _("Cancel"));
fd->okAction = NULL;
fd->cancelAction = NULL;
AG_SetEvent(fd, "widget-shown", WidgetShown, NULL);
AG_SetEvent(fd->tlDirs, "tlist-dblclick", DirSelected, "%p", fd);
AG_SetEvent(fd->tlFiles, "tlist-selected", FileSelected, "%p", fd);
AG_SetEvent(fd->tlFiles, "tlist-dblclick", FileDblClicked, "%p", fd);
AG_SetEvent(fd->tbFile, "textbox-postchg", TextboxChanged, "%p", fd);
AG_SetEvent(fd->tbFile, "textbox-return", TextboxReturn, "%p", fd);
AG_SetEvent(fd->btnOk, "button-pushed", PressedOK, "%p", fd);
AG_SetEvent(fd->btnCancel, "button-pushed", PressedCancel, "%p", fd);
AG_SetEvent(fd->comTypes, "combo-selected", SelectedType, "%p", fd);
}
/*
* Register an event handler for the "OK" button. Overrides type-specific
* handlers.
*/
void
AG_FileDlgOkAction(AG_FileDlg *fd, AG_EventFn fn, const char *fmt, ...)
{
AG_ObjectLock(fd);
if (fd->okAction != NULL) {
AG_UnsetEvent(fd, fd->okAction->name);
}
fd->okAction = AG_SetEvent(fd, NULL, fn, NULL);
AG_EVENT_GET_ARGS(fd->okAction, fmt);
#ifdef AG_THREADS
if (fd->flags & AG_FILEDLG_ASYNC)
fd->okAction->flags |= AG_EVENT_ASYNC;
#endif
AG_ObjectUnlock(fd);
}
/* Register an event handler for the "Cancel" button. */
void
AG_FileDlgCancelAction(AG_FileDlg *fd, AG_EventFn fn, const char *fmt, ...)
{
AG_ObjectLock(fd);
if (fd->cancelAction != NULL) {
AG_UnsetEvent(fd, fd->cancelAction->name);
}
fd->cancelAction = AG_SetEvent(fd, NULL, fn, NULL);
AG_EVENT_GET_ARGS(fd->cancelAction, fmt);
AG_ObjectUnlock(fd);
}
static void
Destroy(void *obj)
{
AG_FileDlg *fd = obj;
AG_FileType *ft, *ft2;
AG_FileOption *fo, *fo2;
Uint i;
for (ft = TAILQ_FIRST(&fd->types);
ft != TAILQ_END(&fd->types);
ft = ft2) {
ft2 = TAILQ_NEXT(ft, types);
for (fo = TAILQ_FIRST(&ft->opts);
fo != TAILQ_END(&ft->opts);
fo = fo2) {
fo2 = TAILQ_NEXT(fo, opts);
Free(fo);
}
for (i = 0; i < ft->nexts; i++) {
Free(ft->exts[i]);
}
Free(ft->exts);
Free(ft);
}
Free(fd->dirMRU);
}
static void
Draw(void *obj)
{
AG_Widget *chld;
WIDGET_FOREACH_CHILD(chld, obj)
AG_WidgetDraw(chld);
}
static void
SizeRequest(void *obj, AG_SizeReq *r)
{
AG_FileDlg *fd = obj;
AG_SizeReq rChld, rOk, rCancel;
AG_WidgetSizeReq(fd->hPane, &rChld);
r->w = rChld.w;
r->h = rChld.h+4;
AG_WidgetSizeReq(fd->lbCwd, &rChld);
r->h += rChld.h+1;
AG_WidgetSizeReq(fd->tbFile, &rChld);
r->h += rChld.h+2;
AG_WidgetSizeReq(fd->comTypes, &rChld);
r->h += rChld.h+2;
AG_WidgetSizeReq(fd->btnOk, &rOk);
AG_WidgetSizeReq(fd->btnCancel, &rCancel);
r->h += MAX(rOk.h,rCancel.h)+1;
}
static int
SizeAllocate(void *obj, const AG_SizeAlloc *a)
{
AG_FileDlg *fd = obj;
AG_SizeReq r;
AG_SizeAlloc aChld;
int hBtn = 0, wBtn = a->w/2;
AG_WidgetSizeReq(fd->btnOk, &r);
hBtn = MAX(hBtn, r.h);
AG_WidgetSizeReq(fd->btnCancel, &r);
hBtn = MAX(hBtn, r.h);
/* Size horizontal pane */
aChld.x = 0;
aChld.y = 0;
aChld.w = a->w;
aChld.h = a->h - hBtn - 10;
AG_WidgetSizeReq(fd->lbCwd, &r);
aChld.h -= r.h;
AG_WidgetSizeReq(fd->tbFile, &r);
aChld.h -= r.h;
AG_WidgetSizeReq(fd->comTypes, &r);
aChld.h -= r.h;
AG_WidgetSizeAlloc(fd->hPane, &aChld);
aChld.y += aChld.h+4;
/* Size cwd label. */
AG_WidgetSizeReq(fd->lbCwd, &r);
aChld.h = r.h;
AG_WidgetSizeAlloc(fd->lbCwd, &aChld);
aChld.y += aChld.h+1;
/* Size entry textbox. */
AG_WidgetSizeReq(fd->tbFile, &r);
aChld.h = r.h;
AG_WidgetSizeAlloc(fd->tbFile, &aChld);
aChld.y += aChld.h+2;
/* Size type selector */
AG_WidgetSizeReq(fd->comTypes, &r);
aChld.h = r.h;
AG_WidgetSizeAlloc(fd->comTypes, &aChld);
aChld.y += aChld.h+2;
/* Size buttons */
aChld.w = wBtn;
aChld.h = hBtn;
AG_WidgetSizeAlloc(fd->btnOk, &aChld);
aChld.x = wBtn;
if (wBtn*2 < a->w) { aChld.w++; }
aChld.h = hBtn;
AG_WidgetSizeAlloc(fd->btnCancel, &aChld);
return (0);
}
/* Register a new file type. */
AG_FileType *
AG_FileDlgAddType(AG_FileDlg *fd, const char *descr, const char *exts,
void (*fn)(AG_Event *), const char *fmt, ...)
{
AG_FileType *ft;
char *dexts, *ds, *ext;
AG_TlistItem *it;
ft = Malloc(sizeof(AG_FileType));
ft->fd = fd;
ft->descr = descr;
ft->exts = Malloc(sizeof(char *));
ft->nexts = 0;
TAILQ_INIT(&ft->opts);
ds = dexts = Strdup(exts);
while ((ext = AG_Strsep(&ds, ",;")) != NULL) {
ft->exts = Realloc(ft->exts, (ft->nexts+1)*sizeof(char *));
ft->exts[ft->nexts++] = Strdup(ext);
}
Free(dexts);
AG_ObjectLock(fd);
if (fn != NULL) {
ft->action = AG_SetEvent(fd, NULL, fn, NULL);
AG_EVENT_GET_ARGS(ft->action, fmt);
#ifdef AG_THREADS
if (fd->flags & AG_FILEDLG_ASYNC)
ft->action->flags |= AG_EVENT_ASYNC;
#endif
} else {
ft->action = NULL;
}
it = AG_TlistAdd(fd->comTypes->list, NULL, "%s (%s)", descr, exts);
it->p1 = ft;
if (TAILQ_EMPTY(&fd->types)) {
AG_ComboSelectPointer(fd->comTypes, ft);
}
TAILQ_INSERT_TAIL(&fd->types, ft, types);
AG_ObjectUnlock(fd);
return (ft);
}
AG_FileOption *
AG_FileOptionNewBool(AG_FileType *ft, const char *descr, const char *key,
int dflt)
{
AG_FileOption *fto;
fto = Malloc(sizeof(AG_FileOption));
fto->descr = descr;
fto->key = key;
fto->unit = NULL;
fto->type = AG_FILEDLG_BOOL;
fto->data.i.val = dflt;
AG_ObjectLock(ft->fd);
TAILQ_INSERT_TAIL(&ft->opts, fto, opts);
AG_ObjectUnlock(ft->fd);
return (fto);
}
AG_FileOption *
AG_FileOptionNewInt(AG_FileType *ft, const char *descr, const char *key,
int dflt, int min, int max)
{
AG_FileOption *fto;
fto = Malloc(sizeof(AG_FileOption));
fto->descr = descr;
fto->key = key;
fto->unit = NULL;
fto->type = AG_FILEDLG_INT;
fto->data.i.val = dflt;
fto->data.i.min = min;
fto->data.i.max = max;
AG_ObjectLock(ft->fd);
TAILQ_INSERT_TAIL(&ft->opts, fto, opts);
AG_ObjectUnlock(ft->fd);
return (fto);
}
AG_FileOption *
AG_FileOptionNewFlt(AG_FileType *ft, const char *descr, const char *key,
float dflt, float min, float max, const char *unit)
{
AG_FileOption *fto;
fto = Malloc(sizeof(AG_FileOption));
fto->descr = descr;
fto->key = key;
fto->unit = NULL;
fto->type = AG_FILEDLG_FLOAT;
fto->data.flt.val = dflt;
fto->data.flt.min = min;
fto->data.flt.max = max;
AG_ObjectLock(ft->fd);
TAILQ_INSERT_TAIL(&ft->opts, fto, opts);
AG_ObjectUnlock(ft->fd);
return (fto);
}
AG_FileOption *
AG_FileOptionNewDbl(AG_FileType *ft, const char *descr, const char *key,
double dflt, double min, double max, const char *unit)
{
AG_FileOption *fto;
fto = Malloc(sizeof(AG_FileOption));
fto->descr = descr;
fto->key = key;
fto->unit = unit;
fto->type = AG_FILEDLG_DOUBLE;
fto->data.dbl.val = dflt;
fto->data.dbl.min = min;
fto->data.dbl.max = max;
AG_ObjectLock(ft->fd);
TAILQ_INSERT_TAIL(&ft->opts, fto, opts);
AG_ObjectUnlock(ft->fd);
return (fto);
}
AG_FileOption *
AG_FileOptionNewString(AG_FileType *ft, const char *descr, const char *key,
const char *dflt)
{
AG_FileOption *fto;
fto = Malloc(sizeof(AG_FileOption));
fto->descr = descr;
fto->key = key;
fto->unit = NULL;
fto->type = AG_FILEDLG_STRING;
Strlcpy(fto->data.s, dflt, sizeof(fto->data.s));
AG_ObjectLock(ft->fd);
TAILQ_INSERT_TAIL(&ft->opts, fto, opts);
AG_ObjectUnlock(ft->fd);
return (fto);
}
/* The FileDlg must be locked. */
AG_FileOption *
AG_FileOptionGet(AG_FileType *ft, const char *key)
{
AG_FileOption *fo;
TAILQ_FOREACH(fo, &ft->opts, opts) {
if (strcmp(fo->key, key) == 0)
break;
}
return (fo);
}
int
AG_FileOptionInt(AG_FileType *ft, const char *key)
{
AG_FileOption *fo;
int rv;
AG_ObjectLock(ft->fd);
fo = AG_FileOptionGet(ft, key);
rv = (fo != NULL) ? fo->data.i.val : -1;
AG_ObjectUnlock(ft->fd);
return (rv);
}
float
AG_FileOptionFlt(AG_FileType *ft, const char *key)
{
AG_FileOption *fo;
float rv;
AG_ObjectLock(ft->fd);
fo = AG_FileOptionGet(ft, key);
rv = (fo != NULL) ? fo->data.flt.val : 0.0;
AG_ObjectUnlock(ft->fd);
return (rv);
}
double
AG_FileOptionDbl(AG_FileType *ft, const char *key)
{
AG_FileOption *fo;
double rv;
AG_ObjectLock(ft->fd);
fo = AG_FileOptionGet(ft, key);
rv = (fo != NULL) ? fo->data.dbl.val : 0.0;
AG_ObjectUnlock(ft->fd);
return (rv);
}
char *
AG_FileOptionString(AG_FileType *ft, const char *key)
{
AG_FileOption *fo;
char *rv;
AG_ObjectLock(ft->fd);
fo = AG_FileOptionGet(ft, key);
rv = (fo != NULL) ? fo->data.s : "";
AG_ObjectUnlock(ft->fd);
return (rv);
}
AG_WidgetClass agFileDlgClass = {
{
"Agar(Widget:FileDlg)",
sizeof(AG_FileDlg),
{ 0,0 },
Init,
NULL, /* free */
Destroy,
NULL, /* load */
NULL, /* save */
NULL /* edit */
},
Draw,
SizeRequest,
SizeAllocate
};