File indexing completed on 2024-04-14 15:48:53

0001 /*
0002  *   Copyright (C) 2007 Ivan Cukic <ivan.cukic+kde@gmail.com>
0003  *   Copyright (C) 2008-2011 Daniel Nicoletti <dantti12@gmail.com>
0004  *
0005  *   This program is free software; you can redistribute it and/or modify
0006  *   it under the terms of the GNU Library/Lesser General Public License
0007  *   version 2, or (at your option) any later version, as published by the
0008  *   Free Software Foundation
0009  *
0010  *   This program is distributed in the hope that it will be useful,
0011  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
0012  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0013  *   GNU General Public License for more details
0014  *
0015  *   You should have received a copy of the GNU Library/Lesser General Public
0016  *   License along with this program; if not, write to the
0017  *   Free Software Foundation, Inc.,
0018  *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
0019  */
0020 
0021 #include "ChangesDelegate.h"
0022 
0023 #include <KIconLoader>
0024 #include <KLocalizedString>
0025 #include <QApplication>
0026 #include <QLoggingCategory>
0027 #include <QPushButton>
0028 #include <QTreeView>
0029 #include <QHeaderView>
0030 
0031 #include <QPainter>
0032 
0033 #include "PackageModel.h"
0034 #include "PkIcons.h"
0035 
0036 #include <Transaction>
0037 
0038 #define FAV_ICON_SIZE 24
0039 #define EMBLEM_ICON_SIZE 8
0040 #define UNIVERSAL_PADDING 4
0041 #define FADE_LENGTH 16
0042 #define MAIN_ICON_SIZE 32
0043 
0044 Q_DECLARE_LOGGING_CATEGORY(APPER_LIB)
0045 
0046 using namespace PackageKit;
0047 
0048 ChangesDelegate::ChangesDelegate(QAbstractItemView *parent) :
0049     KExtendableItemDelegate(parent),
0050     m_viewport(parent->viewport()),
0051     // loads it here to be faster when displaying items
0052     m_packageIcon(QIcon::fromTheme(QLatin1String("package"))),
0053     m_collectionIcon(QIcon::fromTheme(QLatin1String("package-orign"))),
0054     m_installIcon(QIcon::fromTheme(QLatin1String("dialog-cancel"))),
0055     m_installString(i18n("Do not Install")),
0056     m_removeIcon(QIcon::fromTheme(QLatin1String("dialog-cancel"))),
0057     m_removeString(i18n("Do not Remove")),
0058     m_undoIcon(QIcon::fromTheme(QLatin1String("edit-undo"))),
0059     m_undoString(i18n("Deselect")),
0060     m_checkedIcon(QIcon::fromTheme(QLatin1String("dialog-ok-apply")))
0061 {
0062     // maybe rename or copy it to package-available
0063     if (QApplication::isRightToLeft()) {
0064         setExtendPixmap(SmallIcon(QLatin1String("arrow-left")));
0065     } else {
0066         setExtendPixmap(SmallIcon(QLatin1String("arrow-right")));
0067     }
0068     setContractPixmap(SmallIcon(QLatin1String("arrow-down")));
0069     // store the size of the extend pixmap to know how much we should move
0070     m_extendPixmapWidth = SmallIcon(QLatin1String("arrow-right")).size().width();
0071 
0072     QPushButton button, button2;
0073     button.setText(m_installString);
0074     button.setIcon(m_installIcon);
0075     button2.setText(m_removeString);
0076     button2.setIcon(m_removeIcon);
0077     m_buttonSize = button.sizeHint();
0078     int width = qMax(button.sizeHint().width(), button2.sizeHint().width());
0079     button.setText(m_undoString);
0080     width = qMax(width, button2.sizeHint().width());
0081     m_buttonSize.setWidth(width);
0082     m_buttonIconSize = button.iconSize();
0083 }
0084 
0085 void ChangesDelegate::paint(QPainter *painter,
0086                             const QStyleOptionViewItem &option,
0087                             const QModelIndex &index) const
0088 {
0089     if (!index.isValid()) {
0090         return;
0091     }
0092     bool leftToRight = (painter->layoutDirection() == Qt::LeftToRight);
0093 
0094     QStyleOptionViewItem opt(option);
0095     QStyle *style = opt.widget ? opt.widget->style() : QApplication::style();
0096     painter->save();
0097     style->drawPrimitive(QStyle::PE_PanelItemViewItem, &opt, painter, opt.widget);
0098     painter->restore();
0099 
0100     //grab the package from the index pointer
0101     QString pkgName       = index.data(PackageModel::NameRole).toString();
0102     QString pkgSummary    = index.data(PackageModel::SummaryRole).toString();
0103     QString pkgVersion    = index.data(PackageModel::VersionRole).toString();
0104     QString pkgArch       = index.data(PackageModel::ArchRole).toString();
0105 //     QString pkgIconPath   = index.data(PackageModel::IconPathRole).toString();
0106     bool    pkgChecked    = index.data(PackageModel::CheckStateRole).toBool();
0107     bool    pkgCheckable  = !index.data(Qt::CheckStateRole).isNull();
0108     Transaction::Info info;
0109     info = index.data(PackageModel::InfoRole).value<Transaction::Info>();
0110     bool    pkgInstalled  = (info == Transaction::InfoInstalled ||
0111                              info == Transaction::InfoCollectionInstalled);
0112 
0113     bool    pkgCollection = (info == Transaction::InfoCollectionInstalled ||
0114                              info == Transaction::InfoCollectionAvailable);
0115 
0116     QIcon emblemIcon;
0117     if (pkgCheckable) {
0118         // update kind icon
0119         emblemIcon = index.data(PackageModel::IconRole).value<QIcon>();
0120     } else {
0121         emblemIcon = m_checkedIcon;
0122     }
0123 
0124     // pain the background (checkbox and the extender)
0125     if (m_extendPixmapWidth) {
0126         KExtendableItemDelegate::paint(painter, opt, index);
0127     }
0128 
0129     int leftCount;
0130     if (leftToRight) {
0131         opt.rect.setLeft(option.rect.left() + m_extendPixmapWidth + UNIVERSAL_PADDING);
0132         leftCount = opt.rect.left() + UNIVERSAL_PADDING;
0133     } else {
0134         opt.rect.setRight(option.rect.right() - m_extendPixmapWidth - UNIVERSAL_PADDING);
0135         leftCount = opt.rect.width() - (UNIVERSAL_PADDING + MAIN_ICON_SIZE);
0136     }
0137 
0138     int left = opt.rect.left();
0139     int top = opt.rect.top();
0140     int width = opt.rect.width();
0141 
0142     QStyleOptionButton optBt;
0143     optBt.rect = opt.rect;
0144     if (pkgCheckable) {
0145         optBt.rect = style->subElementRect(QStyle::SE_CheckBoxIndicator, &optBt);
0146         // Count the checkbox size
0147         if (leftToRight) {
0148             leftCount += optBt.rect.width();
0149         } else {
0150             leftCount -= optBt.rect.width();
0151         }
0152     } else  if ((option.state & QStyle::State_MouseOver) ||
0153                 (option.state & QStyle::State_Selected) ||
0154                 !pkgChecked) {
0155         if (leftToRight) {
0156             optBt.rect.setLeft(left + width - (m_buttonSize.width() + UNIVERSAL_PADDING));
0157             width -= m_buttonSize.width() + UNIVERSAL_PADDING;
0158         } else {
0159             optBt.rect.setLeft(left + UNIVERSAL_PADDING);
0160             left += m_buttonSize.width() + UNIVERSAL_PADDING;
0161         }
0162         // Calculate the top of the button which is the item height - the button height size divided by 2
0163         // this give us a little value which is the top and bottom margin
0164         optBt.rect.setTop(optBt.rect.top() + ((calcItemHeight(option) - m_buttonSize.height()) / 2));
0165         optBt.rect.setSize(m_buttonSize); // the width and height sizes of the button
0166         optBt.features = QStyleOptionButton::Flat;
0167         optBt.iconSize = m_buttonIconSize;
0168         optBt.icon = pkgInstalled ? m_removeIcon   : m_installIcon;
0169         optBt.text = pkgInstalled ? m_removeString : m_installString;
0170         if (pkgChecked) {
0171             optBt.state |= QStyle::State_Raised | QStyle::State_Active | QStyle::State_Enabled;;
0172         } else {
0173             if ((option.state & QStyle::State_MouseOver) &&
0174                 !(option.state & QStyle::State_Selected)) {
0175                 optBt.state |= QStyle::State_MouseOver;
0176             }
0177             optBt.state |= QStyle::State_Sunken | QStyle::State_Active | QStyle::State_Enabled;
0178         }
0179         style->drawControl(QStyle::CE_PushButton, &optBt, painter);
0180     }
0181 
0182 // QAbstractItemView *view = qobject_cast<QAbstractItemView*>(parent());
0183 //             QPoint pos = view->viewport()->mapFromGlobal(QCursor::pos());
0184 //     kDebug() << pos;
0185 
0186 
0187     // selects the mode to paint the icon based on the info field
0188     QIcon::Mode iconMode = QIcon::Normal;
0189     if (option.state & QStyle::State_MouseOver) {
0190         iconMode = QIcon::Active;
0191     }
0192 
0193     QColor foregroundColor = (option.state.testFlag(QStyle::State_Selected))?
0194     option.palette.color(QPalette::HighlightedText):option.palette.color(QPalette::Text);
0195 
0196     // Painting main column
0197     QStyleOptionViewItem local_option_title(option);
0198     QStyleOptionViewItem local_option_normal(option);
0199 
0200     local_option_normal.font.setPointSize(local_option_normal.font.pointSize() - 1);
0201 
0202     QPixmap pixmap(option.rect.size());
0203     pixmap.fill(Qt::transparent);
0204     QPainter p(&pixmap);
0205     p.translate(-option.rect.topLeft());
0206 
0207     // Main icon
0208     QIcon icon;
0209     if (pkgCollection) {
0210         icon = m_collectionIcon;
0211     } else {
0212         icon = PkIcons::getIcon(index.data(PackageModel::IconRole).toString(), QString());
0213         if (icon.isNull()) {
0214             icon = m_packageIcon;
0215         }
0216     }
0217 //     if (pkgIconPath.isEmpty()) {
0218 //        icon = pkgCollection ? m_collectionIcon : m_packageIcon;
0219 //     } else {
0220 //         icon = PkIcons::getIcon(pkgIconPath, "package");
0221 //     }
0222 
0223     int iconSize = calcItemHeight(option) - 2 * UNIVERSAL_PADDING;
0224     icon.paint(&p,
0225                leftCount,
0226                top + UNIVERSAL_PADDING,
0227                iconSize,
0228                iconSize,
0229                Qt::AlignCenter,
0230                iconMode);
0231 
0232     int textWidth;
0233     if (leftToRight) {
0234         // add the main icon
0235         leftCount += iconSize + UNIVERSAL_PADDING;
0236         textWidth = width - (leftCount - left);
0237     } else {
0238         leftCount -= UNIVERSAL_PADDING;
0239         textWidth = leftCount - left;
0240         leftCount = left;
0241     }
0242 
0243 
0244     // Painting
0245 
0246     // Text
0247     const int itemHeight = calcItemHeight(option);
0248 
0249     p.setPen(foregroundColor);
0250     // compose the top line
0251     // Collections does not have version and arch
0252     if (option.state & QStyle::State_MouseOver && !pkgCollection) {
0253         //! pkgName = pkgName + " - " + pkgVersion + (pkgArch.isNull() ? NULL : " (" + pkgArch + ')');
0254     }
0255 
0256     // draw the top line
0257     int topTextHeight = QFontInfo(local_option_title.font).pixelSize();
0258     p.setFont(local_option_title.font);
0259     p.drawText(leftCount,
0260                top,
0261                textWidth,
0262                topTextHeight + UNIVERSAL_PADDING,
0263                Qt::AlignVCenter | Qt::AlignLeft,
0264                pkgName);
0265 
0266     // draw the bottom line
0267     iconSize = topTextHeight + UNIVERSAL_PADDING;
0268     if (pkgCheckable || pkgInstalled) {
0269         emblemIcon.paint(&p,
0270                          leftToRight ? leftCount : (textWidth + left) - iconSize,
0271                          top + topTextHeight + UNIVERSAL_PADDING,
0272                          iconSize,
0273                          iconSize,
0274                          Qt::AlignVCenter | Qt::AlignHCenter,
0275                          iconMode);
0276     }
0277 
0278     // store the original opacity
0279     qreal opa = p.opacity();
0280     if (!(option.state & QStyle::State_MouseOver) && !(option.state & QStyle::State_Selected)) {
0281         p.setOpacity(opa / 2.5);
0282     }
0283 
0284     p.setFont(local_option_normal.font);
0285     p.drawText(leftToRight ? leftCount + iconSize + UNIVERSAL_PADDING : left - UNIVERSAL_PADDING,
0286                top + itemHeight / 2,
0287                textWidth - iconSize,
0288                QFontInfo(local_option_normal.font).pixelSize() + UNIVERSAL_PADDING,
0289                Qt::AlignTop | Qt::AlignLeft,
0290                pkgSummary);
0291     p.setOpacity(opa);
0292 
0293     QLinearGradient gradient;
0294     // Gradient part of the background - fading of the text at the end
0295     if (leftToRight) {
0296         gradient = QLinearGradient(left + width - UNIVERSAL_PADDING - FADE_LENGTH,
0297                                    0,
0298                                    left + width - UNIVERSAL_PADDING,
0299                                    0);
0300         gradient.setColorAt(0, Qt::white);
0301         gradient.setColorAt(1, Qt::transparent);
0302     } else {
0303         gradient = QLinearGradient(left + UNIVERSAL_PADDING,
0304                                    0,
0305                                    left + UNIVERSAL_PADDING + FADE_LENGTH,
0306                                    0);
0307         gradient.setColorAt(0, Qt::transparent);
0308         gradient.setColorAt(1, Qt::white);
0309     }
0310 
0311     QRect paintRect = option.rect;
0312     p.setCompositionMode(QPainter::CompositionMode_DestinationIn);
0313     p.fillRect(paintRect, gradient);
0314 
0315     if (leftToRight) {
0316         gradient.setStart(left + width
0317                 - (UNIVERSAL_PADDING + EMBLEM_ICON_SIZE) - FADE_LENGTH, 0);
0318         gradient.setFinalStop(left + width
0319                 - (UNIVERSAL_PADDING + EMBLEM_ICON_SIZE), 0);
0320     } else {
0321         gradient.setStart(left + UNIVERSAL_PADDING
0322                 + (UNIVERSAL_PADDING + EMBLEM_ICON_SIZE), 0);
0323         gradient.setFinalStop(left + UNIVERSAL_PADDING
0324                 + (UNIVERSAL_PADDING + EMBLEM_ICON_SIZE) + FADE_LENGTH, 0);
0325     }
0326     paintRect.setHeight(UNIVERSAL_PADDING + MAIN_ICON_SIZE / 2);
0327     p.fillRect(paintRect, gradient);
0328     p.setCompositionMode(QPainter::CompositionMode_SourceOver);
0329     p.end();
0330 
0331     painter->drawPixmap(option.rect.topLeft(), pixmap);
0332 }
0333 
0334 int ChangesDelegate::calcItemHeight(const QStyleOptionViewItem &option) const
0335 {
0336     // Painting main column
0337     QStyleOptionViewItem local_option_title(option);
0338     QStyleOptionViewItem local_option_normal(option);
0339 
0340     local_option_normal.font.setPointSize(local_option_normal.font.pointSize() - 1);
0341 
0342     int textHeight = QFontInfo(local_option_title.font).pixelSize() + QFontInfo(local_option_normal.font).pixelSize();
0343     return textHeight + 3 * UNIVERSAL_PADDING;
0344 }
0345 
0346 bool ChangesDelegate::insideButton(const QRect &rect, const QPoint &pos) const
0347 {
0348 //     kDebug() << rect << pos;
0349     if ((pos.x() >= rect.x() && (pos.x() <= rect.x() + rect.width())) &&
0350         (pos.y() >= rect.y() && (pos.y() <= rect.y() + rect.height()))) {
0351         return true;
0352     }
0353     return false;
0354 }
0355 
0356 bool ChangesDelegate::editorEvent(QEvent *event,
0357                                     QAbstractItemModel *model,
0358                                     const QStyleOptionViewItem &option,
0359                                     const QModelIndex &index)
0360 {
0361     Q_UNUSED(option)
0362 
0363     if (event->type() == QEvent::MouseButtonRelease) {
0364         QAbstractItemView *view = qobject_cast<QAbstractItemView*>(parent());
0365         QPoint point = m_viewport->mapFromGlobal(QCursor::pos());
0366         QTreeView *tree = qobject_cast<QTreeView*>(parent());
0367         if (tree) {
0368             point.ry() -= tree->header()->size().height();
0369         }
0370 
0371         bool leftToRight = QApplication::isLeftToRight();
0372         QStyleOptionButton optBt;
0373         optBt.rect = option.rect;
0374         if (leftToRight) {
0375             optBt.rect.setLeft(option.rect.left() + option.rect.width() - (m_buttonSize.width() + UNIVERSAL_PADDING));
0376         } else {
0377             optBt.rect.setLeft(option.rect.left() + UNIVERSAL_PADDING);
0378         }
0379         // Calculate the top of the button which is the item height - the button height size divided by 2
0380         // this give us a little value which is the top and bottom margin
0381         optBt.rect.setTop(optBt.rect.top() + ((calcItemHeight(option) - m_buttonSize.height()) / 2));
0382         optBt.rect.setSize(m_buttonSize);
0383 
0384         qCDebug(APPER_LIB) << point << option.rect.left() << option << insideButton(optBt.rect, point);
0385 //         kDebug() << view->visualRect(index);
0386         if (insideButton(optBt.rect, point)) {
0387             return model->setData(index,
0388                                   !index.data(PackageModel::CheckStateRole).toBool(),
0389                                   Qt::CheckStateRole);
0390         }
0391         QRect rect = view->visualRect(index);
0392         if (QApplication::isRightToLeft()) {
0393             if ((rect.width() - point.x()) <= m_extendPixmapWidth) {
0394                 emit showExtendItem(index);
0395             }
0396         } else if (point.x() <= m_extendPixmapWidth) {
0397             emit showExtendItem(index);
0398         }
0399     }
0400 
0401     // We need move the option rect left because KExtendableItemDelegate
0402     // drew the extendPixmap
0403     QStyleOptionViewItem opt(option);
0404     if (QApplication::isRightToLeft()) {
0405         opt.rect.setRight(option.rect.right() - m_extendPixmapWidth);
0406     } else {
0407         opt.rect.setLeft(option.rect.left() + m_extendPixmapWidth);
0408     }
0409     // When the exterder is shown the height get compromised,
0410     // this makes sure the check box is always known
0411     opt.rect.setHeight(calcItemHeight(option));
0412     return KExtendableItemDelegate::editorEvent(event, model, opt, index);
0413 }
0414 
0415 void ChangesDelegate::setExtendPixmapWidth(int width)
0416 {
0417     m_extendPixmapWidth = width;
0418 }
0419 
0420 void ChangesDelegate::setViewport(QWidget *viewport)
0421 {
0422     m_viewport = viewport;
0423 }
0424 
0425 QSize ChangesDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index ) const
0426 {
0427     int width = (index.column() == 0) ? index.data(Qt::SizeHintRole).toSize().width() : FAV_ICON_SIZE + 2 * UNIVERSAL_PADDING;
0428     QSize ret(KExtendableItemDelegate::sizeHint(option, index));
0429     // remove the default size of the index
0430     ret -= QStyledItemDelegate::sizeHint(option, index);
0431 
0432     ret.rheight() += calcItemHeight(option);
0433     ret.rwidth()  += width;
0434 
0435     return ret;
0436 }
0437 
0438 #include "moc_ChangesDelegate.cpp"