Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/view.py
diff options
context:
space:
mode:
Diffstat (limited to 'view.py')
-rw-r--r--view.py268
1 files changed, 268 insertions, 0 deletions
diff --git a/view.py b/view.py
new file mode 100644
index 0000000..b7b1914
--- /dev/null
+++ b/view.py
@@ -0,0 +1,268 @@
+# 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
+from gettext import gettext as _
+
+import gobject
+import gtk
+
+# We'd rather not depend on sugar in this class
+from sugar.graphics import style
+
+class MindMapView(gtk.DrawingArea):
+
+ 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)
+
+ self.connect('expose-event', self.__expose_event_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 set_model(self, new_model):
+ logging.debug('set_model %r' % new_model)
+ if self._model == new_model:
+ 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 = []
+ for thought_model in self._model.get_thoughts():
+ though_view = ThoughtView(thought_model)
+ self._thought_views.append(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()
+
+ def __row_deleted_cb(self, model, path):
+ logging.debug('__row_deleted_cb %r' % path)
+
+ for thought_view in self._thought_views:
+ if thought_view.model.id == path[0]:
+ self._thought_views.remove(thought_view)
+ break
+
+ self._invalidate_whole_area()
+
+ # 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
+
+ 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 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
+
+ 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
+
+class ThoughtView(object):
+
+ _PADDING = style.zoom(10)
+
+ def __init__(self, model):
+ self.model = model
+ self._last_width = 0
+ self._last_height = 0
+
+ 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)
+
+ width, height = layout.get_pixel_size()
+
+ x, y = self.model.x + self._PADDING, 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.rectangle(self.model.x, self.model.y, width, height)
+ context.stroke()
+ context.restore()
+
+ self._last_width = width
+ self._last_height = height
+
+ def overlaps(self, rect):
+ return True
+
+ def get_size(self, context=None):
+ return self._last_width, self._last_height
+