diff --git a/include/commands.h b/include/commands.h new file mode 100644 index 0000000..8bf1686 --- /dev/null +++ b/include/commands.h @@ -0,0 +1,43 @@ +#ifndef COMMANDS_H +#define COMMANDS_H + +#include +#include + +#include "graphlogic.h" + +class GraphLogic; + + +class NoActiveNodeException : public std::exception +{ +public: + const char* what() const throw(); +}; + +class CannotPlaceNewNodeException : public std::exception +{ +public: + const char* what() const throw(); +}; + + +class InsertNodeCommand : public QUndoCommand +{ + +public: + + InsertNodeCommand(GraphLogic *graphLogic); + + void undo(); + void redo(); + +private: + + GraphLogic *m_graphLogic; + Node *m_node; + QPointF m_pos; + Node *m_activeNode; +}; + +#endif // COMMANDS_H diff --git a/include/graphlogic.h b/include/graphlogic.h index 9863b7c..10442b0 100644 --- a/include/graphlogic.h +++ b/include/graphlogic.h @@ -2,12 +2,15 @@ #define GRAPHLOGIC_H #include +#include #include "node.h" #include "graphwidget.h" +#include "commands.h" class GraphWidget; +class InsertNodeCommand; class GraphLogic : public QObject { @@ -16,6 +19,7 @@ class GraphLogic : public QObject public: explicit GraphLogic(GraphWidget *parent = 0); + void setUndoStack(QUndoStack *stack); bool processKeyEvent(QKeyEvent *event); void addFirstNode(); @@ -28,17 +32,17 @@ public: public slots: // commands from toolbars: - void insertNode(); // will be undoable - void removeNode(); // will be undoable - void nodeEdited(); // will be undoable - void scaleUp(); // will be undoable - void scaleDown(); // will be undoable + void insertNode(); + void removeNode(); /// @todo Rewrite as an undo action + void nodeEdited(); /// @todo Rewrite as an undo action + void scaleUp(); /// @todo Rewrite as an undo action + void scaleDown(); /// @todo Rewrite as an undo action void nodeColor(); void nodeTextColor(); - void addEdge(); // will be undoable - void removeEdge(); // will be undoable + void addEdge(); /// @todo Rewrite as an undo action + void removeEdge(); /// @todo Rewrite as an undo action void hintMode(); - void insertPicture(const QString &picture); // will be undoable + void insertPicture(const QString &picture); /// @todo Rewrite as an undo action void nodeChanged(); void nodeSelected(); @@ -47,7 +51,7 @@ public slots: signals: - void contentChanged(); + void contentChanged(const bool& changed = true); void notification(const QString &msg); private: @@ -57,9 +61,9 @@ private: void moveLeft(); void moveRight(); - void move(const int &x, const int &y); // will be undoable - void setNodeColor(const QColor &color); // will be undoable - void setNodeTextColor(const QColor &color); // will be undoable + void move(const int &x, const int &y); /// @todo Rewrite as an undo action + void setNodeColor(const QColor &color); /// @todo Rewrite as an undo action + void setNodeTextColor(const QColor &color); /// @todo Rewrite as an undo action // hint mode void appendNumber(const int &unm); @@ -93,7 +97,10 @@ private: bool m_edgeDeleting; std::map m_memberMap; + QUndoStack *m_undoStack; + + friend class InsertNodeCommand; }; #endif // GRAPHLOGIC_H diff --git a/include/mainwindow.h b/include/mainwindow.h index 19a6e4f..d51b236 100644 --- a/include/mainwindow.h +++ b/include/mainwindow.h @@ -4,6 +4,7 @@ #include #include #include +#include #include "graphwidget.h" @@ -46,6 +47,7 @@ public slots: // toolbars void showMainToolbar(const bool &show = true); void showStatusIconToolbar(const bool &show = true); + void showUdoToolbar(const bool &show = true); // handle changed content at quit void quit(); @@ -55,13 +57,11 @@ protected: // handle changed content at exit events void closeEvent(QCloseEvent * event); - // show/hide toolbars, otherwise pass on the event to GraphWidget - void keyPressEvent(QKeyEvent *event); - private: - void setUpMainToolbar(); - void setUpStatusIconToolbar(); + void setupMainToolbar(); + void setupStatusIconToolbar(); + void setupEditToolbar(); void setTitle(const QString &title); Ui::MainWindow *m_ui; @@ -99,6 +99,15 @@ private: QAction *m_delegate; QAction *m_maybe; QSignalMapper *m_signalMapper; + + QUndoStack *m_undoStack; + QUndoView *m_undoView; + QAction *m_undo; + QAction *m_redo; + QAction *m_mainToolbar; + QAction *m_iconToolbar; + QAction *m_undoToolbar; + }; #endif // MAINWINDOW_H diff --git a/include/node.h b/include/node.h index d363a7a..356d246 100644 --- a/include/node.h +++ b/include/node.h @@ -23,6 +23,7 @@ public: void addEdge(Edge *edge, bool startsFromThisNode); void deleteEdge(Node *otherEnd); void removeEdgeFromList(Edge *edge); + void removeEdges(); // graph traversal QList edgesFrom(const bool &excludeSecondaries = true) const; diff --git a/qtmindmap.pro b/qtmindmap.pro index b309013..28832ae 100644 --- a/qtmindmap.pro +++ b/qtmindmap.pro @@ -21,7 +21,8 @@ SOURCES += src/main.cpp \ src/node.cpp \ src/edge.cpp \ src/systemtray.cpp \ - src/argumentparser.cpp + src/argumentparser.cpp \ + src/commands.cpp HEADERS += include/mainwindow.h \ @@ -30,7 +31,8 @@ HEADERS += include/mainwindow.h \ include/node.h \ include/edge.h \ include/systemtray.h \ - include/argumentparser.h + include/argumentparser.h \ + include/commands.h FORMS += ui/mainwindow.ui diff --git a/src/commands.cpp b/src/commands.cpp new file mode 100644 index 0000000..9c50744 --- /dev/null +++ b/src/commands.cpp @@ -0,0 +1,77 @@ +#include "include/commands.h" + +#include + +#include + +InsertNodeCommand::InsertNodeCommand(GraphLogic *graphLogic) + : m_graphLogic(graphLogic) + , m_node(0) + , m_activeNode(m_graphLogic->m_activeNode) +{ + if (!m_activeNode) + throw NoActiveNodeException(); + + setText(QObject::tr("New node added to ").append( + m_activeNode == m_graphLogic->m_nodeList.first() ? + QObject::tr("Base node") : + QString("\"").append(m_activeNode->toPlainText().append("\"")))); + + m_graphLogic->nodeLostFocus(); + + // get the biggest angle between the edges of the Node. + double angle(m_activeNode->calculateBiggestAngle()); + + // let the distance between the current and new Node be 100 pixels + qreal length(100); + + m_pos = m_activeNode->sceneBoundingRect().center() + + QPointF(length * cos(angle), length * sin(angle)) - + Node::newNodeCenter; + + QRectF rect (m_graphLogic->m_graphWidget->scene()->sceneRect().topLeft(), + m_graphLogic->m_graphWidget->scene()->sceneRect().bottomRight() + - Node::newNodeBottomRigth); + + if (!rect.contains(m_pos)) + throw CannotPlaceNewNodeException(); + + // add a new node which inherits the color and textColor + m_node = m_graphLogic->nodeFactory(); + m_node->setColor(m_activeNode->color()); + m_node->setTextColor(m_activeNode->textColor()); + m_node->setHtml(QString("")); +} + +void InsertNodeCommand::undo() +{ + m_graphLogic->m_nodeList.removeAll(m_node); + m_graphLogic->m_graphWidget->scene()->removeItem(m_node); + + m_node->removeEdges(); + + m_graphLogic->setActiveNode(m_activeNode); + + emit m_graphLogic->contentChanged(false); +} + +void InsertNodeCommand::redo() +{ + m_graphLogic->m_graphWidget->scene()->addItem(m_node); + m_graphLogic->m_nodeList.append(m_node); + + m_node->setPos(m_pos); + + m_graphLogic->addEdge(m_activeNode, m_node); + + m_graphLogic->setActiveNode(m_node); + + if (m_graphLogic->m_graphWidget->hasFocus()) + m_graphLogic->nodeEdited(); + + emit m_graphLogic->contentChanged(); + + // it we are in hint mode, the numbers shall be re-calculated + if (m_graphLogic->m_showingNodeNumbers) + m_graphLogic->showNodeNumbers(); +} diff --git a/src/graphlogic.cpp b/src/graphlogic.cpp index 728caf6..85f9b03 100644 --- a/src/graphlogic.cpp +++ b/src/graphlogic.cpp @@ -4,6 +4,9 @@ #include #include #include +#include + +#include "include/commands.h" GraphLogic::GraphLogic(GraphWidget *parent) : QObject(parent) @@ -53,6 +56,11 @@ GraphLogic::GraphLogic(GraphWidget *parent) (Qt::Key_Delete, &GraphLogic::removeNode)); } +void GraphLogic::setUndoStack(QUndoStack *stack) +{ + m_undoStack = stack; +} + bool GraphLogic::processKeyEvent(QKeyEvent *event) { if (event->key() == Qt::Key_Escape) @@ -99,6 +107,8 @@ bool GraphLogic::processKeyEvent(QKeyEvent *event) void GraphLogic::addFirstNode() { Node *node = nodeFactory(); + m_graphWidget->scene()->addItem(node); + m_nodeList.append(node); node->setHtml( QString("")); @@ -145,6 +155,8 @@ bool GraphLogic::readContentFromXmlFile(const QString &fileName) if(!e.isNull()) { Node *node = nodeFactory(); + m_graphWidget->scene()->addItem(node); + m_nodeList.append(node); node->setHtml(e.attribute("htmlContent")); node->setPos(e.attribute("x").toFloat(), e.attribute("y").toFloat()); @@ -282,52 +294,16 @@ void GraphLogic::writeContentToPngFile(const QString &fileName) void GraphLogic::insertNode() { - nodeLostFocus(); - - if (!m_activeNode) + try { - emit notification(tr("No active node.")); - return; + QUndoCommand *insertNodeCommand = new InsertNodeCommand(this); + m_undoStack->push(insertNodeCommand); } - - // get the biggest angle between the edges of the Node. - double angle(m_activeNode->calculateBiggestAngle()); - - // let the distance between the current and new Node be 100 pixels - qreal length(100); - - QPointF pos(length * cos(angle), length * sin(angle)); - - QPointF newPos(m_activeNode->sceneBoundingRect().center() + - pos - Node::newNodeCenter); - QRectF rect (m_graphWidget->scene()->sceneRect().topLeft(), - m_graphWidget->scene()->sceneRect().bottomRight() - - Node::newNodeBottomRigth); - - if (!rect.contains(newPos)) + catch (std::exception &e) { - emit notification(tr("New node would be placed outside of the scene")); + emit notification(e.what()); return; } - - // add a new node which inherits the color and textColor - Node *node = nodeFactory(); - node->setColor(m_activeNode->color()); - node->setTextColor(m_activeNode->textColor()); - node->setHtml(QString("")); - node->setPos(newPos); - - addEdge(m_activeNode, node); - - // set it the active Node and editable, so the user can edit it at once - setActiveNode(node); - nodeEdited(); - - emit contentChanged(); - - // it we are in hint mode, the numbers shall be re-calculated - if (m_showingNodeNumbers) - showNodeNumbers(); } void GraphLogic::removeNode() @@ -695,9 +671,6 @@ Node * GraphLogic::nodeFactory() this, SLOT(nodeMoved(QGraphicsSceneMouseEvent*))); connect(node, SIGNAL(nodeLostFocus()), this, SLOT(nodeLostFocus())); - m_graphWidget->scene()->addItem(node); - m_nodeList.append(node); - return node; } diff --git a/src/graphwidget.cpp b/src/graphwidget.cpp index 27550d0..6dd8f57 100644 --- a/src/graphwidget.cpp +++ b/src/graphwidget.cpp @@ -28,6 +28,8 @@ GraphWidget::GraphWidget(MainWindow *parent) m_graphlogic = new GraphLogic(this); } + + void GraphWidget::newScene() { m_graphlogic->removeAllNodes(); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 5497d8d..e3d930c 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -27,18 +27,21 @@ MainWindow::MainWindow(QWidget *parent) : setCentralWidget(m_graphicsView); m_graphicsView->hide(); - connect(m_graphicsView->graphLogic(), SIGNAL(contentChanged()), - this, SLOT(contentChanged())); + connect(m_graphicsView->graphLogic(), SIGNAL(contentChanged(const bool&)), + this, SLOT(contentChanged(const bool&))); connect(m_graphicsView->graphLogic(), SIGNAL(notification(QString)), this, SLOT(statusBarMsg(QString))); // setup toolbars, don't show them - setUpMainToolbar(); + setupMainToolbar(); m_ui->mainToolBar->hide(); - setUpStatusIconToolbar(); + setupStatusIconToolbar(); m_ui->statusIcons_toolBar->hide(); + + setupEditToolbar(); + m_ui->undoToolBar->hide(); } MainWindow::~MainWindow() @@ -270,6 +273,13 @@ void MainWindow::showStatusIconToolbar(const bool &show) false); } +void MainWindow::showUdoToolbar(const bool &show) +{ + m_ui->undoToolBar->setVisible(show ? + !m_ui->undoToolBar->isVisible() : + false); +} + void MainWindow::quit() { if (m_contentChanged && !closeFile()) @@ -285,27 +295,7 @@ void MainWindow::closeEvent(QCloseEvent * event) event->accept(); } -void MainWindow::keyPressEvent(QKeyEvent *event) -{ - // inactive action does not listen to signals - if (event->modifiers() & Qt::ControlModifier) - { - if (event->key() == Qt::Key_M) - { - showMainToolbar(); - return; - } - if (event->key() == Qt::Key_I) - { - showStatusIconToolbar(); - return; - } - } - - QMainWindow::keyPressEvent(event); -} - -void MainWindow::setUpMainToolbar() +void MainWindow::setupMainToolbar() { // why can't I do this with qtcreator? (adding actions to toolbar) @@ -368,7 +358,6 @@ void MainWindow::setUpMainToolbar() SLOT(hintMode())); m_showMainToolbar = new QAction(tr("Show main toolbar\n(Ctrl m)"), this); - m_showMainToolbar->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_M)); connect(m_showMainToolbar, SIGNAL(activated()), this, SLOT(showMainToolbar())); @@ -398,7 +387,7 @@ void MainWindow::setUpMainToolbar() m_ui->mainToolBar->addAction(m_showStatusIconToolbar); } -void MainWindow::setUpStatusIconToolbar() +void MainWindow::setupStatusIconToolbar() { // map signals so actions can send icon name m_signalMapper = new QSignalMapper(this); @@ -465,11 +454,46 @@ void MainWindow::setUpStatusIconToolbar() m_ui->statusIcons_toolBar->setToolButtonStyle(Qt::ToolButtonTextUnderIcon); } +void MainWindow::setupEditToolbar() +{ + m_undoStack = new QUndoStack(this); + m_undoView = new QUndoView(m_undoStack,this); + m_ui->undoToolBar->addWidget(m_undoView); + + m_undo = m_undoStack->createUndoAction(this, tr("&Undo")); + m_undo->setShortcuts(QKeySequence::Undo); + m_redo = m_undoStack->createRedoAction(this, tr("&Redo")); + m_redo->setShortcuts(QKeySequence::Redo); + m_ui->menuEdit->addAction(m_undo); + m_ui->menuEdit->addAction(m_redo); + + m_ui->menuEdit->addSeparator(); + + m_mainToolbar = new QAction(tr("main toolbar"), this); + m_mainToolbar->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_M)); + connect(m_mainToolbar, SIGNAL(activated()), + this, SLOT (showMainToolbar())); + + m_iconToolbar = new QAction(tr("icon toolbar"), this); + m_iconToolbar->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_I)); + connect(m_iconToolbar, SIGNAL(activated()), + this, SLOT (showStatusIconToolbar())); + + m_undoToolbar = new QAction(tr("undo toolbar"), this); + m_undoToolbar->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_U)); + connect(m_undoToolbar, SIGNAL(activated()), + this, SLOT (showUdoToolbar())); + + m_ui->menuEdit->addAction(m_mainToolbar); + m_ui->menuEdit->addAction(m_iconToolbar); + m_ui->menuEdit->addAction(m_undoToolbar); + + m_graphicsView->graphLogic()->setUndoStack(m_undoStack); +} + void MainWindow::setTitle(const QString &title) { title.isEmpty() ? setWindowTitle("QtMindMap") : setWindowTitle(QString(title).append(" - QtMindMap")); } - - diff --git a/src/node.cpp b/src/node.cpp index a21bbd0..50e5a4c 100644 --- a/src/node.cpp +++ b/src/node.cpp @@ -35,16 +35,7 @@ Node::Node() Node::~Node() { - // dtor of Edge will call removeEdgeFromList on booth nodes. - foreach (EdgeElement element, m_edgeList) - { - Edge *tmp = element.edge; - tmp->sourceNode()->removeEdgeFromList(tmp); - tmp->destNode()->removeEdgeFromList(tmp); - - /// @bug crashes sometimes - delete tmp; - } + removeEdges(); } void Node::addEdge(Edge *edge, bool startsFromThisNode) @@ -85,6 +76,19 @@ void Node::removeEdgeFromList(Edge *edge) } } +void Node::removeEdges() +{ + foreach (EdgeElement element, m_edgeList) + { + Edge *tmp = element.edge; + tmp->sourceNode()->removeEdgeFromList(tmp); + tmp->destNode()->removeEdgeFromList(tmp); + + /// @bug crashes sometimes + delete tmp; + } +} + // edges from this Node. Exclude secondaries if needed (calc subtree) QList Node::edgesFrom(const bool &excludeSecondaries) const { diff --git a/ui/mainwindow.ui b/ui/mainwindow.ui index 9fea0ac..94ec5b5 100644 --- a/ui/mainwindow.ui +++ b/ui/mainwindow.ui @@ -14,7 +14,7 @@ QtMindMap - + :/qtmindmap.svg:/qtmindmap.svg @@ -24,7 +24,7 @@ 0 0 600 - 23 + 21 @@ -47,7 +47,13 @@ + + + &Edit + + + @@ -76,6 +82,17 @@ false + + + undo toolbar + + + LeftToolBarArea + + + true + + true @@ -163,10 +180,20 @@ &Keys + + + Undo + + + + + Redo + + - +