File indexing completed on 2024-05-12 03:48:30
0001 /* 0002 File : TreeViewComboBox.cpp 0003 Project : LabPlot 0004 Description : Provides a QTreeView in a QComboBox 0005 -------------------------------------------------------------------- 0006 SPDX-FileCopyrightText: 2008-2023 Alexander Semke <alexander.semke@web.de> 0007 SPDX-FileCopyrightText: 2008 Tilman Benkert <thzs@gmx.net> 0008 SPDX-License-Identifier: GPL-2.0-or-later 0009 */ 0010 0011 #include "commonfrontend/widgets/TreeViewComboBox.h" 0012 #include "backend/core/AbstractAspect.h" 0013 #include "backend/core/AbstractColumn.h" 0014 #include "backend/core/AspectTreeModel.h" 0015 #include "backend/lib/macros.h" 0016 0017 #include <QEvent> 0018 #include <QGroupBox> 0019 #include <QHeaderView> 0020 #include <QLineEdit> 0021 #include <QStylePainter> 0022 #include <QTreeView> 0023 #include <QVBoxLayout> 0024 0025 #include <KLocalizedString> 0026 0027 #include <cstring> // strcmp() 0028 0029 /*! 0030 \class TreeViewComboBox 0031 \brief Provides a QTreeView in a QComboBox. 0032 0033 \ingroup backend/widgets 0034 */ 0035 TreeViewComboBox::TreeViewComboBox(QWidget* parent) 0036 : QComboBox(parent) 0037 , m_treeView(new QTreeView) 0038 , m_groupBox(new QGroupBox) 0039 , m_lineEdit(new QLineEdit) { 0040 auto* layout = new QVBoxLayout(this); 0041 layout->setContentsMargins(0, 0, 0, 0); 0042 layout->setSpacing(0); 0043 0044 layout->addWidget(m_lineEdit); 0045 layout->addWidget(m_treeView); 0046 0047 m_groupBox->setLayout(layout); 0048 m_groupBox->setParent(parent, Qt::Popup); 0049 m_groupBox->hide(); 0050 m_groupBox->installEventFilter(this); 0051 0052 m_treeView->header()->hide(); 0053 m_treeView->setSelectionMode(QAbstractItemView::SingleSelection); 0054 m_treeView->setUniformRowHeights(true); 0055 0056 m_lineEdit->setPlaceholderText(i18n("Search/Filter text")); 0057 m_lineEdit->setClearButtonEnabled(true); 0058 m_lineEdit->setFocus(); 0059 0060 addItem(QString()); 0061 setCurrentIndex(0); 0062 setEditText(m_lineEditText); 0063 0064 // signal activated() is platform dependent 0065 connect(m_treeView, &QTreeView::pressed, this, &TreeViewComboBox::treeViewIndexActivated); 0066 connect(m_lineEdit, &QLineEdit::textChanged, this, &TreeViewComboBox::filterChanged); 0067 } 0068 0069 void TreeViewComboBox::setTopLevelClasses(const QList<AspectType>& list) { 0070 m_topLevelClasses = list; 0071 } 0072 0073 void TreeViewComboBox::setHiddenAspects(const QList<const AbstractAspect*>& list) { 0074 m_hiddenAspects = list; 0075 } 0076 0077 /*! 0078 Sets the \a model for the view to present. 0079 */ 0080 void TreeViewComboBox::setModel(AspectTreeModel* model) { 0081 m_model = model; 0082 m_treeView->setModel(model); 0083 0084 // show only the first column in the combo box 0085 for (int i = 1; i < model->columnCount(); i++) 0086 m_treeView->hideColumn(i); 0087 0088 // Expand the complete tree in order to see everything in the first popup. 0089 m_treeView->expandAll(); 0090 0091 setEditText(m_lineEditText); 0092 } 0093 0094 /*! 0095 Sets the current item to be the item at \a index and selects it. 0096 \sa currentIndex() 0097 */ 0098 void TreeViewComboBox::setCurrentModelIndex(const QModelIndex& index) { 0099 m_treeView->setCurrentIndex(index); 0100 if (index.isValid()) { 0101 setToolTip(m_model->data(index, Qt::ToolTipRole).toString()); 0102 QComboBox::setItemText(0, index.data().toString()); 0103 } 0104 } 0105 0106 /*! 0107 Returns the model index of the current item. 0108 \sa setCurrentModelIndex() 0109 */ 0110 QModelIndex TreeViewComboBox::currentModelIndex() const { 0111 return m_treeView->currentIndex(); 0112 } 0113 0114 /*! 0115 Displays the tree view of items in the combobox. 0116 Triggers showTopLevelOnly() to show toplevel items only. 0117 */ 0118 void TreeViewComboBox::showPopup() { 0119 if (!m_treeView->model() || !m_treeView->model()->hasChildren()) 0120 return; 0121 0122 QModelIndex root = m_treeView->model()->index(0, 0); 0123 showTopLevelOnly(root); 0124 m_groupBox->show(); 0125 m_groupBox->resize(this->width(), 250); 0126 m_groupBox->move(mapToGlobal(this->rect().topLeft())); 0127 0128 setEditText(m_lineEditText); 0129 m_lineEdit->setText(QString()); // delete the previous search string 0130 m_lineEdit->setFocus(); 0131 } 0132 0133 /*! 0134 \reimp 0135 TODO: why do I have to reimplement paintEvent. It should work 0136 also without 0137 */ 0138 void TreeViewComboBox::paintEvent(QPaintEvent*) { 0139 QStylePainter painter(this); 0140 painter.setPen(palette().color(QPalette::Text)); 0141 // draw the combobox frame, focusrect and selected etc. 0142 QStyleOptionComboBox opt; 0143 initStyleOption(&opt); 0144 opt.currentText = currentText(); // TODO: why it's not working when letting this away? 0145 painter.drawComplexControl(QStyle::CC_ComboBox, opt); 0146 // draw the icon and text 0147 painter.drawControl(QStyle::CE_ComboBoxLabel, opt); 0148 } 0149 0150 void TreeViewComboBox::hidePopup() { 0151 m_groupBox->hide(); 0152 } 0153 0154 void TreeViewComboBox::useCurrentIndexText(const bool set) { 0155 m_useCurrentIndexText = set; 0156 } 0157 0158 /*! 0159 \property QComboBox::currentText 0160 \brief the current text 0161 If the combo box is editable, the current text is the value displayed 0162 by the line edit. Otherwise, it is the value of the current item or 0163 an empty string if the combo box is empty or no current item is set. 0164 The setter setCurrentText() simply calls setEditText() if the combo box is editable. 0165 Otherwise, if there is a matching text in the list, currentIndex is set to the 0166 corresponding index. 0167 If m_useCurrentIndexText is false, the Text set with setText is used. The intention of displaying 0168 this text is to show a text in the case of removed element. 0169 \sa editable, setEditText() 0170 */ 0171 QString TreeViewComboBox::currentText() const { 0172 if (lineEdit()) 0173 return lineEdit()->text(); 0174 else if (currentModelIndex().isValid() && m_useCurrentIndexText) 0175 return itemText(currentIndex()); 0176 else if (!m_useCurrentIndexText) 0177 return m_lineEditText; 0178 0179 return {}; 0180 } 0181 0182 void TreeViewComboBox::setText(const QString& text) { 0183 m_lineEditText = text; 0184 } 0185 0186 void TreeViewComboBox::setInvalid(bool invalid, const QString& tooltip) { 0187 if (invalid) { 0188 SET_WARNING_PALETTE 0189 0190 setToolTip(tooltip); 0191 } else { 0192 setPalette(qApp->palette()); 0193 setToolTip(m_model->data(currentModelIndex(), Qt::ToolTipRole).toString()); 0194 } 0195 } 0196 0197 /*! 0198 Hides the non-toplevel items of the model used in the tree view. 0199 */ 0200 void TreeViewComboBox::showTopLevelOnly(const QModelIndex& index) { 0201 int rows = index.model()->rowCount(index); 0202 for (int i = 0; i < rows; i++) { 0203 QModelIndex child = index.model()->index(i, 0, index); 0204 showTopLevelOnly(child); 0205 const auto* aspect = static_cast<const AbstractAspect*>(child.internalPointer()); 0206 m_treeView->setRowHidden(i, index, !(isTopLevel(aspect) && !isHidden(aspect))); 0207 } 0208 } 0209 0210 /*! 0211 catches the MouseButtonPress-event and hides the tree view on mouse clicking. 0212 */ 0213 bool TreeViewComboBox::eventFilter(QObject* object, QEvent* event) { 0214 if ((object == m_groupBox) && event->type() == QEvent::MouseButtonPress) { 0215 m_groupBox->hide(); 0216 this->setFocus(); 0217 return true; 0218 } 0219 return QComboBox::eventFilter(object, event); 0220 } 0221 0222 // SLOTs 0223 void TreeViewComboBox::treeViewIndexActivated(const QModelIndex& index) { 0224 if (index.internalPointer()) { 0225 QComboBox::setCurrentIndex(0); 0226 QComboBox::setItemText(0, index.data().toString()); 0227 setToolTip(m_model->data(index, Qt::ToolTipRole).toString()); 0228 Q_EMIT currentModelIndexChanged(index); 0229 m_groupBox->hide(); 0230 return; 0231 } 0232 0233 m_treeView->setCurrentIndex(QModelIndex()); 0234 setCurrentIndex(0); 0235 QComboBox::setItemText(0, QString()); 0236 Q_EMIT currentModelIndexChanged(QModelIndex()); 0237 m_groupBox->hide(); 0238 } 0239 0240 void TreeViewComboBox::filterChanged(const QString& text) { 0241 QModelIndex root = m_treeView->model()->index(0, 0); 0242 filter(root, text); 0243 } 0244 0245 bool TreeViewComboBox::filter(const QModelIndex& index, const QString& text) { 0246 bool childVisible = false; 0247 const int rows = index.model()->rowCount(index); 0248 for (int i = 0; i < rows; i++) { 0249 QModelIndex child = index.model()->index(i, 0, index); 0250 auto* aspect = static_cast<AbstractAspect*>(child.internalPointer()); 0251 bool topLevel = isTopLevel(aspect); 0252 if (!topLevel) 0253 continue; 0254 0255 bool visible = aspect->name().contains(text, Qt::CaseInsensitive); 0256 0257 if (visible) { 0258 // current item is visible -> make all its children (allowed top level types only and not hidden) visible without applying the filter 0259 for (int j = 0; j < child.model()->rowCount(child); ++j) { 0260 AbstractAspect* aspect = static_cast<AbstractAspect*>((child.model()->index(j, 0, child)).internalPointer()); 0261 m_treeView->setRowHidden(j, child, !(isTopLevel(aspect) && !isHidden(aspect))); 0262 } 0263 0264 childVisible = true; 0265 } else { 0266 // check children items. if one of the children is visible, make the parent (current) item visible too. 0267 visible = filter(child, text); 0268 if (visible) 0269 childVisible = true; 0270 } 0271 0272 m_treeView->setRowHidden(i, index, !(visible && !isHidden(aspect))); 0273 } 0274 0275 return childVisible; 0276 } 0277 0278 /*! 0279 checks whether \c aspect is one of the allowed top level types 0280 and has selectable children 0281 */ 0282 bool TreeViewComboBox::isTopLevel(const AbstractAspect* aspect) const { 0283 const auto& selectableTypes = m_model->selectableAspects(); 0284 for (AspectType type : m_topLevelClasses) { 0285 if (aspect->type() == type) { 0286 // curent aspect is a top level aspect, 0287 // check whether its selectable 0288 if (selectableTypes.indexOf(type) != -1) 0289 return true; 0290 0291 // check whether the current aspect has selectable children 0292 bool hasSelectableAspects = false; 0293 for (auto selectableType : selectableTypes) { 0294 const auto& children = aspect->children(selectableType, AbstractAspect::ChildIndexFlag::Recursive); 0295 if (!children.isEmpty()) { 0296 hasSelectableAspects = true; 0297 break; 0298 } 0299 } 0300 0301 return hasSelectableAspects; 0302 } 0303 0304 if (type == AspectType::XYAnalysisCurve) 0305 if (aspect->inherits(AspectType::XYAnalysisCurve)) 0306 return true; 0307 } 0308 return false; 0309 } 0310 0311 bool TreeViewComboBox::isHidden(const AbstractAspect* aspect) const { 0312 return (m_hiddenAspects.indexOf(aspect) != -1); 0313 } 0314 0315 void TreeViewComboBox::setAspect(const AbstractAspect* aspect) { 0316 if (aspect) 0317 setCurrentModelIndex(m_model->modelIndexOfAspect(aspect)); 0318 else 0319 setCurrentModelIndex(QModelIndex()); 0320 } 0321 0322 AbstractAspect* TreeViewComboBox::currentAspect() const { 0323 return static_cast<AbstractAspect*>(currentModelIndex().internalPointer()); 0324 } 0325 0326 void TreeViewComboBox::setColumn(const AbstractColumn* column, const QString& path) { 0327 DEBUG(Q_FUNC_INFO) 0328 setAspect(column); 0329 0330 // don't make the combobox red for initially created curves 0331 if (!column && path.isEmpty()) { 0332 setText(QString()); 0333 setInvalid(false); 0334 return; 0335 } 0336 0337 if (column) { 0338 useCurrentIndexText(true); 0339 setInvalid(false); 0340 } else { 0341 useCurrentIndexText(false); 0342 setInvalid(true, i18n("The column \"%1\"\nis not available anymore. It will be automatically used once it is created again.", path)); 0343 } 0344 setText(path.split(QLatin1Char('/')).last()); 0345 }