shinestacker 1.4.0__py3-none-any.whl → 1.5.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of shinestacker might be problematic. Click here for more details.

@@ -1,6 +1,6 @@
1
1
  # pylint: disable=C0114, C0115, C0116, R0904, R0915, E0611, R0902, R0911, R0914, E1003
2
2
  from PySide6.QtCore import Qt, Signal, QEvent, QRectF
3
- from PySide6.QtGui import QPixmap, QPen, QColor, QCursor
3
+ from PySide6.QtGui import QPen, QColor, QCursor
4
4
  from PySide6.QtWidgets import QWidget, QHBoxLayout, QVBoxLayout, QFrame, QGraphicsEllipseItem
5
5
  from .. config.gui_constants import gui_constants
6
6
  from .view_strategy import ViewStrategy, ImageGraphicsViewBase, ViewSignals
@@ -11,6 +11,7 @@ class ImageGraphicsView(ImageGraphicsViewBase):
11
11
  mouse_moved = Signal(QEvent)
12
12
  mouse_released = Signal(QEvent)
13
13
  gesture_event = Signal(QEvent)
14
+ wheel_event = Signal(QEvent)
14
15
 
15
16
  # pylint: disable=C0103
16
17
  def event(self, event):
@@ -29,6 +30,10 @@ class ImageGraphicsView(ImageGraphicsViewBase):
29
30
  def mouseReleaseEvent(self, event):
30
31
  self.mouse_released.emit(event)
31
32
  super().mouseReleaseEvent(event)
33
+
34
+ def wheelEvent(self, event):
35
+ self.wheel_event.emit(event)
36
+ event.accept()
32
37
  # pylint: enable=C0103
33
38
 
34
39
 
@@ -46,7 +51,6 @@ class DoubleViewBase(ViewStrategy, QWidget, ViewSignals):
46
51
  self._connect_signals()
47
52
  self.panning_current = False
48
53
  self.brush_cursor = None
49
- self.setup_brush_cursor()
50
54
  self.setFocusPolicy(Qt.StrongFocus)
51
55
  self.pan_start = None
52
56
  self.pinch_start_scale = None
@@ -58,12 +62,11 @@ class DoubleViewBase(ViewStrategy, QWidget, ViewSignals):
58
62
  self.setup_current_brush_cursor()
59
63
 
60
64
  def setup_layout(self):
61
- """To be implemented by subclasses - creates layout and adds widgets"""
62
65
  raise NotImplementedError("Subclasses must implement setup_layout")
63
66
 
64
67
  def create_pixmaps(self):
65
- self.current_pixmap_item = self.create_pixmap(self.current_scene)
66
- self.master_pixmap_item = self.create_pixmap(self.master_scene)
68
+ self.pixmap_item_current = self.create_pixmap(self.current_scene)
69
+ self.pixmap_item_master = self.create_pixmap(self.master_scene)
67
70
 
68
71
  def _connect_signals(self):
69
72
  self.current_view.mouse_pressed.connect(self.handle_current_mouse_press)
@@ -82,6 +85,8 @@ class DoubleViewBase(ViewStrategy, QWidget, ViewSignals):
82
85
  self.current_view.horizontalScrollBar().setValue)
83
86
  self.master_view.verticalScrollBar().valueChanged.connect(
84
87
  self.current_view.verticalScrollBar().setValue)
88
+ self.current_view.wheel_event.connect(self.handle_wheel_event)
89
+ self.master_view.wheel_event.connect(self.handle_wheel_event)
85
90
  # pylint: disable=C0103, W0201
86
91
  self.current_view.enterEvent = self.current_view_enter_event
87
92
  self.current_view.leaveEvent = self.current_view_leave_event
@@ -92,31 +97,44 @@ class DoubleViewBase(ViewStrategy, QWidget, ViewSignals):
92
97
  def current_view_enter_event(self, event):
93
98
  self.activateWindow()
94
99
  self.setFocus()
95
- self.update_brush_cursor()
100
+ if not self.empty():
101
+ self.update_brush_cursor()
96
102
  super(ImageGraphicsView, self.current_view).enterEvent(event)
97
103
 
98
104
  def current_view_leave_event(self, event):
99
- self.update_brush_cursor()
105
+ if not self.empty():
106
+ self.update_brush_cursor()
100
107
  super(ImageGraphicsView, self.current_view).leaveEvent(event)
101
108
 
102
109
  def master_view_enter_event(self, event):
103
110
  self.activateWindow()
104
111
  self.setFocus()
105
- self.update_brush_cursor()
112
+ if not self.empty():
113
+ self.update_brush_cursor()
106
114
  super(ImageGraphicsView, self.master_view).enterEvent(event)
107
115
 
108
116
  def master_view_leave_event(self, event):
109
- self.update_brush_cursor()
117
+ if not self.empty():
118
+ self.update_brush_cursor()
110
119
  super(ImageGraphicsView, self.master_view).leaveEvent(event)
111
120
 
112
121
  def get_master_view(self):
113
122
  return self.master_view
114
123
 
124
+ def get_current_view(self):
125
+ return self.current_view
126
+
115
127
  def get_master_scene(self):
116
128
  return self.master_scene
117
129
 
130
+ def get_current_scene(self):
131
+ return self.current_scene
132
+
118
133
  def get_master_pixmap(self):
119
- return self.master_pixmap_item
134
+ return self.pixmap_item_master
135
+
136
+ def get_current_pixmap(self):
137
+ return self.pixmap_item_current
120
138
 
121
139
  def get_views(self):
122
140
  return [self.master_view, self.current_view]
@@ -126,8 +144,8 @@ class DoubleViewBase(ViewStrategy, QWidget, ViewSignals):
126
144
 
127
145
  def get_pixmaps(self):
128
146
  return {
129
- self.master_pixmap_item: self.master_view,
130
- self.current_pixmap_item: self.current_view
147
+ self.pixmap_item_master: self.master_view,
148
+ self.pixmap_item_current: self.current_view
131
149
  }
132
150
 
133
151
  # pylint: disable=C0103
@@ -153,22 +171,33 @@ class DoubleViewBase(ViewStrategy, QWidget, ViewSignals):
153
171
  def enterEvent(self, event):
154
172
  self.activateWindow()
155
173
  self.setFocus()
156
- if not self.empty():
174
+ if self.empty():
175
+ self.setCursor(Qt.ArrowCursor)
176
+ self.master_view.setCursor(Qt.ArrowCursor)
177
+ self.current_view.setCursor(Qt.ArrowCursor)
178
+ else:
157
179
  if self.space_pressed:
158
180
  self.master_view.setCursor(Qt.OpenHandCursor)
181
+ self.current_view.setCursor(Qt.OpenHandCursor)
159
182
  else:
160
183
  self.master_view.setCursor(Qt.BlankCursor)
184
+ self.current_view.setCursor(Qt.BlankCursor)
161
185
  if self.brush_cursor:
162
186
  self.brush_cursor.show()
163
187
  super().enterEvent(event)
164
188
 
165
189
  def leaveEvent(self, event):
166
- if self.brush_cursor:
167
- self.brush_cursor.hide()
168
- if self.current_brush_cursor:
169
- self.current_brush_cursor.hide()
170
- self.master_view.setCursor(Qt.ArrowCursor)
171
- self.current_view.setCursor(Qt.ArrowCursor)
190
+ if self.empty():
191
+ self.setCursor(Qt.ArrowCursor)
192
+ self.master_view.setCursor(Qt.ArrowCursor)
193
+ self.current_view.setCursor(Qt.ArrowCursor)
194
+ else:
195
+ if self.brush_cursor is not None:
196
+ self.brush_cursor.hide()
197
+ if self.current_brush_cursor is not None:
198
+ self.current_brush_cursor.hide()
199
+ self.master_view.setCursor(Qt.ArrowCursor)
200
+ self.current_view.setCursor(Qt.ArrowCursor)
172
201
  super().leaveEvent(event)
173
202
 
174
203
  def keyPressEvent(self, event):
@@ -182,6 +211,40 @@ class DoubleViewBase(ViewStrategy, QWidget, ViewSignals):
182
211
  self.update_brush_cursor()
183
212
  # pylint: enable=C0103
184
213
 
214
+ # pylint: enable=R0801
215
+ def handle_wheel_event(self, event):
216
+ if self.empty() or self.gesture_active:
217
+ return
218
+ if event.source() == Qt.MouseEventNotSynthesized: # Physical mouse
219
+ if self.control_pressed:
220
+ self.brush_size_change_requested.emit(1 if event.angleDelta().y() > 0 else -1)
221
+ else:
222
+ if event.angleDelta().y() > 0: # Zoom in
223
+ self.zoom_in()
224
+ else: # Zoom out
225
+ self.zoom_out()
226
+ else: # Touchpad event - handle scrolling
227
+ if not self.control_pressed:
228
+ delta = event.pixelDelta() or event.angleDelta() / 8
229
+ if delta:
230
+ self.scroll_view(self.master_view, delta.x(), delta.y())
231
+ self.scroll_view(self.current_view, delta.x(), delta.y())
232
+ # pylint: disable=R0801
233
+
234
+ def _apply_zoom_to_view(self, view, factor):
235
+ view.scale(factor, factor)
236
+ other_view = self.current_view if view == self.master_view else self.master_view
237
+ other_view.resetTransform()
238
+ other_view.scale(self.zoom_factor(), self.zoom_factor())
239
+
240
+ def show_master(self):
241
+ self.pixmap_item_master.setVisible(True)
242
+ self.pixmap_item_current.setVisible(True)
243
+
244
+ def show_current(self):
245
+ self.pixmap_item_master.setVisible(True)
246
+ self.pixmap_item_current.setVisible(True)
247
+
185
248
  def setup_brush_cursor(self):
186
249
  super().setup_brush_cursor()
187
250
  self.setup_current_brush_cursor()
@@ -194,7 +257,7 @@ class DoubleViewBase(ViewStrategy, QWidget, ViewSignals):
194
257
  if isinstance(item, QGraphicsEllipseItem) and item != self.brush_preview:
195
258
  self.current_scene.removeItem(item)
196
259
  pen_width = gui_constants.BRUSH_LINE_WIDTH / self.zoom_factor()
197
- pen = QPen(QColor(255, 0, 0), pen_width)
260
+ pen = QPen(QColor(255, 0, 0), pen_width, Qt.DotLine)
198
261
  brush = Qt.NoBrush
199
262
  self.current_brush_cursor = self.current_scene.addEllipse(
200
263
  0, 0, self.brush.size, self.brush.size, pen, brush)
@@ -214,15 +277,12 @@ class DoubleViewBase(ViewStrategy, QWidget, ViewSignals):
214
277
  self.current_brush_cursor.hide()
215
278
 
216
279
  def update_cursor_pen_width(self):
217
- if not self.brush_cursor or not self.current_brush_cursor:
218
- return
219
- pen_width = gui_constants.BRUSH_LINE_WIDTH / self.zoom_factor()
220
- master_pen = self.brush_cursor.pen()
221
- master_pen.setWidthF(pen_width)
222
- self.brush_cursor.setPen(master_pen)
223
- current_pen = self.current_brush_cursor.pen()
224
- current_pen.setWidthF(pen_width)
225
- self.current_brush_cursor.setPen(current_pen)
280
+ pen_width = super().update_cursor_pen_width()
281
+ if self.current_brush_cursor:
282
+ current_pen = self.current_brush_cursor.pen()
283
+ current_pen.setWidthF(pen_width)
284
+ self.current_brush_cursor.setPen(current_pen)
285
+ return pen_width
226
286
 
227
287
  def update_brush_cursor(self):
228
288
  if self.empty():
@@ -234,6 +294,7 @@ class DoubleViewBase(ViewStrategy, QWidget, ViewSignals):
234
294
  current_has_mouse = self.current_view.rect().contains(mouse_pos_current)
235
295
  master_has_mouse = self.master_view.rect().contains(mouse_pos_master)
236
296
  if master_has_mouse:
297
+ self.brush_preview.show()
237
298
  super().update_brush_cursor()
238
299
  self.sync_current_cursor_with_master()
239
300
  if self.space_pressed:
@@ -244,21 +305,16 @@ class DoubleViewBase(ViewStrategy, QWidget, ViewSignals):
244
305
  self.master_view.setCursor(Qt.BlankCursor)
245
306
  self.current_view.setCursor(Qt.BlankCursor)
246
307
  elif current_has_mouse:
308
+ self.brush_preview.hide()
247
309
  scene_pos = self.current_view.mapToScene(mouse_pos_current)
248
310
  size = self.brush.size
249
311
  radius = size / 2
250
312
  self.current_brush_cursor.setRect(
251
- scene_pos.x() - radius,
252
- scene_pos.y() - radius,
253
- size, size
254
- )
313
+ scene_pos.x() - radius, scene_pos.y() - radius, size, size)
255
314
  self.current_brush_cursor.show()
256
315
  if self.brush_cursor:
257
316
  self.brush_cursor.setRect(
258
- scene_pos.x() - radius,
259
- scene_pos.y() - radius,
260
- size, size
261
- )
317
+ scene_pos.x() - radius, scene_pos.y() - radius, size, size)
262
318
  self.brush_cursor.show()
263
319
  if self.space_pressed:
264
320
  cursor_style = Qt.OpenHandCursor \
@@ -316,93 +372,48 @@ class DoubleViewBase(ViewStrategy, QWidget, ViewSignals):
316
372
  self.handle_pinch_gesture(pinch_gesture)
317
373
  event.accept()
318
374
 
319
- def handle_pinch_gesture(self, pinch):
320
- if pinch.state() == Qt.GestureStarted:
321
- self.pinch_start_scale = self.zoom_factor()
322
- self.pinch_center_view = pinch.centerPoint()
323
- self.pinch_center_scene = self.master_view.mapToScene(self.pinch_center_view.toPoint())
324
- elif pinch.state() == Qt.GestureUpdated:
325
- new_scale = self.pinch_start_scale * pinch.totalScaleFactor()
326
- new_scale = max(self.min_scale(), min(new_scale, self.max_scale()))
327
- if abs(new_scale - self.zoom_factor()) > 0.01:
328
- self.set_zoom_factor(new_scale)
329
- self._apply_zoom()
330
- new_center = self.master_view.mapToScene(self.pinch_center_view.toPoint())
331
- delta = self.pinch_center_scene - new_center
332
- h_scroll = self.master_view.horizontalScrollBar().value()
333
- v_scroll = self.master_view.verticalScrollBar().value()
334
- self.master_view.horizontalScrollBar().setValue(
335
- h_scroll + int(delta.x() * self.zoom_factor()))
336
- self.master_view.verticalScrollBar().setValue(
337
- v_scroll + int(delta.y() * self.zoom_factor()))
338
-
339
375
  def set_master_image(self, qimage):
340
376
  self.status.set_master_image(qimage)
341
377
  pixmap = self.status.pixmap_master
342
- img_width, img_height = pixmap.width(), pixmap.height()
343
378
  self.master_view.setSceneRect(QRectF(pixmap.rect()))
344
- self.master_pixmap_item.setPixmap(pixmap)
345
- self.set_min_scale(min(gui_constants.MIN_ZOOMED_IMG_WIDTH / img_width,
346
- gui_constants.MIN_ZOOMED_IMG_HEIGHT / img_height))
347
- self.set_max_scale(gui_constants.MAX_ZOOMED_IMG_PX_SIZE)
379
+ self.pixmap_item_master.setPixmap(pixmap)
380
+ img_width, img_height = pixmap.width(), pixmap.height()
381
+ self.set_max_min_scales(img_width, img_height)
348
382
  self.set_zoom_factor(1.0)
349
- self.master_view.fitInView(self.master_pixmap_item, Qt.KeepAspectRatio)
383
+ self.master_view.fitInView(self.pixmap_item_master, Qt.KeepAspectRatio)
350
384
  self.set_zoom_factor(self.get_current_scale())
351
385
  self.set_zoom_factor(max(self.min_scale(), min(self.max_scale(), self.zoom_factor())))
352
386
  self.master_view.resetTransform()
353
387
  self.master_scene.scale(self.zoom_factor(), self.zoom_factor())
354
- self.master_view.centerOn(self.master_pixmap_item)
388
+ self.master_view.centerOn(self.pixmap_item_master)
355
389
  center = self.master_scene.sceneRect().center()
356
390
  self.brush_preview.setPos(max(0, min(center.x(), img_width)),
357
391
  max(0, min(center.y(), img_height)))
358
- self.master_scene.setSceneRect(QRectF(self.master_pixmap_item.boundingRect()))
392
+ self.master_scene.setSceneRect(QRectF(self.pixmap_item_master.boundingRect()))
393
+ self.center_image(self.master_view)
394
+ self.update_cursor_pen_width()
359
395
 
360
396
  def set_current_image(self, qimage):
361
397
  self.status.set_current_image(qimage)
362
398
  pixmap = self.status.pixmap_current
363
399
  self.current_scene.setSceneRect(QRectF(pixmap.rect()))
364
- self.current_pixmap_item.setPixmap(pixmap)
400
+ self.pixmap_item_current.setPixmap(pixmap)
365
401
  self.current_view.resetTransform()
366
402
  self.current_scene.scale(self.zoom_factor(), self.zoom_factor())
367
- # self.current_view.centerOn(self.current_pixmap_item)
368
- self.current_scene.setSceneRect(QRectF(self.current_pixmap_item.boundingRect()))
403
+ self.current_scene.setSceneRect(QRectF(self.pixmap_item_current.boundingRect()))
404
+ self.center_image(self.current_view)
405
+ self.update_cursor_pen_width()
369
406
 
370
- def _arrange_images(self):
407
+ def arrange_images(self):
371
408
  if self.status.empty():
372
409
  return
373
- if self.master_pixmap_item.pixmap().height() == 0:
410
+ if self.pixmap_item_master.pixmap().height() == 0:
374
411
  self.update_master_display()
375
412
  self.update_current_display()
376
413
  self.reset_zoom()
377
- self._apply_zoom()
378
-
379
- def update_master_display(self):
380
- if not self.status.empty():
381
- master_qimage = self.numpy_to_qimage(self.master_layer())
382
- if master_qimage:
383
- pixmap = QPixmap.fromImage(master_qimage)
384
- self.master_pixmap_item.setPixmap(pixmap)
385
- self.master_scene.setSceneRect(QRectF(pixmap.rect()))
386
- self._arrange_images()
387
-
388
- def update_current_display(self):
389
- if not self.status.empty() and self.number_of_layers() > 0:
390
- current_qimage = self.numpy_to_qimage(self.current_layer())
391
- if current_qimage:
392
- pixmap = QPixmap.fromImage(current_qimage)
393
- self.current_pixmap_item.setPixmap(pixmap)
394
- self.current_scene.setSceneRect(QRectF(pixmap.rect()))
395
- self._arrange_images()
396
-
397
- def _apply_zoom(self):
398
- if not self.current_pixmap_item.pixmap().isNull():
399
- self.current_view.resetTransform()
400
- self.current_view.scale(self.zoom_factor(), self.zoom_factor())
401
- # self.current_view.centerOn(self.current_pixmap_item)
402
- if not self.master_pixmap_item.pixmap().isNull():
403
- self.master_view.resetTransform()
404
- self.master_view.scale(self.zoom_factor(), self.zoom_factor())
405
- # self.master_view.centerOn(self.master_pixmap_item)
414
+ else:
415
+ self.center_image(self.master_view)
416
+ self.apply_zoom()
406
417
 
407
418
  def set_brush(self, brush):
408
419
  super().set_brush(brush)
@@ -413,6 +424,9 @@ class DoubleViewBase(ViewStrategy, QWidget, ViewSignals):
413
424
 
414
425
  def clear_image(self):
415
426
  super().clear_image()
427
+ self.setCursor(Qt.ArrowCursor)
428
+ self.master_view.setCursor(Qt.ArrowCursor)
429
+ self.current_view.setCursor(Qt.ArrowCursor)
416
430
  if self.current_brush_cursor:
417
431
  self.current_scene.removeItem(self.current_brush_cursor)
418
432
  self.current_brush_cursor = None
@@ -434,22 +448,6 @@ class DoubleViewBase(ViewStrategy, QWidget, ViewSignals):
434
448
  else:
435
449
  self.current_brush_cursor.hide()
436
450
 
437
- def zoom_in(self):
438
- super().zoom_in()
439
- self.update_cursor_pen_width()
440
-
441
- def zoom_out(self):
442
- super().zoom_out()
443
- self.update_cursor_pen_width()
444
-
445
- def reset_zoom(self):
446
- super().reset_zoom()
447
- self.update_cursor_pen_width()
448
-
449
- def actual_size(self):
450
- super().actual_size()
451
- self.update_cursor_pen_width()
452
-
453
451
 
454
452
  class SideBySideView(DoubleViewBase):
455
453
  def setup_layout(self):
@@ -0,0 +1,43 @@
1
+ # pylint: disable=C0114, C0115, C0116, E1101, W0718
2
+ import traceback
3
+ import cv2
4
+ from .. config.gui_constants import gui_constants
5
+ from .layer_collection import LayerCollectionHandler
6
+
7
+
8
+ class TransfromationManager(LayerCollectionHandler):
9
+ def __init__(self, editor):
10
+ super().__init__(editor.layer_collection)
11
+ self.editor = editor
12
+
13
+ def transform(self, transf_func, label, undoable=True):
14
+ if self.has_no_master_layer():
15
+ return
16
+ if undoable:
17
+ try:
18
+ undo = self.editor.undo_manager
19
+ undo.x_start, undo.x_stop = 0, 1
20
+ undo.y_start, undo.y_stop = 0, 1
21
+ undo.save_undo_state(self.editor.master_layer(), label)
22
+ except Exception as e:
23
+ traceback.print_tb(e.__traceback__)
24
+ self.set_master_layer(transf_func(self.master_layer()))
25
+ self.set_layer_stack([transf_func(layer) for layer in self.layer_stack()])
26
+ self.copy_master_layer()
27
+ self.editor.image_viewer.update_master_display()
28
+ self.editor.image_viewer.update_current_display()
29
+ self.editor.image_viewer.refresh_display()
30
+ self.editor.display_manager.update_thumbnails()
31
+ self.editor.mark_as_modified()
32
+
33
+ def rotate_90_cw(self, undoable=True):
34
+ self.transform(lambda img: cv2.rotate(img, cv2.ROTATE_90_CLOCKWISE),
35
+ gui_constants.ROTATE_90_CW_LABEL, undoable)
36
+
37
+ def rotate_90_ccw(self, undoable=True):
38
+ self.transform(lambda img: cv2.rotate(img, cv2.ROTATE_90_COUNTERCLOCKWISE),
39
+ gui_constants.ROTATE_90_CCW_LABEL, undoable)
40
+
41
+ def rotate_180(self, undoable=True):
42
+ self.transform(lambda img: cv2.rotate(img, cv2.ROTATE_180),
43
+ gui_constants.ROTATE_180_LABEL, undoable)
@@ -6,8 +6,9 @@ from .. config.gui_constants import gui_constants
6
6
  class UndoManager(QObject):
7
7
  stack_changed = Signal(bool, str, bool, str)
8
8
 
9
- def __init__(self):
9
+ def __init__(self, transformation_manager):
10
10
  super().__init__()
11
+ self.transformation_manager = transformation_manager
11
12
  self.x_start = None
12
13
  self.y_start = None
13
14
  self.x_end = None
@@ -59,7 +60,16 @@ class UndoManager(QObject):
59
60
  'description': undo_state['description']
60
61
  }
61
62
  self.redo_stack.append(redo_state)
62
- layer[y_start:y_end, x_start:x_end] = undo_state['master']
63
+ descr = undo_state['description']
64
+ if descr.startswith(gui_constants.ROTATE_LABEL):
65
+ if descr == gui_constants.ROTATE_90_CW_LABEL:
66
+ self.transformation_manager.rotate_90_ccw(False)
67
+ elif descr == gui_constants.ROTATE_90_CCW_LABEL:
68
+ self.transformation_manager.rotate_90_cw(False)
69
+ elif descr == gui_constants.ROTATE_180_LABEL:
70
+ self.transformation_manager.rotate_180(False)
71
+ else:
72
+ layer[y_start:y_end, x_start:x_end] = undo_state['master']
63
73
  undo_desc = self.undo_stack[-1]['description'] if self.undo_stack else ""
64
74
  redo_desc = redo_state['description']
65
75
  self.stack_changed.emit(bool(self.undo_stack), undo_desc, bool(self.redo_stack), redo_desc)
@@ -76,7 +86,16 @@ class UndoManager(QObject):
76
86
  'description': redo_state['description']
77
87
  }
78
88
  self.undo_stack.append(undo_state)
79
- layer[y_start:y_end, x_start:x_end] = redo_state['master']
89
+ descr = undo_state['description']
90
+ if descr.startswith(gui_constants.ROTATE_LABEL):
91
+ if descr == gui_constants.ROTATE_90_CW_LABEL:
92
+ self.transformation_manager.rotate_90_cw(False)
93
+ elif descr == gui_constants.ROTATE_90_CCW_LABEL:
94
+ self.transformation_manager.rotate_90_ccw(False)
95
+ elif descr == gui_constants.ROTATE_180_LABEL:
96
+ self.transformation_manager.rotate_180(False)
97
+ else:
98
+ layer[y_start:y_end, x_start:x_end] = redo_state['master']
80
99
  undo_desc = undo_state['description']
81
100
  redo_desc = self.redo_stack[-1]['description'] if self.redo_stack else ""
82
101
  self.stack_changed.emit(bool(self.undo_stack), undo_desc, bool(self.redo_stack), redo_desc)