From d89b617dddb65701fb2b86bd51ebdb54ec0b0725 Mon Sep 17 00:00:00 2001 From: Sofian Audry Date: Wed, 8 Apr 2020 10:10:25 -0400 Subject: [PATCH] Added contextual menus for reorder actions: raise, lower, etc. (closes # #296). --- src/core/Commands.cpp | 43 ++++++++++ src/core/Commands.h | 17 ++++ src/core/MM.h | 7 ++ src/core/MappingManager.cpp | 31 ++++++- src/core/MappingManager.h | 20 ++++- src/core/ProjectReader.cpp | 12 ++- src/gui/MainWindow.cpp | 156 ++++++++++++++++++++++++++++++++-- src/gui/MainWindow.h | 13 +++ src/gui/MappingListModel.cpp | 16 ++-- src/gui/MappingListModel.h | 1 + src/gui/ShapeGraphicsItem.cpp | 3 +- 11 files changed, 300 insertions(+), 19 deletions(-) diff --git a/src/core/Commands.cpp b/src/core/Commands.cpp index 987e6b2..9d33087 100644 --- a/src/core/Commands.cpp +++ b/src/core/Commands.cpp @@ -315,6 +315,49 @@ void DeleteMappingCommand::redo() _mainWindow->deleteMapping(_mappingId); } +MoveMappingCommand::MoveMappingCommand(MainWindow *mainWindow, uid mappingId, MM::MoveElement moveType, QUndoCommand *parent) : + QUndoCommand(parent), + _mainWindow(mainWindow), + _mappingId(mappingId), + _moveType(moveType) +{ + switch (moveType) { + case MM::Raise: setText(QObject::tr("Raise layer")); break; + case MM::Lower: setText(QObject::tr("Lower layer")); break; + case MM::Top: setText(QObject::tr("Raise layer to top")); break; + case MM::Bottom: setText(QObject::tr("Lower layer to bottom")); break; + default:; // should not happen + } +} + +void MoveMappingCommand::undo() +{ + if (!_mapping.isNull()) + { + // Do the inverse move. + _mainWindow->moveMapping(_mappingId, _fromIdx); + } +} + +void MoveMappingCommand::redo() +{ + // Store mapping pointer before delete it + _mapping = _mainWindow->getMappingManager().getMappingById(_mappingId); + _fromIdx = _mainWindow->getMappingManager().getMappingIndex(_mapping); + int maxMappingIdx = _mainWindow->getMappingManager().nMappings()-1; + + switch (_moveType) { + case MM::Raise: _toIdx = qMax(_fromIdx - 1, 0); break; + case MM::Lower: _toIdx = qMin(_fromIdx + 1, maxMappingIdx); break; + case MM::Top: _toIdx = 0; break; + case MM::Bottom: _toIdx = maxMappingIdx; break; + default:; // should not happen + } + + // Do the move. + _mainWindow->moveMapping(_mappingId, _toIdx); +} + FlipShapeCommand::FlipShapeCommand(MapperGLCanvas *canvas, TransformShapeCommand::TransformShapeOption option, const MShape::ptr &initialShape, MShape::FlipDirection direction, QUndoCommand *parent) : TransformShapeCommand (canvas, option, parent), _initialShape(initialShape) diff --git a/src/core/Commands.h b/src/core/Commands.h index e460c4e..0dab6ce 100644 --- a/src/core/Commands.h +++ b/src/core/Commands.h @@ -223,6 +223,23 @@ private: uid _mappingId; }; +class MoveMappingCommand : public QUndoCommand +{ +public: + explicit MoveMappingCommand(MainWindow *mainWindow, uid mappingId, MM::MoveElement moveType, QUndoCommand *parent = 0); + + void undo() Q_DECL_OVERRIDE; + void redo() Q_DECL_OVERRIDE; + +private: + MainWindow *_mainWindow; + Mapping::ptr _mapping; + uid _mappingId; + MM::MoveElement _moveType; + int _fromIdx; + int _toIdx; +}; + } #endif /* COMMANDS_H_ */ diff --git a/src/core/MM.h b/src/core/MM.h index 7116cd0..91928c1 100644 --- a/src/core/MM.h +++ b/src/core/MM.h @@ -152,6 +152,13 @@ public: MediumStep = 2, BigStep = 20 }; + + enum MoveElement { + Raise, + Lower, + Top, + Bottom, + }; }; } diff --git a/src/core/MappingManager.cpp b/src/core/MappingManager.cpp index 539f490..c9b4ef6 100644 --- a/src/core/MappingManager.cpp +++ b/src/core/MappingManager.cpp @@ -140,7 +140,7 @@ uid MappingManager::addMapping(Mapping::ptr mapping) // Make sure the paint to which this mapping refers to exists in the manager. Q_ASSERT ( paintVector.contains(mapping->getPaint()) ); - mappingVector.push_back(mapping); + mappingVector.insert(0, mapping); mappingMap[mapping->getId()] = mapping; return mapping->getId(); @@ -156,6 +156,7 @@ bool MappingManager::removeMapping(uid mappingId) Q_ASSERT( idx != -1 ); // Q_ASSERT(mappingVector.contains(mapping)); mappingVector.remove(idx); mappingMap.remove(mappingId); + updateMappingsDepths(); return true; } @@ -165,6 +166,23 @@ bool MappingManager::removeMapping(uid mappingId) } } +/// Moves a mapping of given uid by a certain number of steps up or down. +bool MappingManager::moveMapping(uid mappingId, int toIndex) +{ + // Make sure the paint to which this mapping refers to exists in the manager. + int idx = getMappingIndex(mappingId); + if (idx >= 0) + { + mappingVector.move(idx, toIndex); + updateMappingsDepths(); + return true; + } + else + { + return false; + } +} + QVector MappingManager::getVisibleMappings() const { QVector visible; @@ -266,6 +284,17 @@ void MappingManager::reorderMappings(QVector mappingIds) } } +void MappingManager::updateMappingsDepths() +{ + int depth = 0; + for (QVector::iterator it = mappingVector.begin(); + it != mappingVector.end(); ++it) + { + (*it)->setDepth(depth); + depth++; + } +} + //bool MappingManager::removeMapping(Mapping::ptr mapping) //{ //} diff --git a/src/core/MappingManager.h b/src/core/MappingManager.h index ebe7c56..9bc74f3 100644 --- a/src/core/MappingManager.h +++ b/src/core/MappingManager.h @@ -66,6 +66,10 @@ public: /// Returns the uid of a paint. uid getPaintId(Paint::ptr paint) const { return paintMap.key(paint); } + /// Returns indices of paint or (-1) if not found. + int getPaintIndex(Paint::ptr paint) const { return paintVector.lastIndexOf(paint); } + int getPaintIndex(uint paintId) const { return getPaintIndex(paintMap[paintId]); } + /// Removes a paint of given uid. bool removePaint(uid paintId); @@ -96,17 +100,20 @@ public: /// Removes a mapping of given uid. bool removeMapping(uid mappingId); + /// Moves a mapping of given uid by a certain number of steps up or down. + bool moveMapping(uid mappingId, int toIndex); + /// Returns the number of mappings. int nMappings() const { return mappingVector.size(); } /** * Returns the i-th mapping in the vector. Good for iterating over all mappings. Vector is - * ordered from bottom to top layer. + * ordered from bottom (deepest) to top (shallowest) layer. */ Mapping::ptr getMapping(int i) { return mappingVector[i]; } /// Returns mapping with given uid. - Mapping::ptr getMappingById(uid id) { return mappingMap[id]; } + Mapping::ptr getMappingById(uid id) const { return mappingMap[id]; } /// Returns mapping with given name (first match). Mapping::ptr getMappingByName(QString name); @@ -114,9 +121,18 @@ public: /// Returns all mappings with given regexp. QVector getMappingsByNameRegExp(QString namePattern); + /// Returns indices of mapping or (-1) if not found. + int getMappingIndex(Mapping::ptr mapping) const { return mappingVector.lastIndexOf(mapping); } + int getMappingIndex(uint mappingId) const { return getMappingIndex(getMappingById(mappingId)); } + + int getMappingDepth(Mapping::ptr mapping) const { return -getMappingIndex(mapping); } + /// Reorders the mappings according to given list of uids. QVector needs to void reorderMappings(QVector mappingIds); + /// Update mapping depths after a move. + void updateMappingsDepths(); + /// Returns the ordered list of visible mappings, using both the "visible" and "solo" properties. QVector getVisibleMappings() const; diff --git a/src/core/ProjectReader.cpp b/src/core/ProjectReader.cpp index 96d3fd3..0cd6e38 100644 --- a/src/core/ProjectReader.cpp +++ b/src/core/ProjectReader.cpp @@ -121,6 +121,7 @@ void ProjectReader::parseProject(const QDomElement& project) // Parse mappings. QDomNode mappingNode = mappings.firstChild(); + QVector allMappings; while (!mappingNode.isNull()) { Mapping::ptr mapping = parseMapping(mappingNode.toElement()); @@ -130,12 +131,19 @@ void ProjectReader::parseProject(const QDomElement& project) } else { - manager.addMapping(mapping); - _window->addMappingItem(mapping->getId()); + allMappings.push_back(mapping); } mappingNode = mappingNode.nextSibling(); } + + // Add all mappings in reverse order. + for (QVector::const_reverse_iterator it = allMappings.rbegin(); + it != allMappings.rend(); ++it) + { + manager.addMapping(*it); + _window->addMappingItem((*it)->getId()); + } } Paint::ptr ProjectReader::parsePaint(const QDomElement& paintElem) diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index f855803..29474ec 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -176,6 +176,10 @@ void MainWindow::handleMappingItemSelectionChanged(const QModelIndex &index) mappingRotate180Action->setEnabled(true); mappingHorizontalFlipAction->setEnabled(true); mappingVerticalFlipAction->setEnabled(true); + mappingRaiseAction->setEnabled(true); + mappingLowerAction->setEnabled(true); + mappingRaiseToTopAction->setEnabled(true); + mappingLowerToBottomAction->setEnabled(true); // Enable zoom action zoomInAction->setEnabled(true); zoomOutAction->setEnabled(true); @@ -211,14 +215,8 @@ void MainWindow::handleMappingItemChanged(const QModelIndex &index) void MainWindow::handleMappingIndexesMoved() { - // Reorder mappings. - QVector newOrder; - for (int row=mappingListModel->rowCount()-1; row>=0; row--) - { - uid layerId = mappingListModel->getIndexFromRow(row).data(Qt::UserRole).toInt(); - newOrder.push_back(layerId); - } - mappingManager->reorderMappings(newOrder); + // Resync mapping manager. + syncMappingManager(); // Update canvases according to new order. updateCanvases(); @@ -992,6 +990,24 @@ void MainWindow::transformActionMappingItem() } +void MainWindow::reorderMappingItem() +{ + QAction *actionSender = qobject_cast(sender()); + + if (actionSender == mappingRaiseAction) { + undoStack->push(new MoveMappingCommand(this, getCurrentMappingId(), MM::Raise)); + } + else if (actionSender == mappingLowerAction) { + undoStack->push(new MoveMappingCommand(this, getCurrentMappingId(), MM::Lower)); + } + else if (actionSender == mappingRaiseToTopAction) { + undoStack->push(new MoveMappingCommand(this, getCurrentMappingId(), MM::Top)); + } + else if (actionSender == mappingLowerToBottomAction) { + undoStack->push(new MoveMappingCommand(this, getCurrentMappingId(), MM::Bottom)); + } +} + void MainWindow::renameMapping(uid mappingId, const QString &name) { Mapping::ptr mapping = mappingManager->getMappingById(mappingId); @@ -1432,6 +1448,15 @@ void MainWindow::deleteMapping(uid mappingId) } } +void MainWindow::moveMapping(uid mappingId, int idx) +{ + // Cannot delete unexisting mapping. + if (Mapping::getUidAllocator().exists(mappingId)) + { + moveMappingItem(mappingId, idx); + } +} + void MainWindow::duplicateMapping(uid mappingId) { // Clone current Mapping. @@ -1855,6 +1880,38 @@ void MainWindow::createActions() addAction(mappingVerticalFlipAction); connect(mappingVerticalFlipAction, SIGNAL(triggered()), SLOT(transformActionMappingItem())); + mappingRaiseAction = new QAction(tr("Raise"), this); + mappingRaiseAction->setShortcut(Qt::Key_PageUp); + mappingRaiseAction->setToolTip(tr("Raise")); + mappingRaiseAction->setIconVisibleInMenu(true); + mappingRaiseAction->setEnabled(false); + addAction(mappingRaiseAction); + connect(mappingRaiseAction, SIGNAL(triggered()), SLOT(reorderMappingItem())); + + mappingLowerAction = new QAction(tr("Lower"), this); + mappingLowerAction->setShortcut(Qt::Key_PageDown); + mappingLowerAction->setToolTip(tr("Lower")); + mappingLowerAction->setIconVisibleInMenu(true); + mappingLowerAction->setEnabled(false); + addAction(mappingLowerAction); + connect(mappingLowerAction, SIGNAL(triggered()), SLOT(reorderMappingItem())); + + mappingRaiseToTopAction = new QAction(tr("Raise to Top"), this); + mappingRaiseToTopAction->setShortcut(Qt::Key_Home); // bottom = end + mappingRaiseToTopAction->setToolTip(tr("Raise to top")); + mappingRaiseToTopAction->setIconVisibleInMenu(true); + mappingRaiseToTopAction->setEnabled(false); + addAction(mappingRaiseToTopAction); + connect(mappingRaiseToTopAction, SIGNAL(triggered()), SLOT(reorderMappingItem())); + + mappingLowerToBottomAction = new QAction(tr("Lower to Bottom"), this); + mappingLowerToBottomAction->setShortcut(Qt::Key_End); + mappingLowerToBottomAction->setToolTip(tr("Lower to bottom")); + mappingLowerToBottomAction->setIconVisibleInMenu(true); + mappingLowerToBottomAction->setEnabled(false); + addAction(mappingLowerToBottomAction); + connect(mappingLowerToBottomAction, SIGNAL(triggered()), SLOT(reorderMappingItem())); + // Delete paint. deletePaintAction = new QAction(tr("Delete Source"), this); //deletePaintAction->setShortcut(tr("CTRL+DEL")); @@ -2304,6 +2361,11 @@ void MainWindow::createMappingContextMenu() mappingContextMenu->addSeparator(); mappingContextMenu->addAction(mappingHorizontalFlipAction); mappingContextMenu->addAction(mappingVerticalFlipAction); + mappingContextMenu->addSeparator(); + mappingContextMenu->addAction(mappingRaiseAction); + mappingContextMenu->addAction(mappingLowerAction); + mappingContextMenu->addAction(mappingRaiseToTopAction); + mappingContextMenu->addAction(mappingLowerToBottomAction); // Set context menu policy mappingList->setContextMenuPolicy(Qt::CustomContextMenu); @@ -2711,6 +2773,10 @@ void MainWindow::updateLayerActions() mappingRotate180Action->setEnabled(true); mappingHorizontalFlipAction->setEnabled(false); mappingVerticalFlipAction->setEnabled(false); + mappingRaiseAction->setEnabled(false); + mappingLowerAction->setEnabled(false); + mappingRaiseToTopAction->setEnabled(false); + mappingLowerToBottomAction->setEnabled(false); //Disable zoom menus zoomInAction->setEnabled(false); zoomOutAction->setEnabled(false); @@ -3052,6 +3118,67 @@ void MainWindow::removeMappingItem(uid mappingId) updatePlayingState(); } +void MainWindow::moveMappingItem(uid mappingId, int idx) +{ + Mapping::ptr mapping = mappingManager->getMappingById(mappingId); + Q_CHECK_PTR(mapping); + + // Remove mapping from model. + qDebug() << "BEF: " << mappingId << " " << mappingManager->getMappingIndex(mapping) << " , " + << idx << endl; + + for (int i=0; inMappings(); i++) + qDebug() << mappingManager->getMapping(i)->getId() << " " ; + qDebug() << endl; + + uid exchangeMappingId = mappingManager->getMapping(idx)->getId(); + + mappingManager->moveMapping(mappingId, idx); + + qDebug() << "AFT: " << mappingManager->getMappingIndex(mapping) << " , " + << idx << endl; + for (int i=0; inMappings(); i++) + qDebug() << mappingManager->getMapping(i)->getId() << " "; + qDebug() << endl; + + // Remove widget from mappingList. + int row = mappingListModel->getItemRowFromId(mappingId); + int rowTo = mappingListModel->getItemRowFromId(exchangeMappingId); + Q_ASSERT( row >= 0 ); + mappingListModel->moveItem(row, idx); + + qDebug() << "INFO: " << idx << " " << row << " " << rowTo << endl; + +// mappingListModel->moveItem(row, mappingManager->nMappings()-1-idx); + + // // TODO: not sure if we need what lies below + // Update list. + mappingListModel->updateModel(); + + QModelIndex index = mappingListModel->getIndexFromRow(idx); + mappingList->selectionModel()->select(index, QItemSelectionModel::Select); + mappingList->setCurrentIndex(index); + // + // if (mappingListModel->rowCount() == 0) + // removeCurrentMapping(); + // else + // { + // int nextSelectedRow = row == mappingListModel->rowCount() ? row - 1 : row; + // QModelIndex index = mappingListModel->getIndexFromRow(nextSelectedRow); + // mappingList->selectionModel()->select(index, QItemSelectionModel::Select); + // mappingList->setCurrentIndex(index); + // } + + // Update everything. + updateCanvases(); + + // Window was modified. + windowModified(); + + // Update playing state. + updatePlayingState(); +} + void MainWindow::removePaintItem(uid paintId) { Paint::ptr paint = mappingManager->getPaintById(paintId); @@ -3103,6 +3230,19 @@ void MainWindow::clearWindow() clearProject(); } +void MainWindow::syncMappingManager() +{ + // Reorder mappings. + QVector newOrder; + for (int row=0; rowrowCount(); row++) +// for (int row=mappingListModel->rowCount()-1; row>=0; row--) + { + uid layerId = mappingListModel->getIndexFromRow(row).data(Qt::UserRole).toInt(); + newOrder.push_back(layerId); + } + mappingManager->reorderMappings(newOrder); +} + bool MainWindow::fileExists(const QString &file) { QFileInfo checkFile(file); diff --git a/src/gui/MainWindow.h b/src/gui/MainWindow.h index 3eb7a1a..530acc2 100644 --- a/src/gui/MainWindow.h +++ b/src/gui/MainWindow.h @@ -116,6 +116,7 @@ private slots: void setMappingItemSolo(bool solo); void loadLayerMedia(); void transformActionMappingItem(); + void reorderMappingItem(); // Context menu for paints void deletePaintItem(); void renamePaintItem(); @@ -227,6 +228,9 @@ public slots: /// Deletes/removes a mapping. void deleteMapping(uid mappingId); + /// Moves a mapping to given index. + void moveMapping(uid mappingId, int idx); + /// Clone/duplicate a mapping void duplicateMapping(uid mappingId); @@ -308,12 +312,15 @@ public: bool addColorPaint(const QColor& color); void addMappingItem(uid mappingId); void removeMappingItem(uid mappingId); + void moveMappingItem(uid mappingId, int steps); void addPaintItem(uid paintId, const QIcon& icon, const QString& name); void updatePaintItem(uid paintId, const QIcon& icon, const QString& name); void removePaintItem(uid paintId); void renameMapping(uid mappingId, const QString& name); void renamePaint(uid paintId, const QString& name); void clearWindow(); + // Resync mapping manager order the same as the GUI. + void syncMappingManager(); // Check if the file exists bool fileExists(const QString& file); // Check if the file is supported @@ -398,6 +405,12 @@ private: QAction *mappingRotate180Action; QAction *mappingHorizontalFlipAction; QAction *mappingVerticalFlipAction; + // Layer reordering. + QAction *mappingRaiseAction; + QAction *mappingLowerAction; + QAction *mappingRaiseToTopAction; + QAction *mappingLowerToBottomAction; + // Paints context menu action QAction *deletePaintAction; QAction *renamePaintAction; diff --git a/src/gui/MappingListModel.cpp b/src/gui/MappingListModel.cpp index f04c761..75d2831 100644 --- a/src/gui/MappingListModel.cpp +++ b/src/gui/MappingListModel.cpp @@ -155,11 +155,8 @@ bool MappingListModel::dropMimeData(const QMimeData *data, Qt::DropAction action int id; stream >> id; - int rows = getItemRowFromId(id); - if (!beginMoveRows(QModelIndex(), rows, rows, QModelIndex(), endRow)) - continue; - mappingList.move(rows, endRow); - endMoveRows(); + int row = getItemRowFromId(id); + moveItem(row, endRow); ++endRow; } @@ -214,6 +211,15 @@ void MappingListModel::removeItem(int index) mappingList.erase(it + index); } +void MappingListModel::moveItem(int row, int endRow) +{ + if (beginMoveRows(QModelIndex(), row, row, QModelIndex(), (row < endRow ? endRow+1 : endRow))) + { + mappingList.move(row, endRow); + endMoveRows(); + } +} + void MappingListModel::addItem(Mapping::ptr mapping, const QIcon &icon, const QString &label) { MappingItem item; diff --git a/src/gui/MappingListModel.h b/src/gui/MappingListModel.h index 42f5a2e..555ebb2d6 100644 --- a/src/gui/MappingListModel.h +++ b/src/gui/MappingListModel.h @@ -57,6 +57,7 @@ public: void removeItem(int index); void addItem(Mapping::ptr mapping, const QIcon &icon, const QString &label); + void moveItem(int row, int endRow); void updateModel(); void clear(); diff --git a/src/gui/ShapeGraphicsItem.cpp b/src/gui/ShapeGraphicsItem.cpp index a95e618..37d94e8 100644 --- a/src/gui/ShapeGraphicsItem.cpp +++ b/src/gui/ShapeGraphicsItem.cpp @@ -52,7 +52,8 @@ void ShapeGraphicsItem::paint(QPainter *painter, // Sync depth of figure with that of mapping (for layered output). if (isOutput()) - setZValue(getMapping()->getDepth()); + setZValue(MainWindow::window()->getMappingManager().getMappingDepth(getMapping())); + //setZValue(getMapping()->getDepth()); // Paint if visible. if (isMappingVisible())