/*
 * Copyright (C) 2000-2007 Carsten Haitzler, Geoff Harrison and various contributors
 * Copyright (C) 2004-2023 Kim Woelders
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies of the Software, its documentation and marketing & publicity
 * materials, and acknowledgment shall be given in the documentation, materials
 * and software packages that this Software was used.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
#include "E.h"
#if ENABLE_DIALOGS
#include <X11/Xlib.h>
#include "dialog.h"
#include "eimage.h"
#include "ewins.h"
#include "hints.h"
#include "iclass.h"
#include "list.h"
#include "tclass.h"
#include "timers.h"
#include "xwin.h"

#define DEBUG_DIALOGS 0

typedef struct {
    char            horizontal;

    char            numeric;

    int             upper;
    int             lower;
    int             unit;
    int             jump;
    int            *val_ptr;

    int             min_length;

    int             base_orig_w, base_orig_h;
    int             knob_orig_w, knob_orig_h;

    int             base_x, base_y, base_w, base_h;
    int             knob_x, knob_y, knob_w, knob_h;
    int             numeric_x, numeric_y, numeric_w, numeric_h;

    ImageClass     *ic_base;
    ImageClass     *ic_knob;

    char            in_drag;

    Win             base_win;
    Win             knob_win;
} DItemSlider;

typedef struct {
    Win             area_win;
    int             w, h;
    DialogItemCallbackFunc *init_func;
    DialogItemCallbackFunc *event_func;
} DItemArea;

typedef struct {
    Win             check_win;
    int             orig_w, orig_h;
    char            onoff;
    char           *onoff_ptr;
} DItemCheckButton;

typedef struct {
    char           *image;
} DItemImage;

typedef struct {
    char            horizontal;
} DItemSeparator;

typedef struct {
    int             num_columns;
    char            border;
    char            homogenous_h;
    char            homogenous_v;
    int             num_items;
    DItem         **items;
} DItemTable;

typedef struct {
    Win             radio_win;
    int             orig_w, orig_h;
    char            onoff;
    int             val;
    int            *val_ptr;
    DItem          *next;
    DItem          *first;
} DItemRadioButton;

struct _ditem {
    int             type;
    Dialog         *dlg;
    DialogCallbackFunc *func;
    int             val;
    void           *data;
    ImageClass     *iclass;
    TextClass      *tclass;
    EImageBorder    padding;
    char            fill_h;
    char            fill_v;
    char            do_close;
    int             align_h;
    int             align_v;
    int             row_span;
    int             col_span;

    int             x, y, w, h;
    Win             win;
    char           *text;
    union {
        DItemCheckButton check_button;
        DItemTable      table;
        DItemImage      image;
        DItemSeparator  separator;
        DItemRadioButton radio_button;
        DItemSlider     slider;
        DItemArea       area;
    } item;

    char            realized;
    char            update;

    char            state;
    char            hilited;
    char            clicked;
};

typedef struct {
    EX_KeyCode      keycode;
    DialogCallbackFunc *func;
    int             val;
    void           *data;
} DKeyBind;

#define DD_SIZE 800             /* Extra dialog data size */

struct _dialog {
    dlist_t         list;
    EWin           *ewin;
    Win             win;
    int             w, h;
    char           *name;
    char           *title;
    PmapMask        pmm_bg;
    TextClass      *tclass;
    ImageClass     *iclass;
    DItem          *item;
    DialogExitFunc *exit_func;
    int             num_bindings;
    DKeyBind       *keybindings;

    char            redraw;
    char            update;
    char            resize;
    char            close;
    char            set_title;
    int             xu1, yu1, xu2, yu2;
    int             dd[DD_SIZE / sizeof(int)];
};

static EWin    *FindEwinByDialog(Dialog * d);
static int      FindADialog(void);

static void     DialogHandleEvents(Win win, XEvent * ev, void *prm);
static void     DItemHandleEvents(Win win, XEvent * ev, void *prm);

static void     DialogItemsRealize(Dialog * d);
static void     DialogItemDestroy(DItem * di, int clean);
static void     DialogDrawItems(Dialog * d, DItem * di, int x, int y, int w,
                                int h);

static int      DialogItemCheckButtonGetState(DItem * di);

static void     DialogUpdate(Dialog * d);

static void     DialogAddFooter(Dialog * d, DItem * parent,
                                int flags, DialogCallbackFunc * cb);
static void     DialogAddHeader(Dialog * d, DItem * parent,
                                const char *img, const char *txt);

static          LIST_HEAD(dialog_list);

static char     dialog_update_pending = 0;

void
DialogBindKey(Dialog *d, const char *key, DialogCallbackFunc *func, int val,
              void *data)
{
    d->num_bindings++;
    d->keybindings = EREALLOC(DKeyBind, d->keybindings, d->num_bindings);
    d->keybindings[d->num_bindings - 1].val = val;
    d->keybindings[d->num_bindings - 1].func = func;
    d->keybindings[d->num_bindings - 1].data = data;
    d->keybindings[d->num_bindings - 1].keycode = EKeynameToKeycode(key);
}

void
DialogKeybindingsDestroy(Dialog *d)
{
    EFREE_NULL(d->keybindings);
    d->num_bindings = 0;
}

Dialog         *
DialogCreate(const char *name)
{
    Dialog         *d;

    d = ECALLOC(Dialog, 1);
    if (!d)
        return NULL;

    LIST_APPEND(Dialog, &dialog_list, d);

    d->name = Estrdup(name);
    d->win = ECreateClientWindow(VROOT, -20, -20, 2, 2);
    EventCallbackRegister(d->win, DialogHandleEvents, d);

    d->iclass = ImageclassAlloc("DIALOG", 1);
    d->tclass = TextclassAlloc("DIALOG", 1);

    d->xu1 = d->yu1 = 99999;
    d->xu2 = d->yu2 = 0;

    return d;
}

static void
DialogDestroy(Dialog *d)
{
    LIST_REMOVE(Dialog, &dialog_list, d);

    Efree(d->name);
    Efree(d->title);
    DialogKeybindingsDestroy(d);
    if (d->item)
        DialogItemDestroy(d->item, 0);
    ImageclassFree(d->iclass);
    TextclassFree(d->tclass);

    PmapMaskFree(&d->pmm_bg);

    Efree(d);
}

static int
_DialogMatchName(const void *data, const void *match)
{
    return strcmp(((const Dialog *)data)->name, (const char *)match);
}

Dialog         *
DialogFind(const char *name)
{
    return LIST_FIND(Dialog, &dialog_list, _DialogMatchName, name);
}

void
DialogSetTitle(Dialog *d, const char *title)
{
    EFREE_DUP(d->title, title);
    d->set_title = 1;
}

void
DialogSetExitFunction(Dialog *d, DialogExitFunc *func)
{
    d->exit_func = func;
}

void
DialogCallExitFunction(Dialog *d)
{
    if (d->exit_func)
        d->exit_func(d);
}

void           *
DialogDataGet(Dialog *d)
{
    return d->dd;
}

DItem          *
DialogItemAddButton(DItem *parent, const char *text, DialogCallbackFunc *func,
                    int val, char doclose, int image __UNUSED__)
{
    DItem          *di;

    di = DialogAddItem(parent, DITEM_BUTTON);
    DialogItemSetText(di, text);
    DialogItemSetCallback(di, func, 0, NULL);
    di->val = val;
    di->do_close = doclose;

    return di;
}

void
DialogRedraw(Dialog *d)
{
    if ((!d->tclass) || (!d->iclass))
        return;

#if DEBUG_DIALOGS
    Eprintf("%s: win=%#lx pmap=%#lx\n", __func__,
            WinGetXwin(d->win), WinGetPmap(d->win));
#endif

    PmapMaskFree(&d->pmm_bg);
    ImageclassApplyCopy(d->iclass, d->win, d->w, d->h, 0, 0, STATE_NORMAL,
                        &(d->pmm_bg), IC_FLAG_FULL_SIZE);
    if (d->pmm_bg.pmap == NoXID)
        return;

    EGetWindowBackgroundPixmap(d->win);
    EXCopyArea(d->pmm_bg.pmap, WinGetPmap(d->win), 0, 0, d->w, d->h, 0, 0);

    d->redraw = 1;

    DialogDrawItems(d, d->item, 0, 0, 99999, 99999);
}

static void
_DialogEwinInit(EWin *ewin)
{
    Dialog         *d = (Dialog *) ewin->data;

    d->ewin = ewin;

    EwinSetTitle(ewin, d->title);
    EwinSetClass(ewin, d->name, "Enlightenment_Dialog");
    d->set_title = 0;

    ewin->props.focus_when_mapped = 1;
    ewin->props.ignorearrange = 1;

    EoSetSticky(ewin, 1);
    EoSetLayer(ewin, 10);
}

static void
_DialogEwinMoveResize(EWin *ewin, int resize __UNUSED__)
{
    Dialog         *d = (Dialog *) ewin->data;

    if (!d || Mode.mode != MODE_NONE || !EoIsShown(ewin))
        return;
}

static void
_DialogEwinClose(EWin *ewin)
{
    DialogDestroy((Dialog *) ewin->data);
    ewin->data = NULL;
}

static const EWinOps _DialogEwinOps = {
    _DialogEwinInit,
    NULL,
    _DialogEwinMoveResize,
    _DialogEwinClose,
};

void
DialogArrange(Dialog *d, int resize)
{
    if (resize)
        DialogItemsRealize(d);

    if (d->set_title)
    {
        EwinSetTitle(d->ewin, d->title);
        d->set_title = 0;
    }

    ICCCM_SetSizeConstraints(d->ewin, d->w, d->h, d->w, d->h, 0, 0, 1, 1,
                             0.f, 65535.f);

    if (resize)
    {
        EwinResize(d->ewin, d->w, d->h, 0);
        d->resize = 1;
        DialogRedraw(d);
        DialogUpdate(d);
        d->resize = 0;
    }
}

static void
DialogShowArranged(Dialog *d, int center)
{
    EWin           *ewin;

    ewin = FindEwinByDialog(d);
    if (ewin)
    {
        EwinRaise(ewin);
        EwinShow(ewin);
        return;
    }

    DialogItemsRealize(d);

    ewin = AddInternalToFamily(d->win, "DIALOG", EWIN_TYPE_DIALOG,
                               &_DialogEwinOps, d);
    if (!ewin)
        return;

    DialogArrange(d, 0);

    ewin->client.event_mask |= KeyPressMask;
    ESelectInput(d->win, ewin->client.event_mask);

    if (ewin->state.placed)
    {
        EwinMoveResize(ewin, EoGetX(ewin), EoGetY(ewin), d->w, d->h, 0);
    }
    else
    {
        EwinResize(ewin, d->w, d->h, 0);
        if (center || FindADialog() == 1)
            ArrangeEwinCentered(ewin);
        else
            ArrangeEwin(ewin);
    }

    DialogRedraw(d);
    DialogUpdate(d);
    EwinShow(ewin);
}

void
DialogShow(Dialog *d)
{
    DialogShowArranged(d, 0);
}

void
DialogShowCentered(Dialog *d)
{
    DialogShowArranged(d, 1);
}

void
DialogClose(Dialog *d)
{
    d->close = 1;
}

static void
_DialogClose(Dialog *d)
{
    if (!d)
        return;

    DialogCallExitFunction(d);

    EwinHide(d->ewin);
}

void
DialogShowSimple(const DialogDef *dd, void *data)
{
    DialogShowSimpleWithName(dd, dd->name, data);
}

void
DialogFill(Dialog *d, DItem *parent, const DialogDef *dd, void *data)
{
    DItem          *content;

    if (Conf.dialogs.headers && (dd->header_image || dd->header_text))
        DialogAddHeader(d, parent, dd->header_image, _(dd->header_text));

    content = DialogAddItem(parent, DITEM_TABLE);
    if (!content)
        return;

    memset(d->dd, 0, dd->dd_size);
    if (dd->dd_size <= DD_SIZE)
        dd->fill(d, content, data);

    if (dd->func_apply)
        DialogAddFooter(d, parent, dd->flags, dd->func_apply);

    DialogSetExitFunction(d, dd->func_exit);

    SoundPlay(dd->sound);
}

void
DialogShowSimpleWithName(const DialogDef *dd, const char *name, void *data)
{
    Dialog         *d;
    DItem          *table;

    d = DialogFind(name);
    if (d)
    {
        SoundPlay(SOUND_SETTINGS_ACTIVE);
        DialogShow(d);
        return;
    }

    d = DialogCreate(name);
    if (!d)
        return;

    DialogSetTitle(d, _(dd->title));

    table = DialogInitItem(d);
    if (!table)
        return;

    DialogFill(d, table, dd, data);

    DialogShow(d);
}

static DItem   *
DialogItemCreate(int type)
{
    DItem          *di;

    di = ECALLOC(DItem, 1);
    if (!di)
        return di;

    di->type = type;
    di->fill_h = 1;
    di->fill_v = 0;
    di->align_h = 0;
    di->align_v = 512;
    di->row_span = 1;
    di->col_span = 1;
    di->item.table.num_columns = 1;

    DialogItemSetPadding(di, 2, 2, 2, 2);

    return di;
}

DItem          *
DialogInitItem(Dialog *d)
{
    DItem          *di;

    if (d->item)
        return NULL;

    di = DialogItemCreate(DITEM_TABLE);
    d->item = di;
    if (!di)
        return di;

    di->dlg = d;
    di->item.table.num_columns = 1;

    return di;
}

DItem          *
DialogAddItem(DItem *dii, int type)
{
    DItem          *di;

    di = DialogItemCreate(type);
    if (!di)
        return di;

    switch (di->type)
    {
    default:
        break;
    case DITEM_AREA:
        di->item.area.w = 32;
        di->item.area.h = 32;
        break;
    case DITEM_CHECKBUTTON:
        di->item.check_button.onoff = 0;
        di->item.check_button.onoff_ptr = &(di->item.check_button.onoff);
        di->item.check_button.orig_w = 10;
        di->item.check_button.orig_h = 10;
        break;
    case DITEM_TEXT:
        di->fill_h = 0;
        break;
    case DITEM_TABLE:
        di->item.table.num_columns = 1;
        break;
    case DITEM_IMAGE:
        di->item.image.image = NULL;
        break;
    case DITEM_SEPARATOR:
        di->item.separator.horizontal = 0;
        break;
    case DITEM_RADIOBUTTON:
        di->item.radio_button.orig_w = 10;
        di->item.radio_button.orig_h = 10;
        break;
    case DITEM_SLIDER:
        di->item.slider.horizontal = 1;
        di->item.slider.upper = 100;
        di->item.slider.lower = 0;
        di->item.slider.unit = 10;
        di->item.slider.jump = 20;
        di->item.slider.min_length = 64;
        di->item.slider.base_orig_w = 10;
        di->item.slider.base_orig_h = 10;
        di->item.slider.knob_orig_w = 6;
        di->item.slider.knob_orig_h = 6;
        break;
    }

    if (dii)
    {
        dii->item.table.num_items++;
        dii->item.table.items =
            EREALLOC(DItem *, dii->item.table.items, dii->item.table.num_items);
        dii->item.table.items[dii->item.table.num_items - 1] = di;
        di->dlg = dii->dlg;
    }

    return di;
}

static void
DialogAddHeader(Dialog *d __UNUSED__, DItem *parent, const char *img,
                const char *txt)
{
    DItem          *table, *di;

    table = DialogAddItem(parent, DITEM_TABLE);
    DialogItemTableSetOptions(table, 2, 0, 0, 0);
    DialogItemSetAlign(table, 512, 0);
    DialogItemSetFill(table, 0, 0);

    di = DialogAddItem(table, DITEM_IMAGE);
    DialogItemImageSetFile(di, img);

    di = DialogAddItem(table, DITEM_TEXT);
    DialogItemSetText(di, txt);

    DialogAddItem(parent, DITEM_SEPARATOR);
}

static void
DialogAddFooter(Dialog *d, DItem *parent, int flags, DialogCallbackFunc *cb)
{
    DItem          *table;
    int             n_buttons;

    if (!(flags & DLG_NO_SEPARATOR))
        DialogAddItem(parent, DITEM_SEPARATOR);

    table = DialogAddItem(parent, DITEM_TABLE);
    DialogItemSetAlign(table, 512, 0);
    DialogItemSetFill(table, 0, 0);

    n_buttons = 0;
    if (flags & 1)
    {
        DialogItemAddButton(table, _("OK"), cb, 0, 1, DLG_BUTTON_OK);
        n_buttons++;
    }
    if (flags & 2)
    {
        DialogItemAddButton(table, _("Apply"), cb, 0, 0, DLG_BUTTON_APPLY);
        DialogBindKey(d, "Return", cb, 0, NULL);
        n_buttons++;
    }
    if (flags & 4)
    {
        DialogItemAddButton(table, _("Close"), NULL, 0, 1, DLG_BUTTON_CLOSE);
        DialogBindKey(d, "Escape", DialogCallbackClose, 0, NULL);
        n_buttons++;
    }

    DialogItemTableSetOptions(table, n_buttons, 0, 1, 0);
}

Dialog         *
DialogItemGetDialog(DItem *di)
{
    return di->dlg;
}

void
DialogItemSetCallback(DItem *di, DialogCallbackFunc *func, int val, void *data)
{
    di->func = func;
    di->val = val;
    di->data = data;
}

void
DialogItemSetPadding(DItem *di, int left, int right, int top, int bottom)
{
    di->padding.left = left;
    di->padding.right = right;
    di->padding.top = top;
    di->padding.bottom = bottom;
}

void
DialogItemSetFill(DItem *di, char fill_h, char fill_v)
{
    di->fill_h = fill_h;
    di->fill_v = fill_v;
}

void
DialogItemSetAlign(DItem *di, int align_h, int align_v)
{
    di->align_h = align_h;
    di->align_v = align_v;
}

void
DialogItemSetRowSpan(DItem *di, int row_span)
{
    di->row_span = row_span;
}

void
DialogItemSetColSpan(DItem *di, int col_span)
{
    di->col_span = col_span;
}

void
DialogItemCallCallback(Dialog *d, DItem *di)
{
    if (di->func)
        di->func(d, di->val, di->data);
}

static void
DialogMoveItemBy(Dialog *d, DItem *di, int dx, int dy)
{
    int             i;
    EImageBorder   *pad;

    di->x += dx;
    di->y += dy;

    if (di->win)
        EMoveResizeWindow(di->win, di->x, di->y, di->w, di->h);

    switch (di->type)
    {
    case DITEM_TABLE:
        for (i = 0; i < di->item.table.num_items; i++)
            DialogMoveItemBy(d, di->item.table.items[i], dx, dy);
        break;
    case DITEM_AREA:
        pad = ImageclassGetPadding(di->iclass);
        di->item.area.w = di->w - (pad->left + pad->right);
        di->item.area.h = di->h - (pad->top + pad->bottom);
        EMoveResizeWindow(di->item.area.area_win,
                          pad->left, pad->top,
                          di->item.area.w, di->item.area.h);
        break;
    case DITEM_CHECKBUTTON:
        EMoveResizeWindow(di->item.check_button.check_win,
                          di->x,
                          di->y +
                          (di->h - di->item.check_button.orig_h) / 2,
                          di->item.check_button.orig_w,
                          di->item.check_button.orig_h);
        break;
    case DITEM_RADIOBUTTON:
        EMoveResizeWindow(di->item.radio_button.radio_win,
                          di->x,
                          di->y +
                          (di->h -
                           di->item.radio_button.orig_h) / 2,
                          di->item.radio_button.orig_w,
                          di->item.radio_button.orig_h);
        break;
    case DITEM_SLIDER:
        di->item.slider.base_x = 0;
        di->item.slider.base_y = 0;
        di->item.slider.base_w = di->w;
        di->item.slider.base_h = di->h;
        di->item.slider.knob_w = di->item.slider.knob_orig_w;
        di->item.slider.knob_h = di->item.slider.knob_orig_h;
        if (di->item.slider.base_win)
            EMoveResizeWindow(di->item.slider.base_win,
                              di->x + di->item.slider.base_x,
                              di->y + di->item.slider.base_y,
                              di->item.slider.base_w, di->item.slider.base_h);
        if (di->item.slider.knob_win)
            EMoveResizeWindow(di->item.slider.knob_win,
                              di->x + di->item.slider.knob_x,
                              di->y + di->item.slider.knob_y,
                              di->item.slider.knob_w, di->item.slider.knob_h);
        if (di->win)
            EMoveResizeWindow(di->win,
                              di->x + di->item.slider.numeric_x,
                              di->y + di->item.slider.numeric_y,
                              di->item.slider.numeric_w,
                              di->item.slider.numeric_h);
        break;
    }
}

static void
DialogRealizeItem(Dialog *d, DItem *di)
{
    const char     *iclass, *tclass;
    int             iw = 0, ih = 0;
    int             register_win_callback;
    EImage         *im;
    EImageBorder   *pad;

    if (di->realized && di->type != DITEM_TABLE)
        return;
    di->realized = 1;

    iclass = tclass = NULL;
    if (di->type == DITEM_BUTTON)
    {
        iclass = "DIALOG_WIDGET_BUTTON";
        tclass = iclass;
    }
    else if (di->type == DITEM_CHECKBUTTON)
    {
        iclass = "DIALOG_WIDGET_CHECK_BUTTON";
        tclass = iclass;
    }
    else if (di->type == DITEM_TEXT)
    {
        tclass = "DIALOG_WIDGET_TEXT";
    }
    else if (di->type == DITEM_SEPARATOR)
    {
        iclass = "DIALOG_WIDGET_SEPARATOR";
    }
    else if (di->type == DITEM_RADIOBUTTON)
    {
        iclass = "DIALOG_WIDGET_RADIO_BUTTON";
        tclass = iclass;
    }
#if 0
    else if (di->type == DITEM_SLIDER)
    {
        iclass = NULL;
    }
#endif
    else if (di->type == DITEM_AREA)
    {
        iclass = "DIALOG_WIDGET_AREA";
    }

    if (!di->iclass && iclass)
    {
        di->iclass = ImageclassAlloc(iclass, 1);
        if (!di->iclass)
        {
            di->type = DITEM_NONE;
            return;
        }
    }

    if (!di->tclass && tclass)
    {
        di->tclass = TextclassAlloc(tclass, 1);
        if (!di->tclass)
        {
            di->type = DITEM_NONE;
            return;
        }
    }

    if (di->type == DITEM_TABLE)
    {
        int             i;

        for (i = 0; i < di->item.table.num_items; i++)
            DialogRealizeItem(d, di->item.table.items[i]);
    }

    register_win_callback = 1;

    switch (di->type)
    {
    case DITEM_SLIDER:
        if (di->item.slider.numeric)
        {
            di->win = ECreateWindow(d->win, -20, -20, 2, 2, 0);
            EMapWindow(di->win);
            ESelectInput(di->win,
                         EnterWindowMask | LeaveWindowMask |
                         ButtonPressMask | ButtonReleaseMask);
        }
        di->item.slider.base_win = ECreateWindow(d->win, -20, -20, 2, 2, 0);
        EMapWindow(di->item.slider.base_win);
        di->item.slider.knob_win = ECreateWindow(d->win, -20, -20, 2, 2, 0);
        EMapWindow(di->item.slider.knob_win);
        ESelectInput(di->item.slider.base_win,
                     EnterWindowMask | LeaveWindowMask |
                     ButtonPressMask | ButtonReleaseMask);
        EventCallbackRegister(di->item.slider.base_win, DItemHandleEvents, di);
        ESelectInput(di->item.slider.knob_win,
                     EnterWindowMask | LeaveWindowMask |
                     ButtonPressMask | ButtonReleaseMask | PointerMotionMask);
        EventCallbackRegister(di->item.slider.knob_win, DItemHandleEvents, di);

        if (!di->item.slider.ic_base)
        {
            if (di->item.slider.horizontal)
                di->item.slider.ic_base =
                    ImageclassAlloc("DIALOG_WIDGET_SLIDER_BASE_HORIZONTAL", 1);
            else
                di->item.slider.ic_base =
                    ImageclassAlloc("DIALOG_WIDGET_SLIDER_BASE_VERTICAL", 1);
        }
        im = ImageclassGetImage(di->item.slider.ic_base, 0, 0, 0);
        if (im)
        {
            EImageGetSize(im, &di->item.slider.base_orig_w,
                          &di->item.slider.base_orig_h);
            EImageFree(im);
        }

        if (!di->item.slider.ic_knob)
        {
            if (di->item.slider.horizontal)
                di->item.slider.ic_knob =
                    ImageclassAlloc("DIALOG_WIDGET_SLIDER_KNOB_HORIZONTAL", 1);
            else
                di->item.slider.ic_knob =
                    ImageclassAlloc("DIALOG_WIDGET_SLIDER_KNOB_VERTICAL", 1);
        }
        im = ImageclassGetImage(di->item.slider.ic_knob, 0, 0, 0);
        if (im)
        {
            EImageGetSize(im, &di->item.slider.knob_orig_w,
                          &di->item.slider.knob_orig_h);
            EImageFree(im);
        }

        pad = ImageclassGetPadding(di->item.slider.ic_base);
        if (di->item.slider.horizontal)
        {
            iw = di->item.slider.min_length + pad->left + pad->right;
            ih = di->item.slider.base_orig_h;
        }
        else
        {
            iw = di->item.slider.base_orig_w;
            ih = di->item.slider.min_length + pad->top + pad->bottom;
        }
        di->w = iw;
        di->h = ih;
        break;
    case DITEM_BUTTON:
        pad = ImageclassGetPadding(di->iclass);
        TextSize(di->tclass, 0, 0, STATE_NORMAL, di->text, &iw, &ih);
        iw += pad->left + pad->right;
        ih += pad->top + pad->bottom;
        di->win = ECreateWindow(d->win, -20, -20, 2, 2, 0);
        EMapWindow(di->win);
        ESelectInput(di->win,
                     EnterWindowMask | LeaveWindowMask | ButtonPressMask |
                     ButtonReleaseMask);
        di->w = iw;
        di->h = ih;
        break;
    case DITEM_AREA:
        pad = ImageclassGetPadding(di->iclass);
        iw = di->item.area.w;
        ih = di->item.area.h;
        iw += pad->left + pad->right;
        ih += pad->top + pad->bottom;
        di->win = ECreateWindow(d->win, -20, -20, 2, 2, 0);
        EMapWindow(di->win);
        di->item.area.area_win = ECreateWindow(di->win, -20, -20, 2, 2, 0);
        EMapWindow(di->item.area.area_win);
        ESelectInput(di->item.area.area_win,
                     EnterWindowMask | LeaveWindowMask | ButtonPressMask |
                     ButtonReleaseMask | PointerMotionMask);
        EventCallbackRegister(di->item.area.area_win, DItemHandleEvents, di);
        di->w = iw;
        di->h = ih;
        break;
    case DITEM_CHECKBUTTON:
        pad = ImageclassGetPadding(di->iclass);
        im = ImageclassGetImage(di->iclass, 0, 0, 0);
        if (im)
        {
            EImageGetSize(im, &di->item.check_button.orig_w,
                          &di->item.check_button.orig_h);
            EImageFree(im);
        }
        TextSize(di->tclass, 0, 0, STATE_NORMAL, di->text, &iw, &ih);
        if (ih < di->item.check_button.orig_h)
            ih = di->item.check_button.orig_h;
        iw += di->item.check_button.orig_w + pad->left;
        di->item.check_button.check_win =
            ECreateWindow(d->win, -20, -20, 2, 2, 0);
        di->win = ECreateEventWindow(d->win, -20, -20, 2, 2);
        EMapWindow(di->item.check_button.check_win);
        EMapWindow(di->win);
        ESelectInput(di->win,
                     EnterWindowMask | LeaveWindowMask | ButtonPressMask |
                     ButtonReleaseMask);
        di->w = iw;
        di->h = ih;
        break;
    case DITEM_TEXT:
        TextSize(di->tclass, 0, 0, STATE_NORMAL, di->text, &iw, &ih);
        di->w = iw;
        di->h = ih;
        break;
    case DITEM_IMAGE:
        im = ThemeImageLoad(di->item.image.image);
        if (im)
        {
            EImageGetSize(im, &iw, &ih);
            EImageFree(im);
        }
        di->w = iw;
        di->h = ih;
        register_win_callback = 0;
        break;
    case DITEM_SEPARATOR:
        pad = ImageclassGetPadding(di->iclass);
        iw = pad->left + pad->right;
        ih = pad->top + pad->bottom;
        di->win = ECreateWindow(d->win, -20, -20, 2, 2, 0);
        EMapWindow(di->win);
        di->w = iw;
        di->h = ih;
        register_win_callback = 0;
        break;
    case DITEM_RADIOBUTTON:
        pad = ImageclassGetPadding(di->iclass);
        im = ImageclassGetImage(di->iclass, 0, 0, 0);
        if (im)
        {
            EImageGetSize(im, &di->item.radio_button.orig_w,
                          &di->item.radio_button.orig_h);
            EImageFree(im);
        }
        TextSize(di->tclass, 0, 0, STATE_NORMAL, di->text, &iw, &ih);
        if (ih < di->item.radio_button.orig_h)
            ih = di->item.radio_button.orig_h;
        iw += di->item.radio_button.orig_w + pad->left;
        di->item.radio_button.radio_win =
            ECreateWindow(d->win, -20, -20, 2, 2, 0);
        di->win = ECreateEventWindow(d->win, -20, -20, 2, 2);
        EMapWindow(di->item.radio_button.radio_win);
        EMapWindow(di->win);
        ESelectInput(di->win,
                     EnterWindowMask | LeaveWindowMask | ButtonPressMask |
                     ButtonReleaseMask);
        di->w = iw;
        di->h = ih;
        break;
    case DITEM_TABLE:
        {
            int             cols, rows;
            int             i, r, c, x, y;
            int            *col_size, *row_size;

            cols = di->item.table.num_columns;
            rows = 1;
            if (cols <= 0)
                break;

            if (d->item == di)
            {
                /* Top-level item */
                pad = ImageclassGetPadding(d->iclass);
                di->x = pad->left;
                di->y = pad->top;
            }

            col_size = ECALLOC(int, cols);
            row_size = ECALLOC(int, rows);

            if (!col_size || !row_size)
                goto bail_out;

            r = c = 0;
            for (i = 0; i < di->item.table.num_items; i++)
            {
                DItem          *dii;
                int             w, h, j, ww;

                dii = di->item.table.items[i];
                w = dii->w + (dii->padding.left + dii->padding.right);
                h = dii->h + (dii->padding.top + dii->padding.bottom);
                ww = 0;
                for (j = 0; j < dii->col_span; j++)
                    ww += col_size[c + j];
                if (w > ww)
                {
                    ww = (w + dii->col_span - 1) / dii->col_span;
                    for (j = 0; j < dii->col_span; j++)
                        if (col_size[c + j] < ww)
                            col_size[c + j] = ww;
                }
                if (h > row_size[r])
                    row_size[r] = h;
                c += dii->col_span;
                if (c >= cols)
                {
                    int            *pi;

                    c = 0;
                    r++;
                    rows++;

                    pi = EREALLOC(int, row_size, rows);
                    if (!pi)
                        goto bail_out;
                    row_size = pi;

                    row_size[rows - 1] = 0;
                }
            }

            if (di->item.table.homogenous_h)
            {
                int             max = 0;

                for (i = 0; i < cols; i++)
                {
                    if (col_size[i] > max)
                        max = col_size[i];
                }
                for (i = 0; i < cols; i++)
                    col_size[i] = max;
            }
            if (di->item.table.homogenous_v)
            {
                int             max = 0;

                for (i = 0; i < rows; i++)
                {
                    if (row_size[i] > max)
                        max = row_size[i];
                }
                for (i = 0; i < rows; i++)
                    row_size[i] = max;
            }

            iw = ih = 0;
            for (i = 0; i < cols; i++)
                iw += col_size[i];
            for (i = 0; i < rows; i++)
                ih += row_size[i];
            di->w = iw;
            di->h = ih;

            x = y = 0;
            r = c = 0;
            for (i = 0; i < di->item.table.num_items; i++)
            {
                DItem          *dii;
                int             j, sw, sh, dx, dy;

                dii = di->item.table.items[i];

                for (sw = j = 0; j < dii->col_span; j++)
                    sw += col_size[c + j];
                for (sh = j = 0; j < dii->row_span; j++)
                    sh += row_size[r + j];

                if (dii->fill_h)
                    dii->w = sw - (dii->padding.left + dii->padding.right);
                if (dii->fill_v)
                    dii->h = sh - (dii->padding.top + dii->padding.bottom);

                if (dii->w <= 0 || dii->h <= 0)
                    goto skip;

                dx = di->x + x + dii->padding.left +
                    (((sw - (dii->padding.left + dii->padding.right) - dii->w) *
                      dii->align_h) >> 10);
                dy = di->y + y + dii->padding.top +
                    (((sh - (dii->padding.top + dii->padding.bottom) - dii->h) *
                      dii->align_v) >> 10);
                dx -= dii->x;
                dy -= dii->y;
                DialogMoveItemBy(d, dii, dx, dy);

              skip:
                x += sw;
                c += dii->col_span;
                if (c >= cols)
                {
                    x = 0;
                    y += row_size[r];
                    c = 0;
                    r++;
                }
            }

          bail_out:
            Efree(col_size);
            Efree(row_size);
        }
        break;
    case DITEM_NONE:
    default:
        di->w = 0;
        di->h = 0;
        break;
    }

    if (di->win && register_win_callback)
        EventCallbackRegister(di->win, DItemHandleEvents, di);
}

static void
DialogDrawItems(Dialog *d, DItem *di, int x, int y, int w, int h)
{
    d->update = 1;
    di->update = 1;

    if (d->xu1 > x)
        d->xu1 = x;
    if (d->yu1 > y)
        d->yu1 = y;
    x += w;
    y += h;
    if (d->xu2 < x)
        d->xu2 = x;
    if (d->yu2 < y)
        d->yu2 = y;

    dialog_update_pending = 1;

#if DEBUG_DIALOGS
    Eprintf("%s: t=%d u=%d - %d,%d -> %d,%d\n", __func__, di->type, di->update,
            d->xu1, d->yu1, d->xu2, d->yu2);
#endif
}

static void
DialogDrawItem(Dialog *d, DItem *di)
{
    int             state, x, w, val;
    EImageBorder   *pad;
    EImage         *im;

    if (!di->update && di->type != DITEM_TABLE)
        return;

    if (di->x > d->xu2 || di->y > d->yu2 ||
        di->x + di->w <= d->xu1 || di->y + di->h <= d->yu1)
        goto done;

#if DEBUG_DIALOGS
    Eprintf("%s: t=%d u=%d - %d,%d -> %d,%d\n", __func__, di->type, di->update,
            d->xu1, d->yu1, d->xu2, d->yu2);
#endif

#if 0                           /* Debug */
    if (di->type == DITEM_TABLE)
    {
        XGCValues       gcv;
        GC              gc;

        pad = ImageclassGetPadding(d->iclass);
        gcv.subwindow_mode = IncludeInferiors;
        gc = EXCreateGC(WinGetPmap(d->win), GCSubwindowMode, &gcv);
        XSetForeground(disp, gc, Dpy.pixel_black);
        XDrawRectangle(disp, WinGetPmap(d->win), gc,
                       di->x, di->y, di->w, di->h);
        EXFreeGC(gc);
    }
#endif

    switch (di->type)
    {
    case DITEM_TABLE:
        {
            int             i;
            DItem          *dii;

            for (i = 0; i < di->item.table.num_items; i++)
            {
                dii = di->item.table.items[i];
                if (di->update)
                    dii->update = 1;
                DialogDrawItem(d, dii);
            }
        }
        break;

    case DITEM_SLIDER:
        val = (di->item.slider.val_ptr) ?
            *(di->item.slider.val_ptr) : di->item.slider.lower;
        if (di->item.slider.horizontal)
        {
            di->item.slider.knob_x = di->item.slider.base_x +
                (((di->item.slider.base_w - di->item.slider.knob_w) *
                  (val - di->item.slider.lower)) /
                 (di->item.slider.upper - di->item.slider.lower));
            di->item.slider.knob_y = di->item.slider.base_y +
                ((di->item.slider.base_h - di->item.slider.knob_h) / 2);
        }
        else
        {
            di->item.slider.knob_y = di->item.slider.base_y +
                (((di->item.slider.base_h - di->item.slider.knob_h) *
                  (val - di->item.slider.lower)) /
                 (di->item.slider.upper - di->item.slider.lower));
            di->item.slider.knob_x = di->item.slider.base_x +
                ((di->item.slider.base_w - di->item.slider.knob_w) / 2);
        }
        if (di->item.slider.knob_win)
            EMoveResizeWindow(di->item.slider.knob_win,
                              di->x + di->item.slider.knob_x,
                              di->y + di->item.slider.knob_y,
                              di->item.slider.knob_w, di->item.slider.knob_h);
        if (di->item.slider.base_win)
            ImageclassApply(di->item.slider.ic_base,
                            di->item.slider.base_win, 0, 0, STATE_NORMAL);
        state = STATE_NORMAL;
        if ((di->hilited) && (di->clicked))
            state = STATE_CLICKED;
        else if ((di->hilited) && (!di->clicked))
            state = STATE_HILITED;
        else if (!(di->hilited) && (di->clicked))
            state = STATE_CLICKED;
        if (di->item.slider.knob_win)
            ImageclassApply(di->item.slider.ic_knob,
                            di->item.slider.knob_win, 0, 0, state);
        break;

    case DITEM_BUTTON:
        state = STATE_NORMAL;
        if ((di->hilited) && (di->clicked))
            state = STATE_CLICKED;
        else if ((di->hilited) && (!di->clicked))
            state = STATE_HILITED;
        else if (!(di->hilited) && (di->clicked))
            state = STATE_CLICKED;
        ITApply(di->win, di->iclass, NULL, state, 0, 0,
                di->tclass, NULL, di->text, 0);
        break;

    case DITEM_AREA:
        if (!d->redraw)
            break;
        ImageclassApply(di->iclass, di->win, 0, 0, STATE_NORMAL);
        if (di->item.area.init_func)
            di->item.area.init_func(di, 0, NULL);
        break;

    case DITEM_SEPARATOR:
        if (!d->redraw)
            break;
        if (di->item.separator.horizontal)
            ImageclassApply(di->iclass, di->win, 0, 0, STATE_NORMAL);
        else
            ImageclassApply(di->iclass, di->win, 0, 0, STATE_CLICKED);
        break;

    case DITEM_TEXT:
        state = STATE_NORMAL;
        x = di->x;
        w = di->w;
        goto draw_text;

    case DITEM_IMAGE:
        im = ThemeImageLoad(di->item.image.image);
        if (im)
        {
            EImageRenderOnDrawable(im, d->win, WinGetPmap(d->win),
                                   EIMAGE_BLEND | EIMAGE_ANTI_ALIAS,
                                   di->x, di->y, di->w, di->h);
            EImageFree(im);
        }
        break;

    case DITEM_CHECKBUTTON:
        state = STATE_NORMAL;
        if ((di->hilited) && (di->clicked))
            state = STATE_CLICKED;
        else if ((di->hilited) && (!di->clicked))
            state = STATE_HILITED;
        else if (!(di->hilited) && (di->clicked))
            state = STATE_CLICKED;
        ImageclassApply(di->iclass, di->item.check_button.check_win,
                        DialogItemCheckButtonGetState(di), 0, state);
        if (!d->redraw &&
            (TextclassGetTextState(di->tclass, di->state, 0, 0) ==
             TextclassGetTextState(di->tclass, state, 0, 0)))
            break;
        pad = ImageclassGetPadding(di->iclass);
        x = di->x + di->item.check_button.orig_w + pad->left;
        w = di->w - di->item.check_button.orig_w - pad->left;
        goto draw_text;

    case DITEM_RADIOBUTTON:
        state = STATE_NORMAL;
        if ((di->hilited) && (di->clicked))
            state = STATE_CLICKED;
        else if ((di->hilited) && (!di->clicked))
            state = STATE_HILITED;
        else if (!(di->hilited) && (di->clicked))
            state = STATE_CLICKED;
        ImageclassApply(di->iclass, di->item.radio_button.radio_win,
                        di->item.radio_button.onoff, 0, state);
        if (!d->redraw &&
            (TextclassGetTextState(di->tclass, di->state, 0, 0) ==
             TextclassGetTextState(di->tclass, state, 0, 0)))
            break;
        pad = ImageclassGetPadding(di->iclass);
        x = di->x + di->item.radio_button.orig_w + pad->left;
        w = di->w - di->item.radio_button.orig_w - pad->left;
        goto draw_text;

    default:
        break;

      draw_text:
        di->state = state;
        if (!di->text || !di->tclass)
            break;
        if (!d->redraw || di->update)
            EXCopyArea(d->pmm_bg.pmap, WinGetPmap(d->win),
                       di->x, di->y, di->w, di->h, di->x, di->y);
        TextDraw(di->tclass, d->win, WinGetPmap(d->win), 0, 0, state, di->text,
                 x, di->y, w, 99999, TextclassGetJustification(di->tclass));
        break;
    }

  done:
    di->update = 0;
}

static void
DialogUpdate(Dialog *d)
{
    do
    {
        d->update = 0;
        if (d->item)
            DialogDrawItem(d, d->item);
    }
    while (d->update);
    if (d->xu1 < d->xu2 && d->yu1 < d->yu2)
        EClearArea(d->win, d->xu1, d->yu1, d->xu2 - d->xu1, d->yu2 - d->yu1);
    d->xu1 = d->yu1 = 99999;
    d->xu2 = d->yu2 = 0;
}

static void
_DialogsCheckUpdate(void *data __UNUSED__)
{
    Dialog         *d;

    if (!dialog_update_pending)
        return;
    dialog_update_pending = 0;

    LIST_FOR_EACH(Dialog, &dialog_list, d)
    {
        if (d->update)
            DialogUpdate(d);
        d->redraw = 0;
    }
}

void
DialogsInit(void)
{
    IdlerAdd(_DialogsCheckUpdate, NULL);
}

static void
DialogItemsRealize(Dialog *d)
{
    EImageBorder   *pad;

    if (!d->item)
        return;

    DialogRealizeItem(d, d->item);
    DialogDrawItems(d, d->item, 0, 0, 99999, 99999);
    pad = ImageclassGetPadding(d->iclass);
    d->w = d->item->w + pad->left + pad->right;
    d->h = d->item->h + pad->top + pad->bottom;
    EResizeWindow(d->win, d->w, d->h);
}

void
DialogItemSetText(DItem *di, const char *text)
{
    EFREE_DUP(di->text, text);

    if (di->realized)
        DialogDrawItems(di->dlg, di, di->x, di->y, di->w, di->h);
}

void
DialogItemRadioButtonSetFirst(DItem *di, DItem *first)
{
    di->item.radio_button.first = first;
    if (di == first)
        return;
    while (first->item.radio_button.next)
        first = first->item.radio_button.next;
    first->item.radio_button.next = di;
}

void
DialogItemRadioButtonGroupSetValPtr(DItem *di, int *val_ptr)
{
    while (di)
    {
        di->item.radio_button.val_ptr = val_ptr;
        if (*val_ptr == di->item.radio_button.val)
            di->item.radio_button.onoff = 1;
        di = di->item.radio_button.next;
    }
}

void
DialogItemRadioButtonGroupSetVal(DItem *di, int val)
{
    di->item.radio_button.val = val;
}

void
DialogItemCheckButtonSetState(DItem *di, char onoff)
{
    if (*(di->item.check_button.onoff_ptr) == onoff)
        return;
    *(di->item.check_button.onoff_ptr) = onoff;

    if (di->realized)
        DialogDrawItems(di->dlg, di, di->x, di->y, di->w, di->h);
}

void
DialogItemCheckButtonSetPtr(DItem *di, char *onoff_ptr)
{
    di->item.check_button.onoff_ptr = onoff_ptr;
}

static int
DialogItemCheckButtonGetState(DItem *di)
{
    return *(di->item.check_button.onoff_ptr) ? 1 : 0;
}

void
DialogItemTableSetOptions(DItem *di, int num_columns, char border,
                          char homogenous_h, char homogenous_v)
{
    di->item.table.num_columns = num_columns;
    di->item.table.border = border;
    di->item.table.homogenous_h = homogenous_h;
    di->item.table.homogenous_v = homogenous_v;
}

void
DialogItemSeparatorSetOrientation(DItem *di, char horizontal)
{
    di->item.separator.horizontal = horizontal;
}

void
DialogItemImageSetFile(DItem *di, const char *image)
{
    EFREE_DUP(di->item.image.image, image);
    di->fill_h = 0;
    di->fill_v = 0;
}

static int
_DialogItemSliderClampVal(const DItem *di, int val)
{
    if (di->item.slider.lower < di->item.slider.upper)
    {
        if (val < di->item.slider.lower)
            val = di->item.slider.lower;
        else if (val > di->item.slider.upper)
            val = di->item.slider.upper;
    }
    else
    {
        if (val > di->item.slider.lower)
            val = di->item.slider.lower;
        else if (val < di->item.slider.upper)
            val = di->item.slider.upper;
    }

    return val;
}

void
DialogItemSliderSetVal(DItem *di, int val)
{
    val = _DialogItemSliderClampVal(di, val);

    if (di->item.slider.val_ptr)
        *di->item.slider.val_ptr = val;

    if (di->realized)
        DialogDrawItems(di->dlg, di, di->x, di->y, di->w, di->h);
}

void
DialogItemSliderSetValPtr(DItem *di, int *val_ptr)
{
    di->item.slider.val_ptr = val_ptr;
    DialogItemSliderSetVal(di, *val_ptr);
}

void
DialogItemSliderSetBounds(DItem *di, int lower, int upper)
{
    if (upper == lower)
        upper = lower + 1;
    di->item.slider.lower = lower;
    di->item.slider.upper = upper;

    if (di->realized)
        DialogDrawItems(di->dlg, di, di->x, di->y, di->w, di->h);
}

void
DialogItemSliderSetUnits(DItem *di, int units)
{
    di->item.slider.unit = units;
}

void
DialogItemSliderSetJump(DItem *di, int jump)
{
    di->item.slider.jump = jump;
}

void
DialogItemSliderSetMinLength(DItem *di, int min)
{
    di->item.slider.min_length = min;
}

void
DialogItemSliderSetOrientation(DItem *di, char horizontal)
{
    di->item.slider.horizontal = horizontal;
}

void
DialogItemSliderGetBounds(const DItem *di, int *lower, int *upper)
{
    if (lower)
        *lower = di->item.slider.lower;
    if (upper)
        *upper = di->item.slider.upper;
}

void
DialogItemAreaSetSize(DItem *di, int w, int h)
{
    di->item.area.w = w;
    di->item.area.h = h;
}

Win
DialogItemAreaGetWindow(const DItem *di)
{
    return di->item.area.area_win;
}

void
DialogItemAreaGetSize(const DItem *di, int *w, int *h)
{
    *w = di->item.area.w;
    *h = di->item.area.h;
}

void
DialogItemAreaSetInitFunc(DItem *di, DialogItemCallbackFunc *func)
{
    di->item.area.init_func = func;
}

void
DialogItemAreaSetEventFunc(DItem *di, DialogItemCallbackFunc *func)
{
    di->item.area.event_func = func;
}

void
DialogItemTableEmpty(DItem *di)
{
    int             i;

    if (di->type != DITEM_TABLE)
        return;

    for (i = 0; i < di->item.table.num_items; i++)
        DialogItemDestroy(di->item.table.items[i], 1);

    EFREE_NULL(di->item.table.items);
    di->item.table.num_items = 0;
}

static void
DialogItemDestroy(DItem *di, int clean)
{
    if (di->type == DITEM_TABLE)
        DialogItemTableEmpty(di);

    Efree(di->text);

    switch (di->type)
    {
    default:
        break;
    case DITEM_CHECKBUTTON:
        if (!clean)
            break;
        EDestroyWindow(di->item.check_button.check_win);
        break;
    case DITEM_IMAGE:
        Efree(di->item.image.image);
        break;
    case DITEM_RADIOBUTTON:
        if (!clean)
            break;
        EDestroyWindow(di->item.radio_button.radio_win);
        break;
    case DITEM_SLIDER:
        ImageclassFree(di->item.slider.ic_base);
        ImageclassFree(di->item.slider.ic_knob);
        if (!clean)
            break;
        EDestroyWindow(di->item.slider.base_win);
        EDestroyWindow(di->item.slider.knob_win);
        break;
    case DITEM_AREA:
        if (!clean)
            break;
        EDestroyWindow(di->item.area.area_win);
        break;
    }

    if (clean && di->win)
        EDestroyWindow(di->win);
    ImageclassFree(di->iclass);
    TextclassFree(di->tclass);

    Efree(di);
}

/* Convenience callback to close dialog */
void
DialogCallbackClose(Dialog *d, int val __UNUSED__, void *data __UNUSED__)
{
    DialogClose(d);
}

/*
 * Predefined dialogs
 */

void
DialogOK(const char *title, const char *fmt, ...)
{
    char            text[10240];
    va_list         args;

    va_start(args, fmt);
    Evsnprintf(text, sizeof(text), fmt, args);
    va_end(args);

    if (Mode.wm.startup)
        Eprintf("%s: %s\n", title, text);
    else
        DialogOKstr(title, text);
}

void
DialogOKstr(const char *title, const char *txt)
{
    Dialog         *d;
    DItem          *table, *di;

    d = DialogCreate("DIALOG");

    table = DialogInitItem(d);
    DialogSetTitle(d, title);

    di = DialogAddItem(table, DITEM_TEXT);
    DialogItemSetText(di, txt);

    di = DialogItemAddButton(table, _("OK"), DialogCallbackClose, 0, 1,
                             DLG_BUTTON_OK);
    DialogItemSetFill(di, 0, 0);
    DialogItemSetAlign(di, 512, 512);

    DialogBindKey(d, "Return", DialogCallbackClose, 0, NULL);
    DialogBindKey(d, "Escape", DialogCallbackClose, 0, NULL);

    DialogShow(d);
}

/*
 * Dialog event handlers
 */
static int
_DlgPixToVal(const DItem *di, int dx, int sr)
{
    int             vr, val;

    vr = di->item.slider.upper - di->item.slider.lower;
    dx = (int)(((float)dx / (sr * di->item.slider.unit)) * abs(vr) + .5f);
    dx *= di->item.slider.unit;
    if (vr < 0)
        dx = -dx;
    val = di->item.slider.lower + dx;

    val = _DialogItemSliderClampVal(di, val);

    return val;
}

static void
DialogEventKeyPress(Dialog *d, XEvent *ev)
{
    int             i;

    for (i = 0; i < d->num_bindings; i++)
    {
        if (ev->xkey.keycode != d->keybindings[i].keycode)
            continue;
        d->keybindings[i].func(d, d->keybindings[i].val,
                               d->keybindings[i].data);
        break;
    }
}

static void
DialogHandleEvents(Win win __UNUSED__, XEvent *ev, void *prm)
{
    Dialog         *d = (Dialog *) prm;

    switch (ev->type)
    {
    case KeyPress:
        DialogEventKeyPress(d, ev);
        break;
    }

    if (d->close)
        _DialogClose(d);
}

static void
DItemEventMotion(Win win __UNUSED__, DItem *di, XEvent *ev)
{
    int             val;

    switch (di->type)
    {
    case DITEM_AREA:
        if (di->item.area.event_func)
            di->item.area.event_func(di, 0, ev);
        break;

    case DITEM_SLIDER:
        if (!di->item.slider.in_drag)
            break;
        if (ev->xmotion.window == WinGetXwin(di->item.slider.knob_win))
        {
            EQueryPointer(di->item.slider.knob_win,
                          &ev->xbutton.x, &ev->xbutton.y, NULL, NULL);
            if (di->item.slider.horizontal)
            {
                val =
                    _DlgPixToVal(di,
                                 ev->xbutton.x + di->item.slider.knob_x -
                                 di->item.slider.knob_w / 2,
                                 di->item.slider.base_w -
                                 di->item.slider.knob_w);
            }
            else
            {
                val =
                    _DlgPixToVal(di,
                                 ev->xbutton.y + di->item.slider.knob_y -
                                 di->item.slider.knob_h / 2,
                                 di->item.slider.base_h -
                                 di->item.slider.knob_h);
            }
            if (di->item.slider.val_ptr)
                *di->item.slider.val_ptr = val;
            if (di->func)
                (di->func) (di->dlg, di->val, di->data);
        }

        DialogDrawItems(di->dlg, di, di->x, di->y, di->w, di->h);
        break;
    }
}

static void
DItemEventMouseDown(Win win, DItem *di, XEvent *ev)
{
    int             x, y, jump, val;

    switch (di->type)
    {
    case DITEM_AREA:
        if (di->item.area.event_func)
            di->item.area.event_func(di, 0, ev);
        break;

    case DITEM_SLIDER:
        if (ev->xbutton.window == WinGetXwin(di->item.slider.knob_win))
        {
            if (ev->xbutton.button >= 1 && ev->xbutton.button <= 3)
            {
                di->item.slider.in_drag = 1;
                break;
            }
        }

        val = (di->item.slider.val_ptr) ?
            *(di->item.slider.val_ptr) : di->item.slider.lower;
        jump = 0;

        /* Coords -> item.slider.base_win */
        ETranslateCoordinates(win, di->item.slider.base_win,
                              ev->xbutton.x, ev->xbutton.y, &x, &y, NULL);

        switch (ev->xbutton.button)
        {
        case 1:
        case 3:
            if (di->item.slider.horizontal)
            {
                if (ev->xbutton.x >
                    (di->item.slider.knob_x + (di->item.slider.knob_w / 2)))
                    jump = di->item.slider.jump;
                else
                    jump = -di->item.slider.jump;
            }
            else
            {
                if (ev->xbutton.y >
                    (di->item.slider.knob_y + (di->item.slider.knob_h / 2)))
                    jump = di->item.slider.jump;
                else
                    jump = -di->item.slider.jump;
            }
            break;

        case 2:
            if (di->item.slider.horizontal)
            {
                val =
                    _DlgPixToVal(di,
                                 ev->xbutton.x - di->item.slider.knob_w / 2,
                                 di->item.slider.base_w -
                                 di->item.slider.knob_w);
            }
            else
            {
                val =
                    _DlgPixToVal(di,
                                 ev->xbutton.y - di->item.slider.knob_h / 2,
                                 di->item.slider.base_h -
                                 di->item.slider.knob_h);
            }
            break;

        case 4:
        case 5:
            jump = di->item.slider.jump / 2;
            if (!jump)
                jump++;

            if (ev->xbutton.button == 5)
                jump = -jump;
            break;
        }
        if (di->item.slider.lower > di->item.slider.upper)
            jump = -jump;
        val = _DialogItemSliderClampVal(di, val + jump);
        if (di->item.slider.val_ptr)
            *di->item.slider.val_ptr = val;
#if 0                           /* Remove? */
        if (di->func)
            (di->func) (d, di->val, di->data);
#endif
        break;
    }

    di->clicked = 1;

    DialogDrawItems(di->dlg, di, di->x, di->y, di->w, di->h);
}

static void
DItemEventMouseUp(Win win, DItem *di, XEvent *ev)
{
    DItem          *dii;

    if (ev->xbutton.window != Mode.events.last_bpress)
        return;

    switch (di->type)
    {
    case DITEM_AREA:
        if (di->item.area.event_func)
            di->item.area.event_func(di, 0, ev);
        break;

    case DITEM_CHECKBUTTON:
        DialogItemCheckButtonSetState(di, !DialogItemCheckButtonGetState(di));
        break;

    case DITEM_RADIOBUTTON:
        dii = di->item.radio_button.first;
        while (dii)
        {
            if (dii->item.radio_button.onoff)
            {
                dii->item.radio_button.onoff = 0;
                DialogDrawItems(di->dlg, dii, dii->x, dii->y, dii->w, dii->h);
            }
            dii = dii->item.radio_button.next;
        }
        di->item.radio_button.onoff = 1;
        if (di->item.radio_button.val_ptr)
            *di->item.radio_button.val_ptr = di->item.radio_button.val;
        break;

    case DITEM_SLIDER:
        if (win == di->item.slider.knob_win)
            di->item.slider.in_drag = 0;
        break;
    }

    if (di->hilited && di->clicked)
    {
        if (di->func)
            di->func(di->dlg, di->val, di->data);

        if (di->do_close)
            di->dlg->close = 1;
    }

    di->clicked = 0;

    DialogDrawItems(di->dlg, di, di->x, di->y, di->w, di->h);
}

static void
DItemEventMouseIn(Win win __UNUSED__, DItem *di, XEvent *ev)
{
    switch (di->type)
    {
    case DITEM_AREA:
        if (di->item.area.event_func)
            di->item.area.event_func(di, 0, ev);
        break;
    }

    di->hilited = 1;

    DialogDrawItems(di->dlg, di, di->x, di->y, di->w, di->h);
}

static void
DItemEventMouseOut(Win win __UNUSED__, DItem *di, XEvent *ev)
{
    switch (di->type)
    {
    case DITEM_AREA:
        if (di->item.area.event_func)
            di->item.area.event_func(di, 0, ev);
        break;
    }

    if (!di->clicked)
        di->hilited = 0;

    DialogDrawItems(di->dlg, di, di->x, di->y, di->w, di->h);
}

static void
DItemHandleEvents(Win win, XEvent *ev, void *prm)
{
    DItem          *di = (DItem *) prm;

    switch (ev->type)
    {
    case ButtonPress:
        DItemEventMouseDown(win, di, ev);
        break;
    case ButtonRelease:
        DItemEventMouseUp(win, di, ev);
        break;
    case MotionNotify:
        DItemEventMotion(win, di, ev);
        break;
    case EnterNotify:
        DItemEventMouseIn(win, di, ev);
        break;
    case LeaveNotify:
        DItemEventMouseOut(win, di, ev);
        break;
    }

    if (di->dlg->close)
        _DialogClose(di->dlg);
}

/*
 * Finders
 */

static EWin    *
FindEwinByDialog(Dialog *d)
{
    EWin           *const *ewins;
    int             i, num;

    ewins = EwinListGetAll(&num);
    for (i = 0; i < num; i++)
    {
        if ((Dialog *) (ewins[i]->data) == d)
            return ewins[i];
    }

    return NULL;
}

static int
FindADialog(void)
{
    EWin           *const *ewins;
    int             i, num, n;

    ewins = EwinListGetAll(&num);
    for (i = n = 0; i < num; i++)
    {
        if (ewins[i]->type == EWIN_TYPE_DIALOG)
            n++;
    }

    return n;
}

#endif                          /* ENABLE_DIALOGS */
