pitivi r1124 - in branches/SOC_2008_BLEWIS: . pitivi/ui



Author: blewis
Date: Sat May 31 01:48:38 2008
New Revision: 1124
URL: http://svn.gnome.org/viewvc/pitivi?rev=1124&view=rev

Log:
* pitivi/ui/test.py:
Test code...will later delete
* pitivi/ui/timelineobjects.py:
Added new class, but everything is broken
* pitivi/ui/util.py:
Convenience stuff for goocanvas. Similar to goocanvashelper in gst-editor
but with a much shorter name =P


Added:
   branches/SOC_2008_BLEWIS/pitivi/ui/test.py
   branches/SOC_2008_BLEWIS/pitivi/ui/util.py
Modified:
   branches/SOC_2008_BLEWIS/ChangeLog
   branches/SOC_2008_BLEWIS/pitivi/ui/timelineobjects.py

Added: branches/SOC_2008_BLEWIS/pitivi/ui/test.py
==============================================================================
--- (empty file)
+++ branches/SOC_2008_BLEWIS/pitivi/ui/test.py	Sat May 31 01:48:38 2008
@@ -0,0 +1,73 @@
+import gobject
+gobject.threads_init()
+import pygtk
+pygtk.require("2.0")
+import gtk
+import goocanvas
+import timelineobjects
+from itertools import cycle
+from util import make_item, set_pos, center, Text, group
+
+
+LABELS = "one two three four five six seven".split()
+
+box_a = (
+    goocanvas.Rect,
+    {
+        "width" : 50,
+        "height" : 30, 
+        "stroke_color" : "black",
+        "fill_color_rgba" : 0x556633FF
+    },
+    {}
+)
+box_b = (
+    goocanvas.Rect,
+    {
+        "width" : 75,
+        "height" : 30,
+        "stroke_color" : "black",
+        "fill_color_rgba" : 0x663333FF,
+    },
+    {}
+)
+
+box = cycle((box_a, box_b))
+
+label = (
+    Text,
+    {
+        "font" : "Sans 9",
+        "text" : "will be replaced",
+        "fill_color_rgba" : 0x66AA66FF,
+        "anchor" : gtk.ANCHOR_CENTER
+    },
+    {}
+)
+
+def make_box(text):
+    b = make_item(box.next())
+    t = make_item(label)
+    t.props.text = text
+    set_pos(t, center(b))
+    return group(b, t)
+
+def make_widget(text):
+    b = gtk.Button(text)
+    return goocanvas.Widget(widget=b, width=75,
+        height=50)
+
+t = timeline.SimpleTimeline()
+for word in LABELS:
+    t.add(make_box(word))
+
+s = gtk.ScrolledWindow()
+s.set_policy(gtk.POLICY_ALWAYS, gtk.POLICY_NEVER)
+s.add(t)
+w = gtk.Window()
+w.add(s)
+w.show_all()
+w.connect("destroy", gtk.main_quit)
+gtk.main()
+
+

Modified: branches/SOC_2008_BLEWIS/pitivi/ui/timelineobjects.py
==============================================================================
--- branches/SOC_2008_BLEWIS/pitivi/ui/timelineobjects.py	(original)
+++ branches/SOC_2008_BLEWIS/pitivi/ui/timelineobjects.py	Sat May 31 01:48:38 2008
@@ -42,6 +42,9 @@
 from sourcefactories import beautify_length
 from gettext import gettext as _
 
+import goocanvas
+from util import *
+
 # Default width / height ratio for simple elements
 DEFAULT_SIMPLE_SIZE_RATIO = 1.50 # default height / width ratio
 
@@ -69,458 +72,554 @@
     hours = mins / 60
     return "%02d:%02d:%02d.%03d" % (hours, mins, sec, ms)
 
-class SimpleTimeline(gtk.Layout):
-    """ Simple Timeline representation """
-
-    def __init__(self, **kw):
-        gobject.GObject.__init__(self, **kw)
-
-        self.hadjustment = self.get_property("hadjustment")
-
-        # timeline and top level compositions
-        self.timeline = instance.PiTiVi.current.timeline
-        self.condensed = self.timeline.videocomp.condensed
-
-        # TODO : connect signals for when the timeline changes
-
-        # widgets correspondance dictionnary
-        # MAPPING timelineobject => widget
-        self.widgets = {}
-
-        # edit-mode
-        # True when in editing mode
-        self._editingMode = False
-        self.editingWidget = SimpleEditingWidget()
-        self.editingWidget.connect("hide-me", self._editingWidgetHideMeCb)
-
-        # Connect to timeline.  We must remove and reset the callbacks when
-        # changing project.
-        self.project_signals = SignalGroup()
-        # FIXME: do we need this? or will the newproject sginal implicitly
-        # handle this???
-        self._connectToTimeline(instance.PiTiVi.current.timeline)
-        instance.PiTiVi.connect("new-project-loaded",
-            self._newProjectLoadedCb)
-        instance.PiTiVi.connect("project-closed", self._projectClosedCb)
-        instance.PiTiVi.connect("new-project-loading",
-            self._newProjectLoadingCb)
-        instance.PiTiVi.connect("new-project-failed",
-            self._newProjectFailedCb)
-
-        # size
-        self.width = int(DEFAULT_WIDTH)
-        self.height = int(DEFAULT_HEIGHT)
-        self.realWidth = 0 # displayed width of the layout
-        self.childheight = int(DEFAULT_SIMPLE_ELEMENT_HEIGHT)
-        self.childwidth = int(DEFAULT_SIMPLE_ELEMENT_WIDTH)
-        self.set_size_request(int(MINIMUM_WIDTH), int(MINIMUM_HEIGHT))
-        self.set_property("width", int(DEFAULT_WIDTH))
-        self.set_property("height", int(DEFAULT_HEIGHT))
-
-        # event callbacks
-        self.connect("expose-event", self._exposeEventCb)
-        self.connect("notify::width", self._widthChangedCb)
-        self.connect("size-allocate", self._sizeAllocateCb)
-        self.connect("realize", self._realizeCb)
-
-        # drag and drop
-        self.drag_dest_set(gtk.DEST_DEFAULT_DROP | gtk.DEST_DEFAULT_MOTION,
-                           [dnd.FILESOURCE_TUPLE],
-                           gtk.gdk.ACTION_COPY)
-        self.connect("drag-data-received", self._dragDataReceivedCb)
-        self.connect("drag-leave", self._dragLeaveCb)
-        self.connect("drag-motion", self._dragMotionCb)
-        self.slotposition = -1
-
-        self.draggedelement = None
-
-        self.show_all()
+class SimpleTimeline(goocanvas.Canvas):
+    __gtype_name__ = 'SimpleTimeline'
 
+    def __init__(self, *args, **kwargs):
+        goocanvas.Canvas.__init__(self, *args, **kwargs)
+        self.props.automatic_bounds = False
+
+        self.root = self.get_root_item()
+        self.items = HList()
+        self.root.add_child(self.items)
+
+        self.left = None
+        self.l_thresh = None
+        self.right = None
+        self.r_thresh = None
+        self.drag_sigid = None
+
+        self.connect("size_allocate", self._size_allocate)
+        self.items.connect("notify::width", self._request_size)
+        self.items.connect("notify::height", self._request_size)
+
+        self.signals = {}
+
+        self.scale = 1.0
+
+    def _request_size(self, item, prop):
+        self.set_size_request(int(self.items.width), int(
+            self.items.height))
+        self.set_bounds(0, 0, self.items.width, self.items.height)
+        return True
 
-    ## Project callbacks
-
-    def _connectToTimeline(self, timeline):
-        self.timeline = timeline
-        self.condensed = self.timeline.videocomp.condensed
-        self.project_signals.connect(self.timeline.videocomp,
-                                     "condensed-list-changed",
-                                     None, self._condensedListChangedCb)
-
-    def _newProjectLoadingCb(self, unused_inst, unused_project):
-        gst.log("...")
-
-    def _newProjectLoadedCb(self, unused_inst, project):
-        gst.log("...")
-        assert(instance.PiTiVi.current == project)
-        # now we connect to the new project, so we can receive any
-        # signals that might be emitted while the project is loading
-        self._connectToTimeline(project.timeline)
-        # TODO: display final state of project now that loading has
-        # completed. this callback doesn't do do much else
-
-        # LOAD THE TIMELINE !!!
-        self._condensedListChangedCb(None, self.timeline.videocomp.condensed)
-
-    def _newProjectFailedCb(self, unused_inst, unused_reason, unused_uri):
-        # oops the project failed to load
-        self._clearTimeline()
-
-    def _clearTimeline(self):
-        self.switchToNormalMode()
-        self.project_signals.disconnectAll()
-        for widget in self.widgets.itervalues():
-            self.remove(widget)
-        self.widgets = {}
-
-    def _projectClosedCb(self, unused_pitivi, unused_project):
-        self._clearTimeline()
-
-    ## Timeline callbacks
-
-    def _condensedListChangedCb(self, unused_videocomp, clist):
-        """ add/remove the widgets """
-        gst.debug("condensed list changed in videocomp")
-
-        current = self.widgets.keys()
-        self.condensed = clist
-
-        new = [x for x in clist if not x in current]
-        removed = [x for x in current if not x in clist]
-
-        # new elements
-        for element in new:
-            # add the widget to self.widget
-            gst.debug("Adding new element to the layout")
-            if isinstance(element, TimelineFileSource):
-                widget = SimpleSourceWidget(element)
-                widget.connect("delete-me", self._sourceDeleteMeCb, element)
-                widget.connect("edit-me", self._sourceEditMeCb, element)
-                widget.connect("drag-begin", self._sourceDragBeginCb, element)
-                widget.connect("drag-end", self._sourceDragEndCb, element)
-            else:
-                widget = SimpleTransitionWidget(element)
-            self.widgets[element] = widget
-            self.put(widget, 0, 0)
-            widget.show()
-
-        # removed elements
-        for element in removed:
-            self.remove(self.widgets[element])
-            del self.widgets[element]
-
-        self._resizeChildrens()
-
-
-    ## Utility methods
-
-    def _getNearestSourceSlot(self, x):
-        """
-        returns the nearest file slot position available for the given position
-        Returns the value in condensed list position
-        Returns n , the element before which it should go
-        Return -1 if it's meant to go last
-        """
-        if not self.condensed or x < 0:
-            return 0
-        if x > self.width - DEFAULT_SIMPLE_SPACING:
-            return -1
-
-        pos = DEFAULT_SIMPLE_SPACING
-        order = 0
-        # TODO Need to avoid getting position between source and transition
-        for source in self.condensed:
-            if isinstance(source, TimelineSource):
-                spacing = self.childwidth
-            elif isinstance(source, TimelineTransition):
-                spacing = self.childwidth / 2
-            else:
-                # this shouldn't happen !! The condensed list only contains
-                # sources and/or transitions
-                pass
-            if x <= pos + spacing / 2:
-                return order
-            pos = pos + spacing + DEFAULT_SIMPLE_SPACING
-            order = order + 1
-        return -1
-
-    def _getNearestSourceSlotPixels(self, x):
-        """
-        returns the nearest file slot position available for the given position
-        Returns the value in pixels
-        """
-        if not self.condensed or x < 0:
-            return DEFAULT_SIMPLE_SPACING
-        if x > self.width - DEFAULT_SIMPLE_SPACING:
-            return self.width - 2 * DEFAULT_SIMPLE_SPACING
-
-        pos = DEFAULT_SIMPLE_SPACING
-        # TODO Need to avoid getting position between source and transition
-        for source in self.condensed:
-            if isinstance(source, TimelineSource):
-                spacing = self.childwidth
-            elif isinstance(source, TimelineTransition):
-                spacing = self.childwidth / 2
-            else:
-                # this shouldn't happen !! The condensed list only contains
-                # sources and/or transitions
-                pass
-            if x <= pos + spacing / 2:
-                return pos
-            pos = pos + spacing + DEFAULT_SIMPLE_SPACING
-        return pos
-
-
-    ## Drawing
-
-    def _drawDragSlot(self):
-        if self.slotposition == -1:
-            return
-        self.bin_window.draw_rectangle(self.style.black_gc, True,
-                                       self.slotposition, DEFAULT_SIMPLE_SPACING,
-                                       DEFAULT_SIMPLE_SPACING, self.childheight)
-
-    def _eraseDragSlot(self):
-        if self.slotposition == -1:
-            return
-        self.bin_window.draw_rectangle(self.style.white_gc, True,
-                                       self.slotposition, DEFAULT_SIMPLE_SPACING,
-                                       DEFAULT_SIMPLE_SPACING, self.childheight)
-
-    def _gotFileFactory(self, filefactory, x, unused_y):
-        """ got a filefactory at the given position """
-        # remove the slot
-        self._eraseDragSlot()
-        self.slotposition = -1
-        if not filefactory or not filefactory.is_video:
-            return
-        pos = self._getNearestSourceSlot(x)
-
-        gst.debug("_got_filefactory pos : %d" % pos)
-
-        # we just add it here, the drawing will be done in the condensed_list
-        # callback
-        source = TimelineFileSource(factory=filefactory,
-                                    media_type=MEDIA_TYPE_VIDEO,
-                                    name=filefactory.name)
-
-        # ONLY FOR SIMPLE TIMELINE : if video-only, we link a blank audio object
-        if not filefactory.is_audio:
-            audiobrother = TimelineBlankSource(factory=filefactory,
-                                               media_type=MEDIA_TYPE_AUDIO,
-                                               name=filefactory.name)
-            source.setBrother(audiobrother)
-
-        if pos == -1:
-            self.timeline.videocomp.appendSource(source)
-        elif pos:
-            self.timeline.videocomp.insertSourceAfter(source, self.condensed[pos - 1])
-        else:
-            self.timeline.videocomp.prependSource(source)
-
-    def _moveElement(self, element, x):
-        gst.debug("TimelineSource, move %s to x:%d" % (element, x))
-        # remove the slot
-        self._eraseDragSlot()
-        self.slotposition = -1
-        pos = self._getNearestSourceSlot(x)
-
-        self.timeline.videocomp.moveSource(element, pos)
-
-    def _widthChangedCb(self, unused_layout, property):
-        if not property.name == "width":
-            return
-        self.width = self.get_property("width")
-
-    def _motionNotifyEventCb(self, layout, event):
-        pass
-
-
-    ## Drag and Drop callbacks
-
-    def _dragMotionCb(self, unused_layout, unused_context, x, unused_y,
-                      unused_timestamp):
-        # TODO show where the dragged item would go
-        pos = self._getNearestSourceSlotPixels(x + (self.hadjustment.get_value()))
-        rpos = self._getNearestSourceSlot(x + self.hadjustment.get_value())
-        gst.log("SimpleTimeline x:%d , source would go at %d" % (x, rpos))
-        if not pos == self.slotposition:
-            if not self.slotposition == -1:
-                # erase previous slot position
-                self._eraseDragSlot()
-            # draw new slot position
-            self.slotposition = pos
-            self._drawDragSlot()
-
-    def _dragLeaveCb(self, unused_layout, unused_context, unused_timestamp):
-        gst.log("SimpleTimeline")
-        self._eraseDragSlot()
-        self.slotposition = -1
-        # TODO remove the drag emplacement
-
-    def _dragDataReceivedCb(self, unused_layout, context, x, y, selection,
-                            targetType, timestamp):
-        gst.log("SimpleTimeline, targetType:%d, selection.data:%s" % (targetType, selection.data))
-        if targetType == dnd.TYPE_PITIVI_FILESOURCE:
-            uri = selection.data
-        else:
-            context.finish(False, False, timestamp)
-        x = x + int(self.hadjustment.get_value())
-        if self.draggedelement:
-            self._moveElement(self.draggedelement, x)
-        else:
-            self._gotFileFactory(instance.PiTiVi.current.sources[uri], x, y)
-        context.finish(True, False, timestamp)
-        instance.PiTiVi.playground.switchToTimeline()
-
-
-    ## Drawing
-
-    def _realizeCb(self, unused_layout):
-        self.modify_bg(gtk.STATE_NORMAL, self.style.white)
-
-    def _areaIntersect(self, x, y, w, h, x2, y2, w2, h2):
-        """ returns True if the area intersects, else False """
-        # is zone to the left of zone2
-        z1 = gtk.gdk.Rectangle(x, y, w, h)
-        z2 = gtk.gdk.Rectangle(x2, y2, w2, h2)
-        r = z1.intersect(z2)
-        a, b, c, d = r
-        if a or b or c or d:
-            return True
-        return False
-
-    def _exposeEventCb(self, unused_layout, event):
-        x, y, w, h = event.area
-        # redraw the slot rectangle if there's one
-        if not self.slotposition == -1:
-            if self._areaIntersect(x, y, w, h,
-                                   self.slotposition, DEFAULT_SIMPLE_SPACING,
-                                   DEFAULT_SIMPLE_SPACING, self.childheight):
-                self.bin_window.draw_rectangle(self.style.black_gc, True,
-                                               self.slotposition, DEFAULT_SIMPLE_SPACING,
-                                               DEFAULT_SIMPLE_SPACING, self.childheight)
-
-        return False
+    def _size_allocate(self, unused_layout, allocation):
+        x1, y1, x2, y2 = self.get_bounds()
+        height = y2 - y1
+
+        if height > 0:
+            self.scale = allocation.height / height
+            self.set_scale(self.scale)
+        return True
 
-    def _sizeAllocateCb(self, unused_layout, allocation):
-        if not self.height == allocation.height:
-            self.height = allocation.height
-            self.childheight = self.height - 2 * DEFAULT_SIMPLE_SPACING
-            self.childwidth = int(self.height / DEFAULT_SIMPLE_SIZE_RATIO)
-            self._resizeChildrens()
-        self.realWidth = allocation.width
-        if self._editingMode:
-            self.editingWidget.set_size_request(self.realWidth - 20,
-                                                self.height - 20)
-
-    def _resizeChildrens(self):
-        # resize the childrens to self.height
-        # also need to move them to their correct position
-        # TODO : check if there already at the given position
-        # TODO : check if they already have the good size
-        if self._editingMode:
-            return
-        pos = 2 * DEFAULT_SIMPLE_SPACING
-        for source in self.condensed:
-            widget = self.widgets[source]
-            if isinstance(source, TimelineFileSource):
-                widget.set_size_request(self.childwidth, self.childheight)
-                self.move(widget, pos, DEFAULT_SIMPLE_SPACING)
-                pos = pos + self.childwidth + DEFAULT_SIMPLE_SPACING
-            elif isinstance(source, SimpleTransitionWidget):
-                widget.set_size_request(self.childheight / 2, self.childheight)
-                self.move(widget, pos, DEFAULT_SIMPLE_SPACING)
-                pos = pos + self.childwidth + DEFAULT_SIMPLE_SPACING
-        newwidth = pos + DEFAULT_SIMPLE_SPACING
-        self.set_property("width", newwidth)
-
-    ## Child callbacks
-
-    def _sourceDeleteMeCb(self, unused_widget, element):
-        # remove this element from the timeline
-        self.timeline.videocomp.removeSource(element, collapse_neighbours=True)
-
-    def _sourceEditMeCb(self, unused_widget, element):
-        self.switchToEditingMode(element)
-
-    def _sourceDragBeginCb(self, unused_widget, unused_context, element):
-        gst.log("Timeline drag beginning on %s" % element)
-        if self.draggedelement:
-            gst.error("We were already doing a DnD ???")
-        self.draggedelement = element
-        # this element is starting to be dragged
-
-    def _sourceDragEndCb(self, unused_widget, unused_context, element):
-        gst.log("Timeline drag ending on %s" % element)
-        if not self.draggedelement == element:
-            gst.error("The DnD that ended is not the one that started before ???")
-        self.draggedelement = None
-        # this element is no longer dragged
-
-    def _editingWidgetHideMeCb(self, unused_widget):
-        self.switchToNormalMode()
-        # switch back to timeline in playground !
-        instance.PiTiVi.playground.switchToTimeline()
-
-
-
-    ## Editing mode
-
-    def _switchEditingMode(self, source, mode=True):
-        """ Switch editing mode for the given TimelineSource """
-        gst.log("source:%s , mode:%s" % (source, mode))
-
-        if self._editingMode == mode:
-            gst.warning("We were already in the correct editing mode : %s" % mode)
-            return
-
-        if mode and not source:
-            gst.warning("You need to specify a valid TimelineSource")
-            return
-
-        if mode:
-            # switching TO editing mode
-            gst.log("Switching TO editing mode")
-
-            # 1. Hide all sources
-            for widget in self.widgets.itervalues():
-                widget.hide()
-                self.remove(widget)
-
-            self._editingMode = mode
-
-            # 2. Show editing widget
-            self.editingWidget.setSource(source)
-            self.put(self.editingWidget, 10, 10)
-            self.props.width = self.realWidth
-            self.editingWidget.set_size_request(self.realWidth - 20, self.height - 20)
-            self.editingWidget.show()
+    def _child_drag_start(self, child, unused, event):
+        child.raise_(None)
+        self.drag_sigid = child.connect("motion_notify_event", 
+            self._child_drag)
+        x, y = event_coords(self, event)
+        self.pen_down = child.props.x - (x * self.scale)
+        self._set_drag_thresholds(child)
+        return True
 
-        else:
-            gst.log("Switching back to normal mode")
-            # switching FROM editing mode
+    def _set_drag_thresholds(self, item):
+        index = self.items.index(item)
+        if index > 0:
+            self.left = self.items.item_at(index - 1)
+            self.l_thresh = (self.left.props.x -
+                self.left.props.width / 2)
+        if index < len(self.items):
+            self.right = self.items.item_at(index + 1)
+            self.r_thresh = (self.right.props.x + 
+                self.right.props.width / 2 - item.props.width) 
+
+    def _child_drag_end(self, child, target, event):
+        self.items.tidy()
+        if self.drag_sigid:
+            child.disconnect(self.drag_sigid)
+        self.drag_sigid = None
+        self.left = None
+        self.right = None
+        return True
 
-            # 1. Hide editing widget
-            self.editingWidget.hide()
-            self.remove(self.editingWidget)
-
-            self._editingMode = mode
-
-            # 2. Show all sources
-            for widget in self.widgets.itervalues():
-                self.put(widget, 0, 0)
-                widget.show()
-            self._resizeChildrens()
-
-    def switchToEditingMode(self, source):
-        """ Switch to Editing mode for the given TimelineSource """
-        self._switchEditingMode(source)
-
-    def switchToNormalMode(self):
-        """ Switch back to normal timeline mode """
-        self._switchEditingMode(None, False)
+    def _child_drag(self, child, unused, event):
+        x, y = event_coords(self, event)
+        x = (min(self.items.width, max(0, self.scale * x +
+            self.pen_down)))
+        child.props.x = x
+        if self.left:
+            if x <= self.l_thresh:
+                self.items.swap(child, self.left)
+                self._set_drag_thresholds(child)
+        if self.right:
+            if x >= self.r_thresh:
+                self.items.swap(child, self.right)
+                self._set_drag_thresholds(child)
 
+        return True
 
+    def add(self, child):
+        self.items.add_child(child)
+        dn = child.connect("button_press_event", self._child_drag_start)
+        up = child.connect("button_release_event", self._child_drag_end)
+        self.signals[child] = (up, dn)
+
+    def remove(self, child):
+        self.items.remove(child)
+        for sig in self.signals[child]:
+            child.disconnect(sig)
+
+#class SimpleTimeline(gtk.Layout):
+#    """ Simple Timeline representation """
+#
+#    def __init__(self, **kw):
+#        gobject.GObject.__init__(self, **kw)
+#
+#        self.hadjustment = self.get_property("hadjustment")
+#
+#        # timeline and top level compositions
+#        self.timeline = instance.PiTiVi.current.timeline
+#        self.condensed = self.timeline.videocomp.condensed
+#
+#        # TODO : connect signals for when the timeline changes
+#
+#        # widgets correspondance dictionnary
+#        # MAPPING timelineobject => widget
+#        self.widgets = {}
+#
+#        # edit-mode
+#        # True when in editing mode
+#        self._editingMode = False
+#        self.editingWidget = SimpleEditingWidget()
+#        self.editingWidget.connect("hide-me", self._editingWidgetHideMeCb)
+#
+#        # Connect to timeline.  We must remove and reset the callbacks when
+#        # changing project.
+#        self.project_signals = SignalGroup()
+#        # FIXME: do we need this? or will the newproject sginal implicitly
+#        # handle this???
+#        self._connectToTimeline(instance.PiTiVi.current.timeline)
+#        instance.PiTiVi.connect("new-project-loaded",
+#            self._newProjectLoadedCb)
+#        instance.PiTiVi.connect("project-closed", self._projectClosedCb)
+#        instance.PiTiVi.connect("new-project-loading",
+#            self._newProjectLoadingCb)
+#        instance.PiTiVi.connect("new-project-failed",
+#            self._newProjectFailedCb)
+#
+#        # size
+#        self.width = int(DEFAULT_WIDTH)
+#        self.height = int(DEFAULT_HEIGHT)
+#        self.realWidth = 0 # displayed width of the layout
+#        self.childheight = int(DEFAULT_SIMPLE_ELEMENT_HEIGHT)
+#        self.childwidth = int(DEFAULT_SIMPLE_ELEMENT_WIDTH)
+#        self.set_size_request(int(MINIMUM_WIDTH), int(MINIMUM_HEIGHT))
+#        self.set_property("width", int(DEFAULT_WIDTH))
+#        self.set_property("height", int(DEFAULT_HEIGHT))
+#
+#        # event callbacks
+#        self.connect("expose-event", self._exposeEventCb)
+#        self.connect("notify::width", self._widthChangedCb)
+#        self.connect("size-allocate", self._sizeAllocateCb)
+#        self.connect("realize", self._realizeCb)
+#
+#        # drag and drop
+#        self.drag_dest_set(gtk.DEST_DEFAULT_DROP | gtk.DEST_DEFAULT_MOTION,
+#                           [dnd.FILESOURCE_TUPLE],
+#                           gtk.gdk.ACTION_COPY)
+#        self.connect("drag-data-received", self._dragDataReceivedCb)
+#        self.connect("drag-leave", self._dragLeaveCb)
+#        self.connect("drag-motion", self._dragMotionCb)
+#        self.slotposition = -1
+#
+#        self.draggedelement = None
+#
+#        self.show_all()
+#
+#
+#    ## Project callbacks
+#
+#    def _connectToTimeline(self, timeline):
+#        self.timeline = timeline
+#        self.condensed = self.timeline.videocomp.condensed
+#        self.project_signals.connect(self.timeline.videocomp,
+#                                     "condensed-list-changed",
+#                                     None, self._condensedListChangedCb)
+#
+#    def _newProjectLoadingCb(self, unused_inst, unused_project):
+#        gst.log("...")
+#
+#    def _newProjectLoadedCb(self, unused_inst, project):
+#        gst.log("...")
+#        assert(instance.PiTiVi.current == project)
+#        # now we connect to the new project, so we can receive any
+#        # signals that might be emitted while the project is loading
+#        self._connectToTimeline(project.timeline)
+#        # TODO: display final state of project now that loading has
+#        # completed. this callback doesn't do do much else
+#
+#        # LOAD THE TIMELINE !!!
+#        self._condensedListChangedCb(None, self.timeline.videocomp.condensed)
+#
+#    def _newProjectFailedCb(self, unused_inst, unused_reason, unused_uri):
+#        # oops the project failed to load
+#        self._clearTimeline()
+#
+#    def _clearTimeline(self):
+#        self.switchToNormalMode()
+#        self.project_signals.disconnectAll()
+#        for widget in self.widgets.itervalues():
+#            self.remove(widget)
+#        self.widgets = {}
+#
+#    def _projectClosedCb(self, unused_pitivi, unused_project):
+#        self._clearTimeline()
+#
+#    ## Timeline callbacks
+#
+#    def _condensedListChangedCb(self, unused_videocomp, clist):
+#        """ add/remove the widgets """
+#        gst.debug("condensed list changed in videocomp")
+#
+#        current = self.widgets.keys()
+#        self.condensed = clist
+#
+#        new = [x for x in clist if not x in current]
+#        removed = [x for x in current if not x in clist]
+#
+#        # new elements
+#        for element in new:
+#            # add the widget to self.widget
+#            gst.debug("Adding new element to the layout")
+#            if isinstance(element, TimelineFileSource):
+#                widget = SimpleSourceWidget(element)
+#                widget.connect("delete-me", self._sourceDeleteMeCb, element)
+#                widget.connect("edit-me", self._sourceEditMeCb, element)
+#                widget.connect("drag-begin", self._sourceDragBeginCb, element)
+#                widget.connect("drag-end", self._sourceDragEndCb, element)
+#            else:
+#                widget = SimpleTransitionWidget(element)
+#            self.widgets[element] = widget
+#            self.put(widget, 0, 0)
+#            widget.show()
+#
+#        # removed elements
+#        for element in removed:
+#            self.remove(self.widgets[element])
+#            del self.widgets[element]
+#
+#        self._resizeChildrens()
+#
+#
+#    ## Utility methods
+#
+#    def _getNearestSourceSlot(self, x):
+#        """
+#        returns the nearest file slot position available for the given position
+#        Returns the value in condensed list position
+#        Returns n , the element before which it should go
+#        Return -1 if it's meant to go last
+#        """
+#        if not self.condensed or x < 0:
+#            return 0
+#        if x > self.width - DEFAULT_SIMPLE_SPACING:
+#            return -1
+#
+#        pos = DEFAULT_SIMPLE_SPACING
+#        order = 0
+#        # TODO Need to avoid getting position between source and transition
+#        for source in self.condensed:
+#            if isinstance(source, TimelineSource):
+#                spacing = self.childwidth
+#            elif isinstance(source, TimelineTransition):
+#                spacing = self.childwidth / 2
+#            else:
+#                # this shouldn't happen !! The condensed list only contains
+#                # sources and/or transitions
+#                pass
+#            if x <= pos + spacing / 2:
+#                return order
+#            pos = pos + spacing + DEFAULT_SIMPLE_SPACING
+#            order = order + 1
+#        return -1
+#
+#    def _getNearestSourceSlotPixels(self, x):
+#        """
+#        returns the nearest file slot position available for the given position
+#        Returns the value in pixels
+#        """
+#        if not self.condensed or x < 0:
+#            return DEFAULT_SIMPLE_SPACING
+#        if x > self.width - DEFAULT_SIMPLE_SPACING:
+#            return self.width - 2 * DEFAULT_SIMPLE_SPACING
+#
+#        pos = DEFAULT_SIMPLE_SPACING
+#        # TODO Need to avoid getting position between source and transition
+#        for source in self.condensed:
+#            if isinstance(source, TimelineSource):
+#                spacing = self.childwidth
+#            elif isinstance(source, TimelineTransition):
+#                spacing = self.childwidth / 2
+#            else:
+#                # this shouldn't happen !! The condensed list only contains
+#                # sources and/or transitions
+#                pass
+#            if x <= pos + spacing / 2:
+#                return pos
+#            pos = pos + spacing + DEFAULT_SIMPLE_SPACING
+#        return pos
+#
+#
+#    ## Drawing
+#
+#    def _drawDragSlot(self):
+#        if self.slotposition == -1:
+#            return
+#        self.bin_window.draw_rectangle(self.style.black_gc, True,
+#                                       self.slotposition, DEFAULT_SIMPLE_SPACING,
+#                                       DEFAULT_SIMPLE_SPACING, self.childheight)
+#
+#    def _eraseDragSlot(self):
+#        if self.slotposition == -1:
+#            return
+#        self.bin_window.draw_rectangle(self.style.white_gc, True,
+#                                       self.slotposition, DEFAULT_SIMPLE_SPACING,
+#                                       DEFAULT_SIMPLE_SPACING, self.childheight)
+#
+#    def _gotFileFactory(self, filefactory, x, unused_y):
+#        """ got a filefactory at the given position """
+#        # remove the slot
+#        self._eraseDragSlot()
+#        self.slotposition = -1
+#        if not filefactory or not filefactory.is_video:
+#            return
+#        pos = self._getNearestSourceSlot(x)
+#
+#        gst.debug("_got_filefactory pos : %d" % pos)
+#
+#        # we just add it here, the drawing will be done in the condensed_list
+#        # callback
+#        source = TimelineFileSource(factory=filefactory,
+#                                    media_type=MEDIA_TYPE_VIDEO,
+#                                    name=filefactory.name)
+#
+#        # ONLY FOR SIMPLE TIMELINE : if video-only, we link a blank audio object
+#        if not filefactory.is_audio:
+#            audiobrother = TimelineBlankSource(factory=filefactory,
+#                                               media_type=MEDIA_TYPE_AUDIO,
+#                                               name=filefactory.name)
+#            source.setBrother(audiobrother)
+#
+#        if pos == -1:
+#            self.timeline.videocomp.appendSource(source)
+#        elif pos:
+#            self.timeline.videocomp.insertSourceAfter(source, self.condensed[pos - 1])
+#        else:
+#            self.timeline.videocomp.prependSource(source)
+#
+#    def _moveElement(self, element, x):
+#        gst.debug("TimelineSource, move %s to x:%d" % (element, x))
+#        # remove the slot
+#        self._eraseDragSlot()
+#        self.slotposition = -1
+#        pos = self._getNearestSourceSlot(x)
+#
+#        self.timeline.videocomp.moveSource(element, pos)
+#
+#    def _widthChangedCb(self, unused_layout, property):
+#        if not property.name == "width":
+#            return
+#        self.width = self.get_property("width")
+#
+#    def _motionNotifyEventCb(self, layout, event):
+#        pass
+#
+#
+#    ## Drag and Drop callbacks
+#
+#    def _dragMotionCb(self, unused_layout, unused_context, x, unused_y,
+#                      unused_timestamp):
+#        # TODO show where the dragged item would go
+#        pos = self._getNearestSourceSlotPixels(x + (self.hadjustment.get_value()))
+#        rpos = self._getNearestSourceSlot(x + self.hadjustment.get_value())
+#        gst.log("SimpleTimeline x:%d , source would go at %d" % (x, rpos))
+#        if not pos == self.slotposition:
+#            if not self.slotposition == -1:
+#                # erase previous slot position
+#                self._eraseDragSlot()
+#            # draw new slot position
+#            self.slotposition = pos
+#            self._drawDragSlot()
+#
+#    def _dragLeaveCb(self, unused_layout, unused_context, unused_timestamp):
+#        gst.log("SimpleTimeline")
+#        self._eraseDragSlot()
+#        self.slotposition = -1
+#        # TODO remove the drag emplacement
+#
+#    def _dragDataReceivedCb(self, unused_layout, context, x, y, selection,
+#                            targetType, timestamp):
+#        gst.log("SimpleTimeline, targetType:%d, selection.data:%s" % (targetType, selection.data))
+#        if targetType == dnd.TYPE_PITIVI_FILESOURCE:
+#            uri = selection.data
+#        else:
+#            context.finish(False, False, timestamp)
+#        x = x + int(self.hadjustment.get_value())
+#        if self.draggedelement:
+#            self._moveElement(self.draggedelement, x)
+#        else:
+#            self._gotFileFactory(instance.PiTiVi.current.sources[uri], x, y)
+#        context.finish(True, False, timestamp)
+#        instance.PiTiVi.playground.switchToTimeline()
+#
+#
+#    ## Drawing
+#
+#    def _realizeCb(self, unused_layout):
+#        self.modify_bg(gtk.STATE_NORMAL, self.style.white)
+#
+#    def _areaIntersect(self, x, y, w, h, x2, y2, w2, h2):
+#        """ returns True if the area intersects, else False """
+#        # is zone to the left of zone2
+#        z1 = gtk.gdk.Rectangle(x, y, w, h)
+#        z2 = gtk.gdk.Rectangle(x2, y2, w2, h2)
+#        r = z1.intersect(z2)
+#        a, b, c, d = r
+#        if a or b or c or d:
+#            return True
+#        return False
+#
+#    def _exposeEventCb(self, unused_layout, event):
+#        x, y, w, h = event.area
+#        # redraw the slot rectangle if there's one
+#        if not self.slotposition == -1:
+#            if self._areaIntersect(x, y, w, h,
+#                                   self.slotposition, DEFAULT_SIMPLE_SPACING,
+#                                   DEFAULT_SIMPLE_SPACING, self.childheight):
+#                self.bin_window.draw_rectangle(self.style.black_gc, True,
+#                                               self.slotposition, DEFAULT_SIMPLE_SPACING,
+#                                               DEFAULT_SIMPLE_SPACING, self.childheight)
+#
+#        return False
+#
+#    def _sizeAllocateCb(self, unused_layout, allocation):
+#        if not self.height == allocation.height:
+#            self.height = allocation.height
+#            self.childheight = self.height - 2 * DEFAULT_SIMPLE_SPACING
+#            self.childwidth = int(self.height / DEFAULT_SIMPLE_SIZE_RATIO)
+#            self._resizeChildrens()
+#        self.realWidth = allocation.width
+#        if self._editingMode:
+#            self.editingWidget.set_size_request(self.realWidth - 20,
+#                                                self.height - 20)
+#
+#    def _resizeChildrens(self):
+#        # resize the childrens to self.height
+#        # also need to move them to their correct position
+#        # TODO : check if there already at the given position
+#        # TODO : check if they already have the good size
+#        if self._editingMode:
+#            return
+#        pos = 2 * DEFAULT_SIMPLE_SPACING
+#        for source in self.condensed:
+#            widget = self.widgets[source]
+#            if isinstance(source, TimelineFileSource):
+#                widget.set_size_request(self.childwidth, self.childheight)
+#                self.move(widget, pos, DEFAULT_SIMPLE_SPACING)
+#                pos = pos + self.childwidth + DEFAULT_SIMPLE_SPACING
+#            elif isinstance(source, SimpleTransitionWidget):
+#                widget.set_size_request(self.childheight / 2, self.childheight)
+#                self.move(widget, pos, DEFAULT_SIMPLE_SPACING)
+#                pos = pos + self.childwidth + DEFAULT_SIMPLE_SPACING
+#        newwidth = pos + DEFAULT_SIMPLE_SPACING
+#        self.set_property("width", newwidth)
+#
+#    ## Child callbacks
+#
+#    def _sourceDeleteMeCb(self, unused_widget, element):
+#        # remove this element from the timeline
+#        self.timeline.videocomp.removeSource(element, collapse_neighbours=True)
+#
+#    def _sourceEditMeCb(self, unused_widget, element):
+#        self.switchToEditingMode(element)
+#
+#    def _sourceDragBeginCb(self, unused_widget, unused_context, element):
+#        gst.log("Timeline drag beginning on %s" % element)
+#        if self.draggedelement:
+#            gst.error("We were already doing a DnD ???")
+#        self.draggedelement = element
+#        # this element is starting to be dragged
+#
+#    def _sourceDragEndCb(self, unused_widget, unused_context, element):
+#        gst.log("Timeline drag ending on %s" % element)
+#        if not self.draggedelement == element:
+#            gst.error("The DnD that ended is not the one that started before ???")
+#        self.draggedelement = None
+#        # this element is no longer dragged
+#
+#    def _editingWidgetHideMeCb(self, unused_widget):
+#        self.switchToNormalMode()
+#        # switch back to timeline in playground !
+#        instance.PiTiVi.playground.switchToTimeline()
+#
+#
+#
+#    ## Editing mode
+#
+#    def _switchEditingMode(self, source, mode=True):
+#        """ Switch editing mode for the given TimelineSource """
+#        gst.log("source:%s , mode:%s" % (source, mode))
+#
+#        if self._editingMode == mode:
+#            gst.warning("We were already in the correct editing mode : %s" % mode)
+#            return
+#
+#        if mode and not source:
+#            gst.warning("You need to specify a valid TimelineSource")
+#            return
+#
+#        if mode:
+#            # switching TO editing mode
+#            gst.log("Switching TO editing mode")
+#
+#            # 1. Hide all sources
+#            for widget in self.widgets.itervalues():
+#                widget.hide()
+#                self.remove(widget)
+#
+#            self._editingMode = mode
+#
+#            # 2. Show editing widget
+#            self.editingWidget.setSource(source)
+#            self.put(self.editingWidget, 10, 10)
+#            self.props.width = self.realWidth
+#            self.editingWidget.set_size_request(self.realWidth - 20, self.height - 20)
+#            self.editingWidget.show()
+#
+#        else:
+#            gst.log("Switching back to normal mode")
+#            # switching FROM editing mode
+#
+#            # 1. Hide editing widget
+#            self.editingWidget.hide()
+#            self.remove(self.editingWidget)
+#
+#            self._editingMode = mode
+#
+#            # 2. Show all sources
+#            for widget in self.widgets.itervalues():
+#                self.put(widget, 0, 0)
+#                widget.show()
+#            self._resizeChildrens()
+#
+#    def switchToEditingMode(self, source):
+#        """ Switch to Editing mode for the given TimelineSource """
+#        self._switchEditingMode(source)
+#
+#    def switchToNormalMode(self):
+#        """ Switch back to normal timeline mode """
+#        self._switchEditingMode(None, False)
+#
+#
 class SimpleEditingWidget(gtk.EventBox):
     """
     Widget for editing a source in the SimpleTimeline

Added: branches/SOC_2008_BLEWIS/pitivi/ui/util.py
==============================================================================
--- (empty file)
+++ branches/SOC_2008_BLEWIS/pitivi/ui/util.py	Sat May 31 01:48:38 2008
@@ -0,0 +1,339 @@
+#Copyright (C) 2008 Brandon J. Lewis
+#
+#License:
+#
+#    This library is free software; you can redistribute it and/or
+#    modify it under the terms of the GNU Lesser General Public
+#    License as published by the Free Software Foundation; either
+#    version 2 of the License, or (at your option) any later version.
+#
+#    This package 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
+#    Lesser General Public License for more details.
+#
+#    You should have received a copy of the GNU Lesser General Public
+#    License along with this package; if not, write to the Free Software
+#    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+#
+#On Debian systems, the complete text of the GNU Lesser General
+#Public License can be found in `/usr/share/common-licenses/LGPL'.
+
+import gobject
+import goocanvas
+import gtk
+
+## GooCanvas Convenience Functions
+
+def event_coords(canvas, event):
+    """returns the coordinates of an event"""
+    return canvas.convert_from_pixels(event.x, event.y)
+
+def point_difference(p1, p2):
+    """Returns the 2-dvector difference p1 - p2"""
+    p1_x, p1_y = p1
+    p2_x, p2_y = p2
+    return (p1_x - p2_x, p1_y - p2_y)
+
+def point_sum(p1, p2):
+    """Returns the 2d vector sum p1 + p2"""
+    p1_x, p1_y = p1
+    p2_x, p2_y = p2
+    return (p1_x + p2_x, p1_y + p2_y)
+
+def point_mul(factor, point):
+    """Returns a scalar multiple factor * point"""
+    return tuple(factor * v for v in point)
+
+def pos(item):
+    """Returns a tuple x, y representing the position of the 
+    supplied goocanvas Item"""
+    return item.props.x, item.props.y
+
+def pos_change_cb(item, prop, callback, data):
+    """Used internally, don't call this function"""
+    callback(pos(item), item, *data)
+
+def size_change_cb(item, prop, callback):
+    """Used internally, don't call this function"""
+    callback(size(item))
+
+def pos_change(item, callback, *data):
+    """Connects the callback to the x and y property notificaitons.
+    Do not call this function again without calling unlink_pos_change()
+    first"""
+    item.set_data("x_sig_hdl", item.connect("notify::x", pos_change_cb,
+        callback, data))
+    item.set_data("y_sig_hdl", item.connect("notify::y", pos_change_cb,
+        callback, data))
+
+def unlink_pos_change(item):
+    """Disconnects signal handlers after calling pos_change()"""
+    item.disconnect(item.get_data("x_sig_hdl"))
+    item.disconnect(item.get_data("y_sig_hdl"))
+
+def size(item):
+    """Returns the tuple (<width>, <height>) of item"""
+    return item.props.width, item.props.height
+
+def size_change(item, callback):
+    """Connects the callback to the width, height property notifications.
+    """
+    item.set_data("w_sig_hdl", item.connect("notify::width", 
+        size_change_cb, callback))
+    item.set_data("h_sig_hdl", item.connect("notify::height", 
+        size_change_cb, callback))
+
+def unlink_size_change(item):
+    item.disconnect(item.get_data("w_sig_hdl"))
+    item.disconnect(item.get_data("h_sig_hdl"))
+
+def set_pos(item, pos):
+    """Sets the position of item given pos, a tuple of (<x>, <y>)"""
+    item.props.x, item.props.y = pos
+
+def set_size(item, size):
+    """Sets the size of the item given size, a tuple of 
+    (<width>, <height>)"""
+    item.props.width, item.props.height = size
+
+def width(item):
+    return item.props.width
+
+def height(item):
+    return item.props.height
+
+def center(item):
+    return point_sum(pos(item), point_mul(0.5, size(item)))
+
+def make_item(factory):
+    """Create a new goocanvas item given factory, a tuple of 
+    * <class> - the class to create
+    * <properties> - initial properties to set, such as color
+    * <data> - initial data to set
+    """
+    klass, properties, data = factory
+    ret = klass(**properties)
+    for key, value in data.items():
+        ret.set_data(key, value)
+    return ret
+
+def group(*items):
+    """Wrap all the canvas items in items in a smartgroup and return the
+    resulting smartgroup. The item's current position is the offset
+    within the smartgroup"""
+    ret = SmartGroup()
+    
+    for item in items:
+        ret.add_child(item, pos(item))
+    
+    return ret
+
+# these are callbacks for implementing "dragable object features
+def drag_start(item, target, event, canvas, start_cb, transform):
+    """A callback which starts the drag operation of a dragable 
+    object"""
+    item.set_data("dragging", True)
+    if transform:
+        coords = transform(event_coords(canvas, event))
+        print coords
+    else:
+        coords = event_coords(canvas, event)
+    item.set_data("pendown", point_difference(pos(item), coords))
+    if start_cb:
+        start_cb(item)
+    return True
+
+def drag_end(item, target, event, end_cb):
+    """A callback which ends the drag operation of a dragable object"""
+    item.set_data("dragging", False)
+    if end_cb:
+        end_cb(item)
+    return True
+
+def translate_item_group(item, target, event, canvas, transform):
+    """A callback which handles updating the position during a drag
+    operation"""
+    if item.get_data("dragging"):
+        pos = point_sum(item.get_data("pendown"), 
+            event_coords(canvas, event))
+        if transform:
+            set_pos(item, transform(pos))
+            return True
+        set_pos(item, pos)
+        return True
+    return False
+
+def make_dragable(canvas, item, transform=None, start=None, end=None):
+    """Make item dragable with respect to the canvas. Call this 
+    after make_selectable, or it will prevent the latter from working.
+    """
+    item.set_data("dragging", False)
+    item.connect("button_press_event", drag_start, canvas, start, transform)
+    item.connect("button_release_event", drag_end, end)
+    item.connect("motion_notify_event", translate_item_group, canvas,
+        transform)
+
+class Text(goocanvas.Text):
+    '''adds the "missing" height property to goocanvas.Text'''
+    #TODO: width/height are dumy props in this class...they 
+    #should ideally be read-only values calculated from the layout
+    #parameters and text. 
+    __gtype_name__ = 'SmartText'
+
+    height = gobject.property(type=float, default=0)
+    width = gobject.property(type=float, default=0)
+
+    def __init__(self, *args, **kwargs):
+        goocanvas.Text.__init__(self, *args, **kwargs)
+ 
+class SmartGroup(goocanvas.Group):
+    """Extends goocanvas.Group() with 
+    through gobject properties x, y, and width/height"""
+    __gtype_name__ = 'SmartGroup'
+
+    x = gobject.property(type=float, default=0)
+    y = gobject.property(type=float, default=0)
+    width = gobject.property(type=float, default=0)
+    height = gobject.property(type=float, default=0)
+
+    def __init__(self, *args, **kwargs):
+        goocanvas.Group.__init__(self, *args, **kwargs)
+        self.children = {}
+        self.signals = {}
+        self.connect("notify::x", self.move_x_children)
+        self.connect("notify::y", self.move_y_children)
+
+    def move_x_children(self, object, prop):
+        for child, (x, y) in self.children.items():
+            child.set_property('x', self.x + x)
+
+    def move_y_children(self, object, prop):
+        for child, (x, y) in self.children.items():
+            child.set_property('y', self.y + y)
+
+    def update_width(self, obj, prop):
+        def compute(c, p):
+            return (c.get_property('width') + p[0])
+        widths = (compute(c, p) for c, p in self.children.items())
+        self.width = max(widths) if len(self.children) else float(0)
+
+    def update_height(self, obj, prop):
+        def compute(c, p):
+            return (c.get_property('height') + p[1])
+        heights = (compute(c, p) for c, p in self.children.items())
+        self.height = max(heights) if len(self.children) else float(0)
+
+    def set_child_pos(self, child, pos_):
+        set_pos(child, point_sum(pos(self), pos_))
+        self.children[child] = pos_
+
+    def add_child(self, child, p=None):
+        goocanvas.Group.add_child(self, child)
+        cw = child.connect("notify::width", self.update_width)
+        ch = child.connect("notify::height", self.update_height)
+        self.signals[child] = (cw, ch)
+        if not p:
+            self.children[child] = pos(child)
+        else:
+            self.set_child_pos(child, p)
+        self.update_width(None, None)
+        self.update_height(None, None)
+
+    def remove_child(self, child):
+        goocanvas.Group.remove_child(self, child)
+        for s in self.signals[child]:
+            child.disconnect(s)
+        del self.children[child]
+        self.update_width(None, None)
+        self.update_height(None, None)
+
+class List(SmartGroup):
+    __gytpe_name__ = 'List'
+
+    #TODO: changing this property should force update of list
+    spacing = gobject.property(type=float)
+
+    def __len__(self):
+        return len(self.order)
+
+    def __init__(self, spacing=5.0, *args, **kwargs):
+        SmartGroup.__init__(self, *args, **kwargs)
+        self.cur_pos = 0
+        self.spacing = spacing
+        self.order = []
+
+    def tidy(self):
+        cur = 0
+        i = 0
+        for child in self.order:
+            self.set_child_pos(child, self.cur(cur))
+            child.set_data("index", i)
+            cur += self.spacing + self.dimension(child)
+            i += 1
+        self.cur_pos = cur
+    
+    def item_at(self, index):
+        return self.order[index]
+
+    def index(self, child):
+        return child.get_data("index")
+
+    def reorder(self, new_order):
+        order = []
+        for index in new_order:
+            order.append(self.order[index])
+        self.order = order
+        self.tidy()
+
+    def swap(self, a, b):
+        a_index = a.get_data("index")
+        b_index = b.get_data("index")
+        self.order[a_index] = b
+        self.order[b_index] = a
+
+
+    def remove_child(self, child):
+        SmartGroup.remove_child(self, child)
+        self.order.remove(child)
+        self.tidy()
+
+    def add_child(self, child):
+        SmartGroup.add_child(self, child, self.cur(self.cur_pos))
+        self.cur_pos += self.spacing + self.dimension(child)
+        self.order.append(child)
+        child.set_data("index", len(self.order))
+
+    def add(self, child):
+        self.add_child(child)
+
+    def insert_child(self, child, index):
+        SmartGroup.add_child(self, child, self.cur(self.cur_pos))
+        self.order.insert(child, index)
+        self.tidy()
+
+class VList(List):
+    __gtype_name__ = 'VList'
+
+    def __init__(self, *args, **kwargs):
+        List.__init__(self, *args, **kwargs)
+    
+    def cur(self, value):
+        return (0, value)
+
+    def dimension(self, child):
+        return height(child)
+
+class HList(List):
+    __gtype_name__ = 'HList'
+
+    def __init__(self, *args, **kwargs):
+        List.__init__(self, *args, **kwargs)
+
+    def cur(self, value):
+        return (value, 0)
+
+    def dimension(self, child):
+        return width(child)
+
+



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]