File indexing completed on 2024-05-12 15:26:39

0001 /***************************************************************************
0002     File            : AspectTreeModel.h
0003     Project         : LabPlot
0004     Description     : Represents a tree of AbstractAspect objects as a Qt item model.
0005     --------------------------------------------------------------------
0006     Copyright            : (C) 2007-2009 by Knut Franke (knut.franke@gmx.de)
0007     Copyright            : (C) 2007-2009 by Tilman Benkert (thzs@gmx.net)
0008     Copyright            : (C) 2011-2016 Alexander Semke (alexander.semke@web.de)
0009 
0010  ***************************************************************************/
0011 
0012 /***************************************************************************
0013  *                                                                         *
0014  *  This program is free software; you can redistribute it and/or modify   *
0015  *  it under the terms of the GNU General Public License as published by   *
0016  *  the Free Software Foundation; either version 2 of the License, or      *
0017  *  (at your option) any later version.                                    *
0018  *                                                                         *
0019  *  This program is distributed in the hope that it will be useful,        *
0020  *  but WITHOUT ANY WARRANTY; without even the implied warranty of         *
0021  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the          *
0022  *  GNU General Public License for more details.                           *
0023  *                                                                         *
0024  *   You should have received a copy of the GNU General Public License     *
0025  *   along with this program; if not, write to the Free Software           *
0026  *   Foundation, Inc., 51 Franklin Street, Fifth Floor,                    *
0027  *   Boston, MA  02110-1301  USA                                           *
0028  *                                                                         *
0029  ***************************************************************************/
0030 #include "backend/core/AbstractAspect.h"
0031 #include "backend/core/column/Column.h"
0032 #include "backend/worksheet/WorksheetElement.h"
0033 #include "backend/core/AspectTreeModel.h"
0034 
0035 #include <QDateTime>
0036 #include <QIcon>
0037 #include <QMenu>
0038 #include <QApplication>
0039 #include <QFontMetrics>
0040 
0041 #include <KLocalizedString>
0042 
0043 /**
0044  * \class AspectTreeModel
0045  * \brief Represents a tree of AbstractAspect objects as a Qt item model.
0046  *
0047  * This class is an adapter between an AbstractAspect hierarchy and Qt's view classes.
0048  *
0049  * It represents children of an Aspect as rows in the model, with the fixed columns
0050  * Name (AbstractAspect::name()), Type (the class name), Created (AbstractAspect::creationTime())
0051  * and Comment (AbstractAspect::comment()). Name is decorated using AbstractAspect::icon().
0052  * The tooltip for all columns is generated from AbstractAspect::caption().
0053  *
0054  * Name and Comment are editable.
0055  *
0056  * For views which support this (currently ProjectExplorer), the menu created by
0057  * AbstractAspect::createContextMenu() is made available via the custom role ContextMenuRole.
0058  */
0059 
0060 /**
0061  * \enum AspectTreeModel::CustomDataRole
0062  * \brief Custom data roles used in addition to Qt::ItemDataRole
0063  */
0064 /**
0065  * \var AspectTreeModel::ContextMenuRole
0066  * \brief pointer to a new context menu for an Aspect
0067  */
0068 
0069 /**
0070  * \fn QModelIndex AspectTreeModel::modelIndexOfAspect(const AbstractAspect *aspect, int column=0) const
0071  * \brief Convenience wrapper around QAbstractItemModel::createIndex().
0072  */
0073 
0074 AspectTreeModel::AspectTreeModel(AbstractAspect* root, QObject* parent)
0075     : QAbstractItemModel(parent), m_root(root) {
0076 
0077     connect(m_root, &AbstractAspect::aspectDescriptionChanged, this, &AspectTreeModel::aspectDescriptionChanged);
0078     connect(m_root, &AbstractAspect::aspectAboutToBeAdded, this, &AspectTreeModel::aspectAboutToBeAdded);
0079     connect(m_root, &AbstractAspect::aspectAboutToBeRemoved, this, &AspectTreeModel::aspectAboutToBeRemoved);
0080     connect(m_root, &AbstractAspect::aspectAdded, this, &AspectTreeModel::aspectAdded);
0081     connect(m_root, &AbstractAspect::aspectRemoved, this, &AspectTreeModel::aspectRemoved);
0082     connect(m_root, &AbstractAspect::aspectHiddenAboutToChange, this, &AspectTreeModel::aspectHiddenAboutToChange);
0083     connect(m_root, &AbstractAspect::aspectHiddenChanged, this, &AspectTreeModel::aspectHiddenChanged);
0084 }
0085 
0086 /*!
0087   \c list contains the class names of the aspects, that can be selected in the corresponding model view.
0088 */
0089 void AspectTreeModel::setSelectableAspects(const QList<AspectType>& list) {
0090     m_selectableAspects = list;
0091 }
0092 
0093 void AspectTreeModel::setReadOnly(bool readOnly) {
0094     m_readOnly = readOnly;
0095 }
0096 
0097 void AspectTreeModel::enablePlottableColumnsOnly(bool value) {
0098     m_plottableColumnsOnly = value;
0099 }
0100 
0101 void AspectTreeModel::enableNumericColumnsOnly(bool value) {
0102     m_numericColumnsOnly = value;
0103 }
0104 
0105 void AspectTreeModel::enableNonEmptyNumericColumnsOnly(bool value) {
0106     m_nonEmptyNumericColumnsOnly = value;
0107 }
0108 
0109 void AspectTreeModel::enableShowPlotDesignation(bool value) {
0110     m_showPlotDesignation = value;
0111 }
0112 
0113 QModelIndex AspectTreeModel::index(int row, int column, const QModelIndex &parent) const {
0114     if (!hasIndex(row, column, parent))
0115         return QModelIndex{};
0116 
0117     if (!parent.isValid()) {
0118         if (row != 0)
0119             return QModelIndex{};
0120         return createIndex(row, column, m_root);
0121     }
0122 
0123     auto* parent_aspect = static_cast<AbstractAspect*>(parent.internalPointer());
0124     auto* child_aspect = parent_aspect->child<AbstractAspect>(row);
0125     if (!child_aspect)
0126         return QModelIndex{};
0127     return createIndex(row, column, child_aspect);
0128 }
0129 
0130 QModelIndex AspectTreeModel::parent(const QModelIndex &index) const {
0131     if (!index.isValid())
0132         return QModelIndex{};
0133 
0134     auto* parent_aspect = static_cast<AbstractAspect*>(index.internalPointer())->parentAspect();
0135     if (!parent_aspect)
0136         return QModelIndex{};
0137     return modelIndexOfAspect(parent_aspect);
0138 }
0139 
0140 int AspectTreeModel::rowCount(const QModelIndex &parent) const {
0141     if (!parent.isValid())
0142         return 1;
0143 
0144     auto* parent_aspect =  static_cast<AbstractAspect*>(parent.internalPointer());
0145     return parent_aspect->childCount<AbstractAspect>();
0146 }
0147 
0148 int AspectTreeModel::columnCount(const QModelIndex &parent) const {
0149     Q_UNUSED(parent);
0150     return 4;
0151 }
0152 
0153 QVariant AspectTreeModel::headerData(int section, Qt::Orientation orientation, int role) const {
0154     if (orientation != Qt::Horizontal)
0155         return QVariant();
0156 
0157     switch (role) {
0158     case Qt::DisplayRole:
0159         switch (section) {
0160         case 0:
0161             return i18n("Name");
0162         case 1:
0163             return i18n("Type");
0164         case 2:
0165             return i18n("Created");
0166         case 3:
0167             return i18n("Comment");
0168         default:
0169             return QVariant();
0170         }
0171     default:
0172         return QVariant();
0173     }
0174 }
0175 
0176 QVariant AspectTreeModel::data(const QModelIndex &index, int role) const {
0177     if (!index.isValid())
0178         return QVariant();
0179 
0180     auto* aspect = static_cast<AbstractAspect*>(index.internalPointer());
0181     switch (role) {
0182     case Qt::DisplayRole:
0183     case Qt::EditRole:
0184         switch (index.column()) {
0185         case 0: {
0186             const auto* column = dynamic_cast<const Column*>(aspect);
0187             if (column) {
0188                 QString name = aspect->name();
0189                 if (m_plottableColumnsOnly && !column->isPlottable())
0190                     name = i18n("%1   (non-plottable data)", name);
0191                 else if (m_numericColumnsOnly && !column->isNumeric())
0192                     name = i18n("%1   (non-numeric data)", name);
0193                 else if (m_nonEmptyNumericColumnsOnly && !column->hasValues())
0194                     name = i18n("%1   (no values)", name);
0195 
0196                 if (m_showPlotDesignation)
0197                     name += QLatin1Char('\t') + column->plotDesignationString();
0198 
0199                 return name;
0200             } else if (aspect)
0201                 return aspect->name();
0202             else
0203                 return QVariant();
0204         }
0205         case 1:
0206             if (aspect->metaObject()->className() != QLatin1String("CantorWorksheet"))
0207                 return aspect->metaObject()->className();
0208             else
0209                 return QLatin1String("CAS Worksheet");
0210         case 2:
0211             return aspect->creationTime().toString();
0212         case 3:
0213             return aspect->comment().replace('\n', ' ').simplified();
0214         default:
0215             return QVariant();
0216         }
0217     case Qt::ToolTipRole:
0218         if (aspect->comment().isEmpty())
0219             return QLatin1String("<b>") + aspect->name() + QLatin1String("</b>");
0220         else
0221             return QLatin1String("<b>") + aspect->name() + QLatin1String("</b><br><br>") + aspect->comment().replace(QLatin1Char('\n'), QLatin1String("<br>"));
0222     case Qt::DecorationRole:
0223         return index.column() == 0 ? aspect->icon() : QIcon();
0224     case Qt::ForegroundRole: {
0225             const WorksheetElement* we = dynamic_cast<WorksheetElement*>(aspect);
0226             if (we) {
0227                 if (!we->isVisible())
0228                     return QVariant(  QApplication::palette().color(QPalette::Disabled,QPalette::Text ) );
0229             }
0230             return QVariant( QApplication::palette().color(QPalette::Active,QPalette::Text ) );
0231         }
0232     default:
0233         return QVariant();
0234     }
0235 }
0236 
0237 Qt::ItemFlags AspectTreeModel::flags(const QModelIndex &index) const {
0238     if (!index.isValid())
0239         return Qt::NoItemFlags;
0240 
0241     Qt::ItemFlags result;
0242     auto* aspect = static_cast<AbstractAspect*>(index.internalPointer());
0243 
0244     if (!m_selectableAspects.isEmpty()) {
0245         for (AspectType type : m_selectableAspects) {
0246             if (aspect->inherits(type)) {
0247                 result = Qt::ItemIsEnabled | Qt::ItemIsSelectable;
0248                 if (index != this->index(0,0,QModelIndex()) &&  !m_filterString.isEmpty()) {
0249                     if (this->containsFilterString(aspect))
0250                         result = Qt::ItemIsEnabled | Qt::ItemIsSelectable;
0251                     else
0252                         result &= ~Qt::ItemIsEnabled;
0253                 }
0254                 break;
0255             } else
0256                 result &= ~Qt::ItemIsEnabled;
0257         }
0258     } else {
0259         //default case: the list for the selectable aspects is empty and all aspects are selectable.
0260         // Apply filter, if available. Indices, that don't match the filter are not selectable.
0261         //Don't apply any filter to the very first index in the model  - this top index corresponds to the project item.
0262         if (index != this->index(0,0,QModelIndex()) &&  !m_filterString.isEmpty()) {
0263             if (this->containsFilterString(aspect))
0264                 result = Qt::ItemIsEnabled | Qt::ItemIsSelectable;
0265             else
0266                 result = Qt::ItemIsSelectable;
0267         } else
0268             result = Qt::ItemIsEnabled | Qt::ItemIsSelectable;
0269     }
0270 
0271     //the columns "name" and "description" are editable
0272     if (!m_readOnly) {
0273         if (index.column() == 0 || index.column() == 3)
0274             result |= Qt::ItemIsEditable;
0275     }
0276 
0277     const auto* column = dynamic_cast<const Column*>(aspect);
0278     if (column) {
0279         //allow to drag and drop columns for the faster creation of curves in the plots.
0280         //TODO: allow drag&drop later for other objects too, once we implement copy and paste in the project explorer
0281         result = result |Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled;
0282 
0283         if (m_plottableColumnsOnly && !column->isPlottable())
0284             result &= ~Qt::ItemIsEnabled;
0285 
0286         if (m_numericColumnsOnly && !column->isNumeric())
0287             result &= ~Qt::ItemIsEnabled;
0288 
0289         if (m_nonEmptyNumericColumnsOnly && !(column->isNumeric() && column->hasValues()))
0290             result &= ~Qt::ItemIsEnabled;
0291     }
0292 
0293     return result;
0294 }
0295 
0296 void AspectTreeModel::aspectDescriptionChanged(const AbstractAspect* aspect) {
0297     emit dataChanged(modelIndexOfAspect(aspect), modelIndexOfAspect(aspect, 3));
0298 }
0299 
0300 void AspectTreeModel::aspectAboutToBeAdded(const AbstractAspect* parent, const AbstractAspect* before, const AbstractAspect* child) {
0301     Q_UNUSED(child);
0302     int index = parent->indexOfChild<AbstractAspect>(before);
0303     if (index == -1)
0304         index = parent->childCount<AbstractAspect>();
0305 
0306     beginInsertRows(modelIndexOfAspect(parent), index, index);
0307 }
0308 
0309 void AspectTreeModel::aspectAdded(const AbstractAspect* aspect) {
0310     endInsertRows();
0311     AbstractAspect* parent = aspect->parentAspect();
0312     emit dataChanged(modelIndexOfAspect(parent), modelIndexOfAspect(parent, 3));
0313 
0314     connect(aspect, &AbstractAspect::renameRequested, this, &AspectTreeModel::renameRequestedSlot);
0315     connect(aspect, &AbstractAspect::childAspectSelectedInView, this, &AspectTreeModel::aspectSelectedInView);
0316     connect(aspect, &AbstractAspect::childAspectDeselectedInView, this, &AspectTreeModel::aspectDeselectedInView);
0317 
0318     //add signal-slot connects for all children, too
0319     for (const auto* child : aspect->children<AbstractAspect>(AbstractAspect::ChildIndexFlag::Recursive)) {
0320         connect(child, &AbstractAspect::renameRequested, this, &AspectTreeModel::renameRequestedSlot);
0321         connect(child, &AbstractAspect::childAspectSelectedInView, this, &AspectTreeModel::aspectSelectedInView);
0322         connect(child, &AbstractAspect::childAspectDeselectedInView, this, &AspectTreeModel::aspectDeselectedInView);
0323     }
0324 }
0325 
0326 void AspectTreeModel::aspectAboutToBeRemoved(const AbstractAspect* aspect) {
0327     AbstractAspect* parent = aspect->parentAspect();
0328     int index = parent->indexOfChild<AbstractAspect>(aspect);
0329     beginRemoveRows(modelIndexOfAspect(parent), index, index);
0330 }
0331 
0332 void AspectTreeModel::aspectRemoved() {
0333     endRemoveRows();
0334 }
0335 
0336 void AspectTreeModel::aspectHiddenAboutToChange(const AbstractAspect* aspect) {
0337     for (AbstractAspect* i = aspect->parentAspect(); i; i = i->parentAspect())
0338         if (i->hidden())
0339             return;
0340     if (aspect->hidden())
0341         aspectAboutToBeAdded(aspect->parentAspect(), aspect, aspect);
0342     else
0343         aspectAboutToBeRemoved(aspect);
0344 }
0345 
0346 void AspectTreeModel::aspectHiddenChanged(const AbstractAspect* aspect) {
0347     for (AbstractAspect* i = aspect->parentAspect(); i; i = i->parentAspect())
0348         if (i->hidden())
0349             return;
0350     if (aspect->hidden())
0351         aspectRemoved();
0352     else
0353         aspectAdded(aspect);
0354 }
0355 
0356 bool AspectTreeModel::setData(const QModelIndex &index, const QVariant &value, int role) {
0357     if (!index.isValid() || role != Qt::EditRole) return false;
0358     auto* aspect = static_cast<AbstractAspect*>(index.internalPointer());
0359     switch (index.column()) {
0360     case 0: {
0361         if (!aspect->setName(value.toString(), false)) {
0362             emit statusInfo(i18n("The name \"%1\" is already in use. Choose another name.", value.toString()));
0363             return false;
0364         }
0365         break;
0366     } case 3:
0367         aspect->setComment(value.toString());
0368         break;
0369     default:
0370         return false;
0371     }
0372     emit dataChanged(index, index);
0373     return true;
0374 }
0375 
0376 QModelIndex AspectTreeModel::modelIndexOfAspect(const AbstractAspect* aspect, int column) const {
0377     AbstractAspect* parent = aspect->parentAspect();
0378     return createIndex(parent ? parent->indexOfChild<AbstractAspect>(aspect) : 0,
0379                        column, const_cast<AbstractAspect*>(aspect));
0380 }
0381 
0382 /*!
0383     returns the model index of an aspect defined via its path.
0384  */
0385 QModelIndex AspectTreeModel::modelIndexOfAspect(const QString& path, int column) const {
0386     //determine the aspect out of aspect path
0387     AbstractAspect* aspect = nullptr;
0388     auto children = m_root->children(AspectType::AbstractAspect, AbstractAspect::ChildIndexFlag::Recursive);
0389     for (auto* child: children) {
0390         if (child->path() == path) {
0391             aspect = child;
0392             break;
0393         }
0394     }
0395 
0396     //return the model index of the aspect
0397     if (aspect)
0398         return modelIndexOfAspect(aspect, column);
0399 
0400     return QModelIndex{};
0401 }
0402 
0403 void AspectTreeModel::setFilterString(const QString& s) {
0404     m_filterString = s;
0405     QModelIndex topLeft = this->index(0, 0, QModelIndex());
0406     QModelIndex bottomRight = this->index(this->rowCount() - 1, 3, QModelIndex());
0407     emit dataChanged(topLeft, bottomRight);
0408 }
0409 
0410 void AspectTreeModel::setFilterCaseSensitivity(Qt::CaseSensitivity cs) {
0411     m_filterCaseSensitivity = cs;
0412 }
0413 
0414 void AspectTreeModel::setFilterMatchCompleteWord(bool b) {
0415     m_matchCompleteWord = b;
0416 }
0417 
0418 bool AspectTreeModel::containsFilterString(const AbstractAspect* aspect) const {
0419     if (m_matchCompleteWord) {
0420         if (aspect->name().compare(m_filterString, m_filterCaseSensitivity) == 0)
0421             return true;
0422     } else {
0423         if (aspect->name().contains(m_filterString, m_filterCaseSensitivity))
0424             return true;
0425     }
0426 
0427     //check for the occurrence of the filter string in the names of the parents
0428     if ( aspect->parentAspect() )
0429         return this->containsFilterString(aspect->parentAspect());
0430     else
0431         return false;
0432 
0433     //TODO make this optional
0434     //  //check for the occurrence of the filter string in the names of the children
0435 //  foreach(const AbstractAspect * child, aspect->children<AbstractAspect>()) {
0436 //    if ( this->containsFilterString(child) )
0437 //      return true;
0438 //  }
0439 }
0440 
0441 //##############################################################################
0442 //#################################  SLOTS  ####################################
0443 //##############################################################################
0444 void AspectTreeModel::renameRequestedSlot() {
0445     auto* aspect = dynamic_cast<AbstractAspect*>(QObject::sender());
0446     if (aspect)
0447         emit renameRequested(modelIndexOfAspect(aspect));
0448 }
0449 
0450 void AspectTreeModel::aspectSelectedInView(const AbstractAspect* aspect) {
0451     if (aspect->hidden()) {
0452         //a hidden aspect was selected in the view (e.g. plot title in WorksheetView)
0453         //select the parent aspect first, if available
0454         AbstractAspect* parent = aspect->parentAspect();
0455         if (parent)
0456             emit indexSelected(modelIndexOfAspect(parent));
0457 
0458         //emit also this signal, so the GUI can handle this selection.
0459         emit hiddenAspectSelected(aspect);
0460     } else
0461         emit indexSelected(modelIndexOfAspect(aspect));
0462 
0463     //deselect the root item when one of the children was selected in the view
0464     //in order to avoid multiple selection with the project item (if selected) in the project explorer
0465     emit indexDeselected(modelIndexOfAspect(m_root));
0466 }
0467 
0468 void AspectTreeModel::aspectDeselectedInView(const AbstractAspect* aspect) {
0469     if (aspect->hidden()) {
0470         AbstractAspect* parent = aspect->parentAspect();
0471         if (parent)
0472             emit indexDeselected(modelIndexOfAspect(parent));
0473     } else
0474         emit indexDeselected(modelIndexOfAspect(aspect));
0475 }