Warning, file /office/calligra/libs/main/KoDocumentSectionDelegate.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).

0001 /*
0002   Copyright (c) 2006 Gábor Lehel <illissius@gmail.com>
0003   Copyright (c) 2008 Cyrille Berger <cberger@cberger.net>
0004   Copyright (c) 2011 José Luis Vergara <pentalis@gmail.com>
0005 
0006   This library is free software; you can redistribute it and/or
0007   modify it under the terms of the GNU Library General Public
0008   License as published by the Free Software Foundation; either
0009   version 2 of the License, or (at your option) any later version.
0010 
0011   This library is distributed in the hope that it will be useful,
0012   but WITHOUT ANY WARRANTY; without even the implied warranty of
0013   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0014   Library General Public License for more details.
0015 
0016   You should have received a copy of the GNU Library General Public License
0017   along with this library; see the file COPYING.LIB.  If not, write to
0018   the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
0019   Boston, MA 02110-1301, USA.
0020 */
0021 #include "KoDocumentSectionDelegate.h"
0022 #include "KoDocumentSectionModel.h"
0023 #include "KoDocumentSectionToolTip.h"
0024 #include "KoDocumentSectionView.h"
0025 
0026 #include <QApplication>
0027 #include <QKeyEvent>
0028 #include <QLineEdit>
0029 #include <QModelIndex>
0030 #include <QMouseEvent>
0031 #include <QPainter>
0032 #include <QPointer>
0033 #include <QStyle>
0034 #include <QStyleOptionViewItem>
0035 
0036 #include <klocalizedstring.h>
0037 
0038 class KoDocumentSectionDelegate::Private
0039 {
0040 public:
0041     Private() : view(0), edit(0) {}
0042 
0043     KoDocumentSectionView *view;
0044     QPointer<QWidget> edit;
0045     KoDocumentSectionToolTip tip;
0046     static const int margin = 1;
0047 };
0048 
0049 KoDocumentSectionDelegate::KoDocumentSectionDelegate(KoDocumentSectionView *view, QObject *parent)
0050     : QAbstractItemDelegate(parent)
0051     , d(new Private)
0052 {
0053     d->view = view;
0054     view->setItemDelegate(this);
0055     QApplication::instance()->installEventFilter(this);
0056 }
0057 
0058 KoDocumentSectionDelegate::~KoDocumentSectionDelegate()
0059 {
0060     delete d;
0061 }
0062 
0063 QSize KoDocumentSectionDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const
0064 {
0065     switch(d->view->displayMode()) {
0066     case View::ThumbnailMode: {
0067         const int height = thumbnailHeight(option, index) + textBoxHeight(option) + d->margin * 2;
0068         return QSize(availableWidth(), height);
0069     }
0070     case View::DetailedMode:
0071         return QSize(option.rect.width(),
0072             textBoxHeight(option) + option.decorationSize.height() + d->margin);
0073     case View::MinimalMode:
0074         return QSize(option.rect.width(), textBoxHeight(option));
0075     default:
0076         return option.rect.size();
0077     }
0078 }
0079 
0080 void KoDocumentSectionDelegate::paint(QPainter *p, const QStyleOptionViewItem &o, const QModelIndex &index) const
0081 {
0082     p->save();
0083     {
0084         QStyleOptionViewItem option = getOptions(o, index);
0085         QStyle *style = option.widget ? option.widget->style() : QApplication::style();
0086         style->drawPrimitive(QStyle::PE_PanelItemViewItem, &option, p, option.widget);
0087 
0088         p->setFont(option.font);
0089 
0090         drawText(p, option, index);
0091         drawIcons(p, option, index);
0092         drawThumbnail(p, option, index);
0093         drawDecoration(p, option, index);
0094         drawProgressBar(p, option, index);
0095     }
0096     p->restore();
0097 }
0098 
0099 bool KoDocumentSectionDelegate::editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index)
0100 {
0101     if ((event->type() == QEvent::MouseButtonPress || event->type() == QEvent::MouseButtonDblClick)
0102         && (index.flags() & Qt::ItemIsEnabled))
0103     {
0104         QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event);
0105 
0106         const QRect iconsRect_ = iconsRect(option, index).translated(option.rect.topLeft());
0107 
0108         if (iconsRect_.isValid() && iconsRect_.contains(mouseEvent->pos())) {
0109             const int iconWidth = option.decorationSize.width();
0110             int xPos = mouseEvent->pos().x() - iconsRect_.left();
0111             if (xPos % (iconWidth + d->margin) < iconWidth) { //it's on an icon, not a margin
0112                 Model::PropertyList propertyList = index.data(Model::PropertiesRole).value<Model::PropertyList>();
0113                 int clickedProperty = -1;
0114                 // Discover which of all properties was clicked
0115                 for (int i = 0; i < propertyList.count(); ++i) {
0116                     if (propertyList[i].isMutable) {
0117                         xPos -= iconWidth + d->margin;
0118                     }
0119                     ++clickedProperty;
0120                     if (xPos < 0) break;
0121                 }
0122                 // Using Ctrl+click to enter stasis
0123                 if (mouseEvent->modifiers() == Qt::ControlModifier
0124                     && propertyList[clickedProperty].canHaveStasis) {
0125                     // STEP 0: Prepare to Enter or Leave control key stasis
0126                     quint16 numberOfLeaves = model->rowCount(index.parent());
0127                     QModelIndex eachItem;
0128                     // STEP 1: Go.
0129                     if (propertyList[clickedProperty].isInStasis == false) { // Enter
0130                         /* Make every leaf of this node go State = False, saving the old property value to stateInStasis */
0131                         for (quint16 i = 0; i < numberOfLeaves; ++i) { // Foreach leaf in the node (index.parent())
0132                             eachItem = model->index(i, 0, index.parent());
0133                             // The entire property list has to be altered because model->setData cannot set individual properties
0134                             Model::PropertyList eachPropertyList = eachItem.data(Model::PropertiesRole).value<Model::PropertyList>();
0135                             eachPropertyList[clickedProperty].stateInStasis = eachPropertyList[clickedProperty].state.toBool();
0136                             eachPropertyList[clickedProperty].state = false;
0137                             eachPropertyList[clickedProperty].isInStasis = true;
0138                             model->setData(eachItem, QVariant::fromValue(eachPropertyList), Model::PropertiesRole);
0139                         }
0140                         /* Now set the current node's clickedProperty back to True, to save the user time
0141                         (obviously, if the user is clicking one item with ctrl+click, that item should
0142                         have a True property, value while the others are in stasis and set to False) */
0143                         // First refresh propertyList, otherwise old data will be saved back causing bugs
0144                         propertyList = index.data(Model::PropertiesRole).value<Model::PropertyList>();
0145                         propertyList[clickedProperty].state = true;
0146                         model->setData(index, QVariant::fromValue(propertyList), Model::PropertiesRole);
0147                     } else { // Leave
0148                         /* Make every leaf of this node go State = stateInStasis */
0149                         for (quint16 i = 0; i < numberOfLeaves; ++i) {
0150                             eachItem = model->index(i, 0, index.parent());
0151                             // The entire property list has to be altered because model->setData cannot set individual properties
0152                             Model::PropertyList eachPropertyList = eachItem.data(Model::PropertiesRole).value<Model::PropertyList>();
0153                             eachPropertyList[clickedProperty].state = eachPropertyList[clickedProperty].stateInStasis;
0154                             eachPropertyList[clickedProperty].isInStasis = false;
0155                             model->setData(eachItem, QVariant::fromValue(eachPropertyList), Model::PropertiesRole);
0156                         }
0157                     }
0158                 } else {
0159                     propertyList[clickedProperty].state = !propertyList[clickedProperty].state.toBool();
0160                     model->setData(index, QVariant::fromValue(propertyList), Model::PropertiesRole);
0161                 }
0162             }
0163             return true;
0164         }
0165 
0166         if (mouseEvent->button() == Qt::LeftButton &&
0167             mouseEvent->modifiers() == Qt::AltModifier) {
0168 
0169             d->view->setCurrentIndex(index);
0170             model->setData(index, true, Model::AlternateActiveRole);
0171             return true;
0172         }
0173 
0174         if (mouseEvent->button() != Qt::LeftButton) {
0175             d->view->setCurrentIndex(index);
0176             return false;
0177         }
0178     }
0179     else if (event->type() == QEvent::ToolTip) {
0180         QHelpEvent *helpEvent = static_cast<QHelpEvent*>(event);
0181         d->tip.showTip(d->view, helpEvent->pos(), option, index);
0182         return true;
0183     } else if (event->type() == QEvent::Leave) {
0184         d->tip.hide();
0185     }
0186 
0187     return false;
0188 }
0189 
0190 QWidget *KoDocumentSectionDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem&, const QModelIndex&) const
0191 {
0192     d->edit = new QLineEdit(parent);
0193     d->edit->installEventFilter(const_cast<KoDocumentSectionDelegate*>(this)); //hack?
0194     return d->edit;
0195 }
0196 
0197 void KoDocumentSectionDelegate::setEditorData(QWidget *widget, const QModelIndex &index) const
0198 {
0199     QLineEdit *edit = qobject_cast<QLineEdit*>(widget);
0200     Q_ASSERT(edit);
0201 
0202     edit->setText(index.data(Qt::DisplayRole).toString());
0203 }
0204 
0205 void KoDocumentSectionDelegate::setModelData(QWidget *widget, QAbstractItemModel *model, const QModelIndex &index) const
0206 {
0207     QLineEdit *edit = qobject_cast<QLineEdit*>(widget);
0208     Q_ASSERT(edit);
0209 
0210     model->setData(index, edit->text(), Qt::DisplayRole);
0211 }
0212 
0213 void KoDocumentSectionDelegate::updateEditorGeometry(QWidget *widget, const QStyleOptionViewItem &option, const QModelIndex &index) const
0214 {
0215     widget->setGeometry(textRect(option, index).translated(option.rect.topLeft()));
0216 }
0217 
0218 
0219 // PROTECTED
0220 
0221 
0222 bool KoDocumentSectionDelegate::eventFilter(QObject *object, QEvent *event)
0223 {
0224     switch (event->type()) {
0225     case QEvent::MouseButtonPress: {
0226         if (d->edit) {
0227             QMouseEvent *me = static_cast<QMouseEvent*>(event);
0228             if (!QRect(d->edit->mapToGlobal(QPoint()), d->edit->size()).contains(me->globalPos()))
0229                 emit closeEditor(d->edit);
0230         }
0231     } break;
0232     case QEvent::KeyPress: {
0233         QLineEdit *edit = qobject_cast<QLineEdit*>(object);
0234         if (edit && edit == d->edit) {
0235             QKeyEvent *ke = static_cast<QKeyEvent*>(event);
0236             switch (ke->key()) {
0237             case Qt::Key_Escape:
0238                 emit closeEditor(edit);
0239                 return true;
0240             case Qt::Key_Tab:
0241                 emit commitData(edit);
0242                 emit closeEditor(edit,EditNextItem);
0243                 return true;
0244             case Qt::Key_Backtab:
0245                 emit commitData(edit);
0246                 emit closeEditor(edit, EditPreviousItem);
0247                 return true;
0248             case Qt::Key_Return:
0249             case Qt::Key_Enter:
0250                 emit commitData(edit);
0251                 emit closeEditor(edit);
0252                 return true;
0253             default: break;
0254             }
0255         }
0256     } break;
0257     case QEvent::FocusOut : {
0258         QLineEdit *edit = qobject_cast<QLineEdit*>(object);
0259         if (edit && edit == d->edit) {
0260             emit commitData(edit);
0261             emit closeEditor(edit);
0262         }
0263     }
0264     default: break;
0265     }
0266 
0267     return QAbstractItemDelegate::eventFilter(object, event);
0268 }
0269 
0270 
0271 // PRIVATE
0272 
0273 
0274 QStyleOptionViewItem KoDocumentSectionDelegate::getOptions(const QStyleOptionViewItem &o, const QModelIndex &index)
0275 {
0276     QStyleOptionViewItem option = o;
0277     QVariant v = index.data(Qt::FontRole);
0278     if (v.isValid()) {
0279         option.font = v.value<QFont>();
0280         option.fontMetrics = QFontMetrics(option.font);
0281     }
0282     v = index.data(Qt::TextAlignmentRole);
0283     if (v.isValid())
0284         option.displayAlignment = QFlag(v.toInt());
0285     v = index.data(Qt::TextColorRole);
0286     if (v.isValid())
0287         option.palette.setColor(QPalette::Text, v.value<QColor>());
0288     v = index.data(Qt::BackgroundColorRole);
0289     if (v.isValid())
0290         option.palette.setColor(QPalette::Window, v.value<QColor>());
0291 
0292    return option;
0293 }
0294 
0295 int KoDocumentSectionDelegate::thumbnailHeight(const QStyleOptionViewItem &option, const QModelIndex &index) const
0296 {
0297     const QSize size = index.data(Qt::SizeHintRole).toSize();
0298     int width = option.rect.width();
0299     if (!option.rect.isValid())
0300         width = availableWidth();
0301     if (size.width() <= width)
0302         return size.height();
0303     else
0304         return int(width / (qreal(size.width()) / size.height()));
0305 }
0306 
0307 int KoDocumentSectionDelegate::availableWidth() const
0308 {
0309     return d->view->width(); // not viewport()->width(), otherwise we get infinite scrollbar addition/removal!
0310 }
0311 
0312 int KoDocumentSectionDelegate::textBoxHeight(const QStyleOptionViewItem &option) const
0313 {
0314     return qMax(option.fontMetrics.height(), option.decorationSize.height());
0315 }
0316 
0317 QRect KoDocumentSectionDelegate::textRect(const QStyleOptionViewItem &option, const QModelIndex &index) const
0318 {
0319     if (d->view->displayMode() == View::ThumbnailMode) {
0320         const QRect r = decorationRect(option, index);
0321         const int left = r.right() + d->margin;
0322         return QRect(left, r.top(), option.rect.width() - left, textBoxHeight(option));
0323     } else {
0324         static QFont f;
0325         static int minbearing = 1337 + 666; //can be 0 or negative, 2003 is less likely
0326         if (minbearing == 2003 || f != option.font) {
0327             f = option.font; //getting your bearings can be expensive, so we cache them
0328             minbearing = option.fontMetrics.minLeftBearing() + option.fontMetrics.minRightBearing();
0329         }
0330 
0331         int indent = decorationRect(option, index).right() + d->margin;
0332 
0333         const int width = (d->view->displayMode() == View::DetailedMode
0334                             ? option.rect.width()
0335                             : iconsRect(option, index).left())
0336                           - indent - d->margin + minbearing;
0337 
0338         return QRect(indent, 0, width, textBoxHeight(option));
0339     }
0340 }
0341 
0342 QRect KoDocumentSectionDelegate::iconsRect(const QStyleOptionViewItem &option, const QModelIndex &index) const
0343 {
0344     if (d->view->displayMode() == View::ThumbnailMode)
0345         return QRect();
0346 
0347     Model::PropertyList lp = index.data(Model::PropertiesRole).value<Model::PropertyList>();
0348     int propscount = 0;
0349     for (int i = 0, n = lp.count(); i < n; ++i)
0350         if (lp[i].isMutable)
0351             propscount++;
0352 
0353     const int iconswidth = propscount * option.decorationSize.width() + (propscount - 1) * d->margin;
0354 
0355     const int x = d->view->displayMode() == View::DetailedMode ? thumbnailRect(option, index).right() + d->margin : option.rect.width() - iconswidth;
0356     const int y = d->view->displayMode() == View::DetailedMode ? textBoxHeight(option) + d->margin : 0;
0357 
0358     return QRect(x, y, iconswidth, option.decorationSize.height());
0359 }
0360 
0361 QRect KoDocumentSectionDelegate::thumbnailRect(const QStyleOptionViewItem &option, const QModelIndex &index) const
0362 {
0363     if (d->view->displayMode() == View::ThumbnailMode)
0364         return QRect(0, 0, option.rect.width(), thumbnailHeight(option, index));
0365     else
0366         return QRect(0, 0, option.rect.height(), option.rect.height());
0367 }
0368 
0369 QRect KoDocumentSectionDelegate::decorationRect(const QStyleOptionViewItem &option, const QModelIndex &index) const
0370 {
0371     int width = option.decorationSize.width();
0372     if (index.data(Qt::DecorationRole).value<QIcon>().isNull())
0373         width = 0;
0374     switch(d->view->displayMode()) {
0375     case View::ThumbnailMode: {
0376         QFont font = option.font;
0377         if (index.data(Model::ActiveRole).toBool())
0378             font.setBold(!font.bold());
0379         const QFontMetrics metrics(font);
0380         const int totalwidth = metrics.width(index.data(Qt::DisplayRole).toString()) + width + d->margin;
0381         int left;
0382         if (totalwidth < option.rect.width())
0383             left = (option.rect.width() - totalwidth) / 2;
0384         else
0385             left = 0;
0386         return QRect(left, thumbnailRect(option, index).bottom() + d->margin, width, textBoxHeight(option));
0387     }
0388     case View::DetailedMode:
0389     case View::MinimalMode: {
0390         const int left = thumbnailRect(option, index).right() + d->margin;
0391         return QRect(left, 0, width, textBoxHeight(option));
0392     }
0393     default: return QRect();
0394     }
0395 }
0396 
0397 QRect KoDocumentSectionDelegate::progressBarRect(const QStyleOptionViewItem &option, const QModelIndex &index) const
0398 {
0399     if (d->view->displayMode() == View::ThumbnailMode)
0400         return QRect();
0401     QRect iconsRect_ = iconsRect(option, index);
0402     int width = d->view->width() / 4;
0403     if (d->view->displayMode() == View::DetailedMode) {
0404         // In detailed mode the progress bar take 50% width on the right of the icons
0405         return QRect(option.rect.width() - width - d->margin, iconsRect_.top(), width, iconsRect_.height()) ;
0406     } else {
0407         // In minimal mode the progress bar take 50% width on the left of icons
0408         return QRect(iconsRect_.left() - width - d->margin , iconsRect_.top(),
0409                       width, iconsRect_.height());
0410     }
0411 }
0412 
0413 void KoDocumentSectionDelegate::drawText(QPainter *p, const QStyleOptionViewItem &option, const QModelIndex &index) const
0414 {
0415     const QRect r = textRect(option, index).translated(option.rect.topLeft());
0416 
0417     p->save();
0418     {
0419         p->setClipRect(r);
0420         p->translate(r.left(), r.top());
0421         QPalette::ColorGroup cg = (option.state & QStyle::State_Enabled) ? QPalette::Active : QPalette::Disabled;
0422         QPalette::ColorRole cr = (option.state & QStyle::State_Selected) ? QPalette::HighlightedText : QPalette::Text;
0423         p->setPen(option.palette.color(cg, cr));
0424 
0425         if (index.data(Model::ActiveRole).toBool()) {
0426             QFont f = p->font();
0427             f.setBold(!f.bold());
0428             p->setFont(f);
0429         }
0430 
0431         const QString text = index.data(Qt::DisplayRole).toString();
0432         const QString elided = elidedText(p->fontMetrics(), r.width(), Qt::ElideRight, text);
0433         p->drawText(d->margin, 0, r.width(), r.height(), Qt::AlignLeft | Qt::AlignTop, elided);
0434     }
0435     p->restore();
0436 }
0437 
0438 void KoDocumentSectionDelegate::drawIcons(QPainter *p, const QStyleOptionViewItem &option, const QModelIndex &index) const
0439 {
0440     const QRect r = iconsRect(option, index).translated(option.rect.topLeft());
0441 
0442     p->save();
0443     {
0444         p->setClipRect(r);
0445         p->translate(r.left(), r.top());
0446         int x = 0;
0447         Model::PropertyList lp = index.data(Model::PropertiesRole).value<Model::PropertyList>();
0448         for(int i = 0, n = lp.count(); i < n; ++i) {
0449             if (lp[i].isMutable) {
0450                 QIcon icon = lp[i].state.toBool() ? lp[i].onIcon : lp[i].offIcon;
0451                 p->drawPixmap(x, 0, icon.pixmap(option.decorationSize, (option.state & QStyle::State_Enabled) ? QIcon::Normal : QIcon::Disabled));
0452                 x += option.decorationSize.width() + d->margin;
0453             }
0454         }
0455     }
0456     p->restore();
0457 }
0458 
0459 void KoDocumentSectionDelegate::drawThumbnail(QPainter *p, const QStyleOptionViewItem &option, const QModelIndex &index) const
0460 {
0461     const QRect r = thumbnailRect(option, index).translated(option.rect.topLeft());
0462 
0463     p->save();
0464     {
0465         p->setClipRect(r);
0466         const qreal myratio = qreal(r.width()) / r.height();
0467         const qreal thumbratio = index.data(Model::AspectRatioRole).toDouble();
0468         const int s = (myratio > thumbratio) ? r.height() : r.width();
0469 
0470         QImage img = index.data(int(Model::BeginThumbnailRole) + s).value<QImage>();
0471         if (!(option.state & QStyle::State_Enabled)) {
0472             // Make the image grayscale
0473             // TODO: if someone feel bored a more optimized version of this would be welcome
0474             for(int i = 0; i < img.width(); ++i) {
0475                 for(int j = 0; j < img.width(); ++j) {
0476                     img.setPixel(i, j, qGray(img.pixel(i,j)));
0477                 }
0478             }
0479         }
0480         QPoint offset;
0481         offset.setX(r.width()/2 - img.width()/2);
0482         offset.setY(r.height()/2 - img.height()/2);
0483 
0484         if (!img.isNull() && img.width() > 0 && img.height() > 0) {
0485             p->drawImage(r.topLeft() + offset, img);
0486         }
0487     }
0488     p->restore();
0489 }
0490 
0491 void KoDocumentSectionDelegate::drawDecoration(QPainter *p, const QStyleOptionViewItem &option, const QModelIndex &index) const
0492 {
0493     const QRect r = decorationRect(option, index).translated(option.rect.topLeft());
0494 
0495     p->save();
0496     {
0497         p->setClipRect(r);
0498         p->translate(r.topLeft());
0499         if (!index.data(Qt::DecorationRole).value<QIcon>().isNull())
0500             p->drawPixmap(0, 0, index.data(Qt::DecorationRole).value<QIcon>().pixmap(option.decorationSize, (option.state & QStyle::State_Enabled) ? QIcon::Normal : QIcon::Disabled));
0501     }
0502     p->restore();
0503 }
0504 
0505 void KoDocumentSectionDelegate::drawProgressBar(QPainter *p, const QStyleOptionViewItem &option, const QModelIndex &index) const
0506 {
0507     QVariant value = index.data(KoDocumentSectionModel::ProgressRole);
0508     if (!value.isNull() && (value.toInt() >= 0 && value.toInt() <= 100)) {
0509         const QRect r = progressBarRect(option, index).translated(option.rect.topLeft());
0510         p->save();
0511         {
0512             p->setClipRect(r);
0513             QStyle* style = QApplication::style();
0514             QStyleOptionProgressBar opt;
0515 
0516             opt.minimum = 0;
0517             opt.maximum = 100;
0518             opt.progress = value.toInt();
0519             opt.textVisible = true;
0520             opt.textAlignment = Qt::AlignHCenter;
0521             opt.text = i18n("%1 %", opt.progress);
0522             opt.rect = r;
0523             opt.orientation = Qt::Horizontal;
0524             opt.state = option.state;
0525             style->drawControl(QStyle::CE_ProgressBar, &opt, p, 0);
0526         }
0527         p->restore();
0528     }
0529 }