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