Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTomeu Vizoso <tomeu@sugarlabs.org>2009-01-20 16:58:45 (GMT)
committer Tomeu Vizoso <tomeu@sugarlabs.org>2009-01-20 16:58:45 (GMT)
commitb3e2037e6b04890c427d5b69fa1bc6e265410c87 (patch)
tree25aaf8fee7f6a37ba9880138ee7498402f129f5d
parent8535ed6494b8ead86756c414ae9f305f35e25884 (diff)
Refactor canvas stuff to another module
-rw-r--r--canvas.py187
-rw-r--r--view.py253
2 files changed, 276 insertions, 164 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
+
diff --git a/view.py b/view.py
index b7b1914..66e29c7 100644
--- a/view.py
+++ b/view.py
@@ -23,41 +23,38 @@ import gtk
# We'd rather not depend on sugar in this class
from sugar.graphics import style
-class MindMapView(gtk.DrawingArea):
-
+from canvas import Canvas, CanvasElement
+
+class MindMapView(Canvas):
+
+ __gtype_name__ = 'MindMapView'
+
def __init__(self, model=None):
self._model = None
- self._row_changed_sid = None
self._row_deleted_sid = None
self._row_inserted_sid = None
- self._thought_views = None
- gobject.GObject.__init__(self)
+ Canvas.__init__(self)
- self.connect('expose-event', self.__expose_event_cb)
+ self.dragging_started.connect(self.__dragging_started_cb)
+ self.dragging_finished.connect(self.__dragging_finished_cb)
if model is not None:
self.model = model
- # DND stuff
- self._pressed_button = None
- self._press_start_x = None
- self._press_start_y = None
- self._last_clicked_thought = None
- self.drag_source_set(0, [], 0)
- self.add_events(gtk.gdk.BUTTON_PRESS_MASK |
- gtk.gdk.POINTER_MOTION_HINT_MASK)
- self.connect('motion-notify-event', self.__motion_notify_event_cb)
- self.connect('button-press-event', self.__button_press_event_cb)
- self.connect('drag-end', self.__drag_end_cb)
- self.connect('drag-data-get', self.__drag_data_get_cb)
- self.connect('drag-data-received', self.__drag_data_received_cb)
-
- self.drag_dest_set(gtk.DEST_DEFAULT_MOTION |
- gtk.DEST_DEFAULT_HIGHLIGHT |
- gtk.DEST_DEFAULT_DROP,
- [('application/x-thought', 0, 0)],
- gtk.gdk.ACTION_MOVE)
+ def __dragging_started_cb(self, **kwargs):
+ x, y = kwargs['position']
+ logging.debug('__dragging_started_cb %r %r' % (x, y))
+ thought = kwargs['element']
+ thought.dragging = True
+
+ def __dragging_finished_cb(self, **kwargs):
+ x, y = kwargs['position']
+ thought = kwargs['element']
+ logging.debug('__dragging_finished_cb %r %r' % (x, y))
+ thought.dragging = False
+ thought.model.x = x
+ thought.model.y = y
def set_model(self, new_model):
logging.debug('set_model %r' % new_model)
@@ -65,204 +62,132 @@ class MindMapView(gtk.DrawingArea):
return
if self._model is not None:
- self._model.disconnect(self._row_changed_sid)
self._model.disconnect(self._row_deleted_sid)
self._model.disconnect(self._row_inserted_sid)
self._model = new_model
if self._model is not None:
- self._row_changed_sid = \
- self._model.connect('row-changed', self.__row_changed_cb)
self._row_deleted_sid = \
self._model.connect('row-deleted', self.__row_deleted_cb)
self._row_inserted_sid = \
self._model.connect('row-inserted', self.__row_inserted_cb)
- self._thought_views = []
+ self.remove_all_elements()
for thought_model in self._model.get_thoughts():
though_view = ThoughtView(thought_model)
- self._thought_views.append(though_view)
+ self.add_element(though_view)
def get_model(self):
return self._model
model = property(get_model, set_model)
- def __expose_event_cb(self, widget, event):
- logging.debug('expose %r' % self._model)
- if self._model is None or not self._model:
- return
-
- context = self.window.cairo_create()
-
- rect = self.get_allocation()
- rect.x = 0
- rect.y = 0
- logging.debug('do_expose_event %r' % ((rect.x, rect.y, rect.width, rect.height),))
-
- 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 thought_view in self._thought_views:
- if thought_view.overlaps(event.area):
- context.save()
- thought_view.draw(context)
- context.restore()
-
- return False
-
- def _invalidate_whole_area(self):
- alloc = self.get_allocation()
- rect = gtk.gdk.Rectangle(0, 0, alloc.width, alloc.height)
- self.window.invalidate_rect(rect, True)
- self.window.process_updates(True)
-
- def __row_changed_cb(self, model, path, iter):
- logging.debug('__row_changed_cb %r' % path)
- self._invalidate_whole_area()
-
def __row_inserted_cb(self, model, path, iter):
logging.debug('__row_inserted_cb %r' % path)
thought_model = model.get_thought(path[0])
though_view = ThoughtView(thought_model)
- self._thought_views.append(though_view)
-
- self._invalidate_whole_area()
+ self.add_element(though_view)
def __row_deleted_cb(self, model, path):
logging.debug('__row_deleted_cb %r' % path)
- for thought_view in self._thought_views:
+ though_view = self._get_though_by_id(path[0])
+ self.remove_element(though_view)
+
+ def _get_though_by_id(self, thought_id):
+ for thought_view in self.get_elements():
if thought_view.model.id == path[0]:
- self._thought_views.remove(thought_view)
- break
+ return thought_view
+ return None
- self._invalidate_whole_area()
+class ThoughtView(CanvasElement):
- # DND methods
- def __motion_notify_event_cb(self, widget, event):
- if not self._pressed_button:
- return True
-
- # if the mouse button is not pressed, no drag should occurr
- if not event.state & gtk.gdk.BUTTON1_MASK:
- self._pressed_button = None
- return True
+ _PADDING = style.zoom(10)
- logging.debug('motion_notify_event_cb')
+ def __init__(self, model):
+ CanvasElement.__init__(self)
- if event.is_hint:
- x, y, state_ = event.window.get_pointer()
- else:
- x = event.x
- y = event.y
-
- if widget.drag_check_threshold(int(self._press_start_x),
- int(self._press_start_y),
- int(x),
- int(y)):
- context_ = widget.drag_begin([('application/x-thought', 0, 0)],
- gtk.gdk.ACTION_MOVE,
- 1,
- event)
- return True
-
- def __drag_end_cb(self, widget, drag_context):
- logging.debug('drag_end_cb')
- self._pressed_button = None
- self._press_start_x = None
- self._press_start_y = None
- self._last_clicked_thought = None
-
- def __drag_data_get_cb(self, widget, context, selection, target_type,
- event_time):
- logging.debug('drag_data_get_cb: requested target ' + selection.target)
-
- thought_id = self._last_clicked_thought.model.id
- selection.set(selection.target, 8, str(thought_id))
-
- 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_thought = \
- self._get_thought_at_coords(event.x, event.y)
- if self._last_clicked_thought is not None:
- self._pressed_button = event.button
- self._press_start_x = event.x
- self._press_start_y = event.y
-
- return False
-
- def _get_thought_at_coords(self, x, y):
- for thought in self._thought_views:
- thought_x = thought.model.x
- thought_y = thought.model.y
- width, height = thought.get_size()
-
- if x >= thought_x and (x <= thought_x + width) and \
- y >= thought_y and (y <= thought_y + height):
- return thought
- return None
+ self.model = model
+ self._last_width = -1
+ self._last_height = -1
+ self._dragging = False
+
+ self.model.changed.connect(self.__model_changed_cb)
- def __drag_data_received_cb(self, widget, drag_context, x, y,
- selection_data, info, time):
- thought_id = int(selection_data.data)
- for thought in self._thought_views:
- if thought.model.id == thought_id:
- thought.model.x = x
- thought.model.y = y
- break
+ def set_dragging(self, dragging):
+ if self._dragging != dragging:
+ self._dragging = dragging
+ self.invalidate()
-class ThoughtView(object):
+ def get_dragging(self):
+ return self._dragging
- _PADDING = style.zoom(10)
+ dragging = property(get_dragging, set_dragging)
- def __init__(self, model):
- self.model = model
- self._last_width = 0
- self._last_height = 0
+ def _get_name_layout(self, context=None):
+ if context is None:
+ visual = gtk.gdk.screen_get_default().get_system_visual()
+ pixmap = gtk.gdk.Pixmap(None, 1, 1, visual.depth)
+ context = pixmap.cairo_create()
- def draw(self, context):
if self.model.name is None or not self.model.name:
name = _('Unnamed')
else:
name = self.model.name
- context.save()
- context.set_source_rgb(0, 0, 0)
layout = context.create_layout()
layout.set_text(name)
+ return layout
+
+ def draw(self, context):
+ if self._dragging:
+ context.set_source_rgb(0.8, 0.8, 0.8)
+ else:
+ context.set_source_rgb(0, 0, 0)
+
+ context.save()
+ layout = self._get_name_layout(context)
+
width, height = layout.get_pixel_size()
+ width += self._PADDING * 2
+ height += self._PADDING * 2
- x, y = self.model.x + self._PADDING, self.model.y + self._PADDING
+ x = self.model.x + self._PADDING
+ y = self.model.y + self._PADDING
context.translate(x, y)
context.show_layout(layout)
context.restore()
- width += self._PADDING * 2
- height += self._PADDING * 2
-
context.save()
- context.set_line_width(1)
- context.set_source_rgb(0.2, 0.2, 0.2)
+ context.set_line_width(4)
+
context.rectangle(self.model.x, self.model.y, width, height)
context.stroke()
context.restore()
self._last_width = width
self._last_height = height
+
+ CanvasElement.draw(self, context)
- def overlaps(self, rect):
- return True
-
- def get_size(self, context=None):
+ def get_size(self):
return self._last_width, self._last_height
+ def __model_changed_cb(self, **kwargs):
+ self.x = self.model.x
+ self.y = self.model.y
+ self.invalidate()
+
+ def get_rect(self):
+ if -1 in (self._last_width, self._last_height):
+ layout = self._get_name_layout()
+ width, height = layout.get_pixel_size()
+ self._last_width = width + self._PADDING * 2
+ self._last_height = height + self._PADDING * 2
+
+ return gtk.gdk.Rectangle(self.model.x, self.model.y,
+ self._last_width, self._last_height)
+