File indexing completed on 2024-05-12 07:40:43
0001 /* 0002 File : ProjectExplorer.cpp 0003 Project : LabPlot 0004 Description : A tree view for displaying and editing an AspectTreeModel. 0005 -------------------------------------------------------------------- 0006 SPDX-FileCopyrightText: 2007-2008 Tilman Benkert <thzs@gmx.net> 0007 SPDX-FileCopyrightText: 2010-2021 Alexander Semke <alexander.semke@web.de> 0008 0009 SPDX-License-Identifier: GPL-2.0-or-later 0010 */ 0011 0012 #include "ProjectExplorer.h" 0013 #include "backend/core/AbstractPart.h" 0014 #include "backend/core/AspectTreeModel.h" 0015 #include "backend/core/Project.h" 0016 #include "backend/core/Settings.h" 0017 #include "backend/core/column/Column.h" 0018 #include "backend/lib/XmlStreamReader.h" 0019 #include "backend/worksheet/plots/cartesian/CartesianPlot.h" 0020 #include "commonfrontend/core/ContentDockWidget.h" 0021 0022 #include <KConfig> 0023 #include <KConfigGroup> 0024 #include <KLocalizedString> 0025 #include <KMessageBox> 0026 #include <KMessageWidget> 0027 0028 #include <kcoreaddons_version.h> 0029 #if KCOREADDONS_VERSION >= QT_VERSION_CHECK(5, 79, 0) 0030 #define HAS_FUZZY_MATCHER true 0031 #include <KFuzzyMatcher> 0032 #else 0033 #define HAS_FUZZY_MATCHER false 0034 #endif 0035 0036 #include <QClipboard> 0037 #include <QContextMenuEvent> 0038 #include <QDrag> 0039 #include <QHeaderView> 0040 #include <QLabel> 0041 #include <QLineEdit> 0042 #include <QMenu> 0043 #include <QMimeData> 0044 #include <QPushButton> 0045 #include <QTreeView> 0046 #include <QVBoxLayout> 0047 0048 /*! 0049 \class ProjectExplorer 0050 \brief A tree view for displaying and editing an AspectTreeModel. 0051 0052 In addition to the functionality of QTreeView, ProjectExplorer allows 0053 the usage of the context menus provided by AspectTreeModel 0054 and propagates the item selection in the view to the model. 0055 Furthermore, features for searching and filtering in the model are provided. 0056 0057 \ingroup commonfrontend 0058 */ 0059 0060 ProjectExplorer::ProjectExplorer(QWidget* parent) 0061 : m_treeView(new QTreeView(parent)) 0062 , m_frameFilter(new QFrame(this)) { 0063 auto* layout = new QVBoxLayout(this); 0064 layout->setSpacing(0); 0065 layout->setContentsMargins(0, 0, 0, 0); 0066 0067 auto* layoutFilter = new QHBoxLayout(m_frameFilter); 0068 layoutFilter->setSpacing(0); 0069 layoutFilter->setContentsMargins(0, 0, 0, 0); 0070 0071 m_leFilter = new QLineEdit(m_frameFilter); 0072 m_leFilter->setClearButtonEnabled(true); 0073 m_leFilter->setPlaceholderText(i18n("Search/Filter")); 0074 layoutFilter->addWidget(m_leFilter); 0075 0076 bFilterOptions = new QPushButton(m_frameFilter); 0077 bFilterOptions->setIcon(QIcon::fromTheme(QStringLiteral("configure"))); 0078 bFilterOptions->setCheckable(true); 0079 bFilterOptions->setFlat(true); 0080 layoutFilter->addWidget(bFilterOptions); 0081 0082 layout->addWidget(m_frameFilter); 0083 0084 m_treeView->setAnimated(true); 0085 m_treeView->setAlternatingRowColors(true); 0086 m_treeView->setSelectionBehavior(QAbstractItemView::SelectRows); 0087 m_treeView->setSelectionMode(QAbstractItemView::ExtendedSelection); 0088 m_treeView->setUniformRowHeights(true); 0089 m_treeView->viewport()->installEventFilter(this); 0090 m_treeView->header()->setStretchLastSection(true); 0091 m_treeView->header()->installEventFilter(this); 0092 m_treeView->setDragEnabled(true); 0093 m_treeView->setAcceptDrops(true); 0094 m_treeView->setDropIndicatorShown(true); 0095 m_treeView->setDragDropMode(QAbstractItemView::InternalMove); 0096 0097 layout->addWidget(m_treeView); 0098 0099 connect(m_leFilter, &QLineEdit::textChanged, this, &ProjectExplorer::filterTextChanged); 0100 connect(bFilterOptions, &QPushButton::toggled, this, &ProjectExplorer::toggleFilterOptionsMenu); 0101 } 0102 0103 ProjectExplorer::~ProjectExplorer() { 0104 // save the visible columns 0105 QString status; 0106 for (int i = 0; i < list_showColumnActions.size(); ++i) { 0107 if (list_showColumnActions.at(i)->isChecked()) { 0108 if (!status.isEmpty()) 0109 status += QLatin1Char(' '); 0110 status += QString::number(i); 0111 } 0112 } 0113 KConfigGroup group = Settings::group(QStringLiteral("ProjectExplorer")); 0114 group.writeEntry("VisibleColumns", status); 0115 } 0116 0117 void ProjectExplorer::createActions() { 0118 expandTreeAction = new QAction(QIcon::fromTheme(QLatin1String("expand-all")), i18n("Expand All"), this); 0119 connect(expandTreeAction, &QAction::triggered, m_treeView, &QTreeView::expandAll); 0120 0121 expandSelectedTreeAction = new QAction(QIcon::fromTheme(QLatin1String("expand-all")), i18n("Expand Selected"), this); 0122 connect(expandSelectedTreeAction, &QAction::triggered, this, &ProjectExplorer::expandSelected); 0123 0124 collapseTreeAction = new QAction(QIcon::fromTheme(QLatin1String("collapse-all")), i18n("Collapse All"), this); 0125 connect(collapseTreeAction, &QAction::triggered, m_treeView, &QTreeView::collapseAll); 0126 0127 collapseSelectedTreeAction = new QAction(QIcon::fromTheme(QLatin1String("collapse-all")), i18n("Collapse Selected"), this); 0128 connect(collapseSelectedTreeAction, &QAction::triggered, this, &ProjectExplorer::collapseSelected); 0129 0130 deleteSelectedTreeAction = new QAction(QIcon::fromTheme(QStringLiteral("edit-delete")), i18n("Delete Selected"), this); 0131 connect(deleteSelectedTreeAction, &QAction::triggered, this, &ProjectExplorer::deleteSelected); 0132 0133 toggleFilterAction = new QAction(QIcon::fromTheme(QLatin1String("view-filter")), i18n("Search/Filter Options"), this); 0134 toggleFilterAction->setCheckable(true); 0135 toggleFilterAction->setChecked(true); 0136 connect(toggleFilterAction, &QAction::triggered, this, [=]() { 0137 m_frameFilter->setVisible(!m_frameFilter->isVisible()); 0138 }); 0139 } 0140 0141 /*! 0142 shows the context menu in the tree. In addition to the context menu of the currently selected aspect, 0143 treeview specific options are added. 0144 */ 0145 void ProjectExplorer::contextMenuEvent(QContextMenuEvent* event) { 0146 if (!m_treeView->model()) 0147 return; 0148 0149 if (!expandTreeAction) 0150 createActions(); 0151 0152 const auto& index = m_treeView->indexAt(m_treeView->viewport()->mapFrom(this, event->pos())); 0153 if (!index.isValid()) 0154 m_treeView->clearSelection(); 0155 0156 const auto& items = m_treeView->selectionModel()->selectedIndexes(); 0157 QMenu* menu = nullptr; 0158 if (items.size() / 4 == 1) { 0159 auto* aspect = static_cast<AbstractAspect*>(index.internalPointer()); 0160 menu = aspect->createContextMenu(); 0161 0162 if (aspect == m_project) { 0163 auto* firstAction = menu->actions().at(2); 0164 menu->insertSeparator(firstAction); 0165 menu->insertAction(firstAction, expandTreeAction); 0166 menu->insertAction(firstAction, collapseTreeAction); 0167 } 0168 } else { 0169 menu = new QMenu(this); 0170 0171 if (items.size() / 4 > 1) { 0172 // add "expand/collapse" entries if the selected indices have children 0173 bool hasChildren = false; 0174 for (int i = 0; i < items.size() / 4; ++i) { 0175 const auto& index = items.at(i * 4); 0176 const auto* aspect = static_cast<AbstractAspect*>(index.internalPointer()); 0177 if (aspect->childCount<AbstractAspect>()) { 0178 hasChildren = true; 0179 break; 0180 } 0181 } 0182 if (hasChildren) { 0183 menu->addAction(expandSelectedTreeAction); 0184 menu->addAction(collapseSelectedTreeAction); 0185 menu->addSeparator(); 0186 } 0187 0188 menu->addAction(deleteSelectedTreeAction); 0189 } else { 0190 QMenu* projectMenu = m_project->createContextMenu(); 0191 projectMenu->setTitle(m_project->name()); 0192 menu->addMenu(projectMenu); 0193 0194 menu->addSeparator(); 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("Columns")); 0202 columnsMenu->addAction(showAllColumnsAction); 0203 columnsMenu->addSeparator(); 0204 for (auto* action : qAsConst(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 auto* tree_model = dynamic_cast<AspectTreeModel*>(m_treeView->model()); 0227 if (tree_model) { 0228 const auto& index = tree_model->modelIndexOfAspect(aspect); 0229 // TODO: This crashes on Windows in Debug mode 0230 #if !defined(HAVE_WINDOWS) || defined(NDEBUG) 0231 m_treeView->setCurrentIndex(index); 0232 #endif 0233 } 0234 } 0235 0236 /*! 0237 Sets the \c model for the tree view to present. 0238 */ 0239 void ProjectExplorer::setModel(AspectTreeModel* treeModel) { 0240 m_treeView->setModel(treeModel); 0241 0242 connect(treeModel, &AspectTreeModel::renameRequested, m_treeView, static_cast<void (QAbstractItemView::*)(const QModelIndex&)>(&QAbstractItemView::edit)); 0243 connect(treeModel, &AspectTreeModel::indexSelected, this, &ProjectExplorer::selectIndex); 0244 connect(treeModel, &AspectTreeModel::indexDeselected, this, &ProjectExplorer::deselectIndex); 0245 connect(treeModel, &AspectTreeModel::hiddenAspectSelected, this, &ProjectExplorer::hiddenAspectSelected); 0246 0247 connect(m_treeView->selectionModel(), &QItemSelectionModel::selectionChanged, this, &ProjectExplorer::selectionChanged); 0248 0249 // create action for showing/hiding the columns in the tree. 0250 // this is done here since the number of columns is not available in createActions() yet. 0251 if (list_showColumnActions.isEmpty()) { 0252 // read the status of the column actions if available 0253 KConfigGroup group = Settings::group(QStringLiteral("ProjectExplorer")); 0254 const QString& status = group.readEntry(QLatin1String("VisibleColumns"), QString()); 0255 QVector<int> checkedActions; 0256 if (!status.isEmpty()) { 0257 QStringList strList = status.split(QLatin1Char(' ')); 0258 for (int i = 0; i < strList.size(); ++i) 0259 checkedActions << strList.at(i).toInt(); 0260 } 0261 0262 if (!showAllColumnsAction) { 0263 showAllColumnsAction = new QAction(i18n("Show All"), this); 0264 showAllColumnsAction->setCheckable(true); 0265 showAllColumnsAction->setChecked(true); 0266 showAllColumnsAction->setEnabled(false); 0267 connect(showAllColumnsAction, &QAction::triggered, this, &ProjectExplorer::showAllColumns); 0268 } 0269 0270 if (checkedActions.size() != m_treeView->model()->columnCount()) { 0271 showAllColumnsAction->setEnabled(true); 0272 showAllColumnsAction->setChecked(false); 0273 } else { 0274 showAllColumnsAction->setEnabled(false); 0275 showAllColumnsAction->setChecked(true); 0276 } 0277 0278 // create an action for every available column in the model 0279 for (int i = 0; i < m_treeView->model()->columnCount(); i++) { 0280 QAction* showColumnAction = new QAction(treeModel->headerData(i, Qt::Horizontal).toString(), this); 0281 showColumnAction->setCheckable(true); 0282 0283 // restore the status, if available 0284 if (!checkedActions.isEmpty()) { 0285 if (checkedActions.indexOf(i) != -1) 0286 showColumnAction->setChecked(true); 0287 else 0288 m_treeView->hideColumn(i); 0289 } else 0290 showColumnAction->setChecked(true); 0291 0292 list_showColumnActions.append(showColumnAction); 0293 0294 connect(showColumnAction, &QAction::triggered, this, [=] { 0295 ProjectExplorer::toggleColumn(i); 0296 }); 0297 } 0298 } else { 0299 for (int i = 0; i < list_showColumnActions.size(); ++i) { 0300 if (!list_showColumnActions.at(i)->isChecked()) 0301 m_treeView->hideColumn(i); 0302 } 0303 } 0304 } 0305 0306 void ProjectExplorer::setProject(Project* project) { 0307 connect(project, &Project::childAspectAdded, this, &ProjectExplorer::aspectAdded); 0308 connect(project, &Project::requestSaveState, this, &ProjectExplorer::save); 0309 connect(project, &Project::requestLoadState, this, &ProjectExplorer::load); 0310 connect(project, &Project::requestNavigateTo, this, &ProjectExplorer::navigateTo); 0311 m_project = project; 0312 0313 // for newly created projects, resize the header to fit the size of the header section names. 0314 // for projects loaded from a file, this function will be called laterto fit the sizes 0315 // of the content once the project is loaded 0316 resizeHeader(); 0317 0318 // remove all error messages from the previous project, if any are visible 0319 if (m_messageWidget && m_messageWidget->isVisible()) 0320 m_messageWidget->close(); 0321 } 0322 0323 QModelIndex ProjectExplorer::currentIndex() const { 0324 return m_treeView->currentIndex(); 0325 } 0326 0327 AbstractAspect* ProjectExplorer::currentAspect() const { 0328 if (!currentIndex().isValid()) 0329 return nullptr; 0330 return static_cast<AbstractAspect*>(currentIndex().internalPointer()); 0331 } 0332 0333 void ProjectExplorer::search() { 0334 m_leFilter->setFocus(); 0335 } 0336 0337 /*! 0338 handles the contextmenu-event of the horizontal header in the tree view. 0339 Provides a menu for selective showing and hiding of columns. 0340 */ 0341 bool ProjectExplorer::eventFilter(QObject* obj, QEvent* event) { 0342 if (obj == m_treeView->header() && event->type() == QEvent::ContextMenu) { 0343 // Menu for showing/hiding the columns in the tree view 0344 QMenu* columnsMenu = new QMenu(m_treeView->header()); 0345 columnsMenu->addSection(i18n("Columns")); 0346 columnsMenu->addAction(showAllColumnsAction); 0347 columnsMenu->addSeparator(); 0348 for (auto* action : qAsConst(list_showColumnActions)) 0349 columnsMenu->addAction(action); 0350 0351 auto* e = static_cast<QContextMenuEvent*>(event); 0352 columnsMenu->exec(e->globalPos()); 0353 delete columnsMenu; 0354 0355 return true; 0356 } else if (obj == m_treeView->viewport()) { 0357 if (event->type() == QEvent::MouseButtonPress) { 0358 auto* e = static_cast<QMouseEvent*>(event); 0359 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) 0360 const auto position = e->globalPosition().toPoint(); 0361 #else 0362 const auto position = e->globalPos(); 0363 #endif 0364 if (e->button() == Qt::LeftButton) { 0365 QModelIndex index = m_treeView->indexAt(e->pos()); 0366 if (!index.isValid()) 0367 return false; 0368 0369 auto* aspect = static_cast<AbstractAspect*>(index.internalPointer()); 0370 if (aspect->isDraggable()) { 0371 m_dragStartPos = position; 0372 m_dragStarted = false; 0373 } 0374 } 0375 } else if (event->type() == QEvent::MouseMove) { 0376 auto* e = static_cast<QMouseEvent*>(event); 0377 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) 0378 const auto position = e->globalPosition().toPoint(); 0379 #else 0380 const auto position = e->globalPos(); 0381 #endif 0382 if (!m_dragStarted && m_treeView->selectionModel()->selectedIndexes().size() > 0 0383 && (position - m_dragStartPos).manhattanLength() >= QApplication::startDragDistance()) { 0384 m_dragStarted = true; 0385 auto* drag = new QDrag(this); 0386 auto* mimeData = new QMimeData; 0387 0388 // determine the selected objects and serialize the pointers to QMimeData 0389 QVector<quintptr> vec; 0390 QModelIndexList items = m_treeView->selectionModel()->selectedIndexes(); 0391 // there are four model indices in each row -> divide by 4 to obtain the number of selected rows (=aspects) 0392 const int columnCount = m_treeView->model()->columnCount(); 0393 for (int i = 0; i < items.size() / columnCount; ++i) { 0394 const QModelIndex& index = items.at(i * columnCount); 0395 auto* aspect = static_cast<AbstractAspect*>(index.internalPointer()); 0396 vec << (quintptr)aspect; 0397 } 0398 0399 QByteArray data; 0400 QDataStream stream(&data, QIODevice::WriteOnly); 0401 stream << (quintptr)m_project; // serialize the project pointer first, will be used as the unique identifier 0402 stream << vec; 0403 0404 mimeData->setData(QStringLiteral("labplot-dnd"), data); 0405 drag->setMimeData(mimeData); 0406 drag->exec(); 0407 } 0408 } else if (event->type() == QEvent::DragEnter) { 0409 // ignore events not related to internal drags of columns etc., e.g. dropping of external files onto LabPlot 0410 auto* dragEnterEvent = static_cast<QDragEnterEvent*>(event); 0411 const QMimeData* mimeData = dragEnterEvent->mimeData(); 0412 if (!mimeData) { 0413 event->ignore(); 0414 return false; 0415 } 0416 0417 if (mimeData->formats().at(0) != QLatin1String("labplot-dnd")) { 0418 event->ignore(); 0419 return false; 0420 } else { 0421 // drad&drop between the different project windows is not supported yet. 0422 // check whether we're dragging inside of the same project. 0423 QByteArray data = mimeData->data(QLatin1String("labplot-dnd")); 0424 QDataStream stream(&data, QIODevice::ReadOnly); 0425 quintptr ptr = 0; 0426 stream >> ptr; 0427 auto* project = reinterpret_cast<Project*>(ptr); 0428 if (project != m_project) { 0429 event->ignore(); 0430 return false; 0431 } 0432 } 0433 0434 event->setAccepted(true); 0435 } else if (event->type() == QEvent::DragMove) { 0436 auto* dragMoveEvent = static_cast<QDragEnterEvent*>(event); 0437 const QMimeData* mimeData = dragMoveEvent->mimeData(); 0438 QVector<quintptr> vec = m_project->droppedAspects(mimeData); 0439 0440 AbstractAspect* sourceAspect{nullptr}; 0441 if (!vec.isEmpty()) 0442 sourceAspect = reinterpret_cast<AbstractAspect*>(vec.at(0)); 0443 0444 if (!sourceAspect) 0445 return false; 0446 0447 // determine the aspect under the cursor 0448 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) 0449 QModelIndex index = m_treeView->indexAt(dragMoveEvent->position().toPoint()); 0450 #else 0451 QModelIndex index = m_treeView->indexAt(dragMoveEvent->pos()); 0452 #endif 0453 if (!index.isValid()) 0454 return false; 0455 0456 // accept only the events when the aspect being dragged is dropable onto the aspect under the cursor 0457 // and the aspect under the cursor is not already the parent of the dragged aspect 0458 auto* destinationAspect = static_cast<AbstractAspect*>(index.internalPointer()); 0459 bool accept = sourceAspect->dropableOn().indexOf(destinationAspect->type()) != -1 && sourceAspect->parentAspect() != destinationAspect; 0460 event->setAccepted(accept); 0461 } else if (event->type() == QEvent::Drop) { 0462 auto* dropEvent = static_cast<QDropEvent*>(event); 0463 const QMimeData* mimeData = dropEvent->mimeData(); 0464 if (!mimeData) 0465 return false; 0466 0467 QVector<quintptr> vec = m_project->droppedAspects(mimeData); 0468 if (vec.isEmpty()) 0469 return false; 0470 0471 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) 0472 QModelIndex index = m_treeView->indexAt(dropEvent->position().toPoint()); 0473 #else 0474 QModelIndex index = m_treeView->indexAt(dropEvent->pos()); 0475 #endif 0476 if (!index.isValid()) 0477 return false; 0478 0479 // process the dropped objects 0480 auto* aspect = static_cast<AbstractAspect*>(index.internalPointer()); 0481 aspect->processDropEvent(vec); 0482 0483 // expand the current aspect to see the dropped objects 0484 const auto* model = static_cast<const AspectTreeModel*>(m_treeView->model()); 0485 m_treeView->setExpanded(model->modelIndexOfAspect(aspect), true); 0486 } 0487 } 0488 0489 return QObject::eventFilter(obj, event); 0490 } 0491 0492 void ProjectExplorer::keyPressEvent(QKeyEvent* event) { 0493 // current selected aspect 0494 auto* aspect = static_cast<AbstractAspect*>(m_treeView->currentIndex().internalPointer()); 0495 0496 if (event->matches(QKeySequence::Delete)) 0497 deleteSelected(); 0498 else if (event->matches(QKeySequence::Copy)) { 0499 // copy 0500 if (aspect != m_project) { 0501 aspect->copy(); 0502 showErrorMessage(QString()); 0503 } 0504 } else if (event->matches(QKeySequence::Paste)) { 0505 // paste 0506 QString name; 0507 auto t = AbstractAspect::clipboardAspectType(name); 0508 if (!name.isEmpty()) { 0509 if (t != AspectType::AbstractAspect && aspect->pasteTypes().indexOf(t) != -1) { 0510 aspect->paste(); 0511 showErrorMessage(QString()); 0512 } else { 0513 QString msg = i18n("'%1' cannot be pasted into '%2'.", name, aspect->name()); 0514 showErrorMessage(msg); 0515 } 0516 } else { 0517 // no name is available, we are copy&pasting the content of a columm ("the data") and not the column itself 0518 const auto* mimeData = QApplication::clipboard()->mimeData(); 0519 if (!mimeData->hasFormat(QStringLiteral("text/plain"))) 0520 return; 0521 0522 // pasting is allowed into spreadsheet columns only 0523 if (aspect->type() == AspectType::Column && aspect->parentAspect()->type() == AspectType::Spreadsheet) { 0524 auto* column = static_cast<Column*>(aspect); 0525 column->pasteData(); 0526 } else { 0527 QString msg = i18n("Data cannot be pasted into '%1' directly. Select a spreadsheet column for this.", aspect->name()); 0528 showErrorMessage(msg); 0529 } 0530 } 0531 } else if ((event->modifiers() & Qt::ControlModifier) && (event->key() == Qt::Key_D)) { 0532 // duplicate 0533 if (aspect != m_project) { 0534 aspect->copy(); 0535 aspect->parentAspect()->paste(true); 0536 showErrorMessage(QString()); 0537 } 0538 } else if (event->key() == 32) { 0539 // space key - hide/show the current object 0540 changeSelectedVisible(); 0541 } 0542 } 0543 0544 void ProjectExplorer::showErrorMessage(const QString& message) { 0545 if (message.isEmpty()) { 0546 if (m_messageWidget && m_messageWidget->isVisible()) 0547 m_messageWidget->close(); 0548 } else { 0549 if (!m_messageWidget) { 0550 m_messageWidget = new KMessageWidget(this); 0551 m_messageWidget->setMessageType(KMessageWidget::Error); 0552 layout()->addWidget(m_messageWidget); 0553 } 0554 m_messageWidget->setText(message); 0555 m_messageWidget->animatedShow(); 0556 } 0557 } 0558 0559 // ############################################################################## 0560 // ################################# SLOTS #################################### 0561 // ############################################################################## 0562 /*! 0563 expand the aspect \c aspect (the tree index corresponding to it) in the tree view 0564 and makes it visible and selected. Called when a new aspect is added to the project. 0565 */ 0566 void ProjectExplorer::aspectAdded(const AbstractAspect* aspect) { 0567 if (m_project->isLoading() || m_project->aspectAddedSignalSuppressed()) 0568 return; 0569 0570 // don't do anything if hidden aspects were added 0571 if (aspect->hidden()) 0572 return; 0573 0574 // don't do anything for newly added data spreadsheets of data picker curves 0575 if (aspect->type() == AspectType::Spreadsheet && aspect->parentAspect()->type() == AspectType::DatapickerCurve) 0576 return; 0577 0578 const auto* tree_model = qobject_cast<AspectTreeModel*>(m_treeView->model()); 0579 const auto& index = tree_model->modelIndexOfAspect(aspect); 0580 0581 // expand and make the aspect visible 0582 // no need to expand and to show child columns of a statistics spreadsheet 0583 if (aspect->type() != AspectType::StatisticsSpreadsheet) 0584 m_treeView->setExpanded(index, true); 0585 0586 // newly added columns are only expanded but not selected, return here 0587 if (aspect->type() == AspectType::Column) { 0588 m_treeView->setExpanded(tree_model->modelIndexOfAspect(aspect->parentAspect()), true); 0589 return; 0590 } 0591 0592 m_treeView->scrollTo(index); 0593 m_treeView->setCurrentIndex(index); 0594 m_treeView->header()->resizeSections(QHeaderView::ResizeToContents); 0595 m_treeView->header()->resizeSection(0, m_treeView->header()->sectionSize(0) * 1.2); 0596 } 0597 0598 void ProjectExplorer::navigateTo(const QString& path) { 0599 const auto* tree_model = dynamic_cast<AspectTreeModel*>(m_treeView->model()); 0600 if (tree_model) { 0601 const auto& index = tree_model->modelIndexOfAspect(path); 0602 if (!index.isValid()) 0603 return; 0604 m_treeView->scrollTo(index); 0605 m_treeView->setCurrentIndex(index); 0606 auto* aspect = static_cast<AbstractAspect*>(index.internalPointer()); 0607 aspect->setSelected(true); 0608 } 0609 } 0610 0611 void ProjectExplorer::toggleColumn(int index) { 0612 // determine the total number of checked column actions 0613 int checked = 0; 0614 for (const auto* action : qAsConst(list_showColumnActions)) { 0615 if (action->isChecked()) 0616 checked++; 0617 } 0618 0619 if (list_showColumnActions.at(index)->isChecked()) { 0620 m_treeView->showColumn(index); 0621 m_treeView->header()->resizeSection(0, 0); 0622 m_treeView->header()->resizeSections(QHeaderView::ResizeToContents); 0623 0624 for (auto* action : qAsConst(list_showColumnActions)) 0625 action->setEnabled(true); 0626 0627 // deactivate the "show all column"-action, if all actions are checked 0628 if (checked == list_showColumnActions.size()) { 0629 showAllColumnsAction->setEnabled(false); 0630 showAllColumnsAction->setChecked(true); 0631 } 0632 } else { 0633 m_treeView->hideColumn(index); 0634 showAllColumnsAction->setEnabled(true); 0635 showAllColumnsAction->setChecked(false); 0636 0637 // if there is only one checked column-action, deactivated it. 0638 // It should't be possible to hide all columns 0639 if (checked == 1) { 0640 int i = 0; 0641 while (!list_showColumnActions.at(i)->isChecked()) 0642 i++; 0643 0644 list_showColumnActions.at(i)->setEnabled(false); 0645 } 0646 } 0647 } 0648 0649 void ProjectExplorer::showAllColumns() { 0650 for (int i = 0; i < m_treeView->model()->columnCount(); i++) { 0651 m_treeView->showColumn(i); 0652 m_treeView->header()->resizeSection(0, 0); 0653 m_treeView->header()->resizeSections(QHeaderView::ResizeToContents); 0654 } 0655 showAllColumnsAction->setEnabled(false); 0656 0657 for (auto* action : qAsConst(list_showColumnActions)) { 0658 action->setEnabled(true); 0659 action->setChecked(true); 0660 } 0661 } 0662 0663 /*! 0664 toggles the menu for the filter/search options 0665 */ 0666 void ProjectExplorer::toggleFilterOptionsMenu(bool checked) { 0667 if (!checked) 0668 return; 0669 0670 if (!caseSensitiveAction) { 0671 caseSensitiveAction = new QAction(i18n("Case Sensitive"), this); 0672 caseSensitiveAction->setCheckable(true); 0673 caseSensitiveAction->setChecked(false); 0674 connect(caseSensitiveAction, &QAction::triggered, this, [=]() { 0675 if (!m_leFilter->text().isEmpty()) 0676 filterTextChanged(m_leFilter->text()); 0677 }); 0678 0679 matchCompleteWordAction = new QAction(i18n("Match Complete Word"), this); 0680 matchCompleteWordAction->setCheckable(true); 0681 matchCompleteWordAction->setChecked(false); 0682 connect(matchCompleteWordAction, &QAction::triggered, this, [=]() { 0683 if (!m_leFilter->text().isEmpty()) 0684 filterTextChanged(m_leFilter->text()); 0685 }); 0686 0687 #if HAS_FUZZY_MATCHER 0688 fuzzyMatchingAction = new QAction(i18n("Fuzzy Matching"), this); 0689 fuzzyMatchingAction->setCheckable(true); 0690 fuzzyMatchingAction->setChecked(true); 0691 connect(fuzzyMatchingAction, &QAction::triggered, this, [=]() { 0692 bool enabled = !fuzzyMatchingAction->isChecked(); 0693 caseSensitiveAction->setEnabled(enabled); 0694 matchCompleteWordAction->setEnabled(enabled); 0695 if (!m_leFilter->text().isEmpty()) 0696 filterTextChanged(m_leFilter->text()); 0697 }); 0698 caseSensitiveAction->setEnabled(false); 0699 matchCompleteWordAction->setEnabled(false); 0700 #endif 0701 } 0702 0703 QMenu menu; 0704 #if HAS_FUZZY_MATCHER 0705 menu.addAction(fuzzyMatchingAction); 0706 menu.addSeparator(); 0707 #endif 0708 menu.addAction(caseSensitiveAction); 0709 menu.addAction(matchCompleteWordAction); 0710 connect(&menu, &QMenu::aboutToHide, bFilterOptions, &QPushButton::toggle); 0711 menu.exec(bFilterOptions->mapToGlobal(QPoint(0, bFilterOptions->height()))); 0712 } 0713 0714 void ProjectExplorer::resizeHeader() { 0715 m_treeView->header()->resizeSections(QHeaderView::ResizeToContents); 0716 m_treeView->header()->resizeSection(0, m_treeView->header()->sectionSize(0) * 1.2); // make the column "Name" somewhat bigger 0717 } 0718 0719 /*! 0720 called when the filter/search text was changend. 0721 */ 0722 void ProjectExplorer::filterTextChanged(const QString& text) { 0723 QModelIndex root = m_treeView->model()->index(0, 0); 0724 filter(root, text); 0725 } 0726 0727 bool ProjectExplorer::filter(const QModelIndex& index, const QString& text) { 0728 const auto* model = index.model(); 0729 const int rows = model->rowCount(index); 0730 0731 // if the filter string is empty, just traverse the whole model and make every index visible 0732 if (text.isEmpty()) { 0733 for (int i = 0; i < rows; ++i) { 0734 m_treeView->setRowHidden(i, index, false); 0735 const auto& child = model->index(i, 0, index); 0736 if (model->hasChildren(child)) 0737 filter(child, text); 0738 } 0739 return true; 0740 } 0741 0742 #if HAS_FUZZY_MATCHER 0743 bool fuzzyFiltering = true; 0744 if (fuzzyMatchingAction && !fuzzyMatchingAction->isChecked()) 0745 fuzzyFiltering = false; 0746 #endif 0747 0748 bool childVisible = false; 0749 for (int i = 0; i < rows; i++) { 0750 const auto& child = model->index(i, 0, index); 0751 auto* aspect = static_cast<AbstractAspect*>(child.internalPointer()); 0752 bool visible; 0753 #if HAS_FUZZY_MATCHER 0754 if (fuzzyFiltering) 0755 visible = KFuzzyMatcher::matchSimple(text, aspect->name()); 0756 else 0757 #endif 0758 { 0759 bool matchCompleteWord = false; 0760 if (matchCompleteWordAction && matchCompleteWordAction->isChecked()) 0761 matchCompleteWord = true; 0762 0763 Qt::CaseSensitivity sensitivity = Qt::CaseInsensitive; 0764 if (caseSensitiveAction && caseSensitiveAction->isChecked()) 0765 sensitivity = Qt::CaseSensitive; 0766 0767 if (matchCompleteWord) 0768 visible = aspect->name().startsWith(text, sensitivity); 0769 else 0770 visible = aspect->name().contains(text, sensitivity); 0771 } 0772 0773 if (visible) { 0774 // current item is visible -> make all its children visible without applying the filter 0775 for (int j = 0; j < model->rowCount(child); ++j) 0776 m_treeView->setRowHidden(j, child, false); 0777 0778 childVisible = true; 0779 } else { 0780 // check children items. if one of the children is visible, make the parent (current) item visible too. 0781 visible = filter(child, text); 0782 if (visible) 0783 childVisible = true; 0784 } 0785 0786 m_treeView->setRowHidden(i, index, !visible); 0787 } 0788 0789 return childVisible; 0790 } 0791 0792 void ProjectExplorer::selectIndex(const QModelIndex& index) { 0793 if (m_project->isLoading()) 0794 return; 0795 0796 DEBUG(Q_FUNC_INFO) 0797 0798 if (!m_treeView->selectionModel()->isSelected(index)) { 0799 m_changeSelectionFromView = true; 0800 m_treeView->selectionModel()->select(index, QItemSelectionModel::Select | QItemSelectionModel::Rows); 0801 m_treeView->setExpanded(index, true); 0802 m_treeView->scrollTo(index); 0803 } 0804 } 0805 0806 void ProjectExplorer::deselectIndex(const QModelIndex& index) { 0807 if (m_project->isLoading()) 0808 return; 0809 0810 if (m_treeView->selectionModel()->isSelected(index)) { 0811 m_changeSelectionFromView = true; 0812 m_treeView->selectionModel()->select(index, QItemSelectionModel::Deselect | QItemSelectionModel::Rows); 0813 } 0814 } 0815 0816 void ProjectExplorer::selectionChanged(const QItemSelection& selected, const QItemSelection& deselected) { 0817 if (m_project->isLoading()) 0818 return; 0819 0820 // QDEBUG(Q_FUNC_INFO << ", selected/deselected = " << selected << "/" << deselected) 0821 0822 QModelIndex index; 0823 AbstractAspect* aspect = nullptr; 0824 0825 // there are four model indices in each row 0826 //-> divide by 4 to obtain the number of selected rows (=aspects) 0827 const auto& sitems = selected.indexes(); 0828 for (int i = 0; i < sitems.size() / 4; ++i) { 0829 index = sitems.at(i * 4); 0830 aspect = static_cast<AbstractAspect*>(index.internalPointer()); 0831 QDEBUG("sitems ASPECT =" << aspect) 0832 aspect->setSelected(true); 0833 } 0834 0835 const auto& ditems = deselected.indexes(); 0836 for (int i = 0; i < ditems.size() / 4; ++i) { 0837 index = ditems.at(i * 4); 0838 aspect = static_cast<AbstractAspect*>(index.internalPointer()); 0839 QDEBUG("ditems ASPECT =" << aspect) 0840 aspect->setSelected(false); 0841 } 0842 0843 const auto& items = m_treeView->selectionModel()->selectedRows(); 0844 QList<AbstractAspect*> selectedAspects; 0845 for (const auto& index : items) { 0846 aspect = static_cast<AbstractAspect*>(index.internalPointer()); 0847 QDEBUG("items ASPECT =" << aspect) 0848 selectedAspects << aspect; 0849 } 0850 0851 // notify GuiObserver about the new selection 0852 Q_EMIT selectedAspectsChanged(selectedAspects); 0853 0854 // notify MainWin about the new current aspect (last selected aspect). 0855 if (!selectedAspects.isEmpty()) 0856 Q_EMIT currentAspectChanged(selectedAspects.last()); 0857 0858 // emitting the signal above is done to show the properties widgets for the selected aspect(s). 0859 // with this the project explorer looses the focus and don't react on the key events like DEL key press, etc. 0860 // If we explicitly select an item in the project explorer (not via a selection in the view), we want to keep the focus here. 0861 // TODO: after the focus is set again we react on DEL in the event filter, but navigation with the arrow keys in the table 0862 // is still not possible. Looks like we need to set the selection again... 0863 if (!m_changeSelectionFromView) 0864 setFocus(); 0865 else 0866 m_changeSelectionFromView = false; 0867 } 0868 0869 void ProjectExplorer::expandSelected() { 0870 const auto& items = m_treeView->selectionModel()->selectedIndexes(); 0871 for (const auto& index : items) 0872 m_treeView->setExpanded(index, true); 0873 } 0874 0875 void ProjectExplorer::collapseSelected() { 0876 const auto& items = m_treeView->selectionModel()->selectedIndexes(); 0877 for (const auto& index : items) 0878 m_treeView->setExpanded(index, false); 0879 } 0880 0881 void ProjectExplorer::changeSelectedVisible() { 0882 const auto& items = m_treeView->selectionModel()->selectedIndexes(); 0883 0884 // determine all selected aspects 0885 QVector<WorksheetElement*> elements; 0886 const auto columnCount = m_treeView->model()->columnCount(); 0887 for (int i = 0; i < items.size() / columnCount; ++i) { 0888 const QModelIndex& index = items.at(i * columnCount); 0889 auto* aspect = static_cast<AbstractAspect*>(index.internalPointer()); 0890 auto* element = dynamic_cast<WorksheetElement*>(aspect); 0891 if (element) 0892 elements << element; 0893 } 0894 0895 const int numberElements = elements.size(); 0896 if (numberElements == 0) 0897 return; 0898 0899 // Use first element as reference 0900 const auto& firstElement = elements.constFirst(); 0901 const bool newVisible = !firstElement->isVisible(); 0902 0903 if (numberElements > 1) { 0904 firstElement->beginMacro(i18n("%1 elements: set visible: %2").arg(numberElements, newVisible)); 0905 for (auto* e : elements) 0906 e->setVisible(newVisible); 0907 firstElement->endMacro(); 0908 } else 0909 firstElement->setVisible(newVisible); 0910 } 0911 0912 void ProjectExplorer::deleteSelected() { 0913 const auto& items = m_treeView->selectionModel()->selectedIndexes(); 0914 if (!items.size()) 0915 return; 0916 0917 // determine all selected aspects 0918 QVector<AbstractAspect*> aspects; 0919 const auto columnCount = m_treeView->model()->columnCount(); 0920 for (int i = 0; i < items.size() / columnCount; ++i) { 0921 const QModelIndex& index = items.at(i * columnCount); 0922 auto* aspect = static_cast<AbstractAspect*>(index.internalPointer()); 0923 aspects << aspect; 0924 } 0925 0926 QString msg; 0927 if (aspects.size() > 1) 0928 msg = i18n("Do you really want to delete the selected %1 objects?", aspects.size()); 0929 else 0930 msg = i18n("Do you really want to delete %1?", aspects.constFirst()->name()); 0931 0932 #if KCOREADDONS_VERSION >= QT_VERSION_CHECK(5, 100, 0) 0933 auto status = KMessageBox::warningTwoActions(this, 0934 msg, 0935 i18np("Delete selected object", "Delete selected objects", aspects.size()), 0936 KStandardGuiItem::del(), 0937 KStandardGuiItem::cancel()); 0938 if (status == KMessageBox::SecondaryAction) 0939 return; 0940 #else 0941 auto status = KMessageBox::warningYesNo(this, msg, i18np("Delete selected object", "Delete selected objects", aspects.size())); 0942 if (status == KMessageBox::No) 0943 return; 0944 #endif 0945 0946 m_project->beginMacro(i18np("Project Explorer: delete %1 selected object", "Project Explorer: delete %1 selected objects", items.size() / 4)); 0947 0948 // determine aspects to be deleted: 0949 // it's enough to delete parent items in the selection only, 0950 // skip all selected aspects where one of the parents is also in the selection 0951 // for example selected columns of a selected spreadsheet, etc. 0952 QVector<AbstractAspect*> aspectsToDelete; 0953 for (auto* aspect : aspects) { 0954 auto* parent = aspect->parentAspect(); 0955 int parentSelected = false; 0956 while (parent) { 0957 if (aspects.indexOf(parent) != -1) { 0958 parentSelected = true; 0959 break; 0960 } 0961 0962 parent = parent->parentAspect(); 0963 } 0964 0965 if (!parentSelected) 0966 aspectsToDelete << aspect; // parent is not in the selection 0967 } 0968 0969 for (auto* aspect : aspectsToDelete) 0970 aspect->remove(); 0971 0972 m_project->endMacro(); 0973 } 0974 0975 // ############################################################################## 0976 // ################## Serialization/Deserialization ########################### 0977 // ############################################################################## 0978 struct ViewState { 0979 Qt::WindowStates state; 0980 QRect geometry; 0981 }; 0982 0983 /** 0984 * \brief Save the current state of the tree view 0985 * (expanded items and the currently selected item) as XML 0986 */ 0987 void ProjectExplorer::save(QXmlStreamWriter* writer) const { 0988 const auto* model = static_cast<AspectTreeModel*>(m_treeView->model()); 0989 const auto& selectedRows = m_treeView->selectionModel()->selectedRows(); 0990 0991 writer->writeStartElement(QStringLiteral("state")); 0992 0993 // check whether the project node itself is expanded or selected or current 0994 const auto& index = m_treeView->model()->index(0, 0); 0995 if (m_treeView->isExpanded(index)) { 0996 writer->writeStartElement(QStringLiteral("expanded")); 0997 writer->writeAttribute(QStringLiteral("path"), m_project->path()); 0998 writer->writeEndElement(); 0999 } 1000 1001 if (selectedRows.indexOf(index) != -1) { 1002 writer->writeStartElement(QStringLiteral("selected")); 1003 writer->writeAttribute(QStringLiteral("path"), m_project->path()); 1004 writer->writeEndElement(); 1005 } 1006 1007 if (index == m_treeView->currentIndex()) { 1008 writer->writeStartElement(QStringLiteral("current")); 1009 writer->writeAttribute(QStringLiteral("path"), m_project->path()); 1010 writer->writeEndElement(); 1011 } 1012 1013 // traverse the children nodes 1014 const auto& children = m_project->children<AbstractAspect>(AbstractAspect::ChildIndexFlag::Recursive); 1015 for (const auto* aspect : children) { 1016 const QString& path = aspect->path(); 1017 const auto* part = dynamic_cast<const AbstractPart*>(aspect); 1018 1019 if (part && part->hasMdiSubWindow()) { 1020 writer->writeStartElement(QStringLiteral("view")); 1021 const auto& geometry = part->dockWidget()->geometry(); 1022 writer->writeAttribute(QStringLiteral("path"), path); 1023 writer->writeAttribute(QStringLiteral("state"), QString::number(part->view()->windowState())); 1024 writer->writeAttribute(QStringLiteral("x"), QString::number(geometry.x())); 1025 writer->writeAttribute(QStringLiteral("y"), QString::number(geometry.y())); 1026 writer->writeAttribute(QStringLiteral("width"), QString::number(geometry.width())); 1027 writer->writeAttribute(QStringLiteral("height"), QString::number(geometry.height())); 1028 writer->writeEndElement(); 1029 } 1030 1031 const auto& index = model->modelIndexOfAspect(aspect); 1032 if (model->rowCount(index) > 0 && m_treeView->isExpanded(index)) { 1033 writer->writeStartElement(QStringLiteral("expanded")); 1034 writer->writeAttribute(QStringLiteral("path"), path); 1035 writer->writeEndElement(); 1036 } 1037 1038 if (selectedRows.indexOf(index) != -1) { 1039 writer->writeStartElement(QStringLiteral("selected")); 1040 writer->writeAttribute(QStringLiteral("path"), path); 1041 writer->writeEndElement(); 1042 } 1043 1044 if (index == m_treeView->currentIndex()) { 1045 writer->writeStartElement(QStringLiteral("current")); 1046 writer->writeAttribute(QStringLiteral("path"), path); 1047 writer->writeEndElement(); 1048 } 1049 } 1050 1051 writer->writeEndElement(); //"state" 1052 } 1053 1054 /** 1055 * \brief Load from XML 1056 */ 1057 bool ProjectExplorer::load(XmlStreamReader* reader) { 1058 const auto* model = static_cast<AspectTreeModel*>(m_treeView->model()); 1059 QList<QModelIndex> selected; 1060 QList<QModelIndex> expanded; 1061 QModelIndex currentIndex; 1062 QString str; 1063 1064 // xmlVersion < 3: old logic where the "rows" for the selected and expanded items were saved 1065 // ignoring the sub-folder structure. Remove it later. 1066 if (Project::xmlVersion() < 3) { 1067 const auto& aspects = m_project->children<AbstractAspect>(AbstractAspect::ChildIndexFlag::Recursive); 1068 bool expandedItem = false; 1069 bool selectedItem = false; 1070 bool viewItem = false; 1071 (void)viewItem; // because of a strange g++-warning about unused viewItem 1072 bool currentItem = false; 1073 int row; 1074 QXmlStreamAttributes attribs; 1075 1076 while (!reader->atEnd()) { 1077 reader->readNext(); 1078 if (reader->isEndElement() && reader->name() == QLatin1String("state")) 1079 break; 1080 1081 if (!reader->isStartElement()) 1082 continue; 1083 1084 if (reader->name() == QLatin1String("expanded")) { 1085 expandedItem = true; 1086 selectedItem = false; 1087 viewItem = false; 1088 currentItem = false; 1089 } else if (reader->name() == QLatin1String("selected")) { 1090 expandedItem = false; 1091 selectedItem = true; 1092 viewItem = false; 1093 currentItem = false; 1094 } else if (reader->name() == QLatin1String("view")) { 1095 expandedItem = false; 1096 selectedItem = false; 1097 viewItem = true; 1098 currentItem = false; 1099 } else if (reader->name() == QLatin1String("current")) { 1100 expandedItem = false; 1101 selectedItem = false; 1102 viewItem = false; 1103 currentItem = true; 1104 } else if (reader->name() == QLatin1String("row")) { 1105 // we need to read the attributes first and before readElementText() otherwise they are empty 1106 attribs = reader->attributes(); 1107 row = reader->readElementText().toInt(); 1108 1109 QModelIndex index; 1110 if (row == -1) 1111 index = model->modelIndexOfAspect(m_project); //-1 corresponds to the project-item (s.a. ProjectExplorer::save()) 1112 else if (row >= aspects.size() || row < 0) // checking for <0 to protect against wrong values in the XML 1113 continue; 1114 else 1115 index = model->modelIndexOfAspect(aspects.at(row)); 1116 1117 if (expandedItem) 1118 expanded.push_back(index); 1119 else if (selectedItem) 1120 selected.push_back(index); 1121 else if (currentItem) 1122 currentIndex = index; 1123 else if (viewItem) { 1124 if (row < 0) // should never happen, but we need to handle the corrupted file 1125 continue; 1126 1127 auto* part = dynamic_cast<AbstractPart*>(aspects.at(row)); 1128 if (!part) 1129 continue; // TODO: add error/warning message here? 1130 1131 Q_EMIT activateView(part); // request to show the view in MainWin 1132 1133 str = attribs.value(QStringLiteral("state")).toString(); 1134 if (str.isEmpty()) 1135 reader->raiseMissingAttributeWarning(QStringLiteral("state")); 1136 else { 1137 part->view()->setWindowState(Qt::WindowStates(str.toInt())); 1138 part->dockWidget()->setWindowState(Qt::WindowStates(str.toInt())); 1139 } 1140 1141 if (str != QLatin1String("0")) 1142 continue; // no geometry settings required for maximized/minimized windows 1143 1144 QRect geometry; 1145 str = attribs.value(QStringLiteral("x")).toString(); 1146 if (str.isEmpty()) 1147 reader->raiseMissingAttributeWarning(QStringLiteral("x")); 1148 else 1149 geometry.setX(str.toInt()); 1150 1151 str = attribs.value(QStringLiteral("y")).toString(); 1152 if (str.isEmpty()) 1153 reader->raiseMissingAttributeWarning(QStringLiteral("y")); 1154 else 1155 geometry.setY(str.toInt()); 1156 1157 str = attribs.value(QStringLiteral("width")).toString(); 1158 if (str.isEmpty()) 1159 reader->raiseMissingAttributeWarning(QStringLiteral("width")); 1160 else 1161 geometry.setWidth(str.toInt()); 1162 1163 str = attribs.value(QStringLiteral("height")).toString(); 1164 if (str.isEmpty()) 1165 reader->raiseMissingAttributeWarning(QStringLiteral("height")); 1166 else 1167 geometry.setHeight(str.toInt()); 1168 1169 part->dockWidget()->setGeometry(geometry); 1170 } 1171 } 1172 } 1173 1174 } else { 1175 while (!reader->atEnd()) { 1176 reader->readNext(); 1177 if (reader->isEndElement() && reader->name() == QLatin1String("state")) 1178 break; 1179 1180 if (!reader->isStartElement()) 1181 continue; 1182 1183 const auto& attribs = reader->attributes(); 1184 const auto& path = attribs.value(QStringLiteral("path")).toString(); 1185 const auto& index = model->modelIndexOfAspect(path); 1186 1187 if (reader->name() == QLatin1String("expanded")) 1188 expanded << index; 1189 else if (reader->name() == QLatin1String("selected")) 1190 selected << index; 1191 else if (reader->name() == QLatin1String("current")) 1192 currentIndex = index; 1193 else if (reader->name() == QLatin1String("view")) { 1194 auto* aspect = static_cast<AbstractAspect*>(index.internalPointer()); 1195 auto* part = dynamic_cast<AbstractPart*>(aspect); 1196 if (!part) 1197 continue; // TODO: add error/warning message here? 1198 1199 Q_EMIT activateView(part); // request to show the view in MainWin 1200 1201 str = attribs.value(QStringLiteral("state")).toString(); 1202 if (str.isEmpty()) 1203 reader->raiseMissingAttributeWarning(QStringLiteral("state")); 1204 else { 1205 part->view()->setWindowState(Qt::WindowStates(str.toInt())); 1206 part->dockWidget()->setWindowState(Qt::WindowStates(str.toInt())); 1207 } 1208 1209 if (str != QLatin1String("0")) 1210 continue; // no geometry settings required for maximized/minimized windows 1211 1212 QRect geometry; 1213 str = attribs.value(QStringLiteral("x")).toString(); 1214 if (str.isEmpty()) 1215 reader->raiseMissingAttributeWarning(QStringLiteral("x")); 1216 else 1217 geometry.setX(str.toInt()); 1218 1219 str = attribs.value(QStringLiteral("y")).toString(); 1220 if (str.isEmpty()) 1221 reader->raiseMissingAttributeWarning(QStringLiteral("y")); 1222 else 1223 geometry.setY(str.toInt()); 1224 1225 str = attribs.value(QStringLiteral("width")).toString(); 1226 if (str.isEmpty()) 1227 reader->raiseMissingAttributeWarning(QStringLiteral("width")); 1228 else 1229 geometry.setWidth(str.toInt()); 1230 1231 str = attribs.value(QStringLiteral("height")).toString(); 1232 if (str.isEmpty()) 1233 reader->raiseMissingAttributeWarning(QStringLiteral("height")); 1234 else 1235 geometry.setHeight(str.toInt()); 1236 1237 part->dockWidget()->setGeometry(geometry); 1238 } 1239 } 1240 } 1241 1242 for (const auto& index : expanded) { 1243 m_treeView->setExpanded(index, true); 1244 collapseParents(index, expanded); // collapse all parent indices if they are not expanded 1245 } 1246 1247 for (const auto& index : selected) 1248 m_treeView->selectionModel()->select(index, QItemSelectionModel::Select | QItemSelectionModel::Rows); 1249 1250 m_treeView->setCurrentIndex(currentIndex); 1251 m_treeView->scrollTo(currentIndex); 1252 auto* aspect = static_cast<AbstractAspect*>(currentIndex.internalPointer()); 1253 if (aspect) 1254 aspect->setSelected(true); 1255 Q_EMIT currentAspectChanged(aspect); // notify MainWin to bring up the proper view 1256 Q_EMIT selectedAspectsChanged(QList<AbstractAspect*>() << aspect); // notify GuiObserver to bring up the proper dock widget 1257 1258 // when setting the current index above it gets expanded, collapse all parent indices if they were not expanded when saved 1259 collapseParents(currentIndex, expanded); 1260 1261 // resize the header of the view to adjust to the content 1262 resizeHeader(); 1263 1264 return true; 1265 } 1266 1267 void ProjectExplorer::collapseParents(const QModelIndex& index, const QList<QModelIndex>& expanded) { 1268 if (index.column() == 0 && index.row() == 0) { // root/project index, doesn't have any parent 1269 if (expanded.indexOf(index) == -1) 1270 m_treeView->collapse(index); 1271 } else { 1272 const auto& parent = index.parent(); 1273 if (parent != QModelIndex() && expanded.indexOf(parent) == -1) 1274 m_treeView->collapse(parent); 1275 } 1276 }