51ee678a81
- replacing Q_WS_MAC => Q_OS_MAC - incomplete migration to Qt5
482 lines
16 KiB
C++
482 lines
16 KiB
C++
#include "qxtitemdelegate.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 "qxtitemdelegate_p.h"
|
|
#include <QTextDocument>
|
|
#include <QPixmapCache>
|
|
#include <QApplication>
|
|
#include <QTreeView>
|
|
#include <QPainter>
|
|
#include <QTimer>
|
|
|
|
static const int TOP_LEVEL_EXTENT = 2;
|
|
|
|
QxtItemDelegatePrivate::QxtItemDelegatePrivate() :
|
|
textVisible(true),
|
|
progressFormat("%1%"),
|
|
elide(Qt::ElideMiddle),
|
|
style(Qxt::NoDecoration),
|
|
document(0)
|
|
{
|
|
}
|
|
|
|
void QxtItemDelegatePrivate::paintButton(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index, const QTreeView* view) const
|
|
{
|
|
// draw the button
|
|
QStyleOptionButton buttonOption;
|
|
buttonOption.state = option.state;
|
|
#ifdef Q_OS_MAC
|
|
buttonOption.state |= QStyle::State_Raised;
|
|
#endif
|
|
buttonOption.state &= ~QStyle::State_HasFocus;
|
|
if (view->isExpanded(index))
|
|
buttonOption.state |= QStyle::State_Sunken;
|
|
buttonOption.rect = option.rect;
|
|
buttonOption.palette = option.palette;
|
|
buttonOption.features = QStyleOptionButton::None;
|
|
view->style()->drawControl(QStyle::CE_PushButton, &buttonOption, painter, view);
|
|
|
|
// draw the branch indicator
|
|
static const int i = 9;
|
|
const QRect& r = option.rect;
|
|
if (index.model()->hasChildren(index))
|
|
{
|
|
QStyleOption branchOption;
|
|
branchOption.initFrom(view);
|
|
if (branchOption.direction == Qt::LeftToRight)
|
|
branchOption.rect = QRect(r.left() + i / 2, r.top() + (r.height() - i) / 2, i, i);
|
|
else
|
|
branchOption.rect = QRect(r.right() - i / 2 - i, r.top() + (r.height() - i) / 2, i, i);
|
|
branchOption.palette = option.palette;
|
|
branchOption.state = QStyle::State_Children;
|
|
if (view->isExpanded(index))
|
|
branchOption.state |= QStyle::State_Open;
|
|
view->style()->drawPrimitive(QStyle::PE_IndicatorBranch, &branchOption, painter, view);
|
|
}
|
|
|
|
// draw the text
|
|
QRect textrect = QRect(r.left() + i * 2, r.top(), r.width() - ((5 * i) / 2), r.height());
|
|
QString text = option.fontMetrics.elidedText(index.data().toString(), elide, textrect.width());
|
|
view->style()->drawItemText(painter, textrect, Qt::AlignCenter, option.palette, view->isEnabled(), text);
|
|
}
|
|
|
|
void QxtItemDelegatePrivate::paintMenu(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index, const QTreeView* view) const
|
|
{
|
|
// draw the menu bar item
|
|
QStyleOptionMenuItem menuOption;
|
|
menuOption.palette = view->palette();
|
|
menuOption.fontMetrics = view->fontMetrics();
|
|
menuOption.state = QStyle::State_None;
|
|
// QModelIndex::flags() was introduced in 4.2
|
|
// => therefore "index.model()->flags(index)"
|
|
if (view->isEnabled() && index.model()->flags(index) & Qt::ItemIsEnabled)
|
|
menuOption.state |= QStyle::State_Enabled;
|
|
else
|
|
menuOption.palette.setCurrentColorGroup(QPalette::Disabled);
|
|
menuOption.state |= QStyle::State_Selected;
|
|
menuOption.state |= QStyle::State_Sunken;
|
|
menuOption.state |= QStyle::State_HasFocus;
|
|
menuOption.rect = option.rect;
|
|
menuOption.text = index.data().toString();
|
|
menuOption.icon = QIcon(index.data(Qt::DecorationRole).value<QPixmap>());
|
|
view->style()->drawControl(QStyle::CE_MenuBarItem, &menuOption, painter, view);
|
|
|
|
// draw the an arrow as a branch indicator
|
|
if (index.model()->hasChildren(index))
|
|
{
|
|
QStyle::PrimitiveElement arrow;
|
|
if (view->isExpanded(index))
|
|
arrow = QStyle::PE_IndicatorArrowUp;
|
|
else
|
|
arrow = QStyle::PE_IndicatorArrowDown;
|
|
static const int i = 9;
|
|
const QRect& r = option.rect;
|
|
menuOption.rect = QRect(r.left() + i / 2, r.top() + (r.height() - i) / 2, i, i);
|
|
view->style()->drawPrimitive(arrow, &menuOption, painter, view);
|
|
}
|
|
}
|
|
|
|
void QxtItemDelegatePrivate::paintProgress(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const
|
|
{
|
|
QVariant min = index.data(QxtItemDelegate::ProgressMinimumRole);
|
|
QVariant max = index.data(QxtItemDelegate::ProgressMaximumRole);
|
|
|
|
QStyleOptionProgressBar opt;
|
|
opt.minimum = (min.isValid() && min.canConvert(QVariant::Int)) ? min.toInt() : 0;
|
|
opt.maximum = (max.isValid() && max.canConvert(QVariant::Int)) ? max.toInt() : 100;
|
|
opt.progress = index.data(QxtItemDelegate::ProgressValueRole).toInt();
|
|
opt.rect = option.rect;
|
|
opt.textVisible = textVisible;
|
|
opt.text = progressFormat.arg(opt.progress);
|
|
QApplication::style()->drawControl(QStyle::CE_ProgressBar, &opt, painter, 0);
|
|
|
|
QWidget* viewport = dynamic_cast<QWidget*>(painter->device());
|
|
if (viewport)
|
|
{
|
|
if (opt.minimum == 0 && opt.maximum == 0)
|
|
{
|
|
if (!updatedItems.contains(viewport))
|
|
connect(viewport, SIGNAL(destroyed()), this, SLOT(viewDestroyed()));
|
|
updatedItems.replace(viewport, index);
|
|
}
|
|
else
|
|
{
|
|
updatedItems.remove(viewport, index);
|
|
if (!updatedItems.contains(viewport))
|
|
disconnect(viewport, SIGNAL(destroyed()), this, SLOT(viewDestroyed()));
|
|
}
|
|
}
|
|
|
|
if (updatedItems.isEmpty())
|
|
{
|
|
if (updateTimer.isActive())
|
|
updateTimer.stop();
|
|
}
|
|
else
|
|
{
|
|
if (!updateTimer.isActive())
|
|
updateTimer.start(1000 / 25, const_cast<QxtItemDelegatePrivate*>(this));
|
|
}
|
|
}
|
|
|
|
void QxtItemDelegatePrivate::setCurrentEditor(QWidget* editor, const QModelIndex& index) const
|
|
{
|
|
currentEditor = editor;
|
|
currentEdited = index;
|
|
}
|
|
|
|
void QxtItemDelegatePrivate::timerEvent(QTimerEvent* event)
|
|
{
|
|
if (event->timerId() == updateTimer.timerId())
|
|
{
|
|
QMutableHashIterator<QWidget*, QPersistentModelIndex> it(updatedItems);
|
|
while (it.hasNext())
|
|
{
|
|
it.next();
|
|
if (!it.key())
|
|
{
|
|
it.remove();
|
|
continue;
|
|
}
|
|
|
|
// try to update the specific view item instead of the whole view if possible
|
|
if (QAbstractItemView* view = qobject_cast<QAbstractItemView*>(it.key()->parentWidget()))
|
|
view->update(it.value());
|
|
else
|
|
it.key()->update();
|
|
}
|
|
}
|
|
}
|
|
|
|
void QxtItemDelegatePrivate::viewDestroyed()
|
|
{
|
|
QWidget* viewport = qobject_cast<QWidget*>(sender());
|
|
if (viewport)
|
|
{
|
|
updatedItems.remove(viewport);
|
|
}
|
|
}
|
|
|
|
void QxtItemDelegatePrivate::closeEditor(QWidget* editor)
|
|
{
|
|
if (currentEdited.isValid() && editor == currentEditor)
|
|
{
|
|
setCurrentEditor(0, QModelIndex());
|
|
emit qxt_p().editingFinished(currentEdited);
|
|
}
|
|
}
|
|
|
|
/*!
|
|
\class QxtItemDelegate
|
|
\inmodule QxtWidgets
|
|
\brief The QxtItemDelegate class is an extended QItemDelegate
|
|
|
|
QxtItemDelegate provides signals for starting and finishing of
|
|
editing and an optional decoration of top level items in a QTreeView.
|
|
|
|
QxtItemDelegate can also draw a progress bar for items providing
|
|
appropriate progress data. Just like QProgressBar, QxtItemDelegate
|
|
can show a busy indicator. If minimum and maximum both are set to
|
|
\c 0, a busy indicator is shown instead of a percentage of steps.
|
|
*/
|
|
|
|
/*!
|
|
\fn QxtItemDelegate::editingStarted(const QModelIndex& index)
|
|
|
|
This signal is emitted after the editing of \a index has been started.
|
|
|
|
\sa editingFinished()
|
|
*/
|
|
|
|
/*!
|
|
\fn QxtItemDelegate::editingFinished(const QModelIndex& index)
|
|
|
|
This signal is emitted after the editing of \a index has been finished.
|
|
|
|
\sa editingStarted()
|
|
*/
|
|
|
|
/*!
|
|
\enum QxtItemDelegate::Role
|
|
|
|
This enum defines custom roles used by QxtItemDelegate.
|
|
|
|
\value ProgressValueRole The progress value.
|
|
\value ProgressMinimumRole The minimum progress value. The default value \c 0 is used if no value is provided for ProgressMinimumRole.
|
|
\value ProgressMaximumRole The maximum progress value. The default value \c 100 is used if no value is provided for ProgressMaximumRole.
|
|
*/
|
|
|
|
/*!
|
|
Constructs a new QxtItemDelegate with \a parent.
|
|
*/
|
|
QxtItemDelegate::QxtItemDelegate(QObject* parent) : QItemDelegate(parent)
|
|
{
|
|
QXT_INIT_PRIVATE(QxtItemDelegate);
|
|
connect(this, SIGNAL(closeEditor(QWidget*)), &qxt_d(), SLOT(closeEditor(QWidget*)));
|
|
}
|
|
|
|
/*!
|
|
Destructs the item delegate.
|
|
*/
|
|
QxtItemDelegate::~QxtItemDelegate()
|
|
{}
|
|
|
|
/*!
|
|
\property QxtItemDelegate::decorationStyle
|
|
\brief the top level index decoration style
|
|
|
|
Top level indices are decorated according to this property.
|
|
The default value is Qxt::NoDecoration.
|
|
|
|
\bold {Note:} The property has effect only in case the delegate is installed
|
|
on a QTreeView. The view must be the parent of the delegate.
|
|
|
|
\bold {Note:} Set QTreeView::rootIsDecorated to \c false to avoid
|
|
multiple branch indicators.
|
|
|
|
\sa Qxt::DecorationStyle, QTreeView::rootIsDecorated
|
|
*/
|
|
Qxt::DecorationStyle QxtItemDelegate::decorationStyle() const
|
|
{
|
|
return qxt_d().style;
|
|
}
|
|
|
|
void QxtItemDelegate::setDecorationStyle(Qxt::DecorationStyle style)
|
|
{
|
|
qxt_d().style = style;
|
|
}
|
|
|
|
/*!
|
|
\property QxtItemDelegate::elideMode
|
|
\brief the text elide mode
|
|
|
|
The text of a decorated top level index is elided according to this property.
|
|
The default value is Qt::ElideMiddle.
|
|
|
|
\bold {Note:} The property has effect only for decorated top level indices.
|
|
|
|
\sa decorationStyle, Qt::TextElideMode
|
|
*/
|
|
Qt::TextElideMode QxtItemDelegate::elideMode() const
|
|
{
|
|
return qxt_d().elide;
|
|
}
|
|
|
|
void QxtItemDelegate::setElideMode(Qt::TextElideMode mode)
|
|
{
|
|
qxt_d().elide = mode;
|
|
}
|
|
|
|
/*!
|
|
\property QxtItemDelegate::progressTextFormat
|
|
\brief the format of optional progress text
|
|
|
|
The progress text is formatted according to this property.
|
|
The default value is \bold "%1%".
|
|
|
|
\bold {Note:} Progress bar is rendered for indices providing valid
|
|
numerical data for ProgressValueRole.
|
|
|
|
\bold {Note:} \bold \%1 is replaced by the progress percent.
|
|
|
|
\sa progressTextVisible, Role
|
|
*/
|
|
QString QxtItemDelegate::progressTextFormat() const
|
|
{
|
|
return qxt_d().progressFormat;
|
|
}
|
|
|
|
void QxtItemDelegate::setProgressTextFormat(const QString& format)
|
|
{
|
|
qxt_d().progressFormat = format;
|
|
}
|
|
|
|
/*!
|
|
\property QxtItemDelegate::progressTextVisible
|
|
\brief whether progress text is visible
|
|
|
|
The default value is \c true.
|
|
|
|
\bold {Note:} Progress bar is rendered for indices providing valid
|
|
numerical data for ProgressValueRole.
|
|
|
|
\sa progressTextFormat, QxtItemDelegate::Role
|
|
*/
|
|
bool QxtItemDelegate::isProgressTextVisible() const
|
|
{
|
|
return qxt_d().textVisible;
|
|
}
|
|
|
|
void QxtItemDelegate::setProgressTextVisible(bool visible)
|
|
{
|
|
qxt_d().textVisible = visible;
|
|
}
|
|
|
|
/*!
|
|
\reimp
|
|
*/
|
|
QWidget* QxtItemDelegate::createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const
|
|
{
|
|
QWidget* editor = QItemDelegate::createEditor(parent, option, index);
|
|
qxt_d().setCurrentEditor(editor, index);
|
|
emit const_cast<QxtItemDelegate*>(this)->editingStarted(index);
|
|
return editor;
|
|
}
|
|
|
|
/*!
|
|
\reimp
|
|
*/
|
|
void QxtItemDelegate::setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const
|
|
{
|
|
QItemDelegate::setModelData(editor, model, index);
|
|
qxt_d().setCurrentEditor(0, QModelIndex());
|
|
emit const_cast<QxtItemDelegate*>(this)->editingFinished(index);
|
|
}
|
|
|
|
/*!
|
|
\reimp
|
|
*/
|
|
void QxtItemDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const
|
|
{
|
|
const QAbstractItemModel* model = index.model();
|
|
const QTreeView* tree = qobject_cast<QTreeView*>(parent());
|
|
const bool topLevel = !index.parent().isValid();
|
|
|
|
if (tree && model && topLevel && qxt_d().style != Qxt::NoDecoration)
|
|
{
|
|
QStyleOptionViewItem opt;
|
|
opt.QStyleOption::operator=(option);
|
|
opt.showDecorationSelected = false;
|
|
|
|
QModelIndex valid = model->index(index.row(), 0);
|
|
QModelIndex sibling = valid;
|
|
while (sibling.isValid())
|
|
{
|
|
opt.rect |= tree->visualRect(sibling);
|
|
sibling = sibling.sibling(sibling.row(), sibling.column() + 1);
|
|
}
|
|
|
|
switch (qxt_d().style)
|
|
{
|
|
case Qxt::Buttonlike:
|
|
qxt_d().paintButton(painter, opt, valid, tree);
|
|
break;
|
|
case Qxt::Menulike:
|
|
qxt_d().paintMenu(painter, opt, valid, tree);
|
|
break;
|
|
default:
|
|
qWarning("QxtItemDelegate::paint() unknown decoration style");
|
|
QItemDelegate::paint(painter, opt, valid);
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
QItemDelegate::paint(painter, option, index);
|
|
|
|
const QVariant data = index.data(ProgressValueRole);
|
|
if (data.isValid() && data.canConvert(QVariant::Int))
|
|
qxt_d().paintProgress(painter, option, index);
|
|
}
|
|
}
|
|
|
|
/*!
|
|
\reimp
|
|
*/
|
|
void QxtItemDelegate::drawDisplay(QPainter* painter, const QStyleOptionViewItem& option, const QRect& rect, const QString& text) const
|
|
{
|
|
if (!Qt::mightBeRichText(text))
|
|
{
|
|
QItemDelegate::drawDisplay(painter, option, rect, text);
|
|
return;
|
|
}
|
|
|
|
QString key = QString(QLatin1String("QxtItemDelegate:%1")).arg(text);
|
|
QPixmap pixmap;
|
|
if (!QPixmapCache::find(key, pixmap))
|
|
{
|
|
if (!qxt_d().document)
|
|
qxt_d().document = new QTextDocument(const_cast<QxtItemDelegate*>(this));
|
|
qxt_d().document->setHtml(text);
|
|
qxt_d().document->adjustSize();
|
|
|
|
pixmap = QPixmap(qxt_d().document->size().toSize());
|
|
pixmap.fill(Qt::transparent);
|
|
QPainter painter(&pixmap);
|
|
qxt_d().document->drawContents(&painter);
|
|
painter.end();
|
|
QPixmapCache::insert(key, pixmap);
|
|
}
|
|
painter->drawPixmap(option.rect.topLeft(), pixmap);
|
|
}
|
|
|
|
/*!
|
|
\reimp
|
|
*/
|
|
QSize QxtItemDelegate::sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const
|
|
{
|
|
// something slightly bigger for top level indices if decorated
|
|
QSize size = QItemDelegate::sizeHint(option, index);
|
|
if (!index.parent().isValid() && qxt_d().style != Qxt::NoDecoration)
|
|
size += QSize(TOP_LEVEL_EXTENT, TOP_LEVEL_EXTENT);
|
|
|
|
const QString text = index.data(Qt::DisplayRole).toString();
|
|
if (Qt::mightBeRichText(text))
|
|
{
|
|
if (!qxt_d().document)
|
|
qxt_d().document = new QTextDocument(const_cast<QxtItemDelegate*>(this));
|
|
qxt_d().document->setHtml(text);
|
|
qxt_d().document->adjustSize();
|
|
size = size.expandedTo(qxt_d().document->size().toSize()); // TODO: checkbox, icon, etc.
|
|
}
|
|
return size;
|
|
}
|