#include "qxtconfigwidget.h"
/****************************************************************************
** Copyright (c) 2006 - 2011, the LibQxt project.
** See the Qxt AUTHORS file for a list of authors and copyright holders.
** All rights reserved.
**
** Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are met:
**     * Redistributions of source code must retain the above copyright
**       notice, this list of conditions and the following disclaimer.
**     * Redistributions in binary form must reproduce the above copyright
**       notice, this list of conditions and the following disclaimer in the
**       documentation and/or other materials provided with the distribution.
**     * Neither the name of the LibQxt project nor the
**       names of its contributors may be used to endorse or promote products
**       derived from this software without specific prior written permission.
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
** WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
** DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
** DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
** (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
** LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
** ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
**
** <http://libqxt.org>  <foundation@libqxt.org>
*****************************************************************************/

#include "qxtconfigwidget_p.h"
#include <QStackedWidget>
#include <QApplication>
#include <QTableWidget>
#include <QHeaderView>
#include <QVBoxLayout>
#include <QSplitter>
#include <QPainter>

QxtConfigTableWidget::QxtConfigTableWidget(QWidget* parent) : QTableWidget(parent)
{
    int pm = style()->pixelMetric(QStyle::PM_LargeIconSize);
    setIconSize(QSize(pm, pm));
    setItemDelegate(new QxtConfigDelegate(this));
    viewport()->setAttribute(Qt::WA_Hover, true);
}

QStyleOptionViewItem QxtConfigTableWidget::viewOptions() const
{
    QStyleOptionViewItem option = QTableWidget::viewOptions();
    option.displayAlignment = Qt::AlignHCenter | Qt::AlignTop;
    option.decorationAlignment = Qt::AlignHCenter | Qt::AlignTop;
    option.decorationPosition = QStyleOptionViewItem::Top;
    option.showDecorationSelected = false;
    return option;
}

QSize QxtConfigTableWidget::sizeHint() const
{
    return QSize(sizeHintForColumn(0), sizeHintForRow(0));
}

bool QxtConfigTableWidget::hasHoverEffect() const
{
    return static_cast<QxtConfigDelegate*>(itemDelegate())->hover;
}

void QxtConfigTableWidget::setHoverEffect(bool enabled)
{
    static_cast<QxtConfigDelegate*>(itemDelegate())->hover = enabled;
}

QxtConfigDelegate::QxtConfigDelegate(QObject* parent)
        : QItemDelegate(parent), hover(true)
{
}

void QxtConfigDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const
{
    QStyleOptionViewItem opt = option;
    if (hover)
    {
        QPalette::ColorGroup cg = (option.state & QStyle::State_Enabled) ? QPalette::Normal : QPalette::Disabled;
        if (cg == QPalette::Normal && !(option.state & QStyle::State_Active))
            cg = QPalette::Inactive;

        if (option.state & QStyle::State_Selected)
            painter->fillRect(opt.rect, option.palette.brush(cg, QPalette::Highlight));
        else if ((option.state & QStyle::State_MouseOver) && (option.state & QStyle::State_Enabled))
        {
            QColor color = option.palette.color(cg, QPalette::Highlight).light();
            if (color == option.palette.color(cg, QPalette::Base))
                color = option.palette.color(cg, QPalette::AlternateBase);
            painter->fillRect(opt.rect, color);
        }
        else
            painter->fillRect(opt.rect, option.palette.brush(cg, QPalette::Base));

        opt.showDecorationSelected = false;
        opt.state &= ~QStyle::State_HasFocus;
        opt.state &= ~QStyle::State_Selected;
    }
    QItemDelegate::paint(painter, opt, index);
}

QSize QxtConfigDelegate::sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const
{
    int margin = qApp->style()->pixelMetric(QStyle::PM_FocusFrameHMargin) + 1;
    int textWidth = option.fontMetrics.width(index.data().toString());
    int width = qMax(textWidth, option.decorationSize.width()) + 2 * margin;
    int height = option.fontMetrics.height() + option.decorationSize.height() + margin;
    return QSize(width, height);
}

void QxtConfigWidgetPrivate::init(QxtConfigWidget::IconPosition position)
{
    QxtConfigWidget* p = &qxt_p();
    splitter = new QSplitter(p);
    stack = new QStackedWidget(p);
    table = new QxtConfigTableWidget(p);
    pos = position;
    QObject::connect(table, SIGNAL(currentCellChanged(int, int, int, int)), this, SLOT(setCurrentIndex(int, int)));
    QObject::connect(stack, SIGNAL(currentChanged(int)), p, SIGNAL(currentIndexChanged(int)));
    QVBoxLayout* layout = new QVBoxLayout(p);
    layout->addWidget(splitter);
    initTable();
    relayout();
}

void QxtConfigWidgetPrivate::initTable()
{
    table->horizontalHeader()->hide();
    table->verticalHeader()->hide();
    table->setEditTriggers(QAbstractItemView::NoEditTriggers);
    table->setTabKeyNavigation(true);
    table->setAcceptDrops(false);
    table->setDragEnabled(false);
    table->setShowGrid(false);
    table->setSelectionMode(QAbstractItemView::SingleSelection);
}

void QxtConfigWidgetPrivate::relayout()
{
    if (pos == QxtConfigWidget::North)
    {
        splitter->setOrientation(Qt::Vertical);
        table->setRowCount(1);
        table->setColumnCount(0);
        table->horizontalHeader()->setResizeMode(QHeaderView::ResizeToContents);
        table->verticalHeader()->setResizeMode(QHeaderView::Stretch);
        table->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded);
        table->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
    }
    else
    {
        splitter->setOrientation(Qt::Horizontal);
        table->setRowCount(0);
        table->setColumnCount(1);
        table->horizontalHeader()->setResizeMode(QHeaderView::Stretch);
        table->verticalHeader()->setResizeMode(QHeaderView::ResizeToContents);
        table->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
        table->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
    }

    // clear
    for (int i = splitter->count() - 1; i >= 0; --i)
    {
        splitter->widget(i)->setParent(0);
    }

    // relayout
    switch (pos)
    {
    case QxtConfigWidget::North:
        // +-----------+
        // |   Icons   |
        // +-----------|
        // |   Stack   |
        // +-----------|
        splitter->addWidget(table);
        splitter->addWidget(stack);
        break;

    case QxtConfigWidget::West:
        // +---+-------+
        // | I |       |
        // | c |       |
        // | o | Stack |
        // | n |       |
        // | s |       |
        // +---+-------+
        splitter->addWidget(table);
        splitter->addWidget(stack);
        break;

    case QxtConfigWidget::East:
        // +-------+---+
        // |       | I |
        // |       | c |
        // | Stack | o |
        // |       | n |
        // |       | s |
        // +-------+---+
        splitter->addWidget(stack);
        splitter->addWidget(table);
        break;

    default:
        qWarning("QxtConfigWidgetPrivate::relayout(): unknown position");
        break;
    }
}

QTableWidgetItem* QxtConfigWidgetPrivate::item(int index) const
{
    return pos == QxtConfigWidget::North ? table->item(0, index) : table->item(index, 0);
}

void QxtConfigWidgetPrivate::setCurrentIndex(int row, int column)
{
    if (pos == QxtConfigWidget::North)
        setCurrentIndex(column);
    else
        setCurrentIndex(row);
}

void QxtConfigWidgetPrivate::setCurrentIndex(int index)
{
    int previousIndex = stack->currentIndex();
    if (previousIndex != -1 && previousIndex != index)
        qxt_p().cleanupPage(previousIndex);

    stack->setCurrentIndex(index);
    table->setCurrentItem(item(index));

    if (index != -1)
        qxt_p().initializePage(index);
}

/*!
    \class QxtConfigWidget
    \inmodule QxtGui
    \brief The QxtConfigWidget class provides a configuration widget.

    QxtConfigWidget provides a convenient interface for building
    common configuration views. QxtConfigWidget consists of a
    list of icons and a stack of pages.

    \sa QxtConfigDialog
 */

/*!
    \enum QxtConfigWidget::IconPosition

    This enum describes the page icon position.

    \value North The icons are located above the pages.
    \value West The icons are located to the left of the pages.
    \value East The icons are located to the right of the pages.
 */

/*!
    \fn QxtConfigWidget::currentIndexChanged(int index)

    This signal is emitted whenever the current page \a index changes.

    \sa currentIndex()
 */

/*!
    Constructs a new QxtConfigWidget with \a parent and \a flags.
 */
QxtConfigWidget::QxtConfigWidget(QWidget* parent, Qt::WindowFlags flags)
        : QWidget(parent, flags)
{
    QXT_INIT_PRIVATE(QxtConfigWidget);
    qxt_d().init();
}

/*!
    Constructs a new QxtConfigWidget with icon \a position, \a parent and \a flags.
 */
QxtConfigWidget::QxtConfigWidget(QxtConfigWidget::IconPosition position, QWidget* parent, Qt::WindowFlags flags)
        : QWidget(parent, flags)
{
    QXT_INIT_PRIVATE(QxtConfigWidget);
    qxt_d().init(position);
}

/*!
    Destructs the config widget.
 */
QxtConfigWidget::~QxtConfigWidget()
{
}

/*!
    \property QxtConfigWidget::hoverEffect
    \brief whether a hover effect is shown for page icons

    The default value is \c true.

    \bold {Note:} Hovered (but not selected) icons are highlighted with lightened QPalette::Highlight
    (whereas selected icons are highlighted with QPalette::Highlight). In case lightened
    QPalette::Highlight ends up same as QPalette::Base, QPalette::AlternateBase is used
    as a fallback color for the hover effect. This usually happens when QPalette::Highlight
    already is a light color (eg. light gray).
 */
bool QxtConfigWidget::hasHoverEffect() const
{
    return qxt_d().table->hasHoverEffect();
}

void QxtConfigWidget::setHoverEffect(bool enabled)
{
    qxt_d().table->setHoverEffect(enabled);
}

/*!
    \property QxtConfigWidget::iconPosition
    \brief the position of page icons
 */
QxtConfigWidget::IconPosition QxtConfigWidget::iconPosition() const
{
    return qxt_d().pos;
}

void QxtConfigWidget::setIconPosition(QxtConfigWidget::IconPosition position)
{
    if (qxt_d().pos != position)
    {
        qxt_d().pos = position;
        qxt_d().relayout();
    }
}

/*!
    \property QxtConfigWidget::iconSize
    \brief the size of page icons
 */
QSize QxtConfigWidget::iconSize() const
{
    return qxt_d().table->iconSize();
}

void QxtConfigWidget::setIconSize(const QSize& size)
{
    qxt_d().table->setIconSize(size);
}

/*!
    Adds a \a page with \a icon and \a title.

    In case \a title is an empty string, QWidget::windowTitle of \a page is used.

    Returns the index of added page.

    \warning Adding and removing pages dynamically at run time might cause flicker.

    \sa insertPage()
*/
int QxtConfigWidget::addPage(QWidget* page, const QIcon& icon, const QString& title)
{
    return insertPage(-1, page, icon, title);
}

/*!
    Inserts a \a page with \a icon and \a title at \a index.

    In case \a title is an empty string, QWidget::windowTitle of \a page is used.

    Returns the index of inserted page.

    \warning Inserting and removing pages dynamically at run time might cause flicker.

    \sa addPage()
*/
int QxtConfigWidget::insertPage(int index, QWidget* page, const QIcon& icon, const QString& title)
{
    if (!page)
    {
        qWarning("QxtConfigWidget::insertPage(): Attempt to insert null page");
        return -1;
    }

    index = qxt_d().stack->insertWidget(index, page);
    const QString label = !title.isEmpty() ? title : page->windowTitle();
    if (label.isEmpty())
        qWarning("QxtConfigWidget::insertPage(): Inserting a page with an empty title");
    QTableWidgetItem* item = new QTableWidgetItem(icon, label);
    item->setToolTip(label);
    if (qxt_d().pos == QxtConfigWidget::North)
    {
        qxt_d().table->model()->insertColumn(index);
        qxt_d().table->setItem(0, index, item);
        qxt_d().table->resizeRowToContents(0);
    }
    else
    {
        qxt_d().table->model()->insertRow(index);
        qxt_d().table->setItem(index, 0, item);
        qxt_d().table->resizeColumnToContents(0);
    }
    qxt_d().table->updateGeometry();
    return index;
}

/*!
   Removes the page at \a index and returns it.

   \bold {Note:} Does not delete the page widget.
*/
QWidget* QxtConfigWidget::takePage(int index)
{
    if (QWidget* page = qxt_d().stack->widget(index))
    {
        qxt_d().stack->removeWidget(page);
        delete qxt_d().item(index);
        return page;
    }
    else
    {
        qWarning("QxtConfigWidget::removePage(): Unknown index");
        return 0;
    }
}

/*!
    \property QxtConfigWidget::count
    \brief the number of pages
*/
int QxtConfigWidget::count() const
{
    return qxt_d().stack->count();
}

/*!
    \property QxtConfigWidget::currentIndex
    \brief the index of current page
*/
int QxtConfigWidget::currentIndex() const
{
    return qxt_d().stack->currentIndex();
}

void QxtConfigWidget::setCurrentIndex(int index)
{
    qxt_d().setCurrentIndex(index);
}

/*!
    Returns the current page.

    \sa currentIndex(), setCurrentPage()
*/
QWidget* QxtConfigWidget::currentPage() const
{
    return qxt_d().stack->currentWidget();
}

/*!
    Sets the current \a page.

    \sa currentPage(), currentIndex()
*/
void QxtConfigWidget::setCurrentPage(QWidget* page)
{
    qxt_d().setCurrentIndex(qxt_d().stack->indexOf(page));
}

/*!
    Returns the index of \a page or \c -1 if the page is unknown.
*/
int QxtConfigWidget::indexOf(QWidget* page) const
{
    return qxt_d().stack->indexOf(page);
}

/*!
    Returns the page at \a index or \c 0 if the \a index is out of range.
*/
QWidget* QxtConfigWidget::page(int index) const
{
    return qxt_d().stack->widget(index);
}

/*!
    Returns \c true if the page at \a index is enabled; otherwise \c false.

    \sa setPageEnabled()
*/
bool QxtConfigWidget::isPageEnabled(int index) const
{
    const QWidget* widget = page(index);
    return widget && widget->isEnabled();
}

/*!
    Sets the page at \a index \a enabled. The corresponding
    page icon is also \a enabled.

    \sa isPageEnabled()
*/
void QxtConfigWidget::setPageEnabled(int index, bool enabled)
{
    QWidget* page = qxt_d().stack->widget(index);
    QTableWidgetItem* item = qxt_d().item(index);
    if (page && item)
    {
        page->setEnabled(enabled);
        if (enabled)
            item->setFlags(item->flags() | Qt::ItemIsEnabled);
        else
            item->setFlags(item->flags() & ~Qt::ItemIsEnabled);
    }
    else
    {
        qWarning("QxtConfigWidget::setPageEnabled(): Unknown index");
    }
}

/*!
    Returns \c true if the page at \a index is hidden; otherwise \c false.

    \sa setPageHidden()
*/
bool QxtConfigWidget::isPageHidden(int index) const
{
    if (qxt_d().pos == QxtConfigWidget::North)
        return qxt_d().table->isColumnHidden(index);
    return qxt_d().table->isRowHidden(index);
}

/*!
    Sets the page at \a index \a hidden. The corresponding
    page icon is also \a hidden.

    \sa isPageHidden()
*/
void QxtConfigWidget::setPageHidden(int index, bool hidden)
{
    if (qxt_d().pos == QxtConfigWidget::North)
        qxt_d().table->setColumnHidden(index, hidden);
    else
        qxt_d().table->setRowHidden(index, hidden);
}

/*!
    Returns the icon of page at \a index.

    \sa setPageIcon()
*/
QIcon QxtConfigWidget::pageIcon(int index) const
{
    const QTableWidgetItem* item = qxt_d().item(index);
    return (item ? item->icon() : QIcon());
}

/*!
    Sets the \a icon of page at \a index.

    \sa pageIcon()
*/
void QxtConfigWidget::setPageIcon(int index, const QIcon& icon)
{
    QTableWidgetItem* item = qxt_d().item(index);
    if (item)
    {
        item->setIcon(icon);
    }
    else
    {
        qWarning("QxtConfigWidget::setPageIcon(): Unknown index");
    }
}

/*!
    Returns the title of page at \a index.

    \sa setPageTitle()
*/
QString QxtConfigWidget::pageTitle(int index) const
{
    const QTableWidgetItem* item = qxt_d().item(index);
    return (item ? item->text() : QString());
}

/*!
    Sets the \a title of page at \a index.

    \sa pageTitle()
*/
void QxtConfigWidget::setPageTitle(int index, const QString& title)
{
    QTableWidgetItem* item = qxt_d().item(index);
    if (item)
    {
        item->setText(title);
    }
    else
    {
        qWarning("QxtConfigWidget::setPageTitle(): Unknown index");
    }
}

/*!
    Returns the tooltip of page at \a index.

    \sa setPageToolTip()
*/
QString QxtConfigWidget::pageToolTip(int index) const
{
    const QTableWidgetItem* item = qxt_d().item(index);
    return (item ? item->toolTip() : QString());
}

/*!
    Sets the \a tooltip of page at \a index.

    \sa pageToolTip()
*/
void QxtConfigWidget::setPageToolTip(int index, const QString& tooltip)
{
    QTableWidgetItem* item = qxt_d().item(index);
    if (item)
    {
        item->setToolTip(tooltip);
    }
    else
    {
        qWarning("QxtConfigWidget::setPageToolTip(): Unknown index");
    }
}

/*!
    Returns the what's this of page at \a index.

    \sa setPageWhatsThis()
*/
QString QxtConfigWidget::pageWhatsThis(int index) const
{
    const QTableWidgetItem* item = qxt_d().item(index);
    return (item ? item->whatsThis() : QString());
}

/*!
    Sets the \a whatsthis of page at \a index.

    \sa pageWhatsThis()
*/
void QxtConfigWidget::setPageWhatsThis(int index, const QString& whatsthis)
{
    QTableWidgetItem* item = qxt_d().item(index);
    if (item)
    {
        item->setWhatsThis(whatsthis);
    }
    else
    {
        qWarning("QxtConfigWidget::setPageWhatsThis(): Unknown index");
    }
}

/*!
    \bold {Note:} The default implementation calls SLOT(accept()) of
    each page page provided that such slot exists.

    \sa reject()
 */
void QxtConfigWidget::accept()
{
    Q_ASSERT(qxt_d().stack);
    for (int i = 0; i < qxt_d().stack->count(); ++i)
    {
        QMetaObject::invokeMethod(qxt_d().stack->widget(i), "accept");
    }
}

/*!

    \bold {Note:} The default implementation calls SLOT(reject()) of
    each page provided that such slot exists.

    \sa accept()
 */
void QxtConfigWidget::reject()
{
    Q_ASSERT(qxt_d().stack);
    for (int i = 0; i < qxt_d().stack->count(); ++i)
    {
        QMetaObject::invokeMethod(qxt_d().stack->widget(i), "reject");
    }
}

/*!
    Returns the internal splitter used for layouting table and stacked widgets.

    \sa tableWidget() stackedWidget()
*/
QSplitter* QxtConfigWidget::splitter() const
{
    return qxt_d().splitter;
}

/*!
    Returns the internal table widget used for showing page icons.

    \sa splitter() stackedWidget()
*/
QTableWidget* QxtConfigWidget::tableWidget() const
{
    return qxt_d().table;
}

/*!
    Returns the internal stacked widget used for stacking pages.

    \sa splitter() tableWidget()
*/
QStackedWidget* QxtConfigWidget::stackedWidget() const
{
    return qxt_d().stack;
}

/*!
    This virtual function is called to clean up previous
    page at \a index before switching to a new page.

    \bold {Note:} The default implementation calls SLOT(cleanup()) of
    the corresponding page provided that such slot exists.

    \sa initializePage()
*/
void QxtConfigWidget::cleanupPage(int index)
{
    Q_ASSERT(qxt_d().stack);
    QMetaObject::invokeMethod(qxt_d().stack->widget(index), "cleanup");
}

/*!
    This virtual function is called to initialize page at
    \a index before switching to it.

    \bold {Note:} The default implementation calls SLOT(initialize()) of
    the corresponding page provided that such slot exists.

    \sa cleanupPage()
*/
void QxtConfigWidget::initializePage(int index)
{
    Q_ASSERT(qxt_d().stack);
    QMetaObject::invokeMethod(qxt_d().stack->widget(index), "initialize");
}
