예제 #1
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()
예제 #2
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()
예제 #3
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()
예제 #4
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()
예제 #5
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()
예제 #6
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()
예제 #7
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
예제 #8
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 = 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()
예제 #9
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()