コード例 #1
0
ファイル: ruler.py プロジェクト: jojva/pitivi
    def __init__(self, instance, hadj):
        Gtk.DrawingArea.__init__(self)
        Zoomable.__init__(self)
        Loggable.__init__(self)
        self.log("Creating new ScaleRuler")
        self.app = instance
        self._seeker = Seeker()
        self.hadj = hadj
        hadj.connect("value-changed", self._hadjValueChangedCb)
        self.add_events(Gdk.EventMask.POINTER_MOTION_MASK |
            Gdk.EventMask.BUTTON_PRESS_MASK | Gdk.EventMask.BUTTON_RELEASE_MASK |
            Gdk.EventMask.SCROLL_MASK)

        self.pixbuf = None

        # all values are in pixels
        self.pixbuf_offset = 0
        self.pixbuf_offset_painted = 0
        # This is the number of width we allocate for the pixbuf
        self.pixbuf_multiples = 4

        self.position = 0  # In nanoseconds
        self.pressed = False
        self.min_frame_spacing = 5.0
        self.frame_height = 5.0
        self.frame_rate = Gst.Fraction(1 / 1)
        self.ns_per_frame = float(1 / self.frame_rate) * Gst.SECOND
        self.connect('draw', self.drawCb)
        self.connect('configure-event', self.configureEventCb)
        self.callback_id = None
        self.callback_id_scroll = None
コード例 #2
0
ファイル: viewer.py プロジェクト: jojva/pitivi
    def setPipeline(self, pipeline, position=None):
        """
        Set the Viewer to the given Pipeline.

        Properly switches the currently set action to that new Pipeline.

        @param pipeline: The Pipeline to switch to.
        @type pipeline: L{Pipeline}.
        @param position: Optional position to seek to initially.
        """
        self.debug("self.pipeline:%r", self.pipeline)

        self.seeker = Seeker()
        self._disconnectFromPipeline()
        if self.pipeline:
            self.pipeline.set_state(Gst.State.NULL)

        self.pipeline = pipeline
        if self.pipeline:
            self.pipeline.pause()
            self.seeker.seek(position)

            self.pipeline.connect("state-change", self._pipelineStateChangedCb)
            self.pipeline.connect("position", self._positionCb)
            self.pipeline.connect("duration-changed", self._durationChangedCb)

        self.sink = pipeline.video_overlay
        self._switch_output_window()
        self._setUiActive()
コード例 #3
0
    def __init__(self, settings=None, realizedCb=None):
        # Prevent black frames and flickering while resizing or changing focus:
        # The aspect ratio gets overridden by setDisplayAspectRatio.
        Gtk.AspectFrame.__init__(self,
                                 xalign=0.5,
                                 yalign=1.0,
                                 ratio=4.0 / 3.0,
                                 obey_child=False)
        Loggable.__init__(self)

        self.drawing_area = Gtk.DrawingArea()
        self.drawing_area.set_double_buffered(False)
        self.drawing_area.connect("draw", self._drawCb, None)
        # We keep the ViewerWidget hidden initially, or the desktop wallpaper
        # would show through the non-double-buffered widget!
        if realizedCb:
            self.drawing_area.connect("realize", realizedCb, self)
        self.add(self.drawing_area)

        self.drawing_area.show()

        self.seeker = Seeker()
        self.settings = settings
        self.box = None
        self.stored = False
        self.area = None
        self.zoom = 1.0
        self.sink = None
        self.pixbuf = None
        self.pipeline = None
        self.transformation_properties = None
コード例 #4
0
    def __init__(self, instance, uimap):
        Loggable.__init__(self)
        Signallable.__init__(self)
        self.app = instance
        self.bt = {}
        self.settings = {}
        self.source = None
        self.created = False
        self.seeker = Seeker()

        #Drag attributes
        self._drag_events = []
        self._signals_connected = False

        self._createUI()
        self.textbuffer = Gtk.TextBuffer()
        self.pangobuffer = InteractivePangoBuffer()
        self.textarea.set_buffer(self.pangobuffer)

        self.textbuffer.connect("changed", self._updateSourceText)
        self.pangobuffer.connect("changed", self._updateSourceText)

        #Connect buttons
        self.pangobuffer.setup_widget_from_pango(self.bt["bold"],
                                                 "<b>bold</b>")
        self.pangobuffer.setup_widget_from_pango(self.bt["italic"],
                                                 "<i>italic</i>")
コード例 #5
0
ファイル: titleeditor.py プロジェクト: tomak-git/pitivi
    def __init__(self, app):
        Loggable.__init__(self)
        self.app = app
        self.settings = {}
        self.source = None
        self.seeker = Seeker()

        # Drag attributes
        self._drag_events = []
        self._signals_connected = False

        self._createUI()
コード例 #6
0
ファイル: viewer.py プロジェクト: jojva/pitivi
 def __init__(self, settings=None):
     Gtk.DrawingArea.__init__(self)
     Loggable.__init__(self)
     self.seeker = Seeker()
     self.settings = settings
     self.box = None
     self.stored = False
     self.area = None
     self.zoom = 1.0
     self.sink = None
     self.pixbuf = None
     self.pipeline = None
     self.transformation_properties = None
コード例 #7
0
    def __init__(self, timeline, hadj):
        Gtk.DrawingArea.__init__(self)
        Zoomable.__init__(self)
        Loggable.__init__(self)
        self.log("Creating new ScaleRuler")

        # Allows stealing focus from other GTK widgets, prevent accidents:
        self.props.can_focus = True
        self.connect("focus-in-event", self._focusInCb)
        self.connect("focus-out-event", self._focusOutCb)

        self.timeline = timeline
        self._background_color = timeline.get_style_context().lookup_color(
            'theme_bg_color')[1]
        self._seeker = Seeker()
        self.hadj = hadj
        hadj.connect("value-changed", self._hadjValueChangedCb)
        self.add_events(Gdk.EventMask.POINTER_MOTION_MASK
                        | Gdk.EventMask.BUTTON_PRESS_MASK
                        | Gdk.EventMask.BUTTON_RELEASE_MASK
                        | Gdk.EventMask.SCROLL_MASK)

        self.pixbuf = None

        # all values are in pixels
        self.pixbuf_offset = 0
        self.pixbuf_offset_painted = 0
        # This is the number of width we allocate for the pixbuf
        self.pixbuf_multiples = 4

        self.position = 0  # In nanoseconds
        self.pressed = False
        self.frame_rate = Gst.Fraction(1 / 1)
        self.ns_per_frame = float(1 / self.frame_rate) * Gst.SECOND
        self.connect('draw', self.drawCb)
        self.connect('configure-event', self.configureEventCb)
        self.callback_id = None
        self.callback_id_scroll = None
        self.set_size_request(0, HEIGHT)

        style = self.get_style_context()
        color_normal = style.get_color(Gtk.StateFlags.NORMAL)
        color_insensitive = style.get_color(Gtk.StateFlags.INSENSITIVE)
        self._color_normal = color_normal
        self._color_dimmed = Gdk.RGBA(
            *[(x * 3 + y * 2) / 5
              for x, y in ((color_normal.red, color_insensitive.red),
                           (color_normal.green, color_insensitive.green),
                           (color_normal.blue, color_insensitive.blue))])

        self.scales = SCALES
コード例 #8
0
    def __init__(self, app):
        Loggable.__init__(self)
        self.app = app
        self.action_log = app.action_log
        self.settings = {}
        self.source = None
        self.seeker = Seeker()

        # Drag attributes
        self._drag_events = []
        self._signals_connected = False
        self._setting_props = False
        self._setting_initial_props = False
        self._children_props_handler = None

        self._createUI()
コード例 #9
0
ファイル: viewer.py プロジェクト: cmutti/pitivi
    def __init__(self, app):
        Gtk.Box.__init__(self)
        self.set_border_width(SPACING)
        self.app = app
        self.settings = app.settings
        self.system = app.system

        Loggable.__init__(self)
        self.log("New ViewerContainer")

        self.pipeline = None
        self.docked = True
        self.seeker = Seeker()
        self.target = None

        # Only used for restoring the pipeline position after a live clip trim
        # preview:
        self._oldTimelinePos = None

        self._haveUI = False

        self._createUi()

        self.__owning_pipeline = False
        if not self.settings.viewerDocked:
            self.undock()
コード例 #10
0
ファイル: titleeditor.py プロジェクト: dark-al/pitivi
    def __init__(self, instance, uimap):
        Loggable.__init__(self)
        Signallable.__init__(self)
        self.app = instance
        self.bt = {}
        self.settings = {}
        self.source = None
        self.created = False
        self.seeker = Seeker()

        #Drag attributes
        self._drag_events = []
        self._signals_connected = False

        self._createUI()
        self.textbuffer = Gtk.TextBuffer()
        self.pangobuffer = InteractivePangoBuffer()
        self.textarea.set_buffer(self.pangobuffer)

        self.textbuffer.connect("changed", self._updateSourceText)
        self.pangobuffer.connect("changed", self._updateSourceText)

        #Connect buttons
        self.pangobuffer.setup_widget_from_pango(self.bt["bold"], "<b>bold</b>")
        self.pangobuffer.setup_widget_from_pango(self.bt["italic"], "<i>italic</i>")
コード例 #11
0
ファイル: viewer.py プロジェクト: cfoch/pitivi-cfoch
    def __init__(self, app, undock_action=None):
        Gtk.VBox.__init__(self)
        self.set_border_width(SPACING)
        self.app = app
        self.settings = app.settings
        self.system = app.system

        Loggable.__init__(self)
        self.log("New ViewerContainer")

        self.pipeline = None
        self.docked = True
        self.seeker = Seeker()

        # Only used for restoring the pipeline position after a live clip trim preview:
        self._oldTimelinePos = None

        self._haveUI = False

        self._createUi()
        self.undock_action = undock_action
        if undock_action:
            self.undock_action.connect("activate", self._toggleDocked)

            if not self.settings.viewerDocked:
                self.undock()
コード例 #12
0
ファイル: viewer.py プロジェクト: cfoch/pitivi-cfoch
    def __init__(self, settings=None, realizedCb=None):
        # Prevent black frames and flickering while resizing or changing focus:
        # The aspect ratio gets overridden by setDisplayAspectRatio.
        Gtk.AspectFrame.__init__(self, xalign=0.5, yalign=1.0,
                                 ratio=4.0 / 3.0, obey_child=False)
        Loggable.__init__(self)

        self.drawing_area = GtkClutter.Embed()
        self.drawing_area.set_double_buffered(False)
        # We keep the ViewerWidget hidden initially, or the desktop wallpaper
        # would show through the non-double-buffered widget!
        if realizedCb:
            self.drawing_area.connect("realize", realizedCb, self)
        self.add(self.drawing_area)

        layout_manager = Clutter.BinLayout(x_align=Clutter.BinAlignment.FILL, y_align=Clutter.BinAlignment.FILL)
        self.drawing_area.get_stage().set_layout_manager(layout_manager)
        self.texture = Clutter.Texture()
        # This is a trick to make the viewer appear darker at the start.
        self.texture.set_from_rgb_data(data=[0] * 3, has_alpha=False,
                width=1, height=1, rowstride=3, bpp=3,
                flags=Clutter.TextureFlags.NONE)
        self.drawing_area.get_stage().add_child(self.texture)
        self.drawing_area.show()

        self.seeker = Seeker()
        self.settings = settings
        self.box = None
        self.stored = False
        self.area = None
        self.zoom = 1.0
        self.sink = None
        self.pixbuf = None
        self.pipeline = None
        self.transformation_properties = None
コード例 #13
0
ファイル: viewer.py プロジェクト: Jactry/pitivi
    def __init__(self, settings=None, realizedCb=None, sink=None):
        # Prevent black frames and flickering while resizing or changing focus:
        # The aspect ratio gets overridden by setDisplayAspectRatio.
        Gtk.AspectFrame.__init__(self, xalign=0.5, yalign=0.5,
                                 ratio=4.0 / 3.0, obey_child=False)
        Loggable.__init__(self)

        self.drawing_area = Pitivi.viewer_new(sink)
        self.drawing_area.set_double_buffered(False)
        self.drawing_area.connect("draw", self._drawCb, None)
        # We keep the ViewerWidget hidden initially, or the desktop wallpaper
        # would show through the non-double-buffered widget!
        if realizedCb:
            self.drawing_area.connect("realize", realizedCb, self)
        self.add(self.drawing_area)

        self.drawing_area.show()

        self.seeker = Seeker()
        self.settings = settings
        self.box = None
        self.stored = False
        self.area = None
        self.zoom = 1.0
        self.sink = sink
        self.pixbuf = None
        self.pipeline = None
        self.transformation_properties = None
        self._setting_ratio = False
コード例 #14
0
ファイル: viewer.py プロジェクト: luisbg/PiTiVi
    def setPipeline(self, pipeline, position=None):
        """
        Set the Viewer to the given Pipeline.

        Properly switches the currently set action to that new Pipeline.

        @param pipeline: The Pipeline to switch to.
        @type pipeline: L{Pipeline}.
        @param position: Optional position to seek to initially.
        """
        self.debug("self.pipeline:%r", self.pipeline)

        self.seeker = Seeker()
        self._disconnectFromPipeline()
        if self.pipeline:
            self.pipeline.set_state(Gst.State.NULL)

        self.pipeline = pipeline
        if self.pipeline:
            self.pipeline.pause()
            self.seeker.seek(position)

            self.pipeline.connect("state-change", self._pipelineStateChangedCb)
            self.pipeline.connect("position", self._positionCb)
            self.pipeline.connect("window-handle-message", self._windowHandleMessageCb)
            self.pipeline.connect("duration-changed", self._durationChangedCb)

        self._setUiActive()
コード例 #15
0
ファイル: ruler.py プロジェクト: cymacs/pitivi
    def __init__(self, instance, hadj):
        gtk.DrawingArea.__init__(self)
        Zoomable.__init__(self)
        Loggable.__init__(self)
        self.log("Creating new ScaleRuler")
        self.app = instance
        self._seeker = Seeker()
        self.hadj = hadj
        hadj.connect("value-changed", self._hadjValueChangedCb)
        self.add_events(gtk.gdk.POINTER_MOTION_MASK |
            gtk.gdk.BUTTON_PRESS_MASK | gtk.gdk.BUTTON_RELEASE_MASK)

        self.pixbuf = None
        # all values are in pixels
        self.pixbuf_offset = 0
        self.pixbuf_offset_painted = 0
        # This is the number of width we allocate for the pixbuf
        self.pixbuf_multiples = 4

        self.position = 0  # In nanoseconds
        self.pressed = False
        self.need_update = True
        self.min_frame_spacing = 5.0
        self.frame_height = 5.0
        self.frame_rate = gst.Fraction(1 / 1)
コード例 #16
0
ファイル: viewer.py プロジェクト: luisbg/PiTiVi
 def __init__(self, settings=None):
     Gtk.DrawingArea.__init__(self)
     Loggable.__init__(self)
     self.seeker = Seeker()
     self.settings = settings
     self.box = None
     self.stored = False
     self.area = None
     self.zoom = 1.0
     self.sink = None
     self.pixbuf = None
     self.pipeline = None
     self.transformation_properties = None
コード例 #17
0
ファイル: ruler.py プロジェクト: Jactry/pitivi
    def __init__(self, timeline, hadj):
        Gtk.DrawingArea.__init__(self)
        Zoomable.__init__(self)
        Loggable.__init__(self)
        self.log("Creating new ScaleRuler")

        # Allows stealing focus from other GTK widgets, prevent accidents:
        self.props.can_focus = True
        self.connect("focus-in-event", self._focusInCb)
        self.connect("focus-out-event", self._focusOutCb)

        self.timeline = timeline
        self._background_color = timeline.get_style_context().lookup_color(
            'theme_bg_color')[1]
        self._seeker = Seeker()
        self.hadj = hadj
        hadj.connect("value-changed", self._hadjValueChangedCb)
        self.add_events(Gdk.EventMask.POINTER_MOTION_MASK |
                        Gdk.EventMask.BUTTON_PRESS_MASK | Gdk.EventMask.BUTTON_RELEASE_MASK |
                        Gdk.EventMask.SCROLL_MASK)

        self.pixbuf = None

        # all values are in pixels
        self.pixbuf_offset = 0
        self.pixbuf_offset_painted = 0
        # This is the number of width we allocate for the pixbuf
        self.pixbuf_multiples = 4

        self.position = 0  # In nanoseconds
        self.pressed = False
        self.frame_rate = Gst.Fraction(1 / 1)
        self.ns_per_frame = float(1 / self.frame_rate) * Gst.SECOND
        self.connect('draw', self.drawCb)
        self.connect('configure-event', self.configureEventCb)
        self.callback_id = None
        self.callback_id_scroll = None
        self.set_size_request(0, HEIGHT)

        style = self.get_style_context()
        color_normal = style.get_color(Gtk.StateFlags.NORMAL)
        color_insensitive = style.get_color(Gtk.StateFlags.INSENSITIVE)
        self._color_normal = color_normal
        self._color_dimmed = Gdk.RGBA(
            *[(x * 3 + y * 2) / 5
              for x, y in ((color_normal.red, color_insensitive.red),
                           (color_normal.green, color_insensitive.green),
                           (color_normal.blue, color_insensitive.blue))])

        self.scales = SCALES
コード例 #18
0
    def __init__(self, app):
        Gtk.VBox.__init__(self)
        self.set_border_width(SPACING)
        self.app = app
        self.settings = app.settings
        self.system = app.system

        Loggable.__init__(self)
        self.log("New ViewerContainer")

        self.pipeline = None
        self.docked = True
        self.seeker = Seeker()

        # Only used for restoring the pipeline position after a live clip trim preview:
        self._oldTimelinePos = None

        self._haveUI = False

        self._createUi()

        if not self.settings.viewerDocked:
            self.undock()
コード例 #19
0
ファイル: viewer.py プロジェクト: cymacs/pitivi
 def __init__(self, settings=None):
     gtk.DrawingArea.__init__(self)
     Loggable.__init__(self)
     self.seeker = Seeker()
     self.settings = settings
     self.box = None
     self.stored = False
     self.area = None
     self.zoom = 1.0
     self.sink = None
     self.pixbuf = None
     self.pipeline = None
     self.transformation_properties = None
     for state in range(gtk.STATE_INSENSITIVE + 1):
         self.modify_bg(state, self.style.black)
コード例 #20
0
    def __init__(self, app):
        Loggable.__init__(self)
        self.app = app
        self.action_log = app.action_log
        self.settings = {}
        self.source = None
        self.seeker = Seeker()

        # Drag attributes
        self._drag_events = []
        self._signals_connected = False
        self._setting_props = False
        self._setting_initial_props = False
        self._children_props_handler = None

        self._createUI()
コード例 #21
0
ファイル: viewer.py プロジェクト: cmutti/pitivi
    def __init__(self, sink, app=None):
        # Prevent black frames and flickering while resizing or changing focus:
        # The aspect ratio gets overridden by setDisplayAspectRatio.
        Gtk.AspectFrame.__init__(self, xalign=0.5, yalign=0.5,
                                 ratio=4.0 / 3.0, obey_child=False)
        Loggable.__init__(self)
        self.__transformationBox = TransformationBox(app)

        # We only work with a gtkglsink inside a glsinkbin
        try:
            self.drawing_area = sink.props.sink.props.widget
        except AttributeError:
            self.drawing_area = sink.props.widget

        # We keep the ViewerWidget hidden initially, or the desktop wallpaper
        # would show through the non-double-buffered widget!
        self.add(self.__transformationBox)
        self.__transformationBox.add(self.drawing_area)

        self.drawing_area.show()

        self.seeker = Seeker()
        if app:
            self.settings = app.settings
        self.app = app
        self.box = None
        self.stored = False
        self.area = None
        self.zoom = 1.0
        self.sink = sink
        self.pixbuf = None
        self.pipeline = None
        self.transformation_properties = None
        self._setting_ratio = False
        self.__startDraggingPosition = None
        self.__startEditSourcePosition = None
        self.__editSource = None
コード例 #22
0
ファイル: viewer.py プロジェクト: cmutti/pitivi
class ViewerWidget(Gtk.AspectFrame, Loggable):

    """
    Widget for displaying a GStreamer video sink.

    @ivar settings: The settings of the application.
    @type settings: L{GlobalSettings}
    """

    __gsignals__ = {}

    def __init__(self, sink, app=None):
        # Prevent black frames and flickering while resizing or changing focus:
        # The aspect ratio gets overridden by setDisplayAspectRatio.
        Gtk.AspectFrame.__init__(self, xalign=0.5, yalign=0.5,
                                 ratio=4.0 / 3.0, obey_child=False)
        Loggable.__init__(self)
        self.__transformationBox = TransformationBox(app)

        # We only work with a gtkglsink inside a glsinkbin
        try:
            self.drawing_area = sink.props.sink.props.widget
        except AttributeError:
            self.drawing_area = sink.props.widget

        # We keep the ViewerWidget hidden initially, or the desktop wallpaper
        # would show through the non-double-buffered widget!
        self.add(self.__transformationBox)
        self.__transformationBox.add(self.drawing_area)

        self.drawing_area.show()

        self.seeker = Seeker()
        if app:
            self.settings = app.settings
        self.app = app
        self.box = None
        self.stored = False
        self.area = None
        self.zoom = 1.0
        self.sink = sink
        self.pixbuf = None
        self.pipeline = None
        self.transformation_properties = None
        self._setting_ratio = False
        self.__startDraggingPosition = None
        self.__startEditSourcePosition = None
        self.__editSource = None

    def setDisplayAspectRatio(self, ratio):
        self._setting_ratio = True
        self.set_property("ratio", float(ratio))

    def _sizeCb(self, unused_widget, unused_area):
        # The transformation box is cleared when using regular rendering
        # so we need to flush the pipeline
        self.seeker.flush()

    def do_get_preferred_width(self):
        # Do not let a chance for Gtk to choose video natural size
        # as we want to have full control
        return 0, 1

    def do_get_preferred_height(self):
        # Do not let a chance for Gtk to choose video natural size
        # as we want to have full control
        return 0, 1
コード例 #23
0
ファイル: titleeditor.py プロジェクト: dark-al/pitivi
class TitleEditor(Loggable):
    def __init__(self, instance, uimap):
        Loggable.__init__(self)
        Signallable.__init__(self)
        self.app = instance
        self.bt = {}
        self.settings = {}
        self.source = None
        self.created = False
        self.seeker = Seeker()

        #Drag attributes
        self._drag_events = []
        self._signals_connected = False

        self._createUI()
        self.textbuffer = Gtk.TextBuffer()
        self.pangobuffer = InteractivePangoBuffer()
        self.textarea.set_buffer(self.pangobuffer)

        self.textbuffer.connect("changed", self._updateSourceText)
        self.pangobuffer.connect("changed", self._updateSourceText)

        #Connect buttons
        self.pangobuffer.setup_widget_from_pango(self.bt["bold"], "<b>bold</b>")
        self.pangobuffer.setup_widget_from_pango(self.bt["italic"], "<i>italic</i>")

    def _createUI(self):
        builder = Gtk.Builder()
        builder.add_from_file(os.path.join(get_ui_dir(), "titleeditor.ui"))
        builder.connect_signals(self)
        self.widget = builder.get_object("box1")  # To be used by tabsmanager
        self.infobar = builder.get_object("infobar")
        self.editing_box = builder.get_object("editing_box")
        self.textarea = builder.get_object("textview")
        self.markup_button = builder.get_object("markupToggle")
        toolbar = builder.get_object("toolbar")
        toolbar.get_style_context().add_class("inline-toolbar")

        buttons = ["bold", "italic", "font", "font_fore_color", "back_color"]
        for button in buttons:
            self.bt[button] = builder.get_object(button)

        settings = ["valignment", "halignment", "xpos", "ypos"]
        for setting in settings:
            self.settings[setting] = builder.get_object(setting)

        for n, en in {_("Custom"): "position",
                      _("Top"): "top",
                      _("Center"): "center",
                      _("Bottom"): "bottom",
                      _("Baseline"): "baseline"}.items():
            self.settings["valignment"].append(en, n)

        for n, en in {_("Custom"): "position",
                      _("Left"): "left",
                      _("Center"): "center",
                      _("Right"): "right"}.items():
            self.settings["halignment"].append(en, n)
        self._deactivate()

    def _textviewFocusedCb(self, unused_widget, unused_event):
        self.app.gui.setActionsSensitive(False)

    def _textviewUnfocusedCb(self, unused_widget, unused_event):
        self.app.gui.setActionsSensitive(True)

    def _backgroundColorButtonCb(self, widget):
        self.textarea.modify_base(self.textarea.get_state(), widget.get_color())
        color = widget.get_rgba()
        color_int = 0
        color_int += int(color.red * 255) * 256 ** 2
        color_int += int(color.green * 255) * 256 ** 1
        color_int += int(color.blue * 255) * 256 ** 0
        color_int += int(color.alpha * 255) * 256 ** 3
        self.debug("Setting title background color to %s", hex(color_int))
        self.source.set_background(color_int)

    def _frontTextColorButtonCb(self, widget):
        suc, a, t, s = Pango.parse_markup("<span color='" + widget.get_color().to_string() + "'>color</span>", -1, u'\x00')
        ai = a.get_iterator()
        font, lang, attrs = ai.get_font()
        tags = self.pangobuffer.get_tags_from_attrs(None, None, attrs)
        self.pangobuffer.apply_tag_to_selection(tags[0])

    def _fontButtonCb(self, widget):
        font_desc = widget.get_font_name().split(" ")
        font_face = " ".join(font_desc[:-1])
        font_size = str(int(font_desc[-1]) * 1024)
        text = "<span face='" + font_face + "'><span size='" + font_size + "'>text</span></span>"
        suc, a, t, s = Pango.parse_markup(text, -1, u'\x00')
        ai = a.get_iterator()
        font, lang, attrs = ai.get_font()
        tags = self.pangobuffer.get_tags_from_attrs(font, None, attrs)
        for tag in tags:
            self.pangobuffer.apply_tag_to_selection(tag)

    def _markupToggleCb(self, markup_button):
        # FIXME: either make this feature rock-solid or replace it by a
        # Clear markup" button. Currently it is possible for the user to create
        # invalid markup (causing errors) or to get our textbuffer confused
        self.textbuffer.disconnect_by_func(self._updateSourceText)
        self.pangobuffer.disconnect_by_func(self._updateSourceText)
        if markup_button.get_active():
            self.textbuffer.set_text(self.pangobuffer.get_text())
            self.textarea.set_buffer(self.textbuffer)
            for name in self.bt:
                self.bt[name].set_sensitive(False)
        else:
            txt = self.textbuffer.get_text(self.textbuffer.get_start_iter(),
                                           self.textbuffer.get_end_iter(), True)
            self.pangobuffer.set_text(txt)
            self.textarea.set_buffer(self.pangobuffer)
            for name in self.bt:
                self.bt[name].set_sensitive(True)

        self.textbuffer.connect("changed", self._updateSourceText)
        self.pangobuffer.connect("changed", self._updateSourceText)

    def _activate(self):
        """
        Show the title editing UI widgets and hide the infobar
        """
        self.infobar.hide()
        self.textarea.show()
        self.editing_box.show()
        self._connect_signals()

    def _deactivate(self):
        """
        Reset the title editor interface to its default look
        """
        self.infobar.show()
        self.textarea.hide()
        self.editing_box.hide()
        self._disconnect_signals()

    def _updateFromSource(self):
        if self.source is not None:
            source_text = self.source.get_text()
            self.log("Title text set to %s", source_text)
            if source_text is None:
                # FIXME: sometimes we get a TextOverlay/TitleSource
                # without a valid text property. This should not happen.
                source_text = ""
                self.warning('Source did not have a text property, setting it to "" to avoid pango choking up on None')
            self.pangobuffer.set_text(source_text)
            self.textbuffer.set_text(source_text)
            self.settings['xpos'].set_value(self.source.get_xpos())
            self.settings['ypos'].set_value(self.source.get_ypos())
            self.settings['valignment'].set_active_id(self.source.get_valignment().value_name)
            self.settings['halignment'].set_active_id(self.source.get_halignment().value_name)
            if hasattr(self.source, "get_background"):
                self.bt["back_color"].set_visible(True)
                color = self.source.get_background()
                color = Gdk.RGBA(color / 256 ** 2 % 256 / 255.,
                                 color / 256 ** 1 % 256 / 255.,
                                 color / 256 ** 0 % 256 / 255.,
                                 color / 256 ** 3 % 256 / 255.)
                self.bt["back_color"].set_rgba(color)
            else:
                self.bt["back_color"].set_visible(False)

    def _updateSourceText(self, updated_obj):
        if self.source is not None:
            if self.markup_button.get_active():
                text = self.textbuffer.get_text(self.textbuffer.get_start_iter(),
                                                self.textbuffer.get_end_iter(),
                                                True)
            else:
                text = self.pangobuffer.get_text()
            self.log("Source text updated to %s", text)
            self.source.set_text(text)
            self.seeker.flush()

    def _updateSource(self, updated_obj):
        """
        Handle changes in one of the advanced property widgets at the bottom
        """
        if self.source is None:
            return
        for name, obj in self.settings.items():
            if obj == updated_obj:
                if name == "valignment":
                    self.source.set_valignment(getattr(GES.TextVAlign, obj.get_active_id().upper()))
                    self.settings["ypos"].set_visible(obj.get_active_id() == "position")
                elif name == "halignment":
                    self.source.set_halignment(getattr(GES.TextHAlign, obj.get_active_id().upper()))
                    self.settings["xpos"].set_visible(obj.get_active_id() == "position")
                elif name == "xpos":
                    self.settings["halignment"].set_active_id("position")
                    self.source.set_xpos(obj.get_value())
                elif name == "ypos":
                    self.settings["valignment"].set_active_id("position")
                    self.source.set_ypos(obj.get_value())
                self.seeker.flush()
                return

    def _reset(self):
        #TODO: reset not only text
        self.markup_button.set_active(False)
        self.pangobuffer.set_text("")
        self.textbuffer.set_text("")
        #Set right buffer
        self._markupToggleCb(self.markup_button)

    def set_source(self, source, created=False):  # FIXME: this "created" boolean param is a hack
        """
        Set the GESTitleClip to be used with the title editor.
        This can be called either from the title editor in _createCb, or by
        track.py to set the source to None.
        """
        self.debug("Source set to %s", str(source))
        self.source = source
        self._reset()
        self.created = created
        # We can't just assert source is not None... because track.py may ask us
        # to reset the source to None
        if source is None:
            self._deactivate()
        else:
            assert isinstance(source, GES.TextOverlay) or \
                isinstance(source, GES.TitleSource) or \
                isinstance(source, GES.TitleClip)
            self._updateFromSource()
            self._activate()

    def _createCb(self, unused_button):
        """
        The user clicked the "Create and insert" button, initialize the UI
        """
        source = GES.TitleClip()
        source.set_text("")
        source.set_duration(long(Gst.SECOND * 5))
        self.set_source(source, True)
        # TODO: insert on the current layer at the playhead position.
        # If no space is available, create a new layer to insert to on top.
        self.app.gui.timeline_ui.insertEnd([self.source])
        self.app.gui.timeline_ui.timeline.selection.setToObj(self.source, SELECT)
        #After insertion consider as not created
        self.created = False

    def _connect_signals(self):
        if not self._signals_connected:
            self.app.gui.viewer.target.connect("motion-notify-event", self.drag_notify_event)
            self.app.gui.viewer.target.connect("button-press-event", self.drag_press_event)
            self.app.gui.viewer.target.connect("button-release-event", self.drag_release_event)
            self._signals_connected = True

    def _disconnect_signals(self):
        if not self._signals_connected:
            return
        self.app.gui.viewer.target.disconnect_by_func(self.drag_notify_event)
        self.app.gui.viewer.target.disconnect_by_func(self.drag_press_event)
        self.app.gui.viewer.target.disconnect_by_func(self.drag_release_event)
        self._signals_connected = False

    def drag_press_event(self, widget, event):
        if event.button == 1:
            self._drag_events = [(event.x, event.y)]
            #Update drag by drag event change, but not too often
            self.timeout = GLib.timeout_add(100, self.drag_update_event)
            #If drag goes out for 0.3 second, and do not come back, consider drag end
            self._drag_updated = True
            self.timeout = GLib.timeout_add(1000, self.drag_possible_end_event)

    def drag_possible_end_event(self):
        if self._drag_updated:
            #Updated during last timeout, wait more
            self._drag_updated = False
            return True
        else:
            #Not updated - posibly out of bounds, stop drag
            self.log("Drag timeout")
            self._drag_events = []
            return False

    def drag_update_event(self):
        if len(self._drag_events) > 0:
            st = self._drag_events[0]
            self._drag_events = [self._drag_events[-1]]
            e = self._drag_events[0]
            xdiff = e[0] - st[0]
            ydiff = e[1] - st[1]
            xdiff /= self.app.gui.viewer.target.get_allocated_width()
            ydiff /= self.app.gui.viewer.target.get_allocated_height()
            newxpos = self.settings["xpos"].get_value() + xdiff
            newypos = self.settings["ypos"].get_value() + ydiff
            self.settings["xpos"].set_value(newxpos)
            self.settings["ypos"].set_value(newypos)
            self.seeker.flush()
            return True
        else:
            return False

    def drag_notify_event(self, widget, event):
        if len(self._drag_events) > 0 and event.get_state() & Gdk.ModifierType.BUTTON1_MASK:
            self._drag_updated = True
            self._drag_events.append((event.x, event.y))
            st = self._drag_events[0]
            e = self._drag_events[-1]

    def drag_release_event(self, widget, event):
        self._drag_events = []

    def tab_switched(self, unused_notebook, arg1, arg2):
        if arg2 == 2:
            self._connect_signals()
        else:
            self._disconnect_signals()
コード例 #24
0
ファイル: ruler.py プロジェクト: cymacs/pitivi
class ScaleRuler(gtk.DrawingArea, Zoomable, Loggable):

    __gsignals__ = {
        "expose-event": "override",
        "button-press-event": "override",
        "button-release-event": "override",
        "motion-notify-event": "override",
        "scroll-event": "override",
        "seek": (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
                [gobject.TYPE_UINT64])
        }

    border = 0
    min_tick_spacing = 3
    scale = [0, 0, 0, 0.5, 1, 2, 5, 10, 15, 30, 60, 120, 300, 600, 3600]
    subdivide = ((1, 1.0), (2, 0.5), (10, .25))

    def __init__(self, instance, hadj):
        gtk.DrawingArea.__init__(self)
        Zoomable.__init__(self)
        Loggable.__init__(self)
        self.log("Creating new ScaleRuler")
        self.app = instance
        self._seeker = Seeker()
        self.hadj = hadj
        hadj.connect("value-changed", self._hadjValueChangedCb)
        self.add_events(gtk.gdk.POINTER_MOTION_MASK |
            gtk.gdk.BUTTON_PRESS_MASK | gtk.gdk.BUTTON_RELEASE_MASK)

        self.pixbuf = None
        # all values are in pixels
        self.pixbuf_offset = 0
        self.pixbuf_offset_painted = 0
        # This is the number of width we allocate for the pixbuf
        self.pixbuf_multiples = 4

        self.position = 0  # In nanoseconds
        self.pressed = False
        self.need_update = True
        self.min_frame_spacing = 5.0
        self.frame_height = 5.0
        self.frame_rate = gst.Fraction(1 / 1)

    def _hadjValueChangedCb(self, hadj):
        self.pixbuf_offset = self.hadj.get_value()
        self.queue_draw()

## Zoomable interface override

    def zoomChanged(self):
        self.need_update = True
        self.queue_draw()

## timeline position changed method

    def timelinePositionChanged(self, value, unused_frame=None):
        self.position = value
        self.queue_draw()

## gtk.Widget overrides

    def do_expose_event(self, event):
        self.log("exposing ScaleRuler %s", list(event.area))
        x, y, width, height = event.area

        self.repaintIfNeeded(width, height)
        # offset in pixbuf to paint
        offset_to_paint = self.pixbuf_offset - self.pixbuf_offset_painted

        self.window.draw_pixbuf(
            self.style.fg_gc[gtk.STATE_NORMAL],
            self.pixbuf,
            int(offset_to_paint), 0,
            x, y, width, height,
            gtk.gdk.RGB_DITHER_NONE)

        # draw the position
        context = self.window.cairo_create()
        self.drawPosition(context)
        return False

    def do_button_press_event(self, event):
        self.debug("button pressed at x:%d", event.x)
        self.pressed = True
        position = self.pixelToNs(event.x + self.pixbuf_offset)
        self._seeker.seek(position)
        return True

    def do_button_release_event(self, event):
        self.debug("button released at x:%d", event.x)
        self.pressed = False
        # The distinction between the ruler and timeline canvas is theoretical.
        # If the user interacts with the ruler, have the timeline steal focus
        # from other widgets. This reactivates keyboard shortcuts for playback.
        timeline = self.app.gui.timeline_ui
        timeline._canvas.grab_focus(timeline._root_item)
        return False

    def do_motion_notify_event(self, event):
        position = self.pixelToNs(event.x + self.pixbuf_offset)
        if self.pressed:
            self.debug("motion at event.x %d", event.x)
            self._seeker.seek(position)
        else:
            human_time = beautify_length(position)
            cur_frame = int(position / self.ns_per_frame) + 1
            self.set_tooltip_text(human_time + "\n" + _("Frame #%d" % cur_frame))
        return False

    def do_scroll_event(self, event):
        if event.state & gtk.gdk.CONTROL_MASK:
            # Control + scroll = zoom
            if event.direction == gtk.gdk.SCROLL_UP:
                Zoomable.zoomIn()
                self.app.gui.timeline_ui.zoomed_fitted = False
            elif event.direction == gtk.gdk.SCROLL_DOWN:
                Zoomable.zoomOut()
                self.app.gui.timeline_ui.zoomed_fitted = False
        else:
            # No modifier key held down, just scroll
            if event.direction == gtk.gdk.SCROLL_UP or\
                event.direction == gtk.gdk.SCROLL_LEFT:
                self.app.gui.timeline_ui.scroll_left()
            elif event.direction == gtk.gdk.SCROLL_DOWN or\
                event.direction == gtk.gdk.SCROLL_RIGHT:
                self.app.gui.timeline_ui.scroll_right()

## Drawing methods

    def repaintIfNeeded(self, width, height):
        """ (re)create the buffered drawable for the Widget """
        if self.pixbuf:
            # The new offset starts before painted in pixbuf
            if self.pixbuf_offset < self.pixbuf_offset_painted:
                self.need_update = True
            # The new offsets end after pixbuf we have
            if self.pixbuf_offset + width > self.pixbuf_offset_painted + self.pixbuf.get_width():
                self.need_update = True
        else:
            self.need_update = True

        # We want to benefit from double-buffering (so as not to recreate the
        # ruler graphics all the time) yet we don't want to allocate insanely
        # big pixbufs (which would result in big memory usage, or even not being
        # able to allocate such a big pixbuf).
        #
        # We therefore create a pixbuf with a width of 4 times the max viewable
        # width (allocation.width)
        if self.need_update:
            self.log("Repainting the ruler")
            if self.pixbuf:
                del self.pixbuf
            surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, width * self.pixbuf_multiples, height)
            self.pixbuf_offset_painted = self.pixbuf_offset
            cr = cairo.Context(surface)
            self.drawBackground(cr)
            self.drawRuler(cr)
            cr = None
            self.pixbuf = gtk.gdk.pixbuf_new_from_data(surface.get_data(),
            gtk.gdk.COLORSPACE_RGB, True, 8, surface.get_width(),
            surface.get_height(), 4 * surface.get_width())
            surface = None
            self.need_update = False

    def setProjectFrameRate(self, rate):
        """
        Set the lowest scale based on project framerate
        """
        self.frame_rate = rate
        self.ns_per_frame = float(1 / self.frame_rate) * gst.SECOND
        self.scale[0] = float(2 / rate)
        self.scale[1] = float(5 / rate)
        self.scale[2] = float(10 / rate)

    def drawBackground(self, cr):
        setCairoColor(cr, self.style.bg[gtk.STATE_NORMAL])
        cr.rectangle(0, 0, cr.get_target().get_width(), cr.get_target().get_height())
        cr.fill()
        offset = int(self.nsToPixel(gst.CLOCK_TIME_NONE)) - self.pixbuf_offset
        if offset > 0:
            setCairoColor(cr, self.style.bg[gtk.STATE_ACTIVE])
            cr.rectangle(0, 0, int(offset), cr.get_target().get_height())
            cr.fill()

    def drawRuler(self, cr):
        cr.set_font_face(cairo.ToyFontFace("Cantarell"))
        cr.set_font_size(15)
        textwidth = cr.text_extents(time_to_string(0))[2]

        for scale in self.scale:
            spacing = Zoomable.zoomratio * scale
            if spacing >= textwidth * 1.5:
                break

        offset = self.pixbuf_offset % spacing
        zoomRatio = self.zoomratio
        self.drawFrameBoundaries(cr)
        self.drawTicks(cr, offset, spacing, scale)
        self.drawTimes(cr, offset, spacing, scale)

    def drawTick(self, cr, paintpos, height):
        # We need to use 0.5 pixel offsets to get a sharp 1 px line in cairo
        paintpos = int(paintpos - 0.5) + 0.5
        height = int(cr.get_target().get_height() * (1 - height))
        setCairoColor(cr, self.style.fg[gtk.STATE_NORMAL])
        cr.set_line_width(1)
        cr.move_to(paintpos, height)
        cr.line_to(paintpos, cr.get_target().get_height())
        cr.close_path()
        cr.stroke()

    def drawTicks(self, cr, offset, spacing, scale):
        for subdivide, height in self.subdivide:
            spc = spacing / float(subdivide)
            if spc < self.min_tick_spacing:
                break
            paintpos = -spacing + 0.5
            paintpos += spacing - offset
            while paintpos < cr.get_target().get_width():
                self.drawTick(cr, paintpos, height)
                paintpos += spc

    def drawTimes(self, cr, offset, spacing, scale):
        # figure out what the optimal offset is
        interval = long(gst.SECOND * scale)
        seconds = self.pixelToNs(self.pixbuf_offset)
        paintpos = float(self.border) + 2
        if offset > 0:
            seconds = seconds - (seconds % interval) + interval
            paintpos += spacing - offset

        while paintpos < cr.get_target().get_width():
            if paintpos < self.nsToPixel(gst.CLOCK_TIME_NONE):
                state = gtk.STATE_ACTIVE
            else:
                state = gtk.STATE_NORMAL
            timevalue = time_to_string(long(seconds))
            setCairoColor(cr, self.style.fg[state])
            x_bearing, y_bearing = cr.text_extents("0")[:2]
            cr.move_to(int(paintpos), 1 - y_bearing)
            cr.show_text(timevalue)
            paintpos += spacing
            seconds += interval

    def drawFrameBoundaries(self, cr):
        frame_width = self.nsToPixel(self.ns_per_frame)
        if frame_width >= self.min_frame_spacing:
            offset = self.pixbuf_offset % frame_width
            paintpos = -frame_width + 0.5
            height = cr.get_target().get_height()
            y = int(height - self.frame_height)
            states = [gtk.STATE_ACTIVE, gtk.STATE_PRELIGHT]
            paintpos += frame_width - offset
            frame_num = int(paintpos // frame_width) % 2
            while paintpos < cr.get_target().get_width():
                setCairoColor(cr, self.style.bg[states[frame_num]])
                cr.rectangle(paintpos, y, frame_width, height)
                cr.fill()
                frame_num = (frame_num + 1) % 2
                paintpos += frame_width

    def drawPosition(self, context):
        # a simple RED line will do for now
        xpos = self.nsToPixel(self.position) + self.border - self.pixbuf_offset
        context.save()
        context.set_line_width(1.5)
        context.set_source_rgb(1.0, 0, 0)
        context.move_to(xpos, 0)
        context.line_to(xpos, context.get_target().get_height())
        context.stroke()
        context.restore()
コード例 #25
0
ファイル: viewer.py プロジェクト: luisbg/PiTiVi
class ViewerWidget(Gtk.DrawingArea, Loggable):
    """
    Widget for displaying properly GStreamer video sink

    @ivar settings: The settings of the application.
    @type settings: L{GlobalSettings}
    """

    __gsignals__ = {}

    def __init__(self, settings=None):
        Gtk.DrawingArea.__init__(self)
        Loggable.__init__(self)
        self.seeker = Seeker()
        self.settings = settings
        self.box = None
        self.stored = False
        self.area = None
        self.zoom = 1.0
        self.sink = None
        self.pixbuf = None
        self.pipeline = None
        self.transformation_properties = None
        # FIXME PyGi Styling with Gtk3
        #for state in range(Gtk.StateType.INSENSITIVE + 1):
            #self.modify_bg(state, self.style.black)

    def init_transformation_events(self):
        self.set_events(Gdk.EventMask.BUTTON_PRESS_MASK
                        | Gdk.EventMask.BUTTON_RELEASE_MASK
                        | Gdk.EventMask.POINTER_MOTION_MASK
                        | Gdk.EventMask.POINTER_MOTION_HINT_MASK)

    def show_box(self):
        if not self.box:
            self.box = TransformationBox(self.settings)
            self.box.init_size(self.area)
            self._update_gradient()
            self.connect("button-press-event", self.button_press_event)
            self.connect("button-release-event", self.button_release_event)
            self.connect("motion-notify-event", self.motion_notify_event)
            self.connect("size-allocate", self._sizeCb)
            self.box.set_transformation_properties(self.transformation_properties)
            self.renderbox()

    def _sizeCb(self, widget, area):
        # The transformation box is cleared when using regular rendering
        # so we need to flush the pipeline
        self.seeker.flush()

    def hide_box(self):
        if self.box:
            self.box = None
            self.disconnect_by_func(self.button_press_event)
            self.disconnect_by_func(self.button_release_event)
            self.disconnect_by_func(self.motion_notify_event)
            self.seeker.flush()
            self.zoom = 1.0
            if self.sink:
                self.sink.set_render_rectangle(*self.area)

    def set_transformation_properties(self, transformation_properties):
            self.transformation_properties = transformation_properties

    def _store_pixbuf(self):
        """
        When not playing, store a pixbuf of the current viewer image.
        This will allow it to be restored for the transformation box.
        """

        if self.box and self.zoom != 1.0:
            # The transformation box is active and dezoomed
            # crop away 1 pixel border to avoid artefacts on the pixbuf

            self.pixbuf = Gdk.pixbuf_get_from_window(self.get_window(),
                self.box.area.x + 1, self.box.area.y + 1,
                self.box.area.width - 2, self.box.area.height - 2)
        else:
            self.pixbuf = Gdk.pixbuf_get_from_window(self.get_window(),
                0, 0,
                self.get_window().get_width(),
                self.get_window().get_height())

        self.stored = True

    def do_realize(self):
        """
        Redefine gtk DrawingArea's do_realize method to handle multiple OSes.
        This is called when creating the widget to get the window ID.
        """
        Gtk.DrawingArea.do_realize(self)
        if platform.system() == 'Windows':
            self.window_xid = self.props.window.handle
        else:
            self.window_xid = self.get_property('window').get_xid()

    def button_release_event(self, widget, event):
        if event.button == 1:
            self.box.update_effect_properties()
            self.box.release_point()
            self.seeker.flush()
            self.stored = False
        return True

    def button_press_event(self, widget, event):
        if event.button == 1:
            self.box.select_point(event)
        return True

    def _currentStateCb(self, pipeline, state):
        self.pipeline = pipeline
        if state == Gst.State.PAUSED:
            self._store_pixbuf()
        self.renderbox()

    def motion_notify_event(self, widget, event):
        if event.get_state() & Gdk.ModifierType.BUTTON1_MASK:
            if self.box.transform(event):
                if self.stored:
                    self.renderbox()
        return True

    def do_expose_event(self, event):
        self.area = event.area
        if self.box:
            self._update_gradient()
            if self.zoom != 1.0:
                width = int(float(self.area.width) * self.zoom)
                height = int(float(self.area.height) * self.zoom)
                area = ((self.area.width - width) / 2,
                        (self.area.height - height) / 2,
                        width, height)
                self.sink.set_render_rectangle(*area)
            else:
                area = self.area
            self.box.update_size(area)
            self.renderbox()

    def _update_gradient(self):
        self.gradient_background = cairo.LinearGradient(0, 0, 0, self.area.height)
        self.gradient_background.add_color_stop_rgb(0.00, .1, .1, .1)
        self.gradient_background.add_color_stop_rgb(0.50, .2, .2, .2)
        self.gradient_background.add_color_stop_rgb(1.00, .5, .5, .5)

    def renderbox(self):
        if self.box:
            cr = self.window.cairo_create()
            cr.push_group()

            if self.zoom != 1.0:
                # draw some nice background for zoom out
                cr.set_source(self.gradient_background)
                cr.rectangle(0, 0, self.area.width, self.area.height)
                cr.fill()

                # translate the drawing of the zoomed out box
                cr.translate(self.box.area.x, self.box.area.y)

            # clear the drawingarea with the last known clean video frame
            # translate when zoomed out
            if self.pixbuf:
                if self.box.area.width != self.pixbuf.get_width():
                    scale = float(self.box.area.width) / float(self.pixbuf.get_width())
                    cr.save()
                    cr.scale(scale, scale)
                cr.set_source_pixbuf(self.pixbuf, 0, 0)
                cr.paint()
                if self.box.area.width != self.pixbuf.get_width():
                    cr.restore()

            if self.pipeline and self.pipeline.get_state()[1] == Gst.State.PAUSED:
                self.box.draw(cr)
            cr.pop_group_to_source()
            cr.paint()
コード例 #26
0
ファイル: viewer.py プロジェクト: luisbg/PiTiVi
class PitiviViewer(Gtk.VBox, Loggable):
    """
    A Widget to control and visualize a Pipeline

    @ivar pipeline: The current pipeline
    @type pipeline: L{Pipeline}
    @ivar action: The action controlled by this Pipeline
    @type action: L{ViewAction}
    """
    __gtype_name__ = 'PitiviViewer'
    __gsignals__ = {
        "activate-playback-controls": (GObject.SignalFlags.RUN_LAST,
            None, (GObject.TYPE_BOOLEAN,)),
    }

    INHIBIT_REASON = _("Currently playing")

    def __init__(self, app, undock_action=None):
        Gtk.VBox.__init__(self)
        self.set_border_width(SPACING)
        self.app = app
        self.settings = app.settings
        self.system = app.system

        Loggable.__init__(self)
        self.log("New PitiviViewer")

        self.pipeline = None
        self._tmp_pipeline = None  # Used for displaying a preview when trimming

        self.sink = None
        self.docked = True

        # Only used for restoring the pipeline position after a live clip trim preview:
        self._oldTimelinePos = None

        self._haveUI = False

        self._createUi()
        self.target = self.internal
        self.undock_action = undock_action
        if undock_action:
            self.undock_action.connect("activate", self._toggleDocked)

            if not self.settings.viewerDocked:
                self.undock()

    def setPipeline(self, pipeline, position=None):
        """
        Set the Viewer to the given Pipeline.

        Properly switches the currently set action to that new Pipeline.

        @param pipeline: The Pipeline to switch to.
        @type pipeline: L{Pipeline}.
        @param position: Optional position to seek to initially.
        """
        self.debug("self.pipeline:%r", self.pipeline)

        self.seeker = Seeker()
        self._disconnectFromPipeline()
        if self.pipeline:
            self.pipeline.set_state(Gst.State.NULL)

        self.pipeline = pipeline
        if self.pipeline:
            self.pipeline.pause()
            self.seeker.seek(position)

            self.pipeline.connect("state-change", self._pipelineStateChangedCb)
            self.pipeline.connect("position", self._positionCb)
            self.pipeline.connect("window-handle-message", self._windowHandleMessageCb)
            self.pipeline.connect("duration-changed", self._durationChangedCb)

        self._setUiActive()

    def _disconnectFromPipeline(self):
        self.debug("pipeline:%r", self.pipeline)
        if self.pipeline is None:
            # silently return, there's nothing to disconnect from
            return

        self.pipeline.disconnect_by_func(self._pipelineStateChangedCb)
        self.pipeline.disconnect_by_func(self._windowHandleMessageCb)
        self.pipeline.disconnect_by_func(self._positionCb)
        self.pipeline.disconnect_by_func(self._durationChangedCb)

        self.pipeline = None

    def _setUiActive(self, active=True):
        self.debug("active %r", active)
        self.set_sensitive(active)
        if self._haveUI:
            for item in [self.goToStart_button, self.back_button,
                         self.playpause_button, self.forward_button,
                         self.goToEnd_button, self.timecode_entry]:
                item.set_sensitive(active)
        if active:
            self.emit("activate-playback-controls", True)

    def _externalWindowDeleteCb(self, window, event):
        self.dock()
        return True

    def _externalWindowConfigureCb(self, window, event):
        self.settings.viewerWidth = event.width
        self.settings.viewerHeight = event.height
        self.settings.viewerX = event.x
        self.settings.viewerY = event.y

    def _createUi(self):
        """ Creates the Viewer GUI """
        # Drawing area
        # The aspect ratio gets overridden on startup by setDisplayAspectRatio
        self.aframe = Gtk.AspectFrame(xalign=0.5, yalign=1.0, ratio=4.0 / 3.0,
                                      obey_child=False)

        self.internal = ViewerWidget(self.app.settings)
        self.internal.init_transformation_events()
        self.internal.show()
        self.aframe.add(self.internal)
        self.pack_start(self.aframe, True, True, 0)

        self.external_window = Gtk.Window()
        vbox = Gtk.VBox()
        vbox.set_spacing(SPACING)
        self.external_window.add(vbox)
        self.external = ViewerWidget(self.app.settings)
        vbox.pack_start(self.external, True, True, 0)
        self.external_window.connect("delete-event", self._externalWindowDeleteCb)
        self.external_window.connect("configure-event", self._externalWindowConfigureCb)
        self.external_vbox = vbox
        self.external_vbox.show_all()

        # Buttons/Controls
        bbox = Gtk.HBox()
        boxalign = Gtk.Alignment(xalign=0.5, yalign=0.5, xscale=0.0, yscale=0.0)
        boxalign.add(bbox)
        self.pack_start(boxalign, False, True, 0)

        self.goToStart_button = Gtk.ToolButton(Gtk.STOCK_MEDIA_PREVIOUS)
        self.goToStart_button.connect("clicked", self._goToStartCb)
        self.goToStart_button.set_tooltip_text(_("Go to the beginning of the timeline"))
        self.goToStart_button.set_sensitive(False)
        bbox.pack_start(self.goToStart_button, False, True, 0)

        self.back_button = Gtk.ToolButton(Gtk.STOCK_MEDIA_REWIND)
        self.back_button.connect("clicked", self._backCb)
        self.back_button.set_tooltip_text(_("Go back one second"))
        self.back_button.set_sensitive(False)
        bbox.pack_start(self.back_button, False, True, 0)

        self.playpause_button = PlayPauseButton()
        self.playpause_button.connect("play", self._playButtonCb)
        bbox.pack_start(self.playpause_button, False, True, 0)
        self.playpause_button.set_sensitive(False)

        self.forward_button = Gtk.ToolButton(Gtk.STOCK_MEDIA_FORWARD)
        self.forward_button.connect("clicked", self._forwardCb)
        self.forward_button.set_tooltip_text(_("Go forward one second"))
        self.forward_button.set_sensitive(False)
        bbox.pack_start(self.forward_button, False, True, 0)

        self.goToEnd_button = Gtk.ToolButton(Gtk.STOCK_MEDIA_NEXT)
        self.goToEnd_button.connect("clicked", self._goToEndCb)
        self.goToEnd_button.set_tooltip_text(_("Go to the end of the timeline"))
        self.goToEnd_button.set_sensitive(False)
        bbox.pack_start(self.goToEnd_button, False, True, 0)

        # current time
        self.timecode_entry = TimeWidget()
        self.timecode_entry.setWidgetValue(0)
        self.timecode_entry.set_tooltip_text(_('Enter a timecode or frame number\nand press "Enter" to go to that position'))
        self.timecode_entry.connectActivateEvent(self._entryActivateCb)
        self.timecode_entry.connectFocusEvents(self._entryFocusInCb, self._entryFocusOutCb)
        bbox.pack_start(self.timecode_entry, False, 10, 0)
        self._haveUI = True

        # Identify widgets for AT-SPI, making our test suite easier to develop
        # These will show up in sniff, accerciser, etc.
        self.goToStart_button.get_accessible().set_name("goToStart_button")
        self.back_button.get_accessible().set_name("back_button")
        self.playpause_button.get_accessible().set_name("playpause_button")
        self.forward_button.get_accessible().set_name("forward_button")
        self.goToEnd_button.get_accessible().set_name("goToEnd_button")
        self.timecode_entry.get_accessible().set_name("timecode_entry")

        screen = Gdk.Screen.get_default()
        height = screen.get_height()
        if height >= 800:
            # show the controls and force the aspect frame to have at least the same
            # width (+110, which is a magic number to minimize dead padding).
            bbox.show_all()
            req = bbox.size_request()
            width = req.width
            height = req.height
            width += 110
            height = int(width / self.aframe.props.ratio)
            self.aframe.set_size_request(width, height)
        self.show_all()
        self.buttons = bbox
        self.buttons_container = boxalign

    def setDisplayAspectRatio(self, ratio):
        """
        Sets the DAR of the Viewer to the given ratio.

        @arg ratio: The aspect ratio to set on the viewer
        @type ratio: L{float}
        """
        self.debug("Setting ratio of %f [%r]", float(ratio), ratio)
        try:
            self.aframe.set_property("ratio", float(ratio))
        except:
            self.warning("could not set ratio !")

    def _entryActivateCb(self, entry):
        self._seekFromTimecodeWidget()

    def _entryFocusInCb(self, entry, event):
        self.app.gui.setActionsSensitive(False)

    def _entryFocusOutCb(self, entry, event):
        self._seekFromTimecodeWidget()
        self.app.gui.setActionsSensitive(True)

    def _seekFromTimecodeWidget(self):
        nanoseconds = self.timecode_entry.getWidgetValue()
        self.seeker.seek(nanoseconds)

    ## active Timeline calllbacks
    def _durationChangedCb(self, unused_pipeline, duration):
        if duration == 0:
            self._setUiActive(False)
        else:
            self._setUiActive(True)

    ## Control Gtk.Button callbacks

    def setZoom(self, zoom):
        """
        Zoom in or out of the transformation box canvas.
        This is called by clipproperties.
        """
        if self.target.box:
            maxSize = self.target.area
            width = int(float(maxSize.width) * zoom)
            height = int(float(maxSize.height) * zoom)
            area = ((maxSize.width - width) / 2,
                    (maxSize.height - height) / 2,
                    width, height)
            self.sink.set_render_rectangle(*area)
            self.target.box.update_size(area)
            self.target.zoom = zoom
            self.target.sink = self.sink
            self.target.renderbox()

    def _playButtonCb(self, unused_button, playing):
        self.app.current.pipeline.togglePlayback()

    def _goToStartCb(self, unused_button):
        self.seeker.seek(0)

    def _backCb(self, unused_button):
        # Seek backwards one second
        self.seeker.seekRelative(0 - Gst.SECOND)

    def _forwardCb(self, unused_button):
        # Seek forward one second
        self.seeker.seekRelative(Gst.SECOND)

    def _goToEndCb(self, unused_button):
        try:
            end = self.app.current.pipeline.getDuration()
        except:
            self.warning("Couldn't get timeline duration")
        try:
            self.seeker.seek(end)
        except:
            self.warning("Couldn't seek to the end of the timeline")

    ## public methods for controlling playback

    def undock(self):
        if not self.undock_action:
            self.error("Cannot undock because undock_action is missing.")
            return
        if not self.docked:
            return

        self.docked = False
        self.settings.viewerDocked = False
        self.undock_action.set_label(_("Dock Viewer"))
        self.target = self.external

        self.remove(self.buttons_container)
        self.external_vbox.pack_end(self.buttons_container, False, False, 0)
        self.external_window.set_type_hint(Gdk.WindowTypeHint.UTILITY)
        self.external_window.show()

        self.fullscreen_button = Gtk.ToggleToolButton(Gtk.STOCK_FULLSCREEN)
        self.fullscreen_button.set_tooltip_text(_("Show this window in fullscreen"))
        self.buttons.pack_end(self.fullscreen_button, expand=False, fill=False, padding=6)
        self.fullscreen_button.show()
        self.fullscreen_button.connect("toggled", self._toggleFullscreen)

        # if we are playing, switch output immediately
        if self.sink:
            self._switch_output_window()
        self.hide()
        self.external_window.move(self.settings.viewerX, self.settings.viewerY)
        self.external_window.resize(self.settings.viewerWidth, self.settings.viewerHeight)

    def dock(self):
        if not self.undock_action:
            self.error("Cannot dock because undock_action is missing.")
            return
        if self.docked:
            return
        self.docked = True
        self.settings.viewerDocked = True
        self.undock_action.set_label(_("Undock Viewer"))
        self.target = self.internal

        self.fullscreen_button.destroy()
        self.external_vbox.remove(self.buttons_container)
        self.pack_end(self.buttons_container, False, False, 0)
        self.show()
        # if we are playing, switch output immediately
        if self.sink:
            self._switch_output_window()
        self.external_window.hide()

    def _toggleDocked(self, action):
        if self.docked:
            self.undock()
        else:
            self.dock()

    def _toggleFullscreen(self, widget):
        if widget.get_active():
            self.external_window.hide()
            # GTK doesn't let us fullscreen utility windows
            self.external_window.set_type_hint(Gdk.WindowTypeHint.NORMAL)
            self.external_window.show()
            self.external_window.fullscreen()
            widget.set_tooltip_text(_("Exit fullscreen mode"))
        else:
            self.external_window.unfullscreen()
            widget.set_tooltip_text(_("Show this window in fullscreen"))
            self.external_window.hide()
            self.external_window.set_type_hint(Gdk.WindowTypeHint.UTILITY)
            self.external_window.show()

    def _positionCb(self, unused_pipeline, position):
        """
        If the timeline position changed, update the viewer UI widgets.

        This is meant to be called either by the gobject timer when playing,
        or by mainwindow's _timelineSeekCb when the timer is disabled.
        """
        self.timecode_entry.setWidgetValue(position, False)

    def clipTrimPreview(self, tl_obj, position):
        """
        While a clip is being trimmed, show a live preview of it.
        """
        if isinstance(tl_obj, GES.TitleClip) or tl_obj.props.is_image or not hasattr(tl_obj, "get_uri"):
            self.log("%s is an image or has no URI, so not previewing trim" % tl_obj)
            return False

        clip_uri = tl_obj.props.uri
        cur_time = time()
        if not self._tmp_pipeline:
            self.debug("Creating temporary pipeline for clip %s, position %s",
                clip_uri, print_ns(position))

            self._oldTimelinePos = self.pipeline.getPosition()
            self._tmp_pipeline = Gst.ElementFactory.make("playbin", None)
            self._tmp_pipeline.set_property("uri", clip_uri)
            self.setPipeline(SimplePipeline(self._tmp_pipeline))
            self._lastClipTrimTime = cur_time
        if (cur_time - self._lastClipTrimTime) > 0.2:
            # Do not seek more than once every 200 ms (for performance)
            self._tmp_pipeline.seek_simple(Gst.Format.TIME, Gst.SeekFlags.FLUSH, position)
            self._lastClipTrimTime = cur_time

    def clipTrimPreviewFinished(self):
        """
        After trimming a clip, reset the project pipeline into the viewer.
        """
        if self._tmp_pipeline is not None:
            self._tmp_pipeline.set_state(Gst.State.NULL)
            self._tmp_pipeline = None  # Free the memory
            self.setPipeline(self.app.current.pipeline, self._oldTimelinePos)
            self.debug("Back to old pipeline")

    def _pipelineStateChangedCb(self, pipeline, state):
        """
        When playback starts/stops, update the viewer widget,
        play/pause button and (un)inhibit the screensaver.

        This is meant to be called by mainwindow.
        """
        self.info("current state changed : %s", state)
        if int(state) == int(Gst.State.PLAYING):
            self.playpause_button.setPause()
            self.system.inhibitScreensaver(self.INHIBIT_REASON)
        elif int(state) == int(Gst.State.PAUSED):
            self.playpause_button.setPlay()
            self.system.uninhibitScreensaver(self.INHIBIT_REASON)
        else:
            self.sink = None
            self.system.uninhibitScreensaver(self.INHIBIT_REASON)
        self.internal._currentStateCb(self.pipeline, state)

    def _windowHandleMessageCb(self, unused_pipeline, message):
        """
        When the pipeline sends us a message to prepare-xwindow-id,
        tell the viewer to switch its output window.
        """
        self.sink = message.src
        self._switch_output_window()

    def _switch_output_window(self):
        Gdk.threads_enter()
        # Prevent cases where target has no "window_xid" (yes, it happens!):
        self.target.show()
        self.sink.set_window_handle(self.target.window_xid)
        self.sink.expose()
        Gdk.threads_leave()
コード例 #27
0
ファイル: viewer.py プロジェクト: cfoch/pitivi-cfoch
class ViewerContainer(Gtk.VBox, Loggable):
    """
    A wiget holding a viewer and the controls.
    """
    __gtype_name__ = 'ViewerContainer'
    __gsignals__ = {
        "activate-playback-controls": (GObject.SignalFlags.RUN_LAST,
            None, (GObject.TYPE_BOOLEAN,)),
    }

    INHIBIT_REASON = _("Currently playing")

    def __init__(self, app, undock_action=None):
        Gtk.VBox.__init__(self)
        self.set_border_width(SPACING)
        self.app = app
        self.settings = app.settings
        self.system = app.system

        Loggable.__init__(self)
        self.log("New ViewerContainer")

        self.pipeline = None
        self.docked = True
        self.seeker = Seeker()

        # Only used for restoring the pipeline position after a live clip trim preview:
        self._oldTimelinePos = None

        self._haveUI = False

        self._createUi()
        self.undock_action = undock_action
        if undock_action:
            self.undock_action.connect("activate", self._toggleDocked)

            if not self.settings.viewerDocked:
                self.undock()

    @property
    def target(self):
        if self.docked:
            return self.internal
        else:
            return self.external

    def setPipeline(self, pipeline, position=None):
        """
        Set the Viewer to the given Pipeline.

        Properly switches the currently set action to that new Pipeline.

        @param pipeline: The Pipeline to switch to.
        @type pipeline: L{Pipeline}.
        @param position: Optional position to seek to initially.
        """
        self._disconnectFromPipeline()

        self.debug("New pipeline: %r", pipeline)
        self.pipeline = pipeline
        self.pipeline.pause()
        self.seeker.seek(position)

        self.pipeline.connect("state-change", self._pipelineStateChangedCb)
        self.pipeline.connect("position", self._positionCb)
        self.pipeline.connect("duration-changed", self._durationChangedCb)

        self._switch_output_window()
        self._setUiActive()

    def _disconnectFromPipeline(self):
        self.debug("Previous pipeline: %r", self.pipeline)
        if self.pipeline is None:
            # silently return, there's nothing to disconnect from
            return

        self.pipeline.disconnect_by_func(self._pipelineStateChangedCb)
        self.pipeline.disconnect_by_func(self._positionCb)
        self.pipeline.disconnect_by_func(self._durationChangedCb)

        self.pipeline = None

    def _setUiActive(self, active=True):
        self.debug("active %r", active)
        self.set_sensitive(active)
        if self._haveUI:
            for item in [self.goToStart_button, self.back_button,
                         self.playpause_button, self.forward_button,
                         self.goToEnd_button, self.timecode_entry]:
                item.set_sensitive(active)
        if active:
            self.emit("activate-playback-controls", True)

    def _externalWindowDeleteCb(self, unused_window, unused_event):
        self.dock()
        return True

    def _externalWindowConfigureCb(self, unused_window, event):
        self.settings.viewerWidth = event.width
        self.settings.viewerHeight = event.height
        self.settings.viewerX = event.x
        self.settings.viewerY = event.y

    def _videoRealizedCb(self, unused_drawing_area, viewer):
        if viewer == self.target:
            self.log("Viewer widget realized: %s", viewer)
            self._switch_output_window()

    def _createUi(self):
        """ Creates the Viewer GUI """
        # Drawing area
        self.internal = ViewerWidget(self.app.settings, realizedCb=self._videoRealizedCb)
        # Transformation boxed DISABLED
        # self.internal.init_transformation_events()
        self.pack_start(self.internal, True, True, 0)

        self.external_window = Gtk.Window()
        vbox = Gtk.VBox()
        vbox.set_spacing(SPACING)
        self.external_window.add(vbox)
        self.external = ViewerWidget(self.app.settings, realizedCb=self._videoRealizedCb)
        vbox.pack_start(self.external, True, True, 0)
        self.external_window.connect("delete-event", self._externalWindowDeleteCb)
        self.external_window.connect("configure-event", self._externalWindowConfigureCb)
        self.external_vbox = vbox

        # Buttons/Controls
        bbox = Gtk.HBox()
        boxalign = Gtk.Alignment(xalign=0.5, yalign=0.5, xscale=0.0, yscale=0.0)
        boxalign.add(bbox)
        self.pack_start(boxalign, False, True, SPACING)

        self.goToStart_button = Gtk.ToolButton()
        self.goToStart_button.set_icon_name("media-skip-backward")
        self.goToStart_button.connect("clicked", self._goToStartCb)
        self.goToStart_button.set_tooltip_text(_("Go to the beginning of the timeline"))
        self.goToStart_button.set_sensitive(False)
        bbox.pack_start(self.goToStart_button, False, True, 0)

        self.back_button = Gtk.ToolButton()
        self.back_button.set_icon_name("media-seek-backward")
        self.back_button.connect("clicked", self._backCb)
        self.back_button.set_tooltip_text(_("Go back one second"))
        self.back_button.set_sensitive(False)
        bbox.pack_start(self.back_button, False, True, 0)

        self.playpause_button = PlayPauseButton()
        self.playpause_button.connect("play", self._playButtonCb)
        bbox.pack_start(self.playpause_button, False, True, 0)
        self.playpause_button.set_sensitive(False)

        self.forward_button = Gtk.ToolButton()
        self.forward_button.set_icon_name("media-seek-forward")
        self.forward_button.connect("clicked", self._forwardCb)
        self.forward_button.set_tooltip_text(_("Go forward one second"))
        self.forward_button.set_sensitive(False)
        bbox.pack_start(self.forward_button, False, True, 0)

        self.goToEnd_button = Gtk.ToolButton()
        self.goToEnd_button.set_icon_name("media-skip-forward")
        self.goToEnd_button.connect("clicked", self._goToEndCb)
        self.goToEnd_button.set_tooltip_text(_("Go to the end of the timeline"))
        self.goToEnd_button.set_sensitive(False)
        bbox.pack_start(self.goToEnd_button, False, True, 0)

        # current time
        self.timecode_entry = TimeWidget()
        self.timecode_entry.setWidgetValue(0)
        self.timecode_entry.set_tooltip_text(_('Enter a timecode or frame number\nand press "Enter" to go to that position'))
        self.timecode_entry.connectActivateEvent(self._entryActivateCb)
        bbox.pack_start(self.timecode_entry, False, 10, 0)
        self._haveUI = True

        # Identify widgets for AT-SPI, making our test suite easier to develop
        # These will show up in sniff, accerciser, etc.
        self.goToStart_button.get_accessible().set_name("goToStart_button")
        self.back_button.get_accessible().set_name("back_button")
        self.playpause_button.get_accessible().set_name("playpause_button")
        self.forward_button.get_accessible().set_name("forward_button")
        self.goToEnd_button.get_accessible().set_name("goToEnd_button")
        self.timecode_entry.get_accessible().set_name("timecode_entry")

        screen = Gdk.Screen.get_default()
        height = screen.get_height()
        if height >= 800:
            # show the controls and force the aspect frame to have at least the same
            # width (+110, which is a magic number to minimize dead padding).
            bbox.show_all()
            req = bbox.size_request()
            width = req.width
            height = req.height
            width += 110
            height = int(width / self.internal.props.ratio)
            self.internal.set_size_request(width, height)

        self.buttons = bbox
        self.buttons_container = boxalign
        self.show_all()
        self.external_vbox.show_all()

    def setDisplayAspectRatio(self, ratio):
        self.debug("Setting aspect ratio to %f [%r]", float(ratio), ratio)
        self.internal.setDisplayAspectRatio(ratio)
        self.external.setDisplayAspectRatio(ratio)

    def _entryActivateCb(self, unused_entry):
        self._seekFromTimecodeWidget()

    def _seekFromTimecodeWidget(self):
        nanoseconds = self.timecode_entry.getWidgetValue()
        self.seeker.seek(nanoseconds)

    ## active Timeline calllbacks
    def _durationChangedCb(self, unused_pipeline, duration):
        if duration == 0:
            self._setUiActive(False)
        else:
            self._setUiActive(True)

    ## Control Gtk.Button callbacks

    def setZoom(self, zoom):
        """
        Zoom in or out of the transformation box canvas.
        This is called by clipproperties.
        """
        if self.target.box:
            maxSize = self.target.area
            width = int(float(maxSize.width) * zoom)
            height = int(float(maxSize.height) * zoom)
            area = ((maxSize.width - width) / 2,
                    (maxSize.height - height) / 2,
                    width, height)
            self.sink.set_render_rectangle(*area)
            self.target.box.update_size(area)
            self.target.zoom = zoom
            self.target.renderbox()

    def _playButtonCb(self, unused_button, unused_playing):
        self.app.current_project.pipeline.togglePlayback()
        self.app.gui.focusTimeline()

    def _goToStartCb(self, unused_button):
        self.seeker.seek(0)
        self.app.gui.focusTimeline()

    def _backCb(self, unused_button):
        # Seek backwards one second
        self.seeker.seekRelative(0 - Gst.SECOND)
        self.app.gui.focusTimeline()

    def _forwardCb(self, unused_button):
        # Seek forward one second
        self.seeker.seekRelative(Gst.SECOND)
        self.app.gui.focusTimeline()

    def _goToEndCb(self, unused_button):
        end = self.app.current_project.pipeline.getDuration()
        self.seeker.seek(end)
        self.app.gui.focusTimeline()

    ## public methods for controlling playback

    def undock(self):
        if not self.undock_action:
            self.error("Cannot undock because undock_action is missing.")
            return
        if not self.docked:
            return

        self.docked = False
        self.settings.viewerDocked = False
        self.undock_action.set_label(_("Dock Viewer"))

        self.remove(self.buttons_container)
        self.external_vbox.pack_end(self.buttons_container, False, False, 0)
        self.external_window.set_type_hint(Gdk.WindowTypeHint.UTILITY)
        self.external_window.show()

        self.fullscreen_button = Gtk.ToggleToolButton()
        self.fullscreen_button.set_icon_name("view-fullscreen")
        self.fullscreen_button.set_tooltip_text(_("Show this window in fullscreen"))
        self.buttons.pack_end(self.fullscreen_button, expand=False, fill=False, padding=6)
        self.fullscreen_button.show()
        self.fullscreen_button.connect("toggled", self._toggleFullscreen)

        # if we are playing, switch output immediately
        if self.pipeline:
            self._switch_output_window()
        self.hide()
        self.external_window.move(self.settings.viewerX, self.settings.viewerY)
        self.external_window.resize(self.settings.viewerWidth, self.settings.viewerHeight)

    def dock(self):
        if not self.undock_action:
            self.error("Cannot dock because undock_action is missing.")
            return
        if self.docked:
            return
        self.docked = True
        self.settings.viewerDocked = True
        self.undock_action.set_label(_("Undock Viewer"))

        self.fullscreen_button.destroy()
        self.external_vbox.remove(self.buttons_container)
        self.pack_end(self.buttons_container, False, False, 0)
        self.show()
        # if we are playing, switch output immediately
        if self.pipeline:
            self._switch_output_window()
        self.external_window.hide()

    def _toggleDocked(self, unused_action):
        if self.docked:
            self.undock()
        else:
            self.dock()

    def _toggleFullscreen(self, widget):
        if widget.get_active():
            self.external_window.hide()
            # GTK doesn't let us fullscreen utility windows
            self.external_window.set_type_hint(Gdk.WindowTypeHint.NORMAL)
            self.external_window.show()
            self.external_window.fullscreen()
            widget.set_tooltip_text(_("Exit fullscreen mode"))
        else:
            self.external_window.unfullscreen()
            widget.set_tooltip_text(_("Show this window in fullscreen"))
            self.external_window.hide()
            self.external_window.set_type_hint(Gdk.WindowTypeHint.UTILITY)
            self.external_window.show()

    def _positionCb(self, unused_pipeline, position):
        """
        If the timeline position changed, update the viewer UI widgets.

        This is meant to be called either by the gobject timer when playing,
        or by mainwindow's _timelineSeekCb when the timer is disabled.
        """
        self.timecode_entry.setWidgetValue(position, False)

    def clipTrimPreview(self, tl_obj, position):
        """
        While a clip is being trimmed, show a live preview of it.
        """
        if isinstance(tl_obj, GES.TitleClip) or tl_obj.props.is_image or not hasattr(tl_obj, "get_uri"):
            self.log("%s is an image or has no URI, so not previewing trim" % tl_obj)
            return False

        clip_uri = tl_obj.props.uri
        cur_time = time()
        if self.pipeline == self.app.current_project.pipeline:
            self.debug("Creating temporary pipeline for clip %s, position %s",
                clip_uri, format_ns(position))
            self._oldTimelinePos = self.pipeline.getPosition()
            self.setPipeline(AssetPipeline(tl_obj))
            self._lastClipTrimTime = cur_time

        if (cur_time - self._lastClipTrimTime) > 0.2 and self.pipeline.getState() == Gst.State.PAUSED:
            # Do not seek more than once every 200 ms (for performance)
            self.pipeline.simple_seek(position)
            self._lastClipTrimTime = cur_time

    def clipTrimPreviewFinished(self):
        """
        After trimming a clip, reset the project pipeline into the viewer.
        """
        if self.pipeline is not self.app.current_project.pipeline:
            self.pipeline.setState(Gst.State.NULL)
            # Using pipeline.getPosition() here does not work because for some
            # reason it's a bit off, that's why we need self._oldTimelinePos.
            self.setPipeline(self.app.current_project.pipeline, self._oldTimelinePos)
            self.debug("Back to the project's pipeline")

    def _pipelineStateChangedCb(self, unused_pipeline, state):
        """
        When playback starts/stops, update the viewer widget,
        play/pause button and (un)inhibit the screensaver.

        This is meant to be called by mainwindow.
        """
        if int(state) == int(Gst.State.PLAYING):
            self.playpause_button.setPause()
            self.system.inhibitScreensaver(self.INHIBIT_REASON)
        elif int(state) == int(Gst.State.PAUSED):
            self.playpause_button.setPlay()
            self.system.uninhibitScreensaver(self.INHIBIT_REASON)
        else:
            self.system.uninhibitScreensaver(self.INHIBIT_REASON)
        self.internal._currentStateCb(self.pipeline, state)

    def _switch_output_window(self):
        # Don't do anything if we don't have a pipeline
        if self.pipeline is None:
            return

        if self.target.get_realized():
            self.debug("Connecting the pipeline to the viewer's texture")
            self.pipeline.connectWithViewer(self.target)
        else:
            # Show the widget and wait for the realized callback
            self.log("Target is not realized, showing the widget")
            self.target.show()
コード例 #28
0
ファイル: ruler.py プロジェクト: brion/pitivi-work
class ScaleRuler(Gtk.DrawingArea, Zoomable, Loggable):
    """
    Widget for displaying the ruler.

    Displays a series of consecutive intervals. For each interval its beginning
    time is shown. If zoomed in enough, shows the frames in alternate colors.
    """

    __gsignals__ = {
        "button-press-event": "override",
        "button-release-event": "override",
        "motion-notify-event": "override",
        "scroll-event": "override",
        "seek": (GObject.SignalFlags.RUN_LAST, None,
                [GObject.TYPE_UINT64])
    }

    def __init__(self, timeline, hadj):
        Gtk.DrawingArea.__init__(self)
        Zoomable.__init__(self)
        Loggable.__init__(self)
        self.log("Creating new ScaleRuler")

        # Allows stealing focus from other GTK widgets, prevent accidents:
        self.props.can_focus = True
        self.connect("focus-in-event", self._focusInCb)
        self.connect("focus-out-event", self._focusOutCb)

        self.timeline = timeline
        self._background_color = timeline.get_style_context().lookup_color('theme_bg_color')[1]
        self._seeker = Seeker()
        self.hadj = hadj
        hadj.connect("value-changed", self._hadjValueChangedCb)
        self.add_events(Gdk.EventMask.POINTER_MOTION_MASK |
            Gdk.EventMask.BUTTON_PRESS_MASK | Gdk.EventMask.BUTTON_RELEASE_MASK |
            Gdk.EventMask.SCROLL_MASK)

        self.pixbuf = None

        # all values are in pixels
        self.pixbuf_offset = 0
        self.pixbuf_offset_painted = 0
        # This is the number of width we allocate for the pixbuf
        self.pixbuf_multiples = 4

        self.position = 0  # In nanoseconds
        self.pressed = False
        self.frame_rate = Gst.Fraction(1 / 1)
        self.ns_per_frame = float(1 / self.frame_rate) * Gst.SECOND
        self.connect('draw', self.drawCb)
        self.connect('configure-event', self.configureEventCb)
        self.callback_id = None
        self.callback_id_scroll = None
        self.set_size_request(0, 25)

        style = self.get_style_context()
        color_normal = style.get_color(Gtk.StateFlags.NORMAL)
        color_insensitive = style.get_color(Gtk.StateFlags.INSENSITIVE)
        self._color_normal = color_normal
        self._color_dimmed = Gdk.RGBA(
            *[(x * 3 + y * 2) / 5
              for x, y in ((color_normal.red, color_insensitive.red),
                           (color_normal.green, color_insensitive.green),
                           (color_normal.blue, color_insensitive.blue))])

        self.scales = SCALES

    def _focusInCb(self, unused_widget, unused_arg):
        self.log("Ruler has grabbed focus")
        self.timeline.setActionsSensitivity(True)

    def _focusOutCb(self, unused_widget, unused_arg):
        self.log("Ruler has lost focus")
        self.timeline.setActionsSensitivity(False)

    def _hadjValueChangedCb(self, unused_arg):
        self.pixbuf_offset = self.hadj.get_value()
        if self.callback_id_scroll is not None:
            GLib.source_remove(self.callback_id_scroll)
        self.callback_id_scroll = GLib.timeout_add(100, self._maybeUpdate)

## Zoomable interface override

    def _maybeUpdate(self):
        self.queue_draw()
        self.callback_id = None
        self.callback_id_scroll = None
        return False

    def zoomChanged(self):
        if self.callback_id is not None:
            GLib.source_remove(self.callback_id)
        self.callback_id = GLib.timeout_add(100, self._maybeUpdate)

## timeline position changed method

    def setPipeline(self, pipeline):
        pipeline.connect('position', self.timelinePositionCb)

    def timelinePositionCb(self, unused_pipeline, position):
        self.position = position
        self.queue_draw()

## Gtk.Widget overrides
    def configureEventCb(self, widget, unused_event, unused_data=None):
        width = widget.get_allocated_width()
        height = widget.get_allocated_height()
        self.debug("Configuring, height %d, width %d", width, height)

        # Destroy previous buffer
        if self.pixbuf is not None:
            self.pixbuf.finish()
            self.pixbuf = None

        # Create a new buffer
        self.pixbuf = cairo.ImageSurface(cairo.FORMAT_ARGB32, width, height)

        return False

    def drawCb(self, unused_widget, context):
        if self.pixbuf is None:
            self.info('No buffer to paint')
            return False

        pixbuf = self.pixbuf

        # Draw on a temporary context and then copy everything.
        drawing_context = cairo.Context(pixbuf)
        self.drawBackground(drawing_context)
        self.drawRuler(drawing_context)
        self.drawPosition(drawing_context)
        pixbuf.flush()

        context.set_source_surface(self.pixbuf, 0.0, 0.0)
        context.paint()

        return False

    def do_button_press_event(self, event):
        self.debug("button pressed at x:%d", event.x)
        self.pressed = True
        position = self.pixelToNs(event.x + self.pixbuf_offset)
        self._seeker.seek(position, on_idle=True)
        return True

    def do_button_release_event(self, event):
        self.debug("button released at x:%d", event.x)
        self.grab_focus()  # Prevent other widgets from being confused
        self.pressed = False
        return False

    def do_motion_notify_event(self, event):
        position = self.pixelToNs(event.x + self.pixbuf_offset)
        if self.pressed:
            self.debug("motion at event.x %d", event.x)
            self._seeker.seek(position, on_idle=True)

        human_time = beautify_length(position)
        cur_frame = int(position / self.ns_per_frame) + 1
        self.set_tooltip_text(human_time + "\n" + _("Frame #%d" % cur_frame))
        return False

    def do_scroll_event(self, event):
        if event.scroll.state & Gdk.ModifierType.CONTROL_MASK:
            # Control + scroll = zoom
            if event.scroll.direction == Gdk.ScrollDirection.UP:
                Zoomable.zoomIn()
                self.timeline.zoomed_fitted = False
            elif event.scroll.direction == Gdk.ScrollDirection.DOWN:
                Zoomable.zoomOut()
                self.timeline.zoomed_fitted = False
        else:
            # No modifier key held down, just scroll
            if (event.scroll.direction == Gdk.ScrollDirection.UP
            or event.scroll.direction == Gdk.ScrollDirection.LEFT):
                self.timeline.scroll_left()
            elif (event.scroll.direction == Gdk.ScrollDirection.DOWN
            or event.scroll.direction == Gdk.ScrollDirection.RIGHT):
                self.timeline.scroll_right()

    def setProjectFrameRate(self, rate):
        """
        Set the lowest scale based on project framerate
        """
        self.frame_rate = rate
        self.ns_per_frame = float(1 / self.frame_rate) * Gst.SECOND
        self.scales = (float(2 / rate), float(5 / rate), float(10 / rate)) + SCALES

## Drawing methods

    def drawBackground(self, context):
        style = self.get_style_context()
        set_cairo_color(context, self._background_color)
        width = context.get_target().get_width()
        height = context.get_target().get_height()
        context.rectangle(0, 0, width, height)
        context.fill()
        offset = int(self.nsToPixel(Gst.CLOCK_TIME_NONE)) - self.pixbuf_offset
        if offset > 0:
            set_cairo_color(context, style.get_background_color(Gtk.StateFlags.ACTIVE))
            context.rectangle(0, 0, int(offset), context.get_target().get_height())
            context.fill()

    def drawRuler(self, context):
        context.set_font_face(NORMAL_FONT)
        context.set_font_size(NORMAL_FONT_SIZE)

        spacing, scale = self._getSpacing(context)
        offset = self.pixbuf_offset % spacing
        self.drawFrameBoundaries(context)
        self.drawTicks(context, offset, spacing)
        self.drawTimes(context, offset, spacing, scale)

    def _getSpacing(self, context):
        textwidth = context.text_extents(time_to_string(0))[2]
        zoom = Zoomable.zoomratio
        for scale in self.scales:
            spacing = scale * zoom
            if spacing >= textwidth * 1.5:
                return spacing, scale
        raise Exception("Failed to find an interval size for textwidth:%s, zoomratio:%s" % (textwidth, Zoomable.zoomratio))

    def drawTicks(self, context, offset, spacing):
        for count_per_interval, height_ratio in TICK_TYPES:
            space = float(spacing) / count_per_interval
            if space < MIN_TICK_SPACING_PIXELS:
                break
            paintpos = 0.5 - offset
            set_cairo_color(context, self._color_normal)
            while paintpos < context.get_target().get_width():
                self._drawTick(context, paintpos, height_ratio)
                paintpos += space

    def _drawTick(self, context, paintpos, height_ratio):
        # We need to use 0.5 pixel offsets to get a sharp 1 px line in cairo
        paintpos = int(paintpos - 0.5) + 0.5
        target_height = context.get_target().get_height()
        y = int(target_height * (1 - height_ratio))
        context.set_line_width(1)
        context.move_to(paintpos, y)
        context.line_to(paintpos, target_height)
        context.close_path()
        context.stroke()

    def drawTimes(self, context, offset, spacing, scale):
        # figure out what the optimal offset is
        interval = long(Gst.SECOND * scale)
        current_time = self.pixelToNs(self.pixbuf_offset)
        paintpos = TIMES_LEFT_MARGIN_PIXELS
        if offset > 0:
            current_time = current_time - (current_time % interval) + interval
            paintpos += spacing - offset

        state = Gtk.StateFlags.NORMAL
        style = self.get_style_context()
        set_cairo_color(context, style.get_color(state))
        y_bearing = context.text_extents("0")[1]
        millis = scale < 1

        def split(x):
            # Seven elements: h : mm : ss . mmm
            # Using negative indices because the first element (hour)
            # can have a variable length.
            return x[:-10], x[-10], x[-9:-7], x[-7], x[-6:-4], x[-4], x[-3:]

        previous = split(time_to_string(max(0, current_time - interval)))
        width = context.get_target().get_width()
        while paintpos < width:
            context.move_to(int(paintpos), 1 - y_bearing)
            current = split(time_to_string(long(current_time)))
            self._drawTime(context, current, previous, millis)
            previous = current
            paintpos += spacing
            current_time += interval

    def _drawTime(self, context, current, previous, millis):
        hour = int(current[0])
        for index, (element, previous_element) in enumerate(zip(current, previous)):
            if index <= 1 and not hour:
                continue
            if index >= 5 and not millis:
                break
            if element == previous_element:
                color = self._color_dimmed
            else:
                color = self._color_normal
            set_cairo_color(context, color)
            # Display the millis with a smaller font
            small = index >= 5
            if small:
                context.set_font_size(SMALL_FONT_SIZE)
            context.show_text(element)
            if small:
                context.set_font_size(NORMAL_FONT_SIZE)

    def drawFrameBoundaries(self, context):
        """
        Draw the alternating rectangles that represent the project frames at
        high zoom levels. These are based on the framerate set in the project
        settings, not the actual frames on a video codec level.
        """
        frame_width = self.nsToPixel(self.ns_per_frame)
        if not frame_width >= FRAME_MIN_WIDTH_PIXELS:
            return

        offset = self.pixbuf_offset % frame_width
        height = context.get_target().get_height()
        y = int(height - FRAME_HEIGHT_PIXELS)
        # INSENSITIVE is a dark shade of gray, but lacks contrast
        # SELECTED will be bright blue and more visible to represent frames
        style = self.get_style_context()
        states = [style.get_background_color(Gtk.StateFlags.ACTIVE),
                  style.get_background_color(Gtk.StateFlags.SELECTED)]

        frame_num = int(self.pixelToNs(self.pixbuf_offset) * float(self.frame_rate) / Gst.SECOND)
        paintpos = self.pixbuf_offset - offset
        max_pos = context.get_target().get_width() + self.pixbuf_offset
        while paintpos < max_pos:
            paintpos = self.nsToPixel(1 / float(self.frame_rate) * Gst.SECOND * frame_num)
            set_cairo_color(context, states[(frame_num + 1) % 2])
            context.rectangle(0.5 + paintpos - self.pixbuf_offset, y, frame_width, height)
            context.fill()
            frame_num += 1

    def drawPosition(self, context):
        # Add 0.5 so that the line center is at the middle of the pixel,
        # without this the line appears blurry.
        xpos = self.nsToPixel(self.position) - self.pixbuf_offset + 0.5
        context.set_line_width(PLAYHEAD_WIDTH + 2)
        set_cairo_color(context, PLAYHEAD_COLOR)
        context.move_to(xpos, 0)
        context.line_to(xpos, context.get_target().get_height())
        context.stroke()
コード例 #29
0
class TitleEditor(Loggable):

    """
    Widget for configuring the selected title.

    @type app: L{Pitivi}
    """

    def __init__(self, app):
        Loggable.__init__(self)
        self.app = app
        self.action_log = app.action_log
        self.settings = {}
        self.source = None
        self.seeker = Seeker()

        # Drag attributes
        self._drag_events = []
        self._signals_connected = False
        self._setting_props = False
        self._setting_initial_props = False
        self._children_props_handler = None

        self._createUI()

    def _createUI(self):
        builder = Gtk.Builder()
        builder.add_from_file(os.path.join(get_ui_dir(), "titleeditor.ui"))
        builder.connect_signals(self)
        self.widget = builder.get_object("box1")  # To be used by tabsmanager
        self.infobar = builder.get_object("infobar")
        self.editing_box = builder.get_object("editing_box")
        self.textarea = builder.get_object("textview")
        toolbar = builder.get_object("toolbar")
        toolbar.get_style_context().add_class(Gtk.STYLE_CLASS_INLINE_TOOLBAR)

        self.textbuffer = Gtk.TextBuffer()
        self.textarea.set_buffer(self.textbuffer)
        self.textbuffer.connect("changed", self._textChangedCb)

        self.font_button = builder.get_object("fontbutton1")
        self.foreground_color_button = builder.get_object("fore_text_color")
        self.background_color_button = builder.get_object("back_color")

        settings = ["valignment", "halignment", "xpos", "ypos"]
        for setting in settings:
            self.settings[setting] = builder.get_object(setting)

        for n, en in list({_("Custom"): "position",
                           _("Top"): "top",
                           _("Center"): "center",
                           _("Bottom"): "bottom",
                           _("Baseline"): "baseline"}.items()):
            self.settings["valignment"].append(en, n)

        for n, en in list({_("Custom"): "position",
                           _("Left"): "left",
                           _("Center"): "center",
                           _("Right"): "right"}.items()):
            self.settings["halignment"].append(en, n)
        self._deactivate()

    def _setChildProperty(self, name, value):
        self.action_log.begin("Title %s change" % name)
        self._setting_props = True
        self.source.set_child_property(name, value)
        self._setting_props = False
        self.action_log.commit()

    def _backgroundColorButtonCb(self, widget):
        color = gdk_rgba_to_argb(widget.get_rgba())
        self.debug("Setting title background color to %x", color)
        self._setChildProperty("foreground-color", color)

    def _frontTextColorButtonCb(self, widget):
        color = gdk_rgba_to_argb(widget.get_rgba())
        self.debug("Setting title foreground color to %x", color)
        # TODO: Use set_text_color when we work with TitleSources instead of
        # TitleClips
        self._setChildProperty("color", color)

    def _fontButtonCb(self, widget):
        font_desc = widget.get_font_desc().to_string()
        self.debug("Setting font desc to %s", font_desc)
        self._setChildProperty("font-desc", font_desc)

    def _activate(self):
        """
        Show the title editing UI widgets and hide the infobar
        """
        self.infobar.hide()
        self.textarea.show()
        self.editing_box.show()
        self._connect_signals()

    def _deactivate(self):
        """
        Reset the title editor interface to its default look
        """
        self.infobar.show()
        self.textarea.hide()
        self.editing_box.hide()
        self._disconnect_signals()

    def _setWidgetText(self):
        res, source_text = self.source.get_child_property("text")
        text = self.textbuffer.get_text(self.textbuffer.get_start_iter(),
                                        self.textbuffer.get_end_iter(),
                                        True)
        if text == source_text:
            return False

        if res is False:
            # FIXME: sometimes we get a TextOverlay/TitleSource
            # without a valid text property. This should not happen.
            source_text = ""
            self.warning(
                'Source did not have a text property, setting it to "" to avoid pango choking up on None')
        self.log("Title text set to %s", source_text)
        self.textbuffer.set_text(source_text)

        return True

    def _updateFromSource(self):
        if not self.source:
            # Nothing to update from.
            return

        self._setWidgetText()
        self.settings['xpos'].set_value(self.source.get_child_property("xpos")[1])
        self.settings['ypos'].set_value(self.source.get_child_property("ypos")[1])
        self.settings['valignment'].set_active_id(
            self.source.get_child_property("valignment")[1].value_name)
        self.settings['halignment'].set_active_id(
            self.source.get_child_property("halignment")[1].value_name)

        font_desc = Pango.FontDescription.from_string(
            self.source.get_child_property("font-desc")[1])
        self.font_button.set_font_desc(font_desc)

        color = argb_to_gdk_rgba(self.source.get_child_property("color")[1])
        self.foreground_color_button.set_rgba(color)

        color = argb_to_gdk_rgba(self.source.get_child_property("foreground-color")[1])
        self.background_color_button.set_rgba(color)

    def _textChangedCb(self, unused_updated_obj):
        if not self.source:
            # Nothing to update.
            return

        text = self.textbuffer.get_text(self.textbuffer.get_start_iter(),
                                        self.textbuffer.get_end_iter(),
                                        True)
        self.log("Source text updated to %s", text)
        self._setChildProperty("text", text)

    def _updateSource(self, updated_obj):
        """
        Handle changes in one of the advanced property widgets at the bottom
        """
        if not self.source:
            # Nothing to update.
            return

        for name, obj in list(self.settings.items()):
            if obj == updated_obj:
                if name == "valignment":
                    value = getattr(GES.TextVAlign, obj.get_active_id().upper())
                    visible = obj.get_active_id() == "position"
                    self.settings["ypos"].set_visible(visible)
                elif name == "halignment":
                    value = getattr(GES.TextHAlign, obj.get_active_id().upper())
                    visible = obj.get_active_id() == "position"
                    self.settings["xpos"].set_visible(visible)
                else:
                    value = obj.get_value()

                self._setChildProperty(name, value)
                return

    def set_source(self, source):
        """
        Set the clip to be edited with this editor.

        @type source: L{GES.TitleSource}
        """
        self.debug("Source set to %s", source)
        self._deactivate()
        assert isinstance(source, GES.TextOverlay) or \
            isinstance(source, GES.TitleSource)
        self.source = source
        self._updateFromSource()
        self._activate()

    def unset_source(self):
        self._deactivate()
        self.source = None

    def _createCb(self, unused_button):
        """
        The user clicked the "Create and insert" button, initialize the UI
        """
        clip = GES.TitleClip()
        clip.set_text("")
        clip.set_duration(int(Gst.SECOND * 5))

        # TODO: insert on the current layer at the playhead position.
        # If no space is available, create a new layer to insert to on top.
        self.app.gui.timeline_ui.insertEnd([clip])
        self.app.gui.timeline_ui.timeline.selection.setToObj(clip, SELECT)

        self._setting_initial_props = True
        clip.set_color(FOREGROUND_DEFAULT_COLOR)
        clip.set_background(BACKGROUND_DEFAULT_COLOR)
        self._setting_initial_props = False

    def _propertyChangedCb(self, source, unused_gstelement, pspec):
        if self._setting_initial_props:
            return

        if self._setting_props:
            self.seeker.flush()
            return

        flush = False
        if pspec.name == "text":
            if self._setWidgetText() is True:
                flush = True
        elif pspec.name in ["xpos", "ypos"]:
            value = self.source.get_child_property(pspec.name)[1]
            if self.settings[pspec.name].get_value() == value:
                return

            flush = True
            self.settings[pspec.name].set_value(value)
        elif pspec.name in ["valignment", "halignment"]:
            value = self.source.get_child_property(pspec.name)[1].value_name
            if self.settings[pspec.name].get_active_id() == value:
                return

            flush = True
            self.settings[pspec.name].set_active_id(value)
        elif pspec.name == "font-desc":
            value = self.source.get_child_property("font-desc")[1]
            if self.font_button.get_font_desc() == value:
                return

            flush = True
            font_desc = Pango.FontDescription.from_string(value)
            self.font_button.set_font_desc(font_desc)
        elif pspec.name == "color":
            color = argb_to_gdk_rgba(self.source.get_child_property("color")[1])
            if color == self.foreground_color_button.get_rgba():
                return

            flush = True
            self.foreground_color_button.set_rgba(color)
        elif pspec.name == "foreground-color":
            color = argb_to_gdk_rgba(self.source.get_child_property("foreground-color")[1])

            if color == self.background_color_button.get_rgba():
                return

            flush = True
            self.background_color_button.set_rgba(color)

        if flush is True:
            self.seeker.flush()

    def _connect_signals(self):
        if self.source and not self._children_props_handler:
            self._children_props_handler = self.source.connect('deep-notify',
                                                               self._propertyChangedCb)
        if not self._signals_connected:
            self.app.gui.viewer.target.connect(
                "motion-notify-event", self.drag_notify_event)
            self.app.gui.viewer.target.connect(
                "button-press-event", self.drag_press_event)
            self.app.gui.viewer.target.connect(
                "button-release-event", self.drag_release_event)
            self._signals_connected = True

    def _disconnect_signals(self):
        if self._children_props_handler is not None:
            self.source.disconnect(self._children_props_handler)
            self._children_props_handler = None

        if not self._signals_connected:
            return
        self.app.gui.viewer.target.disconnect_by_func(self.drag_notify_event)
        self.app.gui.viewer.target.disconnect_by_func(self.drag_press_event)
        self.app.gui.viewer.target.disconnect_by_func(self.drag_release_event)
        self._signals_connected = False

    def drag_press_event(self, unused_widget, event):
        if event.button == 1:
            self._drag_events = [(event.x, event.y)]
            # Update drag by drag event change, but not too often
            self.timeout = GLib.timeout_add(100, self.drag_update_event)
            # If drag goes out for 0.3 second, and do not come back, consider
            # drag end
            self._drag_updated = True
            self.timeout = GLib.timeout_add(1000, self.drag_possible_end_event)

    def drag_possible_end_event(self):
        if self._drag_updated:
            # Updated during last timeout, wait more
            self._drag_updated = False
            return True
        else:
            # Not updated - posibly out of bounds, stop drag
            self.log("Drag timeout")
            self._drag_events = []
            return False

    def drag_update_event(self):
        if len(self._drag_events) > 0:
            st = self._drag_events[0]
            self._drag_events = [self._drag_events[-1]]
            e = self._drag_events[0]
            xdiff = e[0] - st[0]
            ydiff = e[1] - st[1]
            xdiff /= self.app.gui.viewer.target.get_allocated_width()
            ydiff /= self.app.gui.viewer.target.get_allocated_height()
            newxpos = self.settings["xpos"].get_value() + xdiff
            newypos = self.settings["ypos"].get_value() + ydiff
            self.settings["xpos"].set_value(newxpos)
            self.settings["ypos"].set_value(newypos)
            return True
        else:
            return False

    def drag_notify_event(self, unused_widget, event):
        if len(self._drag_events) > 0 and event.get_state() & Gdk.ModifierType.BUTTON1_MASK:
            self._drag_updated = True
            self._drag_events.append((event.x, event.y))
            st = self._drag_events[0]
            e = self._drag_events[-1]

    def drag_release_event(self, unused_widget, unused_event):
        self._drag_events = []

    def tabSwitchedCb(self, unused_notebook, page_widget, unused_page_index):
        if self.widget == page_widget:
            self._connect_signals()
        else:
            self._disconnect_signals()

    def selectionChangedCb(self, selection):
        selected_clip = selection.getSingleClip(GES.TitleClip)
        source = None
        if selected_clip:
            source = selected_clip.get_children(False)[0]

        if source:
            self.set_source(source)
        else:
            self.unset_source()
コード例 #30
0
class ViewerContainer(Gtk.VBox, Loggable):
    """
    A wiget holding a viewer and the controls.
    """
    __gtype_name__ = 'ViewerContainer'
    __gsignals__ = {
        "activate-playback-controls":
        (GObject.SignalFlags.RUN_LAST, None, (GObject.TYPE_BOOLEAN, )),
    }

    INHIBIT_REASON = _("Currently playing")

    def __init__(self, app):
        Gtk.VBox.__init__(self)
        self.set_border_width(SPACING)
        self.app = app
        self.settings = app.settings
        self.system = app.system

        Loggable.__init__(self)
        self.log("New ViewerContainer")

        self.pipeline = None
        self.docked = True
        self.seeker = Seeker()

        # Only used for restoring the pipeline position after a live clip trim preview:
        self._oldTimelinePos = None

        self._haveUI = False

        self._createUi()

        if not self.settings.viewerDocked:
            self.undock()

    @property
    def target(self):
        if self.docked:
            return self.internal
        else:
            return self.external

    def setPipeline(self, pipeline, position=None):
        """
        Set the Viewer to the given Pipeline.

        Properly switches the currently set action to that new Pipeline.

        @param pipeline: The Pipeline to switch to.
        @type pipeline: L{Pipeline}.
        @param position: Optional position to seek to initially.
        """
        self._disconnectFromPipeline()

        self.debug("New pipeline: %r", pipeline)
        self.pipeline = pipeline
        self.pipeline.pause()
        self.seeker.seek(position)

        self.pipeline.connect("state-change", self._pipelineStateChangedCb)
        self.pipeline.connect("position", self._positionCb)
        self.pipeline.connect("duration-changed", self._durationChangedCb)

        self.sink = pipeline._opengl_sink

        self._switch_output_window()
        self._setUiActive()

    def _disconnectFromPipeline(self):
        self.debug("Previous pipeline: %r", self.pipeline)
        if self.pipeline is None:
            # silently return, there's nothing to disconnect from
            return

        self.pipeline.disconnect_by_func(self._pipelineStateChangedCb)
        self.pipeline.disconnect_by_func(self._positionCb)
        self.pipeline.disconnect_by_func(self._durationChangedCb)

        self.pipeline = None

    def _setUiActive(self, active=True):
        self.debug("active %r", active)
        self.set_sensitive(active)
        if self._haveUI:
            for item in [
                    self.goToStart_button, self.back_button,
                    self.playpause_button, self.forward_button,
                    self.goToEnd_button, self.timecode_entry
            ]:
                item.set_sensitive(active)
        if active:
            self.emit("activate-playback-controls", True)

    def _externalWindowDeleteCb(self, unused_window, unused_event):
        self.dock()
        return True

    def _externalWindowConfigureCb(self, unused_window, event):
        self.settings.viewerWidth = event.width
        self.settings.viewerHeight = event.height
        self.settings.viewerX = event.x
        self.settings.viewerY = event.y

    def _videoRealizedCb(self, unused_drawing_area, viewer):
        if viewer == self.target:
            self.log("Viewer widget realized: %s", viewer)
            self._switch_output_window()

    def _createUi(self):
        """ Creates the Viewer GUI """
        # Drawing area
        self.internal = ViewerWidget(self.app.settings,
                                     realizedCb=self._videoRealizedCb)
        # Transformation boxed DISABLED
        # self.internal.init_transformation_events()
        self.pack_start(self.internal, True, True, 0)

        self.external_window = Gtk.Window()
        vbox = Gtk.VBox()
        vbox.set_spacing(SPACING)
        self.external_window.add(vbox)
        self.external = ViewerWidget(self.app.settings,
                                     realizedCb=self._videoRealizedCb)
        vbox.pack_start(self.external, True, True, 0)
        self.external_window.connect("delete-event",
                                     self._externalWindowDeleteCb)
        self.external_window.connect("configure-event",
                                     self._externalWindowConfigureCb)
        self.external_vbox = vbox

        # Buttons/Controls
        bbox = Gtk.HBox()

        bbox.set_property("valign", Gtk.Align.CENTER)
        bbox.set_property("halign", Gtk.Align.CENTER)

        self.pack_start(bbox, False, True, SPACING)

        self.goToStart_button = Gtk.ToolButton()
        self.goToStart_button.set_icon_name("media-skip-backward")
        self.goToStart_button.connect("clicked", self._goToStartCb)
        self.goToStart_button.set_tooltip_text(
            _("Go to the beginning of the timeline"))
        self.goToStart_button.set_sensitive(False)
        bbox.pack_start(self.goToStart_button, False, True, 0)

        self.back_button = Gtk.ToolButton()
        self.back_button.set_icon_name("media-seek-backward")
        self.back_button.connect("clicked", self._backCb)
        self.back_button.set_tooltip_text(_("Go back one second"))
        self.back_button.set_sensitive(False)
        bbox.pack_start(self.back_button, False, True, 0)

        self.playpause_button = PlayPauseButton()
        self.playpause_button.connect("play", self._playButtonCb)
        bbox.pack_start(self.playpause_button, False, True, 0)
        self.playpause_button.set_sensitive(False)

        self.forward_button = Gtk.ToolButton()
        self.forward_button.set_icon_name("media-seek-forward")
        self.forward_button.connect("clicked", self._forwardCb)
        self.forward_button.set_tooltip_text(_("Go forward one second"))
        self.forward_button.set_sensitive(False)
        bbox.pack_start(self.forward_button, False, True, 0)

        self.goToEnd_button = Gtk.ToolButton()
        self.goToEnd_button.set_icon_name("media-skip-forward")
        self.goToEnd_button.connect("clicked", self._goToEndCb)
        self.goToEnd_button.set_tooltip_text(
            _("Go to the end of the timeline"))
        self.goToEnd_button.set_sensitive(False)
        bbox.pack_start(self.goToEnd_button, False, True, 0)

        self.timecode_entry = TimeWidget()
        self.timecode_entry.setWidgetValue(0)
        self.timecode_entry.set_tooltip_text(
            _('Enter a timecode or frame number\nand press "Enter" to go to that position'
              ))
        self.timecode_entry.connectActivateEvent(self._entryActivateCb)
        bbox.pack_start(self.timecode_entry, False, 10, 0)

        self.undock_button = Gtk.ToolButton()
        self.undock_button.set_icon_name("view-restore")
        self.undock_button.connect("clicked", self.undock)
        self.undock_button.set_tooltip_text(
            _("Detach the viewer\nYou can re-attach it by closing the newly created window."
              ))
        bbox.pack_start(self.undock_button, False, True, 0)

        self._haveUI = True

        # Identify widgets for AT-SPI, making our test suite easier to develop
        # These will show up in sniff, accerciser, etc.
        self.goToStart_button.get_accessible().set_name("goToStart_button")
        self.back_button.get_accessible().set_name("back_button")
        self.playpause_button.get_accessible().set_name("playpause_button")
        self.forward_button.get_accessible().set_name("forward_button")
        self.goToEnd_button.get_accessible().set_name("goToEnd_button")
        self.timecode_entry.get_accessible().set_name("timecode_entry")
        self.undock_button.get_accessible().set_name("undock_button")

        screen = Gdk.Screen.get_default()
        height = screen.get_height()
        if height >= 800:
            # show the controls and force the aspect frame to have at least the same
            # width (+110, which is a magic number to minimize dead padding).
            bbox.show_all()
            req = bbox.size_request()
            width = req.width
            height = req.height
            width += 110
            height = int(width / self.internal.props.ratio)
            self.internal.set_size_request(width, height)

        self.buttons = bbox
        self.buttons_container = bbox
        self.show_all()
        self.external_vbox.show_all()

    def setDisplayAspectRatio(self, ratio):
        self.debug("Setting aspect ratio to %f [%r]", float(ratio), ratio)
        self.internal.setDisplayAspectRatio(ratio)
        self.external.setDisplayAspectRatio(ratio)

    def _entryActivateCb(self, unused_entry):
        self._seekFromTimecodeWidget()

    def _seekFromTimecodeWidget(self):
        nanoseconds = self.timecode_entry.getWidgetValue()
        self.seeker.seek(nanoseconds)

    # Active Timeline calllbacks
    def _durationChangedCb(self, unused_pipeline, duration):
        if duration == 0:
            self._setUiActive(False)
        else:
            self._setUiActive(True)

    # Control Gtk.Button callbacks

    def setZoom(self, zoom):
        """
        Zoom in or out of the transformation box canvas.
        This is called by clipproperties.
        """
        if self.target.box:
            maxSize = self.target.area
            width = int(float(maxSize.width) * zoom)
            height = int(float(maxSize.height) * zoom)
            area = ((maxSize.width - width) / 2, (maxSize.height - height) / 2,
                    width, height)
            self.sink.set_render_rectangle(*area)
            self.target.box.update_size(area)
            self.target.zoom = zoom
            self.target.renderbox()

    def _playButtonCb(self, unused_button, unused_playing):
        self.app.project_manager.current_project.pipeline.togglePlayback()
        self.app.gui.focusTimeline()

    def _goToStartCb(self, unused_button):
        self.seeker.seek(0)
        self.app.gui.focusTimeline()

    def _backCb(self, unused_button):
        # Seek backwards one second
        self.seeker.seekRelative(0 - Gst.SECOND)
        self.app.gui.focusTimeline()

    def _forwardCb(self, unused_button):
        # Seek forward one second
        self.seeker.seekRelative(Gst.SECOND)
        self.app.gui.focusTimeline()

    def _goToEndCb(self, unused_button):
        end = self.app.project_manager.current_project.pipeline.getDuration()
        self.seeker.seek(end)
        self.app.gui.focusTimeline()

    # Public methods for controlling playback

    def undock(self, *unused_widget):
        if not self.docked:
            self.warning("The viewer is already undocked")
            return

        self.docked = False
        self.settings.viewerDocked = False

        self.remove(self.buttons_container)
        self.external_vbox.pack_end(self.buttons_container, False, False, 0)
        self.external_window.set_type_hint(Gdk.WindowTypeHint.UTILITY)
        self.external_window.show()

        self.undock_button.hide()
        self.fullscreen_button = Gtk.ToggleToolButton()
        self.fullscreen_button.set_icon_name("view-fullscreen")
        self.fullscreen_button.set_tooltip_text(
            _("Show this window in fullscreen"))
        self.buttons.pack_end(self.fullscreen_button,
                              expand=False,
                              fill=False,
                              padding=6)
        self.fullscreen_button.show()
        self.fullscreen_button.connect("toggled", self._toggleFullscreen)

        # if we are playing, switch output immediately
        if self.pipeline:
            self._switch_output_window()
        self.hide()
        self.external_window.move(self.settings.viewerX, self.settings.viewerY)
        self.external_window.resize(self.settings.viewerWidth,
                                    self.settings.viewerHeight)

    def dock(self):
        if self.docked:
            self.warning("The viewer is already docked")
            return
        self.docked = True
        self.settings.viewerDocked = True

        self.undock_button.show()
        self.fullscreen_button.destroy()
        self.external_vbox.remove(self.buttons_container)
        self.pack_end(self.buttons_container, False, False, 0)
        self.show()
        # if we are playing, switch output immediately
        if self.pipeline:
            self._switch_output_window()
        self.external_window.hide()

    def _toggleFullscreen(self, widget):
        if widget.get_active():
            self.external_window.hide()
            # GTK doesn't let us fullscreen utility windows
            self.external_window.set_type_hint(Gdk.WindowTypeHint.NORMAL)
            self.external_window.show()
            self.external_window.fullscreen()
            widget.set_tooltip_text(_("Exit fullscreen mode"))
        else:
            self.external_window.unfullscreen()
            widget.set_tooltip_text(_("Show this window in fullscreen"))
            self.external_window.hide()
            self.external_window.set_type_hint(Gdk.WindowTypeHint.UTILITY)
            self.external_window.show()

    def _positionCb(self, unused_pipeline, position):
        """
        If the timeline position changed, update the viewer UI widgets.

        This is meant to be called either by the gobject timer when playing,
        or by mainwindow's _timelineSeekCb when the timer is disabled.
        """
        self.timecode_entry.setWidgetValue(position, False)

    def clipTrimPreview(self, tl_obj, position):
        """
        While a clip is being trimmed, show a live preview of it.
        """
        if isinstance(tl_obj,
                      GES.TitleClip) or tl_obj.props.is_image or not hasattr(
                          tl_obj, "get_uri"):
            self.log("%s is an image or has no URI, so not previewing trim" %
                     tl_obj)
            return False

        clip_uri = tl_obj.props.uri
        cur_time = time()
        if self.pipeline == self.app.project_manager.current_project.pipeline:
            self.debug("Creating temporary pipeline for clip %s, position %s",
                       clip_uri, format_ns(position))
            self._oldTimelinePos = self.pipeline.getPosition()
            self.setPipeline(AssetPipeline(tl_obj))
            self._lastClipTrimTime = cur_time

        if (cur_time - self._lastClipTrimTime
            ) > 0.2 and self.pipeline.getState() == Gst.State.PAUSED:
            # Do not seek more than once every 200 ms (for performance)
            self.pipeline.simple_seek(position)
            self._lastClipTrimTime = cur_time

    def clipTrimPreviewFinished(self):
        """
        After trimming a clip, reset the project pipeline into the viewer.
        """
        if self.pipeline is not self.app.project_manager.current_project.pipeline:
            self.pipeline.setState(Gst.State.NULL)
            # Using pipeline.getPosition() here does not work because for some
            # reason it's a bit off, that's why we need self._oldTimelinePos.
            self.setPipeline(self.app.project_manager.current_project.pipeline,
                             self._oldTimelinePos)
            self.debug("Back to the project's pipeline")

    def _pipelineStateChangedCb(self, unused_pipeline, state):
        """
        When playback starts/stops, update the viewer widget,
        play/pause button and (un)inhibit the screensaver.

        This is meant to be called by mainwindow.
        """
        if int(state) == int(Gst.State.PLAYING):
            self.playpause_button.setPause()
            self.system.inhibitScreensaver(self.INHIBIT_REASON)
        elif int(state) == int(Gst.State.PAUSED):
            self.playpause_button.setPlay()
            self.system.uninhibitScreensaver(self.INHIBIT_REASON)
        else:
            self.system.uninhibitScreensaver(self.INHIBIT_REASON)
        self.internal._currentStateCb(self.pipeline, state)

    def _switch_output_window(self):
        # Don't do anything if we don't have a pipeline
        if self.pipeline is None:
            return

        if self.target.get_realized():
            self.debug("Connecting the pipeline to the viewer's texture")
            if platform.system() == 'Windows':
                xid = self.target.drawing_area.get_window().get_handle()
            else:
                xid = self.target.drawing_area.get_window().get_xid()

            self.sink.set_window_handle(xid)
            self.sink.expose()
        else:
            # Show the widget and wait for the realized callback
            self.log("Target is not realized, showing the widget")
            self.target.show()
コード例 #31
0
ファイル: viewer.py プロジェクト: jojva/pitivi
class ViewerWidget(Gtk.DrawingArea, Loggable):
    """
    Widget for displaying properly GStreamer video sink

    @ivar settings: The settings of the application.
    @type settings: L{GlobalSettings}
    """

    __gsignals__ = {}

    def __init__(self, settings=None):
        Gtk.DrawingArea.__init__(self)
        Loggable.__init__(self)
        self.seeker = Seeker()
        self.settings = settings
        self.box = None
        self.stored = False
        self.area = None
        self.zoom = 1.0
        self.sink = None
        self.pixbuf = None
        self.pipeline = None
        self.transformation_properties = None
        # FIXME PyGi Styling with Gtk3
        #for state in range(Gtk.StateType.INSENSITIVE + 1):
            #self.modify_bg(state, self.style.black)

    def init_transformation_events(self):
        self.set_events(Gdk.EventMask.BUTTON_PRESS_MASK
                        | Gdk.EventMask.BUTTON_RELEASE_MASK
                        | Gdk.EventMask.POINTER_MOTION_MASK
                        | Gdk.EventMask.POINTER_MOTION_HINT_MASK)

    def show_box(self):
        if not self.box:
            self.box = TransformationBox(self.settings)
            self.box.init_size(self.area)
            self._update_gradient()
            self.connect("button-press-event", self.button_press_event)
            self.connect("button-release-event", self.button_release_event)
            self.connect("motion-notify-event", self.motion_notify_event)
            self.connect("size-allocate", self._sizeCb)
            self.box.set_transformation_properties(self.transformation_properties)
            self.renderbox()

    def _sizeCb(self, widget, area):
        # The transformation box is cleared when using regular rendering
        # so we need to flush the pipeline
        self.seeker.flush()

    def hide_box(self):
        if self.box:
            self.box = None
            self.disconnect_by_func(self.button_press_event)
            self.disconnect_by_func(self.button_release_event)
            self.disconnect_by_func(self.motion_notify_event)
            self.seeker.flush()
            self.zoom = 1.0
            if self.sink:
                self.sink.set_render_rectangle(*self.area)

    def set_transformation_properties(self, transformation_properties):
            self.transformation_properties = transformation_properties

    def _store_pixbuf(self):
        """
        When not playing, store a pixbuf of the current viewer image.
        This will allow it to be restored for the transformation box.
        """

        if self.box and self.zoom != 1.0:
            # The transformation box is active and dezoomed
            # crop away 1 pixel border to avoid artefacts on the pixbuf

            self.pixbuf = Gdk.pixbuf_get_from_window(self.get_window(),
                self.box.area.x + 1, self.box.area.y + 1,
                self.box.area.width - 2, self.box.area.height - 2)
        else:
            self.pixbuf = Gdk.pixbuf_get_from_window(self.get_window(),
                0, 0,
                self.get_window().get_width(),
                self.get_window().get_height())

        self.stored = True

    def do_realize(self):
        """
        Redefine gtk DrawingArea's do_realize method to handle multiple OSes.
        This is called when creating the widget to get the window ID.
        """
        Gtk.DrawingArea.do_realize(self)
        if platform.system() == 'Windows':
            self.window_xid = self.props.window.handle
        else:
            self.window_xid = self.get_property('window').get_xid()

    def button_release_event(self, widget, event):
        if event.button == 1:
            self.box.update_effect_properties()
            self.box.release_point()
            self.seeker.flush()
            self.stored = False
        return True

    def button_press_event(self, widget, event):
        if event.button == 1:
            self.box.select_point(event)
        return True

    def _currentStateCb(self, pipeline, state):
        self.pipeline = pipeline
        if state == Gst.State.PAUSED:
            self._store_pixbuf()
        self.renderbox()

    def motion_notify_event(self, widget, event):
        if event.get_state() & Gdk.ModifierType.BUTTON1_MASK:
            if self.box.transform(event):
                if self.stored:
                    self.renderbox()
        return True

    def do_expose_event(self, event):
        self.area = event.area
        if self.box:
            self._update_gradient()
            if self.zoom != 1.0:
                width = int(float(self.area.width) * self.zoom)
                height = int(float(self.area.height) * self.zoom)
                area = ((self.area.width - width) / 2,
                        (self.area.height - height) / 2,
                        width, height)
                self.sink.set_render_rectangle(*area)
            else:
                area = self.area
            self.box.update_size(area)
            self.renderbox()

    def _update_gradient(self):
        self.gradient_background = cairo.LinearGradient(0, 0, 0, self.area.height)
        self.gradient_background.add_color_stop_rgb(0.00, .1, .1, .1)
        self.gradient_background.add_color_stop_rgb(0.50, .2, .2, .2)
        self.gradient_background.add_color_stop_rgb(1.00, .5, .5, .5)

    def renderbox(self):
        if self.box:
            cr = self.window.cairo_create()
            cr.push_group()

            if self.zoom != 1.0:
                # draw some nice background for zoom out
                cr.set_source(self.gradient_background)
                cr.rectangle(0, 0, self.area.width, self.area.height)
                cr.fill()

                # translate the drawing of the zoomed out box
                cr.translate(self.box.area.x, self.box.area.y)

            # clear the drawingarea with the last known clean video frame
            # translate when zoomed out
            if self.pixbuf:
                if self.box.area.width != self.pixbuf.get_width():
                    scale = float(self.box.area.width) / float(self.pixbuf.get_width())
                    cr.save()
                    cr.scale(scale, scale)
                cr.set_source_pixbuf(self.pixbuf, 0, 0)
                cr.paint()
                if self.box.area.width != self.pixbuf.get_width():
                    cr.restore()

            if self.pipeline and self.pipeline.get_state()[1] == Gst.State.PAUSED:
                self.box.draw(cr)
            cr.pop_group_to_source()
            cr.paint()
コード例 #32
0
ファイル: viewer.py プロジェクト: jojva/pitivi
class PitiviViewer(Gtk.VBox, Loggable):
    """
    A Widget to control and visualize a Pipeline

    @ivar pipeline: The current pipeline
    @type pipeline: L{Pipeline}
    @ivar action: The action controlled by this Pipeline
    @type action: L{ViewAction}
    """
    __gtype_name__ = 'PitiviViewer'
    __gsignals__ = {
        "activate-playback-controls": (GObject.SignalFlags.RUN_LAST,
            None, (GObject.TYPE_BOOLEAN,)),
    }

    INHIBIT_REASON = _("Currently playing")

    def __init__(self, app, undock_action=None):
        Gtk.VBox.__init__(self)
        self.set_border_width(SPACING)
        self.app = app
        self.settings = app.settings
        self.system = app.system

        Loggable.__init__(self)
        self.log("New PitiviViewer")

        self.pipeline = None
        self._tmp_pipeline = None  # Used for displaying a preview when trimming

        self.sink = None
        self.docked = True

        # Only used for restoring the pipeline position after a live clip trim preview:
        self._oldTimelinePos = None

        self._haveUI = False

        self._createUi()
        self.target = self.internal
        self.undock_action = undock_action
        if undock_action:
            self.undock_action.connect("activate", self._toggleDocked)

            if not self.settings.viewerDocked:
                self.undock()

    def setPipeline(self, pipeline, position=None):
        """
        Set the Viewer to the given Pipeline.

        Properly switches the currently set action to that new Pipeline.

        @param pipeline: The Pipeline to switch to.
        @type pipeline: L{Pipeline}.
        @param position: Optional position to seek to initially.
        """
        self.debug("self.pipeline:%r", self.pipeline)

        self.seeker = Seeker()
        self._disconnectFromPipeline()
        if self.pipeline:
            self.pipeline.set_state(Gst.State.NULL)

        self.pipeline = pipeline
        if self.pipeline:
            self.pipeline.pause()
            self.seeker.seek(position)

            self.pipeline.connect("state-change", self._pipelineStateChangedCb)
            self.pipeline.connect("position", self._positionCb)
            self.pipeline.connect("duration-changed", self._durationChangedCb)

        self.sink = pipeline.video_overlay
        self._switch_output_window()
        self._setUiActive()

    def _disconnectFromPipeline(self):
        self.debug("pipeline:%r", self.pipeline)
        if self.pipeline is None:
            # silently return, there's nothing to disconnect from
            return

        self.pipeline.disconnect_by_func(self._pipelineStateChangedCb)
        self.pipeline.disconnect_by_func(self._positionCb)
        self.pipeline.disconnect_by_func(self._durationChangedCb)

        self.pipeline = None

    def _setUiActive(self, active=True):
        self.debug("active %r", active)
        self.set_sensitive(active)
        if self._haveUI:
            for item in [self.goToStart_button, self.back_button,
                         self.playpause_button, self.forward_button,
                         self.goToEnd_button, self.timecode_entry]:
                item.set_sensitive(active)
        if active:
            self.emit("activate-playback-controls", True)

    def _externalWindowDeleteCb(self, window, event):
        self.dock()
        return True

    def _externalWindowConfigureCb(self, window, event):
        self.settings.viewerWidth = event.width
        self.settings.viewerHeight = event.height
        self.settings.viewerX = event.x
        self.settings.viewerY = event.y

    def _createUi(self):
        """ Creates the Viewer GUI """
        # Drawing area
        # The aspect ratio gets overridden on startup by setDisplayAspectRatio
        self.aframe = Gtk.AspectFrame(xalign=0.5, yalign=1.0, ratio=4.0 / 3.0,
                                      obey_child=False)

        self.internal = ViewerWidget(self.app.settings)
        self.internal.init_transformation_events()
        self.internal.show()
        self.aframe.add(self.internal)
        self.pack_start(self.aframe, True, True, 0)

        self.external_window = Gtk.Window()
        vbox = Gtk.VBox()
        vbox.set_spacing(SPACING)
        self.external_window.add(vbox)
        self.external = ViewerWidget(self.app.settings)
        vbox.pack_start(self.external, True, True, 0)
        self.external_window.connect("delete-event", self._externalWindowDeleteCb)
        self.external_window.connect("configure-event", self._externalWindowConfigureCb)
        self.external_vbox = vbox
        self.external_vbox.show_all()

        # Buttons/Controls
        bbox = Gtk.HBox()
        boxalign = Gtk.Alignment(xalign=0.5, yalign=0.5, xscale=0.0, yscale=0.0)
        boxalign.add(bbox)
        self.pack_start(boxalign, False, True, 0)

        self.goToStart_button = Gtk.ToolButton(Gtk.STOCK_MEDIA_PREVIOUS)
        self.goToStart_button.connect("clicked", self._goToStartCb)
        self.goToStart_button.set_tooltip_text(_("Go to the beginning of the timeline"))
        self.goToStart_button.set_sensitive(False)
        bbox.pack_start(self.goToStart_button, False, True, 0)

        self.back_button = Gtk.ToolButton(Gtk.STOCK_MEDIA_REWIND)
        self.back_button.connect("clicked", self._backCb)
        self.back_button.set_tooltip_text(_("Go back one second"))
        self.back_button.set_sensitive(False)
        bbox.pack_start(self.back_button, False, True, 0)

        self.playpause_button = PlayPauseButton()
        self.playpause_button.connect("play", self._playButtonCb)
        bbox.pack_start(self.playpause_button, False, True, 0)
        self.playpause_button.set_sensitive(False)

        self.forward_button = Gtk.ToolButton(Gtk.STOCK_MEDIA_FORWARD)
        self.forward_button.connect("clicked", self._forwardCb)
        self.forward_button.set_tooltip_text(_("Go forward one second"))
        self.forward_button.set_sensitive(False)
        bbox.pack_start(self.forward_button, False, True, 0)

        self.goToEnd_button = Gtk.ToolButton(Gtk.STOCK_MEDIA_NEXT)
        self.goToEnd_button.connect("clicked", self._goToEndCb)
        self.goToEnd_button.set_tooltip_text(_("Go to the end of the timeline"))
        self.goToEnd_button.set_sensitive(False)
        bbox.pack_start(self.goToEnd_button, False, True, 0)

        # current time
        self.timecode_entry = TimeWidget()
        self.timecode_entry.setWidgetValue(0)
        self.timecode_entry.set_tooltip_text(_('Enter a timecode or frame number\nand press "Enter" to go to that position'))
        self.timecode_entry.connectActivateEvent(self._entryActivateCb)
        self.timecode_entry.connectFocusEvents(self._entryFocusInCb, self._entryFocusOutCb)
        bbox.pack_start(self.timecode_entry, False, 10, 0)
        self._haveUI = True

        # Identify widgets for AT-SPI, making our test suite easier to develop
        # These will show up in sniff, accerciser, etc.
        self.goToStart_button.get_accessible().set_name("goToStart_button")
        self.back_button.get_accessible().set_name("back_button")
        self.playpause_button.get_accessible().set_name("playpause_button")
        self.forward_button.get_accessible().set_name("forward_button")
        self.goToEnd_button.get_accessible().set_name("goToEnd_button")
        self.timecode_entry.get_accessible().set_name("timecode_entry")

        screen = Gdk.Screen.get_default()
        height = screen.get_height()
        if height >= 800:
            # show the controls and force the aspect frame to have at least the same
            # width (+110, which is a magic number to minimize dead padding).
            bbox.show_all()
            req = bbox.size_request()
            width = req.width
            height = req.height
            width += 110
            height = int(width / self.aframe.props.ratio)
            self.aframe.set_size_request(width, height)
        self.show_all()
        self.buttons = bbox
        self.buttons_container = boxalign

    def setDisplayAspectRatio(self, ratio):
        """
        Sets the DAR of the Viewer to the given ratio.

        @arg ratio: The aspect ratio to set on the viewer
        @type ratio: L{float}
        """
        self.debug("Setting ratio of %f [%r]", float(ratio), ratio)
        try:
            self.aframe.set_property("ratio", float(ratio))
        except:
            self.warning("could not set ratio !")

    def _entryActivateCb(self, entry):
        self._seekFromTimecodeWidget()

    def _entryFocusInCb(self, entry, event):
        self.app.gui.setActionsSensitive(False)

    def _entryFocusOutCb(self, entry, event):
        self._seekFromTimecodeWidget()
        self.app.gui.setActionsSensitive(True)

    def _seekFromTimecodeWidget(self):
        nanoseconds = self.timecode_entry.getWidgetValue()
        self.seeker.seek(nanoseconds)

    ## active Timeline calllbacks
    def _durationChangedCb(self, unused_pipeline, duration):
        if duration == 0:
            self._setUiActive(False)
        else:
            self._setUiActive(True)

    ## Control Gtk.Button callbacks

    def setZoom(self, zoom):
        """
        Zoom in or out of the transformation box canvas.
        This is called by clipproperties.
        """
        if self.target.box:
            maxSize = self.target.area
            width = int(float(maxSize.width) * zoom)
            height = int(float(maxSize.height) * zoom)
            area = ((maxSize.width - width) / 2,
                    (maxSize.height - height) / 2,
                    width, height)
            self.sink.set_render_rectangle(*area)
            self.target.box.update_size(area)
            self.target.zoom = zoom
            self.target.sink = self.sink
            self.target.renderbox()

    def _playButtonCb(self, unused_button, playing):
        self.app.current.pipeline.togglePlayback()

    def _goToStartCb(self, unused_button):
        self.seeker.seek(0)

    def _backCb(self, unused_button):
        # Seek backwards one second
        self.seeker.seekRelative(0 - Gst.SECOND)

    def _forwardCb(self, unused_button):
        # Seek forward one second
        self.seeker.seekRelative(Gst.SECOND)

    def _goToEndCb(self, unused_button):
        try:
            end = self.app.current.pipeline.getDuration()
        except:
            self.warning("Couldn't get timeline duration")
        try:
            self.seeker.seek(end)
        except:
            self.warning("Couldn't seek to the end of the timeline")

    ## public methods for controlling playback

    def undock(self):
        if not self.undock_action:
            self.error("Cannot undock because undock_action is missing.")
            return
        if not self.docked:
            return

        self.docked = False
        self.settings.viewerDocked = False
        self.undock_action.set_label(_("Dock Viewer"))
        self.target = self.external

        self.remove(self.buttons_container)
        self.external_vbox.pack_end(self.buttons_container, False, False, 0)
        self.external_window.set_type_hint(Gdk.WindowTypeHint.UTILITY)
        self.external_window.show()

        self.fullscreen_button = Gtk.ToggleToolButton(Gtk.STOCK_FULLSCREEN)
        self.fullscreen_button.set_tooltip_text(_("Show this window in fullscreen"))
        self.buttons.pack_end(self.fullscreen_button, expand=False, fill=False, padding=6)
        self.fullscreen_button.show()
        self.fullscreen_button.connect("toggled", self._toggleFullscreen)

        # if we are playing, switch output immediately
        if self.sink:
            self._switch_output_window()
        self.hide()
        self.external_window.move(self.settings.viewerX, self.settings.viewerY)
        self.external_window.resize(self.settings.viewerWidth, self.settings.viewerHeight)

    def dock(self):
        if not self.undock_action:
            self.error("Cannot dock because undock_action is missing.")
            return
        if self.docked:
            return
        self.docked = True
        self.settings.viewerDocked = True
        self.undock_action.set_label(_("Undock Viewer"))
        self.target = self.internal

        self.fullscreen_button.destroy()
        self.external_vbox.remove(self.buttons_container)
        self.pack_end(self.buttons_container, False, False, 0)
        self.show()
        # if we are playing, switch output immediately
        if self.sink:
            self._switch_output_window()
        self.external_window.hide()

    def _toggleDocked(self, action):
        if self.docked:
            self.undock()
        else:
            self.dock()

    def _toggleFullscreen(self, widget):
        if widget.get_active():
            self.external_window.hide()
            # GTK doesn't let us fullscreen utility windows
            self.external_window.set_type_hint(Gdk.WindowTypeHint.NORMAL)
            self.external_window.show()
            self.external_window.fullscreen()
            widget.set_tooltip_text(_("Exit fullscreen mode"))
        else:
            self.external_window.unfullscreen()
            widget.set_tooltip_text(_("Show this window in fullscreen"))
            self.external_window.hide()
            self.external_window.set_type_hint(Gdk.WindowTypeHint.UTILITY)
            self.external_window.show()

    def _positionCb(self, unused_pipeline, position):
        """
        If the timeline position changed, update the viewer UI widgets.

        This is meant to be called either by the gobject timer when playing,
        or by mainwindow's _timelineSeekCb when the timer is disabled.
        """
        self.timecode_entry.setWidgetValue(position, False)

    def clipTrimPreview(self, tl_obj, position):
        """
        While a clip is being trimmed, show a live preview of it.
        """
        if isinstance(tl_obj, GES.TitleClip) or tl_obj.props.is_image or not hasattr(tl_obj, "get_uri"):
            self.log("%s is an image or has no URI, so not previewing trim" % tl_obj)
            return False

        clip_uri = tl_obj.props.uri
        cur_time = time()
        if not self._tmp_pipeline:
            self.debug("Creating temporary pipeline for clip %s, position %s",
                clip_uri, print_ns(position))

            self._oldTimelinePos = self.pipeline.getPosition()
            self._tmp_pipeline = Gst.ElementFactory.make("playbin", None)
            self._tmp_pipeline.set_property("uri", clip_uri)
            self.setPipeline(SimplePipeline(self._tmp_pipeline, self._tmp_pipeline))
            self._lastClipTrimTime = cur_time
        if (cur_time - self._lastClipTrimTime) > 0.2:
            # Do not seek more than once every 200 ms (for performance)
            self._tmp_pipeline.seek_simple(Gst.Format.TIME, Gst.SeekFlags.FLUSH, position)
            self._lastClipTrimTime = cur_time

    def clipTrimPreviewFinished(self):
        """
        After trimming a clip, reset the project pipeline into the viewer.
        """
        if self._tmp_pipeline is not None:
            self._tmp_pipeline.set_state(Gst.State.NULL)
            self._tmp_pipeline = None  # Free the memory
            self.setPipeline(self.app.current.pipeline, self._oldTimelinePos)
            self.debug("Back to old pipeline")

    def _pipelineStateChangedCb(self, pipeline, state):
        """
        When playback starts/stops, update the viewer widget,
        play/pause button and (un)inhibit the screensaver.

        This is meant to be called by mainwindow.
        """
        self.info("current state changed : %s", state)
        if int(state) == int(Gst.State.PLAYING):
            self.playpause_button.setPause()
            self.system.inhibitScreensaver(self.INHIBIT_REASON)
        elif int(state) == int(Gst.State.PAUSED):
            self.playpause_button.setPlay()
            self.system.uninhibitScreensaver(self.INHIBIT_REASON)
        else:
            self.sink = None
            self.system.uninhibitScreensaver(self.INHIBIT_REASON)
        self.internal._currentStateCb(self.pipeline, state)

    def _switch_output_window(self):
        Gdk.threads_enter()
        # Prevent cases where target has no "window_xid" (yes, it happens!):
        self.target.show()
        self.sink.set_window_handle(self.target.window_xid)
        self.sink.expose()
        Gdk.threads_leave()
コード例 #33
0
ファイル: viewer.py プロジェクト: cmutti/pitivi
class ViewerContainer(Gtk.Box, Loggable):

    """
    A wiget holding a viewer and the controls.
    """
    __gtype_name__ = 'ViewerContainer'
    __gsignals__ = {
        "activate-playback-controls": (GObject.SignalFlags.RUN_LAST,
                                       None, (GObject.TYPE_BOOLEAN,)),
    }

    INHIBIT_REASON = _("Currently playing")

    def __init__(self, app):
        Gtk.Box.__init__(self)
        self.set_border_width(SPACING)
        self.app = app
        self.settings = app.settings
        self.system = app.system

        Loggable.__init__(self)
        self.log("New ViewerContainer")

        self.pipeline = None
        self.docked = True
        self.seeker = Seeker()
        self.target = None

        # Only used for restoring the pipeline position after a live clip trim
        # preview:
        self._oldTimelinePos = None

        self._haveUI = False

        self._createUi()

        self.__owning_pipeline = False
        if not self.settings.viewerDocked:
            self.undock()

    def setPipeline(self, pipeline, position=None):
        """
        Set the Viewer to the given Pipeline.

        Properly switches the currently set action to that new Pipeline.

        @param pipeline: The Pipeline to switch to.
        @type pipeline: L{Pipeline}.
        @param position: Optional position to seek to initially.
        """
        self._disconnectFromPipeline()

        if self.target:
            parent = self.target.get_parent()
            if parent:
                parent.remove(self.target)

        self.debug("New pipeline: %r", pipeline)
        self.pipeline = pipeline
        if position:
            self.seeker.seek(position)

        self.pipeline.connect("state-change", self._pipelineStateChangedCb)
        self.pipeline.connect("position", self._positionCb)
        self.pipeline.connect("duration-changed", self._durationChangedCb)

        self.__owning_pipeline = False
        self.__createNewViewer()
        self._setUiActive()

        self.pipeline.pause()

    def __createNewViewer(self):
        self.sink = self.pipeline.createSink()
        self.pipeline.setSink(self.sink)

        self.target = ViewerWidget(self.sink, self.app)

        if self.docked:
            self.pack_start(self.target, True, True, 0)
            screen = Gdk.Screen.get_default()
            height = screen.get_height()
            if height >= 800:
                # show the controls and force the aspect frame to have at least the same
                # width (+110, which is a magic number to minimize dead padding).
                req = self.buttons.size_request()
                width = req.width
                height = req.height
                width += 110
                height = int(width / self.target.props.ratio)
                self.target.set_size_request(width, height)
        else:
            self.external_vbox.pack_start(self.target, False, False, 0)
            self.target.props.expand = True
            self.external_vbox.child_set(self.target, fill=True)

        self.setDisplayAspectRatio(self.app.project_manager.current_project.getDAR())
        self.target.show_all()

    def _disconnectFromPipeline(self):
        self.debug("Previous pipeline: %r", self.pipeline)
        if self.pipeline is None:
            # silently return, there's nothing to disconnect from
            return

        self.pipeline.disconnect_by_func(self._pipelineStateChangedCb)
        self.pipeline.disconnect_by_func(self._positionCb)
        self.pipeline.disconnect_by_func(self._durationChangedCb)

        if self.__owning_pipeline:
            self.pipeline.release()
        self.pipeline = None

    def _setUiActive(self, active=True):
        self.debug("active %r", active)
        self.set_sensitive(active)
        if self._haveUI:
            for item in [self.goToStart_button, self.back_button,
                         self.playpause_button, self.forward_button,
                         self.goToEnd_button, self.timecode_entry]:
                item.set_sensitive(active)
        if active:
            self.emit("activate-playback-controls", True)

    def _externalWindowDeleteCb(self, unused_window, unused_event):
        self.dock()
        return True

    def _externalWindowConfigureCb(self, unused_window, event):
        self.settings.viewerWidth = event.width
        self.settings.viewerHeight = event.height
        self.settings.viewerX = event.x
        self.settings.viewerY = event.y

    def _createUi(self):
        """ Creates the Viewer GUI """
        self.set_orientation(Gtk.Orientation.VERTICAL)

        self.external_window = Gtk.Window()
        vbox = Gtk.Box()
        vbox.set_orientation(Gtk.Orientation.VERTICAL)
        vbox.set_spacing(SPACING)
        self.external_window.add(vbox)
        self.external_window.connect(
            "delete-event", self._externalWindowDeleteCb)
        self.external_window.connect(
            "configure-event", self._externalWindowConfigureCb)
        self.external_vbox = vbox

        # Buttons/Controls
        bbox = Gtk.Box()
        bbox.set_orientation(Gtk.Orientation.HORIZONTAL)
        bbox.set_property("valign", Gtk.Align.CENTER)
        bbox.set_property("halign", Gtk.Align.CENTER)
        self.pack_end(bbox, False, False, SPACING)

        self.goToStart_button = Gtk.ToolButton()
        self.goToStart_button.set_icon_name("media-skip-backward")
        self.goToStart_button.connect("clicked", self._goToStartCb)
        self.goToStart_button.set_tooltip_text(
            _("Go to the beginning of the timeline"))
        self.goToStart_button.set_sensitive(False)
        bbox.pack_start(self.goToStart_button, False, False, 0)

        self.back_button = Gtk.ToolButton()
        self.back_button.set_icon_name("media-seek-backward")
        self.back_button.connect("clicked", self._backCb)
        self.back_button.set_tooltip_text(_("Go back one second"))
        self.back_button.set_sensitive(False)
        bbox.pack_start(self.back_button, False, False, 0)

        self.playpause_button = PlayPauseButton()
        self.playpause_button.connect("play", self._playButtonCb)
        bbox.pack_start(self.playpause_button, False, False, 0)
        self.playpause_button.set_sensitive(False)

        self.forward_button = Gtk.ToolButton()
        self.forward_button.set_icon_name("media-seek-forward")
        self.forward_button.connect("clicked", self._forwardCb)
        self.forward_button.set_tooltip_text(_("Go forward one second"))
        self.forward_button.set_sensitive(False)
        bbox.pack_start(self.forward_button, False, False, 0)

        self.goToEnd_button = Gtk.ToolButton()
        self.goToEnd_button.set_icon_name("media-skip-forward")
        self.goToEnd_button.connect("clicked", self._goToEndCb)
        self.goToEnd_button.set_tooltip_text(
            _("Go to the end of the timeline"))
        self.goToEnd_button.set_sensitive(False)
        bbox.pack_start(self.goToEnd_button, False, False, 0)

        self.timecode_entry = TimeWidget()
        self.timecode_entry.setWidgetValue(0)
        self.timecode_entry.set_tooltip_text(
            _('Enter a timecode or frame number\nand press "Enter" to go to that position'))
        self.timecode_entry.connectActivateEvent(self._entryActivateCb)
        bbox.pack_start(self.timecode_entry, False, 10, 0)

        self.undock_button = Gtk.ToolButton()
        self.undock_button.set_icon_name("view-restore")
        self.undock_button.connect("clicked", self.undock)
        self.undock_button.set_tooltip_text(
            _("Detach the viewer\nYou can re-attach it by closing the newly created window."))
        bbox.pack_start(self.undock_button, False, False, 0)

        self._haveUI = True

        # Identify widgets for AT-SPI, making our test suite easier to develop
        # These will show up in sniff, accerciser, etc.
        self.goToStart_button.get_accessible().set_name("goToStart_button")
        self.back_button.get_accessible().set_name("back_button")
        self.playpause_button.get_accessible().set_name("playpause_button")
        self.forward_button.get_accessible().set_name("forward_button")
        self.goToEnd_button.get_accessible().set_name("goToEnd_button")
        self.timecode_entry.get_accessible().set_name("timecode_entry")
        self.undock_button.get_accessible().set_name("undock_button")

        self.buttons = bbox
        self.buttons_container = bbox
        self.show_all()
        self.external_vbox.show_all()

    def setDisplayAspectRatio(self, ratio):
        self.debug("Setting aspect ratio to %f [%r]", float(ratio), ratio)
        self.target.setDisplayAspectRatio(ratio)

    def _entryActivateCb(self, unused_entry):
        self._seekFromTimecodeWidget()

    def _seekFromTimecodeWidget(self):
        nanoseconds = self.timecode_entry.getWidgetValue()
        self.seeker.seek(nanoseconds)

    # Active Timeline calllbacks
    def _durationChangedCb(self, unused_pipeline, duration):
        if duration == 0:
            self._setUiActive(False)
        else:
            self._setUiActive(True)

    def _playButtonCb(self, unused_button, unused_playing):
        self.app.project_manager.current_project.pipeline.togglePlayback()
        self.app.gui.focusTimeline()

    def _goToStartCb(self, unused_button):
        self.seeker.seek(0)
        self.app.gui.focusTimeline()

    def _backCb(self, unused_button):
        # Seek backwards one second
        self.seeker.seekRelative(0 - Gst.SECOND)
        self.app.gui.focusTimeline()

    def _forwardCb(self, unused_button):
        # Seek forward one second
        self.seeker.seekRelative(Gst.SECOND)
        self.app.gui.focusTimeline()

    def _goToEndCb(self, unused_button):
        end = self.app.project_manager.current_project.pipeline.getDuration()
        self.seeker.seek(end)
        self.app.gui.focusTimeline()

    # Public methods for controlling playback

    def undock(self, *unused_widget):
        if not self.docked:
            self.warning("The viewer is already undocked")
            return

        self.docked = False
        self.settings.viewerDocked = False
        self.remove(self.buttons_container)
        position = None
        if self.pipeline:
            position = self.pipeline.getPosition()
            self.pipeline.setState(Gst.State.NULL)
            self.remove(self.target)
            self.__createNewViewer()
        self.external_vbox.pack_end(self.buttons_container, False, False, 0)

        self.undock_button.hide()
        self.fullscreen_button = Gtk.ToggleToolButton()
        self.fullscreen_button.set_icon_name("view-fullscreen")
        self.fullscreen_button.set_tooltip_text(
            _("Show this window in fullscreen"))
        self.buttons.pack_end(
            self.fullscreen_button, expand=False, fill=False, padding=6)
        self.fullscreen_button.show()
        self.fullscreen_button.connect("toggled", self._toggleFullscreen)

        self.external_window.show()
        self.hide()
        self.external_window.move(self.settings.viewerX, self.settings.viewerY)
        self.external_window.resize(
            self.settings.viewerWidth, self.settings.viewerHeight)
        if self.pipeline:
            self.pipeline.pause()
            self.seeker.seek(position)

    def dock(self):
        if self.docked:
            self.warning("The viewer is already docked")
            return

        self.docked = True
        self.settings.viewerDocked = True

        if self.pipeline:
            position = self.pipeline.getPosition()
            self.pipeline.setState(Gst.State.NULL)
            self.external_vbox.remove(self.target)
            self.__createNewViewer()

        self.undock_button.show()
        self.fullscreen_button.destroy()
        self.external_vbox.remove(self.buttons_container)
        self.pack_end(self.buttons_container, False, False, 0)
        self.show()

        self.external_window.hide()
        if position:
            self.pipeline.pause()
            self.seeker.seek(position)

    def _toggleFullscreen(self, widget):
        if widget.get_active():
            self.external_window.hide()
            # GTK doesn't let us fullscreen utility windows
            self.external_window.set_type_hint(Gdk.WindowTypeHint.NORMAL)
            self.external_window.show()
            self.external_window.fullscreen()
            widget.set_tooltip_text(_("Exit fullscreen mode"))
        else:
            self.external_window.unfullscreen()
            widget.set_tooltip_text(_("Show this window in fullscreen"))
            self.external_window.hide()
            self.external_window.set_type_hint(Gdk.WindowTypeHint.UTILITY)
            self.external_window.show()

    def _positionCb(self, unused_pipeline, position):
        """
        If the timeline position changed, update the viewer UI widgets.

        This is meant to be called either by the gobject timer when playing,
        or by mainwindow's _timelineSeekCb when the timer is disabled.
        """
        self.timecode_entry.setWidgetValue(position, False)

    def clipTrimPreview(self, clip, position):
        """
        While a clip is being trimmed, show a live preview of it.
        """
        if not hasattr(clip, "get_uri") or isinstance(clip, GES.TitleClip) or clip.props.is_image:
            self.log(
                "%s is an image or has no URI, so not previewing trim" % clip)
            return False

        clip_uri = clip.props.uri
        cur_time = time()
        if self.pipeline == self.app.project_manager.current_project.pipeline:
            self.debug("Creating temporary pipeline for clip %s, position %s",
                       clip_uri, format_ns(position))
            self._oldTimelinePos = self.pipeline.getPosition(True)
            self.pipeline.set_state(Gst.State.NULL)
            self.setPipeline(AssetPipeline(clip))
            self.__owning_pipeline = True
            self._lastClipTrimTime = cur_time

        if (cur_time - self._lastClipTrimTime) > 0.2 and self.pipeline.getState() == Gst.State.PAUSED:
            # Do not seek more than once every 200 ms (for performance)
            self.pipeline.simple_seek(position)
            self._lastClipTrimTime = cur_time

    def clipTrimPreviewFinished(self):
        """
        After trimming a clip, reset the project pipeline into the viewer.
        """
        if self.pipeline is not self.app.project_manager.current_project.pipeline:
            self.pipeline.setState(Gst.State.NULL)
            # Using pipeline.getPosition() here does not work because for some
            # reason it's a bit off, that's why we need self._oldTimelinePos.
            self.setPipeline(
                self.app.project_manager.current_project.pipeline, self._oldTimelinePos)
            self._oldTimelinePos = None
            self.debug("Back to the project's pipeline")

    def _pipelineStateChangedCb(self, unused_pipeline, state, old_state):
        """
        When playback starts/stops, update the viewer widget,
        play/pause button and (un)inhibit the screensaver.

        This is meant to be called by mainwindow.
        """
        if int(state) == int(Gst.State.PLAYING):
            st = Gst.Structure.new_empty("play")
            self.app.write_action(st)
            self.playpause_button.setPause()
            self.system.inhibitScreensaver(self.INHIBIT_REASON)
        elif int(state) == int(Gst.State.PAUSED):
            if old_state != int(Gst.State.PAUSED):
                st = Gst.Structure.new_empty("pause")
                if old_state == int(Gst.State.PLAYING):
                    st.set_value("playback_time", float(self.pipeline.getPosition()) /
                                 Gst.SECOND)
                self.app.write_action(st)

            self.playpause_button.setPlay()
            self.system.uninhibitScreensaver(self.INHIBIT_REASON)
        else:
            self.system.uninhibitScreensaver(self.INHIBIT_REASON)
コード例 #34
0
ファイル: ruler.py プロジェクト: jojva/pitivi
class ScaleRuler(Gtk.DrawingArea, Zoomable, Loggable):

    __gsignals__ = {
        "button-press-event": "override",
        "button-release-event": "override",
        "motion-notify-event": "override",
        "scroll-event": "override",
        "seek": (GObject.SignalFlags.RUN_LAST, None,
                [GObject.TYPE_UINT64])
    }

    border = 0
    min_tick_spacing = 3
    scale = [0, 0, 0, 0.5, 1, 2, 5, 10, 15, 30, 60, 120, 300, 600, 3600]
    subdivide = ((1, 1.0), (2, 0.5), (10, .25))

    def __init__(self, instance, hadj):
        Gtk.DrawingArea.__init__(self)
        Zoomable.__init__(self)
        Loggable.__init__(self)
        self.log("Creating new ScaleRuler")
        self.app = instance
        self._seeker = Seeker()
        self.hadj = hadj
        hadj.connect("value-changed", self._hadjValueChangedCb)
        self.add_events(Gdk.EventMask.POINTER_MOTION_MASK |
            Gdk.EventMask.BUTTON_PRESS_MASK | Gdk.EventMask.BUTTON_RELEASE_MASK |
            Gdk.EventMask.SCROLL_MASK)

        self.pixbuf = None

        # all values are in pixels
        self.pixbuf_offset = 0
        self.pixbuf_offset_painted = 0
        # This is the number of width we allocate for the pixbuf
        self.pixbuf_multiples = 4

        self.position = 0  # In nanoseconds
        self.pressed = False
        self.min_frame_spacing = 5.0
        self.frame_height = 5.0
        self.frame_rate = Gst.Fraction(1 / 1)
        self.ns_per_frame = float(1 / self.frame_rate) * Gst.SECOND
        self.connect('draw', self.drawCb)
        self.connect('configure-event', self.configureEventCb)
        self.callback_id = None
        self.callback_id_scroll = None

    def _hadjValueChangedCb(self, hadj):
        self.pixbuf_offset = self.hadj.get_value()
        if self.callback_id_scroll is not None:
            GLib.source_remove(self.callback_id_scroll)
        self.callback_id_scroll = GLib.timeout_add(100, self._maybeUpdate)

## Zoomable interface override

    def _maybeUpdate(self):
        self.queue_draw()
        self.callback_id = None
        return False

    def zoomChanged(self):
        if self.callback_id is not None:
            GLib.source_remove(self.callback_id)
        self.callback_id = GLib.timeout_add(100, self._maybeUpdate)

## timeline position changed method

    def timelinePositionChanged(self, value, unused_frame=None):
        self.position = value
        self.queue_draw()

## Gtk.Widget overrides
    def configureEventCb(self, widget, event, data=None):
        self.debug("Configuring, height %d, width %d",
            widget.get_allocated_width(), widget.get_allocated_height())

        # Destroy previous buffer
        if self.pixbuf is not None:
            self.pixbuf.finish()
            self.pixbuf = None

        # Create a new buffer
        self.pixbuf = cairo.ImageSurface(cairo.FORMAT_ARGB32,
                widget.get_allocated_width(), widget.get_allocated_height())

        return False

    def drawCb(self, widget, cr):
        if self.pixbuf is not None:
            db = self.pixbuf

            # Create cairo context with double buffer as is DESTINATION
            cc = cairo.Context(db)

            #draw everything
            self.drawBackground(cc)
            self.drawRuler(cc)
            self.drawPosition(cc)
            db.flush()

            cr.set_source_surface(self.pixbuf, 0.0, 0.0)
            cr.paint()
        else:
            self.info('No buffer to paint buffer')

        return False

    def do_button_press_event(self, event):
        self.debug("button pressed at x:%d", event.x)
        self.pressed = True
        position = self.pixelToNs(event.x + self.pixbuf_offset)
        self._seeker.seek(position)
        return True

    def do_button_release_event(self, event):
        self.debug("button released at x:%d", event.x)
        self.pressed = False
        return False

    def do_motion_notify_event(self, event):
        position = self.pixelToNs(event.x + self.pixbuf_offset)
        if self.pressed:
            self.debug("motion at event.x %d", event.x)
            self._seeker.seek(position)

        human_time = beautify_length(position)
        cur_frame = int(position / self.ns_per_frame) + 1
        self.set_tooltip_text(human_time + "\n" + _("Frame #%d" % cur_frame))
        return False

    def do_scroll_event(self, event):
        if event.scroll.state & Gdk.ModifierType.CONTROL_MASK:
            # Control + scroll = zoom
            if event.scroll.direction == Gdk.ScrollDirection.UP:
                Zoomable.zoomIn()
                self.app.gui.timeline_ui.zoomed_fitted = False
            elif event.scroll.direction == Gdk.ScrollDirection.DOWN:
                Zoomable.zoomOut()
                self.app.gui.timeline_ui.zoomed_fitted = False
        else:
            # No modifier key held down, just scroll
            if (event.scroll.direction == Gdk.ScrollDirection.UP
            or event.scroll.direction == Gdk.ScrollDirection.LEFT):
                self.app.gui.timeline_ui.scroll_left()
            elif (event.scroll.direction == Gdk.ScrollDirection.DOWN
            or event.scroll.direction == Gdk.ScrollDirection.RIGHT):
                self.app.gui.timeline_ui.scroll_right()

    def setProjectFrameRate(self, rate):
        """
        Set the lowest scale based on project framerate
        """
        self.frame_rate = rate
        self.ns_per_frame = float(1 / self.frame_rate) * Gst.SECOND
        self.scale[0] = float(2 / rate)
        self.scale[1] = float(5 / rate)
        self.scale[2] = float(10 / rate)

## Drawing methods

    def drawBackground(self, cr):
        style = self.get_style_context()
        setCairoColor(cr, style.get_background_color(Gtk.StateFlags.NORMAL))
        cr.rectangle(0, 0, cr.get_target().get_width(), cr.get_target().get_height())
        cr.fill()
        offset = int(self.nsToPixel(Gst.CLOCK_TIME_NONE)) - self.pixbuf_offset
        if offset > 0:
            setCairoColor(cr, style.get_background_color(Gtk.StateFlags.ACTIVE))
            cr.rectangle(0, 0, int(offset), cr.get_target().get_height())
            cr.fill()

    def drawRuler(self, cr):
        # FIXME use system defaults
        cr.set_font_face(cairo.ToyFontFace("Cantarell"))
        cr.set_font_size(13)
        textwidth = cr.text_extents(time_to_string(0))[2]

        for scale in self.scale:
            spacing = Zoomable.zoomratio * scale
            if spacing >= textwidth * 1.5:
                break

        offset = self.pixbuf_offset % spacing
        self.drawFrameBoundaries(cr)
        self.drawTicks(cr, offset, spacing, scale)
        self.drawTimes(cr, offset, spacing, scale)

    def drawTick(self, cr, paintpos, height):
        # We need to use 0.5 pixel offsets to get a sharp 1 px line in cairo
        paintpos = int(paintpos - 0.5) + 0.5
        height = int(cr.get_target().get_height() * (1 - height))
        style = self.get_style_context()
        setCairoColor(cr, style.get_color(Gtk.StateType.NORMAL))
        cr.set_line_width(1)
        cr.move_to(paintpos, height)
        cr.line_to(paintpos, cr.get_target().get_height())
        cr.close_path()
        cr.stroke()

    def drawTicks(self, cr, offset, spacing, scale):
        for subdivide, height in self.subdivide:
            spc = spacing / float(subdivide)
            if spc < self.min_tick_spacing:
                break
            paintpos = -spacing + 0.5
            paintpos += spacing - offset
            while paintpos < cr.get_target().get_width():
                self.drawTick(cr, paintpos, height)
                paintpos += spc

    def drawTimes(self, cr, offset, spacing, scale):
        # figure out what the optimal offset is
        interval = long(Gst.SECOND * scale)
        seconds = self.pixelToNs(self.pixbuf_offset)
        paintpos = float(self.border) + 2
        if offset > 0:
            seconds = seconds - (seconds % interval) + interval
            paintpos += spacing - offset

        while paintpos < cr.get_target().get_width():
            if paintpos < self.nsToPixel(Gst.CLOCK_TIME_NONE):
                state = Gtk.StateType.ACTIVE
            else:
                state = Gtk.StateType.NORMAL
            timevalue = time_to_string(long(seconds))
            style = self.get_style_context()
            setCairoColor(cr, style.get_color(state))
            x_bearing, y_bearing = cr.text_extents("0")[:2]
            cr.move_to(int(paintpos), 1 - y_bearing)
            cr.show_text(timevalue)
            paintpos += spacing
            seconds += interval

    def drawFrameBoundaries(self, cr):
        """
        Draw the alternating rectangles that represent the project frames at
        high zoom levels. These are based on the framerate set in the project
        settings, not the actual frames on a video codec level.
        """
        frame_width = self.nsToPixel(self.ns_per_frame)
        if not frame_width >= self.min_frame_spacing:
            return

        offset = self.pixbuf_offset % frame_width
        height = cr.get_target().get_height()
        y = int(height - self.frame_height)
        # INSENSITIVE is a dark shade of gray, but lacks contrast
        # SELECTED will be bright blue and more visible to represent frames
        style = self.get_style_context()
        states = [style.get_background_color(Gtk.StateFlags.ACTIVE),
                  style.get_background_color(Gtk.StateFlags.SELECTED)]

        frame_num = int(self.pixelToNs(self.pixbuf_offset) * float(self.frame_rate) / Gst.SECOND)
        paintpos = self.pixbuf_offset - offset
        max_pos = cr.get_target().get_width() + self.pixbuf_offset
        while paintpos < max_pos:
            paintpos = self.nsToPixel(1 / float(self.frame_rate) * Gst.SECOND * frame_num)
            setCairoColor(cr, states[(frame_num + 1) % 2])
            cr.rectangle(0.5 + paintpos - self.pixbuf_offset, y, frame_width, height)
            cr.fill()
            frame_num += 1

    def drawPosition(self, context):
        # a simple RED line will do for now
        xpos = self.nsToPixel(self.position) + self.border - self.pixbuf_offset
        context.save()
        context.set_line_width(1.5)
        context.set_source_rgb(1.0, 0, 0)
        context.move_to(xpos, 0)
        context.line_to(xpos, context.get_target().get_height())
        context.stroke()
        context.restore()
コード例 #35
0
class TitleEditor(Loggable):
    def __init__(self, instance, uimap):
        Loggable.__init__(self)
        Signallable.__init__(self)
        self.app = instance
        self.bt = {}
        self.settings = {}
        self.source = None
        self.created = False
        self.seeker = Seeker()

        #Drag attributes
        self._drag_events = []
        self._signals_connected = False

        self._createUI()
        self.textbuffer = Gtk.TextBuffer()
        self.pangobuffer = InteractivePangoBuffer()
        self.textarea.set_buffer(self.pangobuffer)

        self.textbuffer.connect("changed", self._updateSourceText)
        self.pangobuffer.connect("changed", self._updateSourceText)

        #Connect buttons
        self.pangobuffer.setup_widget_from_pango(self.bt["bold"],
                                                 "<b>bold</b>")
        self.pangobuffer.setup_widget_from_pango(self.bt["italic"],
                                                 "<i>italic</i>")

    def _createUI(self):
        builder = Gtk.Builder()
        builder.add_from_file(os.path.join(get_ui_dir(), "titleeditor.ui"))
        builder.connect_signals(self)
        self.widget = builder.get_object("box1")  # To be used by tabsmanager
        self.infobar = builder.get_object("infobar")
        self.editing_box = builder.get_object("editing_box")
        self.textarea = builder.get_object("textview")
        self.markup_button = builder.get_object("markupToggle")
        toolbar = builder.get_object("toolbar")
        toolbar.get_style_context().add_class("inline-toolbar")

        buttons = ["bold", "italic", "font", "font_fore_color", "back_color"]
        for button in buttons:
            self.bt[button] = builder.get_object(button)

        settings = ["valignment", "halignment", "xpos", "ypos"]
        for setting in settings:
            self.settings[setting] = builder.get_object(setting)

        for n, en in {
                _("Custom"): "position",
                _("Top"): "top",
                _("Center"): "center",
                _("Bottom"): "bottom",
                _("Baseline"): "baseline"
        }.items():
            self.settings["valignment"].append(en, n)

        for n, en in {
                _("Custom"): "position",
                _("Left"): "left",
                _("Center"): "center",
                _("Right"): "right"
        }.items():
            self.settings["halignment"].append(en, n)
        self._deactivate()

    def _textviewFocusedCb(self, unused_widget, unused_event):
        self.app.gui.setActionsSensitive(False)

    def _textviewUnfocusedCb(self, unused_widget, unused_event):
        self.app.gui.setActionsSensitive(True)

    def _backgroundColorButtonCb(self, widget):
        self.textarea.modify_base(self.textarea.get_state(),
                                  widget.get_color())
        color = widget.get_rgba()
        color_int = 0
        color_int += int(color.red * 255) * 256**2
        color_int += int(color.green * 255) * 256**1
        color_int += int(color.blue * 255) * 256**0
        color_int += int(color.alpha * 255) * 256**3
        self.debug("Setting title background color to %s", hex(color_int))
        self.source.set_background(color_int)

    def _frontTextColorButtonCb(self, widget):
        suc, a, t, s = Pango.parse_markup(
            "<span color='" + widget.get_color().to_string() +
            "'>color</span>", -1, u'\x00')
        ai = a.get_iterator()
        font, lang, attrs = ai.get_font()
        tags = self.pangobuffer.get_tags_from_attrs(None, None, attrs)
        self.pangobuffer.apply_tag_to_selection(tags[0])

    def _fontButtonCb(self, widget):
        font_desc = widget.get_font_name().split(" ")
        font_face = " ".join(font_desc[:-1])
        font_size = str(int(font_desc[-1]) * 1024)
        text = "<span face='" + font_face + "'><span size='" + font_size + "'>text</span></span>"
        suc, a, t, s = Pango.parse_markup(text, -1, u'\x00')
        ai = a.get_iterator()
        font, lang, attrs = ai.get_font()
        tags = self.pangobuffer.get_tags_from_attrs(font, None, attrs)
        for tag in tags:
            self.pangobuffer.apply_tag_to_selection(tag)

    def _markupToggleCb(self, markup_button):
        # FIXME: either make this feature rock-solid or replace it by a
        # Clear markup" button. Currently it is possible for the user to create
        # invalid markup (causing errors) or to get our textbuffer confused
        self.textbuffer.disconnect_by_func(self._updateSourceText)
        self.pangobuffer.disconnect_by_func(self._updateSourceText)
        if markup_button.get_active():
            self.textbuffer.set_text(self.pangobuffer.get_text())
            self.textarea.set_buffer(self.textbuffer)
            for name in self.bt:
                self.bt[name].set_sensitive(False)
        else:
            txt = self.textbuffer.get_text(self.textbuffer.get_start_iter(),
                                           self.textbuffer.get_end_iter(),
                                           True)
            self.pangobuffer.set_text(txt)
            self.textarea.set_buffer(self.pangobuffer)
            for name in self.bt:
                self.bt[name].set_sensitive(True)

        self.textbuffer.connect("changed", self._updateSourceText)
        self.pangobuffer.connect("changed", self._updateSourceText)

    def _activate(self):
        """
        Show the title editing UI widgets and hide the infobar
        """
        self.infobar.hide()
        self.textarea.show()
        self.editing_box.show()
        self._connect_signals()

    def _deactivate(self):
        """
        Reset the title editor interface to its default look
        """
        self.infobar.show()
        self.textarea.hide()
        self.editing_box.hide()
        self._disconnect_signals()

    def _updateFromSource(self):
        if self.source is not None:
            source_text = self.source.get_text()
            self.log("Title text set to %s", source_text)
            if source_text is None:
                # FIXME: sometimes we get a TextOverlay/TitleSource
                # without a valid text property. This should not happen.
                source_text = ""
                self.warning(
                    'Source did not have a text property, setting it to "" to avoid pango choking up on None'
                )
            self.pangobuffer.set_text(source_text)
            self.textbuffer.set_text(source_text)
            self.settings['xpos'].set_value(self.source.get_xpos())
            self.settings['ypos'].set_value(self.source.get_ypos())
            self.settings['valignment'].set_active_id(
                self.source.get_valignment().value_name)
            self.settings['halignment'].set_active_id(
                self.source.get_halignment().value_name)
            if hasattr(self.source, "get_background"):
                self.bt["back_color"].set_visible(True)
                color = self.source.get_background()
                color = Gdk.RGBA(color / 256**2 % 256 / 255.,
                                 color / 256**1 % 256 / 255.,
                                 color / 256**0 % 256 / 255.,
                                 color / 256**3 % 256 / 255.)
                self.bt["back_color"].set_rgba(color)
            else:
                self.bt["back_color"].set_visible(False)

    def _updateSourceText(self, updated_obj):
        if self.source is not None:
            if self.markup_button.get_active():
                text = self.textbuffer.get_text(
                    self.textbuffer.get_start_iter(),
                    self.textbuffer.get_end_iter(), True)
            else:
                text = self.pangobuffer.get_text()
            self.log("Source text updated to %s", text)
            self.source.set_text(text)
            self.seeker.flush()

    def _updateSource(self, updated_obj):
        """
        Handle changes in one of the advanced property widgets at the bottom
        """
        if self.source is None:
            return
        for name, obj in self.settings.items():
            if obj == updated_obj:
                if name == "valignment":
                    self.source.set_valignment(
                        getattr(GES.TextVAlign,
                                obj.get_active_id().upper()))
                    self.settings["ypos"].set_visible(
                        obj.get_active_id() == "position")
                elif name == "halignment":
                    self.source.set_halignment(
                        getattr(GES.TextHAlign,
                                obj.get_active_id().upper()))
                    self.settings["xpos"].set_visible(
                        obj.get_active_id() == "position")
                elif name == "xpos":
                    self.settings["halignment"].set_active_id("position")
                    self.source.set_xpos(obj.get_value())
                elif name == "ypos":
                    self.settings["valignment"].set_active_id("position")
                    self.source.set_ypos(obj.get_value())
                self.seeker.flush()
                return

    def _reset(self):
        #TODO: reset not only text
        self.markup_button.set_active(False)
        self.pangobuffer.set_text("")
        self.textbuffer.set_text("")
        #Set right buffer
        self._markupToggleCb(self.markup_button)

    def set_source(
            self,
            source,
            created=False):  # FIXME: this "created" boolean param is a hack
        """
        Set the GESTitleClip to be used with the title editor.
        This can be called either from the title editor in _createCb, or by
        track.py to set the source to None.
        """
        self.debug("Source set to %s", str(source))
        self.source = source
        self._reset()
        self.created = created
        # We can't just assert source is not None... because track.py may ask us
        # to reset the source to None
        if source is None:
            self._deactivate()
        else:
            assert isinstance(source, GES.TextOverlay) or \
                isinstance(source, GES.TitleSource) or \
                isinstance(source, GES.TitleClip)
            self._updateFromSource()
            self._activate()

    def _createCb(self, unused_button):
        """
        The user clicked the "Create and insert" button, initialize the UI
        """
        source = GES.TitleClip()
        source.set_text("")
        source.set_duration(long(Gst.SECOND * 5))
        self.set_source(source, True)
        # TODO: insert on the current layer at the playhead position.
        # If no space is available, create a new layer to insert to on top.
        self.app.gui.timeline_ui.insertEnd([self.source])
        self.app.gui.timeline_ui.timeline.selection.setToObj(
            self.source, SELECT)
        #After insertion consider as not created
        self.created = False

    def _connect_signals(self):
        if not self._signals_connected:
            self.app.gui.viewer.target.connect("motion-notify-event",
                                               self.drag_notify_event)
            self.app.gui.viewer.target.connect("button-press-event",
                                               self.drag_press_event)
            self.app.gui.viewer.target.connect("button-release-event",
                                               self.drag_release_event)
            self._signals_connected = True

    def _disconnect_signals(self):
        if not self._signals_connected:
            return
        self.app.gui.viewer.target.disconnect_by_func(self.drag_notify_event)
        self.app.gui.viewer.target.disconnect_by_func(self.drag_press_event)
        self.app.gui.viewer.target.disconnect_by_func(self.drag_release_event)
        self._signals_connected = False

    def drag_press_event(self, widget, event):
        if event.button == 1:
            self._drag_events = [(event.x, event.y)]
            #Update drag by drag event change, but not too often
            self.timeout = GLib.timeout_add(100, self.drag_update_event)
            #If drag goes out for 0.3 second, and do not come back, consider drag end
            self._drag_updated = True
            self.timeout = GLib.timeout_add(1000, self.drag_possible_end_event)

    def drag_possible_end_event(self):
        if self._drag_updated:
            #Updated during last timeout, wait more
            self._drag_updated = False
            return True
        else:
            #Not updated - posibly out of bounds, stop drag
            self.log("Drag timeout")
            self._drag_events = []
            return False

    def drag_update_event(self):
        if len(self._drag_events) > 0:
            st = self._drag_events[0]
            self._drag_events = [self._drag_events[-1]]
            e = self._drag_events[0]
            xdiff = e[0] - st[0]
            ydiff = e[1] - st[1]
            xdiff /= self.app.gui.viewer.target.get_allocated_width()
            ydiff /= self.app.gui.viewer.target.get_allocated_height()
            newxpos = self.settings["xpos"].get_value() + xdiff
            newypos = self.settings["ypos"].get_value() + ydiff
            self.settings["xpos"].set_value(newxpos)
            self.settings["ypos"].set_value(newypos)
            self.seeker.flush()
            return True
        else:
            return False

    def drag_notify_event(self, widget, event):
        if len(self._drag_events
               ) > 0 and event.get_state() & Gdk.ModifierType.BUTTON1_MASK:
            self._drag_updated = True
            self._drag_events.append((event.x, event.y))
            st = self._drag_events[0]
            e = self._drag_events[-1]

    def drag_release_event(self, widget, event):
        self._drag_events = []

    def tab_switched(self, unused_notebook, arg1, arg2):
        if arg2 == 2:
            self._connect_signals()
        else:
            self._disconnect_signals()
コード例 #36
0
class ViewerWidget(Gtk.AspectFrame, Loggable):
    """
    Widget for displaying a GStreamer video sink.

    @ivar settings: The settings of the application.
    @type settings: L{GlobalSettings}
    """

    __gsignals__ = {}

    def __init__(self, settings=None, realizedCb=None):
        # Prevent black frames and flickering while resizing or changing focus:
        # The aspect ratio gets overridden by setDisplayAspectRatio.
        Gtk.AspectFrame.__init__(self,
                                 xalign=0.5,
                                 yalign=1.0,
                                 ratio=4.0 / 3.0,
                                 obey_child=False)
        Loggable.__init__(self)

        self.drawing_area = Gtk.DrawingArea()
        self.drawing_area.set_double_buffered(False)
        self.drawing_area.connect("draw", self._drawCb, None)
        # We keep the ViewerWidget hidden initially, or the desktop wallpaper
        # would show through the non-double-buffered widget!
        if realizedCb:
            self.drawing_area.connect("realize", realizedCb, self)
        self.add(self.drawing_area)

        self.drawing_area.show()

        self.seeker = Seeker()
        self.settings = settings
        self.box = None
        self.stored = False
        self.area = None
        self.zoom = 1.0
        self.sink = None
        self.pixbuf = None
        self.pipeline = None
        self.transformation_properties = None
        # FIXME PyGi Styling with Gtk3
        # for state in range(Gtk.StateType.INSENSITIVE + 1):
        # self.modify_bg(state, self.style.black)

    def _drawCb(self, unused, unused1, unused2):
        if self.sink:
            self.sink.expose()

    def setDisplayAspectRatio(self, ratio):
        self.set_property("ratio", float(ratio))

    def init_transformation_events(self):
        self.fixme("TransformationBox disabled")
        """
        self.set_events(Gdk.EventMask.BUTTON_PRESS_MASK
                        | Gdk.EventMask.BUTTON_RELEASE_MASK
                        | Gdk.EventMask.POINTER_MOTION_MASK
                        | Gdk.EventMask.POINTER_MOTION_HINT_MASK)
        """

    def show_box(self):
        self.fixme("TransformationBox disabled")
        """
        if not self.box:
            self.box = TransformationBox(self.settings)
            self.box.init_size(self.area)
            self._update_gradient()
            self.connect("button-press-event", self.button_press_event)
            self.connect("button-release-event", self.button_release_event)
            self.connect("motion-notify-event", self.motion_notify_event)
            self.connect("size-allocate", self._sizeCb)
            self.box.set_transformation_properties(self.transformation_properties)
            self.renderbox()
        """

    def _sizeCb(self, unused_widget, unused_area):
        # The transformation box is cleared when using regular rendering
        # so we need to flush the pipeline
        self.seeker.flush()

    def hide_box(self):
        if self.box:
            self.box = None
            self.disconnect_by_func(self.button_press_event)
            self.disconnect_by_func(self.button_release_event)
            self.disconnect_by_func(self.motion_notify_event)
            self.seeker.flush()
            self.zoom = 1.0
            if self.sink:
                self.sink.set_render_rectangle(*self.area)

    def set_transformation_properties(self, transformation_properties):
        self.transformation_properties = transformation_properties

    def _store_pixbuf(self):
        """
        When not playing, store a pixbuf of the current viewer image.
        This will allow it to be restored for the transformation box.
        """

        if self.box and self.zoom != 1.0:
            # The transformation box is active and dezoomed
            # crop away 1 pixel border to avoid artefacts on the pixbuf

            self.pixbuf = Gdk.pixbuf_get_from_window(self.get_window(),
                                                     self.box.area.x + 1,
                                                     self.box.area.y + 1,
                                                     self.box.area.width - 2,
                                                     self.box.area.height - 2)
        else:
            self.pixbuf = Gdk.pixbuf_get_from_window(
                self.get_window(), 0, 0,
                self.get_window().get_width(),
                self.get_window().get_height())

        self.stored = True

    def button_release_event(self, unused_widget, event):
        if event.button == 1:
            self.box.update_effect_properties()
            self.box.release_point()
            self.seeker.flush()
            self.stored = False
        return True

    def button_press_event(self, unused_widget, event):
        if event.button == 1:
            self.box.select_point(event)
        return True

    def _currentStateCb(self, unused_pipeline, unused_state):
        self.fixme("TransformationBox disabled")
        """
        self.pipeline = pipeline
        if state == Gst.State.PAUSED:
            self._store_pixbuf()
        self.renderbox()
        """

    def motion_notify_event(self, unused_widget, event):
        if event.get_state() & Gdk.ModifierType.BUTTON1_MASK:
            if self.box.transform(event):
                if self.stored:
                    self.renderbox()
        return True

    def do_expose_event(self, event):
        self.area = event.area
        if self.box:
            self._update_gradient()
            if self.zoom != 1.0:
                width = int(float(self.area.width) * self.zoom)
                height = int(float(self.area.height) * self.zoom)
                area = ((self.area.width - width) / 2,
                        (self.area.height - height) / 2, width, height)
                self.sink.set_render_rectangle(*area)
            else:
                area = self.area
            self.box.update_size(area)
            self.renderbox()

    def _update_gradient(self):
        self.gradient_background = cairo.LinearGradient(
            0, 0, 0, self.area.height)
        self.gradient_background.add_color_stop_rgb(0.00, .1, .1, .1)
        self.gradient_background.add_color_stop_rgb(0.50, .2, .2, .2)
        self.gradient_background.add_color_stop_rgb(1.00, .5, .5, .5)

    def renderbox(self):
        if self.box:
            cr = self.window.cairo_create()
            cr.push_group()

            if self.zoom != 1.0:
                # draw some nice background for zoom out
                cr.set_source(self.gradient_background)
                cr.rectangle(0, 0, self.area.width, self.area.height)
                cr.fill()

                # translate the drawing of the zoomed out box
                cr.translate(self.box.area.x, self.box.area.y)

            # clear the drawingarea with the last known clean video frame
            # translate when zoomed out
            if self.pixbuf:
                if self.box.area.width != self.pixbuf.get_width():
                    scale = float(self.box.area.width) / float(
                        self.pixbuf.get_width())
                    cr.save()
                    cr.scale(scale, scale)
                cr.set_source_pixbuf(self.pixbuf, 0, 0)
                cr.paint()
                if self.box.area.width != self.pixbuf.get_width():
                    cr.restore()

            if self.pipeline and self.pipeline.getState() == Gst.State.PAUSED:
                self.box.draw(cr)
            cr.pop_group_to_source()
            cr.paint()
コード例 #37
0
class TitleEditor(Loggable):
    """
    Widget for configuring the selected title.

    @type app: L{Pitivi}
    """
    def __init__(self, app):
        Loggable.__init__(self)
        self.app = app
        self.action_log = app.action_log
        self.settings = {}
        self.source = None
        self.seeker = Seeker()

        # Drag attributes
        self._drag_events = []
        self._signals_connected = False
        self._setting_props = False
        self._setting_initial_props = False
        self._children_props_handler = None

        self._createUI()

    def _createUI(self):
        builder = Gtk.Builder()
        builder.add_from_file(os.path.join(get_ui_dir(), "titleeditor.ui"))
        builder.connect_signals(self)
        self.widget = builder.get_object("box1")  # To be used by tabsmanager
        self.infobar = builder.get_object("infobar")
        self.editing_box = builder.get_object("editing_box")
        self.textarea = builder.get_object("textview")
        toolbar = builder.get_object("toolbar")
        toolbar.get_style_context().add_class(Gtk.STYLE_CLASS_INLINE_TOOLBAR)

        self.textbuffer = Gtk.TextBuffer()
        self.textarea.set_buffer(self.textbuffer)
        self.textbuffer.connect("changed", self._textChangedCb)

        self.font_button = builder.get_object("fontbutton1")
        self.foreground_color_button = builder.get_object("fore_text_color")
        self.background_color_button = builder.get_object("back_color")

        settings = ["valignment", "halignment", "xpos", "ypos"]
        for setting in settings:
            self.settings[setting] = builder.get_object(setting)

        for n, en in list({
                _("Custom"): "position",
                _("Top"): "top",
                _("Center"): "center",
                _("Bottom"): "bottom",
                _("Baseline"): "baseline"
        }.items()):
            self.settings["valignment"].append(en, n)

        for n, en in list({
                _("Custom"): "position",
                _("Left"): "left",
                _("Center"): "center",
                _("Right"): "right"
        }.items()):
            self.settings["halignment"].append(en, n)
        self._deactivate()

    def _setChildProperty(self, name, value):
        self.action_log.begin("Title %s change" % name)
        self._setting_props = True
        self.source.set_child_property(name, value)
        self._setting_props = False
        self.action_log.commit()

    def _backgroundColorButtonCb(self, widget):
        color = gdk_rgba_to_argb(widget.get_rgba())
        self.debug("Setting title background color to %x", color)
        self._setChildProperty("foreground-color", color)

    def _frontTextColorButtonCb(self, widget):
        color = gdk_rgba_to_argb(widget.get_rgba())
        self.debug("Setting title foreground color to %x", color)
        # TODO: Use set_text_color when we work with TitleSources instead of
        # TitleClips
        self._setChildProperty("color", color)

    def _fontButtonCb(self, widget):
        font_desc = widget.get_font_desc().to_string()
        self.debug("Setting font desc to %s", font_desc)
        self._setChildProperty("font-desc", font_desc)

    def _activate(self):
        """
        Show the title editing UI widgets and hide the infobar
        """
        self.infobar.hide()
        self.textarea.show()
        self.editing_box.show()
        self._connect_signals()

    def _deactivate(self):
        """
        Reset the title editor interface to its default look
        """
        self.infobar.show()
        self.textarea.hide()
        self.editing_box.hide()
        self._disconnect_signals()

    def _setWidgetText(self):
        res, source_text = self.source.get_child_property("text")
        text = self.textbuffer.get_text(self.textbuffer.get_start_iter(),
                                        self.textbuffer.get_end_iter(), True)
        if text == source_text:
            return False

        if res is False:
            # FIXME: sometimes we get a TextOverlay/TitleSource
            # without a valid text property. This should not happen.
            source_text = ""
            self.warning(
                'Source did not have a text property, setting it to "" to avoid pango choking up on None'
            )
        self.log("Title text set to %s", source_text)
        self.textbuffer.set_text(source_text)

        return True

    def _updateFromSource(self):
        if not self.source:
            # Nothing to update from.
            return

        self._setWidgetText()
        self.settings['xpos'].set_value(
            self.source.get_child_property("xpos")[1])
        self.settings['ypos'].set_value(
            self.source.get_child_property("ypos")[1])
        self.settings['valignment'].set_active_id(
            self.source.get_child_property("valignment")[1].value_name)
        self.settings['halignment'].set_active_id(
            self.source.get_child_property("halignment")[1].value_name)

        font_desc = Pango.FontDescription.from_string(
            self.source.get_child_property("font-desc")[1])
        self.font_button.set_font_desc(font_desc)

        color = argb_to_gdk_rgba(self.source.get_child_property("color")[1])
        self.foreground_color_button.set_rgba(color)

        color = argb_to_gdk_rgba(
            self.source.get_child_property("foreground-color")[1])
        self.background_color_button.set_rgba(color)

    def _textChangedCb(self, unused_updated_obj):
        if not self.source:
            # Nothing to update.
            return

        text = self.textbuffer.get_text(self.textbuffer.get_start_iter(),
                                        self.textbuffer.get_end_iter(), True)
        self.log("Source text updated to %s", text)
        self._setChildProperty("text", text)

    def _updateSource(self, updated_obj):
        """
        Handle changes in one of the advanced property widgets at the bottom
        """
        if not self.source:
            # Nothing to update.
            return

        for name, obj in list(self.settings.items()):
            if obj == updated_obj:
                if name == "valignment":
                    value = getattr(GES.TextVAlign,
                                    obj.get_active_id().upper())
                    visible = obj.get_active_id() == "position"
                    self.settings["ypos"].set_visible(visible)
                elif name == "halignment":
                    value = getattr(GES.TextHAlign,
                                    obj.get_active_id().upper())
                    visible = obj.get_active_id() == "position"
                    self.settings["xpos"].set_visible(visible)
                else:
                    value = obj.get_value()

                self._setChildProperty(name, value)
                return

    def set_source(self, source):
        """
        Set the clip to be edited with this editor.

        @type source: L{GES.TitleSource}
        """
        self.debug("Source set to %s", source)
        self._deactivate()
        assert isinstance(source, GES.TextOverlay) or \
            isinstance(source, GES.TitleSource)
        self.source = source
        self._updateFromSource()
        self._activate()

    def unset_source(self):
        self._deactivate()
        self.source = None

    def _createCb(self, unused_button):
        """
        The user clicked the "Create and insert" button, initialize the UI
        """
        clip = GES.TitleClip()
        clip.set_text("")
        clip.set_duration(int(Gst.SECOND * 5))

        # TODO: insert on the current layer at the playhead position.
        # If no space is available, create a new layer to insert to on top.
        self.app.gui.timeline_ui.insertEnd([clip])
        self.app.gui.timeline_ui.timeline.selection.setToObj(clip, SELECT)

        self._setting_initial_props = True
        clip.set_color(FOREGROUND_DEFAULT_COLOR)
        clip.set_background(BACKGROUND_DEFAULT_COLOR)
        self._setting_initial_props = False

    def _propertyChangedCb(self, source, unused_gstelement, pspec):
        if self._setting_initial_props:
            return

        if self._setting_props:
            self.seeker.flush()
            return

        flush = False
        if pspec.name == "text":
            if self._setWidgetText() is True:
                flush = True
        elif pspec.name in ["xpos", "ypos"]:
            value = self.source.get_child_property(pspec.name)[1]
            if self.settings[pspec.name].get_value() == value:
                return

            flush = True
            self.settings[pspec.name].set_value(value)
        elif pspec.name in ["valignment", "halignment"]:
            value = self.source.get_child_property(pspec.name)[1].value_name
            if self.settings[pspec.name].get_active_id() == value:
                return

            flush = True
            self.settings[pspec.name].set_active_id(value)
        elif pspec.name == "font-desc":
            value = self.source.get_child_property("font-desc")[1]
            if self.font_button.get_font_desc() == value:
                return

            flush = True
            font_desc = Pango.FontDescription.from_string(value)
            self.font_button.set_font_desc(font_desc)
        elif pspec.name == "color":
            color = argb_to_gdk_rgba(
                self.source.get_child_property("color")[1])
            if color == self.foreground_color_button.get_rgba():
                return

            flush = True
            self.foreground_color_button.set_rgba(color)
        elif pspec.name == "foreground-color":
            color = argb_to_gdk_rgba(
                self.source.get_child_property("foreground-color")[1])

            if color == self.background_color_button.get_rgba():
                return

            flush = True
            self.background_color_button.set_rgba(color)

        if flush is True:
            self.seeker.flush()

    def _connect_signals(self):
        if self.source and not self._children_props_handler:
            self._children_props_handler = self.source.connect(
                'deep-notify', self._propertyChangedCb)
        if not self._signals_connected:
            self.app.gui.viewer.target.connect("motion-notify-event",
                                               self.drag_notify_event)
            self.app.gui.viewer.target.connect("button-press-event",
                                               self.drag_press_event)
            self.app.gui.viewer.target.connect("button-release-event",
                                               self.drag_release_event)
            self._signals_connected = True

    def _disconnect_signals(self):
        if self._children_props_handler is not None:
            self.source.disconnect(self._children_props_handler)
            self._children_props_handler = None

        if not self._signals_connected:
            return
        self.app.gui.viewer.target.disconnect_by_func(self.drag_notify_event)
        self.app.gui.viewer.target.disconnect_by_func(self.drag_press_event)
        self.app.gui.viewer.target.disconnect_by_func(self.drag_release_event)
        self._signals_connected = False

    def drag_press_event(self, unused_widget, event):
        if event.button == 1:
            self._drag_events = [(event.x, event.y)]
            # Update drag by drag event change, but not too often
            self.timeout = GLib.timeout_add(100, self.drag_update_event)
            # If drag goes out for 0.3 second, and do not come back, consider
            # drag end
            self._drag_updated = True
            self.timeout = GLib.timeout_add(1000, self.drag_possible_end_event)

    def drag_possible_end_event(self):
        if self._drag_updated:
            # Updated during last timeout, wait more
            self._drag_updated = False
            return True
        else:
            # Not updated - posibly out of bounds, stop drag
            self.log("Drag timeout")
            self._drag_events = []
            return False

    def drag_update_event(self):
        if len(self._drag_events) > 0:
            st = self._drag_events[0]
            self._drag_events = [self._drag_events[-1]]
            e = self._drag_events[0]
            xdiff = e[0] - st[0]
            ydiff = e[1] - st[1]
            xdiff /= self.app.gui.viewer.target.get_allocated_width()
            ydiff /= self.app.gui.viewer.target.get_allocated_height()
            newxpos = self.settings["xpos"].get_value() + xdiff
            newypos = self.settings["ypos"].get_value() + ydiff
            self.settings["xpos"].set_value(newxpos)
            self.settings["ypos"].set_value(newypos)
            return True
        else:
            return False

    def drag_notify_event(self, unused_widget, event):
        if len(self._drag_events
               ) > 0 and event.get_state() & Gdk.ModifierType.BUTTON1_MASK:
            self._drag_updated = True
            self._drag_events.append((event.x, event.y))
            st = self._drag_events[0]
            e = self._drag_events[-1]

    def drag_release_event(self, unused_widget, unused_event):
        self._drag_events = []

    def tabSwitchedCb(self, unused_notebook, page_widget, unused_page_index):
        if self.widget == page_widget:
            self._connect_signals()
        else:
            self._disconnect_signals()

    def selectionChangedCb(self, selection):
        selected_clip = selection.getSingleClip(GES.TitleClip)
        source = None
        if selected_clip:
            source = selected_clip.get_children(False)[0]

        if source:
            self.set_source(source)
        else:
            self.unset_source()
コード例 #38
0
class ScaleRuler(Gtk.DrawingArea, Zoomable, Loggable):
    """
    Widget for displaying the ruler.

    Displays a series of consecutive intervals. For each interval its beginning
    time is shown. If zoomed in enough, shows the frames in alternate colors.
    """

    __gsignals__ = {
        "button-press-event": "override",
        "button-release-event": "override",
        "motion-notify-event": "override",
        "scroll-event": "override",
        "seek": (GObject.SignalFlags.RUN_LAST, None, [GObject.TYPE_UINT64])
    }

    def __init__(self, timeline, hadj):
        Gtk.DrawingArea.__init__(self)
        Zoomable.__init__(self)
        Loggable.__init__(self)
        self.log("Creating new ScaleRuler")

        # Allows stealing focus from other GTK widgets, prevent accidents:
        self.props.can_focus = True
        self.connect("focus-in-event", self._focusInCb)
        self.connect("focus-out-event", self._focusOutCb)

        self.timeline = timeline
        self._background_color = timeline.get_style_context().lookup_color(
            'theme_bg_color')[1]
        self._seeker = Seeker()
        self.hadj = hadj
        hadj.connect("value-changed", self._hadjValueChangedCb)
        self.add_events(Gdk.EventMask.POINTER_MOTION_MASK
                        | Gdk.EventMask.BUTTON_PRESS_MASK
                        | Gdk.EventMask.BUTTON_RELEASE_MASK
                        | Gdk.EventMask.SCROLL_MASK)

        self.pixbuf = None

        # all values are in pixels
        self.pixbuf_offset = 0
        self.pixbuf_offset_painted = 0
        # This is the number of width we allocate for the pixbuf
        self.pixbuf_multiples = 4

        self.position = 0  # In nanoseconds
        self.pressed = False
        self.frame_rate = Gst.Fraction(1 / 1)
        self.ns_per_frame = float(1 / self.frame_rate) * Gst.SECOND
        self.connect('draw', self.drawCb)
        self.connect('configure-event', self.configureEventCb)
        self.callback_id = None
        self.callback_id_scroll = None
        self.set_size_request(0, HEIGHT)

        style = self.get_style_context()
        color_normal = style.get_color(Gtk.StateFlags.NORMAL)
        color_insensitive = style.get_color(Gtk.StateFlags.INSENSITIVE)
        self._color_normal = color_normal
        self._color_dimmed = Gdk.RGBA(
            *[(x * 3 + y * 2) / 5
              for x, y in ((color_normal.red, color_insensitive.red),
                           (color_normal.green, color_insensitive.green),
                           (color_normal.blue, color_insensitive.blue))])

        self.scales = SCALES

    def _focusInCb(self, unused_widget, unused_arg):
        self.log("Ruler has grabbed focus")
        self.timeline.setActionsSensitivity(True)

    def _focusOutCb(self, unused_widget, unused_arg):
        self.log("Ruler has lost focus")
        self.timeline.setActionsSensitivity(False)

    def _hadjValueChangedCb(self, unused_arg):
        self.pixbuf_offset = self.hadj.get_value()
        if self.callback_id_scroll is not None:
            GLib.source_remove(self.callback_id_scroll)
        self.callback_id_scroll = GLib.timeout_add(100, self._maybeUpdate)

# Zoomable interface override

    def _maybeUpdate(self):
        self.queue_draw()
        self.callback_id = None
        self.callback_id_scroll = None
        return False

    def zoomChanged(self):
        if self.callback_id is not None:
            GLib.source_remove(self.callback_id)
        self.callback_id = GLib.timeout_add(100, self._maybeUpdate)

# Timeline position changed method

    def setPipeline(self, pipeline):
        pipeline.connect('position', self.timelinePositionCb)

    def timelinePositionCb(self, unused_pipeline, position):
        self.position = position
        self.queue_draw()

# Gtk.Widget overrides

    def configureEventCb(self, widget, unused_event, unused_data=None):
        width = widget.get_allocated_width()
        height = widget.get_allocated_height()
        self.debug("Configuring, height %d, width %d", width, height)

        # Destroy previous buffer
        if self.pixbuf is not None:
            self.pixbuf.finish()
            self.pixbuf = None

        # Create a new buffer
        self.height = height
        self.pixbuf = cairo.ImageSurface(cairo.FORMAT_ARGB32, width, height)

        return False

    def drawCb(self, unused_widget, context):
        if self.pixbuf is None:
            self.info('No buffer to paint')
            return False
        self.drawBackground(context)
        self.drawRuler(context)
        self.drawPosition(context)
        return False

    def do_button_press_event(self, event):
        self.debug("button pressed at x:%d", event.x)
        self.pressed = True
        position = self.pixelToNs(event.x + self.pixbuf_offset)
        self._seeker.seek(position, on_idle=True)
        return True

    def do_button_release_event(self, event):
        self.debug("button released at x:%d", event.x)
        self.grab_focus()  # Prevent other widgets from being confused
        self.pressed = False
        return False

    def do_motion_notify_event(self, event):
        position = self.pixelToNs(event.x + self.pixbuf_offset)
        if self.pressed:
            self.debug("motion at event.x %d", event.x)
            self._seeker.seek(position, on_idle=True)

        human_time = beautify_length(position)
        cur_frame = int(position / self.ns_per_frame) + 1
        self.set_tooltip_text(human_time + "\n" + _("Frame #%d" % cur_frame))
        return False

    def do_scroll_event(self, event):
        if event.scroll.state & Gdk.ModifierType.CONTROL_MASK:
            # Control + scroll = zoom
            if event.scroll.direction == Gdk.ScrollDirection.UP:
                Zoomable.zoomIn()
                self.timeline.zoomed_fitted = False
            elif event.scroll.direction == Gdk.ScrollDirection.DOWN:
                Zoomable.zoomOut()
                self.timeline.zoomed_fitted = False
        else:
            # No modifier key held down, just scroll
            if (event.scroll.direction == Gdk.ScrollDirection.UP
                    or event.scroll.direction == Gdk.ScrollDirection.LEFT):
                self.timeline.scroll_left()
            elif (event.scroll.direction == Gdk.ScrollDirection.DOWN
                  or event.scroll.direction == Gdk.ScrollDirection.RIGHT):
                self.timeline.scroll_right()

    def setProjectFrameRate(self, rate):
        """
        Set the lowest scale based on project framerate
        """
        self.frame_rate = rate
        self.ns_per_frame = float(1 / self.frame_rate) * Gst.SECOND
        self.scales = (float(2 / rate), float(5 / rate), float(
            10 / rate)) + SCALES

# Drawing methods

    def drawBackground(self, context):
        style = self.get_style_context()
        set_cairo_color(context, self._background_color)
        width = context.get_target().get_width()
        height = context.get_target().get_height()
        context.rectangle(0, 0, width, height)
        context.fill()
        offset = int(self.nsToPixel(Gst.CLOCK_TIME_NONE)) - self.pixbuf_offset
        if offset > 0:
            set_cairo_color(context,
                            style.get_background_color(Gtk.StateFlags.ACTIVE))
            context.rectangle(0, 0, int(offset), height)
            context.fill()

    def drawRuler(self, context):
        context.set_font_face(NORMAL_FONT)
        context.set_font_size(NORMAL_FONT_SIZE)

        spacing, scale = self._getSpacing(context)
        offset = self.pixbuf_offset % spacing
        self.drawFrameBoundaries(context)
        self.drawTicks(context, offset, spacing)
        self.drawTimes(context, offset, spacing, scale)

    def _getSpacing(self, context):
        textwidth = context.text_extents(time_to_string(0))[2]
        zoom = Zoomable.zoomratio
        for scale in self.scales:
            spacing = scale * zoom
            if spacing >= textwidth * 1.5:
                return spacing, scale
        raise Exception(
            "Failed to find an interval size for textwidth:%s, zoomratio:%s" %
            (textwidth, Zoomable.zoomratio))

    def drawTicks(self, context, offset, spacing):
        for count_per_interval, height_ratio in TICK_TYPES:
            space = float(spacing) / count_per_interval
            if space < MIN_TICK_SPACING_PIXELS:
                break
            paintpos = 0.5 - offset
            set_cairo_color(context, self._color_normal)
            while paintpos < context.get_target().get_width():
                self._drawTick(context, paintpos, height_ratio)
                paintpos += space

    def _drawTick(self, context, paintpos, height_ratio):
        # We need to use 0.5 pixel offsets to get a sharp 1 px line in cairo
        paintpos = int(paintpos - 0.5) + 0.5
        y = self.height * (1.0 - height_ratio)
        context.set_line_width(1)
        context.move_to(paintpos, y)
        context.line_to(paintpos, self.height)
        context.close_path()
        context.stroke()

    def drawTimes(self, context, offset, spacing, scale):
        # figure out what the optimal offset is
        interval = int(Gst.SECOND * scale)
        current_time = self.pixelToNs(self.pixbuf_offset)
        paintpos = TIMES_LEFT_MARGIN_PIXELS
        if offset > 0:
            current_time = current_time - (current_time % interval) + interval
            paintpos += spacing - offset

        state = Gtk.StateFlags.NORMAL
        style = self.get_style_context()
        set_cairo_color(context, style.get_color(state))
        y_bearing = context.text_extents("0")[1]
        millis = scale < 1

        def split(x):
            # Seven elements: h : mm : ss . mmm
            # Using negative indices because the first element (hour)
            # can have a variable length.
            return x[:-10], x[-10], x[-9:-7], x[-7], x[-6:-4], x[-4], x[-3:]

        previous = split(time_to_string(max(0, current_time - interval)))
        width = context.get_target().get_width()
        while paintpos < width:
            context.move_to(int(paintpos), 1 - y_bearing)
            current = split(time_to_string(int(current_time)))
            self._drawTime(context, current, previous, millis)
            previous = current
            paintpos += spacing
            current_time += interval

    def _drawTime(self, context, current, previous, millis):
        hour = int(current[0])
        for index, (element,
                    previous_element) in enumerate(zip(current, previous)):
            if index <= 1 and not hour:
                continue
            if index >= 5 and not millis:
                break
            if element == previous_element:
                color = self._color_dimmed
            else:
                color = self._color_normal
            set_cairo_color(context, color)
            # Display the millis with a smaller font
            small = index >= 5
            if small:
                context.set_font_size(SMALL_FONT_SIZE)
            context.show_text(element)
            if small:
                context.set_font_size(NORMAL_FONT_SIZE)

    def drawFrameBoundaries(self, context):
        """
        Draw the alternating rectangles that represent the project frames at
        high zoom levels. These are based on the framerate set in the project
        settings, not the actual frames on a video codec level.
        """
        frame_width = self.nsToPixel(self.ns_per_frame)
        if not frame_width >= FRAME_MIN_WIDTH_PIXELS:
            return

        offset = self.pixbuf_offset % frame_width
        height = context.get_target().get_height()
        y = int(height - FRAME_HEIGHT_PIXELS)
        # INSENSITIVE is a dark shade of gray, but lacks contrast
        # SELECTED will be bright blue and more visible to represent frames
        style = self.get_style_context()
        states = [
            style.get_background_color(Gtk.StateFlags.ACTIVE),
            style.get_background_color(Gtk.StateFlags.SELECTED)
        ]

        frame_num = int(
            self.pixelToNs(self.pixbuf_offset) * float(self.frame_rate) /
            Gst.SECOND)
        paintpos = self.pixbuf_offset - offset
        max_pos = context.get_target().get_width() + self.pixbuf_offset
        while paintpos < max_pos:
            paintpos = self.nsToPixel(1 / float(self.frame_rate) * Gst.SECOND *
                                      frame_num)
            set_cairo_color(context, states[(frame_num + 1) % 2])
            context.rectangle(0.5 + paintpos - self.pixbuf_offset, y,
                              frame_width, height)
            context.fill()
            frame_num += 1

    def drawPosition(self, context):
        # Add 0.5 so that the line center is at the middle of the pixel,
        # without this the line appears blurry.
        xpos = self.nsToPixel(self.position) - self.pixbuf_offset + 0.5
        context.set_line_width(PLAYHEAD_WIDTH + 2)
        set_cairo_color(context, PLAYHEAD_COLOR)
        context.move_to(xpos, 0)
        context.line_to(xpos, context.get_target().get_height())
        context.stroke()
コード例 #39
0
ファイル: viewer.py プロジェクト: cfoch/pitivi-cfoch
class ViewerWidget(Gtk.AspectFrame, Loggable):
    """
    Widget for displaying a GStreamer video sink.

    @ivar settings: The settings of the application.
    @type settings: L{GlobalSettings}
    """

    __gsignals__ = {}

    def __init__(self, settings=None, realizedCb=None):
        # Prevent black frames and flickering while resizing or changing focus:
        # The aspect ratio gets overridden by setDisplayAspectRatio.
        Gtk.AspectFrame.__init__(self, xalign=0.5, yalign=1.0,
                                 ratio=4.0 / 3.0, obey_child=False)
        Loggable.__init__(self)

        self.drawing_area = GtkClutter.Embed()
        self.drawing_area.set_double_buffered(False)
        # We keep the ViewerWidget hidden initially, or the desktop wallpaper
        # would show through the non-double-buffered widget!
        if realizedCb:
            self.drawing_area.connect("realize", realizedCb, self)
        self.add(self.drawing_area)

        layout_manager = Clutter.BinLayout(x_align=Clutter.BinAlignment.FILL, y_align=Clutter.BinAlignment.FILL)
        self.drawing_area.get_stage().set_layout_manager(layout_manager)
        self.texture = Clutter.Texture()
        # This is a trick to make the viewer appear darker at the start.
        self.texture.set_from_rgb_data(data=[0] * 3, has_alpha=False,
                width=1, height=1, rowstride=3, bpp=3,
                flags=Clutter.TextureFlags.NONE)
        self.drawing_area.get_stage().add_child(self.texture)
        self.drawing_area.show()

        self.seeker = Seeker()
        self.settings = settings
        self.box = None
        self.stored = False
        self.area = None
        self.zoom = 1.0
        self.sink = None
        self.pixbuf = None
        self.pipeline = None
        self.transformation_properties = None
        # FIXME PyGi Styling with Gtk3
        #for state in range(Gtk.StateType.INSENSITIVE + 1):
            #self.modify_bg(state, self.style.black)

    def setDisplayAspectRatio(self, ratio):
        self.set_property("ratio", float(ratio))

    def init_transformation_events(self):
        self.fixme("TransformationBox disabled")
        """
        self.set_events(Gdk.EventMask.BUTTON_PRESS_MASK
                        | Gdk.EventMask.BUTTON_RELEASE_MASK
                        | Gdk.EventMask.POINTER_MOTION_MASK
                        | Gdk.EventMask.POINTER_MOTION_HINT_MASK)
        """

    def show_box(self):
        self.fixme("TransformationBox disabled")
        """
        if not self.box:
            self.box = TransformationBox(self.settings)
            self.box.init_size(self.area)
            self._update_gradient()
            self.connect("button-press-event", self.button_press_event)
            self.connect("button-release-event", self.button_release_event)
            self.connect("motion-notify-event", self.motion_notify_event)
            self.connect("size-allocate", self._sizeCb)
            self.box.set_transformation_properties(self.transformation_properties)
            self.renderbox()
        """

    def _sizeCb(self, unused_widget, unused_area):
        # The transformation box is cleared when using regular rendering
        # so we need to flush the pipeline
        self.seeker.flush()

    def hide_box(self):
        if self.box:
            self.box = None
            self.disconnect_by_func(self.button_press_event)
            self.disconnect_by_func(self.button_release_event)
            self.disconnect_by_func(self.motion_notify_event)
            self.seeker.flush()
            self.zoom = 1.0
            if self.sink:
                self.sink.set_render_rectangle(*self.area)

    def set_transformation_properties(self, transformation_properties):
            self.transformation_properties = transformation_properties

    def _store_pixbuf(self):
        """
        When not playing, store a pixbuf of the current viewer image.
        This will allow it to be restored for the transformation box.
        """

        if self.box and self.zoom != 1.0:
            # The transformation box is active and dezoomed
            # crop away 1 pixel border to avoid artefacts on the pixbuf

            self.pixbuf = Gdk.pixbuf_get_from_window(self.get_window(),
                self.box.area.x + 1, self.box.area.y + 1,
                self.box.area.width - 2, self.box.area.height - 2)
        else:
            self.pixbuf = Gdk.pixbuf_get_from_window(self.get_window(),
                0, 0,
                self.get_window().get_width(),
                self.get_window().get_height())

        self.stored = True

    def button_release_event(self, unused_widget, event):
        if event.button == 1:
            self.box.update_effect_properties()
            self.box.release_point()
            self.seeker.flush()
            self.stored = False
        return True

    def button_press_event(self, unused_widget, event):
        if event.button == 1:
            self.box.select_point(event)
        return True

    def _currentStateCb(self, unused_pipeline, unused_state):
        self.fixme("TransformationBox disabled")
        """
        self.pipeline = pipeline
        if state == Gst.State.PAUSED:
            self._store_pixbuf()
        self.renderbox()
        """

    def motion_notify_event(self, unused_widget, event):
        if event.get_state() & Gdk.ModifierType.BUTTON1_MASK:
            if self.box.transform(event):
                if self.stored:
                    self.renderbox()
        return True

    def do_expose_event(self, event):
        self.area = event.area
        if self.box:
            self._update_gradient()
            if self.zoom != 1.0:
                width = int(float(self.area.width) * self.zoom)
                height = int(float(self.area.height) * self.zoom)
                area = ((self.area.width - width) / 2,
                        (self.area.height - height) / 2,
                        width, height)
                self.sink.set_render_rectangle(*area)
            else:
                area = self.area
            self.box.update_size(area)
            self.renderbox()

    def _update_gradient(self):
        self.gradient_background = cairo.LinearGradient(0, 0, 0, self.area.height)
        self.gradient_background.add_color_stop_rgb(0.00, .1, .1, .1)
        self.gradient_background.add_color_stop_rgb(0.50, .2, .2, .2)
        self.gradient_background.add_color_stop_rgb(1.00, .5, .5, .5)

    def renderbox(self):
        if self.box:
            cr = self.window.cairo_create()
            cr.push_group()

            if self.zoom != 1.0:
                # draw some nice background for zoom out
                cr.set_source(self.gradient_background)
                cr.rectangle(0, 0, self.area.width, self.area.height)
                cr.fill()

                # translate the drawing of the zoomed out box
                cr.translate(self.box.area.x, self.box.area.y)

            # clear the drawingarea with the last known clean video frame
            # translate when zoomed out
            if self.pixbuf:
                if self.box.area.width != self.pixbuf.get_width():
                    scale = float(self.box.area.width) / float(self.pixbuf.get_width())
                    cr.save()
                    cr.scale(scale, scale)
                cr.set_source_pixbuf(self.pixbuf, 0, 0)
                cr.paint()
                if self.box.area.width != self.pixbuf.get_width():
                    cr.restore()

            if self.pipeline and self.pipeline.getState() == Gst.State.PAUSED:
                self.box.draw(cr)
            cr.pop_group_to_source()
            cr.paint()