"Fossies" - the Fresh Open Source Software Archive

Member "labplot-2.8.2/src/commonfrontend/ProjectExplorer.cpp" (24 Feb 2021, 37214 Bytes) of package /linux/privat/labplot-2.8.2.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "ProjectExplorer.cpp" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 2.8.1_vs_2.8.2.

    1 
    2 /***************************************************************************
    3     File                 : ProjectExplorer.cpp
    4     Project              : LabPlot
    5     Description          : A tree view for displaying and editing an AspectTreeModel.
    6     --------------------------------------------------------------------
    7     Copyright            : (C) 2007-2008 by Tilman Benkert (thzs@gmx.net)
    8     Copyright            : (C) 2010-2018 Alexander Semke (alexander.semke@web.de)
    9 
   10  ***************************************************************************/
   11 
   12 /***************************************************************************
   13  *                                                                         *
   14  *  This program is free software; you can redistribute it and/or modify   *
   15  *  it under the terms of the GNU General Public License as published by   *
   16  *  the Free Software Foundation; either version 2 of the License, or      *
   17  *  (at your option) any later version.                                    *
   18  *                                                                         *
   19  *  This program is distributed in the hope that it will be useful,        *
   20  *  but WITHOUT ANY WARRANTY; without even the implied warranty of         *
   21  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the          *
   22  *  GNU General Public License for more details.                           *
   23  *                                                                         *
   24  *   You should have received a copy of the GNU General Public License     *
   25  *   along with this program; if not, write to the Free Software           *
   26  *   Foundation, Inc., 51 Franklin Street, Fifth Floor,                    *
   27  *   Boston, MA  02110-1301  USA                                           *
   28  *                                                                         *
   29  ***************************************************************************/
   30 #include "ProjectExplorer.h"
   31 #include "backend/core/AspectTreeModel.h"
   32 #include "backend/core/AbstractPart.h"
   33 #include "backend/core/Project.h"
   34 #include "backend/lib/XmlStreamReader.h"
   35 #include "backend/worksheet/plots/cartesian/CartesianPlot.h"
   36 #include "commonfrontend/core/PartMdiView.h"
   37 
   38 #include <QContextMenuEvent>
   39 #include <QDrag>
   40 #include <QHeaderView>
   41 #include <QLabel>
   42 #include <QLineEdit>
   43 #include <QMenu>
   44 #include <QMimeData>
   45 #include <QPushButton>
   46 #include <QToolButton>
   47 #include <QTreeView>
   48 #include <QVBoxLayout>
   49 
   50 #include <KConfig>
   51 #include <KConfigGroup>
   52 #include <KSharedConfig>
   53 #include <KLocalizedString>
   54 #include <KMessageBox>
   55 
   56 /*!
   57   \class ProjectExplorer
   58   \brief A tree view for displaying and editing an AspectTreeModel.
   59 
   60   In addition to the functionality of QTreeView, ProjectExplorer allows
   61   the usage of the context menus provided by AspectTreeModel
   62   and propagates the item selection in the view to the model.
   63   Furthermore, features for searching and filtering in the model are provided.
   64 
   65   \ingroup commonfrontend
   66 */
   67 
   68 ProjectExplorer::ProjectExplorer(QWidget* parent) :
   69     m_treeView(new QTreeView(parent)),
   70     m_frameFilter(new QFrame(this)) {
   71 
   72     auto* layout = new QVBoxLayout(this);
   73     layout->setSpacing(0);
   74     layout->setContentsMargins(0, 0, 0, 0);
   75 
   76     auto* layoutFilter = new QHBoxLayout(m_frameFilter);
   77     layoutFilter->setSpacing(0);
   78     layoutFilter->setContentsMargins(0, 0, 0, 0);
   79 
   80     m_leFilter = new QLineEdit(m_frameFilter);
   81     m_leFilter->setClearButtonEnabled(true);
   82     m_leFilter->setPlaceholderText(i18n("Search/Filter"));
   83     layoutFilter->addWidget(m_leFilter);
   84 
   85     bFilterOptions = new QToolButton(m_frameFilter);
   86     bFilterOptions->setIcon(QIcon::fromTheme("configure"));
   87     bFilterOptions->setCheckable(true);
   88     layoutFilter->addWidget(bFilterOptions);
   89 
   90     layout->addWidget(m_frameFilter);
   91 
   92     m_treeView->setAnimated(true);
   93     m_treeView->setAlternatingRowColors(true);
   94     m_treeView->setSelectionBehavior(QAbstractItemView::SelectRows);
   95     m_treeView->setSelectionMode(QAbstractItemView::ExtendedSelection);
   96     m_treeView->setUniformRowHeights(true);
   97     m_treeView->viewport()->installEventFilter(this);
   98     m_treeView->header()->setStretchLastSection(true);
   99     m_treeView->header()->installEventFilter(this);
  100     m_treeView->setDragEnabled(true);
  101     m_treeView->setAcceptDrops(true);
  102     m_treeView->setDropIndicatorShown(true);
  103     m_treeView->setDragDropMode(QAbstractItemView::InternalMove);
  104 
  105     layout->addWidget(m_treeView);
  106 
  107     this->createActions();
  108 
  109     connect(m_leFilter, &QLineEdit::textChanged, this, &ProjectExplorer::filterTextChanged);
  110     connect(bFilterOptions, &QPushButton::toggled, this, &ProjectExplorer::toggleFilterOptionsMenu);
  111 }
  112 
  113 ProjectExplorer::~ProjectExplorer() {
  114     // save the visible columns
  115     QString status;
  116     for (int i = 0; i < list_showColumnActions.size(); ++i) {
  117         if (list_showColumnActions.at(i)->isChecked()) {
  118             if (!status.isEmpty())
  119                 status += QLatin1Char(' ');
  120             status += QString::number(i);
  121         }
  122     }
  123     KConfigGroup group(KSharedConfig::openConfig(), QLatin1String("ProjectExplorer"));
  124     group.writeEntry("VisibleColumns", status);
  125 }
  126 
  127 void ProjectExplorer::createActions() {
  128     caseSensitiveAction = new QAction(i18n("Case Sensitive"), this);
  129     caseSensitiveAction->setCheckable(true);
  130     caseSensitiveAction->setChecked(false);
  131     connect(caseSensitiveAction, &QAction::triggered, this, &ProjectExplorer::toggleFilterCaseSensitivity);
  132 
  133     matchCompleteWordAction = new QAction(i18n("Match Complete Word"), this);
  134     matchCompleteWordAction->setCheckable(true);
  135     matchCompleteWordAction->setChecked(false);
  136     connect(matchCompleteWordAction, &QAction::triggered, this, &ProjectExplorer::toggleFilterMatchCompleteWord);
  137 
  138     expandTreeAction = new QAction(QIcon::fromTheme(QLatin1String("expand-all")), i18n("Expand All"), this);
  139     connect(expandTreeAction, &QAction::triggered, m_treeView, &QTreeView::expandAll);
  140 
  141     expandSelectedTreeAction = new QAction(QIcon::fromTheme(QLatin1String("expand-all")), i18n("Expand Selected"), this);
  142     connect(expandSelectedTreeAction, &QAction::triggered, this, &ProjectExplorer::expandSelected);
  143 
  144     collapseTreeAction = new QAction(QIcon::fromTheme(QLatin1String("collapse-all")), i18n("Collapse All"), this);
  145     connect(collapseTreeAction, &QAction::triggered, m_treeView, &QTreeView::collapseAll);
  146 
  147     collapseSelectedTreeAction = new QAction(QIcon::fromTheme(QLatin1String("collapse-all")), i18n("Collapse Selected"), this);
  148     connect(collapseSelectedTreeAction, &QAction::triggered, this, &ProjectExplorer::collapseSelected);
  149 
  150     deleteSelectedTreeAction = new QAction(QIcon::fromTheme("edit-delete"), i18n("Delete Selected"), this);
  151     connect(deleteSelectedTreeAction, &QAction::triggered, this, &ProjectExplorer::deleteSelected);
  152 
  153     toggleFilterAction = new QAction(QIcon::fromTheme(QLatin1String("view-filter")), i18n("Hide Search/Filter Options"), this);
  154     connect(toggleFilterAction, &QAction::triggered, this, &ProjectExplorer::toggleFilterWidgets);
  155 
  156     showAllColumnsAction = new QAction(i18n("Show All"),this);
  157     showAllColumnsAction->setCheckable(true);
  158     showAllColumnsAction->setChecked(true);
  159     showAllColumnsAction->setEnabled(false);
  160     connect(showAllColumnsAction, &QAction::triggered, this, &ProjectExplorer::showAllColumns);
  161 }
  162 
  163 /*!
  164   shows the context menu in the tree. In addition to the context menu of the currently selected aspect,
  165   treeview specific options are added.
  166 */
  167 void ProjectExplorer::contextMenuEvent(QContextMenuEvent *event) {
  168     if (!m_treeView->model())
  169         return;
  170 
  171     const QModelIndex& index = m_treeView->indexAt(m_treeView->viewport()->mapFrom(this, event->pos()));
  172     if (!index.isValid())
  173         m_treeView->clearSelection();
  174 
  175     const QModelIndexList& items = m_treeView->selectionModel()->selectedIndexes();
  176     QMenu* menu = nullptr;
  177     if (items.size()/4 == 1) {
  178         auto* aspect = static_cast<AbstractAspect*>(index.internalPointer());
  179         menu = aspect->createContextMenu();
  180     } else {
  181         menu = new QMenu();
  182 
  183         QMenu* projectMenu = m_project->createContextMenu();
  184         projectMenu->setTitle(m_project->name());
  185         menu->addMenu(projectMenu);
  186         menu->addSeparator();
  187 
  188         if (items.size()/4 > 1) {
  189             menu->addAction(expandSelectedTreeAction);
  190             menu->addAction(collapseSelectedTreeAction);
  191             menu->addSeparator();
  192             menu->addAction(deleteSelectedTreeAction);
  193             menu->addSeparator();
  194         } else {
  195             menu->addAction(expandTreeAction);
  196             menu->addAction(collapseTreeAction);
  197             menu->addSeparator();
  198             menu->addAction(toggleFilterAction);
  199 
  200             //Menu for showing/hiding the columns in the tree view
  201             QMenu* columnsMenu = menu->addMenu(i18n("Show/Hide columns"));
  202             columnsMenu->addAction(showAllColumnsAction);
  203             columnsMenu->addSeparator();
  204             for (auto* action : list_showColumnActions)
  205                 columnsMenu->addAction(action);
  206 
  207             //TODO
  208             //Menu for showing/hiding the top-level aspects (Worksheet, Spreadhsheet, etc) in the tree view
  209             // QMenu* objectsMenu = menu->addMenu(i18n("Show/Hide objects"));
  210         }
  211     }
  212 
  213     if (menu)
  214         menu->exec(event->globalPos());
  215 
  216     delete menu;
  217 }
  218 
  219 void ProjectExplorer::setCurrentAspect(const AbstractAspect* aspect) {
  220     //HACK: when doing redo/undo in MainWin and an object is being deleted,
  221     //we don't want to jump to another object in the project explorer.
  222     //we reuse the aspectAddedSignalSuppressed also for the deletion of aspects.
  223     if (m_project->aspectAddedSignalSuppressed())
  224         return;
  225 
  226     const AspectTreeModel* tree_model = qobject_cast<AspectTreeModel*>(m_treeView->model());
  227     if (tree_model)
  228         m_treeView->setCurrentIndex(tree_model->modelIndexOfAspect(aspect));
  229 }
  230 
  231 /*!
  232   Sets the \c model for the tree view to present.
  233 */
  234 void ProjectExplorer::setModel(AspectTreeModel* treeModel) {
  235     m_treeView->setModel(treeModel);
  236 
  237     connect(treeModel, &AspectTreeModel::renameRequested,
  238             m_treeView,  static_cast<void (QAbstractItemView::*)(const QModelIndex&)>(&QAbstractItemView::edit));
  239     connect(treeModel, &AspectTreeModel::indexSelected, this, &ProjectExplorer::selectIndex);
  240     connect(treeModel, &AspectTreeModel::indexDeselected, this, &ProjectExplorer::deselectIndex);
  241     connect(treeModel, &AspectTreeModel::hiddenAspectSelected, this, &ProjectExplorer::hiddenAspectSelected);
  242 
  243     connect(m_treeView->selectionModel(), &QItemSelectionModel::currentChanged, this, &ProjectExplorer::currentChanged);
  244     connect(m_treeView->selectionModel(), &QItemSelectionModel::selectionChanged, this, &ProjectExplorer::selectionChanged);
  245 
  246     //create action for showing/hiding the columns in the tree.
  247     //this is done here since the number of columns is  not available in createActions() yet.
  248     if (list_showColumnActions.isEmpty()) {
  249         //read the status of the column actions if available
  250         KConfigGroup group(KSharedConfig::openConfig(), QLatin1String("ProjectExplorer"));
  251         const QString& status = group.readEntry(QLatin1String("VisibleColumns"), QString());
  252         QVector<int> checkedActions;
  253         if (!status.isEmpty()) {
  254             QStringList strList = status.split(QLatin1Char(' '));
  255             for (int i = 0; i < strList.size(); ++i)
  256                 checkedActions << strList.at(i).toInt();
  257         }
  258 
  259         if (checkedActions.size() != m_treeView->model()->columnCount()) {
  260             showAllColumnsAction->setEnabled(true);
  261             showAllColumnsAction->setChecked(false);
  262         } else {
  263             showAllColumnsAction->setEnabled(false);
  264             showAllColumnsAction->setChecked(true);
  265         }
  266 
  267         //create an action for every available column in the model
  268         for (int i = 0; i < m_treeView->model()->columnCount(); i++) {
  269             QAction* showColumnAction =  new QAction(treeModel->headerData(i, Qt::Horizontal).toString(), this);
  270             showColumnAction->setCheckable(true);
  271 
  272             //restore the status, if available
  273             if (!checkedActions.isEmpty()) {
  274                 if (checkedActions.indexOf(i) != -1)
  275                     showColumnAction->setChecked(true);
  276                 else
  277                     m_treeView->hideColumn(i);
  278             } else
  279                 showColumnAction->setChecked(true);
  280 
  281             list_showColumnActions.append(showColumnAction);
  282 
  283             connect(showColumnAction, &QAction::triggered,
  284                     this, [=] { ProjectExplorer::toggleColumn(i); });
  285         }
  286     } else {
  287         for (int i = 0; i < list_showColumnActions.size(); ++i) {
  288             if (!list_showColumnActions.at(i)->isChecked())
  289                 m_treeView->hideColumn(i);
  290         }
  291     }
  292 }
  293 
  294 void ProjectExplorer::setProject(Project* project) {
  295     connect(project, &Project::aspectAdded, this, &ProjectExplorer::aspectAdded);
  296     connect(project, &Project::requestSaveState, this, &ProjectExplorer::save);
  297     connect(project, &Project::requestLoadState, this, &ProjectExplorer::load);
  298     connect(project, &Project::requestNavigateTo, this, &ProjectExplorer::navigateTo);
  299     connect(project, &Project::loaded, this, &ProjectExplorer::projectLoaded);
  300     m_project = project;
  301 
  302     //for newly created projects, resize the header to fit the size of the header section names.
  303     //for projects loaded from a file, this function will be called laterto fit the sizes
  304     //of the content once the project is loaded
  305     resizeHeader();
  306 }
  307 
  308 QModelIndex ProjectExplorer::currentIndex() const {
  309     return m_treeView->currentIndex();
  310 }
  311 
  312 void ProjectExplorer::search() {
  313     m_leFilter->setFocus();
  314 }
  315 
  316 /*!
  317     handles the contextmenu-event of the horizontal header in the tree view.
  318     Provides a menu for selective showing and hiding of columns.
  319 */
  320 bool ProjectExplorer::eventFilter(QObject* obj, QEvent* event) {
  321     if (obj == m_treeView->header() && event->type() == QEvent::ContextMenu) {
  322         //Menu for showing/hiding the columns in the tree view
  323         QMenu* columnsMenu = new QMenu(m_treeView->header());
  324         columnsMenu->addSection(i18n("Columns"));
  325         columnsMenu->addAction(showAllColumnsAction);
  326         columnsMenu->addSeparator();
  327         for (auto* action : list_showColumnActions)
  328             columnsMenu->addAction(action);
  329 
  330         auto* e = static_cast<QContextMenuEvent*>(event);
  331         columnsMenu->exec(e->globalPos());
  332         delete columnsMenu;
  333 
  334         return true;
  335     } else if (obj == m_treeView->viewport()) {
  336         if (event->type() == QEvent::MouseButtonPress) {
  337             auto* e = static_cast<QMouseEvent*>(event);
  338             if (e->button() == Qt::LeftButton) {
  339                 QModelIndex index = m_treeView->indexAt(e->pos());
  340                 if (!index.isValid())
  341                     return false;
  342 
  343                 auto* aspect = static_cast<AbstractAspect*>(index.internalPointer());
  344                 if (aspect->isDraggable()) {
  345                     m_dragStartPos = e->globalPos();
  346                     m_dragStarted = false;
  347                 }
  348             }
  349         } else if (event->type() == QEvent::MouseMove) {
  350             auto* e = static_cast<QMouseEvent*>(event);
  351             if ( !m_dragStarted && m_treeView->selectionModel()->selectedIndexes().size() > 0
  352                 && (e->globalPos() - m_dragStartPos).manhattanLength() >= QApplication::startDragDistance()) {
  353 
  354                 m_dragStarted = true;
  355                 auto* drag = new QDrag(this);
  356                 auto* mimeData = new QMimeData;
  357 
  358                 //determine the selected objects and serialize the pointers to QMimeData
  359                 QVector<quintptr> vec;
  360                 QModelIndexList items = m_treeView->selectionModel()->selectedIndexes();
  361                 //there are four model indices in each row -> divide by 4 to obtain the number of selected rows (=aspects)
  362                 for (int i = 0; i < items.size()/4; ++i) {
  363                     const QModelIndex& index = items.at(i*4);
  364                     auto* aspect = static_cast<AbstractAspect*>(index.internalPointer());
  365                     vec << (quintptr)aspect;
  366                 }
  367 
  368                 QByteArray data;
  369                 QDataStream stream(&data, QIODevice::WriteOnly);
  370                 stream << (quintptr)m_project; //serialize the project pointer first, will be used as the unique identifier
  371                 stream << vec;
  372 
  373                 mimeData->setData("labplot-dnd", data);
  374                 drag->setMimeData(mimeData);
  375                 drag->exec();
  376             }
  377         } else if (event->type() == QEvent::DragEnter) {
  378             //ignore events not related to internal drags of columns etc., e.g. dropping of external files onto LabPlot
  379             auto* dragEnterEvent = static_cast<QDragEnterEvent*>(event);
  380             const QMimeData* mimeData = dragEnterEvent->mimeData();
  381             if (!mimeData) {
  382                 event->ignore();
  383                 return false;
  384             }
  385 
  386             if (mimeData->formats().at(0) != QLatin1String("labplot-dnd")) {
  387                 event->ignore();
  388                 return false;
  389             } else {
  390                 //drad&drop between the different project windows is not supported yet.
  391                 //check whether we're dragging inside of the same project.
  392                 QByteArray data = mimeData->data(QLatin1String("labplot-dnd"));
  393                 QDataStream stream(&data, QIODevice::ReadOnly);
  394                 quintptr ptr = 0;
  395                 stream >> ptr;
  396                 auto* project = reinterpret_cast<Project*>(ptr);
  397                 if (project != m_project) {
  398                     event->ignore();
  399                     return false;
  400                 }
  401             }
  402 
  403             event->setAccepted(true);
  404         } else if (event->type() == QEvent::DragMove) {
  405             auto* dragMoveEvent = static_cast<QDragEnterEvent*>(event);
  406             const QMimeData* mimeData = dragMoveEvent->mimeData();
  407             QVector<quintptr> vec = m_project->droppedAspects(mimeData);
  408 
  409             AbstractAspect* sourceAspect{nullptr};
  410             if (!vec.isEmpty())
  411                 sourceAspect = reinterpret_cast<AbstractAspect*>(vec.at(0));
  412 
  413             if (!sourceAspect)
  414                 return false;
  415 
  416             //determine the aspect under the cursor
  417             QModelIndex index = m_treeView->indexAt(dragMoveEvent->pos());
  418             if (!index.isValid())
  419                 return false;
  420 
  421             //accept only the events when the aspect being dragged is dropable onto the aspect under the cursor
  422             //and the aspect under the cursor is not already the parent of the dragged aspect
  423             auto* destinationAspect = static_cast<AbstractAspect*>(index.internalPointer());
  424             bool accept = sourceAspect->dropableOn().indexOf(destinationAspect->type()) != -1
  425                         && sourceAspect->parentAspect() != destinationAspect;
  426             event->setAccepted(accept);
  427         } else if (event->type() == QEvent::Drop) {
  428             auto* dropEvent = static_cast<QDropEvent*>(event);
  429             const QMimeData* mimeData = dropEvent->mimeData();
  430             if (!mimeData)
  431                 return false;
  432 
  433             QVector<quintptr> vec = m_project->droppedAspects(mimeData);
  434             if (vec.isEmpty())
  435                 return false;
  436 
  437             QModelIndex index = m_treeView->indexAt(dropEvent->pos());
  438             if (!index.isValid())
  439                 return false;
  440 
  441             //process the droped objects
  442             auto* aspect = static_cast<AbstractAspect*>(index.internalPointer());
  443             aspect->processDropEvent(vec);
  444         }
  445     }
  446 
  447     return QObject::eventFilter(obj, event);
  448 }
  449 
  450 void ProjectExplorer::keyPressEvent(QKeyEvent* event) {
  451     if (event->matches(QKeySequence::Delete))
  452         deleteSelected();
  453     else if (event->key() == 32) {
  454         auto* aspect = static_cast<AbstractAspect*>(m_treeView->currentIndex().internalPointer());
  455         auto* we = dynamic_cast<WorksheetElement*>(aspect);
  456         if (we)
  457             we->setVisible(!we->isVisible());
  458     }
  459 }
  460 
  461 //##############################################################################
  462 //#################################  SLOTS  ####################################
  463 //##############################################################################
  464 /*!
  465  * called after the project was loaded.
  466  * resize the header of the view to adjust to the content
  467  * and re-select the currently selected object to show
  468  * its final properties in the dock widget after in Project::load() all
  469  * pointers were restored, relative paths replaced by absolute, etc.
  470  */
  471 void ProjectExplorer::projectLoaded() {
  472     resizeHeader();
  473     selectionChanged(m_treeView->selectionModel()->selection(), QItemSelection());
  474 }
  475 
  476 /*!
  477   expand the aspect \c aspect (the tree index corresponding to it) in the tree view
  478   and makes it visible and selected. Called when a new aspect is added to the project.
  479  */
  480 void ProjectExplorer::aspectAdded(const AbstractAspect* aspect) {
  481     if (m_project->isLoading() ||m_project->aspectAddedSignalSuppressed())
  482         return;
  483 
  484     //don't do anything if hidden aspects were added
  485     if (aspect->hidden())
  486         return;
  487 
  488 
  489     //don't do anything for newly added data spreadsheets of data picker curves
  490     if (aspect->inherits(AspectType::Spreadsheet) &&
  491         aspect->parentAspect()->inherits(AspectType::DatapickerCurve))
  492         return;
  493 
  494     const AspectTreeModel* tree_model = qobject_cast<AspectTreeModel*>(m_treeView->model());
  495     const QModelIndex& index =  tree_model->modelIndexOfAspect(aspect);
  496 
  497     //expand and make the aspect visible
  498     m_treeView->setExpanded(index, true);
  499 
  500     // newly added columns are only expanded but not selected, return here
  501     if (aspect->inherits(AspectType::Column)) {
  502         m_treeView->setExpanded(tree_model->modelIndexOfAspect(aspect->parentAspect()), true);
  503         return;
  504     }
  505 
  506     m_treeView->scrollTo(index);
  507     m_treeView->setCurrentIndex(index);
  508     m_treeView->header()->resizeSections(QHeaderView::ResizeToContents);
  509     m_treeView->header()->resizeSection(0, m_treeView->header()->sectionSize(0)*1.2);
  510 }
  511 
  512 void ProjectExplorer::navigateTo(const QString& path) {
  513     const AspectTreeModel* tree_model = qobject_cast<AspectTreeModel*>(m_treeView->model());
  514     if (tree_model) {
  515         const QModelIndex& index = tree_model->modelIndexOfAspect(path);
  516         m_treeView->scrollTo(index);
  517         m_treeView->setCurrentIndex(index);
  518         auto* aspect = static_cast<AbstractAspect*>(index.internalPointer());
  519         aspect->setSelected(true);
  520     }
  521 }
  522 
  523 void ProjectExplorer::currentChanged(const QModelIndex & current, const QModelIndex & previous) {
  524     if (m_project->isLoading())
  525         return;
  526 
  527     Q_UNUSED(previous);
  528     emit currentAspectChanged(static_cast<AbstractAspect*>(current.internalPointer()));
  529 }
  530 
  531 void ProjectExplorer::toggleColumn(int index) {
  532     //determine the total number of checked column actions
  533     int checked = 0;
  534     for (const auto* action : list_showColumnActions) {
  535         if (action->isChecked())
  536             checked++;
  537     }
  538 
  539     if (list_showColumnActions.at(index)->isChecked()) {
  540         m_treeView->showColumn(index);
  541         m_treeView->header()->resizeSection(0,0 );
  542         m_treeView->header()->resizeSections(QHeaderView::ResizeToContents);
  543 
  544         for (auto* action : list_showColumnActions)
  545             action->setEnabled(true);
  546 
  547         //deactivate the "show all column"-action, if all actions are checked
  548         if ( checked == list_showColumnActions.size() ) {
  549             showAllColumnsAction->setEnabled(false);
  550             showAllColumnsAction->setChecked(true);
  551         }
  552     } else {
  553         m_treeView->hideColumn(index);
  554         showAllColumnsAction->setEnabled(true);
  555         showAllColumnsAction->setChecked(false);
  556 
  557         //if there is only one checked column-action, deactivated it.
  558         //It should't be possible to hide all columns
  559         if ( checked == 1 ) {
  560             int i = 0;
  561             while ( !list_showColumnActions.at(i)->isChecked() )
  562                 i++;
  563 
  564             list_showColumnActions.at(i)->setEnabled(false);
  565         }
  566     }
  567 }
  568 
  569 void ProjectExplorer::showAllColumns() {
  570     for (int i = 0; i < m_treeView->model()->columnCount(); i++) {
  571         m_treeView->showColumn(i);
  572         m_treeView->header()->resizeSection(0,0 );
  573         m_treeView->header()->resizeSections(QHeaderView::ResizeToContents);
  574     }
  575     showAllColumnsAction->setEnabled(false);
  576 
  577     for (auto* action : list_showColumnActions) {
  578         action->setEnabled(true);
  579         action->setChecked(true);
  580     }
  581 }
  582 
  583 /*!
  584   shows/hides the frame with the search/filter widgets
  585 */
  586 void ProjectExplorer::toggleFilterWidgets() {
  587     if (m_frameFilter->isVisible()) {
  588         m_frameFilter->hide();
  589         toggleFilterAction->setText(i18n("Show Search/Filter Options"));
  590     } else {
  591         m_frameFilter->show();
  592         toggleFilterAction->setText(i18n("Hide Search/Filter Options"));
  593     }
  594 }
  595 
  596 /*!
  597   toggles the menu for the filter/search options
  598 */
  599 void ProjectExplorer::toggleFilterOptionsMenu(bool checked) {
  600     if (checked) {
  601         QMenu menu;
  602         menu.addAction(caseSensitiveAction);
  603         menu.addAction(matchCompleteWordAction);
  604         connect(&menu, &QMenu::aboutToHide, bFilterOptions, &QPushButton::toggle);
  605         menu.exec(bFilterOptions->mapToGlobal(QPoint(0,bFilterOptions->height())));
  606     }
  607 }
  608 
  609 void ProjectExplorer::resizeHeader() {
  610     m_treeView->header()->resizeSections(QHeaderView::ResizeToContents);
  611     m_treeView->header()->resizeSection(0, m_treeView->header()->sectionSize(0)*1.2); //make the column "Name" somewhat bigger
  612 }
  613 
  614 /*!
  615   called when the filter/search text was changend.
  616 */
  617 void ProjectExplorer::filterTextChanged(const QString& text) {
  618     QModelIndex root = m_treeView->model()->index(0,0);
  619     filter(root, text);
  620 }
  621 
  622 bool ProjectExplorer::filter(const QModelIndex& index, const QString& text) {
  623     Qt::CaseSensitivity sensitivity = caseSensitiveAction->isChecked() ? Qt::CaseSensitive : Qt::CaseInsensitive;
  624     bool matchCompleteWord = matchCompleteWordAction->isChecked();
  625 
  626     bool childVisible = false;
  627     const int rows = index.model()->rowCount(index);
  628     for (int i = 0; i < rows; i++) {
  629         QModelIndex child = index.model()->index(i, 0, index);
  630         auto* aspect =  static_cast<AbstractAspect*>(child.internalPointer());
  631         bool visible;
  632         if (text.isEmpty())
  633             visible = true;
  634         else if (matchCompleteWord)
  635             visible = aspect->name().startsWith(text, sensitivity);
  636         else
  637             visible = aspect->name().contains(text, sensitivity);
  638 
  639         if (visible) {
  640             //current item is visible -> make all its children visible without applying the filter
  641             for (int j = 0; j < child.model()->rowCount(child); ++j) {
  642                 m_treeView->setRowHidden(j, child, false);
  643                 if (text.isEmpty())
  644                     filter(child, text);
  645             }
  646 
  647             childVisible = true;
  648         } else {
  649             //check children items. if one of the children is visible, make the parent (current) item visible too.
  650             visible = filter(child, text);
  651             if (visible)
  652                 childVisible = true;
  653         }
  654 
  655         m_treeView->setRowHidden(i, index, !visible);
  656     }
  657 
  658     return childVisible;
  659 }
  660 
  661 void ProjectExplorer::toggleFilterCaseSensitivity() {
  662     filterTextChanged(m_leFilter->text());
  663 }
  664 
  665 void ProjectExplorer::toggleFilterMatchCompleteWord() {
  666     filterTextChanged(m_leFilter->text());
  667 }
  668 
  669 void ProjectExplorer::selectIndex(const QModelIndex&  index) {
  670     if (m_project->isLoading())
  671         return;
  672 
  673     if ( !m_treeView->selectionModel()->isSelected(index) ) {
  674         m_changeSelectionFromView = true;
  675         m_treeView->selectionModel()->select(index, QItemSelectionModel::Select | QItemSelectionModel::Rows);
  676         m_treeView->setExpanded(index, true);
  677         m_treeView->scrollTo(index);
  678     }
  679 }
  680 
  681 void ProjectExplorer::deselectIndex(const QModelIndex & index) {
  682     if (m_project->isLoading())
  683         return;
  684 
  685     if ( m_treeView->selectionModel()->isSelected(index) ) {
  686         m_changeSelectionFromView = true;
  687         m_treeView->selectionModel()->select(index, QItemSelectionModel::Deselect | QItemSelectionModel::Rows);
  688     }
  689 }
  690 
  691 void ProjectExplorer::selectionChanged(const QItemSelection &selected, const QItemSelection &deselected) {
  692     QModelIndex index;
  693     QModelIndexList items;
  694     AbstractAspect* aspect = nullptr;
  695 
  696     //there are four model indices in each row
  697     //-> divide by 4 to obtain the number of selected rows (=aspects)
  698     items = selected.indexes();
  699     for (int i = 0; i < items.size()/4; ++i) {
  700         index = items.at(i*4);
  701         aspect = static_cast<AbstractAspect*>(index.internalPointer());
  702         aspect->setSelected(true);
  703     }
  704 
  705     items = deselected.indexes();
  706     for (int i = 0; i < items.size()/4; ++i) {
  707         index = items.at(i*4);
  708         aspect = static_cast<AbstractAspect*>(index.internalPointer());
  709         aspect->setSelected(false);
  710     }
  711 
  712     items = m_treeView->selectionModel()->selectedRows();
  713     QList<AbstractAspect*> selectedAspects;
  714     for (const QModelIndex& index : items) {
  715         aspect = static_cast<AbstractAspect*>(index.internalPointer());
  716         selectedAspects<<aspect;
  717     }
  718 
  719     emit selectedAspectsChanged(selectedAspects);
  720 
  721     //emitting the signal above is done to show the properties widgets for the selected aspect(s).
  722     //with this the project explorer looses the focus and don't react on the key events like DEL key press, etc.
  723     //If we explicitely select an item in the project explorer (not via a selection in the view), we want to keep the focus here.
  724     //TODO: after the focus is set again we react on DEL in the event filter, but navigation with the arrow keys in the table
  725     //is still not possible. Looks like we need to set the selection again...
  726     if (!m_changeSelectionFromView)
  727         setFocus();
  728     else
  729         m_changeSelectionFromView = false;
  730 }
  731 
  732 /*!
  733  * Used to udpate the cursor Dock
  734  */
  735 void ProjectExplorer::updateSelectedAspects() {
  736     QModelIndexList items = m_treeView->selectionModel()->selectedRows();
  737     QList<AbstractAspect*> selectedAspects;
  738     for (const QModelIndex& index : items)
  739         selectedAspects << static_cast<AbstractAspect*>(index.internalPointer());
  740 
  741     emit selectedAspectsChanged(selectedAspects);
  742 }
  743 
  744 void ProjectExplorer::expandSelected() {
  745     const QModelIndexList items = m_treeView->selectionModel()->selectedIndexes();
  746     for (const auto& index : items)
  747         m_treeView->setExpanded(index, true);
  748 }
  749 
  750 void ProjectExplorer::collapseSelected() {
  751     const QModelIndexList items = m_treeView->selectionModel()->selectedIndexes();
  752     for (const auto& index : items)
  753         m_treeView->setExpanded(index, false);
  754 }
  755 
  756 void ProjectExplorer::deleteSelected() {
  757     const QModelIndexList items = m_treeView->selectionModel()->selectedIndexes();
  758     if (!items.size())
  759         return;
  760 
  761 
  762     //determine all selected aspect
  763     QVector<AbstractAspect*> aspects;
  764     for (int i = 0; i < items.size()/4; ++i) {
  765         const QModelIndex& index = items.at(i*4);
  766         auto* aspect = static_cast<AbstractAspect*>(index.internalPointer());
  767         aspects << aspect;
  768     }
  769 
  770     QString msg;
  771     if (aspects.size() > 1)
  772         msg = i18n("Do you really want to delete the selected %1 objects?", aspects.size());
  773     else
  774          msg = i18n("Do you really want to delete %1?", aspects.constFirst()->name());
  775 
  776     int rc = KMessageBox::warningYesNo(this, msg, i18np("Delete selected object", "Delete selected objects", aspects.size()));
  777 
  778     if (rc == KMessageBox::No)
  779         return;
  780 
  781     m_project->beginMacro(i18np("Project Explorer: delete %1 selected object", "Project Explorer: delete %1 selected objects", items.size()/4));
  782 
  783     //determine aspects to be deleted:
  784     //it's enough to delete parent items in the selection only,
  785     //skip all selected aspects where one of the parents is also in the selection
  786     //for example selected columns of a selected spreadsheet, etc.
  787     QVector<AbstractAspect*> aspectsToDelete;
  788     for (auto* aspect : aspects) {
  789         auto* parent = aspect->parentAspect();
  790         int parentSelected = false;
  791         while (parent) {
  792             if (aspects.indexOf(parent) != -1) {
  793                 parentSelected = true;
  794                 break;
  795             }
  796 
  797             parent = parent->parentAspect();
  798         }
  799 
  800         if (!parentSelected)
  801             aspectsToDelete << aspect; //parent is not in the selection
  802     }
  803 
  804     for (auto* aspect : aspectsToDelete)
  805         aspect->remove();
  806 
  807     m_project->endMacro();
  808 }
  809 
  810 //##############################################################################
  811 //##################  Serialization/Deserialization  ###########################
  812 //##############################################################################
  813 struct ViewState {
  814     Qt::WindowStates state;
  815     QRect geometry;
  816 };
  817 
  818 /**
  819  * \brief Save the current state of the tree view
  820  * (expanded items and the currently selected item) as XML
  821  */
  822 void ProjectExplorer::save(QXmlStreamWriter* writer) const {
  823     auto* model = qobject_cast<AspectTreeModel*>(m_treeView->model());
  824     QList<int> selected;
  825     QList<int> expanded;
  826     QList<int> withView;
  827     QVector<ViewState> viewStates;
  828 
  829     int currentRow = -1; //row corresponding to the current index in the tree view, -1 for the root element (=project)
  830     QModelIndexList selectedRows = m_treeView->selectionModel()->selectedRows();
  831 
  832     //check whether the project node itself is expanded
  833     if (m_treeView->isExpanded(m_treeView->model()->index(0,0)))
  834         expanded.push_back(-1);
  835 
  836     int row = 0;
  837     for (const auto* aspect : m_project->children(AspectType::AbstractAspect, AbstractAspect::ChildIndexFlag::Recursive)) {
  838         const QModelIndex& index = model->modelIndexOfAspect(aspect);
  839 
  840         const auto* part = dynamic_cast<const AbstractPart*>(aspect);
  841         if (part && part->hasMdiSubWindow()) {
  842             withView.push_back(row);
  843             ViewState s = {part->view()->windowState(), part->view()->geometry()};
  844             viewStates.push_back(s);
  845         }
  846 
  847         if (model->rowCount(index)>0 && m_treeView->isExpanded(index))
  848             expanded.push_back(row);
  849 
  850         if (selectedRows.indexOf(index) != -1)
  851             selected.push_back(row);
  852 
  853         if (index == m_treeView->currentIndex())
  854             currentRow = row;
  855 
  856         row++;
  857     }
  858 
  859     writer->writeStartElement("state");
  860 
  861     writer->writeStartElement("expanded");
  862     for (const auto e : expanded)
  863         writer->writeTextElement("row", QString::number(e));
  864     writer->writeEndElement();
  865 
  866     writer->writeStartElement("selected");
  867     for (const auto s : selected)
  868         writer->writeTextElement("row", QString::number(s));
  869     writer->writeEndElement();
  870 
  871     writer->writeStartElement("view");
  872     for (int i = 0; i < withView.size(); ++i) {
  873         writer->writeStartElement("row");
  874         const ViewState& s = viewStates.at(i);
  875         writer->writeAttribute( "state", QString::number(s.state) );
  876         writer->writeAttribute( "x", QString::number(s.geometry.x()) );
  877         writer->writeAttribute( "y", QString::number(s.geometry.y()) );
  878         writer->writeAttribute( "width", QString::number(s.geometry.width()) );
  879         writer->writeAttribute( "height", QString::number(s.geometry.height()) );
  880         writer->writeCharacters(QString::number(withView.at(i)));
  881         writer->writeEndElement();
  882     }
  883     writer->writeEndElement();
  884 
  885     writer->writeStartElement("current");
  886     writer->writeTextElement("row", QString::number(currentRow));
  887     writer->writeEndElement();
  888 
  889     writer->writeEndElement();
  890 }
  891 
  892 /**
  893  * \brief Load from XML
  894  */
  895 bool ProjectExplorer::load(XmlStreamReader* reader) {
  896     const AspectTreeModel* model = qobject_cast<AspectTreeModel*>(m_treeView->model());
  897     const auto aspects = m_project->children(AspectType::AbstractAspect, AbstractAspect::ChildIndexFlag::Recursive);
  898 
  899     bool expandedItem = false;
  900     bool selectedItem = false;
  901     bool viewItem = false;
  902     (void)viewItem; // because of a strange g++-warning about unused viewItem
  903     bool currentItem = false;
  904     QModelIndex currentIndex;
  905     QString str;
  906     int row;
  907     QVector<QModelIndex> selected;
  908     QList<QModelIndex> expanded;
  909     QXmlStreamAttributes attribs;
  910     KLocalizedString attributeWarning = ki18n("Attribute '%1' missing or empty, default value is used");
  911 
  912     while (!reader->atEnd()) {
  913         reader->readNext();
  914         if (reader->isEndElement() && reader->name() == "state")
  915             break;
  916 
  917         if (!reader->isStartElement())
  918             continue;
  919 
  920         if (reader->name() == "expanded") {
  921             expandedItem = true;
  922             selectedItem = false;
  923             viewItem = false;
  924             currentItem = false;
  925         } else if (reader->name() == "selected") {
  926             expandedItem = false;
  927             selectedItem = true;
  928             viewItem = false;
  929             currentItem = false;
  930         } else  if (reader->name() == "view") {
  931             expandedItem = false;
  932             selectedItem = false;
  933             viewItem = true;
  934             currentItem = false;
  935         } else  if (reader->name() == "current") {
  936             expandedItem = false;
  937             selectedItem = false;
  938             viewItem = false;
  939             currentItem = true;
  940         } else if (reader->name() == "row") {
  941             //we need to read the attributes first and before readElementText() otherwise they are empty
  942             attribs = reader->attributes();
  943             row = reader->readElementText().toInt();
  944 
  945             QModelIndex index;
  946             if (row == -1)
  947                 index = model->modelIndexOfAspect(m_project); //-1 corresponds to the project-item (s.a. ProjectExplorer::save())
  948             else if (row >= aspects.size() || row < 0 /* checking for <0 to protect against wrong values in the XML */)
  949                 continue;
  950             else
  951                 index = model->modelIndexOfAspect(aspects.at(row));
  952 
  953             if (expandedItem)
  954                 expanded.push_back(index);
  955             else if (selectedItem)
  956                 selected.push_back(index);
  957             else if (currentItem)
  958                 currentIndex = index;
  959             else if (viewItem) {
  960                 if (row < 0) //should never happen, but we need to handle the corrupted file
  961                     continue;
  962 
  963                 auto* part = dynamic_cast<AbstractPart*>(aspects.at(row));
  964                 if (!part)
  965                     continue; //TODO: add error/warning message here?
  966 
  967                 emit activateView(part); //request to show the view in MainWin
  968 
  969                 str = attribs.value("state").toString();
  970                 if (str.isEmpty())
  971                     reader->raiseWarning(attributeWarning.subs("state").toString());
  972                 else {
  973                     part->view()->setWindowState(Qt::WindowStates(str.toInt()));
  974                     part->mdiSubWindow()->setWindowState(Qt::WindowStates(str.toInt()));
  975                 }
  976 
  977                 if (str != "0")
  978                     continue; //no geometry settings required for maximized/minimized windows
  979 
  980                 QRect geometry;
  981                 str = attribs.value("x").toString();
  982                 if (str.isEmpty())
  983                     reader->raiseWarning(attributeWarning.subs("x").toString());
  984                 else
  985                     geometry.setX(str.toInt());
  986 
  987                 str = attribs.value("y").toString();
  988                 if (str.isEmpty())
  989                     reader->raiseWarning(attributeWarning.subs("y").toString());
  990                 else
  991                     geometry.setY(str.toInt());
  992 
  993                 str = attribs.value("width").toString();
  994                 if (str.isEmpty())
  995                     reader->raiseWarning(attributeWarning.subs("width").toString());
  996                 else
  997                     geometry.setWidth(str.toInt());
  998 
  999                 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 }