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