/*
 * 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 "config.h"

#include <X11/Xlib.h>

#include "E.h"
#include "aclass.h"
#include "borders.h"
#include "cursors.h"
#include "ewins.h"
#include "focus.h"
#include "grabs.h"
#include "hints.h"
#include "iclass.h"
#include "icons.h"
#include "list.h"
#include "snaps.h"
#include "tclass.h"
#include "timers.h"
#include "tooltips.h"
#include "windowmatch.h"
#include "xwin.h"

#define EWIN_BORDER_PART_EVENT_MASK \
  (KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask | \
   EnterWindowMask | LeaveWindowMask | PointerMotionMask)
#define EWIN_BORDER_TITLE_EVENT_MASK \
  (EWIN_BORDER_PART_EVENT_MASK)

typedef struct {
    int             min, max;
} WinLimit;

typedef struct {
    int             originbox;
    struct {
        int             percent;
        int             absolute;
    } x            , y;
} WinPoint;

typedef struct {
    WinLimit        width, height;
    WinPoint        topleft, bottomright;
} Geometry;

typedef struct {
    Geometry        geom;
    ImageClass     *iclass;
    ActionClass    *aclass;
    TextClass      *tclass;
    ECursor        *ec;
    signed char     ontop;
    int             flags;
    char            keep_for_shade;
} WinPart;

struct _border {
    dlist_t         list;
    char           *name;
    char           *group_border_name;
    EImageBorder    border;
    int             num_winparts;
    WinPart        *part;
    char            no_extent;
    char            changes_shape;
    char            shadedir;
    char            throwaway;
    unsigned int    ref_count;
    ActionClass    *aclass;
};

struct _ewinbit {
    EWin           *ewin;       /* Belongs to */
    Win             win;
    int             x, y, w, h;
    int             cx, cy, cw, ch;
    char            state;
    char            expose;
    char            left;
    ImageState     *is;
    TextState      *ts;
};

static          LIST_HEAD(border_list);

static void     _BorderDestroy(Border * b);
static void     _BorderWinpartHandleEvents(Win win, XEvent * ev, void *prm);
static void     _BorderFrameHandleEvents(Win win, XEvent * ev, void *prm);
static Border  *_BorderGetFallback(void);

static void
_BorderWinpartRealise(EWin *ewin, int i)
{
    EWinBit        *ewb = &ewin->bits[i];

    if ((ewb->cx != ewb->x) || (ewb->cy != ewb->y) ||
        (ewb->cw != ewb->w) || (ewb->ch != ewb->h))
    {
        if ((ewb->w <= 0) || (ewb->h <= 0))
        {
            EUnmapWindow(ewb->win);
        }
        else
        {
            EMapWindow(ewb->win);
            EMoveResizeWindow(ewb->win, ewb->x, ewb->y, ewb->w, ewb->h);
        }
    }
}

static void
_BorderWinpartITclassApply(EWin *ewin, int i, int force)
{
    EWinBit        *ewb = &ewin->bits[i];
    WinPart        *ebp = &ewin->border->part[i];
    ImageState     *is;
    TextState      *ts;
    const char     *txt;
    int             flags;

    if (!ewb->win)
        return;

#if 0                           /* Debug */
    Eprintf("%s: %#x %#x %2d %d %s\n", __func__,
            EwinGetClientXwin(ewin), EoGetXwin(ewin), i, force,
            EwinGetTitle(ewin));
#endif

    is = ImageclassGetImageState(ebp->iclass, ewb->state,
                                 ewin->state.active, EoIsSticky(ewin));

    ts = NULL;
    txt = NULL;
    flags = 0;
    if (ebp->flags & FLAG_TITLE)
    {
        txt = EwinGetTitle(ewin);
        if (txt && ebp->tclass)
        {
            ts = TextclassGetTextState(ebp->tclass, ewb->state,
                                       ewin->state.active, EoIsSticky(ewin));
            flags = ITA_BGPMAP | ITA_JUSTV;
        }
    }
    if (ebp->flags & FLAG_MINIICON)
    {
        flags |= ITA_BGPMAP;
    }

    if (!force && ewb->is == is && ewb->ts == ts)
        return;
    ewb->is = is;
    ewb->ts = ts;

    ITApply(ewb->win, ebp->iclass, is, ewb->state, ewin->state.active,
            EoIsSticky(ewin), ebp->tclass, ts, txt, flags);

    if (ebp->flags & FLAG_MINIICON)
    {
        EImage         *im;

        im = EwinIconImageGet(ewin, 16, Conf.warplist.icon_mode);
        if (im)
        {
            EX_Pixmap       pmap;
            EImageBorder   *pad;
            int             x, y, w, h;

            x = y = 0;
            w = WinGetW(ewb->win);
            h = WinGetH(ewb->win);
            pad = ImageclassGetPadding(ebp->iclass);
            if (ts)
            {
                /* Occupy square on one side */
                switch (ts->style.orientation)
                {
                default:
                case FONT_TO_RIGHT:
                case FONT_TO_LEFT:
                    w = h;
                    break;
                case FONT_TO_UP:
                    y = h - w;
                    /* FALLTHROUGH */
                case FONT_TO_DOWN:
                    h = w;
                    break;
                }
            }
            if (pad)
            {
                /* Probably not quite right for vertical borders */
                x += pad->left;
                y += pad->top;
                w -= pad->left + pad->right;
                h -= pad->top + pad->bottom;
            }
            if (w < 8 || h < 8)
                goto skip_icon;
            pmap = EGetWindowBackgroundPixmap(ewb->win);
            EImageRenderOnDrawable(im, ewb->win, pmap,
                                   EIMAGE_BLEND | EIMAGE_ANTI_ALIAS,
                                   x, y, w, h);
            EClearWindow(ewb->win);
          skip_icon:
            EImageFree(im);
        }
    }
}

static int
_BorderWinpartDraw(EWin *ewin, int i)
{
    EWinBit        *ewb = &ewin->bits[i];
    int             resize = 0, ret = 0;

    if ((ewb->x != ewb->cx) || (ewb->y != ewb->cy))
    {
        ewb->cx = ewb->x;
        ewb->cy = ewb->y;
        ret = 1;
    }

    if ((ewb->w != ewb->cw) || (ewb->h != ewb->ch))
    {
        resize = 1;
        ewb->cw = ewb->w;
        ewb->ch = ewb->h;
    }

    if ((resize) || (ewb->expose))
    {
        _BorderWinpartITclassApply(ewin, i, 1);
        ewb->expose = 0;
        ret = 1;
    }

    return ret;
}

static void
_BorderWinpartChange(EWin *ewin, int i, int force)
{
    _BorderWinpartITclassApply(ewin, i, force);

    if (ewin->update.shape || ewin->border->changes_shape)
        EwinPropagateShapes(ewin);
}

void
EwinBorderDraw(EWin *ewin, int do_shape, int do_paint)
{
    int             i;

#if 0                           /* Debug */
    Eprintf("%s: %#x %s s=%d p=%d\n", __func__,
            EwinGetClientXwin(ewin), EoGetName(ewin), do_shape, do_paint);
#endif

    for (i = 0; i < ewin->border->num_winparts; i++)
        _BorderWinpartITclassApply(ewin, i, do_shape || do_paint);

    if (do_shape || ewin->update.shape || ewin->border->changes_shape)
        EwinPropagateShapes(ewin);
}

void
EwinBorderUpdateInfo(EWin *ewin)
{
    int             i;

    for (i = 0; i < ewin->border->num_winparts; i++)
    {
        if (ewin->border->part[i].flags & FLAG_TITLE)
            _BorderWinpartITclassApply(ewin, i, 1);
    }
}

static void
_BorderWinpartCalc(const EWin *ewin, int i, int ww, int hh)
{
    int             x, y, w, h, ox, oy, max, min;
    int             topleft, bottomright;

    topleft = ewin->border->part[i].geom.topleft.originbox;
    bottomright = ewin->border->part[i].geom.bottomright.originbox;
    if (topleft >= 0)
        _BorderWinpartCalc(ewin, topleft, ww, hh);
    if (bottomright >= 0)
        _BorderWinpartCalc(ewin, bottomright, ww, hh);

    x = y = 0;
    if (topleft == -1)
    {
        x = ((ewin->border->part[i].geom.topleft.x.percent * ww) >> 10) +
            ewin->border->part[i].geom.topleft.x.absolute;
        y = ((ewin->border->part[i].geom.topleft.y.percent * hh) >> 10) +
            ewin->border->part[i].geom.topleft.y.absolute;
    }
    else if (topleft >= 0)
    {
        x = ((ewin->border->part[i].geom.topleft.x.percent *
              ewin->bits[topleft].w) >> 10) +
            ewin->border->part[i].geom.topleft.x.absolute +
            ewin->bits[topleft].x;
        y = ((ewin->border->part[i].geom.topleft.y.percent *
              ewin->bits[topleft].h) >> 10) +
            ewin->border->part[i].geom.topleft.y.absolute +
            ewin->bits[topleft].y;
    }

    ox = oy = 0;
    if (bottomright == -1)
    {
        ox = ((ewin->border->part[i].geom.bottomright.x.percent * ww) >> 10) +
            ewin->border->part[i].geom.bottomright.x.absolute;
        oy = ((ewin->border->part[i].geom.bottomright.y.percent * hh) >> 10) +
            ewin->border->part[i].geom.bottomright.y.absolute;
    }
    else if (bottomright >= 0)
    {
        ox = ((ewin->border->part[i].geom.bottomright.x.percent *
               ewin->bits[bottomright].w) >> 10) +
            ewin->border->part[i].geom.bottomright.x.absolute +
            ewin->bits[bottomright].x;
        oy = ((ewin->border->part[i].geom.bottomright.y.percent *
               ewin->bits[bottomright].h) >> 10) +
            ewin->border->part[i].geom.bottomright.y.absolute +
            ewin->bits[bottomright].y;
    }

    /*
     * calculate height before width, because we may need it in order to
     * determine the font size. But we might do it the other way around for
     * side borders :-)
     */

    h = (oy - y) + 1;
    max = ewin->border->part[i].geom.height.max;
    min = ewin->border->part[i].geom.height.min;

    /*
     * If the title bar max size is set to zero, then set the title bar size to
     * just a little bit more than the size of the title text.
     */

    if (max == 0 && ewin->border->part[i].flags & FLAG_TITLE)
    {
        int             dummywidth, wmax, wmin;
        ImageClass     *iclass;
        TextClass      *tclass;
        EImageBorder   *pad;

        /*
         * calculate width before height, because we need it in order to
         * determine the font size.
         */

        w = (ox - x) + 1;
        wmax = ewin->border->part[i].geom.width.max;
        wmin = ewin->border->part[i].geom.width.min;
        if (w > wmax)
        {
            w = wmax;
            x = ((x + ox) - w) >> 1;
        }
        else if (w < wmin)
        {
            w = wmin;
        }
        iclass = ewin->border->part[i].iclass;
        tclass = ewin->border->part[i].tclass;
        pad = ImageclassGetPadding(iclass);
        TextSize(tclass, ewin->state.active, EoIsSticky(ewin),
                 ewin->bits[i].state, EwinGetTitle(ewin), &max, &dummywidth);
        max += pad->left + pad->right;
        if (h > max)
        {
            y += ((h - max) * TextclassGetJustification(tclass)) >> 10;
            h = max;
        }
        if (h < min)
        {
            h = min;
        }
    }
    else
    {
        if (h > max)
        {
            h = max;
            y = ((y + oy) - h) >> 1;
        }
        else if (h < min)
        {
            h = min;
        }
        /*
         * and now the width.
         */

        w = (ox - x) + 1;
        max = ewin->border->part[i].geom.width.max;
        min = ewin->border->part[i].geom.width.min;

        /*
         * If the title bar max size is set to zero, then set the title bar
         * size to just a little bit more than the size of the title text.
         */

        if (max == 0 && ewin->border->part[i].flags & FLAG_TITLE)
        {
            int             dummyheight;
            ImageClass     *iclass;
            TextClass      *tclass;
            EImageBorder   *pad;

            iclass = ewin->border->part[i].iclass;
            tclass = ewin->border->part[i].tclass;
            pad = ImageclassGetPadding(iclass);
            TextSize(tclass, ewin->state.active, EoIsSticky(ewin),
                     ewin->bits[i].state, EwinGetTitle(ewin), &max,
                     &dummyheight);
            max += pad->left + pad->right;

            if (w > max)
            {
                x += ((w - max) * TextclassGetJustification(tclass)) >> 10;
                w = max;
            }
        }
        if (w > max)
        {
            w = max;
            x = ((x + ox) - w) >> 1;
        }
        else if (w < min)
        {
            w = min;
        }
    }
    if ((ewin->state.shaded) && (!ewin->border->part[i].keep_for_shade))
    {
        ewin->bits[i].x = -100;
        ewin->bits[i].y = -100;
        ewin->bits[i].w = -1;
        ewin->bits[i].h = -1;
    }
    else
    {
        ewin->bits[i].x = x;
        ewin->bits[i].y = y;
        ewin->bits[i].w = w;
        ewin->bits[i].h = h;
    }
}

void
EwinBorderCalcSizes(EWin *ewin, int propagate)
{
    int             i, ww, hh;
    char            reshape;

    if (!ewin->border)
        return;

    ww = EoGetW(ewin);
    hh = EoGetH(ewin);

    for (i = 0; i < ewin->border->num_winparts; i++)
        ewin->bits[i].w = -2;
    for (i = 0; i < ewin->border->num_winparts; i++)
        if (ewin->bits[i].w == -2)
            _BorderWinpartCalc(ewin, i, ww, hh);
    for (i = 0; i < ewin->border->num_winparts; i++)
        _BorderWinpartRealise(ewin, i);

    reshape = 0;
    for (i = 0; i < ewin->border->num_winparts; i++)
    {
        reshape |= _BorderWinpartDraw(ewin, i);
    }

#if 0                           /* Debug */
    Eprintf("%s: prop=%d reshape=%d\n", __func__, propagate, reshape);
#endif
    if (reshape)
        ewin->update.shape = 1;
    if (propagate && ewin->update.shape)
        EwinPropagateShapes(ewin);
}

static void
_BorderIncRefcount(const Border *b)
{
    ((Border *) b)->ref_count++;
}

static void
_BorderDecRefcount(const Border *b)
{
    ((Border *) b)->ref_count--;
}

const char     *
BorderGetName(const Border *b)
{
    return (b) ? b->name : NULL;
}

int
BorderCanShade(const Border *b)
{
    return !b->no_extent;
}

const EImageBorder *
BorderGetSize(const Border *b)
{
    return &b->border;
}

int
BorderGetShadedir(const Border *b)
{
    return b->shadedir;
}

ActionClass    *
BorderGetAclass(const Border *b)
{
    return b->aclass;
}

void
EwinBorderSelect(EWin *ewin)
{
    const Border   *b;

    if (ewin->inh_wm.b.border)
    {
        b = BorderFind("BORDERLESS");
        goto done;
    }

    /* Quit if we already have a border that isn't an internal one */
    b = ewin->border;
    if (b && strncmp(b->name, "__", 2))
        goto done;

    b = NULL;

    if (ewin->props.no_border)
        b = BorderFind("BORDERLESS");

    if (!b)
        b = WindowMatchEwinBorder(ewin);

    if (!b)
        b = BorderFind("DEFAULT");

    if (!b)
        b = _BorderGetFallback();

  done:
    ewin->normal_border = ewin->border = b;
}

void
EwinBorderDetach(EWin *ewin)
{
    const Border   *b = ewin->border;
    int             i;

    if (!b)
        return;

    TooltipsSetPending(0, NULL, NULL);

    EventCallbackUnregister(EoGetWin(ewin), _BorderFrameHandleEvents, ewin);
    for (i = 0; i < b->num_winparts; i++)
    {
        EventCallbackUnregister(ewin->bits[i].win,
                                _BorderWinpartHandleEvents, &ewin->bits[i]);
        if (ewin->bits[i].win)
            EDestroyWindow(ewin->bits[i].win);
    }
    EFREE_NULL(ewin->bits);
    _BorderDecRefcount(b);

    ewin->border = NULL;

    if (b->throwaway)
        _BorderDestroy((Border *) b);
}

void
EwinBorderSetTo(EWin *ewin, const Border *b)
{
    int             i;

    if (ewin->border == b)
        return;

    if (!b)
    {
        b = ewin->border;
        ewin->border = NULL;
    }

    if (ewin->border)
        EwinBorderDetach(ewin);

    ewin->border = b;
    _BorderIncRefcount(b);
    HintsSetWindowBorder(ewin);

    ewin->state.no_border = b->no_extent;

    EventCallbackRegister(EoGetWin(ewin), _BorderFrameHandleEvents, ewin);

    if (b->num_winparts > 0)
        ewin->bits = EMALLOC(EWinBit, b->num_winparts);

    for (i = 0; i < b->num_winparts; i++)
    {
        ewin->bits[i].ewin = ewin;      /* Reference to associated Ewin */

        ewin->bits[i].win = ECreateWindow(EoGetWin(ewin), -10, -10, 1, 1, 0);
        ECursorApply(b->part[i].ec, ewin->bits[i].win);
        EMapWindow(ewin->bits[i].win);
        EventCallbackRegister(ewin->bits[i].win,
                              _BorderWinpartHandleEvents, &ewin->bits[i]);
        if (b->part[i].flags & FLAG_TITLE)
            ESelectInput(ewin->bits[i].win, EWIN_BORDER_TITLE_EVENT_MASK);
        else
            ESelectInput(ewin->bits[i].win, EWIN_BORDER_PART_EVENT_MASK);
        ewin->bits[i].x = -10;
        ewin->bits[i].y = -10;
        ewin->bits[i].w = -10;
        ewin->bits[i].h = -10;
        ewin->bits[i].cx = -99;
        ewin->bits[i].cy = -99;
        ewin->bits[i].cw = -99;
        ewin->bits[i].ch = -99;
        ewin->bits[i].state = 0;
        ewin->bits[i].expose = 0;
        ewin->bits[i].left = 0;
        ewin->bits[i].is = NULL;
    }

    {
        EX_Window      *wl;
        int             j = 0;

        wl = EMALLOC(EX_Window, b->num_winparts + 1);
        if (!wl)
            return;
        for (i = b->num_winparts - 1; i >= 0; i--)
        {
            if (b->part[i].ontop)
                wl[j++] = WinGetXwin(ewin->bits[i].win);
        }
        wl[j++] = WinGetXwin(EwinGetClientConWin(ewin));
        for (i = b->num_winparts - 1; i >= 0; i--)
        {
            if (!b->part[i].ontop)
                wl[j++] = WinGetXwin(ewin->bits[i].win);
        }
        EXRestackWindows(wl, j);
        Efree(wl);
    }

#if USE_CONTAINER_WIN
    if (!ewin->state.shaded)
        EMoveWindow(ewin->win_container, b->border.left, b->border.top);
#endif

    ewin->update.shape = 1;
    EwinBorderCalcSizes(ewin, 0);
    EwinStateUpdate(ewin);

    SnapshotEwinUpdate(ewin, SNAP_USE_BORDER);
}

void
EwinBorderChange(EWin *ewin, const Border *b, int normal)
{
    if (!b || ewin->border == b ||
        ewin->inh_wm.b.border || ewin->state.fullscreen)
        return;

    EwinBorderSetTo(ewin, b);
    EwinMoveResize(ewin, EoGetX(ewin), EoGetY(ewin),
                   ewin->client.w, ewin->client.h, 0);

    if (normal)
        ewin->normal_border = b;
}

static void
_EwinBorderAssign(EWin *ewin, const Border *b)
{
    if (!b || ewin->border == b || ewin->inh_wm.b.border)
        return;

    if (ewin->border)
        _BorderDecRefcount(ewin->border);
    _BorderIncRefcount(b);

    ewin->border = ewin->normal_border = b;
}

void
EwinBorderSetInitially(EWin *ewin, const char *name)
{
    _EwinBorderAssign(ewin, BorderFind(name));
}

const Border   *
EwinBorderGetGroupBorder(const EWin *ewin)
{
    return BorderFind(ewin->border->group_border_name);
}

static Border  *
_BorderCreate(const char *name)
{
    Border         *b;

    b = ECALLOC(Border, 1);
    if (!b)
        return NULL;

    LIST_PREPEND(Border, &border_list, b);

    b->name = Estrdup(name);
    b->group_border_name = NULL;
    b->shadedir = 2;

    return b;
}

static int
_BorderCheck(Border *b)
{
    b->no_extent = b->num_winparts <= 0 ||
        (b->border.left == 0 && b->border.right == 0 &&
         b->border.top == 0 && b->border.bottom == 0);

    return 0;
}

static void
_BorderDestroy(Border *b)
{
    int             i;

    if (!b)
        return;

    if (b->ref_count > 0)
    {
        DialogOK("Border Error!", _("%u references remain"), b->ref_count);
        return;
    }

    LIST_REMOVE(Border, &border_list, b);

    for (i = 0; i < b->num_winparts; i++)
    {
        ImageclassFree(b->part[i].iclass);
        ActionclassFree(b->part[i].aclass);
        TextclassFree(b->part[i].tclass);
        ECursorFree(b->part[i].ec);
    }

    Efree(b->part);
    Efree(b->name);
    Efree(b->group_border_name);
    ActionclassFree(b->aclass);
    Efree(b);
}

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

Border         *
BorderFind(const char *name)
{
    if (!name)
        return NULL;
    return LIST_FIND(Border, &border_list, _BorderMatchName, name);
}

static void
_BorderWinpartAdd(Border *b, const char *iclass, const char *aclass,
                  const char *tclass, const char *cclass, char ontop, int flags,
                  char isregion __UNUSED__, int wmin, int wmax, int hmin,
                  int hmax, int torigin, int txp, int txa, int typ, int tya,
                  int borigin, int bxp, int bxa, int byp, int bya,
                  char keep_for_shade)
{
    int             n;

    b->num_winparts++;
    n = b->num_winparts;

    b->part = EREALLOC(WinPart, b->part, n);

    b->part[n - 1].iclass = (iclass) ? ImageclassAlloc(iclass, 1) : NULL;
    b->part[n - 1].aclass = (aclass) ? ActionclassAlloc(aclass) : NULL;
    b->part[n - 1].tclass = (tclass) ? TextclassAlloc(tclass, 1) : NULL;
    b->part[n - 1].ec = (cclass) ? ECursorAlloc(cclass) : NULL;

    b->part[n - 1].ontop = ontop;
    b->part[n - 1].flags = flags;
    b->part[n - 1].keep_for_shade = keep_for_shade;
    b->part[n - 1].geom.width.min = wmin;
    b->part[n - 1].geom.width.max = wmax;
    b->part[n - 1].geom.height.min = hmin;
    b->part[n - 1].geom.height.max = hmax;
    b->part[n - 1].geom.topleft.originbox = torigin;
    b->part[n - 1].geom.topleft.x.percent = txp;
    b->part[n - 1].geom.topleft.x.absolute = txa;
    b->part[n - 1].geom.topleft.y.percent = typ;
    b->part[n - 1].geom.topleft.y.absolute = tya;
    b->part[n - 1].geom.bottomright.originbox = borigin;
    b->part[n - 1].geom.bottomright.x.percent = bxp;
    b->part[n - 1].geom.bottomright.x.absolute = bxa;
    b->part[n - 1].geom.bottomright.y.percent = byp;
    b->part[n - 1].geom.bottomright.y.absolute = bya;
}

void
EwinBorderMinShadeSize(const EWin *ewin, int *mw, int *mh)
{
    int             i, pw, ph, w, h, min_w, min_h;
    int             leftborderwidth, rightborderwidth;
    int             topborderwidth, bottomborderwidth;

    pw = ewin->client.w +
        ewin->border->border.left + ewin->border->border.right;
    ph = ewin->client.h +
        ewin->border->border.top + ewin->border->border.bottom;

    for (i = 0; i < ewin->border->num_winparts; i++)
        ewin->bits[i].w = -2;
    for (i = 0; i < ewin->border->num_winparts; i++)
        if (ewin->bits[i].w == -2)
            _BorderWinpartCalc(ewin, i, pw, ph);

    switch (ewin->border->shadedir)
    {
    case 0:
    case 1:
        /* get the correct width, based on the borderparts that */
        /*are remaining visible */
        leftborderwidth = rightborderwidth = 0;
        for (i = 0; i < ewin->border->num_winparts; i++)
        {
            if (!ewin->border->part[i].keep_for_shade)
                continue;

            w = ewin->border->border.left - ewin->bits[i].x;
            if (leftborderwidth < w)
                leftborderwidth = w;

            w = ewin->bits[i].x + ewin->bits[i].w -
                (pw - ewin->border->border.right);
            if (rightborderwidth < w)
                rightborderwidth = w;
        }
        pw = rightborderwidth + leftborderwidth;
        break;
    case 2:
    case 3:
        topborderwidth = bottomborderwidth = 0;
        for (i = 0; i < ewin->border->num_winparts; i++)
        {
            if (!ewin->border->part[i].keep_for_shade)
                continue;

            h = ewin->border->border.top - ewin->bits[i].y;
            if (topborderwidth < h)
                topborderwidth = h;

            h = ewin->bits[i].y + ewin->bits[i].h -
                (ph - ewin->border->border.bottom);
            if (bottomborderwidth < h)
                bottomborderwidth = h;
        }
        ph = bottomborderwidth + topborderwidth;
        break;
    default:
        break;
    }

    for (i = 0; i < ewin->border->num_winparts; i++)
        ewin->bits[i].w = -2;
    for (i = 0; i < ewin->border->num_winparts; i++)
        if (ewin->bits[i].w == -2)
            _BorderWinpartCalc(ewin, i, pw, ph);

    min_w = 0;
    min_h = 0;
    for (i = 0; i < ewin->border->num_winparts; i++)
    {
        if (!ewin->border->part[i].keep_for_shade)
            continue;

        w = ewin->bits[i].x + ewin->bits[i].w;
        if (min_w < w)
            min_w = w;

        h = ewin->bits[i].y + ewin->bits[i].h;
        if (min_h < h)
            min_h = h;
    }

    *mw = min_w;
    *mh = min_h;
}

int
BorderWinpartIndex(EWin *ewin, Win win)
{
    int             i;

    for (i = 0; i < ewin->border->num_winparts; i++)
    {
        if (win == ewin->bits[i].win)
            return i;
    }

    return -1;                  /* Not found */
}

/*
 * Border event handlers
 */
#define DEBUG_BORDER_EVENTS 0

static void
_BorderWinpartEventMouseDown(EWinBit *wbit, XEvent *ev)
{
    EWin           *ewin = wbit->ewin;
    int             part = wbit - ewin->bits;

#if 0                           /* Remove? */
    GrabPointerSet(wbit->win, 0, 0);
#endif

    wbit->state = STATE_CLICKED;
#if DEBUG_BORDER_EVENTS
    Eprintf("%s: %#x %d\n", __func__, WinGetXwin(wbit->win), wbit->state);
#endif
    _BorderWinpartChange(ewin, part, 0);

    FocusHandleClick(ewin, wbit->win);

    if (ewin->border->part[part].aclass)
        ActionclassEvent(ewin->border->part[part].aclass, ev, ewin);
}

static void
_BorderWinpartEventMouseUp(EWinBit *wbit, XEvent *ev)
{
    EWin           *ewin = wbit->ewin;
    int             part = wbit - ewin->bits;
    int             left = wbit->left;

    if (ev)
    {
#if 0                           /* Remove? */
        GrabPointerRelease();
#endif
        /*
         * During a move/resize operation initiated by a border part click
         * the ButtonRelease event may be reported on the border part window
         * if the event happens before the pointer grab is moved to the
         * move/resize event window.
         * This can be tested e.g. by inserting usleep(100000) at the start
         * of _BorderWinpartEventMouseDown().
         */
        MoveResizeEnd(ewin);
    }

    if ((wbit->state == STATE_CLICKED) && (!wbit->left))
        wbit->state = STATE_HILITED;
    else
        wbit->state = STATE_NORMAL;
#if DEBUG_BORDER_EVENTS
    Eprintf("%s: %#x %d\n", __func__, WinGetXwin(wbit->win), wbit->state);
#endif
    _BorderWinpartChange(ewin, part, 0);

    /* Beware! Actions may destroy the current border */
    wbit->left = 0;

    if (ev && WinGetXwin(wbit->win) == Mode.events.last_bpress && !left &&
        ewin->border->part[part].aclass)
        ActionclassEvent(ewin->border->part[part].aclass, ev, ewin);
}

static void
_BorderWinpartEventEnter(EWinBit *wbit, XEvent *ev)
{
    EWin           *ewin = wbit->ewin;
    int             part = wbit - ewin->bits;

#if DEBUG_BORDER_EVENTS
    Eprintf("%s: %#x %d\n", __func__, WinGetXwin(wbit->win), wbit->state);
#endif
    if (wbit->state == STATE_CLICKED)
        wbit->left = 0;

    wbit->state = STATE_HILITED;
    _BorderWinpartChange(ewin, part, 0);
    if (ewin->border->part[part].aclass)
        ActionclassEvent(ewin->border->part[part].aclass, ev, ewin);
}

static void
_BorderWinpartEventLeave(EWinBit *wbit, XEvent *ev)
{
    EWin           *ewin = wbit->ewin;
    int             part = wbit - ewin->bits;

#if DEBUG_BORDER_EVENTS
    Eprintf("%s: %#x %d\n", __func__, WinGetXwin(wbit->win), wbit->state);
#endif
    if (wbit->state == STATE_CLICKED)
        wbit->left = 1;
    else
    {
        wbit->state = STATE_NORMAL;
        _BorderWinpartChange(ewin, part, 0);
        if (ewin->border->part[part].aclass)
            ActionclassEvent(ewin->border->part[part].aclass, ev, ewin);
    }
}

void
BorderCheckState(EWin *ewin, XEvent *ev)
{
    int             i;

    for (i = 0; i < ewin->border->num_winparts; i++)
    {
        switch (ev->type)
        {
        default:
            break;

        case ButtonRelease:
            _BorderWinpartEventMouseUp(ewin->bits + i, NULL);
            break;
        }
    }
}

static int
_BorderAutoshadeTimeout(void *data)
{
    EWin           *ewin = (EWin *) data;

    if (!EwinFindByPtr(ewin))   /* Check, window may be gone */
        return 0;

    ewin->timer = NULL;
    EwinOpShade(ewin, OPSRC_USER, 1);

    return 0;
}

static void
_BorderFrameHandleEvents(Win win __UNUSED__, XEvent *ev, void *prm)
{
    EWin           *ewin = (EWin *) prm;
    int             x, y;

    switch (ev->type)
    {
    case EnterNotify:
        if (ewin->props.autoshade)
        {
            EwinOpShade(ewin, OPSRC_USER, 0);
        }
        if (ewin->border->aclass)
            ActionclassEvent(ewin->border->aclass, ev, ewin);
        break;
    case LeaveNotify:
        if (ewin->props.autoshade && !ewin->state.shaded)
        {
            EQueryPointer(EoGetWin(ewin), &x, &y, NULL, NULL);
            if (x >= 4 && x < EoGetW(ewin) - 4 &&
                y >= 4 && y < EoGetH(ewin) - 4)
                break;
            TIMER_DEL(ewin->timer);
            TIMER_ADD(ewin->timer, 500, _BorderAutoshadeTimeout, ewin);
        }
        if (ewin->border->aclass)
            ActionclassEvent(ewin->border->aclass, ev, ewin);
        break;
    }
}

static ActionClass *
_BorderWinpartGetAclass(void *data)
{
    EWinBit        *wbit = (EWinBit *) data;
    EWin           *ewin;
    int             part;

    /* Validate border part */
    ewin = Mode.mouse_over_ewin;
    if (!ewin)
        return NULL;

    part = wbit - ewin->bits;
    if (part < 0 || part >= ewin->border->num_winparts)
        return NULL;

    return ewin->border->part[part].aclass;
}

static void
_BorderWinpartHandleTooltip(EWinBit *wbit)
{
    EWin           *ewin = wbit->ewin;
    int             part = wbit - ewin->bits;

    if (!ewin->border->part[part].aclass)
        return;
    TooltipsSetPending(0, _BorderWinpartGetAclass, wbit);
}

static void
_BorderWinpartHandleEvents(Win win __UNUSED__, XEvent *ev, void *prm)
{
    EWinBit        *wbit = (EWinBit *) prm;

    switch (ev->type)
    {
    case ButtonPress:
        _BorderWinpartEventMouseDown(wbit, ev);
        break;
    case ButtonRelease:
        _BorderWinpartEventMouseUp(wbit, ev);
        break;
    case EnterNotify:
        /* Beware! Actions may destroy the current border */
        _BorderWinpartHandleTooltip(wbit);
        _BorderWinpartEventEnter(wbit, ev);
        break;
    case LeaveNotify:
        _BorderWinpartEventLeave(wbit, ev);
        break;
    case MotionNotify:
        _BorderWinpartHandleTooltip(wbit);
        break;
    }
}

/*
 * Configuration load/save
 */
#include "conf.h"

static int
_BorderPartLoad(FILE *fs, char type __UNUSED__, Border *b)
{
    int             err = 0;
    char            s[FILEPATH_LEN_MAX];
    char            s2[FILEPATH_LEN_MAX];
    int             i1, i2;
    char            iclass[64], aclass[64], tclass[64], cclass[64];
    char           *piclass, *paclass, *ptclass, *pcclass;
    char            ontop = 1;
    int             flags = FLAG_BUTTON;
    char            isregion = 0, keepshade = 1;
    int             wmin = 0, wmax = 0, hmin = 0, hmax = 0, torigin =
        0, txp = 0, txa = 0, typ = 0, tya = 0, borigin = 0;
    int             bxp = 0, bxa = 0, byp = 0, bya = 0;

    piclass = paclass = ptclass = pcclass = NULL;

    while (GetLine(s, sizeof(s), fs))
    {
        i1 = ConfigParseline1(s, s2, NULL, NULL);
        i2 = atoi(s2);
        switch (i1)
        {
        case CONFIG_CLOSE:
            _BorderWinpartAdd(b, piclass, paclass, ptclass, pcclass, ontop,
                              flags, isregion, wmin, wmax, hmin, hmax,
                              torigin, txp, txa, typ, tya,
                              borigin, bxp, bxa, byp, bya, keepshade);
            goto done;
        case CONFIG_IMAGECLASS:
            STRCPY(iclass, s2);
            piclass = iclass;
            break;
        case CONFIG_ACTIONCLASS:
            STRCPY(aclass, s2);
            paclass = aclass;
            break;
        case CONFIG_TEXT:
            STRCPY(tclass, s2);
            ptclass = tclass;
            break;
        case CONFIG_CURSOR:
            STRCPY(cclass, s2);
            pcclass = cclass;
            break;
        case BORDERPART_ONTOP:
            ontop = i2;
            break;
        case BORDERPART_FLAGS:
            flags = i2;
            break;
        case BORDERPART_ISREGION:
            isregion = i2;
            break;
        case BORDERPART_WMIN:
            wmin = i2;
            if (!wmax)
                wmax = wmin;
            break;
        case BORDERPART_WMAX:
            wmax = i2;
            break;
        case BORDERPART_HMIN:
            hmin = i2;
            if (!hmax)
                hmax = hmin;
            break;
        case BORDERPART_HMAX:
            hmax = i2;
            break;
        case BORDERPART_TORIGIN:
            torigin = i2;
            break;
        case BORDERPART_TXP:
            txp = i2;
            break;
        case BORDERPART_TXA:
            txa = i2;
            break;
        case BORDERPART_TYP:
            typ = i2;
            break;
        case BORDERPART_TYA:
            tya = i2;
            break;
        case BORDERPART_BORIGIN:
            borigin = i2;
            break;
        case BORDERPART_BXP:
            bxp = i2;
            break;
        case BORDERPART_BXA:
            bxa = i2;
            break;
        case BORDERPART_BYP:
            byp = i2;
            break;
        case BORDERPART_BYA:
            bya = i2;
            break;
        case BORDERPART_KEEPSHADE:
            keepshade = i2;
            break;
        default:
            break;
        }
    }
    err = -1;
  done:
    return err;
}

int
BorderConfigLoad(FILE *fs)
{
    int             err = 0;
    Border         *b = 0;
    char            s[FILEPATH_LEN_MAX];
    char            s2[FILEPATH_LEN_MAX];
    int             i1, i2;

    while (GetLine(s, sizeof(s), fs))
    {
        i1 = ConfigParseline1(s, s2, NULL, NULL);
        i2 = atoi(s2);

        switch (i1)
        {
        case CONFIG_CLASSNAME:
            if (BorderFind(s2))
            {
                SkipTillEnd(fs);
                goto done;
            }
            b = _BorderCreate(s2);
            continue;
        }

        if (!b)
            break;

        switch (i1)
        {
        default:
            break;
        case CONFIG_CLOSE:
            _BorderCheck(b);
            goto done;
        case BORDER_INIT:
            if (i2 != CONFIG_OPEN)
                break;
            err = _BorderPartLoad(fs, i1, b);
            if (err)
                break;
            break;
        case BORDER_GROUP_NAME:
            b->group_border_name = Estrdup(s2);
            break;
        case BORDER_LEFT:
            b->border.left = i2;
            break;
        case BORDER_RIGHT:
            b->border.right = i2;
            break;
        case BORDER_TOP:
            b->border.top = i2;
            break;
        case BORDER_BOTTOM:
            b->border.bottom = i2;
            break;
        case BORDER_SHADEDIR:
            b->shadedir = i2;
            break;
        case BORDER_CHANGES_SHAPE:
            b->changes_shape = i2;
            break;
        case CONFIG_ACTIONCLASS:
            b->aclass = ActionclassFind(s2);
            break;
        }
    }
    err = -1;

  done:
    return err;
}

Border         *
BorderCreateFiller(int w, int h, int sw, int sh)
{
    Border         *b;
    int             left, right, top, bottom;

    if (w > sw || h > sh)       /* Borders must be >= 0 */
        return NULL;

    b = _BorderCreate("__FILLER");
    if (!b)
        return b;

    left = (sw - w) / 2;
    left = (left < 0) ? 0 : left;
    right = sw - w - left;
    top = (sh - h) / 2;
    top = (top < 0) ? 0 : top;
    bottom = sh - h - top;

    b->throwaway = 1;

    b->border.left = left;
    b->border.right = right;
    b->border.top = top;
    b->border.bottom = bottom;

    ImageclassGetBlack();       /* Creates the __BLACK ImageClass */

    if (top)
        _BorderWinpartAdd(b, "__BLACK", NULL, NULL, NULL, 1, FLAG_BUTTON, 0,
                          1, 99999, 1, 99999,
                          -1, 0, 0, 0, 0, -1, 1024, -1, 0, top - 1, 1);
    if (bottom)
        _BorderWinpartAdd(b, "__BLACK", NULL, NULL, NULL, 1, FLAG_BUTTON, 0,
                          1, 99999, 1, 99999,
                          -1, 0, 0, 1024, -bottom, -1, 1024, -1, 1024, -1, 1);
    if (left)
        _BorderWinpartAdd(b, "__BLACK", NULL, NULL, NULL, 1, FLAG_BUTTON, 0,
                          1, 99999, 1, 99999,
                          -1, 0, 0, 0, top,
                          -1, 0, left - 1, 1024, -(bottom + 1), 1);
    if (right)
        _BorderWinpartAdd(b, "__BLACK", NULL, NULL, NULL, 1, FLAG_BUTTON, 0,
                          1, 99999, 1, 99999,
                          -1, 1024, -right, 0, top,
                          -1, 1024, -1, 1024, -(bottom + 1), 1);

    return b;
}

static int
_BorderNameCompare(const void *b1, const void *b2)
{
    if (b1 && b2)
        return strcmp((*(const Border **)b1)->name,
                      (*(const Border **)b2)->name);

    return 0;
}

Border        **
BordersGetList(int *pnum)
{
    Border        **lst;

    lst = LIST_GET_ITEMS(Border, &border_list, pnum);
    qsort(lst, *pnum, sizeof(Border *), _BorderNameCompare);

    return lst;
}

static ActionClass *
BorderGetFallbackAclass(void)
{
    ActionClass    *ac;
    Action         *aa;

    ac = ActionclassFind("__fb_bd_ac");
    if (ac)
        return ac;

    /* Create fallback actionclass for the fallback border */
    ac = ActionclassCreate("__fb_bd_ac", 0);
    if (!ac)
        return ac;

    aa = ActionCreate(EVENT_MOUSE_DOWN, 1, 0, 0, 1, 0, NULL, NULL);
    ActionclassAddAction(ac, aa);
    ActionAddTo(aa, "wop * mo ptr");

    aa = ActionCreate(EVENT_MOUSE_DOWN, 1, 0, 0, 2, 0, NULL, NULL);
    ActionclassAddAction(ac, aa);
    ActionAddTo(aa, "wop * close");

    aa = ActionCreate(EVENT_MOUSE_DOWN, 1, 0, 0, 3, 0, NULL, NULL);
    ActionclassAddAction(ac, aa);
    ActionAddTo(aa, "wop * sz ptr");

    return ac;
}

static Border  *
_BorderGetFallback(void)
{
    /*
     * This function creates simple internal data members to be used in 
     * emergencies - ie when all else fails - ie a button is told to use an
     * imageclass that doesn't exist, or no DEFAULT border is defined... at 
     * least E won't barf on us then.
     */
    Border         *b;

    b = BorderFind("__fb_bd");
    if (b)
        return b;

    BorderGetFallbackAclass();  /* Creates the fallback ac */

    /* Create fallback border */
    b = _BorderCreate("__fb_bd");
    if (!b)
        return b;

    b->border.left = 8;
    b->border.right = 8;
    b->border.top = 8;
    b->border.bottom = 8;
    _BorderWinpartAdd(b, "__fb_ic", "__fb_bd_ac", NULL, NULL,
                      1, FLAG_BUTTON, 0, 8, 99999, 8, 99999, -1, 0, 0, 0, 0,
                      -1, 1024, -1, 0, 7, 1);
    _BorderWinpartAdd(b, "__fb_ic", "__fb_bd_ac", NULL, NULL,
                      1, FLAG_BUTTON, 0, 8, 99999, 8, 99999, -1, 0, 0, 1024,
                      -8, -1, 1024, -1, 1024, -1, 1);
    _BorderWinpartAdd(b, "__fb_ic", "__fb_bd_ac", NULL, NULL,
                      1, FLAG_BUTTON, 0, 8, 99999, 8, 99999, -1, 0, 0, 0, 8,
                      -1, 0, 7, 1024, -9, 1);
    _BorderWinpartAdd(b, "__fb_ic", "__fb_bd_ac", NULL, NULL,
                      1, FLAG_BUTTON, 0, 8, 99999, 8, 99999, -1, 1024, -8, 0,
                      8, -1, 1024, -1, 1024, -9, 1);

    return b;
}
