/* KeyboardShortcuts.c- keyboard shortcut bindings
 *
 *  WPrefs - Window Maker Preferences Program
 *
 *  Copyright (c) 1998-2003 Alfredo K. Kojima
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
 *  USA.
 */


#include "config.h" /* for HAVE_XCONVERTCASE */


#include "WPrefs.h"
#include <ctype.h>

#include <X11/keysym.h>


typedef struct _Panel {
    WMBox *box;

    char *sectionName;

    char *description;

    CallbackRec callbacks;

    WMWidget *parent;

    WMLabel *actL;
    WMList *actLs;

    WMFrame *shoF;
    WMTextField *shoT;
    WMButton *cleB;
    WMButton *defB;

    WMLabel *instructionsL;

    WMColor *white;
    WMColor *black;
    WMColor *gray;
    WMFont *font;

    /**/
    char capturing;
    char **shortcuts;
    int actionCount;
} _Panel;



#define ICON_FILE	"keyshortcuts"


/* must be in the same order as the corresponding items in actions list */
static char *keyOptions[] = {
    "RootMenuKey",
    "WindowListKey",
    "WindowMenuKey",
    "HideKey",
    "HideOthersKey",
    "MiniaturizeKey",
    "CloseKey",
    "MaximizeKey",
    "VMaximizeKey",
    "HMaximizeKey",
    "RaiseKey",
    "LowerKey",
    "RaiseLowerKey",
    "ShadeKey",
    "MoveResizeKey",
    "SelectKey",
    "FocusNextKey",
    "FocusPrevKey",
    "NextWorkspaceKey",
    "PrevWorkspaceKey",
    "NextWorkspaceLayerKey",
    "PrevWorkspaceLayerKey",
    "Workspace1Key",
    "Workspace2Key",
    "Workspace3Key",
    "Workspace4Key",
    "Workspace5Key",
    "Workspace6Key",
    "Workspace7Key",
    "Workspace8Key",
    "Workspace9Key",
    "Workspace10Key",
    "WindowShortcut1Key",
    "WindowShortcut2Key",
    "WindowShortcut3Key",
    "WindowShortcut4Key",
    "WindowShortcut5Key",
    "WindowShortcut6Key",
    "WindowShortcut7Key",
    "WindowShortcut8Key",
    "WindowShortcut9Key",
    "WindowShortcut10Key",
    "ScreenSwitchKey",
#ifdef VIRTUAL_DESKTOP
    "VirtualEdgeLeftKey",
    "VirtualEdgeRightKey",
    "VirtualEdgeUpKey",
    "VirtualEdgeDownKey",
#endif
    "ClipRaiseKey",
    "ClipLowerKey",
#ifndef XKB_MODELOCK
    "ClipRaiseLowerKey"
#else
    "ClipRaiseLowerKey",
    "ToggleKbdModeKey"
#endif /* XKB_MODELOCK */
};



#ifndef HAVE_XCONVERTCASE
/* from Xlib */

static void
XConvertCase(register KeySym sym, KeySym *lower, KeySym *upper)
{
    *lower = sym;
    *upper = sym;
    switch(sym >> 8) {
    case 0: /* Latin 1 */
        if ((sym >= XK_A) && (sym <= XK_Z))
            *lower += (XK_a - XK_A);
        else if ((sym >= XK_a) && (sym <= XK_z))
            *upper -= (XK_a - XK_A);
        else if ((sym >= XK_Agrave) && (sym <= XK_Odiaeresis))
            *lower += (XK_agrave - XK_Agrave);
        else if ((sym >= XK_agrave) && (sym <= XK_odiaeresis))
            *upper -= (XK_agrave - XK_Agrave);
        else if ((sym >= XK_Ooblique) && (sym <= XK_Thorn))
            *lower += (XK_oslash - XK_Ooblique);
        else if ((sym >= XK_oslash) && (sym <= XK_thorn))
            *upper -= (XK_oslash - XK_Ooblique);
        break;
    case 1: /* Latin 2 */
        /* Assume the KeySym is a legal value (ignore discontinuities) */
        if (sym == XK_Aogonek)
            *lower = XK_aogonek;
        else if (sym >= XK_Lstroke && sym <= XK_Sacute)
            *lower += (XK_lstroke - XK_Lstroke);
        else if (sym >= XK_Scaron && sym <= XK_Zacute)
            *lower += (XK_scaron - XK_Scaron);
        else if (sym >= XK_Zcaron && sym <= XK_Zabovedot)
            *lower += (XK_zcaron - XK_Zcaron);
        else if (sym == XK_aogonek)
            *upper = XK_Aogonek;
        else if (sym >= XK_lstroke && sym <= XK_sacute)
            *upper -= (XK_lstroke - XK_Lstroke);
        else if (sym >= XK_scaron && sym <= XK_zacute)
            *upper -= (XK_scaron - XK_Scaron);
        else if (sym >= XK_zcaron && sym <= XK_zabovedot)
            *upper -= (XK_zcaron - XK_Zcaron);
        else if (sym >= XK_Racute && sym <= XK_Tcedilla)
            *lower += (XK_racute - XK_Racute);
        else if (sym >= XK_racute && sym <= XK_tcedilla)
            *upper -= (XK_racute - XK_Racute);
        break;
    case 2: /* Latin 3 */
        /* Assume the KeySym is a legal value (ignore discontinuities) */
        if (sym >= XK_Hstroke && sym <= XK_Hcircumflex)
            *lower += (XK_hstroke - XK_Hstroke);
        else if (sym >= XK_Gbreve && sym <= XK_Jcircumflex)
            *lower += (XK_gbreve - XK_Gbreve);
        else if (sym >= XK_hstroke && sym <= XK_hcircumflex)
            *upper -= (XK_hstroke - XK_Hstroke);
        else if (sym >= XK_gbreve && sym <= XK_jcircumflex)
            *upper -= (XK_gbreve - XK_Gbreve);
        else if (sym >= XK_Cabovedot && sym <= XK_Scircumflex)
            *lower += (XK_cabovedot - XK_Cabovedot);
        else if (sym >= XK_cabovedot && sym <= XK_scircumflex)
            *upper -= (XK_cabovedot - XK_Cabovedot);
        break;
    case 3: /* Latin 4 */
        /* Assume the KeySym is a legal value (ignore discontinuities) */
        if (sym >= XK_Rcedilla && sym <= XK_Tslash)
            *lower += (XK_rcedilla - XK_Rcedilla);
        else if (sym >= XK_rcedilla && sym <= XK_tslash)
            *upper -= (XK_rcedilla - XK_Rcedilla);
        else if (sym == XK_ENG)
            *lower = XK_eng;
        else if (sym == XK_eng)
            *upper = XK_ENG;
        else if (sym >= XK_Amacron && sym <= XK_Umacron)
            *lower += (XK_amacron - XK_Amacron);
        else if (sym >= XK_amacron && sym <= XK_umacron)
            *upper -= (XK_amacron - XK_Amacron);
        break;
    case 6: /* Cyrillic */
        /* Assume the KeySym is a legal value (ignore discontinuities) */
        if (sym >= XK_Serbian_DJE && sym <= XK_Serbian_DZE)
            *lower -= (XK_Serbian_DJE - XK_Serbian_dje);
        else if (sym >= XK_Serbian_dje && sym <= XK_Serbian_dze)
            *upper += (XK_Serbian_DJE - XK_Serbian_dje);
        else if (sym >= XK_Cyrillic_YU && sym <= XK_Cyrillic_HARDSIGN)
            *lower -= (XK_Cyrillic_YU - XK_Cyrillic_yu);
        else if (sym >= XK_Cyrillic_yu && sym <= XK_Cyrillic_hardsign)
            *upper += (XK_Cyrillic_YU - XK_Cyrillic_yu);
        break;
    case 7: /* Greek */
        /* Assume the KeySym is a legal value (ignore discontinuities) */
        if (sym >= XK_Greek_ALPHAaccent && sym <= XK_Greek_OMEGAaccent)
            *lower += (XK_Greek_alphaaccent - XK_Greek_ALPHAaccent);
        else if (sym >= XK_Greek_alphaaccent && sym <= XK_Greek_omegaaccent &&
                 sym != XK_Greek_iotaaccentdieresis &&
                 sym != XK_Greek_upsilonaccentdieresis)
            *upper -= (XK_Greek_alphaaccent - XK_Greek_ALPHAaccent);
        else if (sym >= XK_Greek_ALPHA && sym <= XK_Greek_OMEGA)
            *lower += (XK_Greek_alpha - XK_Greek_ALPHA);
        else if (sym >= XK_Greek_alpha && sym <= XK_Greek_omega &&
                 sym != XK_Greek_finalsmallsigma)
            *upper -= (XK_Greek_alpha - XK_Greek_ALPHA);
        break;
    case 0x14: /* Armenian */
        if (sym >= XK_Armenian_AYB && sym <= XK_Armenian_fe) {
            *lower = sym | 1;
            *upper = sym & ~1;
        }
        break;
    }
}
#endif


static char*
captureShortcut(Display *dpy, _Panel *panel)
{
    XEvent ev;
    KeySym ksym, lksym, uksym;
    char buffer[64];
    char *key = NULL;

    while (panel->capturing) {
        XAllowEvents(dpy, AsyncKeyboard, CurrentTime);
        WMNextEvent(dpy, &ev);
        if (ev.type==KeyPress && ev.xkey.keycode!=0) {
            ksym = XKeycodeToKeysym(dpy, ev.xkey.keycode, 0);
            if (!IsModifierKey(ksym)) {
                XConvertCase(ksym, &lksym, &uksym);
                key=XKeysymToString(uksym);

                panel->capturing = 0;
                break;
            }
        }
        WMHandleEvent(&ev);
    }

    if (!key)
        return NULL;

    buffer[0] = 0;

    if (ev.xkey.state & ControlMask) {
        strcat(buffer, "Control+");
    }
    if (ev.xkey.state & ShiftMask) {
        strcat(buffer, "Shift+");
    }
    if (ev.xkey.state & Mod1Mask) {
        strcat(buffer, "Mod1+");
    }
    if (ev.xkey.state & Mod2Mask) {
        strcat(buffer, "Mod2+");
    }
    if (ev.xkey.state & Mod3Mask) {
        strcat(buffer, "Mod3+");
    }
    if (ev.xkey.state & Mod4Mask) {
        strcat(buffer, "Mod4+");
    }
    if (ev.xkey.state & Mod5Mask) {
        strcat(buffer, "Mod5+");
    }
    strcat(buffer, key);

    return wstrdup(buffer);
}


static void
captureClick(WMWidget *w, void *data)
{
    _Panel *panel = (_Panel*)data;
    Display *dpy = WMScreenDisplay(WMWidgetScreen(panel->parent));
    char *shortcut;

    if (!panel->capturing) {
        panel->capturing = 1;
        WMSetButtonText(w, _("Cancel"));
        WMSetLabelText(panel->instructionsL, _("Press the desired shortcut key(s) or click Cancel to stop capturing."));
        XGrabKeyboard(dpy, WMWidgetXID(panel->parent), True, GrabModeAsync,
                      GrabModeAsync, CurrentTime);
        shortcut = captureShortcut(dpy, panel);
        if (shortcut) {
            int row = WMGetListSelectedItemRow(panel->actLs);

            WMSetTextFieldText(panel->shoT, shortcut);
            if (row>=0) {
                if (panel->shortcuts[row])
                    wfree(panel->shortcuts[row]);
                panel->shortcuts[row] = shortcut;

                WMRedisplayWidget(panel->actLs);
            } else {
                wfree(shortcut);
            }
        }
    }
    panel->capturing = 0;
    WMSetButtonText(w, _("Capture"));
    WMSetLabelText(panel->instructionsL, _("Click on Capture to interactively define the shortcut key."));
    XUngrabKeyboard(dpy, CurrentTime);
}



static void
clearShortcut(WMWidget *w, void *data)
{
    _Panel *panel = (_Panel*)data;
    int row = WMGetListSelectedItemRow(panel->actLs);

    WMSetTextFieldText(panel->shoT, NULL);

    if (row>=0) {
        if (panel->shortcuts[row])
            wfree(panel->shortcuts[row]);
        panel->shortcuts[row]=NULL;
        WMRedisplayWidget(panel->actLs);
    }
}



static void
typedKeys(void *observerData, WMNotification *notification)
{
    _Panel *panel = (_Panel*)observerData;
    int row = WMGetListSelectedItemRow(panel->actLs);

    if (row<0)
        return;

    if (panel->shortcuts[row])
        wfree(panel->shortcuts[row]);
    panel->shortcuts[row] = WMGetTextFieldText(panel->shoT);
    if (strlen(panel->shortcuts[row])==0) {
        wfree(panel->shortcuts[row]);
        panel->shortcuts[row] = NULL;
    }
    WMRedisplayWidget(panel->actLs);
}



static void
listClick(WMWidget *w, void *data)
{
    _Panel *panel = (_Panel*)data;
    int row = WMGetListSelectedItemRow(w);

    WMSetTextFieldText(panel->shoT, panel->shortcuts[row]);
}


static char*
trimstr(char *str)
{
    char *p = str;
    int i;

    while (isspace(*p)) p++;
    p = wstrdup(p);
    i = strlen(p);
    while (isspace(p[i]) && i>0) {
        p[i]=0;
        i--;
    }

    return p;
}


static void
showData(_Panel *panel)
{
    char *str;
    int i;

    for (i=0; i<panel->actionCount; i++) {

        str = GetStringForKey(keyOptions[i]);
        if (panel->shortcuts[i])
            wfree(panel->shortcuts[i]);
        if (str)
            panel->shortcuts[i] = trimstr(str);
        else
            panel->shortcuts[i] = NULL;

        if (panel->shortcuts[i] &&
            (strcasecmp(panel->shortcuts[i], "none")==0
             || strlen(panel->shortcuts[i])==0)) {
            wfree(panel->shortcuts[i]);
            panel->shortcuts[i] = NULL;
        }
    }
    WMRedisplayWidget(panel->actLs);
}


static void
paintItem(WMList *lPtr, int index, Drawable d, char *text, int state,
          WMRect *rect)
{
    int width, height, x, y;
    _Panel *panel = (_Panel*)WMGetHangedData(lPtr);
    WMScreen *scr = WMWidgetScreen(lPtr);
    Display *dpy = WMScreenDisplay(scr);
    WMColor *backColor = (state & WLDSSelected) ? panel->white : panel->gray;

    width = rect->size.width;
    height = rect->size.height;
    x = rect->pos.x;
    y = rect->pos.y;

    XFillRectangle(dpy, d, WMColorGC(backColor), x, y, width, height);

    if (panel->shortcuts[index]) {
        WMPixmap *pix = WMGetSystemPixmap(scr, WSICheckMark);
        WMSize size = WMGetPixmapSize(pix);

        WMDrawPixmap(pix, d, x+(20-size.width)/2, (height-size.height)/2+y);
        WMReleasePixmap(pix);
    }

    WMDrawString(scr, d, panel->black, panel->font, x+20, y, text, strlen(text));
}


static void
createPanel(Panel *p)
{
    _Panel *panel = (_Panel*)p;
    WMScreen *scr = WMWidgetScreen(panel->parent);
    WMColor *color;
    WMFont *boldFont;

    panel->capturing = 0;

    panel->white = WMWhiteColor(scr);
    panel->black = WMBlackColor(scr);
    panel->gray = WMGrayColor(scr);
    panel->font = WMSystemFontOfSize(scr, 12);

    panel->box = WMCreateBox(panel->parent);
    WMSetViewExpandsToParent(WMWidgetView(panel->box), 2, 2, 2, 2);

    boldFont = WMBoldSystemFontOfSize(scr, 12);

    /* **************** Actions **************** */
    panel->actL = WMCreateLabel(panel->box);
    WMResizeWidget(panel->actL, 280, 20);
    WMMoveWidget(panel->actL, 20, 10);
    WMSetLabelFont(panel->actL, boldFont);
    WMSetLabelText(panel->actL, _("Actions"));
    WMSetLabelRelief(panel->actL, WRSunken);
    WMSetLabelTextAlignment(panel->actL, WACenter);
    color = WMDarkGrayColor(scr);
    WMSetWidgetBackgroundColor(panel->actL, color);
    WMReleaseColor(color);
    WMSetLabelTextColor(panel->actL, panel->white);

    panel->actLs = WMCreateList(panel->box);
    WMResizeWidget(panel->actLs, 280, 190);
    WMMoveWidget(panel->actLs, 20, 32);
    WMSetListUserDrawProc(panel->actLs, paintItem);
    WMHangData(panel->actLs, panel);

    WMAddListItem(panel->actLs, _("Open applications menu"));
    WMAddListItem(panel->actLs, _("Open window list menu"));
    WMAddListItem(panel->actLs, _("Open window commands menu"));
    WMAddListItem(panel->actLs, _("Hide active application"));
    WMAddListItem(panel->actLs, _("Hide other applications"));
    WMAddListItem(panel->actLs, _("Miniaturize active window"));
    WMAddListItem(panel->actLs, _("Close active window"));
    WMAddListItem(panel->actLs, _("Maximize active window"));
    WMAddListItem(panel->actLs, _("Maximize active window vertically"));
    WMAddListItem(panel->actLs, _("Maximize active window horizontally"));
    WMAddListItem(panel->actLs, _("Raise active window"));
    WMAddListItem(panel->actLs, _("Lower active window"));
    WMAddListItem(panel->actLs, _("Raise/Lower window under mouse pointer"));
    WMAddListItem(panel->actLs, _("Shade active window"));
    WMAddListItem(panel->actLs, _("Move/Resize active window"));
    WMAddListItem(panel->actLs, _("Select active window"));
    WMAddListItem(panel->actLs, _("Focus next window"));
    WMAddListItem(panel->actLs, _("Focus previous window"));
    WMAddListItem(panel->actLs, _("Switch to next workspace"));
    WMAddListItem(panel->actLs, _("Switch to previous workspace"));
    WMAddListItem(panel->actLs, _("Switch to next ten workspaces"));
    WMAddListItem(panel->actLs, _("Switch to previous ten workspaces"));
    WMAddListItem(panel->actLs, _("Switch to workspace 1"));
    WMAddListItem(panel->actLs, _("Switch to workspace 2"));
    WMAddListItem(panel->actLs, _("Switch to workspace 3"));
    WMAddListItem(panel->actLs, _("Switch to workspace 4"));
    WMAddListItem(panel->actLs, _("Switch to workspace 5"));
    WMAddListItem(panel->actLs, _("Switch to workspace 6"));
    WMAddListItem(panel->actLs, _("Switch to workspace 7"));
    WMAddListItem(panel->actLs, _("Switch to workspace 8"));
    WMAddListItem(panel->actLs, _("Switch to workspace 9"));
    WMAddListItem(panel->actLs, _("Switch to workspace 10"));
    WMAddListItem(panel->actLs, _("Shortcut for window 1"));
    WMAddListItem(panel->actLs, _("Shortcut for window 2"));
    WMAddListItem(panel->actLs, _("Shortcut for window 3"));
    WMAddListItem(panel->actLs, _("Shortcut for window 4"));
    WMAddListItem(panel->actLs, _("Shortcut for window 5"));
    WMAddListItem(panel->actLs, _("Shortcut for window 6"));
    WMAddListItem(panel->actLs, _("Shortcut for window 7"));
    WMAddListItem(panel->actLs, _("Shortcut for window 8"));
    WMAddListItem(panel->actLs, _("Shortcut for window 9"));
    WMAddListItem(panel->actLs, _("Shortcut for window 10"));
    WMAddListItem(panel->actLs, _("Switch to Next Screen/Monitor"));
#ifdef VIRTUAL_DESKTOP
    WMAddListItem(panel->actLs, _("Move VirtualDesktop to next left edge"));
    WMAddListItem(panel->actLs, _("Move VirtualDesktop to next right edge"));
    WMAddListItem(panel->actLs, _("Move VirtualDesktop to next top edge"));
    WMAddListItem(panel->actLs, _("Move VirtualDesktop to next bottom edge"));
#endif
    WMAddListItem(panel->actLs, _("Raise Clip"));
    WMAddListItem(panel->actLs, _("Lower Clip"));
    WMAddListItem(panel->actLs, _("Raise/Lower Clip"));
#ifdef XKB_MODELOCK
    WMAddListItem(panel->actLs, _("Toggle keyboard language"));
#endif /* XKB_MODELOCK */

    WMSetListAction(panel->actLs, listClick, panel);

    panel->actionCount = WMGetListNumberOfRows(panel->actLs);
    panel->shortcuts = wmalloc(sizeof(char*)*panel->actionCount);
    memset(panel->shortcuts, 0, sizeof(char*)*panel->actionCount);

    /***************** Shortcut ****************/

    panel->shoF = WMCreateFrame(panel->box);
    WMResizeWidget(panel->shoF, 190, 210);
    WMMoveWidget(panel->shoF, 315, 10);
    WMSetFrameTitle(panel->shoF, _("Shortcut"));

    panel->shoT = WMCreateTextField(panel->shoF);
    WMResizeWidget(panel->shoT, 160, 20);
    WMMoveWidget(panel->shoT, 15, 65);
    WMAddNotificationObserver(typedKeys, panel,
                              WMTextDidChangeNotification, panel->shoT);

    panel->cleB = WMCreateCommandButton(panel->shoF);
    WMResizeWidget(panel->cleB, 75, 24);
    WMMoveWidget(panel->cleB, 15, 95);
    WMSetButtonText(panel->cleB, _("Clear"));
    WMSetButtonAction(panel->cleB, clearShortcut, panel);

    panel->defB = WMCreateCommandButton(panel->shoF);
    WMResizeWidget(panel->defB, 75, 24);
    WMMoveWidget(panel->defB, 100, 95);
    WMSetButtonText(panel->defB, _("Capture"));
    WMSetButtonAction(panel->defB, captureClick, panel);

    panel->instructionsL = WMCreateLabel(panel->shoF);
    WMResizeWidget(panel->instructionsL, 160, 55);
    WMMoveWidget(panel->instructionsL, 15, 140);
    WMSetLabelTextAlignment(panel->instructionsL, WACenter);
    WMSetLabelWraps(panel->instructionsL, True);
    WMSetLabelText(panel->instructionsL, _("Click on Capture to interactively define the shortcut key."));

    WMMapSubwidgets(panel->shoF);

    WMReleaseFont(boldFont);

    WMRealizeWidget(panel->box);
    WMMapSubwidgets(panel->box);


    showData(panel);
}


static void
storeData(_Panel *panel)
{
    int i;
    char *str;

    for (i=0; i<panel->actionCount; i++) {
        str = NULL;
        if (panel->shortcuts[i]) {
            str = trimstr(panel->shortcuts[i]);
            if (strlen(str)==0) {
                wfree(str);
                str = NULL;
            }
        }
        if (str) {
            SetStringForKey(str, keyOptions[i]);
            wfree(str);
        } else {
            SetStringForKey("None", keyOptions[i]);
        }
    }
}



Panel*
InitKeyboardShortcuts(WMScreen *scr, WMWidget *parent)
{
    _Panel *panel;

    panel = wmalloc(sizeof(_Panel));
    memset(panel, 0, sizeof(_Panel));

    panel->sectionName = _("Keyboard Shortcut Preferences");

    panel->description = _("Change the keyboard shortcuts for actions such\n"
                           "as changing workspaces and opening menus.");

    panel->parent = parent;

    panel->callbacks.createWidgets = createPanel;
    panel->callbacks.updateDomain = storeData;

    AddSection(panel, ICON_FILE);

    return panel;
}



syntax highlighted by Code2HTML, v. 0.9.1