# 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_motion = 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 get_elements(self): return 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 True 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._last_clicked_element is None: for element in self._elements: rect = element.get_rect() if (event.x >= rect.x and event.x <= rect.x + rect.width) and \ (event.y >= rect.y and event.y <= rect.y + rect.height): element.hovering = True else: element.hovering = False return False # if the mouse button is not pressed, no drag should occurr if not event.state & gtk.gdk.BUTTON1_MASK: self._cleanup_drag() return False if event.is_hint: x, y, state_ = event.window.get_pointer() else: x = event.x y = event.y if self._dragging: self.dragging_motion.send(self, position=(x, y)) return False logging.debug('motion_notify_event_cb') 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 False class CanvasElement(object): def __init__(self): self.invalidated = dispatch.Signal() self.previous_rect = gtk.gdk.Rectangle(0, 0, 0, 0) self._hovering = False def draw(self, context): self.previous_rect = self.get_rect() def invalidate(self): self.invalidated.send(self) def get_rect(self): pass def set_hovering(self, hovering): if self._hovering != hovering: self._hovering = hovering self.invalidate() def get_hovering(self): return self._hovering hovering = property(get_hovering, set_hovering)