labplot  2.8.2
About: LabPlot is an application for plotting and analysis of 2D and 3D functions and data. It is a complete rewrite of LabPlot1 and lacks in the first release a lot of features available in the predecessor. On the other hand, the GUI and the usability is more superior.
  Fossies Dox: labplot-2.8.2.tar.gz  ("unofficial" and yet experimental doxygen-generated source code documentation)  

ProjectExplorer.cpp
Go to the documentation of this file.
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"
33 #include "backend/core/Project.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 
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 
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 
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 
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.
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 */
235  m_treeView->setModel(treeModel);
236 
237  connect(treeModel, &AspectTreeModel::renameRequested,
238  m_treeView, static_cast<void (QAbstractItemView::*)(const QModelIndex&)>(&QAbstractItemView::edit));
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 
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 
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  */
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  */
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) &&
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 
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 
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 */
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 */
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 
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 
662  filterTextChanged(m_leFilter->text());
663 }
664 
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) ) {
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) ) {
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...
727  setFocus();
728  else
730 }
731 
732 /*!
733  * Used to udpate the cursor Dock
734  */
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 
745  const QModelIndexList items = m_treeView->selectionModel()->selectedIndexes();
746  for (const auto& index : items)
747  m_treeView->setExpanded(index, true);
748 }
749 
751  const QModelIndexList items = m_treeView->selectionModel()->selectedIndexes();
752  for (const auto& index : items)
753  m_treeView->setExpanded(index, false);
754 }
755 
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;
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  */
896  const AspectTreeModel* model = qobject_cast<AspectTreeModel*>(m_treeView->model());
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 }
Base class of all persistent objects in a Project.
void aspectAdded(const AbstractAspect *)
Emitted after a new Aspect has been added to the tree.
@ Recursive
Recursively handle all descendents, not just immediate children.
QString name() const
virtual QMenu * createContextMenu()
Return a new context menu.
bool hidden() const
bool inherits(AspectType type) const
AbstractAspect * parentAspect() const
Return my parent Aspect or 0 if I currently don't have one.
void beginMacro(const QString &text)
Begin an undo stack macro (series of commands)
void setSelected(bool)
bool isLoading() const
QVector< AbstractAspect * > children(AspectType type, ChildIndexFlags flags={}) const
virtual void processDropEvent(const QVector< quintptr > &)
void endMacro()
End the current undo stack macro.
virtual QVector< AspectType > dropableOn() const
Base class of Aspects with MDI windows as views (AspectParts).
Definition: AbstractPart.h:36
Represents a tree of AbstractAspect objects as a Qt item model.
void indexSelected(const QModelIndex &)
QVariant headerData(int section, Qt::Orientation orientation, int role=Qt::DisplayRole) const override
void hiddenAspectSelected(const AbstractAspect *)
void indexDeselected(const QModelIndex &)
QModelIndex modelIndexOfAspect(const AbstractAspect *, int column=0) const
Convenience wrapper around QAbstractItemModel::createIndex().
void renameRequested(const QModelIndex &)
void activateView(AbstractAspect *)
void aspectAdded(const AbstractAspect *)
QAction * caseSensitiveAction
QAction * deleteSelectedTreeAction
void selectionChanged(const QItemSelection &, const QItemSelection &)
void filterTextChanged(const QString &)
ProjectExplorer(QWidget *parent=nullptr)
QAction * toggleFilterAction
bool load(XmlStreamReader *)
Load from XML.
QAction * showAllColumnsAction
void deselectIndex(const QModelIndex &)
QAction * matchCompleteWordAction
void currentAspectChanged(AbstractAspect *)
bool eventFilter(QObject *, QEvent *) override
void currentChanged(const QModelIndex &current, const QModelIndex &previous)
QAction * collapseTreeAction
void hiddenAspectSelected(const AbstractAspect *)
void setProject(Project *)
void keyPressEvent(QKeyEvent *) override
void selectedAspectsChanged(QList< AbstractAspect * > &)
void setCurrentAspect(const AbstractAspect *)
void toggleFilterMatchCompleteWord()
QList< QAction * > list_showColumnActions
void selectIndex(const QModelIndex &)
void toggleFilterOptionsMenu(bool)
QToolButton * bFilterOptions
void save(QXmlStreamWriter *) const
Save the current state of the tree view (expanded items and the currently selected item) as XML.
void navigateTo(const QString &path)
Project * m_project
bool m_changeSelectionFromView
QAction * expandTreeAction
~ProjectExplorer() override
void toggleFilterCaseSensitivity()
bool filter(const QModelIndex &, const QString &)
void collapseParents(const QModelIndex &, const QList< QModelIndex > &expanded)
QLineEdit * m_leFilter
void contextMenuEvent(QContextMenuEvent *) override
QModelIndex currentIndex() const
QAction * collapseSelectedTreeAction
void setModel(AspectTreeModel *)
QFrame * m_frameFilter
QTreeView * m_treeView
QAction * expandSelectedTreeAction
Represents a project.
Definition: Project.h:42
bool aspectAddedSignalSuppressed() const
Definition: Project.cpp:207
void requestNavigateTo(const QString &path)
void requestLoadState(XmlStreamReader *)
QVector< quintptr > droppedAspects(const QMimeData *)
Definition: Project.cpp:360
QMenu * createContextMenu() override
Return a new context menu.
Definition: Project.cpp:154
void requestSaveState(QXmlStreamWriter *) const
void loaded()
Base class for all Worksheet children.
virtual void setVisible(bool on)=0
Show/hide the element.
XML stream parser that supports errors as well as warnings. This class also adds line and column numb...
void raiseWarning(const QString &)
#define i18n(m)
Definition: nsl_common.h:38
Qt::WindowStates state