[pitivi] trackerperspective: Allow covering tracked object to hide it
- From: Alexandru Băluț <alexbalut src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [pitivi] trackerperspective: Allow covering tracked object to hide it
- Date: Tue, 3 May 2022 07:22:01 +0000 (UTC)
commit c6c41c31285d123b90934d4b29514f934217cd4a
Author: Vivek R <123vivekr gmail com>
Date: Sat Aug 8 10:50:53 2020 +0530
trackerperspective: Allow covering tracked object to hide it
This adds a feature to apply a cover effect to a tracked object inside a
video clip. The effect is a videotestsrc with a solid foreground color.
Fixes #1942
data/ui/customwidgets/pitivi:object_effect.ui | 48 ++++++++++
pitivi/clipproperties.py | 65 +++++++-------
pitivi/dialogs/prefs.py | 2 +-
pitivi/effects.py | 23 ++++-
pitivi/greeterperspective.py | 2 +-
pitivi/trackerperspective.py | 123 +++++++++++++++++++++++++-
pitivi/utils/custom_effect_widgets.py | 73 +++++++++++++--
pitivi/utils/ui.py | 26 +++---
tests/test_trackerperspective.py | 45 ++++++++++
9 files changed, 349 insertions(+), 58 deletions(-)
---
diff --git a/data/ui/customwidgets/pitivi:object_effect.ui b/data/ui/customwidgets/pitivi:object_effect.ui
new file mode 100644
index 000000000..8888a91a6
--- /dev/null
+++ b/data/ui/customwidgets/pitivi:object_effect.ui
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.38.2 -->
+<interface>
+ <requires lib="gtk+" version="3.12"/>
+ <object class="GtkBox" id="base_table">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="halign">start</property>
+ <property name="margin-start">12</property>
+ <property name="margin-end">12</property>
+ <property name="margin-top">12</property>
+ <property name="margin-bottom">12</property>
+ <property name="spacing">10</property>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="halign">start</property>
+ <property name="label" translatable="yes">Color:</property>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkColorButton" id="color_button">
+ <property name="use-action-appearance">False</property>
+ <property name="visible">True</property>
+ <property name="can-focus">True</property>
+ <property name="receives-default">True</property>
+ <property name="halign">start</property>
+ <property name="margin-left">2</property>
+ <property name="hexpand">True</property>
+ <property name="use-alpha">True</property>
+ <property name="title" translatable="yes">Pick the clip color</property>
+ <property name="rgba">rgb(255,255,255)</property>
+ <signal name="color-set" handler="color_button_color_set_cb" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+</interface>
diff --git a/pitivi/clipproperties.py b/pitivi/clipproperties.py
index 05e5a8f53..cb906bc3d 100644
--- a/pitivi/clipproperties.py
+++ b/pitivi/clipproperties.py
@@ -45,9 +45,10 @@ from pitivi.configure import in_devel
from pitivi.effects import EffectsPopover
from pitivi.effects import EffectsPropertiesManager
from pitivi.effects import HIDDEN_EFFECTS
-from pitivi.trackerperspective import TrackerPerspective
+from pitivi.trackerperspective import CoverObjectPopover
from pitivi.undo.timeline import CommitTimelineFinalizingAction
-from pitivi.utils.custom_effect_widgets import setup_custom_effect_widgets
+from pitivi.utils.custom_effect_widgets import create_custom_prop_widget_cb
+from pitivi.utils.custom_effect_widgets import create_custom_widget_cb
from pitivi.utils.loggable import Loggable
from pitivi.utils.misc import disconnect_all_by_func
from pitivi.utils.pipeline import PipelineError
@@ -573,7 +574,9 @@ class EffectProperties(Gtk.Expander, Loggable):
self.clip = None
self.effects_properties_manager = EffectsPropertiesManager(app)
- setup_custom_effect_widgets(self.effects_properties_manager)
+ # Set up the effects manager to be able to create custom UI.
+ self.effects_properties_manager.connect("create_widget", create_custom_widget_cb)
+ self.effects_properties_manager.connect("create_property_widget", create_custom_prop_widget_cb)
self.drag_lines_pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size(
os.path.join(get_pixmap_dir(), "grip-lines-solid.svg"),
@@ -598,11 +601,11 @@ class EffectProperties(Gtk.Expander, Loggable):
self.object_tracker_box = Gtk.ButtonBox()
self.object_tracker_box.props.halign = Gtk.Align.CENTER
+ self.cover_popover: Optional[Gtk.Popover] = None
+ self.cover_object_button: Optional[Gtk.MenuButton] = None
if "cvtracker" not in MISSING_SOFT_DEPS:
- self.track_object_button = Gtk.Button(_("Track Object"))
- self.track_object_button.connect("clicked", self.__track_object_button_clicked_cb)
- self.track_object_button.props.halign = Gtk.Align.CENTER
- self.object_tracker_box.pack_start(self.track_object_button, False, False, 0)
+ self.cover_object_button = Gtk.MenuButton(_("Cover Object"))
+ self.object_tracker_box.pack_start(self.cover_object_button, False, False, 0)
self.drag_dest_set(Gtk.DestDefaults.DROP, [EFFECT_TARGET_ENTRY],
Gdk.DragAction.COPY)
@@ -618,40 +621,36 @@ class EffectProperties(Gtk.Expander, Loggable):
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.add_effect_button.connect("toggled", self._add_effect_button_toggled_cb)
+ if self.cover_object_button:
+ self.cover_object_button.connect("toggled", self._cover_object_button_toggled_cb)
self.show_all()
- def __track_object_button_clicked_cb(self, button):
- tracker = TrackerPerspective(self.app, self.clip.asset)
- self.app.project_manager.current_project.pipeline.pause()
- tracker.setup_ui()
- self.app.gui.show_perspective(tracker)
-
- def _add_effect_button_cb(self, button):
+ def _add_effect_button_toggled_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 _cover_object_button_toggled_cb(self, button):
+ if button.props.active:
+ self.cover_popover.update_object_list()
+
def _create_effect_row(self, effect):
if is_time_effect(effect):
return None
- 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_info = self.app.effects.get_info(effect)
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)
+ effect_config_ui = self.effects_properties_manager.get_effect_configuration_ui(effect)
config_ui_revealer = Gtk.Revealer()
config_ui_revealer.add(effect_config_ui)
@@ -668,6 +667,7 @@ class EffectProperties(Gtk.Expander, Loggable):
remove_effect_button.props.margin_right = PADDING
row_widgets_box = Gtk.Box()
+ row_drag_icon = Gtk.Image.new_from_pixbuf(self.drag_lines_pixbuf)
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)
@@ -769,6 +769,7 @@ class EffectProperties(Gtk.Expander, Loggable):
self.clip = clip
if self.clip:
+ cover_object_button_show = False
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):
@@ -777,12 +778,15 @@ class EffectProperties(Gtk.Expander, Loggable):
continue
self._connect_to_track_element(track_element)
if isinstance(track_element, GES.VideoUriSource) and not clip.asset.is_image():
- self.track_object_button.show()
-
+ cover_object_button_show = True
+ if self.cover_object_button:
+ self.cover_object_button.set_visible(cover_object_button_show)
+ if cover_object_button_show:
+ self.cover_popover = CoverObjectPopover(self.app, self.clip)
+ self.cover_object_button.set_popover(self.cover_popover)
self._update_listbox()
- self.show()
- else:
- self.hide()
+
+ self.props.visible = bool(self.clip)
def _track_element_added_cb(self, unused_clip, track_element):
if isinstance(track_element, GES.BaseEffect):
@@ -831,7 +835,7 @@ class EffectProperties(Gtk.Expander, Loggable):
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_info = self.app.effects.get_info(row.effect)
effect_name = effect_info.human_name
data = bytes(effect_name, "UTF-8")
@@ -1116,9 +1120,10 @@ class TransformationProperties(Gtk.Expander, Loggable):
control_source = GstController.InterpolationControlSource()
control_source.props.mode = GstController.InterpolationMode.LINEAR
self.__own_bindings_change = True
- self.source.set_control_source(
- control_source, prop, "direct-absolute")
- self.__own_bindings_change = False
+ try:
+ self.source.set_control_source(control_source, prop, "direct-absolute")
+ finally:
+ self.__own_bindings_change = False
self.__set_default_keyframes_values(control_source, prop)
binding = self.source.get_control_binding(prop)
diff --git a/pitivi/dialogs/prefs.py b/pitivi/dialogs/prefs.py
index de7217656..2f673e2ac 100644
--- a/pitivi/dialogs/prefs.py
+++ b/pitivi/dialogs/prefs.py
@@ -402,7 +402,7 @@ class PreferencesDialog(Loggable):
self.content_box.bind_model(self.list_store, self._create_widget_func, None)
self.content_box.set_header_func(self._add_header_func, None)
- self.content_box.connect("row_activated", self.__row_activated_cb)
+ self.content_box.connect("row-activated", self.__row_activated_cb)
self.content_box.set_selection_mode(Gtk.SelectionMode.NONE)
self.content_box.props.margin = PADDING * 3
self.content_box.props.halign = Gtk.Align.CENTER
diff --git a/pitivi/effects.py b/pitivi/effects.py
index 7c71def48..d61ae68b1 100644
--- a/pitivi/effects.py
+++ b/pitivi/effects.py
@@ -32,6 +32,8 @@ import subprocess
import sys
import threading
from gettext import gettext as _
+from typing import Optional
+from typing import Union
import cairo
from gi.repository import Gdk
@@ -45,6 +47,8 @@ from gi.repository import Gtk
from pitivi.configure import get_pixmap_dir
from pitivi.configure import get_ui_dir
from pitivi.settings import GlobalSettings
+from pitivi.trackerperspective import EFFECT_TRACKED_OBJECT_ID_META
+from pitivi.trackerperspective import EFFECT_TRACKED_OBJECT_NAME_META
from pitivi.utils.loggable import Loggable
from pitivi.utils.ui import disable_scroll
from pitivi.utils.ui import EFFECT_TARGET_ENTRY
@@ -172,7 +176,6 @@ class EffectInfo:
def __init__(self, effect_name, media_type, categories,
human_name, description):
- object.__init__(self)
self.effect_name = effect_name
self.media_type = media_type
self.categories = categories
@@ -308,15 +311,29 @@ class EffectsManager(Loggable):
self.error("Can not use GL effects: %s", e)
HIDDEN_EFFECTS.extend(self.gl_effects)
- def get_info(self, bin_description):
+ def get_info(self, effect: Union[str, GES.Effect]) -> Optional[EffectInfo]:
"""Gets the info for an effect which can be applied.
Args:
- bin_description (str): The bin_description defining the effect.
+ effect: The effect itself or the bin_description defining it.
Returns:
EffectInfo: The info corresponding to the name, or None.
"""
+ if isinstance(effect, GES.Effect):
+ tracked_object_id = effect.get_string(EFFECT_TRACKED_OBJECT_ID_META)
+ if tracked_object_id:
+ tracked_object_name = effect.get_string(EFFECT_TRACKED_OBJECT_NAME_META)
+ # Translators: How the video effect which covers/hides a
+ # tracked object is listed. The {} is entered by the user
+ # and denotes the tracked object.
+ human_name = _("{} cover").format(tracked_object_name)
+ description = _("Object cover effect")
+ return EffectInfo(None, None, None, human_name, description)
+ bin_description = effect.props.bin_description
+ else:
+ bin_description = effect
+
name = EffectInfo.name_from_bin_description(bin_description)
return self._effects.get(name)
diff --git a/pitivi/greeterperspective.py b/pitivi/greeterperspective.py
index cb1b63d72..b822460d8 100644
--- a/pitivi/greeterperspective.py
+++ b/pitivi/greeterperspective.py
@@ -145,7 +145,7 @@ class GreeterPerspective(Perspective):
self.__recent_projects_listbox = builder.get_object("recent_projects_listbox")
self.__recent_projects_listbox.set_selection_mode(Gtk.SelectionMode.NONE)
self.__recent_projects_listbox.connect(
- "row_activated", self.__projects_row_activated_cb)
+ "row-activated", self.__projects_row_activated_cb)
self.__recent_projects_listbox.connect(
"button-press-event", self.__projects_button_press_cb)
diff --git a/pitivi/trackerperspective.py b/pitivi/trackerperspective.py
index 39c18dd3d..b12bcaf23 100644
--- a/pitivi/trackerperspective.py
+++ b/pitivi/trackerperspective.py
@@ -32,6 +32,7 @@ from gi.repository import GES
from gi.repository import Gio
from gi.repository import GObject
from gi.repository import Gst
+from gi.repository import GstController
from gi.repository import GstVideo
from gi.repository import Gtk
@@ -48,6 +49,11 @@ from pitivi.utils.ui import SPACING
# The meta of an Asset holding all the tracked objects data version 1.
ASSET_TRACKED_OBJECTS_META = "pitivi::tracker_data::1"
+# The meta of an Effect holding the object_id of the tracked object.
+EFFECT_TRACKED_OBJECT_ID_META = "pitivi:tracked_object_id"
+# The meta of an Effect holding the name of the tracked object.
+EFFECT_TRACKED_OBJECT_NAME_META = "pitivi:tracked_object_name"
+
# TODO: Replace with bisect.bisect_left when we use Python 3.10.
def bisect_left(values, val, key):
@@ -475,7 +481,7 @@ class ToplevelWidget(Gtk.Box, Loggable):
def _remove_object_button_clicked_cb(self, button):
row = self.object_listbox.get_selected_row()
index = row.get_index()
- tracked_object = self.tracked_objects_store.get_item(index)
+ tracked_object: TrackedObjectItem = self.tracked_objects_store.get_item(index)
self.tracked_objects_store.remove(index)
self.remove_object_button.props.sensitive = False
@@ -677,3 +683,118 @@ class TrackerPerspective(Perspective):
def refresh(self):
"""Refreshes the perspective."""
self.toplevel_widget.play_pause_button.grab_focus()
+
+
+class CoverObjectPopover(Gtk.Popover, Loggable):
+ """Popover for selecting an object to cover."""
+
+ # The representation of the effect providing the cover.
+ _EFFECT_PIPELINE = "video videotestsrc pattern=solid-color foreground-color=0xff000000 ! framepositioner
name=positioner ! gescompositor"
+
+ def __init__(self, app, clip: GES.Clip):
+ Gtk.Popover.__init__(self)
+ Loggable.__init__(self)
+
+ self.app = app
+
+ self.clip: GES.Clip = clip
+ self.object_manager: Optional[ObjectManager] = None
+
+ self.listbox = Gtk.ListBox()
+ self.listbox.connect("row-activated", self.__row_activated_cb)
+
+ self.scroll_window = Gtk.ScrolledWindow()
+ self.scroll_window.add(self.listbox)
+ self.scroll_window.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC)
+ self.scroll_window.props.max_content_height = 350
+ self.scroll_window.props.propagate_natural_height = True
+
+ vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, margin=PADDING)
+ vbox.pack_start(self.scroll_window, True, True, 0)
+ vbox.show_all()
+
+ self.add(vbox)
+
+ def update_object_list(self):
+ """Updates the list of not yet covered objects."""
+ self.object_manager = ObjectManager(self.clip.asset)
+
+ for row in self.listbox.get_children():
+ self.listbox.remove(row)
+
+ # Check which tracked objects have already been covered.
+ covered_objects = []
+ for effect in self.clip.get_top_effects():
+ tracked_object_id = effect.get_string(EFFECT_TRACKED_OBJECT_ID_META)
+ if tracked_object_id:
+ covered_objects.append(tracked_object_id)
+
+ # Allow selecting the not-yet-covered objects.
+ for _index, object_id, name in self.object_manager.objects:
+ if object_id not in covered_objects:
+ self.listbox.add(TrackedObjectRow(object_id, name))
+
+ # Allow tracking new objects.
+ button_row = Gtk.ListBoxRow(selectable=False)
+ track_objects_button = Gtk.Button(_("Track objects"))
+ track_objects_button.connect("clicked", self.__track_objects_button_clicked_cb)
+ button_row.add(track_objects_button)
+ self.listbox.add(button_row)
+
+ self.listbox.show_all()
+
+ def __row_activated_cb(self, listbox: Gtk.ListBox, row: TrackedObjectRow):
+ self._create_effect(row.object_id, row.name)
+
+ self.popdown()
+
+ def __effect_control_binding_added_cb(self, track_element, binding, object_id):
+ control_source = binding.props.control_source
+ timed_data = self.object_manager.values[object_id]
+ for timestamp, (x, y, w, h) in timed_data:
+ if binding.name == "posx":
+ value = x
+ elif binding.name == "posy":
+ value = y
+ elif binding.name == "width":
+ value = w
+ elif binding.name == "height":
+ value = h
+ else:
+ break
+
+ control_source.set(timestamp, value)
+
+ def __clip_child_added_cb(self, clip, track_element, object_id):
+ if not isinstance(track_element, GES.Effect):
+ return
+
+ clip.disconnect_by_func(self.__clip_child_added_cb)
+
+ track_element.connect("control-binding-added",
+ self.__effect_control_binding_added_cb,
+ object_id)
+ try:
+ for prop in ("posx", "posy", "width", "height"):
+ control_source = GstController.InterpolationControlSource()
+ control_source.props.mode = GstController.InterpolationMode.NONE
+ track_element.set_control_source(control_source, prop, "direct-absolute")
+ finally:
+ track_element.disconnect_by_func(self.__effect_control_binding_added_cb)
+
+ def _create_effect(self, object_id: str, name: str):
+ effect = GES.Effect.new(self._EFFECT_PIPELINE)
+ effect.register_meta_string(GES.MetaFlag.READABLE, EFFECT_TRACKED_OBJECT_ID_META, object_id)
+ effect.register_meta_string(GES.MetaFlag.READABLE, EFFECT_TRACKED_OBJECT_NAME_META, name)
+
+ self.log("Waiting for effect to be added to the clip")
+ self.clip.connect("child-added", self.__clip_child_added_cb, object_id)
+ self.clip.add_top_effect(effect, 0)
+
+ self.app.project_manager.current_project.pipeline.commit_timeline()
+
+ def __track_objects_button_clicked_cb(self, button):
+ tracker = TrackerPerspective(self.app, self.clip.asset)
+ self.app.project_manager.current_project.pipeline.pause()
+ tracker.setup_ui()
+ self.app.gui.show_perspective(tracker)
diff --git a/pitivi/utils/custom_effect_widgets.py b/pitivi/utils/custom_effect_widgets.py
index 44b5a60ed..5c2e27157 100644
--- a/pitivi/utils/custom_effect_widgets.py
+++ b/pitivi/utils/custom_effect_widgets.py
@@ -23,6 +23,7 @@ from gi.repository import Gdk
from gi.repository import Gtk
from pitivi import configure
+from pitivi.trackerperspective import EFFECT_TRACKED_OBJECT_ID_META
from pitivi.utils.loggable import Loggable
from pitivi.utils.ui import create_model
from pitivi.utils.widgets import ColorPickerButton
@@ -31,12 +32,6 @@ from pitivi.utils.widgets import ColorPickerButton
CUSTOM_WIDGETS_DIR = os.path.join(configure.get_ui_dir(), "customwidgets")
-def setup_custom_effect_widgets(effect_prop_manager):
- """Sets up the specified effects manager to be able to create custom UI."""
- effect_prop_manager.connect("create_widget", create_custom_widget_cb)
- effect_prop_manager.connect("create_property_widget", create_custom_prop_widget_cb)
-
-
def setup_from_ui_file(element_setting_widget, path):
"""Creates and connects the UI for a widget."""
# Load the ui file using builder
@@ -59,6 +54,11 @@ def create_custom_prop_widget_cb(unused_effect_prop_manager, effect_widget, effe
def create_custom_widget_cb(effect_prop_manager, effect_widget, effect):
"""Creates custom effect UI."""
+ tracked_object_id = effect.get_string(EFFECT_TRACKED_OBJECT_ID_META)
+ if tracked_object_id:
+ widget = object_cover_effect_widget(effect_prop_manager, effect_widget, effect)
+ return widget
+
effect_name = effect.get_property("bin-description")
path = os.path.join(CUSTOM_WIDGETS_DIR, effect_name + ".ui")
@@ -83,6 +83,67 @@ def create_custom_widget_cb(effect_prop_manager, effect_widget, effect):
return widget
+def object_cover_effect_widget(effect_prop_manager, element_setting_widget, element):
+ """Creates the UI for the `Object cover` effect."""
+ builder = setup_from_ui_file(element_setting_widget, os.path.join(CUSTOM_WIDGETS_DIR,
"pitivi:object_effect.ui"))
+ base_table = builder.get_object("base_table")
+
+ def set_foreground_color(color):
+ from pitivi.undo.timeline import CommitTimelineFinalizingAction
+ pipeline = effect_prop_manager.app.project_manager.current_project.pipeline
+ action_log = effect_prop_manager.app.action_log
+ with action_log.started("Effect property change",
+ finalizing_action=CommitTimelineFinalizingAction(pipeline),
+ toplevel=True):
+ element.set_child_property("foreground-color", color)
+
+ def color_picker_value_changed_cb(widget: ColorPickerButton):
+ """Handles the selection of a color with the color picker."""
+ argb = widget.calculate_argb()
+ set_foreground_color(argb)
+
+ color_picker_button = ColorPickerButton()
+ base_table.add(color_picker_button)
+ handler_value_changed = color_picker_button.connect("value-changed", color_picker_value_changed_cb)
+
+ def color_button_color_set_cb(button: Gtk.ColorButton):
+ """Handles the selection of a color with the color button."""
+ color = button.get_rgba()
+ red = int(color.red * 255)
+ green = int(color.green * 255)
+ blue = int(color.blue * 255)
+ argb = (0xFF << 24) + (red << 16) + (green << 8) + blue
+ set_foreground_color(argb)
+
+ color_button = builder.get_object("color_button")
+ handler_color_set = color_button.connect("color-set", color_button_color_set_cb)
+
+ def update_ui():
+ res, argb = element.get_child_property("foreground-color")
+ assert res
+ color = Gdk.RGBA()
+ color.red = ((argb >> 16) & 0xFF) / 255
+ color.green = ((argb >> 8) & 0xFF) / 255
+ color.blue = ((argb >> 0) & 0xFF) / 255
+ color.alpha = ((argb >> 24) & 0xFF) / 255
+ color_button.set_rgba(color)
+
+ update_ui()
+
+ def notify_foreground_color_cb(self, element, param_spec):
+ color_picker_button.handler_block(handler_value_changed)
+ color_button.handler_block(handler_color_set)
+ try:
+ update_ui()
+ finally:
+ color_picker_button.handler_unblock(handler_value_changed)
+ color_button.handler_block(handler_color_set)
+
+ element.connect("notify::foreground-color", notify_foreground_color_cb)
+
+ return base_table
+
+
def create_alpha_widget(effect_prop_manager, element_setting_widget, element):
"""Creates the UI for the `alpha` effect."""
builder = setup_from_ui_file(element_setting_widget, os.path.join(CUSTOM_WIDGETS_DIR, "alpha.ui"))
diff --git a/pitivi/utils/ui.py b/pitivi/utils/ui.py
index 4303cf793..cf591ddbe 100644
--- a/pitivi/utils/ui.py
+++ b/pitivi/utils/ui.py
@@ -437,20 +437,18 @@ def gtk_style_context_get_color(context, state):
return color
-def argb_to_gdk_rgba(color_int):
- return Gdk.RGBA(color_int / 256 ** 2 % 256 / 255.,
- color_int / 256 ** 1 % 256 / 255.,
- color_int / 256 ** 0 % 256 / 255.,
- color_int / 256 ** 3 % 256 / 255.)
+def argb_to_gdk_rgba(argb: int) -> Gdk.RGBA:
+ return Gdk.RGBA(((argb >> 16) & 0xFF) / 255,
+ ((argb >> 8) & 0xFF) / 255,
+ ((argb >> 0) & 0xFF) / 255,
+ ((argb >> 24) & 0xFF) / 255)
-def gdk_rgba_to_argb(color):
- color_int = 0
- color_int += int(color.alpha * 255) * 256 ** 3
- color_int += int(color.red * 255) * 256 ** 2
- color_int += int(color.green * 255) * 256 ** 1
- color_int += int(color.blue * 255) * 256 ** 0
- return color_int
+def gdk_rgba_to_argb(color: Gdk.RGBA) -> int:
+ return ((int(color.alpha * 255) << 24) +
+ (int(color.red * 255) << 16) +
+ (int(color.green * 255) << 8) +
+ int(color.blue * 255))
def pack_color_32(red, green, blue, alpha=0xFFFF):
@@ -497,10 +495,6 @@ def unpack_color_64(value):
return red, green, blue, alpha
-def hex_to_rgb(value):
- return tuple(float(int(value[i:i + 2], 16)) / 255.0 for i in range(0, 6, 2))
-
-
def set_cairo_color(context, color):
if isinstance(color, Gdk.RGBA):
cairo_color = (float(color.red), float(color.green), float(color.blue))
diff --git a/tests/test_trackerperspective.py b/tests/test_trackerperspective.py
index 2d5e8905a..2a0af20a6 100644
--- a/tests/test_trackerperspective.py
+++ b/tests/test_trackerperspective.py
@@ -16,12 +16,57 @@
# License along with this program; if not, see <http://www.gnu.org/licenses/>.
"""Tests for the pitivi.trackerperspective module."""
# pylint: disable=protected-access
+from unittest import skipUnless
+
from gi.repository import GES
+from pitivi.check import MISSING_SOFT_DEPS
from pitivi.trackerperspective import ObjectManager
from tests import common
+class TestCoverObjectPopover(common.TestCase):
+ """Tests for the CoverObjectPopover class."""
+
+ @skipUnless("cvtracker" not in MISSING_SOFT_DEPS, "cvtracker element missing")
+ @common.setup_project_with_clips(assets_names=["tears_of_steel.webm"])
+ @common.setup_clipproperties
+ def test_cover(self):
+ clip, = self.layer.get_clips()
+ self.click_clip(clip, expect_selected=True)
+
+ expander = self.clipproperties.effect_expander
+ expander.cover_object_button.clicked()
+ self.assertTrue(expander.cover_popover.props.visible)
+ # Only one row containing the Track Objects button should exist.
+ self.assertEqual(len(expander.cover_popover.listbox.get_children()), 1)
+
+ expander.cover_object_button.clicked()
+ self.assertFalse(expander.cover_popover.props.visible)
+
+ object_manager = ObjectManager(clip.asset)
+ object_manager.add_object(1, "object1", "Object 1")
+ object_manager.update_object_position("object1", 0, (10, 20, 30, 40))
+ object_manager.add_object(2, "object2", "Object 2")
+ object_manager.update_object_position("object2", 1, (20, 30, 40, 50))
+ object_manager.save()
+
+ expander.cover_object_button.clicked()
+ self.assertTrue(expander.cover_popover.props.visible)
+ # Two rows for two object and one for Track Objects.
+ self.assertEqual(len(expander.cover_popover.listbox.get_children()), 3)
+
+ self.assertEqual(len(clip.get_top_effects()), 0)
+ expander.cover_popover.listbox.get_row_at_index(0).emit("activate")
+ self.assertFalse(expander.cover_popover.props.visible)
+ self.assertEqual(len(clip.get_top_effects()), 1)
+
+ expander.cover_object_button.clicked()
+ self.assertTrue(expander.cover_popover.props.visible)
+ # One row for the uncovered object and one for the Track Objects button.
+ self.assertEqual(len(expander.cover_popover.listbox.get_children()), 2)
+
+
class TestObjectManager(common.TestCase):
"""Tests for the ObjectManager class."""
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]