class ObjectsCanvas(wx.ScrolledWindow): def __init__(self, parent, frame, **kwargs): """ Constructor must be called after objects_container is set!!! Because of notebook wanting itself to be parent, we pass frame as another variable """ super(ObjectsCanvas, self).__init__(parent, **kwargs) self.frame = frame self.SetDoubleBuffered(True) self.size = 0, 0 self.SetName("ObjectCanvas") self.commands = Command() self.saved_command = self.commands self.canvas_size = [1, 1] # w, h self.view_point = [0, 0] # x, y self.filepath = None self.zoom = 1 self.update_bounds() self.do_bindings() def zoom_out(self): if self.zoom >=0.25: self.zoom -= 0.1 self.update_bounds() self.Refresh() def zoom_in(self): self.zoom += 0.1 self.update_bounds() self.Refresh() def zoom_restore(self): self.zoom = 1 self.update_bounds() self.Refresh() @property def strategy(self): return self.frame.strategy def do_bindings(self): self.Bind(wx.EVT_PAINT, self.on_paint_event) self.Bind(wx.EVT_SIZE, self.on_size_event) self.Bind(wx.EVT_LEFT_DOWN, self.on_left_button_down) self.Bind(wx.EVT_LEFT_UP, self.on_left_button_up) self.Bind(wx.EVT_MOTION, self.on_mouse_motion) self.Bind(wx.EVT_LEFT_DCLICK, self.on_left_button_dclick) self.Bind(wx.EVT_RIGHT_DOWN, self.on_right_button_down) self.Bind(wx.EVT_RIGHT_UP, self.on_right_button_up) self.Bind(wx.EVT_SCROLLWIN, self.on_scroll) @property def is_changed(self): return self.commands is not self.saved_command @property def has_unsaved_changes(self): if self.is_changed: return True if self.filepath is None: if self.petri.places or self.petri.transitions: return True return False def GetObjectsInRect(self, lx, ly, tx, ty): for obj in self.get_objects_iter(): if not obj.is_selectable(): continue obj.unselect_temporary() if obj.in_rect(lx, ly, tx, ty): yield obj def get_name(self): result = self.GetName() if self.has_unsaved_changes: result = '*'+result return result def load_from_file(self, filepath, format): net = None with open(filepath, 'rb') as f: data = f.read() net = format.import_net(data) self.update_title(filepath) self.filepath = filepath self.petri = net def update_title(self, filepath): basename = osp.basename(filepath) title = osp.splitext(basename)[0] self.SetName(title) def save_to_file(self, filepath, net, format): if format.persistent: self.update_title(filepath) data = format.export_net(net) with open(filepath, 'wb') as f: f.write(data) if format.persistent: self.filepath = filepath self.saved_command = self.commands def on_scroll(self, event): orientation = event.GetOrientation() event.Skip() wx.CallAfter(self.process_scroll_event, orientation) def process_scroll_event(self, orientation): if not self.strategy.is_moving_objects: pos = self.GetScrollPos(orientation) rng = self.GetScrollRange(orientation) - self.GetScrollThumb(orientation) if rng==0: self.Refresh() return ind = 1 if orientation == wx.VERTICAL else 0 new_vp = (float(pos) / rng) * (self.canvas_size[ind] - self.size[ind]) self.view_point[ind] = int(new_vp) self.Refresh() def append_command(self, command): if command.does_nothing: return self.commands = self.commands.add_command(command) self.frame.on_command_append() def update_frame_menu(self): self.frame.update_menu() def undo(self): if self.can_undo(): self.commands = self.commands.go_prev() self.Refresh() def can_undo(self): return self.strategy.allow_undo_redo and self.commands.has_prev() def redo(self): if self.can_redo(): self.commands = self.commands.go_next() self.Refresh() def can_redo(self): return self.strategy.allow_undo_redo and self.commands.has_next() def can_select(self): return self.strategy.can_select can_cut = can_delete = can_paste = can_copy = can_select def copy(self): if self.can_copy(): self.on_copy() def on_copy(self): raise NotImplementedError def paste(self): if self.can_paste(): self.on_paste() def on_paste(self): raise NotImplementedError def cut(self): if self.can_cut(): self.on_cut() def on_cut(self): raise NotImplementedError def delete(self): if self.can_delete(): self.on_delete() def on_delete(self): raise NotImplementedError def select_all(self): if self.can_select(): self.strategy.on_select_all() def save(self, format): return self.save_as(filepath=self.filepath, format=format) def save_as(self, format, filepath=None): if filepath is None: while True: print format dlg = wx.FileDialog( self, message="Save file as ...", defaultFile="", wildcard=format.get_wildcard(), style=wx.SAVE ) if dlg.ShowModal() == wx.ID_OK: filepath = dlg.GetPath() else: return if not osp.exists(filepath): break if not osp.isfile(filepath): continue dlg = wx.MessageDialog(self, message='File (%s) already exists. Overwrite it?'%filepath, style=wx.YES_NO|wx.CANCEL|wx.CENTER) result = dlg.ShowModal() dlg.Destroy() if result == wx.ID_YES: break elif result == wx.ID_NO: continue elif result == wx.ID_CANCEL: return if not filepath: return self.save_to_file(filepath, self.petri, format=format) return True def on_size_event(self, event): self.size = event.GetSize() self.update_bounds() self.Refresh() def on_paint_event(self, event): dc = SmartDC(self) w, h = self.size dc.SetPen(wx.WHITE_PEN) dc.SetBrush(wx.WHITE_BRUSH) dc.DrawRectangleOnScreen(0,0,w,h) self.draw(dc, w, h) def on_left_button_down(self, event): if self.strategy.need_capture_mouse: self.mouse_in = True self.on_lbutton_timer() self.strategy.on_left_down(event) self.SetFocus() def on_right_button_down(self, event): self.strategy.on_right_down(event) def on_right_button_up(self, event): self.strategy.on_right_up(event) def on_left_button_dclick(self, event): self.strategy.on_left_dclick(event) self.SetFocus() def on_key_down(self, event): self.strategy.on_key_down(event) def on_lbutton_timer(self, *args, **kwargs): """ Dirty hack to catch the moment when mouse leaves window and capture it. wx.EVT_LEAVE_WINDOW is not always sent. """ x, y, w, h = self.GetScreenRect() w -= constants.VSCROLL_X h -= constants.HSCROLL_Y mx, my = wx.GetMousePosition() state = wx.GetMouseState() if not state.LeftDown(): if self.HasCapture(): self.ReleaseMouse() return mouse_in = x <= mx <= x+w and y <= my <= y+h if mouse_in and not self.mouse_in: if self.HasCapture(): self.ReleaseMouse() elif not mouse_in and self.mouse_in: self.CaptureMouse() self.mouse_in = mouse_in wx.CallLater(20, self.on_lbutton_timer) def get_object_at(self, x, y): """ Gets object under given virtual position """ # Check in reverse order so the topmost object will be selected for obj in self.get_objects_reversed_iter(): if obj.contains_point(x, y): return obj def on_mouse_motion(self, event): self.strategy.on_motion(event) def on_left_button_up(self, event): self.strategy.on_left_up(event) def get_objects_iter(self): raise NotImplementedError def get_objects_reversed_iter(self): raise NotImplementedError def update_bounds(self): """ Update canvas size and adjust scrollbars to it """ if self.strategy.is_moving_objects: # Do not update when user is moving something, because it will cause mess. return max_x, max_y = 0, 0 for obj in self.get_objects_iter(): x,y = obj.get_position() w,h = obj.get_size() max_x = max(max_x, x+w) max_y = max(max_y, y+h) max_x = max_x * self.zoom max_y = max_y * self.zoom max_x = max(max_x+constants.RIGHT_OFFSET, self.size[0]-constants.VSCROLL_X) max_y = max(max_y+constants.BOTTOM_OFFSET, self.size[1]-constants.HSCROLL_Y) self.canvas_size[0], self.canvas_size[1] = max_x, max_y prev_x, prev_y = self.GetScrollPos(wx.HORIZONTAL), self.GetScrollPos(wx.VERTICAL) self.SetScrollbars(1, 1, max_x, max_y) self.Scroll(prev_x, prev_y) self.process_scroll_event(wx.VERTICAL) self.process_scroll_event(wx.HORIZONTAL) def screen_to_canvas_coordinates(self, point): x, y = point vx, vy = self.view_point x = x / self.zoom y = y / self.zoom return (x+vx, y+vy) def canvas_to_screen_coordinates(self, point): x, y = point x = (x - self.view_point[0])*self.zoom y = (y - self.view_point[1])*self.zoom return (x, y) def draw(self, dc, w, h): for obj in self.get_objects_iter(): obj.draw(dc, self.zoom) self.strategy.draw(dc, self.zoom)