diff options
Diffstat (limited to 'canvas.py')
-rw-r--r-- | canvas.py | 187 |
1 files changed, 187 insertions, 0 deletions
diff --git a/canvas.py b/canvas.py new file mode 100644 index 0000000..d137f78 --- /dev/null +++ b/canvas.py @@ -0,0 +1,187 @@ +# Copyright (C) 2009, Tomeu Vizoso +# +# This program 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 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +import logging + +import gobject +import gtk + +from sugar import dispatch + +class Canvas(gtk.DrawingArea): + + __gtype_name__ = 'MindMapCanvas' + + def __init__(self): + self._elements = [] + self._pressed_button = None + self._press_start_x = None + self._press_start_y = None + self._last_clicked_element = None + self._dragging = False + + gobject.GObject.__init__(self) + + self.dragging_started = dispatch.Signal() + self.dragging_finished = dispatch.Signal() + + self.add_events(gtk.gdk.BUTTON_PRESS_MASK | + gtk.gdk.BUTTON_RELEASE_MASK | + gtk.gdk.POINTER_MOTION_MASK | + gtk.gdk.POINTER_MOTION_HINT_MASK) + + self.connect('expose-event', self.__expose_event_cb) + self.connect('button-press-event', self.__button_press_event_cb) + self.connect('button-release-event', self.__button_release_event_cb) + self.connect('motion-notify-event', self.__motion_notify_event_cb) + + def __expose_event_cb(self, widget, event): + context = self.window.cairo_create() + + rect = self.get_allocation() + rect.x = 0 + rect.y = 0 + + context.rectangle(event.area.x, event.area.y, + event.area.width, event.area.height) + context.clip() + + context.set_source_rgb(1.0, 1.0, 1.0) + context.paint() + + for element in self._elements: + rect = element.get_rect() + if rect.intersect(event.area): + context.save() + + context.rectangle(rect.x, rect.y, rect.width, rect.height) + context.clip() + + element.draw(context) + + context.restore() + + return False + + def get_element_at(self, x, y): + for element in self._elements: + rect = element.get_rect() + if (x >= rect.x and x <= rect.x + rect.width) and \ + (y >= rect.y and y <= rect.y + rect.height): + return element + return None + + def _invalidate(self, x, y, width, height): + if self.window is None: + return + rect = gtk.gdk.Rectangle(x, y, width, height) + self.window.invalidate_rect(rect, True) + self.window.process_updates(True) + + def add_element(self, element): + element.invalidated.connect(self.__element_invalidated_cb) + self._elements.append(element) + rect = element.get_rect() + self._invalidate(rect.x, rect.y, rect.width, rect.height) + + def remove_all_elements(self): + rect = gtk.gdk.Rectangle(0, 0, 0, 0) + for element in self._elements: + rect.union(element.get_rect()) + + self._invalidate(rect.x, rect.y, rect.width, rect.height) + + self._elements = [] + + def __element_invalidated_cb(self, **kwargs): + element = kwargs['sender'] + + rect = element.previous_rect + self._invalidate(rect.x, rect.y, rect.width, rect.height) + + rect = element.get_rect() + self._invalidate(rect.x, rect.y, rect.width, rect.height) + + def __button_press_event_cb(self, widget, event): + logging.debug('button_press_event_cb') + + if event.button == 1 and event.type == gtk.gdk.BUTTON_PRESS: + self._last_clicked_element = self.get_element_at(event.x, event.y) + logging.debug('button_press_event_cb %r' % self._last_clicked_element) + if self._last_clicked_element is not None: + self._pressed_button = event.button + self._press_start_x = event.x + self._press_start_y = event.y + + return False + + def __button_release_event_cb(self, widget, event): + logging.debug('button_release_event_cb') + if self._dragging: + self.dragging_finished.send(self, position=(event.x, event.y), + element=self._last_clicked_element) + self._cleanup_drag() + + return False + + def _cleanup_drag(self): + self._last_clicked_element = None + self._pressed_button = None + self._press_start_x = None + self._press_start_y = None + self._dragging = False + + def __motion_notify_event_cb(self, widget, event): + if self._dragging or self._last_clicked_element is None: + return True + + # if the mouse button is not pressed, no drag should occurr + if not event.state & gtk.gdk.BUTTON1_MASK: + self._cleanup_drag() + return True + + logging.debug('motion_notify_event_cb') + + if event.is_hint: + x, y, state_ = event.window.get_pointer() + else: + x = event.x + y = event.y + + if self.drag_check_threshold(int(self._press_start_x), + int(self._press_start_y), + int(x), + int(y)): + self._dragging = True + self.dragging_started.send(self, position=(x, y), + element=self._last_clicked_element) + + return True + +class CanvasElement(object): + def __init__(self): + self.invalidated = dispatch.Signal() + self.previous_rect = (0, 0, 0, 0) + + def draw(self, context): + self.previous_rect = self.get_rect() + + def invalidate(self): + self.invalidated.send(self) + + def get_rect(self): + pass + |