Commit ae89189585488735cc15398f0e362c32daf7c038

Authored by tfmoraes
1 parent 49c1f7b2

ENH: Implemented the undo and redo functionality in the edition tool

invesalius/data/mask.py
@@ -22,6 +22,7 @@ import plistlib @@ -22,6 +22,7 @@ import plistlib
22 import random 22 import random
23 import shutil 23 import shutil
24 import tempfile 24 import tempfile
  25 +import weakref
25 26
26 import numpy 27 import numpy
27 import vtk 28 import vtk
@@ -31,12 +32,155 @@ import imagedata_utils as iu @@ -31,12 +32,155 @@ import imagedata_utils as iu
31 32
32 from wx.lib.pubsub import pub as Publisher 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 class Mask(): 178 class Mask():
35 general_index = -1 179 general_index = -1
36 def __init__(self): 180 def __init__(self):
37 Mask.general_index += 1 181 Mask.general_index += 1
38 self.index = Mask.general_index 182 self.index = Mask.general_index
39 - self.imagedata = '' # vtkImageData 183 + self.imagedata = ''
40 self.colour = random.choice(const.MASK_COLOUR) 184 self.colour = random.choice(const.MASK_COLOUR)
41 self.opacity = const.MASK_OPACITY 185 self.opacity = const.MASK_OPACITY
42 self.threshold_range = const.THRESHOLD_RANGE 186 self.threshold_range = const.THRESHOLD_RANGE
@@ -47,10 +191,24 @@ class Mask(): @@ -47,10 +191,24 @@ class Mask():
47 self.was_edited = False 191 self.was_edited = False
48 self.__bind_events() 192 self.__bind_events()
49 193
  194 + self.history = EditionHistory()
  195 +
50 def __bind_events(self): 196 def __bind_events(self):
51 Publisher.subscribe(self.OnFlipVolume, 'Flip volume') 197 Publisher.subscribe(self.OnFlipVolume, 'Flip volume')
52 Publisher.subscribe(self.OnSwapVolumeAxes, 'Swap volume axes') 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 def SavePlist(self, dir_temp, filelist): 212 def SavePlist(self, dir_temp, filelist):
55 mask = {} 213 mask = {}
56 filename = u'mask_%d' % self.index 214 filename = u'mask_%d' % self.index
@@ -129,3 +287,6 @@ class Mask(): @@ -129,3 +287,6 @@ class Mask():
129 self.temp_file = tempfile.mktemp() 287 self.temp_file = tempfile.mktemp()
130 shape = shape[0] + 1, shape[1] + 1, shape[2] + 1 288 shape = shape[0] + 1, shape[1] + 1, shape[2] + 1
131 self.matrix = numpy.memmap(self.temp_file, mode='w+', dtype='uint8', shape=shape) 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,6 +17,8 @@
17 # detalhes. 17 # detalhes.
18 #-------------------------------------------------------------------------- 18 #--------------------------------------------------------------------------
19 import math 19 import math
  20 +import os
  21 +import tempfile
20 22
21 import numpy 23 import numpy
22 import vtk 24 import vtk
@@ -64,6 +66,9 @@ class SliceBuffer(object): @@ -64,6 +66,9 @@ class SliceBuffer(object):
64 self.vtk_mask = None 66 self.vtk_mask = None
65 67
66 68
  69 +
  70 +
  71 +
67 class Slice(object): 72 class Slice(object):
68 __metaclass__= utils.Singleton 73 __metaclass__= utils.Singleton
69 # Only one slice will be initialized per time (despite several viewers 74 # Only one slice will be initialized per time (despite several viewers
@@ -135,6 +140,9 @@ class Slice(object): @@ -135,6 +140,9 @@ class Slice(object):
135 140
136 Publisher.subscribe(self.OnFlipVolume, 'Flip volume') 141 Publisher.subscribe(self.OnFlipVolume, 'Flip volume')
137 Publisher.subscribe(self.OnSwapVolumeAxes, 'Swap volume axes') 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 def GetMaxSliceNumber(self, orientation): 147 def GetMaxSliceNumber(self, orientation):
140 shape = self.matrix.shape 148 shape = self.matrix.shape
@@ -290,7 +298,6 @@ class Slice(object): @@ -290,7 +298,6 @@ class Slice(object):
290 mask = self.buffer_slices[orientation].mask 298 mask = self.buffer_slices[orientation].mask
291 image = self.buffer_slices[orientation].image 299 image = self.buffer_slices[orientation].image
292 thresh_min, thresh_max = self.current_mask.edition_threshold_range 300 thresh_min, thresh_max = self.current_mask.edition_threshold_range
293 - self.current_mask.was_edited = True  
294 301
295 if hasattr(position, '__iter__'): 302 if hasattr(position, '__iter__'):
296 py, px = position 303 py, px = position
@@ -350,12 +357,6 @@ class Slice(object): @@ -350,12 +357,6 @@ class Slice(object):
350 roi_m = mask[yi:yf,xi:xf] 357 roi_m = mask[yi:yf,xi:xf]
351 roi_i = image[yi:yf, xi:xf] 358 roi_i = image[yi:yf, xi:xf]
352 359
353 - print  
354 - print"IMAGE", roi_m.shape  
355 - print "BRUSH", index.shape  
356 - print "IMAGE[BRUSH]", roi_m[index].shape  
357 - print  
358 -  
359 if operation == const.BRUSH_THRESH: 360 if operation == const.BRUSH_THRESH:
360 # It's a trick to make points between threshold gets value 254 361 # It's a trick to make points between threshold gets value 254
361 # (1 * 253 + 1) and out ones gets value 1 (0 * 253 + 1). 362 # (1 * 253 + 1) and out ones gets value 1 (0 * 253 + 1).
@@ -558,6 +559,8 @@ class Slice(object): @@ -558,6 +559,8 @@ class Slice(object):
558 print "Showing Mask" 559 print "Showing Mask"
559 proj = Project() 560 proj = Project()
560 proj.mask_dict[index].is_shown = value 561 proj.mask_dict[index].is_shown = value
  562 + proj.mask_dict[index].on_show()
  563 +
561 if (index == self.current_mask.index): 564 if (index == self.current_mask.index):
562 for buffer_ in self.buffer_slices.values(): 565 for buffer_ in self.buffer_slices.values():
563 buffer_.discard_vtk_mask() 566 buffer_.discard_vtk_mask()
@@ -884,12 +887,37 @@ class Slice(object): @@ -884,12 +887,37 @@ class Slice(object):
884 """ 887 """
885 b_mask = self.buffer_slices[orientation].mask 888 b_mask = self.buffer_slices[orientation].mask
886 index = self.buffer_slices[orientation].index 889 index = self.buffer_slices[orientation].index
  890 +
  891 + # TODO: Voltar a usar marcacao na mascara
887 if orientation == 'AXIAL': 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 self.current_mask.matrix[index+1,1:,1:] = b_mask 898 self.current_mask.matrix[index+1,1:,1:] = b_mask
  899 + self.current_mask.matrix[index+1, 0, 0] = 2
  900 +
889 elif orientation == 'CORONAL': 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 self.current_mask.matrix[1:, index+1, 1:] = b_mask 907 self.current_mask.matrix[1:, index+1, 1:] = b_mask
  908 + self.current_mask.matrix[0, index+1, 0] = 2
  909 +
891 elif orientation == 'SAGITAL': 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 self.current_mask.matrix[1:, 1:, index+1] = b_mask 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 for o in self.buffer_slices: 922 for o in self.buffer_slices:
895 if o != orientation: 923 if o != orientation:
@@ -897,6 +925,28 @@ class Slice(object): @@ -897,6 +925,28 @@ class Slice(object):
897 self.buffer_slices[o].discard_vtk_mask() 925 self.buffer_slices[o].discard_vtk_mask()
898 Publisher.sendMessage('Reload actual slice') 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 def __build_mask(self, imagedata, create=True): 950 def __build_mask(self, imagedata, create=True):
901 # create new mask instance and insert it into project 951 # create new mask instance and insert it into project
902 if create: 952 if create:
invesalius/data/viewer_slice.py
@@ -20,6 +20,7 @@ @@ -20,6 +20,7 @@
20 #-------------------------------------------------------------------------- 20 #--------------------------------------------------------------------------
21 21
22 import itertools 22 import itertools
  23 +import tempfile
23 24
24 import numpy 25 import numpy
25 26
@@ -803,6 +804,7 @@ class Viewer(wx.Panel): @@ -803,6 +804,7 @@ class Viewer(wx.Panel):
803 def OnBrushRelease(self, evt, obj): 804 def OnBrushRelease(self, evt, obj):
804 if (self.slice_.buffer_slices[self.orientation].mask is None): 805 if (self.slice_.buffer_slices[self.orientation].mask is None):
805 return 806 return
  807 +
806 self.slice_.apply_slice_buffer_to_mask(self.orientation) 808 self.slice_.apply_slice_buffer_to_mask(self.orientation)
807 self._flush_buffer = False 809 self._flush_buffer = False
808 810
invesalius/gui/frame.py
@@ -165,11 +165,13 @@ class Frame(wx.Frame): @@ -165,11 +165,13 @@ class Frame(wx.Frame):
165 t2 = LayoutToolBar(self) 165 t2 = LayoutToolBar(self)
166 t3 = ObjectToolBar(self) 166 t3 = ObjectToolBar(self)
167 t4 = SliceToolBar(self) 167 t4 = SliceToolBar(self)
  168 + t5 = HistoryToolBar(self)
168 else: 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 aui_manager.AddPane(t1, wx.aui.AuiPaneInfo(). 176 aui_manager.AddPane(t1, wx.aui.AuiPaneInfo().
175 Name("General Features Toolbar"). 177 Name("General Features Toolbar").
@@ -191,6 +193,11 @@ class Frame(wx.Frame): @@ -191,6 +193,11 @@ class Frame(wx.Frame):
191 ToolbarPane().Top().Floatable(False). 193 ToolbarPane().Top().Floatable(False).
192 LeftDockable(False).RightDockable(False)) 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 aui_manager.Update() 201 aui_manager.Update()
195 self.aui_manager = aui_manager 202 self.aui_manager = aui_manager
196 203
@@ -358,6 +365,11 @@ class Frame(wx.Frame): @@ -358,6 +365,11 @@ class Frame(wx.Frame):
358 const.ID_SWAP_XZ: (2, 0), 365 const.ID_SWAP_XZ: (2, 0),
359 const.ID_SWAP_YZ: (1, 0)}[id] 366 const.ID_SWAP_YZ: (1, 0)}[id]
360 self.SwapAxes(axes) 367 self.SwapAxes(axes)
  368 + elif id == wx.ID_UNDO:
  369 + self.OnUndo()
  370 + elif id == wx.ID_REDO:
  371 + self.OnRedo()
  372 +
361 def OnSize(self, evt): 373 def OnSize(self, evt):
362 """ 374 """
363 Refresh GUI when frame is resized. 375 Refresh GUI when frame is resized.
@@ -436,6 +448,13 @@ class Frame(wx.Frame): @@ -436,6 +448,13 @@ class Frame(wx.Frame):
436 Publisher.sendMessage('Update scroll') 448 Publisher.sendMessage('Update scroll')
437 Publisher.sendMessage('Reload actual slice') 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,6 +495,8 @@ class MenuBar(wx.MenuBar):
476 # mail list in Oct 20 2008 495 # mail list in Oct 20 2008
477 sub = Publisher.subscribe 496 sub = Publisher.subscribe
478 sub(self.OnEnableState, "Enable state project") 497 sub(self.OnEnableState, "Enable state project")
  498 + sub(self.OnEnableUndo, "Enable undo")
  499 + sub(self.OnEnableRedo, "Enable redo")
479 500
480 def __init_items(self): 501 def __init_items(self):
481 """ 502 """
@@ -523,11 +544,10 @@ class MenuBar(wx.MenuBar): @@ -523,11 +544,10 @@ class MenuBar(wx.MenuBar):
523 app(const.ID_SWAP_YZ, _("A-P <-> T-B")) 544 app(const.ID_SWAP_YZ, _("A-P <-> T-B"))
524 545
525 file_edit = wx.Menu() 546 file_edit = wx.Menu()
526 - app = file_edit.Append  
527 file_edit.AppendMenu(wx.NewId(), _('Flip'), flip_menu) 547 file_edit.AppendMenu(wx.NewId(), _('Flip'), flip_menu)
528 file_edit.AppendMenu(wx.NewId(), _('Swap axes'), swap_axes_menu) 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 #app(const.ID_EDIT_LIST, "Show Undo List...") 551 #app(const.ID_EDIT_LIST, "Show Undo List...")
532 ################################################################# 552 #################################################################
533 553
@@ -578,7 +598,7 @@ class MenuBar(wx.MenuBar): @@ -578,7 +598,7 @@ class MenuBar(wx.MenuBar):
578 598
579 # Add all menus to menubar 599 # Add all menus to menubar
580 self.Append(file_menu, _("File")) 600 self.Append(file_menu, _("File"))
581 - #self.Append(file_edit, _("Edit")) 601 + self.Append(file_edit, _("Edit"))
582 #self.Append(view_menu, "View") 602 #self.Append(view_menu, "View")
583 #self.Append(tools_menu, "Tools") 603 #self.Append(tools_menu, "Tools")
584 self.Append(options_menu, _("Options")) 604 self.Append(options_menu, _("Options"))
@@ -609,6 +629,19 @@ class MenuBar(wx.MenuBar): @@ -609,6 +629,19 @@ class MenuBar(wx.MenuBar):
609 for item in self.enable_items: 629 for item in self.enable_items:
610 self.Enable(item, True) 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,12 +1430,166 @@ class LayoutToolBar(wx.ToolBar):
1397 self.ontool_text = True 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)