diff --git a/editor/customelementeditor.cpp b/editor/customelementeditor.cpp index f1da3ca18..1a813385b 100644 --- a/editor/customelementeditor.cpp +++ b/editor/customelementeditor.cpp @@ -143,6 +143,14 @@ void CustomElementEditor::setupMenus() { file_menu -> addSeparator(); file_menu -> addAction(quit); + QAction *undo = ce_scene -> undoStack().createUndoAction(this, tr("Annuler")); + QAction *redo = ce_scene -> undoStack().createRedoAction(this, tr("Refaire")); + undo -> setShortcuts(QKeySequence::Undo); + redo -> setShortcuts(QKeySequence::Redo); + + edit_menu -> addAction(undo); + edit_menu -> addAction(redo); + edit_menu -> addSeparator(); edit_menu -> addAction(selectall); edit_menu -> addAction(deselectall); edit_menu -> addAction(inv_select); @@ -181,7 +189,7 @@ void CustomElementEditor::setupInterface() { // widget par defaut dans le QDockWidget default_informations = new QLabel(); - // panel sur le cote + // panel sur le cote pour editer les parties tools_dock = new QDockWidget(tr("Informations"), this); tools_dock -> setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea); tools_dock -> setFeatures(QDockWidget::AllDockWidgetFeatures); @@ -190,6 +198,15 @@ void CustomElementEditor::setupInterface() { QWidget *info_widget = new QWidget(); info_widget -> setLayout(new QVBoxLayout(info_widget)); tools_dock -> setWidget(info_widget); + + // panel sur le cote pour les annulations + undo_dock = new QDockWidget(tr("Annulations"), this); + undo_dock -> setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea); + undo_dock -> setFeatures(QDockWidget::AllDockWidgetFeatures); + undo_dock -> setMinimumWidth(285); + addDockWidget(Qt::RightDockWidgetArea, undo_dock); + undo_dock -> setWidget(new QUndoView(&(ce_scene -> undoStack()), this)); + slot_updateInformations(); // barre d'etat diff --git a/editor/customelementeditor.h b/editor/customelementeditor.h index d05fed012..8b69ecb02 100644 --- a/editor/customelementeditor.h +++ b/editor/customelementeditor.h @@ -25,6 +25,8 @@ class CustomElementEditor : public QMainWindow { EditorScene *ce_scene; /// container pour les widgets d'edition des parties QDockWidget *tools_dock; + /// container pour la liste des annulations + QDockWidget *undo_dock; /// actions du menu fichier QAction *new_element, *open, *save, *save_as, *quit; /// actions du menu edition diff --git a/editor/editorcommands.cpp b/editor/editorcommands.cpp new file mode 100644 index 000000000..5486aad20 --- /dev/null +++ b/editor/editorcommands.cpp @@ -0,0 +1,125 @@ +#include "editorcommands.h" + +/*** DeletePartsCommand ***/ +/** + Constructeur + @param scene EditorScene concernee + @param parts Liste des parties supprimees + @param parent QUndoCommand parent +*/ +DeletePartsCommand::DeletePartsCommand( + EditorScene *scene, + const QList parts, + QUndoCommand *parent +) : + QUndoCommand(QObject::tr("suppression"), parent), + deleted_parts(parts), + editor_scene(scene) +{ + foreach(QGraphicsItem *qgi, deleted_parts) { + editor_scene -> qgiManager().manage(qgi); + } +} + +/// Destructeur : detruit egalement les parties supprimees +DeletePartsCommand::~DeletePartsCommand() { + foreach(QGraphicsItem *qgi, deleted_parts) { + editor_scene -> qgiManager().release(qgi); + } +} + +/// Restaure les parties supprimees +void DeletePartsCommand::undo() { + foreach(QGraphicsItem *qgi, deleted_parts) { + editor_scene -> addItem(qgi); + } +} + +/// Supprime les parties +void DeletePartsCommand::redo() { + foreach(QGraphicsItem *qgi, deleted_parts) { + editor_scene -> removeItem(qgi); + } +} + +/*** MovePartsCommand ***/ +/** + Constructeur + @param m Mouvement sous forme de QPointF + @param scene EditorScene concernee + @param parts Liste des parties deplacees + @param parent QUndoCommand parent +*/ +MovePartsCommand::MovePartsCommand( + const QPointF &m, + EditorScene *scene, + const QList parts, + QUndoCommand *parent +) : + QUndoCommand(QObject::tr("d\351placement"), parent), + movement(m), + first_redo(true) +{ + moved_parts = parts; + editor_scene = scene; +} + +/// Destructeur +MovePartsCommand::~MovePartsCommand() { +} + +/// Annule le deplacement +void MovePartsCommand::undo() { + foreach(QGraphicsItem *qgi, moved_parts) qgi -> moveBy(-movement.x(), -movement.y()); +} + +/// Refait le deplacement +void MovePartsCommand::redo() { + // le premier appel a redo, lors de la construction de l'objet, ne doit pas se faire + if (first_redo) { + first_redo = false; + return; + } + foreach(QGraphicsItem *qgi, moved_parts) qgi -> moveBy(movement.x(), movement.y()); +} + +/*** AddPartCommand ***/ +/** + Constructeur + @param name Nom de la partie ajoutee + @param parts Liste des parties deplacees + @param parent QUndoCommand parent +*/ +AddPartCommand::AddPartCommand( + const QString &name, + EditorScene *scene, + QGraphicsItem *p, + QUndoCommand *parent +) : + QUndoCommand(QObject::tr("ajout ") + name, parent), + part(p), + editor_scene(scene), + first_redo(true) +{ + editor_scene -> qgiManager().manage(part); +} + +/// Destructeur +AddPartCommand::~AddPartCommand() { + editor_scene -> qgiManager().release(part); +} + +/// Annule le deplacement +void AddPartCommand::undo() { + editor_scene -> removeItem(part); +} + +/// Refait le deplacement +void AddPartCommand::redo() { + // le premier appel a redo, lors de la construction de l'objet, ne doit pas se faire + if (first_redo) { + first_redo = false; + return; + } + editor_scene -> addItem(part); +} diff --git a/editor/editorcommands.h b/editor/editorcommands.h new file mode 100644 index 000000000..0e1479024 --- /dev/null +++ b/editor/editorcommands.h @@ -0,0 +1,85 @@ +#ifndef EDITOR_COMMANDS_H +#define EDITOR_COMMANDS_H +#include "customelementpart.h" +#include "editorscene.h" +#include "qgimanager.h" +#include +/** + Cette classe represente l'action de supprimer une ou plusieurs + parties lors de l'edition d'un element +*/ +class DeletePartsCommand : public QUndoCommand { + // constructeurs, destructeur + public: + DeletePartsCommand(EditorScene *, const QList, QUndoCommand * = 0); + virtual ~DeletePartsCommand(); + private: + DeletePartsCommand(const DeletePartsCommand &); + + // methodes + virtual void undo(); + virtual void redo(); + + // attributs + private: + /// Liste des parties supprimees + QList deleted_parts; + /// scene sur laquelle se produisent les actions + EditorScene *editor_scene; +}; + +/** + Cette classe represente l'action de deplacer une ou plusieurs + parties lors de l'edition d'un element +*/ +class MovePartsCommand : public QUndoCommand { + // constructeurs, destructeur + public: + MovePartsCommand(const QPointF &, EditorScene *, const QList, QUndoCommand * = 0); + virtual ~MovePartsCommand(); + private: + MovePartsCommand(const MovePartsCommand &); + + // methodes + virtual void undo(); + virtual void redo(); + + // attributs + private: + /// Liste des parties supprimees + QList moved_parts; + /// scene sur laquelle se produisent les actions + EditorScene *editor_scene; + /// translation appliquee + QPointF movement; + /// booleen pour eviter d'appeler redo() lors de la construction de l'objet + bool first_redo; +}; + +/** + Cette classe represente l'action de deplacer une ou plusieurs + parties lors de l'edition d'un element +*/ +class AddPartCommand : public QUndoCommand { + // constructeurs, destructeur + public: + AddPartCommand(const QString &, EditorScene *, QGraphicsItem *, QUndoCommand * = 0); + virtual ~AddPartCommand(); + private: + AddPartCommand(const AddPartCommand &); + + // methodes + virtual void undo(); + virtual void redo(); + + // attributs + private: + /// Liste des parties supprimees + QGraphicsItem *part; + /// scene sur laquelle se produisent les actions + EditorScene *editor_scene; + /// booleen pour eviter d'appeler redo() lors de la construction de l'objet + bool first_redo; +}; + +#endif diff --git a/editor/editorscene.cpp b/editor/editorscene.cpp index 905c78657..d9ed6ded1 100644 --- a/editor/editorscene.cpp +++ b/editor/editorscene.cpp @@ -9,6 +9,7 @@ #include "parttextfield.h" #include "partarc.h" #include "hotspoteditor.h" +#include "editorcommands.h" #define GRILLE_X 10 #define GRILLE_Y 10 @@ -16,7 +17,8 @@ EditorScene::EditorScene(QObject *parent) : QGraphicsScene(parent), _width(3), _height(7), - _hotspot(15, 35) + _hotspot(15, 35), + qgi_manager(this) { current_polygon = NULL; connect(this, SIGNAL(changed(const QList &)), this, SLOT(slot_checkSelectionChanged())); @@ -161,38 +163,51 @@ void EditorScene::mouseReleaseEvent(QGraphicsSceneMouseEvent *e) { if (behavior != Polygon && current_polygon != NULL) current_polygon = NULL; if (e -> button() & Qt::LeftButton) { switch(behavior) { - case Normal: - QGraphicsScene::mouseReleaseEvent(e); - break; case Line: + undo_stack.push(new AddPartCommand(tr("ligne"), this, current_line)); break; case Ellipse: current_ellipse -> setRect(current_ellipse -> rect().normalized()); + undo_stack.push(new AddPartCommand(tr("ellipse"), this, current_ellipse)); break; case Arc: current_arc-> setRect(current_arc -> rect().normalized()); + undo_stack.push(new AddPartCommand(tr("arc"), this, current_arc)); break; case Circle: current_circle -> setRect(current_circle -> rect().normalized()); + undo_stack.push(new AddPartCommand(tr("cercle"), this, current_circle)); break; case Terminal: terminal = new PartTerminal(0, this); terminal -> setPos(e -> scenePos()); + undo_stack.push(new AddPartCommand(tr("borne"), this, terminal)); break; case Text: text = new PartText(0, this); text -> setPos(e -> scenePos()); + undo_stack.push(new AddPartCommand(tr("texte"), this, text)); break; case TextField: textfield = new PartTextField(0, this); textfield -> setPos(e -> scenePos()); + undo_stack.push(new AddPartCommand(tr("champ de texte"), this, textfield)); break; + case Normal: default: QGraphicsScene::mouseReleaseEvent(e); + // detecte les deplacements de parties + if (!selectedItems().isEmpty()) { + QPointF movement = e -> scenePos() - e -> buttonDownScenePos(Qt::LeftButton); + if (!movement.isNull()) { + undo_stack.push(new MovePartsCommand(movement, this, selectedItems())); + } + } } } else if (e -> button() & Qt::RightButton) { if (behavior == Polygon) { behavior = Normal; + undo_stack.push(new AddPartCommand(tr("polygone"), this, current_polygon)); current_polygon = NULL; emit(needNormalMode()); } else QGraphicsScene::mouseReleaseEvent(e); @@ -367,6 +382,14 @@ void EditorScene::fromXml(const QDomDocument &xml_document) { } } +QUndoStack &EditorScene::undoStack() { + return(undo_stack); +} + +QGIManager &EditorScene::qgiManager() { + return(qgi_manager); +} + void EditorScene::slot_checkSelectionChanged() { static QList cache_selecteditems = QList(); QList selecteditems = selectedItems(); @@ -392,10 +415,7 @@ void EditorScene::slot_delete() { if (selected_items.isEmpty()) return; // efface tout ce qui est selectionne - foreach(QGraphicsItem *qgi, selected_items) { - removeItem(qgi); - delete qgi; - } + undo_stack.push(new DeletePartsCommand(this, selected_items)); } void EditorScene::slot_editSizeHotSpot() { diff --git a/editor/editorscene.h b/editor/editorscene.h index 49b85fc29..1104831ec 100644 --- a/editor/editorscene.h +++ b/editor/editorscene.h @@ -4,6 +4,7 @@ #include #include "nameslistwidget.h" #include "orientationsetwidget.h" +#include "qgimanager.h" class PartLine; class PartEllipse; class PartCircle; @@ -35,6 +36,10 @@ class EditorScene : public QGraphicsScene { NamesList _names; /// Liste des orientations de l'element OrientationSet ori; + /// Pile des actions annulables + QUndoStack undo_stack; + /// Gestionnaire de QGraphicsItem + QGIManager qgi_manager; /// Variables relatives a la gestion du dessin des parties sur la scene Behavior behavior; @@ -58,6 +63,8 @@ class EditorScene : public QGraphicsScene { void setOrientations(const OrientationSet &); virtual const QDomDocument toXml() const; virtual void fromXml(const QDomDocument &); + QUndoStack &undoStack(); + QGIManager &qgiManager(); protected: virtual void mouseMoveEvent(QGraphicsSceneMouseEvent *); diff --git a/qelectrotech.pro b/qelectrotech.pro index ba2a2b6b7..2df00aaa9 100644 --- a/qelectrotech.pro +++ b/qelectrotech.pro @@ -54,7 +54,9 @@ HEADERS += aboutqet.h \ editor/arceditor.h \ editor/parttextfield.h \ editor/textfieldeditor.h \ - hotspoteditor.h + hotspoteditor.h \ + editor/editorcommands.h \ + qgimanager.h SOURCES += aboutqet.cpp \ borderinset.cpp \ conducer.cpp \ @@ -102,7 +104,9 @@ SOURCES += aboutqet.cpp \ editor/arceditor.cpp \ editor/parttextfield.cpp \ editor/textfieldeditor.cpp \ - hotspoteditor.cpp + hotspoteditor.cpp \ + editor/editorcommands.cpp \ + qgimanager.cpp RESOURCES += qelectrotech.qrc TRANSLATIONS += lang/qet_en.ts lang/qt_fr.ts QT += xml diff --git a/qgimanager.cpp b/qgimanager.cpp new file mode 100644 index 000000000..12db6fa74 --- /dev/null +++ b/qgimanager.cpp @@ -0,0 +1,54 @@ +#include "qgimanager.h" + +/** + Constructeur + @param sc QGraphicsScene a utiliser pour gerer au mieux les QGraphicsItem +*/ +QGIManager::QGIManager(QGraphicsScene *sc) : + scene(sc), + destroy_qgi_on_delete(true) +{ +} + +/** + Destructeur + Lors de sa destruction, le QGI Manager detruit les QGraphicsItem restants + si ceux-ci n'appartiennent pas a la scene ; ce comportement peut etre + change avec la methode setDestroyQGIOnDelete + @see setDestroyQGIOnDelete +*/ +QGIManager::~QGIManager(){ + if (!destroy_qgi_on_delete) return; + foreach(QGraphicsItem *qgi, qgi_manager.keys()) { + if (!scene -> items().contains(qgi)) delete qgi; + } +} + +/** + Demande au QGIManager de gerer un QGI + @param qgi QGraphicsItem a gerer +*/ +void QGIManager::manage(QGraphicsItem *qgi) { + if (qgi_manager.contains(qgi)) ++ qgi_manager[qgi]; + else qgi_manager.insert(qgi, 1); +} + +/** + Indique au QGIManager qu'une reference vers un QGI a ete detruite + S'il n'y a plus de references vers ce QGI et que celui-ci n'est pas present + sur la scene de ce QGIManager, alors il sera detruit. + @param qgi QGraphicsItem a ne plus gerer +*/ +void QGIManager::release(QGraphicsItem *qgi) { + if (!qgi_manager.contains(qgi)) return; + -- qgi_manager[qgi]; + if (!qgi_manager[qgi] && !(scene -> items().contains(qgi))) delete qgi; +} + +/** + Indique au QGIManager de detruire les QGraphicsItem restants lors de sa + destruction si ceux-ci n'appartiennent pas a la scene +*/ +void QGIManager::setDestroyQGIOnDelete(bool b) { + destroy_qgi_on_delete = b; +} diff --git a/qgimanager.h b/qgimanager.h new file mode 100644 index 000000000..8f6f88ac8 --- /dev/null +++ b/qgimanager.h @@ -0,0 +1,26 @@ +#ifndef QGI_MANAGER_H +#define QGI_MANAGER_H +#include +#include +#include +class QGIManager { + // constructeurs, destructeurs + public: + QGIManager(QGraphicsScene *); + virtual ~QGIManager(); + private: + QGIManager(const QGIManager &); + + // attributs + private: + QGraphicsScene *scene; + QHash qgi_manager; + bool destroy_qgi_on_delete; + + //methodes + public: + void manage(QGraphicsItem *); + void release(QGraphicsItem *); + void setDestroyQGIOnDelete(bool); +}; +#endif