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 }