File indexing completed on 2024-04-28 15:13:25

0001 
0002 /***************************************************************************
0003     File                 : ProjectExplorer.cpp
0004     Project              : LabPlot
0005     Description          : A tree view for displaying and editing an AspectTreeModel.
0006     --------------------------------------------------------------------
0007     Copyright            : (C) 2007-2008 by Tilman Benkert (thzs@gmx.net)
0008     Copyright            : (C) 2010-2018 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 "ProjectExplorer.h"
0031 #include "backend/core/AspectTreeModel.h"
0032 #include "backend/core/AbstractPart.h"
0033 #include "backend/core/Project.h"
0034 #include "backend/lib/XmlStreamReader.h"
0035 #include "backend/worksheet/plots/cartesian/CartesianPlot.h"
0036 #include "commonfrontend/core/PartMdiView.h"
0037 
0038 #include <QContextMenuEvent>
0039 #include <QDrag>
0040 #include <QHeaderView>
0041 #include <QLabel>
0042 #include <QLineEdit>
0043 #include <QMenu>
0044 #include <QMimeData>
0045 #include <QPushButton>
0046 #include <QToolButton>
0047 #include <QTreeView>
0048 #include <QVBoxLayout>
0049 
0050 #include <KConfig>
0051 #include <KConfigGroup>
0052 #include <KSharedConfig>
0053 #include <KLocalizedString>
0054 #include <KMessageBox>
0055 
0056 /*!
0057   \class ProjectExplorer
0058   \brief A tree view for displaying and editing an AspectTreeModel.
0059 
0060   In addition to the functionality of QTreeView, ProjectExplorer allows
0061   the usage of the context menus provided by AspectTreeModel
0062   and propagates the item selection in the view to the model.
0063   Furthermore, features for searching and filtering in the model are provided.
0064 
0065   \ingroup commonfrontend
0066 */
0067 
0068 ProjectExplorer::ProjectExplorer(QWidget* parent) :
0069     m_treeView(new QTreeView(parent)),
0070     m_frameFilter(new QFrame(this)) {
0071 
0072     auto* layout = new QVBoxLayout(this);
0073     layout->setSpacing(0);
0074     layout->setContentsMargins(0, 0, 0, 0);
0075 
0076     auto* layoutFilter = new QHBoxLayout(m_frameFilter);
0077     layoutFilter->setSpacing(0);
0078     layoutFilter->setContentsMargins(0, 0, 0, 0);
0079 
0080     m_leFilter = new QLineEdit(m_frameFilter);
0081     m_leFilter->setClearButtonEnabled(true);
0082     m_leFilter->setPlaceholderText(i18n("Search/Filter"));
0083     layoutFilter->addWidget(m_leFilter);
0084 
0085     bFilterOptions = new QToolButton(m_frameFilter);
0086     bFilterOptions->setIcon(QIcon::fromTheme("configure"));
0087     bFilterOptions->setCheckable(true);
0088     layoutFilter->addWidget(bFilterOptions);
0089 
0090     layout->addWidget(m_frameFilter);
0091 
0092     m_treeView->setAnimated(true);
0093     m_treeView->setAlternatingRowColors(true);
0094     m_treeView->setSelectionBehavior(QAbstractItemView::SelectRows);
0095     m_treeView->setSelectionMode(QAbstractItemView::ExtendedSelection);
0096     m_treeView->setUniformRowHeights(true);
0097     m_treeView->viewport()->installEventFilter(this);
0098     m_treeView->header()->setStretchLastSection(true);
0099     m_treeView->header()->installEventFilter(this);
0100     m_treeView->setDragEnabled(true);
0101     m_treeView->setAcceptDrops(true);
0102     m_treeView->setDropIndicatorShown(true);
0103     m_treeView->setDragDropMode(QAbstractItemView::InternalMove);
0104 
0105     layout->addWidget(m_treeView);
0106 
0107     this->createActions();
0108 
0109     connect(m_leFilter, &QLineEdit::textChanged, this, &ProjectExplorer::filterTextChanged);
0110     connect(bFilterOptions, &QPushButton::toggled, this, &ProjectExplorer::toggleFilterOptionsMenu);
0111 }
0112 
0113 ProjectExplorer::~ProjectExplorer() {
0114     // save the visible columns
0115     QString status;
0116     for (int i = 0; i < list_showColumnActions.size(); ++i) {
0117         if (list_showColumnActions.at(i)->isChecked()) {
0118             if (!status.isEmpty())
0119                 status += QLatin1Char(' ');
0120             status += QString::number(i);
0121         }
0122     }
0123     KConfigGroup group(KSharedConfig::openConfig(), QLatin1String("ProjectExplorer"));
0124     group.writeEntry("VisibleColumns", status);
0125 }
0126 
0127 void ProjectExplorer::createActions() {
0128     caseSensitiveAction = new QAction(i18n("Case Sensitive"), this);
0129     caseSensitiveAction->setCheckable(true);
0130     caseSensitiveAction->setChecked(false);
0131     connect(caseSensitiveAction, &QAction::triggered, this, &ProjectExplorer::toggleFilterCaseSensitivity);
0132 
0133     matchCompleteWordAction = new QAction(i18n("Match Complete Word"), this);
0134     matchCompleteWordAction->setCheckable(true);
0135     matchCompleteWordAction->setChecked(false);
0136     connect(matchCompleteWordAction, &QAction::triggered, this, &ProjectExplorer::toggleFilterMatchCompleteWord);
0137 
0138     expandTreeAction = new QAction(QIcon::fromTheme(QLatin1String("expand-all")), i18n("Expand All"), this);
0139     connect(expandTreeAction, &QAction::triggered, m_treeView, &QTreeView::expandAll);
0140 
0141     expandSelectedTreeAction = new QAction(QIcon::fromTheme(QLatin1String("expand-all")), i18n("Expand Selected"), this);
0142     connect(expandSelectedTreeAction, &QAction::triggered, this, &ProjectExplorer::expandSelected);
0143 
0144     collapseTreeAction = new QAction(QIcon::fromTheme(QLatin1String("collapse-all")), i18n("Collapse All"), this);
0145     connect(collapseTreeAction, &QAction::triggered, m_treeView, &QTreeView::collapseAll);
0146 
0147     collapseSelectedTreeAction = new QAction(QIcon::fromTheme(QLatin1String("collapse-all")), i18n("Collapse Selected"), this);
0148     connect(collapseSelectedTreeAction, &QAction::triggered, this, &ProjectExplorer::collapseSelected);
0149 
0150     deleteSelectedTreeAction = new QAction(QIcon::fromTheme("edit-delete"), i18n("Delete Selected"), this);
0151     connect(deleteSelectedTreeAction, &QAction::triggered, this, &ProjectExplorer::deleteSelected);
0152 
0153     toggleFilterAction = new QAction(QIcon::fromTheme(QLatin1String("view-filter")), i18n("Hide Search/Filter Options"), this);
0154     connect(toggleFilterAction, &QAction::triggered, this, &ProjectExplorer::toggleFilterWidgets);
0155 
0156     showAllColumnsAction = new QAction(i18n("Show All"),this);
0157     showAllColumnsAction->setCheckable(true);
0158     showAllColumnsAction->setChecked(true);
0159     showAllColumnsAction->setEnabled(false);
0160     connect(showAllColumnsAction, &QAction::triggered, this, &ProjectExplorer::showAllColumns);
0161 }
0162 
0163 /*!
0164   shows the context menu in the tree. In addition to the context menu of the currently selected aspect,
0165   treeview specific options are added.
0166 */
0167 void ProjectExplorer::contextMenuEvent(QContextMenuEvent *event) {
0168     if (!m_treeView->model())
0169         return;
0170 
0171     const QModelIndex& index = m_treeView->indexAt(m_treeView->viewport()->mapFrom(this, event->pos()));
0172     if (!index.isValid())
0173         m_treeView->clearSelection();
0174 
0175     const QModelIndexList& items = m_treeView->selectionModel()->selectedIndexes();
0176     QMenu* menu = nullptr;
0177     if (items.size()/4 == 1) {
0178         auto* aspect = static_cast<AbstractAspect*>(index.internalPointer());
0179         menu = aspect->createContextMenu();
0180     } else {
0181         menu = new QMenu();
0182 
0183         QMenu* projectMenu = m_project->createContextMenu();
0184         projectMenu->setTitle(m_project->name());
0185         menu->addMenu(projectMenu);
0186         menu->addSeparator();
0187 
0188         if (items.size()/4 > 1) {
0189             menu->addAction(expandSelectedTreeAction);
0190             menu->addAction(collapseSelectedTreeAction);
0191             menu->addSeparator();
0192             menu->addAction(deleteSelectedTreeAction);
0193             menu->addSeparator();
0194         } else {
0195             menu->addAction(expandTreeAction);
0196             menu->addAction(collapseTreeAction);
0197             menu->addSeparator();
0198             menu->addAction(toggleFilterAction);
0199 
0200             //Menu for showing/hiding the columns in the tree view
0201             QMenu* columnsMenu = menu->addMenu(i18n("Show/Hide columns"));
0202             columnsMenu->addAction(showAllColumnsAction);
0203             columnsMenu->addSeparator();
0204             for (auto* action : list_showColumnActions)
0205                 columnsMenu->addAction(action);
0206 
0207             //TODO
0208             //Menu for showing/hiding the top-level aspects (Worksheet, Spreadhsheet, etc) in the tree view
0209             // QMenu* objectsMenu = menu->addMenu(i18n("Show/Hide objects"));
0210         }
0211     }
0212 
0213     if (menu)
0214         menu->exec(event->globalPos());
0215 
0216     delete menu;
0217 }
0218 
0219 void ProjectExplorer::setCurrentAspect(const AbstractAspect* aspect) {
0220     //HACK: when doing redo/undo in MainWin and an object is being deleted,
0221     //we don't want to jump to another object in the project explorer.
0222     //we reuse the aspectAddedSignalSuppressed also for the deletion of aspects.
0223     if (m_project->aspectAddedSignalSuppressed())
0224         return;
0225 
0226     const AspectTreeModel* tree_model = qobject_cast<AspectTreeModel*>(m_treeView->model());
0227     if (tree_model)
0228         m_treeView->setCurrentIndex(tree_model->modelIndexOfAspect(aspect));
0229 }
0230 
0231 /*!
0232   Sets the \c model for the tree view to present.
0233 */
0234 void ProjectExplorer::setModel(AspectTreeModel* treeModel) {
0235     m_treeView->setModel(treeModel);
0236 
0237     connect(treeModel, &AspectTreeModel::renameRequested,
0238             m_treeView,  static_cast<void (QAbstractItemView::*)(const QModelIndex&)>(&QAbstractItemView::edit));
0239     connect(treeModel, &AspectTreeModel::indexSelected, this, &ProjectExplorer::selectIndex);
0240     connect(treeModel, &AspectTreeModel::indexDeselected, this, &ProjectExplorer::deselectIndex);
0241     connect(treeModel, &AspectTreeModel::hiddenAspectSelected, this, &ProjectExplorer::hiddenAspectSelected);
0242 
0243     connect(m_treeView->selectionModel(), &QItemSelectionModel::currentChanged, this, &ProjectExplorer::currentChanged);
0244     connect(m_treeView->selectionModel(), &QItemSelectionModel::selectionChanged, this, &ProjectExplorer::selectionChanged);
0245 
0246     //create action for showing/hiding the columns in the tree.
0247     //this is done here since the number of columns is  not available in createActions() yet.
0248     if (list_showColumnActions.isEmpty()) {
0249         //read the status of the column actions if available
0250         KConfigGroup group(KSharedConfig::openConfig(), QLatin1String("ProjectExplorer"));
0251         const QString& status = group.readEntry(QLatin1String("VisibleColumns"), QString());
0252         QVector<int> checkedActions;
0253         if (!status.isEmpty()) {
0254             QStringList strList = status.split(QLatin1Char(' '));
0255             for (int i = 0; i < strList.size(); ++i)
0256                 checkedActions << strList.at(i).toInt();
0257         }
0258 
0259         if (checkedActions.size() != m_treeView->model()->columnCount()) {
0260             showAllColumnsAction->setEnabled(true);
0261             showAllColumnsAction->setChecked(false);
0262         } else {
0263             showAllColumnsAction->setEnabled(false);
0264             showAllColumnsAction->setChecked(true);
0265         }
0266 
0267         //create an action for every available column in the model
0268         for (int i = 0; i < m_treeView->model()->columnCount(); i++) {
0269             QAction* showColumnAction =  new QAction(treeModel->headerData(i, Qt::Horizontal).toString(), this);
0270             showColumnAction->setCheckable(true);
0271 
0272             //restore the status, if available
0273             if (!checkedActions.isEmpty()) {
0274                 if (checkedActions.indexOf(i) != -1)
0275                     showColumnAction->setChecked(true);
0276                 else
0277                     m_treeView->hideColumn(i);
0278             } else
0279                 showColumnAction->setChecked(true);
0280 
0281             list_showColumnActions.append(showColumnAction);
0282 
0283             connect(showColumnAction, &QAction::triggered,
0284                     this, [=] { ProjectExplorer::toggleColumn(i); });
0285         }
0286     } else {
0287         for (int i = 0; i < list_showColumnActions.size(); ++i) {
0288             if (!list_showColumnActions.at(i)->isChecked())
0289                 m_treeView->hideColumn(i);
0290         }
0291     }
0292 }
0293 
0294 void ProjectExplorer::setProject(Project* project) {
0295     connect(project, &Project::aspectAdded, this, &ProjectExplorer::aspectAdded);
0296     connect(project, &Project::requestSaveState, this, &ProjectExplorer::save);
0297     connect(project, &Project::requestLoadState, this, &ProjectExplorer::load);
0298     connect(project, &Project::requestNavigateTo, this, &ProjectExplorer::navigateTo);
0299     connect(project, &Project::loaded, this, &ProjectExplorer::projectLoaded);
0300     m_project = project;
0301 
0302     //for newly created projects, resize the header to fit the size of the header section names.
0303     //for projects loaded from a file, this function will be called laterto fit the sizes
0304     //of the content once the project is loaded
0305     resizeHeader();
0306 }
0307 
0308 QModelIndex ProjectExplorer::currentIndex() const {
0309     return m_treeView->currentIndex();
0310 }
0311 
0312 void ProjectExplorer::search() {
0313     m_leFilter->setFocus();
0314 }
0315 
0316 /*!
0317     handles the contextmenu-event of the horizontal header in the tree view.
0318     Provides a menu for selective showing and hiding of columns.
0319 */
0320 bool ProjectExplorer::eventFilter(QObject* obj, QEvent* event) {
0321     if (obj == m_treeView->header() && event->type() == QEvent::ContextMenu) {
0322         //Menu for showing/hiding the columns in the tree view
0323         QMenu* columnsMenu = new QMenu(m_treeView->header());
0324         columnsMenu->addSection(i18n("Columns"));
0325         columnsMenu->addAction(showAllColumnsAction);
0326         columnsMenu->addSeparator();
0327         for (auto* action : list_showColumnActions)
0328             columnsMenu->addAction(action);
0329 
0330         auto* e = static_cast<QContextMenuEvent*>(event);
0331         columnsMenu->exec(e->globalPos());
0332         delete columnsMenu;
0333 
0334         return true;
0335     } else if (obj == m_treeView->viewport()) {
0336         if (event->type() == QEvent::MouseButtonPress) {
0337             auto* e = static_cast<QMouseEvent*>(event);
0338             if (e->button() == Qt::LeftButton) {
0339                 QModelIndex index = m_treeView->indexAt(e->pos());
0340                 if (!index.isValid())
0341                     return false;
0342 
0343                 auto* aspect = static_cast<AbstractAspect*>(index.internalPointer());
0344                 if (aspect->isDraggable()) {
0345                     m_dragStartPos = e->globalPos();
0346                     m_dragStarted = false;
0347                 }
0348             }
0349         } else if (event->type() == QEvent::MouseMove) {
0350             auto* e = static_cast<QMouseEvent*>(event);
0351             if ( !m_dragStarted && m_treeView->selectionModel()->selectedIndexes().size() > 0
0352                 && (e->globalPos() - m_dragStartPos).manhattanLength() >= QApplication::startDragDistance()) {
0353 
0354                 m_dragStarted = true;
0355                 auto* drag = new QDrag(this);
0356                 auto* mimeData = new QMimeData;
0357 
0358                 //determine the selected objects and serialize the pointers to QMimeData
0359                 QVector<quintptr> vec;
0360                 QModelIndexList items = m_treeView->selectionModel()->selectedIndexes();
0361                 //there are four model indices in each row -> divide by 4 to obtain the number of selected rows (=aspects)
0362                 for (int i = 0; i < items.size()/4; ++i) {
0363                     const QModelIndex& index = items.at(i*4);
0364                     auto* aspect = static_cast<AbstractAspect*>(index.internalPointer());
0365                     vec << (quintptr)aspect;
0366                 }
0367 
0368                 QByteArray data;
0369                 QDataStream stream(&data, QIODevice::WriteOnly);
0370                 stream << (quintptr)m_project; //serialize the project pointer first, will be used as the unique identifier
0371                 stream << vec;
0372 
0373                 mimeData->setData("labplot-dnd", data);
0374                 drag->setMimeData(mimeData);
0375                 drag->exec();
0376             }
0377         } else if (event->type() == QEvent::DragEnter) {
0378             //ignore events not related to internal drags of columns etc., e.g. dropping of external files onto LabPlot
0379             auto* dragEnterEvent = static_cast<QDragEnterEvent*>(event);
0380             const QMimeData* mimeData = dragEnterEvent->mimeData();
0381             if (!mimeData) {
0382                 event->ignore();
0383                 return false;
0384             }
0385 
0386             if (mimeData->formats().at(0) != QLatin1String("labplot-dnd")) {
0387                 event->ignore();
0388                 return false;
0389             } else {
0390                 //drad&drop between the different project windows is not supported yet.
0391                 //check whether we're dragging inside of the same project.
0392                 QByteArray data = mimeData->data(QLatin1String("labplot-dnd"));
0393                 QDataStream stream(&data, QIODevice::ReadOnly);
0394                 quintptr ptr = 0;
0395                 stream >> ptr;
0396                 auto* project = reinterpret_cast<Project*>(ptr);
0397                 if (project != m_project) {
0398                     event->ignore();
0399                     return false;
0400                 }
0401             }
0402 
0403             event->setAccepted(true);
0404         } else if (event->type() == QEvent::DragMove) {
0405             auto* dragMoveEvent = static_cast<QDragEnterEvent*>(event);
0406             const QMimeData* mimeData = dragMoveEvent->mimeData();
0407             QVector<quintptr> vec = m_project->droppedAspects(mimeData);
0408 
0409             AbstractAspect* sourceAspect{nullptr};
0410             if (!vec.isEmpty())
0411                 sourceAspect = reinterpret_cast<AbstractAspect*>(vec.at(0));
0412 
0413             if (!sourceAspect)
0414                 return false;
0415 
0416             //determine the aspect under the cursor
0417             QModelIndex index = m_treeView->indexAt(dragMoveEvent->pos());
0418             if (!index.isValid())
0419                 return false;
0420 
0421             //accept only the events when the aspect being dragged is dropable onto the aspect under the cursor
0422             //and the aspect under the cursor is not already the parent of the dragged aspect
0423             auto* destinationAspect = static_cast<AbstractAspect*>(index.internalPointer());
0424             bool accept = sourceAspect->dropableOn().indexOf(destinationAspect->type()) != -1
0425                         && sourceAspect->parentAspect() != destinationAspect;
0426             event->setAccepted(accept);
0427         } else if (event->type() == QEvent::Drop) {
0428             auto* dropEvent = static_cast<QDropEvent*>(event);
0429             const QMimeData* mimeData = dropEvent->mimeData();
0430             if (!mimeData)
0431                 return false;
0432 
0433             QVector<quintptr> vec = m_project->droppedAspects(mimeData);
0434             if (vec.isEmpty())
0435                 return false;
0436 
0437             QModelIndex index = m_treeView->indexAt(dropEvent->pos());
0438             if (!index.isValid())
0439                 return false;
0440 
0441             //process the droped objects
0442             auto* aspect = static_cast<AbstractAspect*>(index.internalPointer());
0443             aspect->processDropEvent(vec);
0444         }
0445     }
0446 
0447     return QObject::eventFilter(obj, event);
0448 }
0449 
0450 void ProjectExplorer::keyPressEvent(QKeyEvent* event) {
0451     if (event->matches(QKeySequence::Delete))
0452         deleteSelected();
0453     else if (event->key() == 32) {
0454         auto* aspect = static_cast<AbstractAspect*>(m_treeView->currentIndex().internalPointer());
0455         auto* we = dynamic_cast<WorksheetElement*>(aspect);
0456         if (we)
0457             we->setVisible(!we->isVisible());
0458     }
0459 }
0460 
0461 //##############################################################################
0462 //#################################  SLOTS  ####################################
0463 //##############################################################################
0464 /*!
0465  * called after the project was loaded.
0466  * resize the header of the view to adjust to the content
0467  * and re-select the currently selected object to show
0468  * its final properties in the dock widget after in Project::load() all
0469  * pointers were restored, relative paths replaced by absolute, etc.
0470  */
0471 void ProjectExplorer::projectLoaded() {
0472     resizeHeader();
0473     selectionChanged(m_treeView->selectionModel()->selection(), QItemSelection());
0474 }
0475 
0476 /*!
0477   expand the aspect \c aspect (the tree index corresponding to it) in the tree view
0478   and makes it visible and selected. Called when a new aspect is added to the project.
0479  */
0480 void ProjectExplorer::aspectAdded(const AbstractAspect* aspect) {
0481     if (m_project->isLoading() ||m_project->aspectAddedSignalSuppressed())
0482         return;
0483 
0484     //don't do anything if hidden aspects were added
0485     if (aspect->hidden())
0486         return;
0487 
0488 
0489     //don't do anything for newly added data spreadsheets of data picker curves
0490     if (aspect->inherits(AspectType::Spreadsheet) &&
0491         aspect->parentAspect()->inherits(AspectType::DatapickerCurve))
0492         return;
0493 
0494     const AspectTreeModel* tree_model = qobject_cast<AspectTreeModel*>(m_treeView->model());
0495     const QModelIndex& index =  tree_model->modelIndexOfAspect(aspect);
0496 
0497     //expand and make the aspect visible
0498     m_treeView->setExpanded(index, true);
0499 
0500     // newly added columns are only expanded but not selected, return here
0501     if (aspect->inherits(AspectType::Column)) {
0502         m_treeView->setExpanded(tree_model->modelIndexOfAspect(aspect->parentAspect()), true);
0503         return;
0504     }
0505 
0506     m_treeView->scrollTo(index);
0507     m_treeView->setCurrentIndex(index);
0508     m_treeView->header()->resizeSections(QHeaderView::ResizeToContents);
0509     m_treeView->header()->resizeSection(0, m_treeView->header()->sectionSize(0)*1.2);
0510 }
0511 
0512 void ProjectExplorer::navigateTo(const QString& path) {
0513     const AspectTreeModel* tree_model = qobject_cast<AspectTreeModel*>(m_treeView->model());
0514     if (tree_model) {
0515         const QModelIndex& index = tree_model->modelIndexOfAspect(path);
0516         m_treeView->scrollTo(index);
0517         m_treeView->setCurrentIndex(index);
0518         auto* aspect = static_cast<AbstractAspect*>(index.internalPointer());
0519         aspect->setSelected(true);
0520     }
0521 }
0522 
0523 void ProjectExplorer::currentChanged(const QModelIndex & current, const QModelIndex & previous) {
0524     if (m_project->isLoading())
0525         return;
0526 
0527     Q_UNUSED(previous);
0528     emit currentAspectChanged(static_cast<AbstractAspect*>(current.internalPointer()));
0529 }
0530 
0531 void ProjectExplorer::toggleColumn(int index) {
0532     //determine the total number of checked column actions
0533     int checked = 0;
0534     for (const auto* action : list_showColumnActions) {
0535         if (action->isChecked())
0536             checked++;
0537     }
0538 
0539     if (list_showColumnActions.at(index)->isChecked()) {
0540         m_treeView->showColumn(index);
0541         m_treeView->header()->resizeSection(0,0 );
0542         m_treeView->header()->resizeSections(QHeaderView::ResizeToContents);
0543 
0544         for (auto* action : list_showColumnActions)
0545             action->setEnabled(true);
0546 
0547         //deactivate the "show all column"-action, if all actions are checked
0548         if ( checked == list_showColumnActions.size() ) {
0549             showAllColumnsAction->setEnabled(false);
0550             showAllColumnsAction->setChecked(true);
0551         }
0552     } else {
0553         m_treeView->hideColumn(index);
0554         showAllColumnsAction->setEnabled(true);
0555         showAllColumnsAction->setChecked(false);
0556 
0557         //if there is only one checked column-action, deactivated it.
0558         //It should't be possible to hide all columns
0559         if ( checked == 1 ) {
0560             int i = 0;
0561             while ( !list_showColumnActions.at(i)->isChecked() )
0562                 i++;
0563 
0564             list_showColumnActions.at(i)->setEnabled(false);
0565         }
0566     }
0567 }
0568 
0569 void ProjectExplorer::showAllColumns() {
0570     for (int i = 0; i < m_treeView->model()->columnCount(); i++) {
0571         m_treeView->showColumn(i);
0572         m_treeView->header()->resizeSection(0,0 );
0573         m_treeView->header()->resizeSections(QHeaderView::ResizeToContents);
0574     }
0575     showAllColumnsAction->setEnabled(false);
0576 
0577     for (auto* action : list_showColumnActions) {
0578         action->setEnabled(true);
0579         action->setChecked(true);
0580     }
0581 }
0582 
0583 /*!
0584   shows/hides the frame with the search/filter widgets
0585 */
0586 void ProjectExplorer::toggleFilterWidgets() {
0587     if (m_frameFilter->isVisible()) {
0588         m_frameFilter->hide();
0589         toggleFilterAction->setText(i18n("Show Search/Filter Options"));
0590     } else {
0591         m_frameFilter->show();
0592         toggleFilterAction->setText(i18n("Hide Search/Filter Options"));
0593     }
0594 }
0595 
0596 /*!
0597   toggles the menu for the filter/search options
0598 */
0599 void ProjectExplorer::toggleFilterOptionsMenu(bool checked) {
0600     if (checked) {
0601         QMenu menu;
0602         menu.addAction(caseSensitiveAction);
0603         menu.addAction(matchCompleteWordAction);
0604         connect(&menu, &QMenu::aboutToHide, bFilterOptions, &QPushButton::toggle);
0605         menu.exec(bFilterOptions->mapToGlobal(QPoint(0,bFilterOptions->height())));
0606     }
0607 }
0608 
0609 void ProjectExplorer::resizeHeader() {
0610     m_treeView->header()->resizeSections(QHeaderView::ResizeToContents);
0611     m_treeView->header()->resizeSection(0, m_treeView->header()->sectionSize(0)*1.2); //make the column "Name" somewhat bigger
0612 }
0613 
0614 /*!
0615   called when the filter/search text was changend.
0616 */
0617 void ProjectExplorer::filterTextChanged(const QString& text) {
0618     QModelIndex root = m_treeView->model()->index(0,0);
0619     filter(root, text);
0620 }
0621 
0622 bool ProjectExplorer::filter(const QModelIndex& index, const QString& text) {
0623     Qt::CaseSensitivity sensitivity = caseSensitiveAction->isChecked() ? Qt::CaseSensitive : Qt::CaseInsensitive;
0624     bool matchCompleteWord = matchCompleteWordAction->isChecked();
0625 
0626     bool childVisible = false;
0627     const int rows = index.model()->rowCount(index);
0628     for (int i = 0; i < rows; i++) {
0629         QModelIndex child = index.model()->index(i, 0, index);
0630         auto* aspect =  static_cast<AbstractAspect*>(child.internalPointer());
0631         bool visible;
0632         if (text.isEmpty())
0633             visible = true;
0634         else if (matchCompleteWord)
0635             visible = aspect->name().startsWith(text, sensitivity);
0636         else
0637             visible = aspect->name().contains(text, sensitivity);
0638 
0639         if (visible) {
0640             //current item is visible -> make all its children visible without applying the filter
0641             for (int j = 0; j < child.model()->rowCount(child); ++j) {
0642                 m_treeView->setRowHidden(j, child, false);
0643                 if (text.isEmpty())
0644                     filter(child, text);
0645             }
0646 
0647             childVisible = true;
0648         } else {
0649             //check children items. if one of the children is visible, make the parent (current) item visible too.
0650             visible = filter(child, text);
0651             if (visible)
0652                 childVisible = true;
0653         }
0654 
0655         m_treeView->setRowHidden(i, index, !visible);
0656     }
0657 
0658     return childVisible;
0659 }
0660 
0661 void ProjectExplorer::toggleFilterCaseSensitivity() {
0662     filterTextChanged(m_leFilter->text());
0663 }
0664 
0665 void ProjectExplorer::toggleFilterMatchCompleteWord() {
0666     filterTextChanged(m_leFilter->text());
0667 }
0668 
0669 void ProjectExplorer::selectIndex(const QModelIndex&  index) {
0670     if (m_project->isLoading())
0671         return;
0672 
0673     if ( !m_treeView->selectionModel()->isSelected(index) ) {
0674         m_changeSelectionFromView = true;
0675         m_treeView->selectionModel()->select(index, QItemSelectionModel::Select | QItemSelectionModel::Rows);
0676         m_treeView->setExpanded(index, true);
0677         m_treeView->scrollTo(index);
0678     }
0679 }
0680 
0681 void ProjectExplorer::deselectIndex(const QModelIndex & index) {
0682     if (m_project->isLoading())
0683         return;
0684 
0685     if ( m_treeView->selectionModel()->isSelected(index) ) {
0686         m_changeSelectionFromView = true;
0687         m_treeView->selectionModel()->select(index, QItemSelectionModel::Deselect | QItemSelectionModel::Rows);
0688     }
0689 }
0690 
0691 void ProjectExplorer::selectionChanged(const QItemSelection &selected, const QItemSelection &deselected) {
0692     QModelIndex index;
0693     QModelIndexList items;
0694     AbstractAspect* aspect = nullptr;
0695 
0696     //there are four model indices in each row
0697     //-> divide by 4 to obtain the number of selected rows (=aspects)
0698     items = selected.indexes();
0699     for (int i = 0; i < items.size()/4; ++i) {
0700         index = items.at(i*4);
0701         aspect = static_cast<AbstractAspect*>(index.internalPointer());
0702         aspect->setSelected(true);
0703     }
0704 
0705     items = deselected.indexes();
0706     for (int i = 0; i < items.size()/4; ++i) {
0707         index = items.at(i*4);
0708         aspect = static_cast<AbstractAspect*>(index.internalPointer());
0709         aspect->setSelected(false);
0710     }
0711 
0712     items = m_treeView->selectionModel()->selectedRows();
0713     QList<AbstractAspect*> selectedAspects;
0714     for (const QModelIndex& index : items) {
0715         aspect = static_cast<AbstractAspect*>(index.internalPointer());
0716         selectedAspects<<aspect;
0717     }
0718 
0719     emit selectedAspectsChanged(selectedAspects);
0720 
0721     //emitting the signal above is done to show the properties widgets for the selected aspect(s).
0722     //with this the project explorer looses the focus and don't react on the key events like DEL key press, etc.
0723     //If we explicitely select an item in the project explorer (not via a selection in the view), we want to keep the focus here.
0724     //TODO: after the focus is set again we react on DEL in the event filter, but navigation with the arrow keys in the table
0725     //is still not possible. Looks like we need to set the selection again...
0726     if (!m_changeSelectionFromView)
0727         setFocus();
0728     else
0729         m_changeSelectionFromView = false;
0730 }
0731 
0732 /*!
0733  * Used to udpate the cursor Dock
0734  */
0735 void ProjectExplorer::updateSelectedAspects() {
0736     QModelIndexList items = m_treeView->selectionModel()->selectedRows();
0737     QList<AbstractAspect*> selectedAspects;
0738     for (const QModelIndex& index : items)
0739         selectedAspects << static_cast<AbstractAspect*>(index.internalPointer());
0740 
0741     emit selectedAspectsChanged(selectedAspects);
0742 }
0743 
0744 void ProjectExplorer::expandSelected() {
0745     const QModelIndexList items = m_treeView->selectionModel()->selectedIndexes();
0746     for (const auto& index : items)
0747         m_treeView->setExpanded(index, true);
0748 }
0749 
0750 void ProjectExplorer::collapseSelected() {
0751     const QModelIndexList items = m_treeView->selectionModel()->selectedIndexes();
0752     for (const auto& index : items)
0753         m_treeView->setExpanded(index, false);
0754 }
0755 
0756 void ProjectExplorer::deleteSelected() {
0757     const QModelIndexList items = m_treeView->selectionModel()->selectedIndexes();
0758     if (!items.size())
0759         return;
0760 
0761 
0762     //determine all selected aspect
0763     QVector<AbstractAspect*> aspects;
0764     for (int i = 0; i < items.size()/4; ++i) {
0765         const QModelIndex& index = items.at(i*4);
0766         auto* aspect = static_cast<AbstractAspect*>(index.internalPointer());
0767         aspects << aspect;
0768     }
0769 
0770     QString msg;
0771     if (aspects.size() > 1)
0772         msg = i18n("Do you really want to delete the selected %1 objects?", aspects.size());
0773     else
0774          msg = i18n("Do you really want to delete %1?", aspects.constFirst()->name());
0775 
0776     int rc = KMessageBox::warningYesNo(this, msg, i18np("Delete selected object", "Delete selected objects", aspects.size()));
0777 
0778     if (rc == KMessageBox::No)
0779         return;
0780 
0781     m_project->beginMacro(i18np("Project Explorer: delete %1 selected object", "Project Explorer: delete %1 selected objects", items.size()/4));
0782 
0783     //determine aspects to be deleted:
0784     //it's enough to delete parent items in the selection only,
0785     //skip all selected aspects where one of the parents is also in the selection
0786     //for example selected columns of a selected spreadsheet, etc.
0787     QVector<AbstractAspect*> aspectsToDelete;
0788     for (auto* aspect : aspects) {
0789         auto* parent = aspect->parentAspect();
0790         int parentSelected = false;
0791         while (parent) {
0792             if (aspects.indexOf(parent) != -1) {
0793                 parentSelected = true;
0794                 break;
0795             }
0796 
0797             parent = parent->parentAspect();
0798         }
0799 
0800         if (!parentSelected)
0801             aspectsToDelete << aspect; //parent is not in the selection
0802     }
0803 
0804     for (auto* aspect : aspectsToDelete)
0805         aspect->remove();
0806 
0807     m_project->endMacro();
0808 }
0809 
0810 //##############################################################################
0811 //##################  Serialization/Deserialization  ###########################
0812 //##############################################################################
0813 struct ViewState {
0814     Qt::WindowStates state;
0815     QRect geometry;
0816 };
0817 
0818 /**
0819  * \brief Save the current state of the tree view
0820  * (expanded items and the currently selected item) as XML
0821  */
0822 void ProjectExplorer::save(QXmlStreamWriter* writer) const {
0823     auto* model = qobject_cast<AspectTreeModel*>(m_treeView->model());
0824     QList<int> selected;
0825     QList<int> expanded;
0826     QList<int> withView;
0827     QVector<ViewState> viewStates;
0828 
0829     int currentRow = -1; //row corresponding to the current index in the tree view, -1 for the root element (=project)
0830     QModelIndexList selectedRows = m_treeView->selectionModel()->selectedRows();
0831 
0832     //check whether the project node itself is expanded
0833     if (m_treeView->isExpanded(m_treeView->model()->index(0,0)))
0834         expanded.push_back(-1);
0835 
0836     int row = 0;
0837     for (const auto* aspect : m_project->children(AspectType::AbstractAspect, AbstractAspect::ChildIndexFlag::Recursive)) {
0838         const QModelIndex& index = model->modelIndexOfAspect(aspect);
0839 
0840         const auto* part = dynamic_cast<const AbstractPart*>(aspect);
0841         if (part && part->hasMdiSubWindow()) {
0842             withView.push_back(row);
0843             ViewState s = {part->view()->windowState(), part->view()->geometry()};
0844             viewStates.push_back(s);
0845         }
0846 
0847         if (model->rowCount(index)>0 && m_treeView->isExpanded(index))
0848             expanded.push_back(row);
0849 
0850         if (selectedRows.indexOf(index) != -1)
0851             selected.push_back(row);
0852 
0853         if (index == m_treeView->currentIndex())
0854             currentRow = row;
0855 
0856         row++;
0857     }
0858 
0859     writer->writeStartElement("state");
0860 
0861     writer->writeStartElement("expanded");
0862     for (const auto e : expanded)
0863         writer->writeTextElement("row", QString::number(e));
0864     writer->writeEndElement();
0865 
0866     writer->writeStartElement("selected");
0867     for (const auto s : selected)
0868         writer->writeTextElement("row", QString::number(s));
0869     writer->writeEndElement();
0870 
0871     writer->writeStartElement("view");
0872     for (int i = 0; i < withView.size(); ++i) {
0873         writer->writeStartElement("row");
0874         const ViewState& s = viewStates.at(i);
0875         writer->writeAttribute( "state", QString::number(s.state) );
0876         writer->writeAttribute( "x", QString::number(s.geometry.x()) );
0877         writer->writeAttribute( "y", QString::number(s.geometry.y()) );
0878         writer->writeAttribute( "width", QString::number(s.geometry.width()) );
0879         writer->writeAttribute( "height", QString::number(s.geometry.height()) );
0880         writer->writeCharacters(QString::number(withView.at(i)));
0881         writer->writeEndElement();
0882     }
0883     writer->writeEndElement();
0884 
0885     writer->writeStartElement("current");
0886     writer->writeTextElement("row", QString::number(currentRow));
0887     writer->writeEndElement();
0888 
0889     writer->writeEndElement();
0890 }
0891 
0892 /**
0893  * \brief Load from XML
0894  */
0895 bool ProjectExplorer::load(XmlStreamReader* reader) {
0896     const AspectTreeModel* model = qobject_cast<AspectTreeModel*>(m_treeView->model());
0897     const auto aspects = m_project->children(AspectType::AbstractAspect, AbstractAspect::ChildIndexFlag::Recursive);
0898 
0899     bool expandedItem = false;
0900     bool selectedItem = false;
0901     bool viewItem = false;
0902     (void)viewItem; // because of a strange g++-warning about unused viewItem
0903     bool currentItem = false;
0904     QModelIndex currentIndex;
0905     QString str;
0906     int row;
0907     QVector<QModelIndex> selected;
0908     QList<QModelIndex> expanded;
0909     QXmlStreamAttributes attribs;
0910     KLocalizedString attributeWarning = ki18n("Attribute '%1' missing or empty, default value is used");
0911 
0912     while (!reader->atEnd()) {
0913         reader->readNext();
0914         if (reader->isEndElement() && reader->name() == "state")
0915             break;
0916 
0917         if (!reader->isStartElement())
0918             continue;
0919 
0920         if (reader->name() == "expanded") {
0921             expandedItem = true;
0922             selectedItem = false;
0923             viewItem = false;
0924             currentItem = false;
0925         } else if (reader->name() == "selected") {
0926             expandedItem = false;
0927             selectedItem = true;
0928             viewItem = false;
0929             currentItem = false;
0930         } else  if (reader->name() == "view") {
0931             expandedItem = false;
0932             selectedItem = false;
0933             viewItem = true;
0934             currentItem = false;
0935         } else  if (reader->name() == "current") {
0936             expandedItem = false;
0937             selectedItem = false;
0938             viewItem = false;
0939             currentItem = true;
0940         } else if (reader->name() == "row") {
0941             //we need to read the attributes first and before readElementText() otherwise they are empty
0942             attribs = reader->attributes();
0943             row = reader->readElementText().toInt();
0944 
0945             QModelIndex index;
0946             if (row == -1)
0947                 index = model->modelIndexOfAspect(m_project); //-1 corresponds to the project-item (s.a. ProjectExplorer::save())
0948             else if (row >= aspects.size() || row < 0 /* checking for <0 to protect against wrong values in the XML */)
0949                 continue;
0950             else
0951                 index = model->modelIndexOfAspect(aspects.at(row));
0952 
0953             if (expandedItem)
0954                 expanded.push_back(index);
0955             else if (selectedItem)
0956                 selected.push_back(index);
0957             else if (currentItem)
0958                 currentIndex = index;
0959             else if (viewItem) {
0960                 if (row < 0) //should never happen, but we need to handle the corrupted file
0961                     continue;
0962 
0963                 auto* part = dynamic_cast<AbstractPart*>(aspects.at(row));
0964                 if (!part)
0965                     continue; //TODO: add error/warning message here?
0966 
0967                 emit activateView(part); //request to show the view in MainWin
0968 
0969                 str = attribs.value("state").toString();
0970                 if (str.isEmpty())
0971                     reader->raiseWarning(attributeWarning.subs("state").toString());
0972                 else {
0973                     part->view()->setWindowState(Qt::WindowStates(str.toInt()));
0974                     part->mdiSubWindow()->setWindowState(Qt::WindowStates(str.toInt()));
0975                 }
0976 
0977                 if (str != "0")
0978                     continue; //no geometry settings required for maximized/minimized windows
0979 
0980                 QRect geometry;
0981                 str = attribs.value("x").toString();
0982                 if (str.isEmpty())
0983                     reader->raiseWarning(attributeWarning.subs("x").toString());
0984                 else
0985                     geometry.setX(str.toInt());
0986 
0987                 str = attribs.value("y").toString();
0988                 if (str.isEmpty())
0989                     reader->raiseWarning(attributeWarning.subs("y").toString());
0990                 else
0991                     geometry.setY(str.toInt());
0992 
0993                 str = attribs.value("width").toString();
0994                 if (str.isEmpty())
0995                     reader->raiseWarning(attributeWarning.subs("width").toString());
0996                 else
0997                     geometry.setWidth(str.toInt());
0998 
0999                 str = attribs.value("height").toString();
1000                 if (str.isEmpty())
1001                     reader->raiseWarning(attributeWarning.subs("height").toString());
1002                 else
1003                     geometry.setHeight(str.toInt());
1004 
1005                 part->mdiSubWindow()->setGeometry(geometry);
1006             }
1007         }
1008     }
1009 
1010     for (const auto& index : expanded) {
1011         m_treeView->setExpanded(index, true);
1012         collapseParents(index, expanded);//collapse all parent indices if they are not expanded
1013     }
1014 
1015     for (const auto& index : selected)
1016         m_treeView->selectionModel()->select(index, QItemSelectionModel::Select | QItemSelectionModel::Rows);
1017 
1018     m_treeView->setCurrentIndex(currentIndex);
1019     m_treeView->scrollTo(currentIndex);
1020     auto* aspect = static_cast<AbstractAspect*>(currentIndex.internalPointer());
1021     emit currentAspectChanged(aspect);
1022 
1023     //when setting the current index above it gets expanded, collapse all parent indices if they are were not expanded when saved
1024     collapseParents(currentIndex, expanded);
1025 
1026     return true;
1027 }
1028 
1029 void ProjectExplorer::collapseParents(const QModelIndex& index, const QList<QModelIndex>& expanded) {
1030     //root index doesn't have any parents - this case is not caught by the second if-statement below
1031     if (index.column() == 0 && index.row() == 0)
1032         return;
1033 
1034     const QModelIndex parent = index.parent();
1035     if (parent == QModelIndex())
1036         return;
1037 
1038     if (expanded.indexOf(parent) == -1)
1039         m_treeView->collapse(parent);
1040 }