/*
 * iconwidget.cpp - misc. Iconset- and PsiIcon-aware widgets
 * Copyright (C) 2003-2006  Michail Pishchagin
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 *
 */

#include "iconwidget.h"
#include "iconsetdisplay.h"
#include "iconsetselect.h"
#include "icontoolbutton.h"
#include "iconbutton.h"

#include <QApplication>
#include <QPainter>
#include <QBrush>

#ifndef WIDGET_PLUGIN
#    include "iconset.h"
#    include <QStyle>
#    include <QBitmap>
#    include <QMap>
#       include "pixmaputil.h"
#else
#    include <QImage>

static const char *cancel_xpm[] = {
"22 22 60 1",
"     c None",
".    c #E84300",
"+    c #E63F00",
"@    c #D11E00",
"#    c #D11B00",
"$    c #F69D50",
"%    c #F59A4D",
"&    c #E23800",
"*    c #EE5F1F",
"=    c #ED5A1D",
"-    c #CD1700",
";    c #FECBA2",
">    c #FEC69A",
",    c #F39045",
"'    c #DE3200",
")    c #FE7B3C",
"!    c #FE7234",
"~    c #EC4C15",
"{    c #CC1100",
"]    c #FEC091",
"^    c #FEBA89",
"/    c #F2873D",
"(    c #DA2C00",
"_    c #FE692C",
":    c #EB4712",
"<    c #CA0F00",
"[    c #FEB480",
"}    c #FEAE78",
"|    c #F07D35",
"1    c #D62600",
"2    c #FEA870",
"3    c #FEA166",
"4    c #EF722D",
"5    c #D32100",
"6    c #FE9B5F",
"7    c #FE9356",
"8    c #F16C2A",
"9    c #F16525",
"0    c #FE8B4D",
"a    c #FE8445",
"b    c #EE4B15",
"c    c #FE6025",
"d    c #EE4310",
"e    c #C90E00",
"f    c #FE561D",
"g    c #FE4B16",
"h    c #EA2F08",
"i    c #C70900",
"j    c #FE4010",
"k    c #FE350B",
"l    c #EA1D03",
"m    c #C60700",
"n    c #FE2906",
"o    c #FE1A02",
"p    c #E90900",
"q    c #C50300",
"r    c #FE0A00",
"s    c #FE0000",
"t    c #E90000",
"u    c #C40000",
"                      ",
"                      ",
"    .+          @#    ",
"   .$%&        @*=-   ",
"  .$;>,'      @*)!~{  ",
"  +%>]^/(    @*)!_:<  ",
"   &,^[}|1  @*)!_:<   ",
"    '/}2345@*)!_:<    ",
"     (|36789)!_:<     ",
"      1470a)!_:<      ",
"       58a)!_b<       ",
"       @9)!_cde       ",
"      @*)!_cfghi      ",
"     @*)!_bdgjklm     ",
"    @*)!_:<ehknopq    ",
"   @*)!_:<  ilorstu   ",
"  @*)!_:<    mpssstu  ",
"  #=!_:<      qtsstu  ",
"   -~:<        uttu   ",
"    {<          uu    ",
"                      ",
"                      "};
#endif

//----------------------------------------------------------------------------
// RealIconWidgetItem
//----------------------------------------------------------------------------

class RealIconWidgetItem : public IconWidgetItem
{
    Q_OBJECT
public:
    RealIconWidgetItem(QListWidget *parent = 0)
    : IconWidgetItem(parent) {}

    virtual void paint(QPainter *painter) const = 0;
    virtual int height() const = 0;
    virtual int width() const = 0;
    virtual QPoint textPosition(QPainter *painter) const = 0;

    static const int TextPositionRole = Qt::UserRole + 1;

    QVariant data(int role) const
    {
        if (role == Qt::SizeHintRole)
            return QVariant(QSize(width(), height()));
        else if (role == Qt::DecorationRole) {
#ifndef WIDGET_PLUGIN
            QPixmap pix = PixmapUtil::createTransparentPixmap(width(), height());
#else
            QPixmap pix(width(), height()); // junk inside
#endif
            QPainter p(&pix);
            paint(&p);
            p.end();
            return QVariant(pix);
        }
        else if (role == TextPositionRole) {
            QPixmap pix(width(), height());
            QPainter p(&pix);
            QPoint pos = textPosition(&p);
            p.end();
            return QVariant(pos);
        }
        return QListWidgetItem::data(role);
    }

public slots:
    void update()
    {
        setData(Qt::UserRole, data(Qt::UserRole).toInt() + 1);
    }
};

//----------------------------------------------------------------------------
// IconWidgetDelegate
//----------------------------------------------------------------------------

class IconWidgetDelegate : public QAbstractItemDelegate
{
public:
    IconWidgetDelegate(QObject *parent)
    : QAbstractItemDelegate(parent) {}

    QSize sizeHint(const QStyleOptionViewItem &/*option*/, const QModelIndex &index) const
    {
        return index.model()->data(index, Qt::SizeHintRole).toSize();
    }

    void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
    {
        const QAbstractItemModel *model = index.model();

        QPalette::ColorGroup cg = option.state & QStyle::State_Enabled
            ? QPalette::Normal : QPalette::Disabled;

        // draw the background color
        if (option.showDecorationSelected && (option.state & QStyle::State_Selected)) {
            painter->fillRect(option.rect, option.palette.brush(cg, QPalette::Highlight));
            painter->setPen(option.palette.color(cg, QPalette::HighlightedText));
        }
        else {
            painter->setPen(option.palette.color(cg, QPalette::Text));
            QVariant value = model->data(index, Qt::BackgroundRole);
            if (value.isValid() && qvariant_cast<QColor>(value).isValid())
                painter->fillRect(option.rect, qvariant_cast<QColor>(value));
        }

        painter->drawPixmap(option.rect.topLeft(), model->data(index, Qt::DecorationRole).value<QPixmap>());

        QVariant textPosition = model->data(index, RealIconWidgetItem::TextPositionRole);
        if (textPosition.isValid() && !qvariant_cast<QPoint>(textPosition).isNull())
            painter->drawText(option.rect.topLeft() + qvariant_cast<QPoint>(textPosition), model->data(index, Qt::DisplayRole).value<QString>());

        if (option.state & QStyle::State_HasFocus) {
            QStyleOptionFocusRect o;
            o.QStyleOption::operator=(option);
            QRect r = option.rect;
            QPoint margin(1, 1);
            o.rect = QRect(r.topLeft() + margin, r.bottomRight() - margin);
            o.state |= QStyle::State_KeyboardFocusChange;
            o.backgroundColor = option.palette.color(cg, (option.state & QStyle::State_Selected)
                ? QPalette::Highlight : QPalette::Background);
            QApplication::style()->drawPrimitive(QStyle::PE_FrameFocusRect, &o, painter);
        }
    }
};

//----------------------------------------------------------------------------
// IconsetSelect
//----------------------------------------------------------------------------

class IconsetSelectItem : public RealIconWidgetItem
{
    Q_OBJECT
private:
    static const int margin;
    static const int displayNumIcons;
#ifndef WIDGET_PLUGIN
    Iconset iss;
    QMap<PsiIcon*, QRect> iconRects;
#endif
    int w = 16, h = 16;
    mutable int fullW, fullH;

public:
    IconsetSelectItem(QListWidget *parent, const Iconset &_iconset)
        : RealIconWidgetItem(parent)
    {
        Q_UNUSED(fullW)
        Q_UNUSED(fullH)
#ifndef WIDGET_PLUGIN
        iss = _iconset;
        setText( iss.name() );

        w = margin;
        h = 2*margin;

        int count;

        QListIterator<PsiIcon *> it = iss.iterator();
        count = 0;
        while ( it.hasNext() ) {
            if ( count++ >= displayNumIcons )
                break; // display only first displayNumIcons icons

            PsiIcon *icon = it.next();
            QPixmap pix = icon->pixmap();

            iconRects[icon] = QRect( w, margin, pix.width(), pix.height() );

            w += pix.width() + margin;
            h = qMax( h, pix.height() + 2*margin );

            connect(icon, SIGNAL(pixmapChanged()), SLOT(update()));
            icon->activated(false); // start animation
        }

        QMutableMapIterator<PsiIcon*, QRect> it2(iconRects);
        while (it2.hasNext()) {
            it2.next();
            QRect r = it2.value();
            it2.setValue(QRect(r.x(), (h - r.height()) / 2, r.width(), r.height()));
        }
#else
        Q_UNUSED( _iconset );
#endif
    }

    ~IconsetSelectItem()
    {
#ifndef WIDGET_PLUGIN
        QMap<PsiIcon*, QRect>::Iterator it;
        for (it = iconRects.begin(); it != iconRects.end(); it++)
            it.key()->stop();
#endif
    }

    const Iconset *iconset() const
    {
#ifndef WIDGET_PLUGIN
        return &iss;
#else
        return 0;
#endif
    }

    int height() const
    {
        int hh = listWidget()->fontMetrics().lineSpacing() + h;
        return qMax( hh, QApplication::globalStrut().height() );
    }

    int width() const
    {
        int ww = qMax(listWidget()->fontMetrics().width( text() ) + 6 + 15, w + 10);
        return qMax( ww, QApplication::globalStrut().width() );
    }


    void paint(QPainter *painter) const
    {
#ifndef WIDGET_PLUGIN
        QFontMetrics fm = painter->fontMetrics();

        QMap<PsiIcon*, QRect>::ConstIterator it;
        for (it = iconRects.begin(); it != iconRects.end(); it++) {
            PsiIcon *icon = it.key();
            QRect r = it.value();
            painter->drawPixmap(QPoint(10 + r.left(), fm.lineSpacing() + 2 + r.top()), icon->pixmap());
        }
#else
        Q_UNUSED(painter);
#endif
    }
    QPoint textPosition(QPainter *painter) const
    {
        QFontMetrics fm = painter->fontMetrics();
        return QPoint(3, fm.ascent() + (fm.leading()+1)/2 + 1);
    }
};
const int IconsetSelectItem::margin = 3;
const int IconsetSelectItem::displayNumIcons = 10;

IconsetSelect::IconsetSelect(QWidget *parent)
: QListWidget(parent)
{
    setItemDelegate(new IconWidgetDelegate(this));
}

IconsetSelect::~IconsetSelect()
{
}

void IconsetSelect::insert(const Iconset &iconset)
{
#ifndef WIDGET_PLUGIN
    new IconsetSelectItem(this, iconset);
#else
    Q_UNUSED(iconset);
#endif
}

void IconsetSelect::moveItemUp()
{
    if ( currentRow() < 1 )
        return;

    QListWidgetItem *i = currentItem();
    if ( !i )
        return;
    int prevRow = row(i) - 1;
    i = takeItem(row(i));
    insertItem(prevRow, i);
    setItemSelected(i, true);
    setCurrentItem(i);
}

void IconsetSelect::moveItemDown()
{
    if ( !currentItem() || currentRow() > (int)count() - 2 )
        return;

    QListWidgetItem *i = currentItem();
    if ( !i )
        return;
    int nextRow = row(i) + 1;
    i = takeItem(row(i));
    insertItem(nextRow, i);
    setCurrentItem(i);
}

const Iconset *IconsetSelect::iconset() const
{
    IconsetSelectItem *i = (IconsetSelectItem *)currentItem();
    if ( !i ) {
        QList<QListWidgetItem *> items = selectedItems();
        i = !items.isEmpty() ? (IconsetSelectItem *)items.first() : 0;
    }
    if ( i )
        return i->iconset();
    return 0;
}

QListWidgetItem *IconsetSelect::lastItem() const
{
    return item(count() - 1);
}

QStyleOptionViewItem IconsetSelect::viewOptions() const
{
    QStyleOptionViewItem o = QListWidget::viewOptions();
    o.showDecorationSelected = true;
    return o;
}

//----------------------------------------------------------------------------
// IconsetDisplay
//----------------------------------------------------------------------------

class IconsetDisplayItem : public RealIconWidgetItem
{
    Q_OBJECT
private:
    static const int margin;
#ifndef WIDGET_PLUGIN
    PsiIcon icon;
#endif
    int w = 16, h = 16;

public:
    IconsetDisplayItem(QListWidget *parent, PsiIcon *i, int iconW)
        : RealIconWidgetItem(parent)
    {
#ifndef WIDGET_PLUGIN
        icon = *i;
        w = iconW;

        connect(&icon, SIGNAL(pixmapChanged()), SLOT(update()));
        icon.activated(false);

        h = icon.pixmap().height();

        QStringList str;
        foreach(PsiIcon::IconText t, icon.text())
            str += t.text;

        if ( !str.isEmpty() )
            setText(str.join(", "));
        else
            setText(tr("Name: '%1'").arg(icon.name()));
#else
        Q_UNUSED( i );
        Q_UNUSED( iconW );
#endif
    }

    ~IconsetDisplayItem()
    {
#ifndef WIDGET_PLUGIN
        icon.stop();
#endif
    }

    int height() const
    {
        int hh = qMax(h + 2*margin, listWidget()->fontMetrics().lineSpacing() + 2);
        return qMax( hh, QApplication::globalStrut().height() );
    }

    int width() const
    {
        int ww = listWidget()->fontMetrics().width(text()) + w + 3*margin + 15;
        return qMax( ww, QApplication::globalStrut().width() );
    }

    void paint(QPainter *painter) const
    {
#ifndef WIDGET_PLUGIN
        painter->drawPixmap(QPoint((2*margin+w - icon.pixmap().width())/2, margin), icon.pixmap());
#else
        Q_UNUSED(painter);
#endif
    }
    QPoint textPosition(QPainter *painter) const
    {
        QFontMetrics fm = painter->fontMetrics();
        return QPoint(w + 2*margin, fm.ascent() + (fm.leading()+1)/2 + 1);
    }

};
const int IconsetDisplayItem::margin = 3;

IconsetDisplay::IconsetDisplay(QWidget *parent)
: QListWidget(parent)
{
    setItemDelegate(new IconWidgetDelegate(this));
}

IconsetDisplay::~IconsetDisplay()
{
}

void IconsetDisplay::setIconset(const Iconset &iconset)
{
#ifndef WIDGET_PLUGIN
    int w = 0;
    QListIterator<PsiIcon *> it = iconset.iterator();
    while ( it.hasNext() ) {
        w = qMax(w, it.next()->pixmap().width());
    }

    it = iconset.iterator();
    while ( it.hasNext() ) {
        new IconsetDisplayItem(this, it.next(), w);
    }
#else
    Q_UNUSED(iconset);
#endif
}

//----------------------------------------------------------------------------
// IconButton
//----------------------------------------------------------------------------

class IconButton::Private : public QObject
{
    Q_OBJECT
public:
    PsiIcon *icon;
    IconButton *button;
    bool textVisible;
    bool activate, forced;
#ifdef WIDGET_PLUGIN
    QString iconName;
#endif

public:
    Private(IconButton *b)
    {
        icon = nullptr;
        button = b;
        textVisible = true;
        activate = false;
        forced = false;
    }

    ~Private()
    {
        iconStop();
    }

    void setIcon(PsiIcon *i)
    {
#ifndef WIDGET_PLUGIN
        iconStop();
        if ( i )
            icon = i->copy();
        iconStart();
#else
        Q_UNUSED(i);
#endif
    }

    void iconStart()
    {
#ifndef WIDGET_PLUGIN
        if ( icon ) {
            connect(icon, SIGNAL(pixmapChanged()), SLOT(iconUpdated()));
            if ( activate )
                icon->activated(true); // FIXME: should icon play sound when it's activated on button?
        }

        updateIcon();
#endif
    }

    void iconStop()
    {
#ifndef WIDGET_PLUGIN
        if ( icon ) {
            disconnect(icon, 0, this, 0 );
            if ( activate )
                icon->stop();

            delete icon;
            icon = 0;
        }
#endif
    }

    void update()
    {
        iconUpdated();
    }

    void updateIcon()
    {
        iconUpdated();
    }

public slots:
    void iconUpdated()
    {
        button->setUpdatesEnabled(false);
#ifndef WIDGET_PLUGIN
        button->setIcon(icon ? icon->pixmap() : QPixmap());
#else
        QPixmap pix;
        if (!iconName.isEmpty()) {
            QPixmap pix((const char **)cancel_xpm);
            pix = QPixmap(pix);
        }
        button->setIcon(pix);
#endif
        button->setUpdatesEnabled(true);
        button->update();
    }
};

IconButton::IconButton(QWidget *parent)
: QPushButton(parent)
{
    d = new Private(this);
}

IconButton::~IconButton()
{
    delete d;
}

void IconButton::setIcon(const QPixmap &p)
{
    QPushButton::setIcon(p);
}

void IconButton::forceSetPsiIcon(const PsiIcon *i, bool activate)
{
    d->activate = activate;
    d->setIcon((PsiIcon *)i);
    d->forced = true;
}

void IconButton::setPsiIcon(const PsiIcon *i, bool activate)
{
#ifndef HAVE_X11
    if ( !text().isEmpty() )
        return;
#endif

    forceSetPsiIcon(i, activate);
    d->forced = false;
}

void IconButton::setPsiIcon(const QString &name)
{
#ifndef WIDGET_PLUGIN
    setPsiIcon( IconsetFactory::iconPtr(name) );
#else
    d->iconName = name;
    d->iconUpdated();
#endif
}

QString IconButton::psiIconName() const
{
#ifndef WIDGET_PLUGIN
    if ( d->icon )
        return d->icon->name();
    return QString::null;
#else
    return d->iconName;
#endif
}

void IconButton::setText(const QString &text)
{
#ifndef HAVE_X11
    if ( !d->forced )
        setPsiIcon(0);
#endif

    QPushButton::setText( text );
    d->updateIcon();
}

bool IconButton::textVisible() const
{
    return d->textVisible;
}

void IconButton::setTextVisible(bool v)
{
    d->textVisible = v;
    d->updateIcon();
}

//----------------------------------------------------------------------------
// IconToolButton
//----------------------------------------------------------------------------

class IconToolButton::Private : public QObject
{
    Q_OBJECT
public:
    PsiIcon *icon;
    IconToolButton *button;
    bool activate;
#ifdef WIDGET_PLUGIN
    QString iconName;
#endif

public:
    Private(IconToolButton *b)
    {
        icon = nullptr;
        button = b;
        activate = false;
    }

    ~Private()
    {
        iconStop();
    }

    void setIcon(const PsiIcon *i)
    {
#ifndef WIDGET_PLUGIN
        iconStop();
        if ( i )
            icon = new PsiIcon(*i);
        iconStart();
#else
        Q_UNUSED(i);
#endif
    }

    void iconStart()
    {
#ifndef WIDGET_PLUGIN
        if ( icon ) {
            connect(icon, SIGNAL(pixmapChanged()), SLOT(iconUpdated()));
            if ( activate )
                icon->activated(true); // FIXME: should icon play sound when it's activated on button?
        }
        iconUpdated();
#endif
    }

    void iconStop()
    {
#ifndef WIDGET_PLUGIN
        if ( icon ) {
            disconnect(icon, 0, this, 0 );
            if ( activate )
                icon->stop();

            delete icon;
            icon = 0;
        }
#endif
    }

    void update()
    {
        iconUpdated();
    }

private slots:
    void iconUpdated()
    {
        button->setUpdatesEnabled(false);
#ifndef WIDGET_PLUGIN
        QPixmap pix = icon ? icon->pixmap() : QPixmap();
        if (pix.isNull())
            button->QToolButton::setIcon(QIcon());
        else
            button->QToolButton::setIcon(pix);
        button->setIcon(icon ? icon->pixmap() : QPixmap());
#else
        button->setIcon(QPixmap());
#endif
        button->setUpdatesEnabled(true);
        button->update();
    }
};

IconToolButton::IconToolButton(QWidget *parent)
: QToolButton(parent)
{
    d = new Private(this);
}

IconToolButton::~IconToolButton()
{
    delete d;
}

void IconToolButton::setIcon(const QIcon &p)
{
    QToolButton::setIcon(p);
}

void IconToolButton::setPsiIcon(const PsiIcon *i, bool activate)
{
    d->activate = activate;
    d->setIcon ((PsiIcon *)i);
}

void IconToolButton::setPsiIcon(const QString &name)
{
#ifndef WIDGET_PLUGIN
    setPsiIcon( IconsetFactory::iconPtr(name) );
#else
    d->iconName = name;
#endif
}

QString IconToolButton::psiIconName() const
{
#ifndef WIDGET_PLUGIN
    if ( d->icon )
        return d->icon->name();
    return QString::null;
#else
    return d->iconName;
#endif
}

void IconToolButton::paintEvent(QPaintEvent* event)
{
    setToolButtonStyle(icon().isNull() ?
                       Qt::ToolButtonTextOnly :
                       Qt::ToolButtonIconOnly);
    QToolButton::paintEvent(event);
}

#include "iconwidget.moc"
