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 }