Commit ae89189585488735cc15398f0e362c32daf7c038
1 parent
49c1f7b2
Exists in
master
and in
65 other branches
ENH: Implemented the undo and redo functionality in the edition tool
Showing
4 changed files
with
416 additions
and
16 deletions
Show diff stats
invesalius/data/mask.py
... | ... | @@ -22,6 +22,7 @@ import plistlib |
22 | 22 | import random |
23 | 23 | import shutil |
24 | 24 | import tempfile |
25 | +import weakref | |
25 | 26 | |
26 | 27 | import numpy |
27 | 28 | import vtk |
... | ... | @@ -31,12 +32,155 @@ import imagedata_utils as iu |
31 | 32 | |
32 | 33 | from wx.lib.pubsub import pub as Publisher |
33 | 34 | |
35 | + | |
36 | +class EditionHistoryNode(object): | |
37 | + def __init__(self, index, orientation, array, clean=False): | |
38 | + self.index = index | |
39 | + self.orientation = orientation | |
40 | + self.filename = tempfile.mktemp(suffix='.npy') | |
41 | + self.clean = clean | |
42 | + | |
43 | + self._save_array(array) | |
44 | + | |
45 | + def _save_array(self, array): | |
46 | + numpy.save(self.filename, array) | |
47 | + print "Saving history", self.index, self.orientation, self.filename, self.clean | |
48 | + | |
49 | + def commit_history(self, mvolume): | |
50 | + array = numpy.load(self.filename) | |
51 | + if self.orientation == 'AXIAL': | |
52 | + mvolume[self.index+1,1:,1:] = array | |
53 | + if self.clean: | |
54 | + mvolume[self.index+1, 0, 0] = 1 | |
55 | + elif self.orientation == 'CORONAL': | |
56 | + mvolume[1:, self.index+1, 1:] = array | |
57 | + if self.clean: | |
58 | + mvolume[0, self.index+1, 0] = 1 | |
59 | + elif self.orientation == 'SAGITAL': | |
60 | + mvolume[1:, 1:, self.index+1] = array | |
61 | + if self.clean: | |
62 | + mvolume[0, 0, self.index+1] = 1 | |
63 | + | |
64 | + print "applying to", self.orientation, "at slice", self.index | |
65 | + | |
66 | + def __del__(self): | |
67 | + print "Removing", self.filename | |
68 | + os.remove(self.filename) | |
69 | + | |
70 | + | |
71 | +class EditionHistory(object): | |
72 | + def __init__(self, size=50): | |
73 | + self.history = [] | |
74 | + self._copies = weakref.WeakValueDictionary() | |
75 | + self.index = -1 | |
76 | + self.size = size | |
77 | + | |
78 | + Publisher.sendMessage("Enable undo", False) | |
79 | + Publisher.sendMessage("Enable redo", False) | |
80 | + | |
81 | + def new_node(self, index, orientation, array, p_array, clean): | |
82 | + try: | |
83 | + p_node = self.history[self.index] | |
84 | + except IndexError: | |
85 | + p_node = None | |
86 | + | |
87 | + if self.index == -1 or (orientation != p_node.orientation or p_node.index != index): | |
88 | + try: | |
89 | + node = self._copies[(orientation, index)] | |
90 | + except KeyError: | |
91 | + node = EditionHistoryNode(index, orientation, p_array, clean) | |
92 | + self._copies[(orientation, index)] = node | |
93 | + self.add(node) | |
94 | + | |
95 | + node = EditionHistoryNode(index, orientation, array, clean) | |
96 | + self._copies[(orientation, index)] = node | |
97 | + self.add(node) | |
98 | + | |
99 | + def add(self, node): | |
100 | + if self.index == self.size: | |
101 | + self.history.pop(0) | |
102 | + self.index -= 1 | |
103 | + | |
104 | + if self.index < len(self.history): | |
105 | + self.history = self.history[:self.index + 1] | |
106 | + self.history.append(node) | |
107 | + self.index += 1 | |
108 | + | |
109 | + print "INDEX", self.index, len(self.history), self.history | |
110 | + Publisher.sendMessage("Enable undo", True) | |
111 | + Publisher.sendMessage("Enable redo", False) | |
112 | + | |
113 | + def undo(self, mvolume, actual_slices=None): | |
114 | + h = self.history | |
115 | + if self.index > 0: | |
116 | + #if self.index > 0 and h[self.index].clean: | |
117 | + ##self.index -= 1 | |
118 | + ##h[self.index].commit_history(mvolume) | |
119 | + #self._reload_slice(self.index - 1) | |
120 | + if actual_slices and actual_slices[h[self.index - 1].orientation] != h[self.index - 1].index: | |
121 | + self._reload_slice(self.index - 1) | |
122 | + else: | |
123 | + self.index -= 1 | |
124 | + h[self.index].commit_history(mvolume) | |
125 | + if actual_slices and self.index and actual_slices[h[self.index - 1].orientation] == h[self.index - 1].index: | |
126 | + self.index -= 1 | |
127 | + h[self.index].commit_history(mvolume) | |
128 | + self._reload_slice(self.index) | |
129 | + Publisher.sendMessage("Enable redo", True) | |
130 | + | |
131 | + if self.index == 0: | |
132 | + Publisher.sendMessage("Enable undo", False) | |
133 | + print "AT", self.index, len(self.history), self.history[self.index].filename | |
134 | + | |
135 | + def redo(self, mvolume, actual_slices=None): | |
136 | + h = self.history | |
137 | + if self.index < len(h) - 1: | |
138 | + #if self.index < len(h) - 1 and h[self.index].clean: | |
139 | + ##self.index += 1 | |
140 | + ##h[self.index].commit_history(mvolume) | |
141 | + #self._reload_slice(self.index + 1) | |
142 | + | |
143 | + if actual_slices and actual_slices[h[self.index + 1].orientation] != h[self.index + 1].index: | |
144 | + self._reload_slice(self.index + 1) | |
145 | + else: | |
146 | + self.index += 1 | |
147 | + h[self.index].commit_history(mvolume) | |
148 | + if actual_slices and self.index < len(h) - 1 and actual_slices[h[self.index + 1].orientation] == h[self.index + 1].index: | |
149 | + self.index += 1 | |
150 | + h[self.index].commit_history(mvolume) | |
151 | + self._reload_slice(self.index) | |
152 | + Publisher.sendMessage("Enable undo", True) | |
153 | + | |
154 | + if self.index == len(h) - 1: | |
155 | + Publisher.sendMessage("Enable redo", False) | |
156 | + print "AT", self.index, len(h), h[self.index].filename | |
157 | + | |
158 | + def _reload_slice(self, index): | |
159 | + Publisher.sendMessage(('Set scroll position', self.history[index].orientation), | |
160 | + self.history[index].index) | |
161 | + | |
162 | + def _config_undo_redo(self, visible): | |
163 | + v_undo = False | |
164 | + v_redo = False | |
165 | + | |
166 | + if self.history and visible: | |
167 | + v_undo = True | |
168 | + v_redo = True | |
169 | + if self.index == 0: | |
170 | + v_undo = False | |
171 | + elif self.index == len(self.history) - 1: | |
172 | + v_redo = False | |
173 | + | |
174 | + Publisher.sendMessage("Enable undo", v_undo) | |
175 | + Publisher.sendMessage("Enable redo", v_redo) | |
176 | + | |
177 | + | |
34 | 178 | class Mask(): |
35 | 179 | general_index = -1 |
36 | 180 | def __init__(self): |
37 | 181 | Mask.general_index += 1 |
38 | 182 | self.index = Mask.general_index |
39 | - self.imagedata = '' # vtkImageData | |
183 | + self.imagedata = '' | |
40 | 184 | self.colour = random.choice(const.MASK_COLOUR) |
41 | 185 | self.opacity = const.MASK_OPACITY |
42 | 186 | self.threshold_range = const.THRESHOLD_RANGE |
... | ... | @@ -47,10 +191,24 @@ class Mask(): |
47 | 191 | self.was_edited = False |
48 | 192 | self.__bind_events() |
49 | 193 | |
194 | + self.history = EditionHistory() | |
195 | + | |
50 | 196 | def __bind_events(self): |
51 | 197 | Publisher.subscribe(self.OnFlipVolume, 'Flip volume') |
52 | 198 | Publisher.subscribe(self.OnSwapVolumeAxes, 'Swap volume axes') |
53 | 199 | |
200 | + def save_history(self, index, orientation, array, p_array, clean=False): | |
201 | + self.history.new_node(index, orientation, array, p_array, clean) | |
202 | + | |
203 | + def undo_history(self, actual_slices): | |
204 | + self.history.undo(self.matrix, actual_slices) | |
205 | + | |
206 | + def redo_history(self, actual_slices): | |
207 | + self.history.redo(self.matrix, actual_slices) | |
208 | + | |
209 | + def on_show(self): | |
210 | + self.history._config_undo_redo(self.is_shown) | |
211 | + | |
54 | 212 | def SavePlist(self, dir_temp, filelist): |
55 | 213 | mask = {} |
56 | 214 | filename = u'mask_%d' % self.index |
... | ... | @@ -129,3 +287,6 @@ class Mask(): |
129 | 287 | self.temp_file = tempfile.mktemp() |
130 | 288 | shape = shape[0] + 1, shape[1] + 1, shape[2] + 1 |
131 | 289 | self.matrix = numpy.memmap(self.temp_file, mode='w+', dtype='uint8', shape=shape) |
290 | + | |
291 | + def __del__(self): | |
292 | + self.history._config_undo_redo(False) | ... | ... |
invesalius/data/slice_.py
... | ... | @@ -17,6 +17,8 @@ |
17 | 17 | # detalhes. |
18 | 18 | #-------------------------------------------------------------------------- |
19 | 19 | import math |
20 | +import os | |
21 | +import tempfile | |
20 | 22 | |
21 | 23 | import numpy |
22 | 24 | import vtk |
... | ... | @@ -64,6 +66,9 @@ class SliceBuffer(object): |
64 | 66 | self.vtk_mask = None |
65 | 67 | |
66 | 68 | |
69 | + | |
70 | + | |
71 | + | |
67 | 72 | class Slice(object): |
68 | 73 | __metaclass__= utils.Singleton |
69 | 74 | # Only one slice will be initialized per time (despite several viewers |
... | ... | @@ -135,6 +140,9 @@ class Slice(object): |
135 | 140 | |
136 | 141 | Publisher.subscribe(self.OnFlipVolume, 'Flip volume') |
137 | 142 | Publisher.subscribe(self.OnSwapVolumeAxes, 'Swap volume axes') |
143 | + | |
144 | + Publisher.subscribe(self.__undo_edition, 'Undo edition') | |
145 | + Publisher.subscribe(self.__redo_edition, 'Redo edition') | |
138 | 146 | |
139 | 147 | def GetMaxSliceNumber(self, orientation): |
140 | 148 | shape = self.matrix.shape |
... | ... | @@ -290,7 +298,6 @@ class Slice(object): |
290 | 298 | mask = self.buffer_slices[orientation].mask |
291 | 299 | image = self.buffer_slices[orientation].image |
292 | 300 | thresh_min, thresh_max = self.current_mask.edition_threshold_range |
293 | - self.current_mask.was_edited = True | |
294 | 301 | |
295 | 302 | if hasattr(position, '__iter__'): |
296 | 303 | py, px = position |
... | ... | @@ -350,12 +357,6 @@ class Slice(object): |
350 | 357 | roi_m = mask[yi:yf,xi:xf] |
351 | 358 | roi_i = image[yi:yf, xi:xf] |
352 | 359 | |
353 | ||
354 | - print"IMAGE", roi_m.shape | |
355 | - print "BRUSH", index.shape | |
356 | - print "IMAGE[BRUSH]", roi_m[index].shape | |
357 | ||
358 | - | |
359 | 360 | if operation == const.BRUSH_THRESH: |
360 | 361 | # It's a trick to make points between threshold gets value 254 |
361 | 362 | # (1 * 253 + 1) and out ones gets value 1 (0 * 253 + 1). |
... | ... | @@ -558,6 +559,8 @@ class Slice(object): |
558 | 559 | print "Showing Mask" |
559 | 560 | proj = Project() |
560 | 561 | proj.mask_dict[index].is_shown = value |
562 | + proj.mask_dict[index].on_show() | |
563 | + | |
561 | 564 | if (index == self.current_mask.index): |
562 | 565 | for buffer_ in self.buffer_slices.values(): |
563 | 566 | buffer_.discard_vtk_mask() |
... | ... | @@ -884,12 +887,37 @@ class Slice(object): |
884 | 887 | """ |
885 | 888 | b_mask = self.buffer_slices[orientation].mask |
886 | 889 | index = self.buffer_slices[orientation].index |
890 | + | |
891 | + # TODO: Voltar a usar marcacao na mascara | |
887 | 892 | if orientation == 'AXIAL': |
893 | + #if self.current_mask.matrix[index+1, 0, 0] != 2: | |
894 | + #self.current_mask.save_history(index, orientation, | |
895 | + #self.current_mask.matrix[index+1,1:,1:], | |
896 | + #clean=True) | |
897 | + p_mask = self.current_mask.matrix[index+1,1:,1:].copy() | |
888 | 898 | self.current_mask.matrix[index+1,1:,1:] = b_mask |
899 | + self.current_mask.matrix[index+1, 0, 0] = 2 | |
900 | + | |
889 | 901 | elif orientation == 'CORONAL': |
902 | + #if self.current_mask.matrix[0, index+1, 0] != 2: | |
903 | + #self.current_mask.save_history(index, orientation, | |
904 | + #self.current_mask.matrix[1:, index+1, 1:], | |
905 | + #clean=True) | |
906 | + p_mask = self.current_mask.matrix[1:, index+1, 1:].copy() | |
890 | 907 | self.current_mask.matrix[1:, index+1, 1:] = b_mask |
908 | + self.current_mask.matrix[0, index+1, 0] = 2 | |
909 | + | |
891 | 910 | elif orientation == 'SAGITAL': |
911 | + #if self.current_mask.matrix[0, 0, index+1] != 2: | |
912 | + #self.current_mask.save_history(index, orientation, | |
913 | + #self.current_mask.matrix[1:, 1:, index+1], | |
914 | + #clean=True) | |
915 | + p_mask = self.current_mask.matrix[1:, 1:, index+1].copy() | |
892 | 916 | self.current_mask.matrix[1:, 1:, index+1] = b_mask |
917 | + self.current_mask.matrix[0, 0, index+1] = 2 | |
918 | + | |
919 | + self.current_mask.save_history(index, orientation, b_mask, p_mask) | |
920 | + self.current_mask.was_edited = True | |
893 | 921 | |
894 | 922 | for o in self.buffer_slices: |
895 | 923 | if o != orientation: |
... | ... | @@ -897,6 +925,28 @@ class Slice(object): |
897 | 925 | self.buffer_slices[o].discard_vtk_mask() |
898 | 926 | Publisher.sendMessage('Reload actual slice') |
899 | 927 | |
928 | + def __undo_edition(self, pub_evt): | |
929 | + buffer_slices = self.buffer_slices | |
930 | + actual_slices = {"AXIAL": buffer_slices["AXIAL"].index, | |
931 | + "CORONAL": buffer_slices["CORONAL"].index, | |
932 | + "SAGITAL": buffer_slices["SAGITAL"].index,} | |
933 | + self.current_mask.undo_history(actual_slices) | |
934 | + for o in self.buffer_slices: | |
935 | + self.buffer_slices[o].discard_mask() | |
936 | + self.buffer_slices[o].discard_vtk_mask() | |
937 | + Publisher.sendMessage('Reload actual slice') | |
938 | + | |
939 | + def __redo_edition(self, pub_evt): | |
940 | + buffer_slices = self.buffer_slices | |
941 | + actual_slices = {"AXIAL": buffer_slices["AXIAL"].index, | |
942 | + "CORONAL": buffer_slices["CORONAL"].index, | |
943 | + "SAGITAL": buffer_slices["SAGITAL"].index,} | |
944 | + self.current_mask.redo_history(actual_slices) | |
945 | + for o in self.buffer_slices: | |
946 | + self.buffer_slices[o].discard_mask() | |
947 | + self.buffer_slices[o].discard_vtk_mask() | |
948 | + Publisher.sendMessage('Reload actual slice') | |
949 | + | |
900 | 950 | def __build_mask(self, imagedata, create=True): |
901 | 951 | # create new mask instance and insert it into project |
902 | 952 | if create: | ... | ... |
invesalius/data/viewer_slice.py
... | ... | @@ -20,6 +20,7 @@ |
20 | 20 | #-------------------------------------------------------------------------- |
21 | 21 | |
22 | 22 | import itertools |
23 | +import tempfile | |
23 | 24 | |
24 | 25 | import numpy |
25 | 26 | |
... | ... | @@ -803,6 +804,7 @@ class Viewer(wx.Panel): |
803 | 804 | def OnBrushRelease(self, evt, obj): |
804 | 805 | if (self.slice_.buffer_slices[self.orientation].mask is None): |
805 | 806 | return |
807 | + | |
806 | 808 | self.slice_.apply_slice_buffer_to_mask(self.orientation) |
807 | 809 | self._flush_buffer = False |
808 | 810 | ... | ... |
invesalius/gui/frame.py
... | ... | @@ -165,11 +165,13 @@ class Frame(wx.Frame): |
165 | 165 | t2 = LayoutToolBar(self) |
166 | 166 | t3 = ObjectToolBar(self) |
167 | 167 | t4 = SliceToolBar(self) |
168 | + t5 = HistoryToolBar(self) | |
168 | 169 | else: |
169 | - t4 = ProjectToolBar(self) | |
170 | - t3 = LayoutToolBar(self) | |
171 | - t2 = ObjectToolBar(self) | |
172 | - t1 = SliceToolBar(self) | |
170 | + t5 = ProjectToolBar(self) | |
171 | + t4 = LayoutToolBar(self) | |
172 | + t3 = ObjectToolBar(self) | |
173 | + t2 = SliceToolBar(self) | |
174 | + t1 = HistoryToolBar(self) | |
173 | 175 | |
174 | 176 | aui_manager.AddPane(t1, wx.aui.AuiPaneInfo(). |
175 | 177 | Name("General Features Toolbar"). |
... | ... | @@ -191,6 +193,11 @@ class Frame(wx.Frame): |
191 | 193 | ToolbarPane().Top().Floatable(False). |
192 | 194 | LeftDockable(False).RightDockable(False)) |
193 | 195 | |
196 | + aui_manager.AddPane(t5, wx.aui.AuiPaneInfo(). | |
197 | + Name("Slice Toolbar"). | |
198 | + ToolbarPane().Top().Floatable(False). | |
199 | + LeftDockable(False).RightDockable(False)) | |
200 | + | |
194 | 201 | aui_manager.Update() |
195 | 202 | self.aui_manager = aui_manager |
196 | 203 | |
... | ... | @@ -358,6 +365,11 @@ class Frame(wx.Frame): |
358 | 365 | const.ID_SWAP_XZ: (2, 0), |
359 | 366 | const.ID_SWAP_YZ: (1, 0)}[id] |
360 | 367 | self.SwapAxes(axes) |
368 | + elif id == wx.ID_UNDO: | |
369 | + self.OnUndo() | |
370 | + elif id == wx.ID_REDO: | |
371 | + self.OnRedo() | |
372 | + | |
361 | 373 | def OnSize(self, evt): |
362 | 374 | """ |
363 | 375 | Refresh GUI when frame is resized. |
... | ... | @@ -436,6 +448,13 @@ class Frame(wx.Frame): |
436 | 448 | Publisher.sendMessage('Update scroll') |
437 | 449 | Publisher.sendMessage('Reload actual slice') |
438 | 450 | |
451 | + def OnUndo(self): | |
452 | + print "Undo" | |
453 | + Publisher.sendMessage('Undo edition') | |
454 | + | |
455 | + def OnRedo(self): | |
456 | + print "Redo" | |
457 | + Publisher.sendMessage('Redo edition') | |
439 | 458 | |
440 | 459 | |
441 | 460 | |
... | ... | @@ -476,6 +495,8 @@ class MenuBar(wx.MenuBar): |
476 | 495 | # mail list in Oct 20 2008 |
477 | 496 | sub = Publisher.subscribe |
478 | 497 | sub(self.OnEnableState, "Enable state project") |
498 | + sub(self.OnEnableUndo, "Enable undo") | |
499 | + sub(self.OnEnableRedo, "Enable redo") | |
479 | 500 | |
480 | 501 | def __init_items(self): |
481 | 502 | """ |
... | ... | @@ -523,11 +544,10 @@ class MenuBar(wx.MenuBar): |
523 | 544 | app(const.ID_SWAP_YZ, _("A-P <-> T-B")) |
524 | 545 | |
525 | 546 | file_edit = wx.Menu() |
526 | - app = file_edit.Append | |
527 | 547 | file_edit.AppendMenu(wx.NewId(), _('Flip'), flip_menu) |
528 | 548 | file_edit.AppendMenu(wx.NewId(), _('Swap axes'), swap_axes_menu) |
529 | - #app(wx.ID_UNDO, "Undo\tCtrl+Z") | |
530 | - #app(wx.ID_REDO, "Redo\tCtrl+Y") | |
549 | + file_edit.Append(wx.ID_UNDO, "Undo\tCtrl+Z").Enable(False) | |
550 | + file_edit.Append(wx.ID_REDO, "Redo\tCtrl+Y").Enable(False) | |
531 | 551 | #app(const.ID_EDIT_LIST, "Show Undo List...") |
532 | 552 | ################################################################# |
533 | 553 | |
... | ... | @@ -578,7 +598,7 @@ class MenuBar(wx.MenuBar): |
578 | 598 | |
579 | 599 | # Add all menus to menubar |
580 | 600 | self.Append(file_menu, _("File")) |
581 | - #self.Append(file_edit, _("Edit")) | |
601 | + self.Append(file_edit, _("Edit")) | |
582 | 602 | #self.Append(view_menu, "View") |
583 | 603 | #self.Append(tools_menu, "Tools") |
584 | 604 | self.Append(options_menu, _("Options")) |
... | ... | @@ -609,6 +629,19 @@ class MenuBar(wx.MenuBar): |
609 | 629 | for item in self.enable_items: |
610 | 630 | self.Enable(item, True) |
611 | 631 | |
632 | + def OnEnableUndo(self, pubsub_evt): | |
633 | + value = pubsub_evt.data | |
634 | + if value: | |
635 | + self.FindItemById(wx.ID_UNDO).Enable(True) | |
636 | + else: | |
637 | + self.FindItemById(wx.ID_UNDO).Enable(False) | |
638 | + | |
639 | + def OnEnableRedo(self, pubsub_evt): | |
640 | + value = pubsub_evt.data | |
641 | + if value: | |
642 | + self.FindItemById(wx.ID_REDO).Enable(True) | |
643 | + else: | |
644 | + self.FindItemById(wx.ID_REDO).Enable(False) | |
612 | 645 | # ------------------------------------------------------------------ |
613 | 646 | # ------------------------------------------------------------------ |
614 | 647 | # ------------------------------------------------------------------ |
... | ... | @@ -1397,12 +1430,166 @@ class LayoutToolBar(wx.ToolBar): |
1397 | 1430 | self.ontool_text = True |
1398 | 1431 | |
1399 | 1432 | |
1433 | +class HistoryToolBar(wx.ToolBar): | |
1434 | + """ | |
1435 | + Toolbar related to general layout/ visualization configuration | |
1436 | + e.g: show/hide task panel and show/hide text on viewers. | |
1437 | + """ | |
1438 | + def __init__(self, parent): | |
1439 | + style = wx.TB_FLAT|wx.TB_NODIVIDER | wx.TB_DOCKABLE | |
1440 | + wx.ToolBar.__init__(self, parent, -1, wx.DefaultPosition, | |
1441 | + wx.DefaultSize, | |
1442 | + style) | |
1400 | 1443 | |
1444 | + self.SetToolBitmapSize(wx.Size(32,32)) | |
1401 | 1445 | |
1446 | + self.parent = parent | |
1447 | + self.__init_items() | |
1448 | + self.__bind_events() | |
1449 | + self.__bind_events_wx() | |
1402 | 1450 | |
1451 | + self.ontool_layout = False | |
1452 | + self.ontool_text = True | |
1453 | + #self.enable_items = [ID_TEXT] | |
1403 | 1454 | |
1455 | + self.Realize() | |
1456 | + #self.SetStateProjectClose() | |
1404 | 1457 | |
1458 | + def __bind_events(self): | |
1459 | + """ | |
1460 | + Bind events related to pubsub. | |
1461 | + """ | |
1462 | + sub = Publisher.subscribe | |
1463 | + #sub(self._EnableState, "Enable state project") | |
1464 | + #sub(self._SetLayoutWithTask, "Set layout button data only") | |
1465 | + #sub(self._SetLayoutWithoutTask, "Set layout button full") | |
1466 | + sub(self.OnEnableUndo, "Enable undo") | |
1467 | + sub(self.OnEnableRedo, "Enable redo") | |
1405 | 1468 | |
1469 | + def __bind_events_wx(self): | |
1470 | + """ | |
1471 | + Bind normal events from wx (except pubsub related). | |
1472 | + """ | |
1473 | + #self.Bind(wx.EVT_TOOL, self.OnToggle) | |
1474 | + wx.EVT_TOOL( self, wx.ID_UNDO, self.OnUndo ) | |
1475 | + wx.EVT_TOOL( self, wx.ID_REDO, self.OnRedo ) | |
1406 | 1476 | |
1477 | + def __init_items(self): | |
1478 | + """ | |
1479 | + Add tools into toolbar. | |
1480 | + """ | |
1481 | + self.AddSimpleTool(wx.ID_UNDO, wx.ArtProvider_GetBitmap(wx.ART_UNDO, wx.ART_OTHER, wx.Size( 16, 16)), 'Undo', '') | |
1482 | + self.AddSimpleTool(wx.ID_REDO, wx.ArtProvider_GetBitmap(wx.ART_REDO, wx.ART_OTHER, wx.Size( 16, 16)), 'Redo', '') | |
1483 | + self.EnableTool(wx.ID_UNDO, False) | |
1484 | + self.EnableTool(wx.ID_REDO, False) | |
1407 | 1485 | |
1486 | + def _EnableState(self, pubsub_evt): | |
1487 | + """ | |
1488 | + Based on given state, enable or disable menu items which | |
1489 | + depend if project is open or not. | |
1490 | + """ | |
1491 | + state = pubsub_evt.data | |
1492 | + if state: | |
1493 | + self.SetStateProjectOpen() | |
1494 | + else: | |
1495 | + self.SetStateProjectClose() | |
1408 | 1496 | |
1497 | + def _SetLayoutWithoutTask(self, pubsub_evt): | |
1498 | + """ | |
1499 | + Set item bitmap to task panel hiden. | |
1500 | + """ | |
1501 | + self.SetToolNormalBitmap(ID_LAYOUT,self.BMP_WITHOUT_MENU) | |
1502 | + | |
1503 | + def _SetLayoutWithTask(self, pubsub_evt): | |
1504 | + """ | |
1505 | + Set item bitmap to task panel shown. | |
1506 | + """ | |
1507 | + self.SetToolNormalBitmap(ID_LAYOUT,self.BMP_WITH_MENU) | |
1508 | + | |
1509 | + def OnUndo(self, event): | |
1510 | + print "Undo" | |
1511 | + Publisher.sendMessage('Undo edition') | |
1512 | + | |
1513 | + def OnRedo(self, event): | |
1514 | + print "Redo" | |
1515 | + Publisher.sendMessage('Redo edition') | |
1516 | + | |
1517 | + def OnToggle(self, event): | |
1518 | + """ | |
1519 | + Update status of toolbar item (bitmap and help) | |
1520 | + """ | |
1521 | + id = event.GetId() | |
1522 | + if id == ID_LAYOUT: | |
1523 | + self.ToggleLayout() | |
1524 | + elif id== ID_TEXT: | |
1525 | + self.ToggleText() | |
1526 | + | |
1527 | + for item in VIEW_TOOLS: | |
1528 | + state = self.GetToolState(item) | |
1529 | + if state and (item != id): | |
1530 | + self.ToggleTool(item, False) | |
1531 | + | |
1532 | + def SetStateProjectClose(self): | |
1533 | + """ | |
1534 | + Disable menu items (e.g. text) when project is closed. | |
1535 | + """ | |
1536 | + self.ontool_text = True | |
1537 | + self.ToggleText() | |
1538 | + for tool in self.enable_items: | |
1539 | + self.EnableTool(tool, False) | |
1540 | + | |
1541 | + def SetStateProjectOpen(self): | |
1542 | + """ | |
1543 | + Disable menu items (e.g. text) when project is closed. | |
1544 | + """ | |
1545 | + self.ontool_text = False | |
1546 | + self.ToggleText() | |
1547 | + for tool in self.enable_items: | |
1548 | + self.EnableTool(tool, True) | |
1549 | + | |
1550 | + def ToggleLayout(self): | |
1551 | + """ | |
1552 | + Based on previous layout item state, toggle it. | |
1553 | + """ | |
1554 | + if self.ontool_layout: | |
1555 | + self.SetToolNormalBitmap(ID_LAYOUT,self.BMP_WITHOUT_MENU) | |
1556 | + Publisher.sendMessage('Show task panel') | |
1557 | + self.SetToolShortHelp(ID_LAYOUT,_("Hide task panel")) | |
1558 | + self.ontool_layout = False | |
1559 | + else: | |
1560 | + self.bitmap = self.BMP_WITH_MENU | |
1561 | + self.SetToolNormalBitmap(ID_LAYOUT,self.BMP_WITH_MENU) | |
1562 | + Publisher.sendMessage('Hide task panel') | |
1563 | + self.SetToolShortHelp(ID_LAYOUT, _("Show task panel")) | |
1564 | + self.ontool_layout = True | |
1565 | + | |
1566 | + def ToggleText(self): | |
1567 | + """ | |
1568 | + Based on previous text item state, toggle it. | |
1569 | + """ | |
1570 | + if self.ontool_text: | |
1571 | + self.SetToolNormalBitmap(ID_TEXT,self.BMP_WITH_TEXT) | |
1572 | + Publisher.sendMessage('Hide text actors on viewers') | |
1573 | + self.SetToolShortHelp(ID_TEXT,_("Show text")) | |
1574 | + Publisher.sendMessage('Update AUI') | |
1575 | + self.ontool_text = False | |
1576 | + else: | |
1577 | + self.SetToolNormalBitmap(ID_TEXT, self.BMP_WITHOUT_TEXT) | |
1578 | + Publisher.sendMessage('Show text actors on viewers') | |
1579 | + self.SetToolShortHelp(ID_TEXT,_("Hide text")) | |
1580 | + Publisher.sendMessage('Update AUI') | |
1581 | + self.ontool_text = True | |
1582 | + | |
1583 | + def OnEnableUndo(self, pubsub_evt): | |
1584 | + value = pubsub_evt.data | |
1585 | + if value: | |
1586 | + self.EnableTool(wx.ID_UNDO, True) | |
1587 | + else: | |
1588 | + self.EnableTool(wx.ID_UNDO, False) | |
1589 | + | |
1590 | + def OnEnableRedo(self, pubsub_evt): | |
1591 | + value = pubsub_evt.data | |
1592 | + if value: | |
1593 | + self.EnableTool(wx.ID_REDO, True) | |
1594 | + else: | |
1595 | + self.EnableTool(wx.ID_REDO, False) | ... | ... |