class TimelineControls(gtk.VBox):
    def __init__(self):
        gtk.VBox.__init__(self)
        self._tracks = []
        self.set_spacing(LAYER_SPACING)
        self.set_size_request(TRACK_CONTROL_WIDTH, -1)

## Timeline callbacks

    def _set_timeline(self):
        while self._tracks:
            self._trackRemoved(None, 0)
        if self.timeline:
            for track in self.timeline.tracks:
                self._trackAdded(None, track)

    timeline = receiver(_set_timeline)

    @handler(timeline, "track-added")
    def _trackAdded(self, timeline, track):
        track = TrackControls(track)
        self._tracks.append(track)
        self.pack_start(track, False, False)
        track.show()

    @handler(timeline, "track-removed")
    def _trackRemoved(self, unused_timeline, position):
        track = self._tracks[position]
        del self._tracks[position]
        self.remove(track)
Example #2
0
class TrimHandle(View, goocanvas.Rect, Zoomable):
    """A component of a TimelineObject which manage's the source's edit
    points"""

    element = receiver()

    def __init__(self, element, timeline, **kwargs):
        self.element = element
        self.timeline = timeline
        goocanvas.Rect.__init__(self,
                                width=5,
                                fill_color_rgba=0x00000022,
                                line_width=0,
                                **kwargs)
        View.__init__(self)
        Zoomable.__init__(self)
Example #3
0
File: track.py Project: qlf/Pitivi
class Transition(goocanvas.Rect, Zoomable):
    def __init__(self, transition):
        goocanvas.Rect.__init__(self)
        Zoomable.__init__(self)
        self.props.fill_color_rgba = 0xFFFFFF99
        self.props.stroke_color_rgba = 0x00000099
        self.set_simple_transform(0, -LAYER_SPACING + 3, 1.0, 0)
        self.props.height = LAYER_SPACING - 6
        self.props.pointer_events = goocanvas.EVENTS_NONE
        self.props.radius_x = 2
        self.props.radius_y = 2
        self.transition = transition

    def _setTransition(self):
        if self.transition:
            self._updateAll()

    def _updateAll(self):
        transition = self.transition
        start = transition.start
        duration = transition.duration
        priority = transition.priority
        self._updateStart(transition, start)
        self._updateDuration(transition, duration)
        self._updatePriority(transition, priority)

    transition = receiver(_setTransition)

    @handler(transition, "start-changed")
    def _updateStart(self, transition, start):
        self.props.x = self.nsToPixel(start)

    @handler(transition, "duration-changed")
    def _updateDuration(self, transition, duration):
        width = max(0, self.nsToPixel(duration))
        if width == 0:
            self.props.visibility = goocanvas.ITEM_INVISIBLE
        else:
            self.props.visibility = goocanvas.ITEM_VISIBLE
        self.props.width = width

    @handler(transition, "priority-changed")
    def _updatePriority(self, transition, priority):
        self.props.y = (LAYER_HEIGHT_EXPANDED + LAYER_SPACING) * priority

    def zoomChanged(self):
        self._updateAll()
class TrackControls(gtk.Label):
    __gtype_name__ = 'TrackControls'

    def __init__(self, track):
        gtk.Label.__init__(self)
        self.set_alignment(0.5, 0.1)
        self.set_markup(track_name(track))
        self.track = track
        self.set_size_request(TRACK_CONTROL_WIDTH, LAYER_HEIGHT_EXPANDED)

    def _setTrack(self):
        if self.track:
            self._maxPriorityChanged(None, self.track.max_priority)

    track = receiver(_setTrack)

    @handler(track, "max-priority-changed")
    def _maxPriorityChanged(self, track, max_priority):
        self.set_size_request(TRACK_CONTROL_WIDTH, (1 + max_priority) *
                              (LAYER_HEIGHT_EXPANDED + LAYER_SPACING))
Example #5
0
class TrimHandle(View, goocanvas.Image, Zoomable):
    """A component of a TrackObject which manage's the source's edit
    points"""

    element = receiver()

    def __init__(self, instance, element, timeline, **kwargs):
        self.app = instance
        self.element = element
        self.timeline = timeline
        goocanvas.Image.__init__(self,
                                 pixbuf=TRIMBAR_PIXBUF,
                                 line_width=0,
                                 **kwargs)
        View.__init__(self)
        Zoomable.__init__(self)

    def focus(self):
        self.props.pixbuf = TRIMBAR_PIXBUF_FOCUS

    def unfocus(self):
        self.props.pixbuf = TRIMBAR_PIXBUF
Example #6
0
class Track(goocanvas.Group, Zoomable):
    __gtype_name__ = 'Track'

    def __init__(self, instance, track, timeline=None):
        goocanvas.Group.__init__(self)
        Zoomable.__init__(self)
        self.app = instance
        self.widgets = {}
        self.timeline = timeline
        self.track = track
        self.max_priority = 0
        self._expanded = True

## Properties

    def setExpanded(self, expanded):
        if expanded != self._expanded:
            self._expanded = expanded

            for widget in self.widgets.itervalues():
                widget.expanded = expanded
            self.get_canvas().regroupTracks()

    def getHeight(self):
        if self._expanded:
            return (1 + self.track.max_priority) * (LAYER_HEIGHT_EXPANDED +
                                                    LAYER_SPACING)
        else:
            return LAYER_HEIGHT_COLLAPSED + LAYER_SPACING

    height = property(getHeight)

    ## Public API

    ## track signals

    def _setTrack(self):
        if self.track:
            for trackobj in self.track.track_objects:
                if trackobj is self.track.default_track_object:
                    continue
                self._objectAdded(None, trackobj)

    track = receiver(_setTrack)

    @handler(track, "track-object-added")
    def _objectAdded(self, unused_timeline, track_object):
        w = TrackObject(self.app, track_object, self.track, self.timeline)
        self.widgets[track_object] = w
        self.add_child(w)

    @handler(track, "track-object-removed")
    def _objectRemoved(self, unused_timeline, track_object):
        w = self.widgets[track_object]
        self.remove_child(w)
        del self.widgets[track_object]
        Zoomable.removeInstance(w)

    @handler(track, "max-priority-changed")
    def _maxPriorityChanged(self, track, max_priority):
        self.get_canvas().regroupTracks()
Example #7
0
class TrackObject(View, goocanvas.Group, Zoomable):
    class Controller(TimelineController):
        def drag_start(self, item, target, event):
            TimelineController.drag_start(self, item, target, event)
            self._view.raise_(None)
            self._context = MoveContext(
                self._view.timeline, self._view.element,
                self._view.timeline.selection.getSelectedTrackObjs())
            self._view.app.action_log.begin("move object")

        def _getMode(self):
            if self._shift_down:
                return self._context.RIPPLE
            return self._context.DEFAULT

        def click(self, pos):
            mode = SELECT
            if self._last_event.get_state() & gtk.gdk.SHIFT_MASK:
                mode = SELECT_ADD
            elif self._last_event.get_state() & gtk.gdk.CONTROL_MASK:
                mode = UNSELECT
            self._view.timeline.setSelectionToObj(self._view.element, mode)

    def __init__(self, instance, element, track, timeline):
        goocanvas.Group.__init__(self)
        View.__init__(self)
        Zoomable.__init__(self)
        self.app = instance
        self.track = track
        self.timeline = timeline
        self.namewidth = 0
        self.nameheight = 0

        self.bg = goocanvas.Rect(height=self.height, line_width=0)

        self.content = Preview(element)

        self.name = goocanvas.Text(x=NAME_HOFFSET + NAME_PADDING,
                                   y=NAME_VOFFSET + NAME_PADDING,
                                   operator=cairo.OPERATOR_ADD,
                                   alignment=pango.ALIGN_LEFT)
        self.namebg = goocanvas.Rect(radius_x=2,
                                     radius_y=2,
                                     x=NAME_HOFFSET,
                                     y=NAME_VOFFSET,
                                     line_width=0)

        self.start_handle = StartHandle(self.app,
                                        element,
                                        timeline,
                                        height=self.height)
        self.end_handle = EndHandle(self.app,
                                    element,
                                    timeline,
                                    height=self.height)

        self.selection_indicator = goocanvas.Rect(
            visibility=goocanvas.ITEM_INVISIBLE,
            line_width=0.0,
            height=self.height)

        for thing in (self.bg, self.content, self.selection_indicator,
                      self.start_handle, self.end_handle, self.namebg,
                      self.name):
            self.add_child(thing)

        for prop, interpolator in element.getInterpolators().itervalues():
            self.add_child(Curve(instance, element, interpolator, 50))

        self.element = element
        self.settings = instance.settings
        self.normal()

## Properties

    _height = LAYER_HEIGHT_EXPANDED

    def setHeight(self, height):
        self._height = height
        self.start_handle.props.height = height
        self.end_handle.props.height = height
        self._update()

    def getHeight(self):
        return self._height

    height = property(getHeight, setHeight)

    _expanded = True

    def setExpanded(self, expanded):
        self._expanded = expanded
        if not self._expanded:
            self.height = LAYER_HEIGHT_COLLAPSED
            self.content.props.visibility = goocanvas.ITEM_INVISIBLE
            self.namebg.props.visibility = goocanvas.ITEM_INVISIBLE
            self.bg.props.height = LAYER_HEIGHT_COLLAPSED
            self.name.props.y = 0
        else:
            self.height = LAYER_HEIGHT_EXPANDED
            self.content.props.visibility = goocanvas.ITEM_VISIBLE
            self.namebg.props.visibility = goocanvas.ITEM_VISIBLE
            self.bg.props.height = LAYER_HEIGHT_EXPANDED
            self.height = LAYER_HEIGHT_EXPANDED
            self.name.props.y = NAME_VOFFSET + NAME_PADDING

    def getExpanded(self):
        return self._expanded

    expanded = property(getExpanded, setExpanded)

    ## Public API

    def focus(self):
        self.start_handle.focus()
        self.end_handle.focus()

    def unfocus(self):
        self.start_handle.unfocus()
        self.end_handle.unfocus()

    def zoomChanged(self):
        self._update()

## settings signals

    def _setSettings(self):
        if self.settings:
            self.clipAppearanceSettingsChanged()

    settings = receiver(_setSettings)

    @handler(settings, "audioClipBgChanged")
    @handler(settings, "videoClipBgChanged")
    @handler(settings, "selectedColorChanged")
    @handler(settings, "clipFontDescChanged")
    def clipAppearanceSettingsChanged(self, *args):
        if isinstance(self.element.stream, VideoStream):
            color = self.settings.videoClipBg
        elif isinstance(self.element.stream, AudioStream):
            color = self.settings.audioClipBg
        pattern = unpack_cairo_gradient(color)
        self.bg.props.fill_pattern = pattern

        self.namebg.props.fill_pattern = pattern

        self.selection_indicator.props.fill_pattern = unpack_cairo_pattern(
            self.settings.selectedColor)

        self.name.props.font = self.settings.clipFontDesc
        self.name.props.fill_pattern = unpack_cairo_pattern(
            self.settings.clipFontColor)
        twidth, theight = text_size(self.name)
        self.namewidth = twidth
        self.nameheight = theight
        self._update()

## element signals

    def _setElement(self):
        if self.element:
            self.name.props.text = os.path.basename(
                unquote(self.element.factory.name))
            twidth, theight = text_size(self.name)
            self.namewidth = twidth
            self.nameheight = theight
            self._update()

    element = receiver(_setElement)

    @handler(element, "start-changed")
    @handler(element, "duration-changed")
    def startChangedCb(self, track_object, start):
        self._update()

    @handler(element, "selected-changed")
    def selected_changed(self, element, state):
        if element.selected:
            self.selection_indicator.props.visibility = goocanvas.ITEM_VISIBLE
        else:
            self.selection_indicator.props.visibility = \
                goocanvas.ITEM_INVISIBLE

    @handler(element, "priority-changed")
    def priority_changed(self, element, priority):
        self._update()

    def _update(self):
        try:
            x = self.nsToPixel(self.element.start)
        except Exception, e:
            print self.element.start
            raise Exception(e)
        y = (self.height + LAYER_SPACING) * self.element.priority
        self.set_simple_transform(x, y, 1, 0)
        width = self.nsToPixel(self.element.duration)
        w = width - self.end_handle.props.width
        self.name.props.clip_path = "M%g,%g h%g v%g h-%g z" % (0, 0, w,
                                                               self.height, w)
        self.bg.props.width = width
        self.selection_indicator.props.width = width
        self.end_handle.props.x = w
        if self.expanded:
            if w - NAME_HOFFSET > 0:
                self.namebg.props.height = self.nameheight + NAME_PADDING2X
                self.namebg.props.width = min(w - NAME_HOFFSET,
                                              self.namewidth + NAME_PADDING2X)
                self.namebg.props.visibility = goocanvas.ITEM_VISIBLE
            else:
                self.namebg.props.visibility = goocanvas.ITEM_INVISIBLE
Example #8
0
class PitiviMainWindow(gtk.Window, Loggable):
    """
    Pitivi's main window.

    @cvar app: The application object
    @type app: L{Application}
    @cvar project: The current project
    @type project: L{Project}
    """


    def __init__(self, instance):
        """ initialize with the Pitivi object """
        gtk.Window.__init__(self)
        Loggable.__init__(self)
        self.log("Creating MainWindow")
        self.actions = None
        self.toggleactions = None
        self.actiongroup = None
        self.settings = instance.settings
        self.is_fullscreen = self.settings.mainWindowFullScreen
        self.timelinepos = 0
        self.prefsdialog = None
        create_stock_icons()
        self._setActions(instance)
        self._createUi(instance)
        self.app = instance
        self._zoom_duration_changed = False

        self.app.projectManager.connect("new-project-loading",
                self._projectManagerNewProjectLoadingCb)
        self.app.projectManager.connect("new-project-loaded",
                self._projectManagerNewProjectLoadedCb)
        self.app.projectManager.connect("new-project-failed",
                self._projectManagerNewProjectFailedCb)
        self.app.projectManager.connect("save-project-failed",
                self._projectManagerSaveProjectFailedCb)
        self.app.projectManager.connect("project-saved",
                self._projectManagerProjectSavedCb)
        self.app.projectManager.connect("closing-project",
                self._projectManagerClosingProjectCb)
        self.app.projectManager.connect("reverting-to-saved",
                self._projectManagerRevertingToSavedCb)
        self.app.projectManager.connect("project-closed",
                self._projectManagerProjectClosedCb)
        self.app.projectManager.connect("missing-uri",
                self._projectManagerMissingUriCb)

        self.app.action_log.connect("commit", self._actionLogCommit)
        self.app.action_log.connect("undo", self._actionLogUndo)
        self.app.action_log.connect("redo", self._actionLogRedo)
        self.app.action_log.connect("cleaned", self._actionLogCleaned)

        # if no webcams available, hide the webcam action
        if self.app.deviceprobe is not None:
            # On Windows disable device probe
            if platform.system() != 'Windows':
                self.app.deviceprobe.connect("device-added", self._deviceChangeCb)
                self.app.deviceprobe.connect("device-removed", self._deviceChangeCb)
                if len(self.app.deviceprobe.getVideoSourceDevices()) < 1:
                    self.webcam_button.set_sensitive(False)
        else:
            self.webcam_button.set_sensitive(False)

        self.show()

    def showEncodingDialog(self, project, pause=True):
        """
        Shows the L{EncodingDialog} for the given project Timeline.

        @param project: The project
        @type project: L{Project}
        @param pause: If C{True}, pause the timeline before displaying the dialog.
        @type pause: C{bool}
        """
        from encodingdialog import EncodingDialog

        if pause:
            project.pipeline.pause()
        win = EncodingDialog(self, project)
        win.window.connect("destroy", self._encodingDialogDestroyCb)
        self.set_sensitive(False)
        win.show()

    def _encodingDialogDestroyCb(self, unused_dialog):
        self.set_sensitive(True)

    def _recordCb(self, unused_button):
        self.showEncodingDialog(self.project)

    def _setActions(self, instance):
        PLAY = _("Start Playback")
        PAUSE = _("Stop Playback")
        LOOP = _("Loop over selected area")

        """ sets up the GtkActions """
        self.actions = [
            ("NewProject", gtk.STOCK_NEW, None,
             None, _("Create a new project"), self._newProjectMenuCb),
            ("OpenProject", gtk.STOCK_OPEN, None,
             None, _("Open an existing project"), self._openProjectCb),
            ("SaveProject", gtk.STOCK_SAVE, None,
             None, _("Save the current project"), self._saveProjectCb),
            ("SaveProjectAs", gtk.STOCK_SAVE_AS, None,
             None, _("Save the current project"), self._saveProjectAsCb),
            ("RevertToSavedProject", gtk.STOCK_REVERT_TO_SAVED, None,
             None, _("Reload the current project"), self._revertToSavedProjectCb),
            ("ProjectSettings", gtk.STOCK_PROPERTIES, _("Project Settings"),
             None, _("Edit the project settings"), self._projectSettingsCb),
            ("RenderProject", 'pitivi-render' , _("_Render project"),
             None, _("Render project"), self._recordCb),
            ("Undo", gtk.STOCK_UNDO,
             _("_Undo"),
             "<Ctrl>Z", _("Undo the last operation"), self._undoCb),
            ("Redo", gtk.STOCK_REDO,
             _("_Redo"),
             "<Ctrl>Y", _("Redo the last operation that was undone"), self._redoCb),
            ("PluginManager", gtk.STOCK_PREFERENCES ,
             _("_Plugins..."),
             None, _("Manage plugins"), self._pluginManagerCb),
            ("Preferences", gtk.STOCK_PREFERENCES, _("_Preferences"),
              None, None, self._prefsCb),
            ("ImportfromCam", gtk.STOCK_ADD ,
             _("Import from _Webcam..."),
             None, _("Import Camera stream"), self._ImportWebcam),
            ("Screencast", gtk.STOCK_ADD ,
             _("_Make screencast..."),
             None, _("Capture the desktop"), self._Screencast),
            ("NetstreamCapture", gtk.STOCK_ADD ,
             _("_Capture Network Stream..."),
             None, _("Capture Network Stream"), self._ImportNetstream),
            ("Quit", gtk.STOCK_QUIT, None, None, None, self._quitCb),
            ("About", gtk.STOCK_ABOUT, None, None,
             _("Information about %s") % APPNAME, self._aboutCb),
            ("File", None, _("_File")),
            ("Edit", None, _("_Edit")),
            ("View", None, _("_View")),
            ("Library", None, _("_Project")),
            ("Timeline", None, _("_Timeline")),
            ("Viewer", None, _("Previe_w")),
            ("PlayPause", gtk.STOCK_MEDIA_PLAY, None, "space", PLAY,
                self.playPause),
            ("Loop", gtk.STOCK_REFRESH, _("Loop"), None, LOOP,
                self.loop),
            ("Help", None, _("_Help")),
        ]

        self.toggleactions = [
            ("FullScreen", gtk.STOCK_FULLSCREEN, None, "f",
             _("View the main window on the whole screen"),
                 self._fullScreenCb),
            ("FullScreenAlternate", gtk.STOCK_FULLSCREEN, None, "F11", None,
                self._fullScreenAlternateCb),
            ("ShowHideMainToolbar", None, _("Main Toolbar"), None, None,
                self._showHideMainToolBar,
                self.settings.mainWindowShowMainToolbar),
            ("ShowHideTimelineToolbar", None, _("Timeline Toolbar"), None,
                None, self._showHideTimelineToolbar,
                self.settings.mainWindowShowTimelineToolbar),
        ]

        self.actiongroup = gtk.ActionGroup("mainwindow")
        self.actiongroup.add_actions(self.actions)
        self.actiongroup.add_toggle_actions(self.toggleactions)

        # deactivating non-functional actions
        # FIXME : reactivate them
        save_action = self.actiongroup.get_action("SaveProject")
        save_action.set_sensitive(False)

        for action in self.actiongroup.list_actions():
            action_name = action.get_name()
            if action_name == "RenderProject":
                self.render_button = action
                # this will be set sensitive when the timeline duration changes
                action.set_sensitive(False)
                action.props.is_important = True
            elif action_name == "ImportfromCam":
                self.webcam_button = action
                action.set_sensitive(False)
            elif action_name == "Screencast":
                # FIXME : re-enable this action once istanbul integration is complete
                # and upstream istanbul has applied packages for proper interaction.
                action.set_sensitive(False)
                action.set_visible(False)
            elif action_name in [
                "ProjectSettings", "Quit", "File", "Edit", "Help", "About",
                "View", "FullScreen", "FullScreenAlternate",
                "ImportSourcesFolder", "PluginManager", "PlayPause",
                "Project", "FrameForward", "FrameBackward",
                "ShowHideMainToolbar", "ShowHideTimelineToolbar", "Library",
                "Timeline", "Viewer", "FrameForward", "FrameBackward",
                "SecondForward", "SecondBackward", "EdgeForward",
                "EdgeBackward", "Preferences"]:
                action.set_sensitive(True)
            elif action_name in ["NewProject", "SaveProjectAs", "OpenProject"]:
                if instance.settings.fileSupportEnabled:
                    action.set_sensitive(True)
            elif action_name == "SaveProject":
                if instance.settings.fileSupportEnabled:
                    action.set_sensitive(True)
                action.props.is_important = True
            elif action_name == "Undo":
                action.set_sensitive(True)
                action.props.is_important = True
            else:
                action.set_sensitive(False)

        self.uimanager = gtk.UIManager()
        self.add_accel_group(self.uimanager.get_accel_group())
        self.uimanager.insert_action_group(self.actiongroup, 0)
        if 'pitivi.exe' in __file__.lower():
            xml = LIBDIR + '\\pitivi.exe'
        else:
            xml = __file__
        self.uimanager.add_ui_from_file(os.path.join(os.path.dirname(
            os.path.abspath(xml)), "mainwindow.xml"))

    def _createUi(self, instance):
        """ Create the graphical interface """
        self.set_title("%s" % (APPNAME))
        self.connect("delete-event", self._deleteCb)
        self.connect("configure-event", self._configureCb)

        # main menu & toolbar
        vbox = gtk.VBox(False)
        self.add(vbox)
        vbox.show()
        self.menu = self.uimanager.get_widget("/MainMenuBar")
        vbox.pack_start(self.menu, expand=False)
        self.menu.show()
        self.toolbar = self.uimanager.get_widget("/MainToolBar")
        vbox.pack_start(self.toolbar, expand=False)
        self.toolbar.show()
        # timeline and project tabs
        vpaned = gtk.VPaned()
        vbox.pack_start(vpaned)
        vpaned.show()

        self.timeline = Timeline(instance, self.uimanager)
        self.timeline.project = self.project

        vpaned.pack2(self.timeline, resize=True, shrink=False)
        self.timeline.show()
        self.mainhpaned = gtk.HPaned()
        vpaned.pack1(self.mainhpaned, resize=True, shrink=False)

        self.secondhpaned = gtk.HPaned()
        self.mainhpaned.pack1(self.secondhpaned, resize=True, shrink=False)
        self.secondhpaned.show()
        self.mainhpaned.show()

        self.projecttabs = BaseTabs(instance)

        self.sourcelist = SourceList(instance, self.uimanager)
        self.projecttabs.append_page(self.sourcelist, gtk.Label(_("Media Library")))
        self._connectToSourceList()
        self.sourcelist.show()

        self.effectlist = EffectList(instance, self.uimanager)
        self.projecttabs.append_page(self.effectlist, gtk.Label(_("Effect Library")))
        self.effectlist.show()

        self.secondhpaned.pack1(self.projecttabs, resize=True, shrink=False)
        self.projecttabs.show()

        #Clips properties
        self.propertiestabs = BaseTabs(instance, True)
        self.clipconfig = ClipProperties(instance, self.uimanager)
        self.clipconfig.project = self.project
        self.propertiestabs.append_page(self.clipconfig,
                                        gtk.Label(_("Effects configurations")))
        self.clipconfig.show()

        self.secondhpaned.pack2(self.propertiestabs, resize= True, shrink=False)
        self.propertiestabs.show()

        # Viewer
        self.viewer = PitiviViewer()
        # drag and drop
        self.viewer.drag_dest_set(gtk.DEST_DEFAULT_DROP | gtk.DEST_DEFAULT_MOTION,
                           [dnd.FILESOURCE_TUPLE, dnd.URI_TUPLE],
                           gtk.gdk.ACTION_COPY)
        self.viewer.connect("drag_data_received", self._viewerDndDataReceivedCb)
        self.mainhpaned.pack2(self.viewer, resize=False, shrink=False)
        self.viewer.show()
        self.viewer.connect("expose-event", self._exposeEventCb)

        # window and pane position defaults
        self.mainhpaned = self.mainhpaned
        self.hpaned = self.secondhpaned
        self.vpaned = vpaned
        height = -1
        width = -1
        if self.settings.mainWindowHPanePosition:
            self.hpaned.set_position(self.settings.mainWindowHPanePosition)
        if self.settings.mainWindowMainHPanePosition:
            self.mainhpaned.set_position(self.settings.mainWindowMainHPanePosition)
        if self.settings.mainWindowVPanePosition:
            self.vpaned.set_position(self.settings.mainWindowVPanePosition)
        if self.settings.mainWindowWidth:
            width = self.settings.mainWindowWidth
        if self.settings.mainWindowHeight:
            height = self.settings.mainWindowHeight
        self.set_default_size(width, height)
        if height == -1 and width == -1:
            self.maximize()
        self._do_pending_fullscreen = False
        # FIXME: don't know why this doesn't work
        #if self.settings.mainWindowFullScreen:
        #    self._do_pending_fullscreen = True

        # timeline toolbar
        # FIXME: remove toolbar padding and shadow. In fullscreen mode, the
        # toolbar buttons should be clickable with the mouse cursor at the
        # very bottom of the screen.
        ttb = self.uimanager.get_widget("/TimelineToolBar")
        vbox.pack_start(ttb, expand=False)
        ttb.show()

        self.show()

        if not self.settings.mainWindowShowMainToolbar:
            self.toolbar.props.visible = False

        if not self.settings.mainWindowShowTimelineToolbar:
            ttb.props.visible = False

        #application icon
        self.set_icon_name("pitivi")

        #pulseaudio 'role' (http://0pointer.de/blog/projects/tagging-audio.htm
        os.environ["PULSE_PROP_media.role"] = "production"
        os.environ["PULSE_PROP_application.icon_name"] = "pitivi"

    def _connectToSourceList(self):
        self.sourcelist.connect('play', self._sourceListPlayCb)

    def toggleFullScreen(self):
        """ Toggle the fullscreen mode of the application """
        if not self.is_fullscreen:
            self.viewer.window.fullscreen()
            self.is_fullscreen = True
        else:
            self.viewer.window.unfullscreen()
            self.is_fullscreen = False

    #TODO check if it is the way to go
    def setActionsSensitive(self, action_names, sensitive):
        """
        Permit to get the focus for the keyboard letter keys
        for other operation as typing text in an entry, or loose it
        @param action_names: The name of actions we
                             want to set to sensitive or not
        @type action_names: A {list} of action names
        @param sensitiive: %True if actions must be sensitive False otherwise
        @type action_names: C{Bool}
        """
        for action in self.actiongroup.list_actions():
            if action.get_name() in action_names:
                action.set_sensitive(sensitive)

        if self.timeline:
            for action_group in self.timeline.ui_manager.get_action_groups():
                for action in action_group.list_actions():
                    if action.get_name() in action_names:
                        action.set_sensitive(sensitive)

## PlayGround callback
    def _windowizeViewer(self, button, pane):
        # FIXME: the viewer can't seem to handle being unparented/reparented
        pane.remove(self.viewer)
        window = gtk.Window()
        window.add(self.viewer)
        window.connect("destroy", self._reparentViewer, pane)
        window.resize(200, 200)
        window.show_all()

    def _reparentViewer(self, window, pane):
        window.remove(self.viewer)
        pane.pack2(self.viewer, resize=False, shrink=False)
        self.viewer.show()

## Missing Plugin Support

    def _installPlugins(self, details, missingPluginsCallback):
        context = gst.pbutils.InstallPluginsContext()
        context.set_xid(self.window.xid)

        res = gst.pbutils.install_plugins_async(details, context,
                missingPluginsCallback)
        return res

## UI Callbacks

    def _configureCb(self, unused_widget, event):
        if not self.is_fullscreen:
            self.settings.mainWindowWidth = event.width
            self.settings.mainWindowHeight = event.height

    def _deleteCb(self, unused_widget, unused_data=None):
        self._saveWindowSettings()
        if not self.app.shutdown():
            return True

        return False

    def _exposeEventCb(self, unused_widget, event):
        if self._do_pending_fullscreen:
            self._fullScreenAlternateCb(None)
            self._do_pending_fullscreen = False

    def _saveWindowSettings(self):
        self.settings.mainWindowFullScreen = self.is_fullscreen
        self.settings.mainWindowHPanePosition = self.hpaned.get_position()
        self.settings.mainWindowMainHPanePosition = self.mainhpaned.get_position()
        self.settings.mainWindowVPanePosition = self.vpaned.get_position()
        mtb = self.actiongroup.get_action("ShowHideMainToolbar")
        ttb = self.actiongroup.get_action("ShowHideTimelineToolbar")
        self.settings.mainWindowShowMainToolbar = mtb.props.active
        self.settings.mainWindowShowTimelineToolbar = ttb.props.active


    def _sourceListPlayCb(self, sourcelist, factory):
        self._viewFactory(factory)

## Toolbar/Menu actions callback

    def _newProjectMenuCb(self, unused_action):
        self.app.projectManager.newBlankProject()

    def _openProjectCb(self, unused_action):

        chooser = gtk.FileChooserDialog(_("Open File..."),
            self,
            action=gtk.FILE_CHOOSER_ACTION_OPEN,
            buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
                gtk.STOCK_OPEN, gtk.RESPONSE_OK))
        chooser.set_icon_name("pitivi")
        chooser.set_select_multiple(False)
        chooser.set_current_folder(self.settings.lastProjectFolder)
        formats = formatter.list_formats()
        for format in formats:
            filt = gtk.FileFilter()
            filt.set_name(format[1])
            for ext in format[2]:
                filt.add_pattern("*%s" % ext)
            chooser.add_filter(filt)
        default = gtk.FileFilter()
        default.set_name(_("All Supported Formats"))
        default.add_custom(gtk.FILE_FILTER_URI, supported)
        chooser.add_filter(default)

        response = chooser.run()
        self.settings.lastProjectFolder = chooser.get_current_folder()
        if response == gtk.RESPONSE_OK:
            uri = chooser.get_uri()
            uri = unquote(uri)
            self.app.projectManager.loadProject(uri)

        chooser.destroy()
        return True

    def _saveProjectCb(self, unused_action):
        if not self.project.uri:
            self._saveProjectAsCb(unused_action)
        else:
            self.app.projectManager.saveProject(self.project, overwrite=True)

    def _saveProjectAsCb(self, unused_action):
        uri = self._showSaveAsDialog(self.app.current)
        if uri is not None:
            return self.app.projectManager.saveProject(self.project, uri, overwrite=True)

        return False

    def _revertToSavedProjectCb(self, unused_action):
        return self.app.projectManager.revertToSavedProject()


    def _projectSettingsCb(self, unused_action):
        from projectsettings import ProjectSettingsDialog
        ProjectSettingsDialog(self, self.app.current).show()

    def _quitCb(self, unused_action):
        self._saveWindowSettings()
        self.app.shutdown()

    def _fullScreenCb(self, unused_action):
        self.toggleFullScreen()

    def _fullScreenAlternateCb(self, unused_action):
        self.actiongroup.get_action("FullScreen").activate()

    def _showHideMainToolBar(self, action):
        self.uimanager.get_widget("/MainToolBar").props.visible = \
            action.props.active

    def _showHideTimelineToolbar(self, action):
        self.uimanager.get_widget("/TimelineToolBar").props.visible = \
            action.props.active

    def _aboutResponseCb(self, dialog, unused_response):
        dialog.destroy()

    def _showWebsiteCb(self, dialog, uri):
        import webbrowser
        webbrowser.open_new(uri)

    def _aboutCb(self, unused_action):
        abt = gtk.AboutDialog()
        abt.set_name(APPNAME)
        abt.set_version("v%s" % pitivi_version)
        gtk.about_dialog_set_url_hook(self._showWebsiteCb)
        abt.set_website("http://www.pitivi.org/")
        authors = ["Edward Hervey <*****@*****.**>",
                   "Alessandro Decina <*****@*****.**>",
                   "Brandon Lewis <*****@*****.**> (UI)",
                   "",
                   _("Contributors:"),
                   "Christophe Sauthier <*****@*****.**> (i18n)",
                   "Laszlo Pandy <*****@*****.**> (UI)",
                   "Ernst Persson  <*****@*****.**>",
                   "Richard Boulton <*****@*****.**>",
                   "Thibaut Girka <*****@*****.**> (UI)",
                   "Jeff Fortin <*****@*****.**> (UI)",
                   "Johan Dahlin <*****@*****.**> (UI)",
                   "Luca Della Santina <*****@*****.**>",
                   "Thijs Vermeir <*****@*****.**>",
                   "Sarath Lakshman <*****@*****.**>"]
        abt.set_authors(authors)
        abt.set_license(_("GNU Lesser General Public License\n"
                          "See http://www.gnu.org/copyleft/lesser.html for more details"))
        abt.set_icon_name("pitivi")
        abt.set_logo_icon_name("pitivi")
        abt.connect("response", self._aboutResponseCb)
        abt.show()

    def _undoCb(self, action):
        self.app.action_log.undo()

    def _redoCb(self, action):
        self.app.action_log.redo()

    def _pluginManagerCb(self, unused_action):
        from pluginmanagerdialog import PluginManagerDialog
        PluginManagerDialog(self.app.plugin_manager)

    # Import from Webcam callback
    def _ImportWebcam(self,unused_action):
        from webcam_managerdialog import WebcamManagerDialog
        w = WebcamManagerDialog(self.app)
        w.show()

    # Capture network stream callback
    def _ImportNetstream(self,unused_action):
        from netstream_managerdialog import NetstreamManagerDialog
        NetstreamManagerDialog()

    # screencast callback
    def _Screencast(self,unused_action):
        from screencast_managerdialog import ScreencastManagerDialog
        ScreencastManagerDialog(self.app)

    ## Devices changed
    def _deviceChangeCb(self, probe, unused_device):
        if len(probe.getVideoSourceDevices()) < 1:
            self.webcam_button.set_sensitive(False)
        else:
            self.webcam_button.set_sensitive(True)

    def _hideChildWindow(self, window, event):
        window.hide()
        return True

    def _prefsCb(self, unused_action):
        if not self.prefsdialog:
            from pitivi.ui.prefs import PreferencesDialog
            self.prefsdialog = PreferencesDialog(self.app)
            self.prefsdialog.set_transient_for(self)
            self.prefsdialog.connect("delete-event", self._hideChildWindow)
        self.prefsdialog.show()

    def rewind(self, unused_action):
        pass

    def playPause(self, unused_action):
        self.viewer.togglePlayback()

    def pause(self, unused_action):
        self.viewer.pause()

    def fastForward(self, unused_action):
        pass

    def loop(self, unused_action):
        pass

    def _projectManagerNewProjectLoadedCb(self, projectManager, project):
        self.log("A NEW project is loaded, update the UI!")
        self.project = project
        self._connectToProjectSources(project.sources)
        can_render = project.timeline.duration > 0
        self.render_button.set_sensitive(can_render)
        self._syncDoUndo(self.app.action_log)

        if project.timeline.duration != 0:
            self._setBestZoomRatio()
        else:
            self._zoom_duration_changed = True

        self.project.seeker.connect("seek", self._timelineSeekCb)

        # preliminary seek to ensure the project pipeline is configured
        self.project.seeker.seek(0)

    def _setBestZoomRatio(self):
        ruler_width = self.timeline.ruler.get_allocation()[2]
        timeline_duration = self.project.timeline.duration

        ideal_zoom_ratio = ruler_width / float(timeline_duration / gst.SECOND)
        nearest_zoom_level = Zoomable.computeZoomLevel(ideal_zoom_ratio)
        Zoomable.setZoomLevel(nearest_zoom_level)

    def _projectManagerNewProjectLoadingCb(self, projectManager, uri):
        self.log("A NEW project is being loaded, deactivate UI")

    def _projectManagerSaveProjectFailedCb(self, projectManager,
            project, uri, exception):
        # FIXME: do something here
        self.error("failed to save project")

    def _projectManagerProjectSavedCb(self, projectManager, project, uri):
        self.app.action_log.checkpoint()
        self._syncDoUndo(self.app.action_log)
        if project.uri is None:
            project.uri = uri

    def _projectManagerClosingProjectCb(self, projectManager, project):
        if not project.hasUnsavedModifications():
            return True

        if project.uri:
            save = gtk.STOCK_SAVE
        else:
            save = gtk.STOCK_SAVE_AS

        dialog = gtk.Dialog("",
            self, gtk.DIALOG_MODAL | gtk.DIALOG_NO_SEPARATOR,
            (_("Close without saving"), gtk.RESPONSE_REJECT,
                    gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
                    save, gtk.RESPONSE_YES))
        dialog.set_icon_name("pitivi")
        dialog.set_resizable(False)
        dialog.set_has_separator(False)
        dialog.set_default_response(gtk.RESPONSE_YES)

        primary = gtk.Label()
        primary.set_line_wrap(True)
        primary.set_use_markup(True)
        primary.set_alignment(0, 0.5)

        message = _("Save changes to the current project before closing?")
        primary.set_markup("<span weight=\"bold\">" + message + "</span>")

        secondary = gtk.Label()
        secondary.set_line_wrap(True)
        secondary.set_use_markup(True)
        secondary.set_alignment(0, 0.5)
        secondary.props.label = _("If you don't save some of your "
                "changes will be lost")

        # put the text in a vbox
        vbox = gtk.VBox(False, 12)
        vbox.pack_start(primary, expand=True, fill=True)
        vbox.pack_start(secondary, expand=True, fill=True)

        # make the [[image] text] hbox
        image = gtk.image_new_from_stock(gtk.STOCK_DIALOG_WARNING,
               gtk.ICON_SIZE_DIALOG)
        hbox = gtk.HBox(False, 12)
        hbox.pack_start(image, expand=False)
        hbox.pack_start(vbox, expand=True, fill=True)
        action_area = dialog.get_action_area()
        # FIXME: find out where this "6" comes from. It's needed to align our
        # hbox with the action area button box
        hbox.set_border_width(6)

        # stuff the hbox in the dialog
        content_area = dialog.get_content_area()
        content_area.pack_start(hbox, expand=True, fill=True)
        content_area.set_spacing(14)
        hbox.show_all()

        response = dialog.run()
        dialog.destroy()
        if response == gtk.RESPONSE_YES:
            if project.uri is not None:
                res = self.app.projectManager.saveProject(project, overwrite=True)
            else:
                res = self._saveProjectAsCb(None)
        elif response == gtk.RESPONSE_REJECT:
            res = True
        else:
            res = False

        return res

    def _projectManagerProjectClosedCb(self, projectManager, project):
        # we must disconnect from the project pipeline before it is released
        self._disconnectFromProjectSources(project.sources)
        self.viewer.setAction(None)
        self.viewer.setPipeline(None)
        project.seeker.disconnect_by_func(self._timelineSeekCb)
        return False

    def _projectManagerRevertingToSavedCb(self, projectManager, project):
        if project.hasUnsavedModifications():
            dialog = gtk.MessageDialog(self,
                                gtk.DIALOG_MODAL,
                                gtk.MESSAGE_WARNING,
                                gtk.BUTTONS_NONE,
                                _("Do you want to reload current project?")
                                )
            dialog.set_icon_name("pitivi")
            dialog.add_buttons(gtk.STOCK_CANCEL, gtk.RESPONSE_NO,
                                        gtk.STOCK_REVERT_TO_SAVED, gtk.RESPONSE_YES)
            dialog.set_title(_("Revert to saved project"))
            dialog.set_resizable(False)
            dialog.set_property("secondary-text",
                                            _("All unsaved changes will be lost.")
                                        )
            dialog.set_default_response(gtk.RESPONSE_NO)
            response = dialog.run()
            dialog.destroy()
            if response <> gtk.RESPONSE_YES:
                return False
        return True


    def _projectManagerNewProjectFailedCb(self, projectManager, uri, exception):
        # ungrey UI
        dialog = gtk.MessageDialog(self,
            gtk.DIALOG_MODAL,
            gtk.MESSAGE_ERROR,
            gtk.BUTTONS_OK,
            _("PiTiVi is unable to load file \"%s\"") %
                uri)
        dialog.set_icon_name("pitivi")
        dialog.set_title(_("Error Loading File"))
        dialog.set_property("secondary-text", str(exception))
        dialog.run()
        dialog.destroy()
        self.set_sensitive(True)

    def _projectManagerMissingUriCb(self, instance, formatter, uri, factory):
        dialog = gtk.Dialog(_("Locate missing file..."),
            self,
            gtk.DIALOG_MODAL,
            buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
            gtk.STOCK_OPEN, gtk.RESPONSE_OK))
        dialog.set_icon_name("pitivi")
        dialog.set_border_width(12)
        dialog.get_content_area().set_spacing(6)

        text = _("The following file has moved, please tell PiTiVi where to find it.") + \
            "\n\n" + beautify_factory(factory) + "\n" + \
            "<b>%s</b>" % _("Duration:") + beautify_length(factory.duration)

        label = gtk.Label()
        label.set_markup(text)
        label.set_justify(gtk.JUSTIFY_CENTER)
        dialog.get_content_area().pack_start(label, False, False)
        label.show()

        chooser = gtk.FileChooserWidget(action=gtk.FILE_CHOOSER_ACTION_OPEN)
        chooser.set_select_multiple(False)
        chooser.set_current_folder(self.settings.lastProjectFolder)
        dialog.get_content_area().pack_start(chooser, True, True)
        chooser.show()

        dialog.set_size_request(640, 480)
        response = dialog.run()

        if response == gtk.RESPONSE_OK:
            self.log("User chose a URI to save project to")
            new = chooser.get_uri()
            if new:
                formatter.addMapping(uri, unquote(new))
        else:
            self.log("User didn't choose a URI to save project to")
            # FIXME: not calling addMapping doesn't keep the formatter from
            # re-emitting the same signal. How do we get out of this
            # situation?
            pass

        dialog.destroy()

    def _connectToProjectSources(self, sourcelist):
        sourcelist.connect("missing-plugins", self._sourceListMissingPluginsCb)

    def _disconnectFromProjectSources(self, sourcelist):
        sourcelist.disconnect_by_func(self._sourceListMissingPluginsCb)

    def _actionLogCommit(self, action_log, stack, nested):
        if nested:
            return

        self._syncDoUndo(action_log)

    def _actionLogCleaned(self, action_log):
        self._syncDoUndo(action_log)

    def _actionLogUndo(self, action_log, stack):
        self._syncDoUndo(action_log)

    def _actionLogRedo(self, action_log, stack):
        self._syncDoUndo(action_log)

    def _syncDoUndo(self, action_log):
        undo_action = self.actiongroup.get_action("Undo")
        can_undo = bool(action_log.undo_stacks)
        undo_action.set_sensitive(can_undo)

        dirty = action_log.dirty()
        save_action = self.actiongroup.get_action("SaveProject")
        save_action.set_sensitive(dirty)
        if self.app.current.uri is not None:
            revert_action = self.actiongroup.get_action("RevertToSavedProject")
            revert_action.set_sensitive(dirty)
        self.app.current.setModificationState(dirty)

        redo_action = self.actiongroup.get_action("Redo")
        can_redo = bool(action_log.redo_stacks)
        redo_action.set_sensitive(can_redo)

        if self.project is not None:
            app_name = "%s" % (APPNAME)
            title = u"%s \u2014 %s" % (self.project.name, app_name)
            if dirty:
                title = "*" + title
            title = title.encode("utf8")
            self.set_title(title)

## PiTiVi current project callbacks

    def _setProject(self):
        if self.project:
            self.project_pipeline = self.project.pipeline
            self.project_timeline = self.project.timeline
            if self.timeline:
                self.timeline.project = self.project
                self.clipconfig.project = self.project
                self.app.timelineLogObserver.effect_properties_tracker.pipeline\
                                                        = self.project.pipeline

    project = receiver(_setProject)

    @handler(project, "settings-changed")
    def _settingsChangedCb(self, project, old, new):
        if self.viewer.action == self.project.view_action:
            self.viewer.setDisplayAspectRatio(float(new.videopar *
            new.videowidth) / float(new.videoheight))

    def _sourceListMissingPluginsCb(self, project, uri, factory,
            details, descriptions, missingPluginsCallback):
        res = self._installPlugins(details, missingPluginsCallback)
        return res

## Current Project Pipeline

    def _setProjectPipeline(self):
        if self.project_pipeline:
            # connect to timeline
            self.project_pipeline.activatePositionListener()
            self._timelinePipelinePositionChangedCb(self.project_pipeline, 0)

    project_pipeline = receiver()

    @handler(project_pipeline, "error")
    def _pipelineErrorCb(self, unused_pipeline, error, detail):
        pass

    @handler(project_pipeline, "position")
    def _timelinePipelinePositionChangedCb(self, pipeline, position):
        self.timeline.timelinePositionChanged(position)
        self.timelinepos = position

    @handler(project_pipeline, "state-changed")
    def _timelinePipelineStateChangedCb(self, pipeline, state):
        self.timeline.stateChanged(state)

## Project Timeline (not to be confused with UI timeline)

    project_timeline = receiver()

    @handler(project_timeline, "duration-changed")
    def _timelineDurationChangedCb(self, timeline, duration):
        if duration > 0:
            sensitive = True
            if self._zoom_duration_changed:
                self._setBestZoomRatio()
                self._zoom_duration_changed = False
        else:
            sensitive = False
        self.render_button.set_sensitive(sensitive)

## other

    def _showSaveAsDialog(self, project):
        self.log("Save URI requested")
        chooser = gtk.FileChooserDialog(_("Save As..."),
            self,
            action=gtk.FILE_CHOOSER_ACTION_SAVE,
            buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
            gtk.STOCK_SAVE, gtk.RESPONSE_OK))

        chooser.set_icon_name("pitivi")
        chooser.set_select_multiple(False)
        chooser.set_current_name(_("Untitled.xptv"))
        chooser.set_current_folder(self.settings.lastProjectFolder)
        chooser.props.do_overwrite_confirmation = True
        formats = formatter.list_formats()
        for format in formats:
            filt = gtk.FileFilter()
            filt.set_name(format[1])
            for ext in format[2]:
                filt.add_pattern("*.%s" % ext)
            chooser.add_filter(filt)
        default = gtk.FileFilter()
        default.set_name(_("Detect Automatically"))
        default.add_pattern("*")
        chooser.add_filter(default)

        response = chooser.run()
        self.settings.lastProjectFolder = chooser.get_current_folder()

        if response == gtk.RESPONSE_OK:
            self.log("User chose a URI to save project to")
            # need to do this to work around bug in gst.uri_construct
            # which escapes all /'s in path!
            uri = "file://" + chooser.get_filename()
            format = chooser.get_filter().get_name()
            if format == _("Detect Automatically"):
                format = None
            self.log("uri:%s , format:%s", uri, format)
            ret = uri
        else:
            self.log("User didn't choose a URI to save project to")
            ret = None

        chooser.destroy()
        return ret

    def _viewerDndDataReceivedCb(self, unused_widget, context, unused_x, unused_y,
                           selection, targetType, ctime):
        # FIXME : This should be handled by the main application who knows how
        # to switch between pipelines.
        self.info("context:%s, targetType:%s", context, targetType)
        if targetType == dnd.TYPE_URI_LIST:
            uri = selection.data.strip().split("\n")[0].strip()
        elif targetType == dnd.TYPE_PITIVI_FILESOURCE:
            uri = selection.data
        else:
            context.finish(False, False, ctime)
            return

        # Use factory from our source list if we have the given uri
        try:
            fact = self.project.sources.getUri(uri)
        except SourceListError:
            from pitivi.factories.file import FileSourceFactory
            fact = FileSourceFactory(uri)
        self._viewFactory(fact)
        context.finish(True, False, ctime)

    def _viewFactory(self, factory):
        # FIXME: we change the viewer pipeline unconditionally for now
        # we need a pipeline for playback
        pipeline = Pipeline()
        action = ViewAction()
        action.addProducers(factory)
        self.viewer.setPipeline(None)
        self.viewer.showSlider()
        # FIXME: why do I have to call viewer.setAction ?
        self.viewer.setAction(action)
        self.viewer.setPipeline(pipeline)
        self.viewer.play()

    def _timelineSeekCb(self, ruler, position, format):
        self.debug("position:%s", gst.TIME_ARGS (position))
        if self.viewer.action != self.project.view_action:
            self.viewer.setPipeline(None)
            self.viewer.hideSlider()
            self.viewer.setAction(self.project.view_action)
            self.viewer.setPipeline(self.project.pipeline)
            # get the pipeline settings and set the DAR of the viewer
            sett = self.project.getSettings()
            self.viewer.setDisplayAspectRatio(float(sett.videopar * sett.videowidth) / float(sett.videoheight))
        # everything above only needs to be done if the viewer isn't already
        # set to the pipeline.
        self.project.pipeline.pause()
        try:
            self.project.pipeline.seek(position, format)
        except:
            self.debug("Seeking failed")
class PropertyEditor(gtk.ScrolledWindow):

    __MODULES__ = {}

    def __init__(self, instance, *args, **kwargs):
        gtk.ScrolledWindow.__init__(self, *args, **kwargs)
        self.instance = instance
        self.timeline = instance.current.timeline
        self._createUi()
        self._selectionChangedCb(self.timeline)
        self._module_instances = {}
        self._default_editor = DefaultPropertyEditor()

    def _createUi(self):
        # basic initialization
        self.set_border_width(5)

        # scrolled window
        self.set_shadow_type(gtk.SHADOW_ETCHED_IN)
        self._no_objs = gtk.Viewport()
        self._no_objs.add(gtk.Label(_("No Objects Selected")))
        self._contents = self._no_objs
        self.add(self._no_objs)

## Public API

    @classmethod
    def addModule(cls, core_class, widget_class):
        cls.__MODULES__[core_class] = widget_class

    @classmethod
    def delModule(cls, core_class):
        del cls.__MODULES__[core_class]

## Internal Methods

    def _get_widget_for_type(self, t):
        w = self._default_editor
        if t in self._module_instances:
            w = self._module_instances[t]
        elif t in self.__MODULES__:
            w = self.__MODULES__[t]()
            self._module_instances[t] = w
        return w

    def _set_contents(self, widget):
        if widget != self._contents:
            self.remove(self._contents)
            self._contents = widget
            self.add(widget)
            self.show_all()

## Instance Callbacks

    instance = receiver()

    @handler(instance, "new-project-loaded")
    def _newProjectLoading(self, unused_inst, project):
        self.timeline = project.timeline

    @handler(instance, "new-project-failed")
    def _newProjectFailed(self, unused_inst, unused_reason, unused_uri):
        self.timeline = None

## Timeline Callbacks

    timeline = receiver()

    @handler(timeline, "selection-changed")
    def _selectionChangedCb(self, timeline):
        if not self.timeline:
            return
        objs = self.timeline.getSelection()
        if objs:
            t = same((type(obj.factory) for obj in objs))
            if t:
                widget = self._get_widget_for_type(t)
            else:
                widget = DefaultPropertyEditor(objs)
            widget.setObjects(objs)
        else:
            widget = self._no_objs
        self._set_contents(widget)
Example #10
0
class Curve(goocanvas.ItemSimple, goocanvas.Item, View, Zoomable):

    __gtype_name__ = 'Curve'

    class Controller(Controller):

        _cursor = HAND
        _kf = None

        def drag_start(self, item, target, event):
            self._view.app.action_log.begin("volume change")
            initial = self.from_item_event(item, event)
            if self._kf:
                self._mousedown = self._view.keyframes[self._kf] - initial
            if not self._kf:
                # we are moving the entire curve, so we need to know the
                # inital position of each keyframe
                self._offsets = dict(self._view.keyframes)
                self._segment = self._view.findSegment(
                    self.xyToTimeValue(initial)[0])

        def drag_end(self, item, target, event):
            self._view.app.action_log.commit()

        def set_pos(self, obj, pos):
            interpolator = self._view.interpolator
            if self._kf:
                time, value = self.xyToTimeValue(pos)
                self._kf.time = time
                self._kf.value = value
            else:
                for kf in self._segment:
                    time, value = self.xyToTimeValue(self._offsets[kf] + pos -
                                                     self.pos(self._view))
                    kf.value = value

        def hover(self, item, target, event):
            coords = self.from_item_event(item, event)
            self._kf = self._view.findKeyframe(coords)
            self._view.setFocusedKf(self._kf)

        def double_click(self, pos):
            interpolator = self._view.interpolator
            kf = self._view.findKeyframe(pos)
            if kf is None:
                time, value = self.xyToTimeValue(pos)
                self._view.app.action_log.begin("add volume point")
                kf = interpolator.newKeyframe(time)
                self._view.setFocusedKf(kf)
                self._view.app.action_log.commit()
            else:
                self._view.app.action_log.begin("remove volume point")
                self._view.interpolator.removeKeyframe(kf)
                self._view.app.action_log.commit()

        def xyToTimeValue(self, pos):
            view = self._view
            interpolator = view.interpolator
            bounds = view.bounds
            time = (Zoomable.pixelToNs(pos[0] - bounds.x1) +
                    view.element.in_point)
            value = ((1 - (pos[1] - bounds.y1 - view._min) / view._range) *
                     interpolator.range) + interpolator.lower
            return time, value

        def enter(self, item, target):
            coords = self.from_item_event(item, self._last_event)
            self._kf = self._view.findKeyframe(coords)
            self._view.setFocusedKf(self._kf)
            self._view.focus()

        def leave(self, item, target):
            self._view.normal()

    def __init__(self,
                 instance,
                 element,
                 interpolator,
                 height=LAYER_HEIGHT_EXPANDED,
                 **kwargs):
        super(Curve, self).__init__(**kwargs)
        View.__init__(self)
        Zoomable.__init__(self)
        self.app = instance
        self.keyframes = {}
        self.height = float(height)
        self.element = element
        self.props.pointer_events = goocanvas.EVENTS_STROKE
        self.interpolator = interpolator
        self._focused_kf = None
        self.normal()

## properties

    def _get_height(self):
        return self._height

    def _set_height(self, value):
        self._height = value
        self._min = CURVE_STROKE_WIDTH / 2
        self._max = value - (CURVE_STROKE_WIDTH / 2)
        self._range = self._max - self._min
        self.changed(True)

    height = gobject.property(_get_height, _set_height, type=float)

    ## element callbacks

    def _set_element(self):
        self.previewer = previewer.get_preview_for_object(self.element)

    element = receiver(setter=_set_element)

    @handler(element, "in-point-changed")
    @handler(element, "media-duration-changed")
    def _media_props_changed(self, obj, unused_start_duration):
        self.changed(True)

## interpolator callbacks

    interpolator = receiver()

    @handler(interpolator, "keyframe-removed")
    def keyframeRemoved(self, unused_interpolator, keyframe):
        if keyframe in self.keyframes:
            del self.keyframes[keyframe]
            if keyframe is self._focused_kf:
                self._focused_kf = None
        self.changed(False)

    @handler(interpolator, "keyframe-added")
    @handler(interpolator, "keyframe-moved")
    def curveChanged(self, unused_interpolator, unused_keyframe):
        self.changed(False)

## Zoomable interface overries

    def zoomChanged(self):
        self.changed(True)

## goocanvas item methods

    def do_simple_update(self, cr):
        cr.identity_matrix()
        if self.element.factory:
            self.bounds = goocanvas.Bounds(
                0, 0, Zoomable.nsToPixel(self.element.duration), self.height)

    def _getKeyframeXY(self, kf):
        interp = self.interpolator
        x = self.nsToPixel(kf.time - self.element.in_point)
        y = self._range - ((
            (kf.value - interp.lower) / interp.range) * self._range)
        return point.Point(x + self.bounds.x1, y + self.bounds.y1 + self._min)

    def _controlPoint(self, cr, kf):
        pos = self._getKeyframeXY(kf)
        x, y = pos
        cr.rectangle(x - KW_WIDTH2, y - KW_HEIGHT2, KW_WIDTH, KW_HEIGHT)
        self.keyframes[kf] = pos

    def do_simple_paint(self, cr, bounds):
        bounds = intersect(self.bounds, bounds)
        cr.identity_matrix()
        if self.interpolator:
            height = bounds.y2 - bounds.y1
            width = bounds.x2 - bounds.x1
            cr.rectangle(bounds.x1, bounds.y1, width, height)
            cr.clip()
            self.make_curve(cr)
            cr.set_line_width(self.line_width)
            cr.set_source_rgb(1, 0, 0)
            cr.stroke()
            self.make_keyframes(cr)
            cr.set_line_width(1.0)
            cr.set_source_rgb(1, 1, 1)
            cr.fill_preserve()
            cr.set_source_rgb(1, 0, 0)
            cr.stroke()
            # re-draw the focused keyframe, if it exists, inverted
            if self._focused_kf:
                self._controlPoint(cr, self._focused_kf)
                cr.set_source_rgb(1, 0, 0)
                cr.fill_preserve()
                cr.set_source_rgb(1, 1, 1)
                cr.stroke()

    def make_curve(self, cr):
        if not self.interpolator:
            return
        iterator = self.interpolator.keyframes
        cr.move_to(*self._getKeyframeXY(iterator.next()))
        for kf in iterator:
            cr.line_to(*self._getKeyframeXY(kf))
        cr.line_to(*self._getKeyframeXY(self.interpolator.end))

    def make_keyframes(self, cr):
        for kf in self.interpolator.keyframes:
            self._controlPoint(cr, kf)

    def do_simple_is_item_at(self, x, y, cr, pointer_event):
        if (between(0, x, self.nsToPixel(self.element.duration))
                and between(0, y, self.height)):
            x += self.bounds.x1
            y += self.bounds.y1
            self.make_curve(cr)
            cr.set_line_width(10.0)
            return cr.in_stroke(x, y) or bool(self.findKeyframe((x, y)))
        return False

## public

    def setFocusedKf(self, kf):
        if self._focused_kf is not kf:
            self.changed(False)
        self._focused_kf = kf

    def focus(self):
        self.line_width = CURVE_STROKE_WIDTH * 1.5
        self.changed(False)

    def normal(self):
        self.line_width = CURVE_STROKE_WIDTH
        self.setFocusedKf(None)
        self.changed(False)

    def findKeyframe(self, pos):
        x, y = pos
        for keyframe, value in self.keyframes.iteritems():
            kx, ky = value
            if (between(kx - KW_MOUSE_WIDTH, x, kx + KW_MOUSE_WIDTH) and
                    between(ky - KW_MOUSE_HEIGHT, y, ky + KW_MOUSE_HEIGHT)):
                return keyframe
        return None

    def findSegment(self, time):
        before = self.interpolator.start
        after = self.interpolator.end
        for keyframe in self.keyframes.iterkeys():
            if between(before.time, keyframe.time, time):
                before = keyframe
            if between(time, keyframe.time, after.time):
                after = keyframe
        assert before.time <= after.time
        return before, after
class Timeline(gtk.Table, Loggable, Zoomable):

    # the screen width of the current unit
    unit_width = 10

    # specific levels of zoom, in (multiplier, unit) pairs which
    # from zoomed out to zoomed in

    def __init__(self, instance, ui_manager):
        gtk.Table.__init__(self, rows=2, columns=1, homogeneous=False)
        Loggable.__init__(self)
        Zoomable.__init__(self)
        self.log("Creating Timeline")

        self.project = None
        self.ui_manager = ui_manager
        self.app = instance
        self._temp_objects = None
        self._factories = None
        self._finish_drag = False
        self._position = 0
        self._state = gst.STATE_NULL
        self._createUI()
        self._prev_duration = 0
        self.shrink = True
        self.rate = gst.Fraction(1, 1)
        self._seeker = Seeker(80)
        self._seeker.connect('seek', self._seekerSeekCb)

    def _createUI(self):
        self.leftSizeGroup = gtk.SizeGroup(gtk.SIZE_GROUP_HORIZONTAL)
        self.hadj = gtk.Adjustment()
        self.vadj = gtk.Adjustment()

        # controls for tracks and layers
        self._controls = TimelineControls()
        controlwindow = gtk.ScrolledWindow(None, self.vadj)
        controlwindow.set_policy(gtk.POLICY_NEVER, gtk.POLICY_NEVER)
        controlwindow.add_with_viewport(self._controls)
        controlwindow.set_size_request(-1, 1)
        self.attach(controlwindow, 0, 1, 1, 2, xoptions=0)

        # timeline ruler
        self.ruler = ruler.ScaleRuler(self.hadj)
        self.ruler.set_size_request(0, 25)
        self.ruler.set_border_width(2)
        self.ruler.connect("key-press-event", self._keyPressEventCb)
        self.attach(self.ruler, 1, 2, 0, 1, yoptions=0)

        # proportional timeline
        self._canvas = TimelineCanvas(self.app)
        timelinewindow = gtk.ScrolledWindow(self.hadj, self.vadj)
        timelinewindow.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
        timelinewindow.add(self._canvas)
        #FIXME: remove padding between scrollbar and scrolled window
        self.attach(timelinewindow, 1, 2, 1, 2)

        # drag and drop
        self.drag_dest_set(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-drop", self._dragDropCb)
        self.connect("drag-motion", self._dragMotionCb)

        # toolbar actions
        actions = (
            ("ZoomIn", gtk.STOCK_ZOOM_IN, None, None, ZOOM_IN, self._zoomInCb),
            ("ZoomOut", gtk.STOCK_ZOOM_OUT, None, None, ZOOM_OUT,
             self._zoomOutCb),
        )

        selection_actions = (
            ("DeleteObj", gtk.STOCK_DELETE, None, "Delete", DELETE,
             self.deleteSelected),
            ("UnlinkObj", "pitivi-unlink", None, "<Shift><Control>L", UNLINK,
             self.unlinkSelected),
            ("LinkObj", "pitivi-link", None, "<Control>L", LINK,
             self.linkSelected),
            ("UngroupObj", "pitivi-ungroup", None, "<Shift><Control>G",
             UNGROUP, self.ungroupSelected),
            ("GroupObj", "pitivi-group", None, "<Control>G", GROUP,
             self.groupSelected),
        )

        toggle_actions = (("Razor", "pitivi-split", _("Razor"), "<Ctrl>R",
                           RAZOR, self.toggleRazor), )

        actiongroup = gtk.ActionGroup("timelinepermanent")
        actiongroup.add_actions(actions)
        actiongroup.add_toggle_actions(toggle_actions)
        self.ui_manager.insert_action_group(actiongroup, 0)

        actiongroup = gtk.ActionGroup("timelineselection")
        actiongroup.add_actions(selection_actions)
        self.link_action = actiongroup.get_action("LinkObj")
        self.unlink_action = actiongroup.get_action("UnlinkObj")
        self.group_action = actiongroup.get_action("GroupObj")
        self.ungroup_action = actiongroup.get_action("UngroupObj")
        self.delete_action = actiongroup.get_action("DeleteObj")

        self.ui_manager.insert_action_group(actiongroup, -1)

        self.ui_manager.add_ui_from_string(ui)

        # drag and drop
        self.drag_dest_set(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-drop", self._dragDropCb)
        self.connect("drag-motion", self._dragMotionCb)
        self._canvas.connect("button-press-event", self._buttonPress)
        self._canvas.connect("button-release-event", self._buttonRelease)
        self._canvas.connect("key-press-event", self._keyPressEventCb)

## Event callbacks

    def _keyPressEventCb(self, unused_widget, event):
        kv = event.keyval
        self.debug("kv:%r", kv)
        if kv not in [gtk.keysyms.Left, gtk.keysyms.Right]:
            return False
        mod = event.get_state()
        try:
            if mod & gtk.gdk.CONTROL_MASK:
                now = self.project.pipeline.getPosition()
                ltime, rtime = self.project.timeline.edges.closest(now)

            if kv == gtk.keysyms.Left:
                if mod & gtk.gdk.SHIFT_MASK:
                    self._seekRelative(-gst.SECOND)
                elif mod & gtk.gdk.CONTROL_MASK:
                    self._seeker.seek(ltime + 1)
                else:
                    self._seekRelative(-long(self.rate * gst.SECOND))
            elif kv == gtk.keysyms.Right:
                if mod & gtk.gdk.SHIFT_MASK:
                    self._seekRelative(gst.SECOND)
                elif mod & gtk.gdk.CONTROL_MASK:
                    self._seeker.seek(rtime + 1)
                else:
                    self._seekRelative(long(self.rate * gst.SECOND))
        finally:
            return True

    def _seekRelative(self, time):
        pipeline = self.project.pipeline
        seekvalue = max(
            0, min(pipeline.getPosition() + time, pipeline.getDuration()))
        self._seeker.seek(seekvalue)

    def _seekerSeekCb(self, seeker, position, format):
        self.project.pipeline.seek(position, format)

    def _buttonPress(self, window, event):
        self.shrink = False

    def _buttonRelease(self, window, event):
        self.shrink = True
        self._timelineStartDurationChanged(self.timeline,
                                           self.timeline.duration)

## Drag and Drop callbacks

    def _dragMotionCb(self, unused, context, x, y, timestamp):
        self.warning("self._factories:%r, self._temp_objects:%r",
                     not not self._factories, not not self._temp_objects)
        if self._factories is None:
            atom = gtk.gdk.atom_intern(dnd.FILESOURCE_TUPLE[0])
            self.drag_get_data(context, atom, timestamp)
            self.drag_highlight()
        else:
            # actual drag-and-drop
            if not self._temp_objects:
                self.timeline.disableUpdates()
                self._add_temp_source()
                focus = self._temp_objects[0]
                self._move_context = MoveContext(self.timeline, focus,
                                                 set(self._temp_objects[1:]))
            self._move_temp_source(self.hadj.props.value + x, y)
        return True

    def _dragLeaveCb(self, unused_layout, unused_context, unused_tstamp):
        if self._temp_objects:
            try:
                for obj in self._temp_objects:
                    self.timeline.removeTimelineObject(obj, deep=True)
            finally:
                self._temp_objects = None
        self.drag_unhighlight()
        self.timeline.enableUpdates()

    def _dragDropCb(self, widget, context, x, y, timestamp):
        self.app.action_log.begin("add clip")
        self._add_temp_source()
        focus = self._temp_objects[0]
        self._move_context = MoveContext(self.timeline, focus,
                                         set(self._temp_objects[1:]))
        self._move_temp_source(self.hadj.props.value + x, y)
        self._move_context.finish()
        self.app.action_log.commit()
        context.drop_finish(True, timestamp)
        self._factories = None
        self._temp_objects = None
        return True

    def _dragDataReceivedCb(self, unused_layout, context, x, y, selection,
                            targetType, timestamp):
        self.log("SimpleTimeline, targetType:%d, selection.data:%s" %
                 (targetType, selection.data))
        # FIXME: let's have just one target type, call it
        # TYPE_PITIVI_OBJECTFACTORY.
        # TODO: handle uri targets by doign an import-add. This would look
        # something like this:
        # tell current project to import the uri
        # wait for source-added signal, meanwhile ignore dragMotion signals
        # when ready, add factories to the timeline.
        if targetType == dnd.TYPE_PITIVI_FILESOURCE:
            uris = selection.data.split("\n")
        else:
            context.finish(False, False, timestamp)
        self._factories = [self.project.sources.getUri(uri) for uri in uris]
        context.drag_status(gtk.gdk.ACTION_COPY, timestamp)
        return True

    def _add_temp_source(self):
        self._temp_objects = [
            self.timeline.addSourceFactory(factory)
            for factory in self._factories
        ]

    def _move_temp_source(self, x, y):
        x1, y1, x2, y2 = self._controls.get_allocation()
        offset = 10 + (x2 - x1)
        x, y = self._canvas.convert_from_pixels(x - offset, y)
        priority = int((y // (LAYER_HEIGHT_EXPANDED + LAYER_SPACING)))
        delta = Zoomable.pixelToNs(x)
        self._move_context.editTo(delta, priority)

## Zooming and Scrolling

    def zoomChanged(self):
        # this has to be in a timeout, because the resize hasn't actually
        # completed yet, and so the canvas can't actually complete the scroll
        gobject.idle_add(self.scrollToPlayhead)

    def timelinePositionChanged(self, position):
        self._position = position
        self.ruler.timelinePositionChanged(position)
        self._canvas._position = position
        if self._state == gst.STATE_PLAYING:
            self.scrollToPlayhead()

    def stateChanged(self, state):
        self._state = state

    def scrollToPlayhead(self):
        width = self.get_allocation().width
        new_pos = Zoomable.nsToPixel(self._position)
        scroll_pos = self.hadj.get_value()
        if (new_pos < scroll_pos) or (new_pos > scroll_pos + width):
            self.scrollToPosition(new_pos - width / 2)
        return False

    def scrollToPosition(self, position):
        if position > self.hadj.upper:
            # we can't perform the scroll because the canvas needs to be
            # updated
            gobject.idle_add(self._scrollToPosition, position)
        else:
            self._scrollToPosition(position)

    def _scrollToPosition(self, position):
        self.hadj.set_value(position)
        return False

## Project callbacks

    def _setProject(self):
        if self.project:
            self.timeline = self.project.timeline
            self._controls.timeline = self.timeline
            self._canvas.timeline = self.timeline
            self._canvas.zoomChanged()
            self.ruler.setProjectFrameRate(
                self.project.getSettings().videorate)
            self.ruler.zoomChanged()
            self._settingsChangedCb(self.project)

    project = receiver(_setProject)

    @handler(project, "settings-changed")
    def _settingsChangedCb(self, project):
        rate = self.project.getSettings().videorate
        self.rate = float(1 / rate)
        self.ruler.setProjectFrameRate(rate)

## Timeline callbacks

    def _setTimeline(self):
        if self.timeline:
            self._timelineSelectionChanged(self.timeline)
            self._timelineStartDurationChanged(self.timeline,
                                               self.timeline.duration)

        self._controls.timeline = self.timeline

    timeline = receiver(_setTimeline)

    @handler(timeline, "duration-changed")
    def _timelineStartDurationChanged(self, unused_timeline, duration):
        if self.shrink:
            self._prev_duration = duration
            self.ruler.setMaxDuration(duration + 60 * gst.SECOND)
            self._canvas.setMaxDuration(duration + 60 * gst.SECOND)
            self.ruler.setShadedDuration(duration)
        else:
            # only resize if new size is larger
            if duration > self._prev_duration:
                self._prev_duration = duration
                self.ruler.setMaxDuration(duration)
                self._canvas.setMaxDuration(duration)
                #self.ruler.setShadedDuration(duration)

    @handler(timeline, "selection-changed")
    def _timelineSelectionChanged(self, timeline):
        delete = False
        link = False
        unlink = False
        group = False
        ungroup = False
        timeline_objects = {}
        if timeline.selection:
            delete = True
            if len(timeline.selection) > 1:
                link = True
                group = True

            start = None
            duration = None
            for obj in self.timeline.selection:
                if obj.link:
                    unlink = True

                if len(obj.track_objects) > 1:
                    ungroup = True

                if start is not None and duration is not None:
                    if obj.start != start or obj.duration != duration:
                        group = False
                else:
                    start = obj.start
                    duration = obj.duration

        self.delete_action.set_sensitive(delete)
        self.link_action.set_sensitive(link)
        self.unlink_action.set_sensitive(unlink)
        self.group_action.set_sensitive(group)
        self.ungroup_action.set_sensitive(ungroup)

## ToolBar callbacks

    def hide(self):
        self.actiongroup.set_visible(False)
        gtk.Vbox.hide(self)

    def _zoomInCb(self, unused_action):
        Zoomable.zoomIn()

    def _zoomOutCb(self, unused_action):
        Zoomable.zoomOut()

    def deleteSelected(self, unused_action):
        if self.timeline:
            self.app.action_log.begin("delete clip")
            self.timeline.deleteSelection()
            self.app.action_log.commit()

    def unlinkSelected(self, unused_action):
        if self.timeline:
            self.timeline.unlinkSelection()

    def linkSelected(self, unused_action):
        if self.timeline:
            self.timeline.linkSelection()

    def ungroupSelected(self, unused_action):
        if self.timeline:
            self.timeline.ungroupSelection()

    def groupSelected(self, unused_action):
        if self.timeline:
            self.timeline.groupSelection()

    def toggleRazor(self, action):
        if action.props.active:
            self._canvas.activateRazor(action)
        else:
            self._canvas.deactivateRazor()
Example #12
0
class Preview(goocanvas.ItemSimple, goocanvas.Item, Zoomable):

    __gtype_name__ = 'Preview'

    def __init__(self, instance, element, height=46, **kwargs):
        super(Preview, self).__init__(**kwargs)
        Zoomable.__init__(self)
        self.app = instance
        self.height = float(height)
        self.element = element
        self.props.pointer_events = False
        # ghetto hack
        self.hadj = instance.gui.timeline.hadj

## properties

    def _get_height(self):
        return self._height

    def _set_height(self, value):
        self._height = value
        self.changed(True)
    height = gobject.property(_get_height, _set_height, type=float)

## element callbacks

    def _set_element(self):
        self.previewer = previewer.get_preview_for_object(self.app,
            self.element)
    element = receiver(setter=_set_element)

    @handler(element, "in-point-changed")
    @handler(element, "media-duration-changed")
    def _media_props_changed(self, obj, unused_start_duration):
        self.changed(True)

## previewer callbacks

    previewer = receiver()

    @handler(previewer, "update")
    def _update_preview(self, previewer, segment):
        # if segment is none we are not just drawing a new thumbnail, so we
        # should update bounds
        if segment == None:
            self.changed(True)
        else:
            self.changed(False)

## Zoomable interface overries

    def zoomChanged(self):
        self.changed(True)

## goocanvas item methods

    def do_simple_update(self, cr):
        cr.identity_matrix()
        if self.element.factory:
            border_width = self.previewer._spacing()
            self.bounds = goocanvas.Bounds(border_width, 4,
            max(0, Zoomable.nsToPixel(self.element.duration) -
                border_width), self.height)

    def do_simple_paint(self, cr, bounds):
        x1 = -self.hadj.get_value()
        cr.identity_matrix()
        if self.element.factory:
            self.previewer.render_cairo(cr, intersect(self.bounds, bounds),
            self.element, x1, self.bounds.y1)

    def do_simple_is_item_at(self, x, y, cr, pointer_event):
        return (between(0, x, self.nsToPixel(self.element.duration)) and
            between(0, y, self.height))
Example #13
0
class TimelineObject(View, goocanvas.Group, Zoomable):

    element = receiver()

    __HEIGHT__ = 50
    __NORMAL__ = 0x709fb899
    __SELECTED__ = 0xa6cee3AA

    class Controller(TimelineController):
        def click(self, pos):
            mode = 0
            if self._last_event.get_state() & gtk.gdk.SHIFT_MASK:
                mode = 1
            elif self._last_event.get_state() & gtk.gdk.CONTROL_MASK:
                mode = 2
            self._view.timeline.setSelectionToObj(self._view.element, mode)

    def __init__(self, element, composition, timeline):
        goocanvas.Group.__init__(self)
        View.__init__(self)
        Zoomable.__init__(self)

        self.element = element
        self.comp = composition
        self.timeline = timeline

        self.bg = goocanvas.Rect(height=self.__HEIGHT__,
                                 fill_color_rgba=self.__NORMAL__,
                                 line_width=0)

        self.content = Preview(self.element)
        self.name = goocanvas.Text(x=10,
                                   text=os.path.basename(
                                       unquote(element.factory.name)),
                                   font="Sans 9",
                                   fill_color_rgba=0x000000FF,
                                   alignment=pango.ALIGN_LEFT)

        self.start_handle = StartHandle(element,
                                        timeline,
                                        height=self.__HEIGHT__)
        self.end_handle = EndHandle(element, timeline, height=self.__HEIGHT__)

        for thing in (self.bg, self.content, self.start_handle,
                      self.end_handle, self.name):
            self.add_child(thing)

        if element:
            self.zoomChanged()
        self.normal()

    def zoomChanged(self):
        self._start_duration_cb(self.element, self.element.start,
                                self.element.duration)

    @handler(element, "start-duration-changed")
    def _start_duration_cb(self, obj, start, duration):
        self.set_simple_transform(self.nsToPixel(start), 0, 1, 0)
        width = self.nsToPixel(duration)
        w = width - self.end_handle.props.width
        self.name.props.clip_path = "M%g,%g h%g v%g h-%g z" % (
            0, 0, w, self.__HEIGHT__, w)
        self.bg.props.width = width
        # place end handle at appropriate distance
        self.end_handle.props.x = w

    @handler(element, "selected-changed")
    def _selected_changed(self, element):
        if element.selected:
            self.bg.props.fill_color_rgba = self.__SELECTED__
        else:
            self.bg.props.fill_color_rgba = self.__NORMAL__
Example #14
0
class Preview(goocanvas.ItemSimple, goocanvas.Item, Zoomable):

    __gtype_name__ = 'Preview'

    def __init__(self, element, height=50, **kwargs):
        super(Preview, self).__init__(**kwargs)
        Zoomable.__init__(self)
        self.height = float(height)
        self.element = element
        self.props.pointer_events = False

## properties

    def _get_height(self):
        return self._height

    def _set_height(self, value):
        self._height = value
        self.changed(True)

    height = gobject.property(_get_height, _set_height, type=float)

    ## element callbacks

    def _set_element(self):
        self.previewer = previewer.get_preview_for_object(self.element)

    element = receiver(setter=_set_element)

    @handler(element, "in-point-changed")
    @handler(element, "media-duration-changed")
    def _media_props_changed(self, obj, unused_start_duration):
        self.changed(True)

## previewer callbacks

    previewer = receiver()

    @handler(previewer, "update")
    def _update_preview(self, previewer, segment):
        self.changed(False)

## Zoomable interface overries

    def zoomChanged(self):
        self.changed(True)

## goocanvas item methods

    def do_simple_update(self, cr):
        cr.identity_matrix()
        if self.element.factory:
            self.bounds = goocanvas.Bounds(
                0, 0, Zoomable.nsToPixel(self.element.duration), self.height)

    def do_simple_paint(self, cr, bounds):
        cr.identity_matrix()
        if self.element.factory:
            self.previewer.render_cairo(cr, intersect(self.bounds, bounds),
                                        self.element, self.bounds.y1)

    def do_simple_is_item_at(self, x, y, cr, pointer_event):
        return (between(0, x, self.nsToPixel(self.element.duration))
                and between(0, y, self.height))
Example #15
0
class Timeline(gtk.Table, Loggable, Zoomable):

    # the screen width of the current unit
    unit_width = 10
    # specific levels of zoom, in (multiplier, unit) pairs which
    # from zoomed out to zoomed in


    def __init__(self, instance, ui_manager):
        gtk.Table.__init__(self, rows=2, columns=1, homogeneous=False)
        Loggable.__init__(self)
        Zoomable.__init__(self)
        self.log("Creating Timeline")

        self._updateZoom = True
        self.project = None
        self.ui_manager = ui_manager
        self.app = instance
        self._temp_objects = None
        self._factories = None
        self._finish_drag = False
        self._position = 0
        self._state = gst.STATE_NULL
        self._createUI()
        self._prev_duration = 0
        self.shrink = True
        self.rate = gst.Fraction(1,1)

    def _createUI(self):
        self.leftSizeGroup = gtk.SizeGroup(gtk.SIZE_GROUP_HORIZONTAL)
        self.props.row_spacing = 2
        self.props.column_spacing = 2
        self.hadj = gtk.Adjustment()
        self.vadj = gtk.Adjustment()

        # zooming slider
        self._zoomAdjustment = gtk.Adjustment()
        self._zoomAdjustment.set_value(Zoomable.getCurrentZoomLevel())
        self._zoomAdjustment.connect("value-changed",
            self._zoomAdjustmentChangedCb)
        self._zoomAdjustment.props.lower = 0
        self._zoomAdjustment.props.upper = Zoomable.zoom_steps
        zoomslider = gtk.HScale(self._zoomAdjustment)
        zoomslider.props.draw_value = False
        zoomslider.set_tooltip_text(_("Zoom Timeline"))
        self.attach(zoomslider, 0, 1, 0, 1, yoptions=0, xoptions=gtk.FILL)

        # controls for tracks and layers
        self._controls = TimelineControls()
        controlwindow = gtk.Viewport(None, self.vadj)
        controlwindow.add(self._controls)
        controlwindow.set_size_request(-1, 1)
        controlwindow.set_shadow_type(gtk.SHADOW_OUT)
        self.attach(controlwindow, 0, 1, 1, 2, xoptions=0)

        # timeline ruler
        self.ruler = ruler.ScaleRuler(self.app, self.hadj)
        self.ruler.set_size_request(0, 25)
        self.ruler.set_border_width(2)
        self.ruler.connect("key-press-event", self._keyPressEventCb)
        self.ruler.connect("size-allocate", self._rulerSizeAllocateCb)
        rulerframe = gtk.Frame()
        rulerframe.set_shadow_type(gtk.SHADOW_OUT)
        rulerframe.add(self.ruler)
        self.attach(rulerframe, 1, 2, 0, 1, yoptions=0)

        # proportional timeline
        self._canvas = TimelineCanvas(self.app)
        self._root_item = self._canvas.get_root_item()
        self.attach(self._canvas, 1, 2, 1, 2)

        # scrollbar
        self._hscrollbar = gtk.HScrollbar(self.hadj)
        self._vscrollbar = gtk.VScrollbar(self.vadj)
        self.attach(self._hscrollbar, 1, 2, 2, 3, yoptions=0)
        self.attach(self._vscrollbar, 2, 3, 1, 2, xoptions=0)
        self.hadj.connect("value-changed", self._updateScrollPosition)
        self.vadj.connect("value-changed", self._updateScrollPosition)

        # error infostub
        self.infostub = InfoStub()
        self.attach(self.infostub, 1, 2, 4, 5, yoptions=0)

        self.show_all()
        self.infostub.hide()

        # toolbar actions
        actions = (
            ("ZoomIn", gtk.STOCK_ZOOM_IN, None, "<Control>plus", ZOOM_IN,
                self._zoomInCb),
            ("ZoomOut", gtk.STOCK_ZOOM_OUT, None, "<Control>minus", ZOOM_OUT,
                self._zoomOutCb),

            # actions for adding additional accelerators
            ("ControlEqualAccel", gtk.STOCK_ZOOM_IN, None, "<Control>equal", ZOOM_IN,
                self._zoomInCb),
            ("ControlKPAddAccel", gtk.STOCK_ZOOM_IN, None, "<Control>KP_Add", ZOOM_IN,
                self._zoomInCb),
            ("ControlKPSubtractAccel", gtk.STOCK_ZOOM_OUT, None, "<Control>KP_Subtract", ZOOM_OUT,
                self._zoomOutCb),
        )

        selection_actions = (
            ("DeleteObj", gtk.STOCK_DELETE, None, "Delete", DELETE,
                self.deleteSelected),
            ("UnlinkObj", "pitivi-unlink", None, "<Shift><Control>L", UNLINK,
                self.unlinkSelected),
            ("LinkObj", "pitivi-link", None, "<Control>L", LINK,
                self.linkSelected),
            ("UngroupObj", "pitivi-ungroup", None, "<Shift><Control>G", UNGROUP,
                self.ungroupSelected),
            ("GroupObj", "pitivi-group", None, "<Control>G", GROUP,
                self.groupSelected),
        )

        self.playhead_actions = (
            ("Split", "pitivi-split", _("Split"), "S", SPLIT,
                self.split),
            ("Keyframe", "pitivi-keyframe", _("Add a keyframe"), "K", KEYFRAME,
                self.keyframe),
            ("Prevframe", "pitivi-prevframe", _("_Prevframe"), "E", PREVFRAME,
                self.prevframe),
            ("Nextframe", "pitivi-nextframe", _("_Nextframe"), "R", NEXTFRAME,
                self.nextframe),
        )

        actiongroup = gtk.ActionGroup("timelinepermanent")
        actiongroup.add_actions(actions)
        self.ui_manager.insert_action_group(actiongroup, 0)

        actiongroup = gtk.ActionGroup("timelineselection")
        actiongroup.add_actions(selection_actions)
        actiongroup.add_actions(self.playhead_actions)
        self.link_action = actiongroup.get_action("LinkObj")
        self.unlink_action = actiongroup.get_action("UnlinkObj")
        self.group_action = actiongroup.get_action("GroupObj")
        self.ungroup_action = actiongroup.get_action("UngroupObj")
        self.delete_action = actiongroup.get_action("DeleteObj")
        self.split_action = actiongroup.get_action("Split")
        self.keyframe_action = actiongroup.get_action("Keyframe")
        self.prevframe_action = actiongroup.get_action("Prevframe")
        self.nextframe_action = actiongroup.get_action("Nextframe")

        self.ui_manager.insert_action_group(actiongroup, -1)

        self.ui_manager.add_ui_from_string(ui)

        # drag and drop
        self.drag_dest_set(gtk.DEST_DEFAULT_MOTION,
            [dnd.FILESOURCE_TUPLE, dnd.EFFECT_TUPLE],
            gtk.gdk.ACTION_COPY)

        self.connect("drag-data-received", self._dragDataReceivedCb)
        self.connect("drag-leave", self._dragLeaveCb)
        self.connect("drag-drop", self._dragDropCb)
        self.connect("drag-motion", self._dragMotionCb)
        self._canvas.connect("button-press-event", self._buttonPress)
        self._canvas.connect("button-release-event", self._buttonRelease)
        self._canvas.connect("key-press-event", self._keyPressEventCb)


## Event callbacks

    def _keyPressEventCb(self, unused_widget, event):
        kv = event.keyval
        self.debug("kv:%r", kv)
        if kv not in [gtk.keysyms.Left, gtk.keysyms.Right]:
            return False
        mod = event.get_state()
        try:
            if mod & gtk.gdk.CONTROL_MASK:
                now = self.project.pipeline.getPosition()
                ltime, rtime = self.project.timeline.edges.closest(now)

            if kv == gtk.keysyms.Left:
                if mod & gtk.gdk.SHIFT_MASK:
                    self._seekRelative(-gst.SECOND)
                elif mod & gtk.gdk.CONTROL_MASK:
                    self._seeker.seek(ltime+1)
                else:
                    self._seekRelative(-long(self.rate * gst.SECOND))
            elif kv == gtk.keysyms.Right:
                if mod & gtk.gdk.SHIFT_MASK:
                    self._seekRelative(gst.SECOND)
                elif mod & gtk.gdk.CONTROL_MASK:
                    self._seeker.seek(rtime+1)
                else:
                    self._seekRelative(long(self.rate * gst.SECOND))
        finally:
            return True

    def _seekRelative(self, time):
        pipeline = self.project.pipeline
        seekvalue = max(0, min(pipeline.getPosition() + time,
            pipeline.getDuration()))
        self._seeker.seek(seekvalue)

    def _buttonPress(self, window, event):
        self.shrink = False

    def _buttonRelease(self, window, event):
        self.shrink = True
        self._timelineStartDurationChanged(self.timeline,
            self.timeline.duration)

## Drag and Drop callbacks

    def _dragMotionCb(self, unused, context, x, y, timestamp):
        self.warning("self._factories:%r, self._temp_objects:%r",
                     not not self._factories,
                     not not self._temp_objects)

        if self._factories is None:
            if  context.targets in DND_EFFECT_LIST:
                atom = gtk.gdk.atom_intern(dnd.EFFECT_TUPLE[0])
            else:
                atom = gtk.gdk.atom_intern(dnd.FILESOURCE_TUPLE[0])

            self.drag_get_data(context, atom, timestamp)
            self.drag_highlight()
        else:
            if  context.targets not in DND_EFFECT_LIST:
                if not self._temp_objects:
                    self.timeline.disableUpdates()
                    self._add_temp_source()
                    focus = self._temp_objects[0]
                    self._move_context = MoveContext(self.timeline,
                            focus, set(self._temp_objects[1:]))
                self._move_temp_source(self.hadj.props.value + x, y)
        return True

    def _dragLeaveCb(self, unused_layout, context, unused_tstamp):
        if self._temp_objects:
            try:
                for obj in self._temp_objects:
                    self.timeline.removeTimelineObject(obj, deep=True)
            finally:
                self._temp_objects = None

        self.drag_unhighlight()
        self.timeline.enableUpdates()

    def _dragDropCb(self, widget, context, x, y, timestamp):
        if  context.targets not in DND_EFFECT_LIST:
            self.app.action_log.begin("add clip")
            self.timeline.disableUpdates()

            self._add_temp_source()
            self.timeline.selection.setSelection(self._temp_objects, SELECT)
            focus = self._temp_objects[0]
            self._move_context = MoveContext(self.timeline,
                                             focus, set(self._temp_objects[1:]))
            self._move_temp_source(self.hadj.props.value + x, y)
            self._move_context.finish()
            self.app.action_log.commit()
            context.drop_finish(True, timestamp)
            self._factories = None
            self._temp_objects = None
            self.app.current.seeker.seek(self._position)

            return True
        elif context.targets in DND_EFFECT_LIST:
            if not self.timeline.timeline_objects:
                return False
            factory = self._factories[0]
            timeline_objs = self._getTimelineObjectUnderMouse(x, y, factory.getInputStreams()[0])
            if timeline_objs:
                self.app.action_log.begin("add effect")
                self.timeline.addEffectFactoryOnObject(factory,
                                               timeline_objects = timeline_objs)
                self.app.action_log.commit()
                self._factories = None
                self.app.current.seeker.seek(self._position)
                context.drop_finish(True, timestamp)

                self.timeline.selection.setSelection(timeline_objs, SELECT)

            return True

        return False

    def _dragDataReceivedCb(self, unused_layout, context, x, y,
        selection, targetType, timestamp):
        self.log("SimpleTimeline, targetType:%d, selection.data:%s" %
            (targetType, selection.data))
        # FIXME: let's have just one target type, call it
        # TYPE_PITIVI_OBJECTFACTORY.
        # TODO: handle uri targets by doign an import-add. This would look
        # something like this:
        # tell current project to import the uri
        # wait for source-added signal, meanwhile ignore dragMotion signals
        # when ready, add factories to the timeline.

        if targetType not in [dnd.TYPE_PITIVI_FILESOURCE, dnd.TYPE_PITIVI_EFFECT]:
            context.finish(False, False, timestamp)
            return

        if targetType == dnd.TYPE_PITIVI_FILESOURCE:
            uris = selection.data.split("\n")
            self._factories = [self.project.sources.getUri(uri) for uri in uris]
        else:
            if not self.timeline.timeline_objects:
                return False
            self._factories = [self.app.effects.getFactoryFromName(selection.data)]

        context.drag_status(gtk.gdk.ACTION_COPY, timestamp)
        return True

    def _getTimelineObjectUnderMouse(self, x, y, stream):
        timeline_objs = []
        items_in_area = self._canvas.getItemsInArea(x, y-15, x+1, y-30)
        tracks = [obj for obj in items_in_area[0]]
        track_objects = [obj for obj in items_in_area[1]]
        for track_object in track_objects:
            if (type(stream) == type(track_object.stream)):
                timeline_objs.append(track_object.timeline_object)

        return timeline_objs

    def _add_temp_source(self):
        self._temp_objects = [self.timeline.addSourceFactory(factory)
            for factory in self._factories]

    def _move_temp_source(self, x, y):
        x1, y1, x2, y2 = self._controls.get_allocation()
        offset = 10 + (x2 - x1)
        x, y = self._canvas.convert_from_pixels(x - offset, y)
        priority = int((y // (LAYER_HEIGHT_EXPANDED + LAYER_SPACING)))
        delta = Zoomable.pixelToNs(x)
        self._move_context.editTo(delta, priority)

## Zooming and Scrolling

    def _updateScrollPosition(self, adjustment):
        self._root_item.set_simple_transform( -self.hadj.get_value(), 
            -self.vadj.get_value(), 1.0, 0)

    def _zoomAdjustmentChangedCb(self, adjustment):
        # GTK crack
        self._updateZoom = False
        Zoomable.setZoomLevel(int(adjustment.get_value()))
        self._updateZoom = True

    def zoomChanged(self):
        self._canvas.props.redraw_when_scrolled = True
        if self._updateZoom:
            self._zoomAdjustment.set_value(self.getCurrentZoomLevel())
        self.ruler.queue_resize()
        self.ruler.queue_draw()

    def timelinePositionChanged(self, position):
        self._position = position
        self.ruler.timelinePositionChanged(position)
        self._canvas.timelinePositionChanged(position)
        if self._state == gst.STATE_PLAYING:
            self.scrollToPlayhead()

    def stateChanged(self, state):
        self._state = state

    def scrollToPlayhead(self):
        """
        If the current position is out of the view bouds, then scroll
        as close to the center of the view as possible or as close as the
        timeline canvas allows.
        """
        page_size = self.hadj.get_page_size()

        new_pos = Zoomable.nsToPixel(self._position)
        scroll_pos = self.hadj.get_value()
        if (new_pos > scroll_pos + page_size) or (new_pos < scroll_pos):
            self.scrollToPosition(min(new_pos - page_size / 2, self.hadj.upper - page_size - 1))
        return False

    def scrollToPosition(self, position):
        if position > self.hadj.upper:
            # we can't perform the scroll because the canvas needs to be
            # updated
            gobject.idle_add(self._scrollToPosition, position)
        else:
            self._scrollToPosition(position)

    def _scrollToPosition(self, position):
        self.hadj.set_value(position)
        return False

    def _rulerSizeAllocateCb(self, ruler, allocation):
        self._canvas.props.redraw_when_scrolled = False

## Project callbacks

    def _setProject(self):
        if self.project:
            self.timeline = self.project.timeline
            self._controls.timeline = self.timeline
            self._canvas.timeline = self.timeline
            self._canvas.zoomChanged()
            self.ruler.setProjectFrameRate(self.project.getSettings().videorate)
            self.ruler.zoomChanged()
            self._settingsChangedCb(self.project, None, self.project.getSettings())
            self._seeker = self.project.seeker

    project = receiver(_setProject)

    @handler(project, "settings-changed")
    def _settingsChangedCb(self, project, old, new):
        rate = new.videorate
        self.rate = float(1 / rate)
        self.ruler.setProjectFrameRate(rate)

## Timeline callbacks

    def _setTimeline(self):
        if self.timeline:
            self._timelineSelectionChanged(self.timeline)
            self._timelineStartDurationChanged(self.timeline,
                self.timeline.duration)

        self._controls.timeline = self.timeline

    timeline = receiver(_setTimeline)

    @handler(timeline, "duration-changed")
    def _timelineStartDurationChanged(self, unused_timeline, duration):
        if self.shrink:
            self._prev_duration = duration
            self.ruler.setMaxDuration(duration + 60 * gst.SECOND)
            self._canvas.setMaxDuration(duration + 60 * gst.SECOND)
            self.ruler.setShadedDuration(duration)
        else:
            # only resize if new size is larger
            if duration > self._prev_duration:
                self._prev_duration = duration
                self.ruler.setMaxDuration(duration)
                self._canvas.setMaxDuration(duration)
                #self.ruler.setShadedDuration(duration)

    @handler(timeline, "selection-changed")
    def _timelineSelectionChanged(self, timeline):
        delete = False
        link = False
        unlink = False
        group = False
        ungroup = False
        split = False
        keyframe = False
        timeline_objects = {}
        if timeline.selection:
            delete = True
            if len(timeline.selection) > 1:
                link = True
                group = True

            start = None
            duration = None
            for obj in self.timeline.selection:
                if obj.link:
                    link = False
                    unlink = True

                if len(obj.track_objects) > 1:
                    ungroup = True

                if start is not None and duration is not None:
                    if obj.start != start or obj.duration != duration:
                        group = False
                else:
                    start = obj.start
                    duration = obj.duration

            split = True
            keyframe = True

        self.delete_action.set_sensitive(delete)
        self.link_action.set_sensitive(link)
        self.unlink_action.set_sensitive(unlink)
        self.group_action.set_sensitive(group)
        self.ungroup_action.set_sensitive(ungroup)
        self.split_action.set_sensitive(split)
        self.keyframe_action.set_sensitive(keyframe)


## ToolBar callbacks

    def hide(self):
        self.actiongroup.set_visible(False)
        gtk.Vbox.hide(self)

    def _zoomInCb(self, unused_action):
        Zoomable.zoomIn()

    def _zoomOutCb(self, unused_action):
        Zoomable.zoomOut()

    def deleteSelected(self, unused_action):
        if self.timeline:
            self.app.action_log.begin("delete clip")
            self.timeline.deleteSelection()
            self.app.action_log.commit()

    def unlinkSelected(self, unused_action):
        if self.timeline:
            self.timeline.unlinkSelection()

    def linkSelected(self, unused_action):
        if self.timeline:
            self.timeline.linkSelection()

    def ungroupSelected(self, unused_action):
        if self.timeline:
            self.app.action_log.begin("ungroup")
            self.timeline.ungroupSelection()
            self.app.action_log.commit()

    def groupSelected(self, unused_action):
        if self.timeline:
            self.timeline.groupSelection()

    def split(self, action):
        self.app.action_log.begin("split")
        self.timeline.disableUpdates()
        self.timeline.split(self._position)
        self.timeline.enableUpdates()
        self.app.action_log.commit()
        # work-around for 603149
        self.project.seeker.seek(self._position)

    def keyframe(self, action):
        timeline_position = self._position
        selected = self.timeline.selection.getSelectedTrackObjs()
        
        for obj in selected:
            keyframe_exists = False

            position_in_obj = (timeline_position - obj.start) + obj.in_point
            interpolators = obj.getInterpolators()
            for value in interpolators:
                interpolator = obj.getInterpolator(value)
                keyframes = interpolator.getInteriorKeyframes()
                for kf in keyframes:
                    if kf.getTime() == position_in_obj:
                        keyframe_exists = True
                        self.app.action_log.begin("remove volume point")
                        interpolator.removeKeyframe(kf)
                        self.app.action_log.commit()
                if keyframe_exists == False:
                    self.app.action_log.begin("add volume point")
                    interpolator.newKeyframe(position_in_obj)
                    self.app.action_log.commit()

    def prevframe(self, action):
        timeline_position = self._position

        prev_kf = self.timeline.getPrevKeyframe(timeline_position)
        if prev_kf != None:
            self._seeker.seek(prev_kf)
            self.scrollToPlayhead()

    def nextframe(self, action):
        timeline_position = self._position

        next_kf = self.timeline.getNextKeyframe(timeline_position)
        if next_kf:
            self._seeker.seek(next_kf)
            self.scrollToPlayhead()
Example #16
0
class Controller(object):
    """A controller which implements drag-and-drop bahavior on connected view
    objects. Subclasses may override the drag_start, drag_end, pos, and
    set_pos methods"""

    # note we SHOULD be using the gtk function for this, but it doesn't appear
    # to be exposed in pygtk
    __DRAG_THRESHOLD__ = Point(0, 0)

    _view = receiver()

    _dragging = None
    _canvas = None
    _cursor = None
    _ptr_within = False
    _last_click = None
    _initial = None
    _mousedown = None
    _last_event = None
    _pending_drag_start = None
    _pending_drag_end = False
    _shift_down = False
    _control_down = False
    _handle_enter_leave = True
    _handle_mouse_up_down = True
    _handle_motion_notify = True

    def __init__(self, view=None):
        object.__init__(self)
        self._view = view

## convenience functions

    def from_event(self, event):
        """returns the coordinates of an event"""
        return Point(*self._canvas.convert_from_pixels(event.x, event.y))

    def from_item_event(self, item, event):
        return Point(*self._canvas.convert_from_item_space(
            item, *self.from_event(event)))

    def to_item_space(self, item, point):
        return Point(*self._canvas.convert_to_item_space(item, *point))

    def pos(self, item):
        bounds = item.get_bounds()
        return Point(bounds.x1, bounds.y1)

## signal handlers

    @handler(_view, "enter_notify_event")
    def enter_notify_event(self, item, target, event):
        self._event_common(item, target, event)
        self._canvas.grab_focus(item)
        if self._cursor and item is target:
            event.window.set_cursor(self._cursor)
        if not self._dragging:
            self.enter(item, target)
        self._ptr_within = True
        return self._handle_enter_leave or self._dragging

    @handler(_view, "leave_notify_event")
    def leave_notify_event(self, item, target, event):
        self._event_common(item, target, event)
        self._canvas.keyboard_ungrab(item, event.time)
        self._ptr_within = False
        if not self._dragging:
            self.leave(item, target)
            event.window.set_cursor(ARROW)
        return self._handle_enter_leave or self._dragging

    @handler(_view, "button_press_event")
    def button_press_event(self, item, target, event):
        self._event_common(item, target, event)
        if not self._canvas:
            self._canvas = item.get_canvas()
        self._mousedown = self.pos(item) - self.transform(
            self.from_item_event(item, event))
        self._dragging = target
        self._initial = self.pos(target)
        self._pending_drag_start = (item, target, event)
        return self._handle_mouse_up_down

    @handler(_view, "motion_notify_event")
    def motion_notify_event(self, item, target, event):
        self._event_common(item, target, event)
        if self._dragging:
            if self._pending_drag_start is not None:
                pending_drag_start, self._pending_drag_start = \
                        self._pending_drag_start, None
                self._pending_drag_end = True
                self._drag_start(*pending_drag_start)

            self.set_pos(
                self._dragging,
                self.transform(self._mousedown +
                               self.from_item_event(item, event)))
            return self._handle_motion_notify
        else:
            self.hover(item, target, event)
        return False

    @handler(_view, "button_release_event")
    def button_release_event(self, item, target, event):
        self._event_common(item, target, event)
        self._drag_end(item, self._dragging, event)
        self._dragging = None
        return self._handle_mouse_up_down

    @handler(_view, "key_press_event")
    def key_press_event(self, item, target, event):
        self._event_common(item, target, event)
        kv = event.keyval
        if kv in (gtk.keysyms.Shift_L, gtk.keysyms.Shift_R):
            self._shift_down = True
        elif kv in (gtk.keysyms.Control_L, gtk.keysyms.Control_R):
            self._control_down = True
        return self.key_press(kv)

    @handler(_view, "key_release_event")
    def key_release_event(self, item, target, event):
        self._event_common(item, target, event)
        kv = event.keyval
        if kv in (gtk.keysyms.Shift_L, gtk.keysyms.Shift_R):
            self._shift_down = False
        elif kv in (gtk.keysyms.Control_L, gtk.keysyms.Control_R):
            self._control_down = False
        return self.key_release(kv)

## internal callbacks

    def _event_common(self, item, target, event):
        if not self._canvas:
            self._canvas = item.get_canvas()
        self._last_event = event
        s = event.get_state()
        self._shift_down = s & gtk.gdk.SHIFT_MASK
        self._control_down = s & gtk.gdk.CONTROL_MASK

    def _drag_start(self, item, target, event):
        self.drag_start(item, target, event)

    def _drag_end(self, item, target, event):
        self._pending_drag_start = None
        pending_drag_end, self._pending_drag_end = self._pending_drag_end, False
        if pending_drag_end:
            self.drag_end(item, target, event)

        if self._ptr_within and self._drag_threshold():
            point = self.from_item_event(item, event)
            if self._last_click and (event.time - self._last_click < 400):
                self.double_click(point)
            else:
                self.click(point)
            self._last_click = event.time
            event.window.set_cursor(self._cursor)
        else:
            event.window.set_cursor(ARROW)

    def _drag_threshold(self):
        last = self.pos(self._dragging)
        difference = abs(self._initial - last)
        if abs(self._initial - last) > self.__DRAG_THRESHOLD__:
            return False
        return True

## protected interface for subclasses

    def click(self, pos):
        pass

    def double_click(self, pos):
        pass

    def drag_start(self, item, target, event):
        pass

    def drag_end(self, item, target, event):
        pass

    def set_pos(self, obj, pos):
        obj.props.x, obj.props.y = pos

    def transform(self, pos):
        return pos

    def enter(self, item, target):
        pass

    def leave(self, item, target):
        pass

    def key_press(self, keyval):
        pass

    def key_release(self, keyval):
        pass

    def hover(self, item, target, event):
        pass
Example #17
0
class TimelineCanvas(goocanvas.Canvas, Zoomable, Loggable):

    __gsignals__ = {
        "scroll-event":"override"
        }

    _tracks = None

    def __init__(self, instance, timeline=None):
        goocanvas.Canvas.__init__(self)
        Zoomable.__init__(self)
        Loggable.__init__(self)
        self.app = instance
        self._selected_sources = []
        self._tracks = []
        self._height = 0
        self._position = 0

        self._block_size_request = False
        self.props.integer_layout = True
        self.props.automatic_bounds = False

        self._createUI()
        self.timeline = timeline
        self.settings = instance.settings

    def _createUI(self):
        self._cursor = ARROW
        root = self.get_root_item()
        self.tracks = goocanvas.Group()
        root.add_child(self.tracks)
        self._marquee = goocanvas.Rect(
            stroke_pattern = unpack_cairo_pattern(0x33CCFF66),
            fill_pattern = unpack_cairo_pattern(0x33CCFF66),
            visibility = goocanvas.ITEM_INVISIBLE)
        self._razor = goocanvas.Rect(
            line_width=0,
            fill_color="orange",
            width=1,
            visibility=goocanvas.ITEM_INVISIBLE)
        root.add_child(self._marquee)
        root.add_child(self._razor)
        root.connect("motion-notify-event", self._selectionDrag)
        root.connect("button-press-event", self._selectionStart)
        root.connect("button-release-event", self._selectionEnd)
        height = (LAYER_HEIGHT_EXPANDED + TRACK_SPACING + LAYER_SPACING) * 2
        # add some padding for the horizontal scrollbar
        height += 21
        self.set_size_request(-1, height)

    def from_event(self, event):
        return Point(*self.convert_from_pixels(event.x, event.y))

    def setExpanded(self, track_object, expanded):
        track_ui = None
        for track in self._tracks:
            if track.track == track_object:
                track_ui = track
                break

        track_ui.setExpanded(expanded)

    def do_scroll_event(self, event):
        if event.state & gtk.gdk.SHIFT_MASK:
            # shift + scroll => vertical (up/down) scroll
            if event.direction == gtk.gdk.SCROLL_LEFT:
                event.direction = gtk.gdk.SCROLL_UP
            elif event.direction == gtk.gdk.SCROLL_RIGHT:
                event.direction = gtk.gdk.SCROLL_DOWN
            event.state &= ~gtk.gdk.SHIFT_MASK
        elif event.state & gtk.gdk.CONTROL_MASK:
            # zoom + scroll => zooming (up: zoom in)
            if event.direction == gtk.gdk.SCROLL_UP:
                Zoomable.zoomIn()
                return True
            elif event.direction == gtk.gdk.SCROLL_DOWN:
                Zoomable.zoomOut()
                return True
            return False
        else:
            if event.direction == gtk.gdk.SCROLL_UP:
                event.direction = gtk.gdk.SCROLL_LEFT
            elif event.direction == gtk.gdk.SCROLL_DOWN:
                event.direction = gtk.gdk.SCROLL_RIGHT
        return goocanvas.Canvas.do_scroll_event(self, event)

## sets the cursor as appropriate

    def _mouseEnterCb(self, unused_item, unused_target, event):
        event.window.set_cursor(self._cursor)
        return True

## implements selection marquee

    _selecting = False
    _mousedown = None
    _marquee = None

    def _normalize(self, p1, p2):
        w, h = p2 - p1
        x, y = p1
        if w < 0:
            w = abs(w)
            x -= w
        if h < 0:
            h = abs(h)
            y -= h
        return (x, y), (w, h)


    def _selectionDrag(self, item, target, event):
        if self._selecting:
            cur = self.from_event(event)
            pos, size = self._normalize(self._mousedown, cur)
            m = self._marquee
            m.props.x, m.props.y = pos
            m.props.width, m.props.height = size
            return True
        return False

    def _selectionStart(self, item, target, event):
        self._selecting = True
        self._marquee.props.visibility = goocanvas.ITEM_VISIBLE
        self._mousedown = self.from_event(event)
        self._marquee.props.width = 0
        self._marquee.props.height = 0
        self.pointer_grab(self.get_root_item(), gtk.gdk.POINTER_MOTION_MASK |
            gtk.gdk.BUTTON_RELEASE_MASK, self._cursor, event.time)
        return True

    def _selectionEnd(self, item, target, event):
        self.pointer_ungrab(self.get_root_item(), event.time)
        self._selecting = False
        self._marquee.props.visibility = goocanvas.ITEM_INVISIBLE
        mode = 0
        if event.get_state() & gtk.gdk.SHIFT_MASK:
            mode = 1
        if event.get_state() & gtk.gdk.CONTROL_MASK:
            mode = 2
        self.timeline.setSelectionTo(self._objectsUnderMarquee(), mode)
        return True

    def _objectsUnderMarquee(self):
        items = self.get_items_in_area(self._marquee.get_bounds(), True, True,
            True)
        if items:
            return set((item.element for item in items if isinstance(item,
                TrackObject)))
        return set()

## Razor Tool Implementation

    def activateRazor(self, action):
        self._razor_sigid = self.connect("button_press_event",
            self._razorClickedCb)
        self._razor_release_sigid = self.connect("button_release_event",
            self._razorReleasedCb)
        self._razor_motion_sigid = self.connect("motion_notify_event",
            self._razorMovedCb)
        self._razor.props.visibility = goocanvas.ITEM_VISIBLE
        self._action = action
        return True

    def deactivateRazor(self):
        self.disconnect(self._razor_sigid)
        self.disconnect(self._razor_motion_sigid)
        self.disconnect(self._razor_release_sigid)
        self._razor.props.visibility = goocanvas.ITEM_INVISIBLE

    def _razorMovedCb(self, canvas, event):
        def snap(x):
            pos = self.nsToPixel(self._position)
            if abs(x - pos) <= self.settings.edgeSnapDeadband:
                return pos
            return x
        x, y = self.convert_from_pixels(event.x, event.y)
        self._razor.props.x = snap(self.nsToPixel(self.pixelToNs(x)))
        return True

    def _razorReleasedCb(self, unused_canvas, event):
        self._action.props.active = False

        x, y = self.convert_from_pixels(event.x, event.y)
        bounds = goocanvas.Bounds(x, y, x, y)
        items = self.get_items_in_area(bounds, True, True, True)
        if items:
            for item in items:
                if isinstance(item, TrackObject):
                    self.app.action_log.begin("split object")
                    item.element.split(self._snapToPlayhead(self.pixelToNs(x)))
                    self.app.action_log.commit()

        return True

    def _razorClickedCb(self, unused_canvas, unused_event):
        return True

    def _snapToPlayhead(self, time):
        thresh = self.pixelToNs(self.settings.edgeSnapDeadband)
        if abs(time - self._position) <= thresh:
            return self._position
        return time

    max_duration = 0

    def setMaxDuration(self, duration):
        self.max_duration = duration
        self._request_size()

    def _request_size(self):
        w = Zoomable.nsToPixel(self.max_duration)
        self.set_bounds(0, 0, w, self._height)
        self._razor.props.height = self._height
        self.get_root_item().changed(True)

## Zoomable Override

    def zoomChanged(self):
        if self.timeline:
            self.timeline.dead_band = self.pixelToNs(
                self.settings.edgeSnapDeadband)
            self._request_size()


## settings callbacks

    def _setSettings(self):
        self.zoomChanged()

    settings = receiver(_setSettings)

    @handler(settings, "edgeSnapDeadbandChanged")
    def _edgeSnapDeadbandChangedCb(self, settings):
        self.zoomChanged()

## Timeline callbacks

    def _set_timeline(self):
        while self._tracks:
            self._trackRemoved(None, 0)
        if self.timeline:
            for track in self.timeline.tracks:
                self._trackAdded(None, track)
        self.zoomChanged()

    timeline = receiver(_set_timeline)

    @handler(timeline, "track-added")
    def _trackAdded(self, timeline, track):
        track = Track(self.app, track, self.timeline)
        self._tracks.append(track)
        track.set_canvas(self)
        self.tracks.add_child(track)
        self.regroupTracks()

    @handler(timeline, "track-removed")
    def _trackRemoved(self, unused_timeline, position):
        track = self._tracks[position]
        del self._tracks[position]
        track.remove()
        self.regroupTracks()

    def regroupTracks(self):
        height = 0
        for i, track in enumerate(self._tracks):
            track.set_simple_transform(0, height, 1, 0)
            height += track.height + TRACK_SPACING
        self._height = height
        self._request_size()
Example #18
0
class TimelineCanvas(goocanvas.Canvas, Zoomable, Loggable):

    __gtype_name__ = 'TimelineCanvas'
    __gsignals__ = {
        "expose-event": "override",
    }

    _tracks = None

    def __init__(self, instance, timeline=None):
        goocanvas.Canvas.__init__(self)
        Zoomable.__init__(self)
        Loggable.__init__(self)
        self.app = instance
        self._selected_sources = []
        self._tracks = []
        self.height = 0
        self._position = 0

        self._block_size_request = False
        self.props.integer_layout = True
        self.props.automatic_bounds = False
        self.props.clear_background = False
        self.get_root_item().set_simple_transform(0, 2.0, 1.0, 0)

        self._createUI()
        self.timeline = timeline
        self.settings = instance.settings

    def _createUI(self):
        self._cursor = ARROW
        root = self.get_root_item()
        self.tracks = goocanvas.Group()
        self.tracks.set_simple_transform(0, KW_LABEL_Y_OVERFLOW, 1.0, 0)
        root.add_child(self.tracks)
        self._marquee = goocanvas.Rect(
            parent=root,
            stroke_pattern=unpack_cairo_pattern(0x33CCFF66),
            fill_pattern=unpack_cairo_pattern(0x33CCFF66),
            visibility=goocanvas.ITEM_INVISIBLE)
        self._playhead = goocanvas.Rect(y=-10,
                                        parent=root,
                                        line_width=1,
                                        fill_color_rgba=0x000000FF,
                                        stroke_color_rgba=0xFFFFFFFF,
                                        width=3)
        self._playhead_controller = PlayheadController(self._playhead)
        self.connect("size-allocate", self._size_allocate_cb)
        root.connect("motion-notify-event", self._selectionDrag)
        root.connect("button-press-event", self._selectionStart)
        root.connect("button-release-event", self._selectionEnd)
        self.height = (LAYER_HEIGHT_EXPANDED + TRACK_SPACING +
                       LAYER_SPACING) * 2
        # add some padding for the horizontal scrollbar
        self.height += 21
        self.set_size_request(-1, self.height)

    def from_event(self, event):
        x, y = event.x, event.y
        x += self.app.gui.timeline.hadj.get_value()
        return Point(*self.convert_from_pixels(x, y))

    def setExpanded(self, track_object, expanded):
        track_ui = None
        for track in self._tracks:
            if track.track == track_object:
                track_ui = track
                break

        track_ui.setExpanded(expanded)

## sets the cursor as appropriate

    def _mouseEnterCb(self, unused_item, unused_target, event):
        event.window.set_cursor(self._cursor)
        return True

    def do_expose_event(self, event):
        allocation = self.get_allocation()
        width = allocation.width
        height = allocation.height
        # draw the canvas background
        # we must have props.clear_background set to False

        self.style.apply_default_background(event.window, True,
                                            gtk.STATE_ACTIVE, event.area,
                                            event.area.x, event.area.y,
                                            event.area.width,
                                            event.area.height)

        goocanvas.Canvas.do_expose_event(self, event)

## implements selection marquee

    _selecting = False
    _mousedown = None
    _marquee = None
    _got_motion_notify = False

    def getItemsInArea(self, x1, y1, x2, y2):
        '''
        Permits to get the Non UI L{Track}/L{TrackObject} in a list of set
        corresponding to the L{Track}/L{TrackObject} which are in the are

        @param x1: The horizontal coordinate of the up left corner of the area
        @type x1: An C{int}
        @param y1: The vertical coordinate of the up left corner of the area
        @type y1: An C{int}
        @param x2: The horizontal coordinate of the down right corner of the
                   area
        @type x2: An C{int}
        @param x2: The vertical coordinate of the down right corner of the area
        @type x2: An C{int}

        @returns: A list of L{Track}, L{TrackObject} tuples
        '''
        items = self.get_items_in_area(goocanvas.Bounds(x1, y1, x2, y2), True,
                                       True, True)
        if not items:
            return [], []

        tracks = set()
        track_objects = set()

        for item in items:
            if isinstance(item, Track):
                tracks.add(item.track)
            elif isinstance(item, TrackObject):
                track_objects.add(item.element)

        return tracks, track_objects

    def _normalize(self, p1, p2, adjust=0):
        w, h = p2 - p1
        x, y = p1
        if w - adjust < 0:
            w = abs(w - adjust)
            x -= w
        else:
            w -= adjust
        if h < 0:
            h = abs(h)
            y -= h
        return (x, y), (w, h)

    def _selectionDrag(self, item, target, event):
        if self._selecting:
            self._got_motion_notify = True
            cur = self.from_event(event)
            pos, size = self._normalize(self._mousedown, cur,
                                        self.app.gui.timeline.hadj.get_value())
            self._marquee.props.x, self._marquee.props.y = pos
            self._marquee.props.width, self._marquee.props.height = size
            return True
        return False

    def _selectionStart(self, item, target, event):
        self._selecting = True
        self._marquee.props.visibility = goocanvas.ITEM_VISIBLE
        self._mousedown = self.from_event(event)
        self._marquee.props.width = 0
        self._marquee.props.height = 0
        self.pointer_grab(
            self.get_root_item(),
            gtk.gdk.POINTER_MOTION_MASK | gtk.gdk.BUTTON_RELEASE_MASK,
            self._cursor, event.time)
        return True

    def _selectionEnd(self, item, target, event):
        seeker = self.app.current.seeker
        self.pointer_ungrab(self.get_root_item(), event.time)
        self._selecting = False
        self._marquee.props.visibility = goocanvas.ITEM_INVISIBLE
        if not self._got_motion_notify:
            self.timeline.setSelectionTo(set(), 0)
            seeker.seek(Zoomable.pixelToNs(event.x))
        else:
            self._got_motion_notify = False
            mode = 0
            if event.get_state() & gtk.gdk.SHIFT_MASK:
                mode = 1
            if event.get_state() & gtk.gdk.CONTROL_MASK:
                mode = 2
            self.timeline.setSelectionTo(self._objectsUnderMarquee(), mode)
        return True

    def _objectsUnderMarquee(self):
        items = self.get_items_in_area(self._marquee.get_bounds(), True, True,
                                       True)
        if items:
            return set((item.element for item in items
                        if isinstance(item, TrackObject) and item.bg in items))
        return set()

## playhead implementation

    position = 0

    def timelinePositionChanged(self, position):
        self.position = position
        self._playhead.props.x = self.nsToPixel(position)

    max_duration = 0

    def setMaxDuration(self, duration):
        self.max_duration = duration
        self._request_size()

    def _request_size(self):
        alloc = self.get_allocation()
        self.set_bounds(0, 0, alloc.width, alloc.height)
        self._playhead.props.height = (self.height + SPACING)

    def _size_allocate_cb(self, widget, allocation):
        self._request_size()

    def zoomChanged(self):
        self.queue_draw()
        if self.timeline:
            self.timeline.dead_band = self.pixelToNs(
                self.settings.edgeSnapDeadband)
            self.timelinePositionChanged(self.position)

## settings callbacks

    def _setSettings(self):
        self.zoomChanged()

    settings = receiver(_setSettings)

    @handler(settings, "edgeSnapDeadbandChanged")
    def _edgeSnapDeadbandChangedCb(self, settings):
        self.zoomChanged()

## Timeline callbacks

    def _set_timeline(self):
        while self._tracks:
            self._trackRemoved(None, 0)
        if self.timeline:
            for track in self.timeline.tracks:
                self._trackAdded(None, track)
        self.zoomChanged()

    timeline = receiver(_set_timeline)

    @handler(timeline, "track-added")
    def _trackAdded(self, timeline, track):
        track = Track(self.app, track, self.timeline)
        self._tracks.append(track)
        track.set_canvas(self)
        self.tracks.add_child(track)
        self.regroupTracks()

    @handler(timeline, "track-removed")
    def _trackRemoved(self, unused_timeline, position):
        track = self._tracks[position]
        del self._tracks[position]
        track.remove()
        self.regroupTracks()

    def regroupTracks(self):
        height = 0
        for i, track in enumerate(self._tracks):
            track.set_simple_transform(0, height, 1, 0)
            height += track.height + TRACK_SPACING
        self.height = height
        self._request_size()