File indexing completed on 2024-09-15 12:02:40
0001 /* 0002 SPDX-FileCopyrightText: 2007 David Nolden <david.nolden.kdevelop@art-master.de> 0003 0004 SPDX-License-Identifier: LGPL-2.0-or-later 0005 */ 0006 0007 #include "expandingwidgetmodel.h" 0008 0009 #include <QApplication> 0010 #include <QBrush> 0011 #include <QModelIndex> 0012 #include <QTreeView> 0013 0014 #include <KColorUtils> 0015 #include <KTextEdit> 0016 #include <ktexteditor/codecompletionmodel.h> 0017 0018 #include "katecompletiondelegate.h" 0019 #include "katepartdebug.h" 0020 0021 using namespace KTextEditor; 0022 0023 inline QModelIndex firstColumn(const QModelIndex &index) 0024 { 0025 return index.sibling(index.row(), 0); 0026 } 0027 0028 ExpandingWidgetModel::ExpandingWidgetModel(QWidget *parent) 0029 : QAbstractItemModel(parent) 0030 { 0031 } 0032 0033 ExpandingWidgetModel::~ExpandingWidgetModel() 0034 { 0035 clearExpanding(); 0036 } 0037 0038 static QColor doAlternate(const QColor &color) 0039 { 0040 QColor background = QApplication::palette().window().color(); 0041 return KColorUtils::mix(color, background, 0.15); 0042 } 0043 0044 uint ExpandingWidgetModel::matchColor(const QModelIndex &index) const 0045 { 0046 int matchQuality = contextMatchQuality(index.sibling(index.row(), 0)); 0047 0048 if (matchQuality > 0) { 0049 bool alternate = index.row() & 1; 0050 0051 QColor badMatchColor(0xff00aa44); // Blue-ish green 0052 QColor goodMatchColor(0xff00ff00); // Green 0053 0054 QColor background = treeView()->palette().light().color(); 0055 0056 QColor totalColor = KColorUtils::mix(badMatchColor, goodMatchColor, ((float)matchQuality) / 10.0); 0057 0058 if (alternate) { 0059 totalColor = doAlternate(totalColor); 0060 } 0061 0062 const qreal dynamicTint = 0.2; 0063 const qreal minimumTint = 0.2; 0064 qreal tintStrength = (dynamicTint * matchQuality) / 10; 0065 if (tintStrength != 0.0) { 0066 tintStrength += minimumTint; // Some minimum tinting strength, else it's not visible any more 0067 } 0068 0069 return KColorUtils::tint(background, totalColor, tintStrength).rgb(); 0070 } else { 0071 return 0; 0072 } 0073 } 0074 0075 QVariant ExpandingWidgetModel::data(const QModelIndex &index, int role) const 0076 { 0077 switch (role) { 0078 case Qt::BackgroundRole: { 0079 if (index.column() == 0) { 0080 // Highlight by match-quality 0081 uint color = matchColor(index); 0082 if (color) { 0083 return QBrush(color); 0084 } 0085 } 0086 // Use a special background-color for expanded items 0087 if (isExpanded(index)) { 0088 if (index.row() & 1) { 0089 return doAlternate(treeView()->palette().toolTipBase().color()); 0090 } else { 0091 return treeView()->palette().toolTipBase(); 0092 } 0093 } 0094 } 0095 } 0096 return QVariant(); 0097 } 0098 0099 void ExpandingWidgetModel::clearExpanding() 0100 { 0101 QMap<QModelIndex, ExpandingWidgetModel::ExpandingType> oldExpandState = m_expandState; 0102 for (auto &widget : std::as_const(m_expandingWidgets)) { 0103 if (widget) { 0104 widget->deleteLater(); // By using deleteLater, we prevent crashes when an action within a widget makes the completion cancel 0105 } 0106 } 0107 m_expandingWidgets.clear(); 0108 m_expandState.clear(); 0109 0110 for (auto it = oldExpandState.constBegin(); it != oldExpandState.constEnd(); ++it) { 0111 if (it.value() == Expanded) { 0112 Q_EMIT dataChanged(it.key(), it.key()); 0113 } 0114 } 0115 } 0116 0117 bool ExpandingWidgetModel::isExpandable(const QModelIndex &idx_) const 0118 { 0119 QModelIndex idx(firstColumn(idx_)); 0120 0121 if (!m_expandState.contains(idx)) { 0122 m_expandState.insert(idx, NotExpandable); 0123 QVariant v = data(idx, CodeCompletionModel::IsExpandable); 0124 if (v.canConvert<bool>() && v.toBool()) { 0125 m_expandState[idx] = Expandable; 0126 } 0127 } 0128 0129 return m_expandState[idx] != NotExpandable; 0130 } 0131 0132 bool ExpandingWidgetModel::isExpanded(const QModelIndex &idx_) const 0133 { 0134 QModelIndex idx(firstColumn(idx_)); 0135 return m_expandState.contains(idx) && m_expandState[idx] == Expanded; 0136 } 0137 0138 void ExpandingWidgetModel::setExpanded(QModelIndex idx_, bool expanded) 0139 { 0140 QModelIndex idx(firstColumn(idx_)); 0141 0142 // qCDebug(LOG_KTE) << "Setting expand-state of row " << idx.row() << " to " << expanded; 0143 if (!idx.isValid()) { 0144 return; 0145 } 0146 0147 if (isExpandable(idx)) { 0148 if (!expanded && m_expandingWidgets.contains(idx) && m_expandingWidgets[idx]) { 0149 m_expandingWidgets[idx]->hide(); 0150 } 0151 0152 m_expandState[idx] = expanded ? Expanded : Expandable; 0153 0154 if (expanded && !m_expandingWidgets.contains(idx)) { 0155 QVariant v = data(idx, CodeCompletionModel::ExpandingWidget); 0156 0157 if (v.canConvert<QWidget *>()) { 0158 m_expandingWidgets[idx] = v.value<QWidget *>(); 0159 } else if (v.canConvert<QString>()) { 0160 // Create a html widget that shows the given string 0161 QTextEdit *edit = new QTextEdit(v.toString()); 0162 edit->setReadOnly(true); 0163 edit->resize(200, 50); // Make the widget small so it embeds nicely. 0164 m_expandingWidgets[idx] = edit; 0165 } else { 0166 m_expandingWidgets[idx] = nullptr; 0167 } 0168 } 0169 0170 Q_EMIT dataChanged(idx, idx); 0171 0172 if (treeView()) { 0173 treeView()->scrollTo(idx); 0174 } 0175 } 0176 } 0177 0178 int ExpandingWidgetModel::basicRowHeight(const QModelIndex &idx_) const 0179 { 0180 QModelIndex idx(firstColumn(idx_)); 0181 0182 auto *delegate = (KateCompletionDelegate *)treeView()->itemDelegate(idx); 0183 if (!delegate || !idx.isValid()) { 0184 qCDebug(LOG_KTE) << "ExpandingWidgetModel::basicRowHeight: Could not get delegate"; 0185 return 15; 0186 } 0187 return delegate->basicSizeHint(idx).height(); 0188 } 0189 0190 void ExpandingWidgetModel::placeExpandingWidget(const QModelIndex &idx_) 0191 { 0192 QModelIndex idx(firstColumn(idx_)); 0193 if (!idx.isValid() || !isExpanded(idx)) { 0194 return; 0195 } 0196 0197 QWidget *w = m_expandingWidgets.value(idx); 0198 if (!w) { 0199 return; 0200 } 0201 0202 QRect rect = treeView()->visualRect(idx); 0203 0204 if (!rect.isValid() || rect.bottom() < 0 || rect.top() >= treeView()->height()) { 0205 // The item is currently not visible 0206 w->hide(); 0207 return; 0208 } 0209 0210 // Find out the basic width of the row 0211 const int numColumns = idx.model()->columnCount(idx.parent()); 0212 int left = 0; 0213 for (int i = 0; i < numColumns; ++i) { 0214 auto index = idx.sibling(idx.row(), i); 0215 auto text = index.data().toString(); 0216 if (!index.data(Qt::DecorationRole).isNull()) { 0217 left += 24; 0218 } 0219 0220 if (!text.isEmpty()) { 0221 left += treeView()->visualRect(index).left(); 0222 break; 0223 } 0224 } 0225 rect.setLeft(rect.left() + left); 0226 0227 for (int i = 0; i < numColumns; ++i) { 0228 QModelIndex rightMostIndex = idx.sibling(idx.row(), i); 0229 int right = treeView()->visualRect(rightMostIndex).right(); 0230 if (right > rect.right()) { 0231 rect.setRight(right); 0232 } 0233 } 0234 rect.setRight(rect.right() - 5); 0235 0236 // These offsets must match exactly those used in KateCompletionDeleage::sizeHint() 0237 rect.setTop(rect.top() + basicRowHeight(idx)); 0238 rect.setHeight(w->height()); 0239 0240 if (w->parent() != treeView()->viewport() || w->geometry() != rect || !w->isVisible()) { 0241 w->setParent(treeView()->viewport()); 0242 0243 w->setGeometry(rect); 0244 w->show(); 0245 } 0246 } 0247 0248 void ExpandingWidgetModel::placeExpandingWidgets() 0249 { 0250 for (QMap<QModelIndex, QPointer<QWidget>>::const_iterator it = m_expandingWidgets.constBegin(); it != m_expandingWidgets.constEnd(); ++it) { 0251 placeExpandingWidget(it.key()); 0252 } 0253 } 0254 0255 QWidget *ExpandingWidgetModel::expandingWidget(const QModelIndex &idx_) const 0256 { 0257 QModelIndex idx(firstColumn(idx_)); 0258 0259 if (m_expandingWidgets.contains(idx)) { 0260 return m_expandingWidgets[idx]; 0261 } else { 0262 return nullptr; 0263 } 0264 } 0265 0266 void ExpandingWidgetModel::cacheIcons() const 0267 { 0268 if (m_expandedIcon.isNull()) { 0269 m_expandedIcon = QIcon::fromTheme(QStringLiteral("arrow-down")); 0270 } 0271 0272 if (m_collapsedIcon.isNull()) { 0273 m_collapsedIcon = QIcon::fromTheme(QStringLiteral("arrow-right")); 0274 } 0275 } 0276 0277 QList<QVariant> mergeCustomHighlighting(int leftSize, const QList<QVariant> &left, int rightSize, const QList<QVariant> &right) 0278 { 0279 QList<QVariant> ret = left; 0280 if (left.isEmpty()) { 0281 ret << QVariant(0); 0282 ret << QVariant(leftSize); 0283 ret << QTextFormat(QTextFormat::CharFormat); 0284 } 0285 0286 if (right.isEmpty()) { 0287 ret << QVariant(leftSize); 0288 ret << QVariant(rightSize); 0289 ret << QTextFormat(QTextFormat::CharFormat); 0290 } else { 0291 QList<QVariant>::const_iterator it = right.constBegin(); 0292 while (it != right.constEnd()) { 0293 { 0294 QList<QVariant>::const_iterator testIt = it; 0295 for (int a = 0; a < 2; a++) { 0296 ++testIt; 0297 if (testIt == right.constEnd()) { 0298 qCWarning(LOG_KTE) << "Length of input is not multiple of 3"; 0299 break; 0300 } 0301 } 0302 } 0303 0304 ret << QVariant((*it).toInt() + leftSize); 0305 ++it; 0306 ret << QVariant((*it).toInt()); 0307 ++it; 0308 ret << *it; 0309 if (!(*it).value<QTextFormat>().isValid()) { 0310 qCDebug(LOG_KTE) << "Text-format is invalid"; 0311 } 0312 ++it; 0313 } 0314 } 0315 return ret; 0316 } 0317 0318 // It is assumed that between each two strings, one space is inserted 0319 QList<QVariant> mergeCustomHighlighting(QStringList strings, QList<QVariantList> highlights, int grapBetweenStrings) 0320 { 0321 if (strings.isEmpty()) { 0322 qCWarning(LOG_KTE) << "List of strings is empty"; 0323 return QList<QVariant>(); 0324 } 0325 0326 if (highlights.isEmpty()) { 0327 qCWarning(LOG_KTE) << "List of highlightings is empty"; 0328 return QList<QVariant>(); 0329 } 0330 0331 if (strings.count() != highlights.count()) { 0332 qCWarning(LOG_KTE) << "Length of string-list is " << strings.count() << " while count of highlightings is " << highlights.count() << ", should be same"; 0333 return QList<QVariant>(); 0334 } 0335 0336 // Merge them together 0337 QString totalString = strings[0]; 0338 QVariantList totalHighlighting = highlights[0]; 0339 0340 strings.pop_front(); 0341 highlights.pop_front(); 0342 0343 while (!strings.isEmpty()) { 0344 totalHighlighting = mergeCustomHighlighting(totalString.length(), totalHighlighting, strings[0].length(), highlights[0]); 0345 totalString += strings[0]; 0346 0347 for (int a = 0; a < grapBetweenStrings; a++) { 0348 totalString += QLatin1Char(' '); 0349 } 0350 0351 strings.pop_front(); 0352 highlights.pop_front(); 0353 } 0354 // Combine the custom-highlightings 0355 return totalHighlighting; 0356 } 0357 0358 #include "moc_expandingwidgetmodel.cpp"