File indexing completed on 2024-05-19 15:46:51

0001 /*
0002     SPDX-FileCopyrightText: 2006 Hamish Rodda <rodda@kde.org>
0003     SPDX-FileCopyrightText: 2007 David Nolden <david.nolden.kdevelop@art-master.de>
0004 
0005     SPDX-License-Identifier: LGPL-2.0-or-later
0006 */
0007 
0008 #include "expandingdelegate.h"
0009 
0010 #include <QTextLine>
0011 #include <QPainter>
0012 #include <QBrush>
0013 #include <QTreeView>
0014 #include <QApplication>
0015 
0016 #include "expandingwidgetmodel.h"
0017 #include <debug.h>
0018 
0019 ExpandingDelegate::ExpandingDelegate(ExpandingWidgetModel* model, QObject* parent)
0020     : QItemDelegate(parent)
0021     , m_model(model)
0022 {
0023 }
0024 
0025 //Gets the background-color in the way QItemDelegate does it
0026 static QColor getUsedBackgroundColor(const QStyleOptionViewItem& option, const QModelIndex& index)
0027 {
0028     if (option.showDecorationSelected && (option.state & QStyle::State_Selected)) {
0029         QPalette::ColorGroup cg = option.state & QStyle::State_Enabled
0030                                   ? QPalette::Normal : QPalette::Disabled;
0031         if (cg == QPalette::Normal && !(option.state & QStyle::State_Active)) {
0032             cg = QPalette::Inactive;
0033         }
0034 
0035         return option.palette.brush(cg, QPalette::Highlight).color();
0036     } else {
0037         QVariant value = index.data(Qt::BackgroundRole);
0038         if ((value).canConvert<QBrush>()) {
0039             return qvariant_cast<QBrush>(value).color();
0040         }
0041     }
0042 
0043     return QApplication::palette().base().color();
0044 }
0045 
0046 static void dampColors(QColor& col)
0047 {
0048     //Reduce the colors that are less visible to the eye, because they are closer to black when it comes to contrast
0049     //The most significant color to the eye is green. Then comes red, and then blue, with blue _much_ less significant.
0050 
0051     col.setBlue(0);
0052     col.setRed(col.red() / 2);
0053 }
0054 
0055 //A hack to compute more eye-focused contrast values
0056 static double readabilityContrast(QColor foreground, QColor background)
0057 {
0058     dampColors(foreground);
0059     dampColors(background);
0060     return abs(foreground.green() - background.green()) + abs(foreground.red() - background.red()) + abs(foreground.blue() - background.blue());
0061 }
0062 
0063 void ExpandingDelegate::paint(QPainter* painter, const QStyleOptionViewItem& optionOld, const QModelIndex& index) const
0064 {
0065     QStyleOptionViewItem option(optionOld);
0066 
0067     m_currentIndex = index;
0068 
0069     adjustStyle(index, option);
0070 
0071     const QModelIndex sourceIndex = model()->mapToSource(index);
0072     if (index.column() == 0) {
0073         model()->placeExpandingWidget(sourceIndex);
0074     }
0075 
0076     //Make sure the decorations are painted at the top, because the center of expanded items will be filled with the embedded widget.
0077     if (model()->isPartiallyExpanded(sourceIndex) == ExpandingWidgetModel::ExpandUpwards) {
0078         m_cachedAlignment = Qt::AlignBottom;
0079     } else {
0080         m_cachedAlignment = Qt::AlignTop;
0081     }
0082 
0083     option.decorationAlignment = m_cachedAlignment;
0084     option.displayAlignment = m_cachedAlignment;
0085 
0086     //qCDebug( PLUGIN_QUICKOPEN ) << "Painting row " << index.row() << ", column " << index.column() << ", internal " << index.internalPointer() << ", drawselected " << option.showDecorationSelected << ", selected " << (option.state & QStyle::State_Selected);
0087 
0088     m_cachedHighlights.clear();
0089     m_backgroundColor = getUsedBackgroundColor(option, index);
0090 
0091     if (model()->indexIsItem(sourceIndex)) {
0092         m_currentColumnStart = 0;
0093         m_cachedHighlights = createHighlighting(index, option);
0094     }
0095 
0096     /*qCDebug( PLUGIN_QUICKOPEN ) << "Highlights for line:";
0097        foreach (const QTextLayout::FormatRange& fr, m_cachedHighlights)
0098        qCDebug( PLUGIN_QUICKOPEN ) << fr.start << " len " << fr.length << " format ";*/
0099 
0100     QItemDelegate::paint(painter, option, index);
0101 
0102     ///This is a bug workaround for the Qt raster paint engine: It paints over widgets embedded into the viewport when updating due to mouse events
0103     ///@todo report to Qt Software
0104     if (model()->isExpanded(sourceIndex) && model()->expandingWidget(sourceIndex)) {
0105         model()->expandingWidget(sourceIndex)->update();
0106     }
0107 }
0108 
0109 QVector<QTextLayout::FormatRange> ExpandingDelegate::createHighlighting(const QModelIndex& index, QStyleOptionViewItem& option) const
0110 {
0111     Q_UNUSED(index);
0112     Q_UNUSED(option);
0113     return QVector<QTextLayout::FormatRange>();
0114 }
0115 
0116 QSize ExpandingDelegate::basicSizeHint(const QModelIndex& index) const
0117 {
0118     return QItemDelegate::sizeHint(QStyleOptionViewItem(), index);
0119 }
0120 
0121 QSize ExpandingDelegate::sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const
0122 {
0123     const QModelIndex sourceIndex = model()->mapToSource(index);
0124     QSize s = QItemDelegate::sizeHint(option, index);
0125     if (model()->isExpanded(sourceIndex) && model()->expandingWidget(sourceIndex)) {
0126         QWidget* widget = model()->expandingWidget(sourceIndex);
0127         QSize widgetSize = widget->size();
0128 
0129         s.setHeight(widgetSize.height() + s.height() + 10); //10 is the sum that must match exactly the offsets used in ExpandingWidgetModel::placeExpandingWidgets
0130     } else if (model()->isPartiallyExpanded(sourceIndex) != ExpandingWidgetModel::ExpansionType::NotExpanded) {
0131         s.setHeight(s.height() + 30 + 10);
0132     }
0133     return s;
0134 }
0135 
0136 void ExpandingDelegate::adjustStyle(const QModelIndex& index, QStyleOptionViewItem& option) const
0137 {
0138     Q_UNUSED(index)
0139     Q_UNUSED(option)
0140 }
0141 
0142 void ExpandingDelegate::adjustRect(QRect& rect) const
0143 {
0144     const QModelIndex sourceIndex = model()->mapToSource(m_currentIndex);
0145     if (!model()->indexIsItem(sourceIndex) /*&& m_currentIndex.column() == 0*/) {
0146         rect.setLeft(model()->treeView()->columnViewportPosition(0));
0147         int columnCount = model()->columnCount(sourceIndex.parent());
0148 
0149         if (!columnCount) {
0150             return;
0151         }
0152         rect.setRight(model()->treeView()->columnViewportPosition(columnCount - 1) + model()->treeView()->columnWidth(columnCount - 1));
0153     }
0154 }
0155 
0156 void ExpandingDelegate::drawDisplay(QPainter* painter, const QStyleOptionViewItem& option, const QRect& _rect, const QString& text) const
0157 {
0158     QRect rect(_rect);
0159 
0160     adjustRect(rect);
0161 
0162     QTextLayout layout(text, option.font, painter->device());
0163 
0164     QVector<QTextLayout::FormatRange> additionalFormats;
0165 
0166     int missingFormats = text.length();
0167 
0168     for (int i = 0; i < m_cachedHighlights.count(); ++i) {
0169         if (m_cachedHighlights[i].start + m_cachedHighlights[i].length <= m_currentColumnStart) {
0170             continue;
0171         }
0172 
0173         if (additionalFormats.isEmpty()) {
0174             if (i != 0 && m_cachedHighlights[i - 1].start + m_cachedHighlights[i - 1].length > m_currentColumnStart) {
0175                 QTextLayout::FormatRange before;
0176                 before.start = 0;
0177                 before.length = m_cachedHighlights[i - 1].start + m_cachedHighlights[i - 1].length - m_currentColumnStart;
0178                 before.format = m_cachedHighlights[i - 1].format;
0179                 additionalFormats.append(before);
0180             }
0181         }
0182 
0183         QTextLayout::FormatRange format;
0184         format.start = m_cachedHighlights[i].start - m_currentColumnStart;
0185         format.length = m_cachedHighlights[i].length;
0186         format.format = m_cachedHighlights[i].format;
0187 
0188         additionalFormats.append(format);
0189     }
0190 
0191     if (!additionalFormats.isEmpty()) {
0192         missingFormats = text.length() - (additionalFormats.back().length + additionalFormats.back().start);
0193     }
0194 
0195     if (missingFormats > 0) {
0196         QTextLayout::FormatRange format;
0197         format.start = text.length() - missingFormats;
0198         format.length = missingFormats;
0199         QTextCharFormat fm;
0200         fm.setForeground(option.palette.text());
0201         format.format = fm;
0202         additionalFormats.append(format);
0203     }
0204 
0205     if (m_backgroundColor.isValid()) {
0206         QColor background = m_backgroundColor;
0207 //     qCDebug(PLUGIN_QUICKOPEN) << text << "background:" << background.name();
0208         //Now go through the formats, and make sure the contrast background/foreground is readable
0209         for (auto& additionalFormat : additionalFormats) {
0210             QColor currentBackground = background;
0211             if (additionalFormat.format.hasProperty(QTextFormat::BackgroundBrush)) {
0212                 currentBackground = additionalFormat.format.background().color();
0213             }
0214 
0215             QColor currentColor = additionalFormat.format.foreground().color();
0216 
0217             double currentContrast = readabilityContrast(currentColor, currentBackground);
0218             QColor invertedColor(0xffffffff - additionalFormat.format.foreground().color().rgb());
0219             double invertedContrast = readabilityContrast(invertedColor, currentBackground);
0220 
0221 //       qCDebug(PLUGIN_QUICKOPEN) << "values:" << invertedContrast << currentContrast << invertedColor.name() << currentColor.name();
0222 
0223             if (invertedContrast > currentContrast) {
0224 //         qCDebug(PLUGIN_QUICKOPEN) << text << additionalFormats[a].length << "switching from" << currentColor.name() << "to" << invertedColor.name();
0225                 QBrush b(additionalFormat.format.foreground());
0226                 b.setColor(invertedColor);
0227                 additionalFormat.format.setForeground(b);
0228             }
0229         }
0230     }
0231 
0232     for (int a = additionalFormats.size() - 1; a >= 0; --a) {
0233         if (additionalFormats[a].length == 0) {
0234             additionalFormats.removeAt(a);
0235         } else {
0236             ///For some reason the text-formats seem to be invalid in some way, sometimes
0237             ///@todo Fix this properly, it sucks not copying everything over
0238             QTextCharFormat fm;
0239             fm.setForeground(QBrush(additionalFormats[a].format.foreground().color()));
0240             fm.setBackground(additionalFormats[a].format.background());
0241             fm.setUnderlineStyle(additionalFormats[a].format.underlineStyle());
0242             fm.setUnderlineColor(additionalFormats[a].format.underlineColor());
0243             fm.setFontWeight(additionalFormats[a].format.fontWeight());
0244             additionalFormats[a].format = fm;
0245         }
0246     }
0247 
0248 //   qCDebug( PLUGIN_QUICKOPEN ) << "Highlights for text [" << text << "] col start " << m_currentColumnStart << ":";
0249 //   foreach (const QTextLayout::FormatRange& fr, additionalFormats)
0250 //     qCDebug( PLUGIN_QUICKOPEN ) << fr.start << " len " << fr.length << "foreground" << fr.format.foreground() << "background" << fr.format.background();
0251 
0252     layout.setFormats(additionalFormats);
0253 
0254     QTextOption to;
0255 
0256     to.setAlignment(m_cachedAlignment);
0257 
0258     to.setWrapMode(QTextOption::WrapAnywhere);
0259     layout.setTextOption(to);
0260 
0261     layout.beginLayout();
0262     QTextLine line = layout.createLine();
0263     line.setLineWidth(rect.width());
0264     layout.endLayout();
0265 
0266     //We need to do some hand layouting here
0267     if (to.alignment() & Qt::AlignBottom) {
0268         layout.draw(painter, QPoint(rect.left(), rect.bottom() - ( int )line.height()));
0269     } else {
0270         layout.draw(painter, rect.topLeft());
0271     }
0272 
0273     return;
0274 
0275     //if (painter->fontMetrics().width(text) > textRect.width() && !text.contains(QLatin1Char('\n')))
0276     //str = elidedText(option.fontMetrics, textRect.width(), option.textElideMode, text);
0277     //qt_format_text(option.font, textRect, option.displayAlignment, str, 0, 0, 0, 0, painter);
0278 }
0279 
0280 void ExpandingDelegate::drawDecoration(QPainter* painter, const QStyleOptionViewItem& option, const QRect& rect, const QPixmap& pixmap) const
0281 {
0282     const QModelIndex sourceIndex = model()->mapToSource(m_currentIndex);
0283     if (model()->indexIsItem(sourceIndex)) {
0284         QItemDelegate::drawDecoration(painter, option, rect, pixmap);
0285     }
0286 }
0287 
0288 void ExpandingDelegate::drawBackground(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const
0289 {
0290     Q_UNUSED(index)
0291     QStyleOptionViewItem opt = option;
0292     //initStyleOption(&opt, index);
0293     //Problem: This isn't called at all, because drawBrackground is not virtual :-/
0294     QStyle* style = model()->treeView()->style() ? model()->treeView()->style() : QApplication::style();
0295     style->drawControl(QStyle::CE_ItemViewItem, &opt, painter);
0296 }
0297 
0298 ExpandingWidgetModel* ExpandingDelegate::model() const
0299 {
0300     return m_model;
0301 }
0302 
0303 void ExpandingDelegate::heightChanged() const
0304 {
0305 }
0306 
0307 bool ExpandingDelegate::editorEvent(QEvent* event, QAbstractItemModel* /*model*/, const QStyleOptionViewItem& /*option*/, const QModelIndex& index)
0308 {
0309     if (event->type() == QEvent::MouseButtonRelease) {
0310         const QModelIndex sourceIndex = model()->mapToSource(index);
0311         event->accept();
0312         model()->setExpanded(sourceIndex, !model()->isExpanded(sourceIndex));
0313         heightChanged();
0314 
0315         return true;
0316     } else {
0317         event->ignore();
0318     }
0319 
0320     return false;
0321 }
0322 
0323 QVector<QTextLayout::FormatRange> ExpandingDelegate::highlightingFromVariantList(const QList<QVariant>& customHighlights) const
0324 {
0325     QVector<QTextLayout::FormatRange> ret;
0326 
0327     for (int i = 0; i + 2 < customHighlights.count(); i += 3) {
0328         if (!customHighlights[i].canConvert(QVariant::Int) || !customHighlights[i + 1].canConvert(QVariant::Int) || !customHighlights[i + 2].canConvert<QTextFormat>()) {
0329             qCWarning(PLUGIN_QUICKOPEN) << "Unable to convert triple to custom formatting.";
0330             continue;
0331         }
0332 
0333         QTextLayout::FormatRange format;
0334         format.start = customHighlights[i].toInt();
0335         format.length = customHighlights[i + 1].toInt();
0336         format.format = customHighlights[i + 2].value<QTextFormat>().toCharFormat();
0337 
0338         if (!format.format.isValid()) {
0339             qCWarning(PLUGIN_QUICKOPEN) << "Format is not valid";
0340         }
0341 
0342         ret << format;
0343     }
0344 
0345     return ret;
0346 }
0347 
0348 #include "moc_expandingdelegate.cpp"