Beispiel #1
0
    def test_dependent_properties(self):
        """Checks dependent properties updating is handled correctly."""
        mainloop = common.create_main_loop()
        app = common.create_pitivi()
        app.project_manager.new_blank_project()
        manager = EffectsPropertiesManager(app)

        called = False

        def set_child_property(prop_name, value):
            nonlocal called
            called = True

            self.assertEqual(prop_name, "aspect-ratio")
            GES.Effect.set_child_property(effect, prop_name, value)

            # When setting the aspect-ratio property, and the stars align,
            # the effect also changes the left/right properties.
            # Here we simulate the updating of the dependent properties.
            GES.Effect.set_child_property(effect, "left", 100)
            GES.Effect.set_child_property(effect, "right", 100)

        effect = GES.Effect.new("aspectratiocrop")
        effect.set_child_property = set_child_property

        effect_widget = manager.get_effect_configuration_ui(effect)

        widgets = {prop.name: widget
                   for prop, widget in effect_widget.properties.items()}
        # Simulate the user choosing an aspect-ratio.
        widgets["aspect-ratio"].set_widget_value(Gst.Fraction(4, 3))

        mainloop.run(until_empty=True)

        self.assertTrue(called)
Beispiel #2
0
class EffectProperties(Gtk.Expander, Loggable):
    """Widget for viewing a list of effects and configuring them.

    Attributes:
        app (Pitivi): The app.
        clip (GES.Clip): The clip being configured.
    """
    def __init__(self, app):
        Gtk.Expander.__init__(self)

        self.set_expanded(True)
        self.set_label(_("Effects"))
        Loggable.__init__(self)

        self.app = app
        self.clip = None

        self.effects_properties_manager = EffectsPropertiesManager(app)
        setup_custom_effect_widgets(self.effects_properties_manager)

        self.drag_lines_pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size(
            os.path.join(get_pixmap_dir(), "grip-lines-solid.svg"), 15, 15)

        self.expander_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
        self.effects_listbox = Gtk.ListBox()

        placeholder_label = Gtk.Label(
            _("To apply an effect to the clip, drag it from the Effect Library "
              "or use the button below."))
        placeholder_label.set_line_wrap(True)
        placeholder_label.show()
        self.effects_listbox.set_placeholder(placeholder_label)

        # Add effect popover button
        self.effect_popover = EffectsPopover(app)
        self.add_effect_button = Gtk.MenuButton(_("Add Effect"))
        self.add_effect_button.set_popover(self.effect_popover)
        self.add_effect_button.props.halign = Gtk.Align.CENTER

        self.drag_dest_set(Gtk.DestDefaults.DROP, [EFFECT_TARGET_ENTRY],
                           Gdk.DragAction.COPY)

        self.expander_box.pack_start(self.effects_listbox, False, False, 0)
        self.expander_box.pack_start(self.add_effect_button, False, False,
                                     PADDING)

        self.add(self.expander_box)

        # Connect all the widget signals
        self.connect("drag-motion", self._drag_motion_cb)
        self.connect("drag-leave", self._drag_leave_cb)
        self.connect("drag-data-received", self._drag_data_received_cb)

        self.add_effect_button.connect("toggled", self._add_effect_button_cb)

        self.show_all()

    def _add_effect_button_cb(self, button):
        # MenuButton interacts directly with the popover, bypassing our subclassed method
        if button.props.active:
            self.effect_popover.search_entry.set_text("")

    def _create_effect_row(self, effect):
        effect_info = self.app.effects.get_info(effect.props.bin_description)

        vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)

        row_drag_icon = Gtk.Image.new_from_pixbuf(self.drag_lines_pixbuf)

        toggle = Gtk.CheckButton()
        toggle.props.active = effect.props.active

        effect_label = Gtk.Label(effect_info.human_name)
        effect_label.set_tooltip_text(effect_info.description)

        # Set up revealer + expander
        effect_config_ui = self.effects_properties_manager.get_effect_configuration_ui(
            effect)
        config_ui_revealer = Gtk.Revealer()
        config_ui_revealer.add(effect_config_ui)

        expander = Gtk.Expander()
        expander.set_label_widget(effect_label)
        expander.props.valign = Gtk.Align.CENTER
        expander.props.vexpand = True

        config_ui_revealer.props.halign = Gtk.Align.CENTER
        expander.connect("notify::expanded", self._toggle_expander_cb,
                         config_ui_revealer)

        remove_effect_button = Gtk.Button.new_from_icon_name(
            "window-close", Gtk.IconSize.BUTTON)
        remove_effect_button.props.margin_right = PADDING

        row_widgets_box = Gtk.Box()
        row_widgets_box.pack_start(row_drag_icon, False, False, PADDING)
        row_widgets_box.pack_start(toggle, False, False, PADDING)
        row_widgets_box.pack_start(expander, True, True, PADDING)
        row_widgets_box.pack_end(remove_effect_button, False, False, 0)

        vbox.pack_start(row_widgets_box, False, False, 0)
        vbox.pack_start(config_ui_revealer, False, False, 0)

        event_box = Gtk.EventBox()
        event_box.add(vbox)

        row = Gtk.ListBoxRow(selectable=False, activatable=False)
        row.effect = effect
        row.toggle = toggle
        row.add(event_box)

        # Set up drag&drop
        event_box.drag_source_set(Gdk.ModifierType.BUTTON1_MASK,
                                  [EFFECT_TARGET_ENTRY], Gdk.DragAction.MOVE)
        event_box.connect("drag-begin", self._drag_begin_cb)
        event_box.connect("drag-data-get", self._drag_data_get_cb)

        row.drag_dest_set(Gtk.DestDefaults.ALL, [EFFECT_TARGET_ENTRY],
                          Gdk.DragAction.MOVE | Gdk.DragAction.COPY)
        row.connect("drag-data-received", self._drag_data_received_cb)

        remove_effect_button.connect("clicked", self._remove_button_cb, row)
        toggle.connect("toggled", self._effect_active_toggle_cb, row)

        return row

    def _update_listbox(self):
        for row in self.effects_listbox.get_children():
            self.effects_listbox.remove(row)

        for effect in self.clip.get_top_effects():
            if effect.props.bin_description in HIDDEN_EFFECTS:
                continue
            effect_row = self._create_effect_row(effect)
            self.effects_listbox.add(effect_row)

        self.effects_listbox.show_all()

    def _toggle_expander_cb(self, expander, unused_prop, revealer):
        revealer.props.reveal_child = expander.props.expanded

    def _get_effect_row(self, effect):
        for row in self.effects_listbox.get_children():
            if row.effect == effect:
                return row
        return None

    def _add_effect_row(self, effect):
        row = self._create_effect_row(effect)
        self.effects_listbox.add(row)
        self.effects_listbox.show_all()

    def _remove_effect_row(self, effect):
        row = self._get_effect_row(effect)
        self.effects_listbox.remove(row)

    def _move_effect_row(self, effect, new_index):
        row = self._get_effect_row(effect)
        self.effects_listbox.remove(row)
        self.effects_listbox.insert(row, new_index)

    def _remove_button_cb(self, button, row):
        effect = row.effect
        self._remove_effect(effect)

    def _remove_effect(self, effect):
        pipeline = self.app.project_manager.current_project.pipeline
        with self.app.action_log.started(
                "remove effect",
                finalizing_action=CommitTimelineFinalizingAction(pipeline),
                toplevel=True):
            effect.get_parent().remove(effect)

    def _effect_active_toggle_cb(self, toggle, row):
        effect = row.effect
        pipeline = self.app.project_manager.current_project.pipeline
        with self.app.action_log.started(
                "change active state",
                finalizing_action=CommitTimelineFinalizingAction(pipeline),
                toplevel=True):
            effect.props.active = toggle.props.active

    def set_clip(self, clip):
        if self.clip:
            self.clip.disconnect_by_func(self._track_element_added_cb)
            self.clip.disconnect_by_func(self._track_element_removed_cb)
            for track_element in self.clip.get_children(recursive=True):
                if isinstance(track_element, GES.BaseEffect):
                    self._disconnect_from_track_element(track_element)

        self.clip = clip
        if self.clip:
            self.clip.connect("child-added", self._track_element_added_cb)
            self.clip.connect("child-removed", self._track_element_removed_cb)
            for track_element in self.clip.get_children(recursive=True):
                if isinstance(track_element, GES.BaseEffect):
                    self._connect_to_track_element(track_element)

            self._update_listbox()
            self.show()
        else:
            self.hide()

    def _track_element_added_cb(self, unused_clip, track_element):
        if isinstance(track_element, GES.BaseEffect):
            self._connect_to_track_element(track_element)
            self._add_effect_row(track_element)

    def _connect_to_track_element(self, track_element):
        track_element.connect("notify::active", self._notify_active_cb)
        track_element.connect("notify::priority", self._notify_priority_cb)

    def _disconnect_from_track_element(self, track_element):
        track_element.disconnect_by_func(self._notify_active_cb)
        track_element.disconnect_by_func(self._notify_priority_cb)

    def _notify_active_cb(self, track_element, unused_param_spec):
        row = self._get_effect_row(track_element)
        row.toggle.props.active = track_element.props.active

    def _notify_priority_cb(self, track_element, unused_param_spec):
        index = self.clip.get_top_effect_index(track_element)
        row = self.effects_listbox.get_row_at_index(index)

        if not row:
            return

        if row.effect != track_element:
            self._move_effect_row(track_element, index)

    def _track_element_removed_cb(self, unused_clip, track_element):
        if isinstance(track_element, GES.BaseEffect):
            self._disconnect_from_track_element(track_element)
            self._remove_effect_row(track_element)

    def _drag_begin_cb(self, eventbox, context):
        """Draws the drag icon."""
        row = eventbox.get_parent()
        alloc = row.get_allocation()

        surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, alloc.width,
                                     alloc.height)
        ctx = cairo.Context(surface)

        row.draw(ctx)
        ctx.paint_with_alpha(0.35)

        Gtk.drag_set_icon_surface(context, surface)

    def _drag_data_get_cb(self, eventbox, drag_context, selection_data,
                          unused_info, unused_timestamp):
        row = eventbox.get_parent()
        effect_info = self.app.effects.get_info(
            row.effect.props.bin_description)
        effect_name = effect_info.human_name

        data = bytes(effect_name, "UTF-8")
        selection_data.set(drag_context.list_targets()[0], 0, data)

    def _drag_motion_cb(self, unused_widget, unused_drag_context, unused_x, y,
                        unused_timestamp):
        """Highlights some widgets to indicate it can receive drag&drop."""
        self.debug(
            "Something is being dragged in the clip properties' effects list")
        row = self.effects_listbox.get_row_at_y(y)
        if row:
            self.effects_listbox.drag_highlight_row(row)
            self.expander_box.drag_unhighlight()
        else:
            self.effects_listbox.drag_highlight()

    def _drag_leave_cb(self, unused_widget, drag_context, unused_timestamp):
        """Unhighlights the widgets which can receive drag&drop."""
        self.debug(
            "The item being dragged has left the clip properties' effects list"
        )

        self.effects_listbox.drag_unhighlight_row()
        self.effects_listbox.drag_unhighlight()

    def _drag_data_received_cb(self, widget, drag_context, unused_x, y,
                               selection_data, unused_info, timestamp):
        if not self.clip:
            # Indicate that a drop will not be accepted.
            Gdk.drag_status(drag_context, 0, timestamp)
            return

        if self.effects_listbox.get_row_at_y(y):
            # Drop happened inside the lisbox
            drop_index = widget.get_index()
        else:
            drop_index = len(self.effects_listbox.get_children()) - 1

        if drag_context.get_suggested_action() == Gdk.DragAction.COPY:
            # An effect dragged probably from the effects list.
            factory_name = str(selection_data.get_data(), "UTF-8")

            self.debug("Effect dragged at position %s", drop_index)
            effect_info = self.app.effects.get_info(factory_name)
            pipeline = self.app.project_manager.current_project.pipeline
            with self.app.action_log.started(
                    "add effect",
                    finalizing_action=CommitTimelineFinalizingAction(pipeline),
                    toplevel=True):
                effect = self.clip.ui.add_effect(effect_info)
                if effect:
                    self.clip.set_top_effect_index(effect, drop_index)

        elif drag_context.get_suggested_action() == Gdk.DragAction.MOVE:
            # An effect dragged from the same listbox to change its position.
            source_eventbox = Gtk.drag_get_source_widget(drag_context)
            source_row = source_eventbox.get_parent()
            source_index = source_row.get_index()

            self._move_effect(self.clip, source_index, drop_index)

        drag_context.finish(True, False, timestamp)

    def _move_effect(self, clip, source_index, drop_index):
        # Handle edge cases
        if drop_index < 0:
            drop_index = 0
        if drop_index > len(clip.get_top_effects()) - 1:
            drop_index = len(clip.get_top_effects()) - 1
        if source_index == drop_index:
            # Noop.
            return

        effects = clip.get_top_effects()
        effect = effects[source_index]
        pipeline = self.app.project_manager.current_project.pipeline

        with self.app.action_log.started(
                "move effect",
                finalizing_action=CommitTimelineFinalizingAction(pipeline),
                toplevel=True):
            clip.set_top_effect_index(effect, drop_index)