Created
April 20, 2015 04:24
-
-
Save mathsam/60ba7e1a7c910e829146 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python | |
#coding=utf-8 | |
# Nathive (and this file) is free software: you can redistribute it and/or | |
# modify it under the terms of the GNU General Public License as published by | |
# the Free Software Foundation, either version 3 of the License, or newer. | |
# | |
# You should have received a copy of the GNU General Public License along with | |
# this file. If not, see <http://www.gnu.org/licenses/>. | |
import time | |
import gtk | |
from nathive.libc import core | |
from nathive.gui.sandbox import Sandbox | |
from nathive.gui.hud import Hud | |
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) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment