/*
 *  Copyright (c) 2004 Gunnar Schmi Dt <gunnar@schmi-dt.de>
 *  Copyright (c) 2008 Sebastian Sauer <mail@dipe.org>
 *
 *  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
 */

#include "kbstateapplet.h"

#include <QGraphicsLinearLayout>

#include <QtGui/QPainter>
#include <QtGui/qdrawutil.h>
#include <QtGui/QCursor>
#include <QtGui/QImage>
#include <QtGui/QMouseEvent>
#include <QtCore/QTimerEvent>
#include <QtGui/QResizeEvent>
#include <QtCore/QProcess>
#include <QtGui/QX11Info>
#include <QtGui/QGraphicsScene>
#include <QtGui/QGraphicsView>
#include <QtGui/QGraphicsProxyWidget>
#include <QtGui/QCheckBox>

#include <kapplication.h>
#include <klocale.h>
#include <kglobal.h>
#include <kglobalsettings.h>
#include <kcolorscheme.h>
#include <kiconloader.h>
#include <kiconeffect.h>
#include <kdemacros.h>

struct ModifierKey {
   const unsigned int mask;
   const KeySym keysym;
   const char *name;
   const char *icon;
   const char *text;
   const bool isModifier;
};

static ModifierKey modifierKeys[] = {
    { ShiftMask, 0, I18N_NOOP("Shift"), "shiftkey", "", true },
    { ControlMask, 0, I18N_NOOP("Control"), "controlkey", "", true },
    { 0, XK_Alt_L, I18N_NOOP("Alt"), "altkey", "", true },
    { 0, 0, I18N_NOOP("Win"), "superkey", "", true },
    { 0, XK_Meta_L, I18N_NOOP("Meta"), "metakey", "", true },
    { 0, XK_Super_L, I18N_NOOP("Super"), "superkey", "", true },
    { 0, XK_Hyper_L, I18N_NOOP("Hyper"), "hyperkey", "", true },
    { 0, 0, I18N_NOOP("Alt Graph"), "", I18N_NOOP("æ"), true },
    { 0, XK_Num_Lock, I18N_NOOP("Num Lock"), "lockkey", I18N_NOOP("Num"), false },
    { LockMask, 0, I18N_NOOP("Caps Lock"), "capskey", "", false },
    { 0, XK_Scroll_Lock, I18N_NOOP("Scroll Lock"), "lockkey", I18N_NOOP("Scroll"), false },
    { 0, 0, "", "", "", false }
};

/********************************************************************/

KbStateWidget::KbStateWidget(KbStateApplet *applet, QWidget *parent)
    : QWidget(parent)
    , m_applet(applet)
    , m_size(20)
    , componentData("kbstateapplet")
{
    setAttribute(Qt::WA_NoSystemBackground);

    for (int i = 0; i < 8; i++)
        icons[i] = 0;
    m_iconLoader = new KIconLoader(componentData.componentName(), componentData.dirs());
    state = 0;

    for (int i = 0; strcmp(modifierKeys[i].name, "") != 0; i++) {
        int mask = modifierKeys[i].mask;
        if (mask == 0)
        {
            if (modifierKeys[i].keysym != 0)
                mask = XkbKeysymToModifiers (this->x11Display(), modifierKeys[i].keysym);
#if 0 //TODO kde4: how to port it ?
            else if (strcmp(modifierKeys[i].name, "Win") == 0)
                mask = KKeyNative::modXWin();
#endif
            else
                mask = XkbKeysymToModifiers (this->x11Display(), XK_Mode_switch)
                    | XkbKeysymToModifiers (this->x11Display(), XK_ISO_Level3_Shift)
                    | XkbKeysymToModifiers (this->x11Display(), XK_ISO_Level3_Latch)
                    | XkbKeysymToModifiers (this->x11Display(), XK_ISO_Level3_Lock);
        }
        int map = 0;
        for (map = 0; map < 8; map++) {
            if ((mask & (1 << map)) != 0)
                break;
        }
        if ((map <= 7) && !(icons[map])) {
            icons[map] = new KeyIcon (i, m_iconLoader, this, modifierKeys[i].name);
            icons[map]->setToolTip( i18n (modifierKeys[i].name));
            connect (icons[map], SIGNAL(stateChangeRequest (KeyIcon*,bool,bool)), SLOT(stateChangeRequest (KeyIcon*,bool,bool)));
            if (modifierKeys[i].isModifier)
                modifiers.append(icons[map]);
            else
                lockkeys.append(icons[map]);
        }
    }

    mouse = new MouseIcon (m_iconLoader, this, "mouse");
    sticky = new TimeoutIcon (m_iconLoader, "", "kbstate_stickykeys", this, "sticky");
    slow = new TimeoutIcon (m_iconLoader, "", "kbstate_slowkeys", this, "slow");
    bounce = new TimeoutIcon (m_iconLoader, "", "", this, "bounce");

    xkb = XkbGetMap(QX11Info::display(), 0, XkbUseCoreKbd);
    if (xkb != 0) {
        XkbGetControls (QX11Info::display(), XkbAllControlsMask, xkb);
        if (xkb->ctrls != 0)
            accessxFeatures = xkb->ctrls->enabled_ctrls;
        else
            accessxFeatures = 0;
    }
    else
        accessxFeatures = 0;

    //startTimer(100); // ten times a second
    connect(KGlobalSettings::self(), SIGNAL(kdisplayPaletteChanged()), SLOT(paletteChanged()));

    kapp->installX11EventFilter (this);
    int opcode_rtn, error_rtn;
    XkbQueryExtension (QX11Info::display(), &opcode_rtn, &xkb_base_event_type,  &error_rtn, NULL, NULL);
    XkbSelectEvents (QX11Info::display(), XkbUseCoreKbd, XkbAllEventsMask, XkbAllEventsMask);
}

KbStateWidget::~KbStateWidget() {
   delete m_iconLoader;
}

// Calculates the layout based on a given number of modifiers, lockkeys and accessx features.
// Output:
// lines:  number of lines
// length: number of icons per line
// size:   size of the icons
void calculateSizes (int space, int modifiers, int lockkeys, int accessx, bool showMouse, int &lines, int &length, int &size)
{
    // Calculate lines and length
    if (showMouse)
        ++accessx;

    lines = space>=size ? space/size : 1;
    length = modifiers + lockkeys + accessx;

    if (length > 0 && lines >= 2) {
        length = (length + lines-1)/lines;

        // As we want to have some line breaks, we need to do some corrections:
        // Calculate the number of lines that are really needed:
        int linesNeeded = (modifiers+length-1)/length + (lockkeys+length-1)/length;
        int tmp1 = modifiers%length == 0 ? 0 : length - modifiers%length;
        int tmp2 = lockkeys%length == 0 ? 0 : length - lockkeys%length;
        if ((tmp1 + tmp2) < accessx)
            linesNeeded = (modifiers+lockkeys+accessx + length-1)/length;

        // If we need more lines than we have, try with more icons per line:
        while (linesNeeded > lines) {
            length++;
            linesNeeded = (modifiers+length-1)/length + (lockkeys+length-1)/length;
            int tmp1 = modifiers%length == 0 ? 0 : length - modifiers%length;
            int tmp2 = lockkeys%length == 0 ? 0 : length - lockkeys%length;
            if ((tmp1 + tmp2) < accessx)
                linesNeeded = (modifiers+lockkeys+accessx + length-1)/length;
        }
        lines = linesNeeded;
    }
}

int KbStateWidget::widthForHeight(int h) const {
    int lines, length;
    int size = m_size;
    int accessx = 0;
    if ((accessxFeatures & XkbStickyKeysMask) != 0)
        accessx++;
    if ((accessxFeatures & XkbSlowKeysMask) != 0)
        accessx++;
    if ((accessxFeatures & XkbBounceKeysMask) != 0)
        accessx++;
    calculateSizes (h, showModifiers?modifiers.count():0, showLockkeys?lockkeys.count():0, showAccessX?accessx:0, showMouse, lines, length, size);
    size = h/lines;
    return length*size;
}

int KbStateWidget::heightForWidth(int w) const {
    int lines, length;
    int size = m_size;
    int accessx = 0;
    if ((accessxFeatures & XkbStickyKeysMask) != 0)
        accessx++;
    if ((accessxFeatures & XkbSlowKeysMask) != 0)
        accessx++;
    if ((accessxFeatures & XkbBounceKeysMask) != 0)
        accessx++;
    calculateSizes (w, showModifiers?modifiers.count():0, showLockkeys?lockkeys.count():0, showAccessX?accessx:0, showMouse, lines, length, size);
    size = w/lines;
    return length*size;
}

int KbStateWidget::orientation() const {
    return m_applet->formFactor() == Plasma::Vertical ? Qt::Vertical : Qt::Horizontal;
}

Display* KbStateWidget::x11Display() const {
    return QX11Info::display();
}

void KbStateWidget::resizeEvent( QResizeEvent*e ) {
    QWidget::resizeEvent(e);
    layout();
}

void KbStateWidget::layout() {
    int size = m_size;

    int lines, length, x,y,dx,dy, ldx,ldy;
    int modifierCount = showModifiers?modifiers.count():0;
    int lockkeyCount  = showLockkeys?lockkeys.count():0;
    int accessxCount  = 0;

    if (showAccessX) {
        if ((accessxFeatures & XkbStickyKeysMask) != 0)
            accessxCount++;
        if ((accessxFeatures & XkbSlowKeysMask) != 0)
            accessxCount++;
        if ((accessxFeatures & XkbBounceKeysMask) != 0)
            accessxCount++;
    }

    if (orientation() == Qt::Vertical) {
        calculateSizes (height(), modifierCount, lockkeyCount, accessxCount, showMouse, lines, length, size);
        size = height()/lines;
        x  = 0;
        y  = (height()-lines*size)/2;
        dx = size;
        dy = 0;
        ldx= 0;
        ldy= size;
    }
    else {
        calculateSizes (width(), modifierCount, lockkeyCount, accessxCount, showMouse, lines, length, size);
        size = width()/lines;
        x  = (width()-lines*size)/2;
        y  = 0;
        dx = 0;
        dy = size;
        ldx= size;
        ldy= 0;
    }

    int item = 1;
    foreach(StatusIcon* icon, modifiers) {
        if (showModifiers) {
            icon->setGeometry (x, y, size, size);
            icon->show();
            icon->update();
            item++; x+=dx; y+=dy;
            if (item > length) {
                x = x - length*dx + ldx;
                y = y - length*dy + ldy;
                item = 1;
            }
        }
        else {
            icon->hide();
        }
    }

    int lockkeyLines = (lockkeyCount+length-1)/qMax(1,length);
    int accessxLines = lines - (modifierCount+length-1)/qMax(1,length) - lockkeyLines;

    if (showMouse)
        ++accessxCount;

    if (lockkeyLines*length + accessxLines*length >= lockkeyCount + accessxCount) {
        if (lines > 1 && item > 1) {
            x = x - (item-1)*dx + ldx;
            y = y - (item-1)*dy + ldy;
            item = 1;
        }
    }
    else {
        accessxLines++;
    }

	if (showMouse && showAccessX && accessxLines > 0) {
        mouse->setGeometry (x, y, size, size);
        mouse->show();
        mouse->update();
        accessxCount--;
        item++; x+=dx; y+=dy;
        if (item > length) {
            x = x - length*dx + ldx;
            y = y - length*dy + ldy;
            item = 1;
            accessxLines--;
        }
    }
    else {
        mouse->hide();
    }

    if ((accessxFeatures & XkbStickyKeysMask) != 0 && showAccessX && accessxLines > 0) {
        sticky->setGeometry (x, y, size, size);
        sticky->show();
        sticky->update();
        accessxCount--;
        item++; x+=dx; y+=dy;
        if (item > length) {
            x = x - length*dx + ldx;
            y = y - length*dy + ldy;
            item = 1;
            accessxLines--;
        }
    }
    else {
        sticky->hide();
    }

    if ((accessxFeatures & XkbSlowKeysMask) != 0 && showAccessX && accessxLines > 0) {
        slow->setGeometry (x, y, size, size);
        slow->show();
        slow->update();
        accessxCount--;
        item++; x+=dx; y+=dy;
        if (item > length) {
            x = x - length*dx + ldx;
            y = y - length*dy + ldy;
            item = 1;
            accessxLines--;
        }
    }
    else {
        slow->hide();
    }

    if ((accessxFeatures & XkbBounceKeysMask) != 0 && showAccessX && accessxLines > 0) {
        bounce->setGeometry (x, y, size, size);
        bounce->show();
        bounce->update();
        accessxCount--;
        item++; x+=dx; y+=dy;
        if (item > length) {
            x = x - length*dx + ldx;
            y = y - length*dy + ldy;
            item = 1;
            accessxLines--;
        }
    }
    else {
        bounce->hide();
    }

    if (lines > 1) {
        if (item != 1) {
            x = x - (item-1)*dx + ldx;
            y = y - (item-1)*dy + ldy;
        }
        item = 1;
    }
    foreach(StatusIcon *icon, lockkeys) {
        if (showLockkeys) {
            icon->setGeometry (x, y, size, size);
            icon->show();
            icon->update();
            item++; x+=dx; y+=dy;
            if (item > length) {
                x = x - length*dx + ldx;
                y = y - length*dy + ldy;
                item = 1;
            }
        }
        else {
            icon->hide();
        }
    }

    if ((accessxFeatures & XkbBounceKeysMask) != 0 && showAccessX && accessxCount > 0) {
        bounce->setGeometry (x, y, size, size);
        bounce->show();
        bounce->update();
        item++; x+=dx; y+=dy;
        accessxCount--;
    }
    if ((accessxFeatures & XkbSlowKeysMask) != 0 && showAccessX && accessxCount > 0) {
        slow->setGeometry (x, y, size, size);
        slow->show();
        slow->update();
        item++; x+=dx; y+=dy;
        accessxCount--;
    }
    if ((accessxFeatures & XkbStickyKeysMask) != 0 && showAccessX && accessxCount > 0) {
        sticky->setGeometry (x, y, size, size);
        sticky->show();
        sticky->update();
        item++; x+=dx; y+=dy;
        accessxCount--;
    }
    if (showMouse && accessxCount > 0) {
        mouse->setGeometry (x, y, size, size);
        mouse->show();
        mouse->update();
        item++; x+=dx; y+=dy;
        accessxCount--;
    }
}

void KbStateWidget::paletteChanged() {
    for (int i = 0; i < 8; i++) {
        if (icons[i])
            icons[i]->updateImages();
    }
    mouse->update();
    sticky->update();
    slow->update();
    bounce->update();
}

bool KbStateWidget::x11Event (XEvent *evt) {
    if (evt->type == xkb_base_event_type + XkbEventCode) {
        XkbEvent *kbevt = (XkbEvent *)evt;
        switch (kbevt->any.xkb_type) {
            case XkbStateNotify:
                timerEvent (0);
                mouse->setState (kbevt->state.ptr_buttons);
                break;
            case XkbAccessXNotify:
                switch (kbevt->accessx.detail) {
                    case XkbAXN_SKPress:
                        slow->setGlyth(i18nc("a (the first letter in the alphabet)", "a"));
                        slow->setImage("unlatched");
                        break;
                    case XkbAXN_SKAccept:
                        slow->setImage("keypressok");
                        break;
                    case XkbAXN_SKRelease:
                        slow->setGlyth(" ");
                        slow->setImage("kbstate_slowkeys");
                        break;
                    case XkbAXN_SKReject:
                        slow->setImage("keypressno", kbevt->accessx.sk_delay>150?kbevt->accessx.sk_delay:150);
                        break;
                    case XkbAXN_BKAccept:
                        slow->setGlyth(i18nc("a (the first letter in the alphabet)", "a"));
                        bounce->setImage("keypressok", kbevt->accessx.sk_delay>150?kbevt->accessx.sk_delay:150);
                        break;
                    case XkbAXN_BKReject:
                        slow->setGlyth(i18nc("a (the first letter in the alphabet)", "a"));
                        bounce->setImage("keypressno", kbevt->accessx.sk_delay>150?kbevt->accessx.sk_delay:150);
                        break;
                }
                break;
            case XkbControlsNotify: {
                XkbControlsNotifyEvent* event = (XkbControlsNotifyEvent*)evt;
                accessxFeatures = event->enabled_ctrls;
                if ((accessxFeatures & XkbMouseKeysMask) != 0) {
                    XkbGetControls (QX11Info::display(), XkbMouseKeysMask, xkb);
                    if (xkb->ctrls->mk_dflt_btn < 1)
                        mouse->setActiveKey (1);
                    else if (xkb->ctrls->mk_dflt_btn > 3)
                        mouse->setActiveKey (1);
                    else
                        mouse->setActiveKey (xkb->ctrls->mk_dflt_btn);
                }
                else
                    mouse->setActiveKey (0);
                layout();
                updateGeometry();
                emit updateLayout();
            } break;
            case XkbExtensionDeviceNotify: // This is a hack around the fact that XFree86's XKB doesn't give AltGr notifications
                break;
            default:
                break;
        }
    }
    return false;
}

void KbStateWidget::timerEvent(QTimerEvent*) {
    XkbStateRec state_return;
    XkbGetState (this->x11Display(), XkbUseCoreKbd, &state_return);
    unsigned char latched = XkbStateMods (&state_return);
    unsigned char locked  = XkbModLocks  (&state_return);
    int mods = ((int)latched)<<8 | locked;
    if (state != mods) {
        state = mods;
        for (int i = 0; i < 8; i++) {
            if (icons[i])
                icons[i]->setState ((latched&(1<<i)) != 0, (locked&(1<<i)) != 0);
        }
    }
}

void KbStateWidget::stateChangeRequest (KeyIcon *source, bool latched, bool locked) {
    for (int i = 0; i < 8; i++) {
        if (icons[i] == source) {
            if (locked) {
                XkbLockModifiers (this->x11Display(), XkbUseCoreKbd, 1<<i, 1<<i);
            }
            else if (latched) {
                XkbLockModifiers (this->x11Display(), XkbUseCoreKbd, 1<<i, 0);
                XkbLatchModifiers (this->x11Display(), XkbUseCoreKbd, 1<<i, 1<<i);
            }
            else {
                XkbLockModifiers (this->x11Display(), XkbUseCoreKbd, 1<<i, 0);
                XkbLatchModifiers (this->x11Display(), XkbUseCoreKbd, 1<<i, 0);
            }
        }
    }
}

void KbStateWidget::updateSettings() {
    layout();
    updateGeometry();
    emit updateLayout();
}

/********************************************************************/

KeyIcon::KeyIcon (int keyId, KIconLoader *iconLoader, QWidget *parent, const char *name)
    : StatusIcon (modifierKeys[keyId].name, parent, name)
{
    this->iconLoader = iconLoader;
    this->keyId = keyId;
    this->tristate = (modifierKeys[keyId].isModifier);
    isLocked  = false;
    isLatched = false;
    updateImages ();
    connect (this, SIGNAL(clicked()), SLOT(clickedSlot()));
}

KeyIcon::~KeyIcon () {
}

void KeyIcon::setState (bool latched, bool locked) {
   latched = latched | locked;
   isLatched = latched;
   isLocked  = locked;
   update();
}

void KeyIcon::clickedSlot () {
    if (tristate)
        emit stateChangeRequest (this, !isLocked, isLatched&!isLocked);
    else
        emit stateChangeRequest (this, false, !isLocked);
}

void KeyIcon::resizeEvent( QResizeEvent*e ) {
    QWidget::resizeEvent(e);
    updateImages();
}

void KeyIcon::updateImages () {
    int size = width()<height() ? width() : height();
    locked    = iconLoader->loadIcon("object-locked", KIconLoader::Panel, size-4);
    if (strcmp(modifierKeys[keyId].icon, "")) {
        latched   = iconLoader->loadIcon(modifierKeys[keyId].icon, KIconLoader::NoGroup, size-4);
        unlatched = iconLoader->loadIcon(modifierKeys[keyId].icon, KIconLoader::NoGroup, size-4);
        QImage img = latched.toImage();
        KIconEffect::colorize(img,  KColorScheme(QPalette::Active, KColorScheme::Selection).foreground().color(), 1.0);
        latched = QPixmap::fromImage(img);
        img = unlatched.toImage();
        KIconEffect::colorize(img, KColorScheme(QPalette::Active, KColorScheme::View).foreground().color(), 1.0);
        unlatched = QPixmap::fromImage(img);
    }
    update();
}

void KeyIcon::paintEvent(QPaintEvent *) {
    QPainter p(this);

    QColor black;
    int x = (width()-locked.width())/2;
    int y = (height()-locked.height())/2;
    int o = 0;
    if (isLocked || isLatched) {
        qDrawShadePanel (&p, 0, 0, width(), height(), KColorScheme(QPalette::Active, KColorScheme::Selection).shade(KColorScheme::LightShade), true, 1, NULL);
        p.fillRect (1,1,width()-2,height()-2, KColorScheme(QPalette::Active, KColorScheme::Selection).background().color());
        if (strcmp(modifierKeys[keyId].icon, ""))
            p.drawPixmap (x+1,y+1, latched);
        black = KColorScheme(QPalette::Active, KColorScheme::Selection).foreground().color();
        o = 1;
    }
    else {
        qDrawShadePanel (&p, 0, 0, width(), height(), KColorScheme(QPalette::Active, KColorScheme::View).shade(KColorScheme::LightShade), false, 1, NULL);
        p.fillRect (1,1,width()-2,height()-2, KColorScheme(QPalette::Active, KColorScheme::View).background().color());
        if (strcmp(modifierKeys[keyId].icon, ""))
            p.drawPixmap (x,y, unlatched);
        black = KColorScheme(QPalette::Active, KColorScheme::View).foreground().color();
    }

    QString text = modifierKeys[keyId].text; //i18n( modifierKeys[keyId].text );
    if (!text.isEmpty() && !text.isNull()) {
        QFont font = KGlobalSettings::generalFont();
        font.setWeight(QFont::Black);
        QFontMetrics metrics(font);
        QRect rect = metrics.boundingRect (text);
        int size;
        if (!strcmp(modifierKeys[keyId].name, "Alt Graph"))
            size = rect.width()>rect.height()?rect.width():rect.height();
        else
            size = rect.width()>12*rect.height()/5?rect.width():12*rect.height()/5;
        if (font.pixelSize() != -1)
            font.setPixelSize (font.pixelSize()*width()*19/size/32);
        else
            font.setPointSizeF (font.pointSizeF()*width()*19/size/32);
        p.setPen (black);
        p.setFont (font);
        if (!strcmp(modifierKeys[keyId].name, "Alt Graph"))
            p.drawText (o,o, width(), height(), Qt::AlignCenter, text);
        else
            p.drawText (o,o, width(), height()*(251)/384, Qt::AlignCenter, text);
    }
    if (tristate && isLocked) {
        p.drawPixmap(x+o,y+o, locked);
    }
}

/********************************************************************/

MouseIcon::MouseIcon (KIconLoader *iconLoader, QWidget *parent, const char *name)
    : StatusIcon ("", parent, name)
{
    this->iconLoader = iconLoader;
    state = 0;
    activekey = 0;
    updateImages ();
}

MouseIcon::~MouseIcon () {
}

void MouseIcon::setState (int state) {
    this->state = state;
    update();
}

void MouseIcon::setActiveKey (int activekey) {
    this->activekey = activekey;
    update();
}

void MouseIcon::resizeEvent( QResizeEvent*e ) {
    QWidget::resizeEvent(e);
    updateImages();
}

QPixmap loadIcon(KIconLoader *iconLoader, int size, const QColor &color, const QString &name) {
    QPixmap result = iconLoader->loadIcon(name, KIconLoader::NoGroup, size);
    QImage img = result.toImage();
    KIconEffect::colorize(img, color, 1.0);
    result = QPixmap::fromImage(img);
    return result;
}

void MouseIcon::updateImages () {
    int size = width()<height() ? width() : height();
    QColor textcolor = KColorScheme(QPalette::Active, KColorScheme::View).foreground().color();
    QColor basecolor = KColorScheme(QPalette::Active, KColorScheme::View).background().color();
    mouse = loadIcon (iconLoader, size, textcolor, "kbstate_mouse");
    leftSelected   = loadIcon (iconLoader, size, textcolor, "kbstate_mouse_left_selected");
    middleSelected = loadIcon (iconLoader, size, textcolor, "kbstate_mouse_mid_selected");
    rightSelected  = loadIcon (iconLoader, size, textcolor, "kbstate_mouse_right_selected");
    leftDot   = loadIcon (iconLoader, size, textcolor, "kbstate_mouse_left");
    middleDot = loadIcon (iconLoader, size, textcolor, "kbstate_mouse_mid");
    rightDot  = loadIcon (iconLoader, size, textcolor, "kbstate_mouse_right");
    leftDotSelected   = loadIcon (iconLoader, size, basecolor, "kbstate_mouse_left");
    middleDotSelected = loadIcon (iconLoader, size, basecolor, "kbstate_mouse_mid");
    rightDotSelected  = loadIcon (iconLoader, size, basecolor, "kbstate_mouse_right");
    update();
}

void MouseIcon::paintEvent(QPaintEvent *) {
    QPainter p(this);
    qDrawShadePanel (&p, 0, 0, width(), height(), KColorScheme(QPalette::Active, KColorScheme::Selection).shade(KColorScheme::LightShade), true, 1, NULL);
    p.fillRect (1,1,width()-2,height()-2, KColorScheme(QPalette::Active, KColorScheme::View).background().color());
    p.drawPixmap(0,0, mouse);
    if ((state & Button1Mask) != 0)
        p.drawPixmap(0,0, leftSelected);
    if ((state & Button2Mask) != 0)
        p.drawPixmap(0,0, middleSelected);
    if ((state & Button3Mask) != 0)
        p.drawPixmap(0,0, rightSelected);
    switch (activekey) {
        case 0:
            break;
        case 1:
            if ((state & Button1Mask) != 0)
                p.drawPixmap(0,0, leftDotSelected);
            else
                p.drawPixmap(0,0, leftDot);
            break;
        case 2:
            if ((state & Button2Mask) != 0)
                p.drawPixmap(0,0, middleDotSelected);
            else
                p.drawPixmap(0,0, middleDot);
            break;
        case 3:
            if ((state & Button3Mask) != 0)
                p.drawPixmap(0,0, rightDotSelected);
            else
                p.drawPixmap(0,0, rightDot);
            break;
    }
}

/********************************************************************/

TimeoutIcon::TimeoutIcon (KIconLoader *iconLoader, const QString &text, const QString &featurename, QWidget *parent, const char *name)
    : StatusIcon (text, parent, name)
{
    this->iconLoader = iconLoader;
    this->featurename = featurename;
    glyth = " ";
    setImage (featurename);
    connect (&timer, SIGNAL(timeout()), this, SLOT(timeout()));
}

TimeoutIcon::~TimeoutIcon () {
}

void TimeoutIcon::update () {
    int size = width()<height() ? width() : height();
    if (pixmap.width() != size)
        pixmap = iconLoader->loadIcon(iconname, KIconLoader::NoGroup, size);
    QImage img = pixmap.toImage();
    KIconEffect::colorize(img, KColorScheme(QPalette::Active, KColorScheme::View).foreground().color(), 1.0);
    pixmap = QPixmap::fromImage(img);
    image = pixmap;
    QWidget::update();
}

void TimeoutIcon::setGlyth (const QString &glyth) {
    timer.stop();
    this->glyth = glyth;
    QImage img = pixmap.toImage();
    KIconEffect::colorize(img, KColorScheme(QPalette::Active, KColorScheme::View).foreground().color(), 1.0);
    pixmap = QPixmap::fromImage(img);
    image = pixmap;
    update();
}

void TimeoutIcon::setImage (const QString &name, int timeout) {
    timer.stop();
    iconname = name;
    if (!name.isNull() && !name.isEmpty()) {
        int size = width()<height() ? width() : height();
            pixmap = iconLoader->loadIcon(iconname, KIconLoader::NoGroup, size);
        QImage img = pixmap.toImage();
        KIconEffect::colorize(img, KColorScheme(QPalette::Active, KColorScheme::View).foreground().color(), 1.0);
        pixmap = QPixmap::fromImage(img);
        image = pixmap;
    }
    update();
    if (timeout > 0)
        timer.start (timeout);
}

void TimeoutIcon::timeout () {
    setGlyth (" ");
    setImage(featurename);
}

void TimeoutIcon::paintEvent(QPaintEvent *) {
    QPainter p(this);

    QString text = glyth;
    int count  = 1;
    int factor = 19;

    if (!iconname.isNull() && !iconname.isEmpty())
        p.drawPixmap(0,0, image);
    else if (glyth == " ") {
        text = i18nc("a (the first letter in the alphabet)", "a");
        count = 3;
        factor = 64;
    }

    QFont font = KGlobalSettings::generalFont();
    font.setWeight(QFont::Black);
    QFontMetrics metrics(font);
    QRect rect = metrics.boundingRect (text);
    int size = count*rect.width() > rect.height() ? count*rect.width() : rect.height();
    if (font.pixelSize() != -1)
        font.setPixelSize (font.pixelSize()*width()*factor/size/64);
    else
        font.setPointSizeF (font.pointSizeF()*width()*factor/size/64);

    p.setFont (font);
    if (count == 1) {
        p.setPen (KColorScheme(QPalette::Active, KColorScheme::View).foreground().color());
        p.drawText (0,0, width()/2, height()/2, Qt::AlignCenter, text);
    }
    else {
        QColor t = KColorScheme(QPalette::Active, KColorScheme::View).foreground().color();
        QColor b = KColorScheme(QPalette::Active, KColorScheme::View).background().color();
        p.setPen (QColor ((2*t.red()+3*b.red())/5, (2*t.green()+3*b.green())/5, (2*t.blue()+3*b.blue())/5));
        p.drawText (width()/2,0, width()/2, height(), Qt::AlignCenter, text);
        p.setPen (QColor ((2*t.red()+b.red())/3, (2*t.green()+b.green())/3, (2*t.blue()+b.blue())/3));
        p.drawText (0,0, width(), height(), Qt::AlignCenter, text);
        p.setPen (KColorScheme(QPalette::Active, KColorScheme::View).foreground().color());
        p.drawText (0,0, width()/2, height(), Qt::AlignCenter, text);
    }
}

/********************************************************************/

StatusIcon::StatusIcon (const QString &text, QWidget *parent, const char *name)
    : QPushButton (parent)
{
    setObjectName(name);
    //setText(text);
    setToolTip(text);
    setSizePolicy(QSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored));
}

StatusIcon::~StatusIcon () {
}

QSize StatusIcon::minimumSizeHint () const {
    return QSize (0,0);
}

/********************************************************************/

K_EXPORT_PLASMA_APPLET(kbstate, KbStateApplet)

class KbStateApplet::Private
{
    public:
        QGraphicsLinearLayout* m_layout;
        KbStateWidget* m_widget;
        QGraphicsProxyWidget* m_proxy;
        QPointer<QWidget> m_configwidget;
        QCheckBox *m_modifierCheckBox, *lockkeysCheckBox, *mouseCheckBox, *accessxCheckBox;
        explicit Private() : m_layout(0), m_widget(0), m_proxy(0), m_configwidget(0) {}
};

KbStateApplet::KbStateApplet(QObject *parent, const QVariantList &args)
    : Plasma::Applet(parent, args)
    , d(new Private)
{
    KGlobal::locale()->insertCatalog("kbstateapplet");
    setAspectRatioMode(Plasma::IgnoreAspectRatio);
    setHasConfigurationInterface(true);
    resize(300, 60);
}

KbStateApplet::~KbStateApplet()
{
    delete d->m_widget;
    delete d;
}

void KbStateApplet::init()
{
    Plasma::Applet::init();

    d->m_widget = new KbStateWidget(this);

    KConfigGroup c = config();
    d->m_widget->showModifiers = c.readEntry("Modifierkeys visible", true);
    d->m_widget->showLockkeys = c.readEntry("Lockkeys visible", true);
    d->m_widget->showMouse = c.readEntry("Mouse status visible", true);
    d->m_widget->showAccessX = c.readEntry("Slowkeys status visible", true);
    d->m_widget->showAccessX = c.readEntry("AccessX status visible", d->m_widget->showAccessX);

    d->m_proxy = new QGraphicsProxyWidget(this);
    d->m_proxy->setWidget(d->m_widget);
    d->m_proxy->show();

    updateConstraints(Plasma::AllConstraints);
}

void KbStateApplet::constraintsEvent(Plasma::Constraints constraints)
{
    if (! d->m_widget)
        return;

    if (constraints & Plasma::SizeConstraint || constraints & Plasma::FormFactorConstraint) {
        d->m_widget->setGeometry(contentsRect().toRect());
        if (formFactor() == Plasma::Horizontal) {
            setMinimumWidth(d->m_widget->widthForHeight(contentsRect().height()));
            setMinimumHeight(0);
        }
        else if (formFactor() == Plasma::Vertical) {
            setMinimumHeight(d->m_widget->heightForWidth(contentsRect().width()));
            setMinimumWidth(0);
        }
        else {
            setMinimumWidth(0);
            setMinimumHeight(0);
        }
    }
}

void KbStateApplet::createConfigurationInterface(KConfigDialog *parent)
{
    if (! d->m_configwidget) {
        d->m_configwidget = new QWidget(parent->mainWidget());
        QVBoxLayout *l = new QVBoxLayout(d->m_configwidget);
        d->m_configwidget->setLayout(l);
        d->m_modifierCheckBox = new QCheckBox(i18n("Show Modifier Keys"), d->m_configwidget);
        l->addWidget(d->m_modifierCheckBox);
        d->lockkeysCheckBox = new QCheckBox(i18n("Show Lock Keys"), d->m_configwidget);
        l->addWidget(d->lockkeysCheckBox);
        d->mouseCheckBox = new QCheckBox(i18n("Show Mouse Status"), d->m_configwidget);
        l->addWidget(d->mouseCheckBox);
        d->accessxCheckBox = new QCheckBox(i18n("Show AccessX Status"), d->m_configwidget);
        l->addWidget(d->accessxCheckBox);
    }

    d->m_modifierCheckBox->setChecked(d->m_widget->showModifiers);
    d->lockkeysCheckBox->setChecked(d->m_widget->showLockkeys);
    d->mouseCheckBox->setChecked(d->m_widget->showMouse);
    d->accessxCheckBox->setChecked(d->m_widget->showAccessX);

    parent->setButtons( KDialog::Ok | KDialog::Cancel | KDialog::Apply );
    parent->addPage(d->m_configwidget, parent->windowTitle(), icon());
    connect(parent, SIGNAL(applyClicked()), this, SLOT(configAccepted()));
    connect(parent, SIGNAL(okClicked()), this, SLOT(configAccepted()));
}

void KbStateApplet::configAccepted()
{
    d->m_widget->showModifiers = d->m_modifierCheckBox->isChecked();
    d->m_widget->showLockkeys = d->lockkeysCheckBox->isChecked();
    d->m_widget->showMouse = d->mouseCheckBox->isChecked();
    d->m_widget->showAccessX = d->accessxCheckBox->isChecked();

    KConfigGroup c = config();
    c.writeEntry("Modifierkeys visible", d->m_widget->showModifiers);
    c.writeEntry("Lockkeys visible", d->m_widget->showLockkeys);
    c.writeEntry("Mouse status visible", d->m_widget->showMouse);
    c.writeEntry("AccessX status visible", d->m_widget->showAccessX);

    d->m_widget->updateSettings();
    emit configNeedsSaving();
}

#include "kbstateapplet.moc"
