def __init__(self, document): """Create canvas and show it. @document: Parent document object.""" # Allow debug tracking. main.log.allow_tracking(self) # Store parent document to use in methods. self.document = document # Last redraw timestamp. self.lastredraw = None # Event hook. self.pressed_button = 0 self.layout = gtk.Layout() self.eventbox = gtk.EventBox() self.eventbox.add(self.layout) self.eventbox.add_events(gtk.gdk.POINTER_MOTION_MASK) self.eventbox.connect('button-press-event', self.button_cb) self.eventbox.connect('button-release-event', self.release_cb) self.eventbox.connect('motion-notify-event', self.motion_cb) self.eventbox.connect('scroll-event', self.zoom_cb) self.eventbox.connect('enter-notify-event', self.enter_cb) self.eventbox.connect('leave-notify-event', self.leave_cb) self.eventbox.connect('size_allocate', self.allocate_cb) self.last_allocation_area = None self.last_motion_coordinates = None main.gui.window.add_events(gtk.gdk.POINTER_MOTION_MASK) main.gui.window.connect('key-press-event', self.key_cb) # Scrollbars. self.hscrollbar = gtk.HScrollbar() self.vscrollbar = gtk.VScrollbar() self.hadjustment = self.hscrollbar.get_adjustment() self.vadjustment = self.vscrollbar.get_adjustment() self.hadjustment.connect( 'value-changed', lambda x: self.sandbox.redraw_all(False, False, True)) self.vadjustment.connect( 'value-changed', lambda x: self.sandbox.redraw_all(False, False, True)) # Table. self.table = gtk.Table(2, 2) self.table.attach(self.eventbox, 0, 1, 0, 1) self.table.attach(self.hscrollbar, 0, 1, 1, 2, 4, 4) self.table.attach(self.vscrollbar, 1, 2, 0, 1, 4, 4) self.table.show_all() # Sandbox. self.sandbox = Sandbox(self) self.zoom_blocked = False # Head up display. self.hud = Hud(self) # Init redraw timing limits. self.obscured = [0] * 4 self.obscured_time = None # Perform to display. self.redraw_all()
class Canvas(object): """Canvas macro-widget displayed into tabs, each resulting object manage the visual representation of the parent document, including scrollbars, sandbox zoom system and hud floating stuff. ⌥: Main > Documents > {n}Document > Canvas.""" def __init__(self, document): """Create canvas and show it. @document: Parent document object.""" # Allow debug tracking. main.log.allow_tracking(self) # Store parent document to use in methods. self.document = document # Last redraw timestamp. self.lastredraw = None # Event hook. self.pressed_button = 0 self.layout = gtk.Layout() self.eventbox = gtk.EventBox() self.eventbox.add(self.layout) self.eventbox.add_events(gtk.gdk.POINTER_MOTION_MASK) self.eventbox.connect('button-press-event', self.button_cb) self.eventbox.connect('button-release-event', self.release_cb) self.eventbox.connect('motion-notify-event', self.motion_cb) self.eventbox.connect('scroll-event', self.zoom_cb) self.eventbox.connect('enter-notify-event', self.enter_cb) self.eventbox.connect('leave-notify-event', self.leave_cb) self.eventbox.connect('size_allocate', self.allocate_cb) self.last_allocation_area = None self.last_motion_coordinates = None main.gui.window.add_events(gtk.gdk.POINTER_MOTION_MASK) main.gui.window.connect('key-press-event', self.key_cb) # Scrollbars. self.hscrollbar = gtk.HScrollbar() self.vscrollbar = gtk.VScrollbar() self.hadjustment = self.hscrollbar.get_adjustment() self.vadjustment = self.vscrollbar.get_adjustment() self.hadjustment.connect( 'value-changed', lambda x: self.sandbox.redraw_all(False, False, True)) self.vadjustment.connect( 'value-changed', lambda x: self.sandbox.redraw_all(False, False, True)) # Table. self.table = gtk.Table(2, 2) self.table.attach(self.eventbox, 0, 1, 0, 1) self.table.attach(self.hscrollbar, 0, 1, 1, 2, 4, 4) self.table.attach(self.vscrollbar, 1, 2, 0, 1, 4, 4) self.table.show_all() # Sandbox. self.sandbox = Sandbox(self) self.zoom_blocked = False # Head up display. self.hud = Hud(self) # Init redraw timing limits. self.obscured = [0] * 4 self.obscured_time = None # Perform to display. self.redraw_all() def allocate_cb(self, widget, event): area = (event.x, event.y, event.width, event.height) if area == self.last_allocation_area: return self.last_allocation_area = area self.sandbox.redraw_all() def layout_reverse(self): """Reverse the layout children order, it is a way to keep the hud items over the sandbox despite the lack of a z-index system in the layout and fixed gtk widgets.""" childrens = self.layout.get_children() for children in reversed(childrens): x = self.layout.child_get_property(children, 'x') y = self.layout.child_get_property(children, 'y') self.layout.remove(children) self.layout.put(children, x, y) def enter_cb(self, widget, event): """To do when the user moves the mouse into the eventbox. @widget: Root widget. @event: Emited event object.""" self.hud.create_cursor() def leave_cb(self, widget, event): """To do when the user moves the mouse out of the eventbox. @widget: Root widget. @event: Emited event object.""" #self.hud.remove_cursor() # The eventbox emit the leave event pass # when the user click it, WTH! def key_cb(self, widget, event): """To do when the user press a key. @widget: Root widget. @event: Emited event object.""" tool = main.plugins.activetool if not tool: return catched = tool.key_press(event.keyval) return catched def button_cb(self, widget, event): """To do when the user click a mouse button over the eventbox. @widget: Root widget. @event: Emited event object.""" self.pressed_button = event.button x, y = self.sandbox.recoordinate(event.x, event.y) tool = main.plugins.activetool if not tool: return if self.pressed_button == 1: tool.button_primary(x, y, int(event.x), int(event.y)) elif self.pressed_button == 3: tool.button_secondary(x, y, int(event.x), int(event.y)) elif self.pressed_button == 2: self.scroll_xroot = event.x self.scroll_yroot = event.y self.hud.hide_cursor() main.gui.cursor.set_from_name('hand-closed') def motion_cb(self, widget, event): """To do when the user move the mouse over the eventbox. @widget: Root widget. @event: Emited event object.""" x, y = self.sandbox.recoordinate(event.x, event.y) if (x, y) == self.last_motion_coordinates: return else: self.last_motion_coordinates = (x, y) tool = main.plugins.activetool if not tool: return if self.pressed_button == 1: tool.motion_primary(x, y, int(event.x), int(event.y)) elif self.pressed_button == 3: tool.motion_secondary(x, y, int(event.x), int(event.y)) elif self.pressed_button == 2: self.handscroll(event.x, event.y) else: tool.motion_over(x, y, int(event.x), int(event.y)) main.gui.statusbar.update(x, y) self.hud.move_cursor(x, y) self.hud.dump_cursor() def release_cb(self, widget, event): """To do when the user release a mouse button. @widget: Root widget. @event: Emited event object.""" tool = main.plugins.activetool if not tool: return if self.pressed_button == 1: tool.release_primary() elif self.pressed_button == 2: main.gui.cursor.set_default() elif self.pressed_button == 3: tool.release_secondary() self.pressed_button = 0 self.hud.show_cursor() def handscroll(self, x, y): """Perform a freehand scroll in relation to the click root position. @x: The motion coordinate in x axis. @y: The motion coordinate in y axis.""" xrel = x - self.scroll_xroot yrel = y - self.scroll_yroot self.scroll_xroot = x self.scroll_yroot = y self.hadjustment.set_value(self.hadjustment.value - xrel) self.vadjustment.set_value(self.vadjustment.value - yrel) def zoom_cb(self, widget, event): """To do when the user uses the mouse scroll over the eventbox. @widget: Root widget. @event: Emited event object.""" if self.zoom_blocked: return else: self.zoom_blocked = True # Get coordinates. x = int(event.x) y = int(event.y) # Set pointer center. re_x, re_y = self.sandbox.recoordinate(x, y) sandbox_width, sandbox_height = self.sandbox.get_alloc() center = [re_x, re_y] # Set pointer gap from center. gap_x = (x - (sandbox_width / 2)) gap_y = (y - (sandbox_height / 2)) gap = [gap_x, gap_y] # Allowed zoom levels. multipliers = [1, 1.5, 2, 3, 4, 5, 6, 7, 8] multipliers += [10, 12, 14, 16, 20, 24, 28, 32] levels = [1.0 / x for x in reversed(multipliers)] levels += [float(x) for x in multipliers[1:]] level = levels.index(self.sandbox.factor) # Change zoom factor. if event.direction == gtk.gdk.SCROLL_UP: if level == len(levels)-1: self.zoom_blocked = False return self.sandbox.factor = levels[level+1] if event.direction == gtk.gdk.SCROLL_DOWN: if level == 0: self.zoom_blocked = False return self.sandbox.factor = levels[level-1] # Set interpolation algoritm. if self.sandbox.factor > 1: self.interpolation = gtk.gdk.INTERP_NEAREST else: self.interpolation = gtk.gdk.INTERP_NEAREST # Fill background. self.sandbox.pixbuf.fill(0x00000000) # Apply new zoom. main.gui.statusbar.update(*center) self.sandbox.redraw_all(center, gap) # Refresh hud. self.hud.create_cursor() self.hud.move_cursor(*center) self.hud.dump_cursor() self.hud.set_area(None) self.hud.dump() # Unblock zoom change. self.zoom_blocked = False def redraw(self, x, y, width, height, propagate=True, timing=False): """Redraw an outdated area in the displayed image. @x: The x coordinate of upper-left corner of rectangle. @y: The y coordinate of upper-left corner of rectangle. @width: The width of rectangle. @height: The height of rectangle. @propagate: Boolean to redraw or not the related sandbox area. @timing: Boolean to use or not the redraw timing system.""" # Use the timing system. if timing: # Update obscure limits. obs = self.obscured if not obs[0] or x < obs[0]: obs[0] = x if not obs[1] or y < obs[1]: obs[1] = y if not obs[2] or x+width > obs[2]: obs[2] = x+width if not obs[3] or y+height > obs[3]: obs[3] = y+height if not self.obscured_time: self.obscured_time = time.time() # Abort redraw if there are more redraws in the stack and # the last redraw is too close. if gtk.events_pending(): if time.time() - self.obscured_time < 0.04: return # Overwrite redraw area with obscure limits. x = self.obscured[0] y = self.obscured[1] width = self.obscured[2] - self.obscured[0] height = self.obscured[3] - self.obscured[1] # Clear area with black and transparent pixels. core.clear( self.document.pointer, self.document.width, self.document.height, x, y, width, height) # Draw prelower layer. prelower = self.document.layers.prelower prelower.composite(1, self.document, 0, 0, x, y, width, height) # Avoid if there is no active layer. if self.document.layers.active: # Draw active (middle) layer. self.document.layers.active.composite( 1, self.document, self.document.layers.active.xpos, self.document.layers.active.ypos, x, y, width, height) # Draw preupper layer. preupper = self.document.layers.preupper preupper.composite(1, self.document, 0, 0, x, y, width, height) # Progagate redraw to sandbox. if propagate: if hasattr(self.sandbox, 'pixbuf'): self.sandbox.redraw(x, y, width, height) # Reset redraw timing limits. if timing: self.obscured = [0] * 4 self.obscured_time = None def redraw_all(self, propagate=True): """Redraw the displayed image completely. @propagate: Boolean to redraw or not the related sandbox area.""" self.redraw(0, 0, self.document.width, self.document.height, propagate) def redraw_step(self, x, y, width, height, recursion=True): """Redraw an outdated area in the displayed image step by step. @x: The x coordinate of upper-left corner of rectangle. @y: The y coordinate of upper-left corner of rectangle. @width: The width of rectangle. @height: The height of rectangle.""" # Step width and height. step_w = 250 step_h = 160 # Calc needed steps to redraw the requested zone. steps_in_x = width / step_w steps_in_y = height / step_h if width % step_w: steps_in_x += 1 if height % step_h: steps_in_y += 1 # Set redraw timestamp. thisredraw = time.time() self.lastredraw = thisredraw # Redraw step by step, stop if there are new redraws pending. for step_y in range(steps_in_y): for step_x in range(steps_in_x): if gtk.events_pending(): gtk.main_iteration() if thisredraw != self.lastredraw: return self.redraw( x + (step_x * step_w), y + (step_y * step_h), step_w, step_h) # Redraw all to clean artifacts, this only will be executed if the # previous loop ends completely (there is no more event pendings). if recursion: self.redraw_all_step(False) def redraw_all_step(self, recursion=True): """Redraw the displayed image completely step by step.""" self.redraw_step( 0, 0, self.document.width, self.document.height, recursion)