/*[
 * This file is part of the SharedComponents Software.
 * 
 * Copyright 2008 Paradigm B.V. and/or its affiliates and subsidiaries.
 * All rights reserved.
 * 
 * Use of this software is subject to the terms and conditions of the license
 * agreement with Paradigm Geophysical B.V. and/or its affiliates and
 * subsidiaries (collectively "Paradigm").
 * 
 * Warning: This computer program is protected by copyright law and international
 * treaties. Unauthorized reproduction or distribution of this program, or any
 * portion of it, may result in severe civil and criminal penalties, and will be
 * prosecuted to the maximum extent possible under the law.
]*/

#include <widgets/ExpandableFrame.h>

#include <QtGui/QPainter>
#include <QtGui/QStyle>
#include <QtGui/QFontMetrics>
#include <QtGui/QVBoxLayout>
#include <QtGui/QStyleOptionGroupBox>

namespace pdgm {
namespace gui {

/*! \class ExpandableFrame 
 * 
 */


/*!
 * \brief Constructor of class ExpandableFrame
 */
ExpandableFrame::ExpandableFrame(QWidget* parent) :
    QFrame(parent),
    headerPad_(4),
    topMargin_(-1),
    widget_(NULL),
    cursorOverHeader_(false)
{
    setWidget(new QWidget);

    setTitle("Options");    // must be done with existing widget

    setAttribute(Qt::WA_Hover);

    // cursor management
    cursor_ = cursor();
    setMouseTracking(true);

    setFrameStyle(QFrame::StyledPanel); // necessary for the stylesheet
}

/*!
 * \brief Constructor of class ExpandableFrame
 * Creates a box with a title \p title which is able to show/hide its contents.
 * - \p expanded specifies whether the contents is shown or not for the first display
 */
ExpandableFrame::ExpandableFrame(
    const QString& title, 
    QWidget* parent
) :
    QFrame(parent),
    headerPad_(4),
    topMargin_(-1),
    widget_(NULL),
    cursorOverHeader_(false)
{
    setWidget(new QWidget);

    // Must be done with existing widget
    if( title.isEmpty() ) {
        updateHeaderSize();
    }
    else {
        setTitle(title);
    }

    setAttribute(Qt::WA_Hover);

    // cursor management
    cursor_ = cursor();
    setMouseTracking(true);

    setFrameStyle(QFrame::StyledPanel); // necessary for the stylesheet
}

/*!
 * Destructor.
 */
ExpandableFrame::~ExpandableFrame() {
}

/*!
 * Returns the viewport widget, that is the widget containing all the
 * children.
 */
QWidget* ExpandableFrame::widget() const {
    return widget_;
}

/*!
 * Removes the viewport widget, and passes ownership of the widget
 * to the caller.
 * \sa widget()
 */
QWidget* ExpandableFrame::takeWidget() {
    QWidget* widget = widget_;
    widget_ = NULL;
    if( widget != NULL ) {
        widget->setParent(NULL);
    } 
    return widget;
}


/*!
 * Sets the viewport to be the given \p widget.
 * The ExpandableFrame will take ownership of the given \p widget.
 *
 * If \p widget is \c NULL, ExpandableFrame will assign a new QWidget
 * instance for the viewport.
*/
void ExpandableFrame::setWidget(QWidget* widget) {
    if( widget == widget_ ) {
        return;
    }
    if( widget == NULL ) {
        widget = new QWidget;
    }
    if( layout() == NULL ) {
        QVBoxLayout* layout = new QVBoxLayout(this);
        layout->setMargin(1);
    }

    delete widget_;
    layout()->addWidget(widget);
    if( widget->parent() != this ) {
        widget->setParent(this);
    }
    if( !widget->testAttribute(Qt::WA_Resized) ) {
        widget->resize(widget->sizeHint());
    }
    widget_ = widget;
    widget_->setFocusProxy(this);
    //widget_->setMouseTracking(true);  // superseded by Hover events
    if( isVisible() ) {
        widget_->show();
    }
}


/*!
 * Returns which widget should be used to compute the sizeHint().
 * Returns \c NULL if no widget can be used.
 */
const QWidget* ExpandableFrame::sizeHintComputerWidget() const {
    if( widget()->layout() != NULL && widget()->layout()->count() != 0 ) {
        return widget();
    }
    return NULL;
}

/*!
 * Returns the size hint. This size is at least the size of the
 * header with the header padding.
 */
QSize ExpandableFrame::sizeHint() const {
    const QWidget* sizeComputer = sizeHintComputerWidget();
    QSize size = sizeComputer == NULL ? this->size() : QFrame::sizeHint();
    if( sizeComputer != NULL ) {
        size = sizeComputer->sizeHint();
        // add the margins
        int top, left, bottom, right;
        getContentsMargins(&left, &top, &right, &bottom);
        size += QSize(left + right, top + bottom);
    }

    // ensure that the computed size hint holds at least the header
    QSize h_size = headerSize();

    if( size.width() < h_size.width() + 2 * headerPad_ ) {
        size.setWidth(h_size.width() + 2 * headerPad_);
    }
    if( size.height() < h_size.height() + 2 * headerPad_ ) {
        size.setHeight(h_size.height() + 2 * headerPad_);
    }

    return size;
}

int ExpandableFrame::headerPad() const {
    return headerPad_;
}

void ExpandableFrame::setHeaderPad(int pad) {
    if( headerPad_ == pad ) {
        return;
    }
    if( pad < 0 ) {
        pad = DefaultHeaderPad;
    }

    headerPad_ = pad;
    if( updateHeaderSize() ) {
        updateGeometry();
        update();
    }
}

/*!
 * Returns the title of the frame.
 */
QString ExpandableFrame::title() const {
    return title_;
}

/*!
 * Sets the title of the frame.
 */
void ExpandableFrame::setTitle(const QString& title) {
    if( title_ == title ) {
        return;
    }
    title_ = title;

    if( updateHeaderSize() ) {
        updateGeometry();
        update();
    }
}


/*!
 * Returns the size of the header. The header consists of the icon and the title.
 */
QSize ExpandableFrame::headerSize() const {
    return headerSize_;
}

/*!
 * Updates the size of the header. This takes into account the
 * icon, the title, the associated font and the header padding.
 * Returns \c true if the size changed, \c false otherwise.
 */
bool ExpandableFrame::updateHeaderSize() {
    // Compute pads to use
    enum { Top, Horizontal, Vertical, PadCount };


    int pad[PadCount] = { headerPad_, headerPad_, headerPad_ };

    for( int i = 0; i != PadCount; ++i ) {
        if( pad[i] < 0 ) {
            pad[i] = DefaultHeaderPad;
        }
    }

    // Update header size
    QFontMetrics metrics = fontMetrics();
    const int title_width = metrics.width(title_);
    const int title_height = metrics.height();

    headerSize_.setWidth(title_width);
    headerSize_.setHeight(title_height);

    // update top padding, ie. the space from the top of the xframe to the content
    int oldTopMargin = topMargin_;
    topMargin_ = headerSize_.height() + pad[Top] + pad[Vertical];

    if( oldTopMargin == topMargin_ ) {
        return false;
    }

    // Update top margin
    int margins[4];
    getContentsMargins(&margins[0], &margins[1], &margins[2], &margins[3]);
    if( topMargin_ != margins[1] ) {
        setContentsMargins(margins[0], topMargin_, margins[2], margins[3]);
    }

    return true;
}


bool ExpandableFrame::event(QEvent *event) {
    switch( event->type() ) {
        case QEvent::HoverMove: {
            // mouseMoveEvent can be eaten by sub-windows, while not HoverMove events
            QHoverEvent* h_evt = dynamic_cast<QHoverEvent*>(event);
            if( h_evt != NULL ) {
                updatePointer(h_evt->pos());
            }
            break;
        }
        case QEvent::ContentsRectChange: {
            // Make sure the topMargin_ is not overwritten
            int margins[4];
            getContentsMargins(&margins[0], &margins[1], &margins[2], &margins[3]);
            if( topMargin_ != margins[1] ) {
                setContentsMargins(margins[0], topMargin_, margins[2], margins[3]);
            }
            break;
        }
        case QEvent::StyleChange: {
            if( updateHeaderSize() ) {
                updateGeometry();
            }
            break;
        }
    }
    return QFrame::event(event);
}

/*!
 * Overrides paintEvent.
 * Draws the title bar on top of the frame.
 */ 
void ExpandableFrame::paintEvent(QPaintEvent* /*event*/) {
    QPainter painter(this);
    drawFrame(&painter);

    QStyle* s = style();

    int left;
    int top;
    int right;
    int bottom;
    getContentsMargins(&left, &top, &right, &bottom);

    int h_spacing = headerPad_;
    if( h_spacing < 0 ) {
        h_spacing = DefaultHeaderPad;
    }

    // Note: the points xA denotes top-left while xB is bottom right
    // content area
    const QRect cr = contentsRect();
    const QPoint cA = cr.topLeft();
    const QPoint cB = cr.bottomRight();

    // decoration around the content area

    // header area
    const QPoint hA(cA.x()-left, cA.y()-topMargin_);
    const QPoint hB(cB.x()+right, cA.y());    // topMargin_ holds already all the padding

    // border
    const QPoint bA = hA;
    const QPoint bB(cB.x()+right, cB.y()+bottom);

    QStyleOptionGroupBox option;
    option.initFrom(this);
    option.rect = QRect(bA, bB);
    s->drawPrimitive(QStyle::PE_FrameGroupBox, &option, &painter);
    if( underMouse() ) {
        option.rect.adjust(1, 1, -1, -1);
        s->drawPrimitive(QStyle::PE_FrameGroupBox, &option, &painter);
    }

    const int flags = QStyle::visualAlignment(layoutDirection(), Qt::AlignTop);

    // text area
    QFontMetrics metrics = fontMetrics();
    int font_hight = metrics.height();
    int offset = (topMargin_ - font_hight)/2; // center the text in the header area
    QPoint tA(cA.x()+h_spacing, hA.y() + offset);
    QPoint tB(hB.x(), tA.y() + font_hight); 
    QRect text_rect = QRect(tA, tB);
    QPalette p = palette();
    s->drawItemText(&painter, text_rect, flags, p, isEnabled(), title_, foregroundRole());
}

void ExpandableFrame::updatePointer(const QPoint& pos) {
    if( pos.y() < topMargin_ ) {
        if( !cursorOverHeader_ ) {
            cursorOverHeader_ = true;
            cursor_ = cursor();
            setCursor(QCursor(Qt::PointingHandCursor));
        }
    }
    else {
        if( cursorOverHeader_ ) {
            cursorOverHeader_ = false;
            setCursor(cursor_);
        }
    }
}

}} //namespace pdgm::gui
