undoCommands.py 5.51 KB
Newer Older
Maxime Perrotin's avatar
Maxime Perrotin committed
1
2
3
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Maxime Perrotin's avatar
Maxime Perrotin committed
4
    OpenGEODE - A tiny SDL Editor for TASTE - Support for Undo/Redo
Maxime Perrotin's avatar
Maxime Perrotin committed
5
6
7
8
9
10
11

    Undo/Redo commands for generic symbols when used in a diagram editor.

    Note when creating a new command:
        the redo() function is *called* when the command is created.
        No need to perform the action before.

Maxime Perrotin's avatar
Maxime Perrotin committed
12
    Copyright (c) 2012-2015 European Space Agency
Maxime Perrotin's avatar
Maxime Perrotin committed
13
14
15
16
17
18
19

    Designed and implemented by Maxime Perrotin

    Contact: maxime.perrotin@esa.int
"""


20
import logging
Maxime Perrotin's avatar
Maxime Perrotin committed
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
from PySide.QtGui import QUndoCommand
from PySide.QtCore import QPropertyAnimation, QEasingCurve, QAbstractAnimation

LOG = logging.getLogger(__name__)


class UndoMacro(object):
    ''' Context manager for Undo macros '''

    def __init__(self, stack, text):
        ''' Create context manager '''
        self.stack = stack
        self.text = text

    def __enter__(self):
        ''' Create macro for a set of undo commands '''
        self.stack.beginMacro(self.text)

    def __exit__(self, atype, value, traceback):
        ''' Stop the recording of undo commands '''
        self.stack.endMacro()


class ReplaceText(QUndoCommand):
    ''' Undo/Redo command for updating the text in a symbol '''
    def __init__(self, text_id, old_text, new_text):
        super(ReplaceText, self).__init__()
        self.setText('Replace text')
        self.text = text_id
        self.old_text = old_text
        self.new_text = new_text

    def undo(self):
        self.text.setPlainText(self.old_text)

    def redo(self):
        self.text.setPlainText(self.new_text)


class ResizeSymbol(QUndoCommand):
    ''' Undo/Redo command for resizing a symbol '''
    def __init__(self, symbol_id, old_rect, new_rect):
        super(ResizeSymbol, self).__init__()
        self.setText('Resize symbol')
        self.symbol = symbol_id
        self.old_rect = old_rect
        self.new_rect = new_rect

    def undo(self):
70
71
72
73
74
75
76
        try:
            self.symbol.resize_item(self.old_rect)
            # Update grabber size
            self.symbol.grabber.display()
        except AttributeError:
            # Not all symbols can be resized
            pass
Maxime Perrotin's avatar
Maxime Perrotin committed
77
78

    def redo(self):
79
80
81
82
83
84
85
        try:
            self.symbol.resize_item(self.new_rect)
            # Update grabber size
            self.symbol.grabber.display()
        except AttributeError:
            # Not all symbols can be resized
            pass
Maxime Perrotin's avatar
Maxime Perrotin committed
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167


class InsertSymbol(QUndoCommand):
    ''' Undo/Redo command for inserting a new item '''
    def __init__(self, item, parent, pos):
        super(InsertSymbol, self).__init__()
        self.item = item
        self.parent = parent
        self.pos_x = pos.x() if pos else None
        self.pos_y = pos.y() if pos else None
        try:
            self.scene = item.scene() or parent.scene()
        except AttributeError:
            LOG.debug('NO SCENE TO INSERT "' + str(item) + '"')

    def undo(self):
        self.pos_x = self.item.x()
        self.pos_y = self.item.y()
        self.item.delete_symbol()
        # Replaced removeItem with hide/show to avoid exit crash
        self.item.hide()

    def redo(self):
        try:
            if self.item not in self.scene.items():
                self.scene.addItem(self.item)
        except AttributeError:
            pass
        self.item.insert_symbol(self.parent, self.pos_x, self.pos_y)
        # Replaced removeItem with hide/show to avoid exit crash
        self.item.show()
        self.item.grabber.display()
        try:
            self.scene.refresh()
        except AttributeError:
            pass


class DeleteSymbol(QUndoCommand):
    ''' Undo/Redo command for a symbol deletion '''
    def __init__(self, item):
        super(DeleteSymbol, self).__init__()
        self.item = item
        self.scene = item.scene()
        self.parent = item.parentItem() if item.hasParent else None
        self.pos_x = 0
        self.pos_y = 0

    def undo(self):
        self.item.insert_symbol(self.parent, self.pos_x, self.pos_y)
        if self.item not in self.scene.items():
            self.scene.addItem(self.item)
        # Replaced removeItem with hide/show to avoid exit crash
        self.item.show()
        self.scene.refresh()

    def redo(self):
        self.pos_x = self.item.x()
        self.pos_y = self.item.y()
        self.item.delete_symbol()
        # Replaced removeItem with hide/show to avoid exit crash
        self.item.hide()
        self.scene.refresh()


class MoveSymbol(QUndoCommand):
    ''' Undo/Redo command for moving symbols '''
    def __init__(self, symbol_id, old_pos, new_pos, animate=False):
        super(MoveSymbol, self).__init__()
        self.setText('Move symbol')
        self.symbol = symbol_id
        self.old_pos = old_pos
        self.new_pos = new_pos
        if animate:
            self.animation = QPropertyAnimation(self.symbol, "position")
            self.animation.setDuration(500)
            self.animation.setStartValue(self.old_pos)
            self.animation.setEndValue(self.new_pos)
            self.animation.setEasingCurve(QEasingCurve.OutCirc)

    def undo(self):
        ''' Undo a symbol move '''
168
        self.symbol.position = self.old_pos
Maxime Perrotin's avatar
Maxime Perrotin committed
169
170
171
172
173
174
175
176
177
178
        try:
            self.symbol.decisionParent.updateConnectionPointPosition()
        except AttributeError:
            pass

    def redo(self):
        ''' Apply a symbol move '''
        try:
            self.animation.start()
        except AttributeError:
179
            self.symbol.position = self.new_pos
Maxime Perrotin's avatar
Maxime Perrotin committed
180
181
182
183
        try:
            self.symbol.decisionParent.updateConnectionPointPosition()
        except AttributeError:
            pass