Commit 699ae091 authored by yatinmaan's avatar yatinmaan

WIP: effects.py: Port Effects UI to ListBox and add categories

parent 3c719067
Pipeline #88883 passed with stage
in 25 minutes and 21 seconds
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.20.0 --> <!-- Generated with glade 3.22.1 -->
<interface> <interface>
<requires lib="gtk+" version="3.10"/> <requires lib="gtk+" version="3.10"/>
<object class="GtkToolbar" id="effectslibrary_toolbar"> <object class="GtkToolbar" id="effectslibrary_toolbar">
...@@ -7,60 +7,12 @@ ...@@ -7,60 +7,12 @@
<property name="can_focus">False</property> <property name="can_focus">False</property>
<property name="show_arrow">False</property> <property name="show_arrow">False</property>
<property name="icon_size">1</property> <property name="icon_size">1</property>
<child>
<object class="GtkToggleToolButton" id="video_togglebutton">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="tooltip_text" translatable="yes">Show video effects</property>
<property name="use_underline">True</property>
<property name="icon_name">video-x-generic</property>
<property name="active">True</property>
<signal name="toggled" handler="_toggleViewTypeCb" swapped="no"/>
<child internal-child="accessible">
<object class="AtkObject" id="video_togglebutton-atkobject">
<property name="AtkObject::accessible-name">effects library video togglebutton</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="homogeneous">True</property>
</packing>
</child>
<child>
<object class="GtkToggleToolButton" id="audio_togglebutton">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="tooltip_text" translatable="yes">Show audio effects</property>
<property name="use_underline">True</property>
<property name="icon_name">audio-x-generic</property>
<signal name="toggled" handler="_toggleViewTypeCb" swapped="no"/>
<child internal-child="accessible">
<object class="AtkObject" id="audio_togglebutton-atkobject">
<property name="AtkObject::accessible-name">effects library audio togglebutton</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="homogeneous">True</property>
</packing>
</child>
<child> <child>
<object class="GtkToolItem" id="toolbutton1"> <object class="GtkToolItem" id="toolbutton1">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">False</property> <property name="can_focus">False</property>
<child> <child>
<object class="GtkComboBoxText" id="categories"> <placeholder/>
<property name="visible">True</property>
<property name="can_focus">False</property>
<signal name="changed" handler="_categoryChangedCb" swapped="no"/>
<child internal-child="accessible">
<object class="AtkObject" id="categories-atkobject">
<property name="AtkObject::accessible-name">effect category combobox</property>
</object>
</child>
</object>
</child> </child>
</object> </object>
<packing> <packing>
...@@ -74,22 +26,16 @@ ...@@ -74,22 +26,16 @@
<property name="can_focus">False</property> <property name="can_focus">False</property>
<property name="margin_start">5</property> <property name="margin_start">5</property>
<child> <child>
<object class="GtkEntry" id="search_entry"> <object class="GtkSearchEntry" id="search_entry">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">True</property> <property name="can_focus">True</property>
<property name="has_tooltip">True</property> <property name="has_tooltip">True</property>
<property name="margin_start">3</property> <property name="caps_lock_warning">False</property>
<property name="invisible_char"></property> <property name="primary_icon_name">edit-find-symbolic</property>
<property name="secondary_icon_name">edit-clear-symbolic</property> <property name="secondary_icon_name">edit-clear-symbolic</property>
<property name="secondary_icon_tooltip_text" translatable="yes">Clear the current search</property> <property name="placeholder_text" translatable="yes">Search Effects</property>
<property name="placeholder_text" translatable="yes">Search...</property>
<signal name="changed" handler="_searchEntryChangedCb" swapped="no"/>
<signal name="icon-release" handler="_searchEntryIconClickedCb" swapped="no"/> <signal name="icon-release" handler="_searchEntryIconClickedCb" swapped="no"/>
<child internal-child="accessible"> <signal name="search-changed" handler="_searchEntryChangedCb" swapped="no"/>
<object class="AtkObject" id="search_entry-atkobject">
<property name="AtkObject::accessible-name">effects library search entry</property>
</object>
</child>
</object> </object>
</child> </child>
</object> </object>
......
...@@ -35,6 +35,7 @@ import sys ...@@ -35,6 +35,7 @@ import sys
import threading import threading
from gettext import gettext as _ from gettext import gettext as _
import cairo
from gi.repository import Gdk from gi.repository import Gdk
from gi.repository import GdkPixbuf from gi.repository import GdkPixbuf
from gi.repository import GES from gi.repository import GES
...@@ -55,7 +56,16 @@ from pitivi.utils.widgets import GstElementSettingsWidget ...@@ -55,7 +56,16 @@ from pitivi.utils.widgets import GstElementSettingsWidget
(VIDEO_EFFECT, AUDIO_EFFECT) = list(range(1, 3)) (VIDEO_EFFECT, AUDIO_EFFECT) = list(range(1, 3))
AUDIO_EFFECTS_CATEGORIES = () AUDIO_EFFECTS_CATEGORIES = (
(_("Audio"), (
"pitch", "freeverb", "removesilence", "festival", "speed",
"audiorate", "volume", "equalizer-nbands", "equalizer-3bands",
"equalizer-10bands", "rglimiter", "rgvolume", "audiopanorama",
"audioinvert", "audiokaraoke", "audioamplify", "audiodynamic",
"audiocheblimit", "audiochebband", "audioiirfilter", "audiowsinclimit",
"audiowsincband", "audiofirfilter", "audioecho", "scaletempo", "stereo",
)),
)
ALLOWED_ONLY_ONCE_EFFECTS = ['videoflip'] ALLOWED_ONLY_ONCE_EFFECTS = ['videoflip']
...@@ -187,6 +197,7 @@ class EffectInfo(object): ...@@ -187,6 +197,7 @@ class EffectInfo(object):
except: except:
icon = GdkPixbuf.Pixbuf.new_from_file( icon = GdkPixbuf.Pixbuf.new_from_file(
os.path.join(pixdir, "defaultthumbnail.svg")) os.path.join(pixdir, "defaultthumbnail.svg"))
return icon return icon
@property @property
...@@ -379,167 +390,137 @@ class EffectListWidget(Gtk.Box, Loggable): ...@@ -379,167 +390,137 @@ class EffectListWidget(Gtk.Box, Loggable):
builder.connect_signals(self) builder.connect_signals(self)
toolbar = builder.get_object("effectslibrary_toolbar") toolbar = builder.get_object("effectslibrary_toolbar")
toolbar.get_style_context().add_class(Gtk.STYLE_CLASS_INLINE_TOOLBAR) toolbar.get_style_context().add_class(Gtk.STYLE_CLASS_INLINE_TOOLBAR)
self.video_togglebutton = builder.get_object("video_togglebutton")
self.audio_togglebutton = builder.get_object("audio_togglebutton")
self.categoriesWidget = builder.get_object("categories")
self.searchEntry = builder.get_object("search_entry") self.searchEntry = builder.get_object("search_entry")
# Store self.main_view = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
self.storemodel = Gtk.ListStore( self.category_view = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
str, str, int, object, str, GdkPixbuf.Pixbuf) self.search_view = Gtk.ListBox()
self.storemodel.set_sort_column_id( self.search_view.set_filter_func(self._search_filter)
COL_NAME_TEXT, Gtk.SortType.ASCENDING)
self.main_view.pack_start(self.category_view, True, True, 0)
# Create the filter for searching the storemodel. self.main_view.pack_start(self.search_view, True, True, 0)
self.model_filter = self.storemodel.filter_new()
self.model_filter.set_visible_func(self._setRowVisible, data=None)
self.view = Gtk.TreeView(model=self.model_filter)
self.view.props.headers_visible = False
self.view.get_selection().set_mode(Gtk.SelectionMode.SINGLE)
icon_col = Gtk.TreeViewColumn()
icon_col.set_spacing(SPACING)
icon_col.set_sizing(Gtk.TreeViewColumnSizing.FIXED)
icon_col.props.fixed_width = ICON_WIDTH
icon_cell = Gtk.CellRendererPixbuf()
icon_cell.props.xpad = 6
icon_col.pack_start(icon_cell, True)
icon_col.add_attribute(icon_cell, "pixbuf", COL_ICON)
text_col = Gtk.TreeViewColumn()
text_col.set_expand(True)
text_col.set_spacing(SPACING)
text_col.set_sizing(Gtk.TreeViewColumnSizing.AUTOSIZE)
text_cell = Gtk.CellRendererText()
text_cell.props.yalign = 0.0
text_cell.props.xpad = 6
text_cell.set_property("ellipsize", Pango.EllipsizeMode.END)
text_col.pack_start(text_cell, True)
text_col.set_cell_data_func(
text_cell, self.viewDescriptionCellDataFunc, None)
self.view.append_column(icon_col)
self.view.append_column(text_col)
self.view.connect("query-tooltip", self._treeViewQueryTooltipCb)
self.view.props.has_tooltip = True
# Make the treeview a drag source which provides effects.
self.view.enable_model_drag_source(
Gdk.ModifierType.BUTTON1_MASK, [EFFECT_TARGET_ENTRY], Gdk.DragAction.COPY)
self.view.connect("button-press-event", self._buttonPressEventCb)
self.view.connect("select-cursor-row", self._enterPressEventCb)
self.view.connect("drag-data-get", self._dndDragDataGetCb)
scrollwin = Gtk.ScrolledWindow() scrollwin = Gtk.ScrolledWindow()
scrollwin.props.hscrollbar_policy = Gtk.PolicyType.NEVER scrollwin.props.hscrollbar_policy = Gtk.PolicyType.NEVER
scrollwin.props.vscrollbar_policy = Gtk.PolicyType.AUTOMATIC scrollwin.props.vscrollbar_policy = Gtk.PolicyType.AUTOMATIC
scrollwin.add(self.view) scrollwin.add(self.main_view)
self.pack_start(toolbar, False, False, 0) self.pack_start(toolbar, False, False, 0)
self.pack_start(scrollwin, True, True, 0) self.pack_start(scrollwin, True, True, 0)
# Delay the loading of the available effects so the application # Delay the loading of the available effects so the application
# starts faster. # starts faster.
GLib.idle_add(self._loadAvailableEffectsCb) GLib.idle_add(self._load_available_effects_cb)
self.populate_categories_widget()
# Individually show the tab's widgets. # Individually show the tab's widgets.
# If you use self.show_all(), the tab will steal focus on startup. # If you use self.show_all(), the tab will steal focus on startup.
scrollwin.show_all() scrollwin.show_all()
toolbar.show_all() toolbar.show_all()
self.search_view.hide()
def _treeViewQueryTooltipCb(self, view, x, y, keyboard_mode, tooltip): def _load_available_effects_cb(self):
is_row, x, y, model, path, tree_iter = view.get_tooltip_context( self._set_up_category_view()
x, y, keyboard_mode) self._add_effects_to_listbox(self.search_view)
if not is_row:
return False
view.set_tooltip_row(tooltip, path) return False
  • Can be removed. By default a method returns None which is the same as False.

Please register or sign in to reply
tooltip.set_markup(self.formatDescription(model, tree_iter))
return True
def viewDescriptionCellDataFunc(self, unused_column, cell, model, iter_, unused_data): def _set_up_category_view(self):
cell.props.markup = self.formatDescription(model, iter_) all_categories = sorted(set(self.app.effects.video_categories +
self.app.effects.audio_categories)) # TODO? Merge Categories
def formatDescription(self, model, iter_): # Add category expanders
name, element_name, desc = model.get(iter_, COL_NAME_TEXT, COL_ELEMENT_NAME, COL_DESC_TEXT) for category in all_categories:
escape = GLib.markup_escape_text widget = self._get_category_widget(category)
return "<b>%s</b>\n%s" % (escape(name), escape(desc)) self.category_view.add(widget)
def _loadAvailableEffectsCb(self): # Add effects to category expanders
self._addFactories(self.app.effects.video_effects, VIDEO_EFFECT) for box in self.category_view.get_children():
self._addFactories(self.app.effects.audio_effects, AUDIO_EFFECT) expander = box.get_children()[0]
return False listbox = expander.get_child()
category_name = expander.get_label()
self._add_effects_to_listbox(listbox, category_name)
self.category_view.show_all()
def _add_effects_to_listbox(self, listbox, category='All effects'):
effects = self.app.effects.video_effects + self.app.effects.audio_effects
for effect in effects:
name = effect.get_name()
def _addFactories(self, elements, effectType):
for element in elements:
name = element.get_name()
if name in HIDDEN_EFFECTS: if name in HIDDEN_EFFECTS:
continue continue
effect_info = self.app.effects.getInfo(name) effect_info = self.app.effects.getInfo(name)
self.storemodel.append([effect_info.human_name,
effect_info.description,
effectType,
effect_info.categories,
name,
effect_info.icon])
def populate_categories_widget(self):
self.categoriesWidget.get_model().clear()
icon_column = self.view.get_column(0)
if self._effectType is VIDEO_EFFECT:
for category in self.app.effects.video_categories:
self.categoriesWidget.append_text(category)
icon_column.props.visible = True
else:
for category in self.app.effects.audio_categories:
self.categoriesWidget.append_text(category)
icon_column.props.visible = False
self.categoriesWidget.set_active(0) if category in effect_info.categories:
widget = self._get_effect_widget(name)
listbox.add(widget)
def _dndDragDataGetCb(self, unused_view, drag_context, selection_data, unused_info, unused_timestamp): def _get_category_widget(self, category):
  • nitpick: this method always creates a widget, maybe use "create" instead of "get"

Please register or sign in to reply
data = bytes(self.getSelectedEffect(), "UTF-8") box = Gtk.Box()
selection_data.set(drag_context.list_targets()[0], 0, data) expander = Gtk.Expander(label=category, margin=10)
Please register or sign in to reply
listbox = Gtk.ListBox(activate_on_single_click=False)
listbox.connect("row-activated", self._apply_selected_effect)
def _rowUnderMouseSelected(self, view, event): expander.add(listbox)
result = view.get_path_at_pos(int(event.x), int(event.y)) box.pack_start(expander, True, True, 0)
if result:
path = result[0]
selection = view.get_selection()
return selection.path_is_selected(path) and\
selection.count_selected_rows() > 0
return False
def _enterPressEventCb(self, unused_view, unused_event=None): return box
self._addSelectedEffect()
def _buttonPressEventCb(self, view, event): def _get_effect_widget(self, effect_name):
chain_up = True effect_info = self.app.effects.getInfo(effect_name)
if event.button == 3: effect_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, margin=5)
  • Instead of using 5, use the constant we have for this: SPACING IIRC (and 2*SPACING instead of 10 etc)

Please register or sign in to reply
chain_up = False effect_box.effect_name = effect_name
elif event.type == getattr(Gdk.EventType, '2BUTTON_PRESS'): effect_box.set_tooltip_text(effect_info.description)
self._addSelectedEffect() label = Gtk.Label(effect_info.human_name, xalign=0)
else: icon = Gtk.Image.new_from_pixbuf(effect_info.icon)
chain_up = not self._rowUnderMouseSelected(view, event)
if chain_up: effect_box.pack_start(icon, False, True, 5)
Please register or sign in to reply
self._draggedItems = None effect_box.pack_start(label, True, True, 0)
else:
self._draggedItems = self.getSelectedEffect() # Set up drag behavoir
eventbox = Gtk.EventBox(visible_window=False)
eventbox.drag_source_set(Gdk.ModifierType.BUTTON1_MASK, [EFFECT_TARGET_ENTRY], Gdk.DragAction.COPY)
eventbox.connect("drag-data-get", self._dndDragDataGetCb)
eventbox.connect("drag-begin", self._dndDragBeginCb)
eventbox.add(effect_box)
row = Gtk.ListBoxRow(selectable=False)
row.add(eventbox)
Gtk.TreeView.do_button_press_event(view, event) return row
return True
def _addSelectedEffect(self): def _dndDragDataGetCb(self, eventbox, drag_context, selection_data, unused_info, unused_timestamp):
effect_box = eventbox.get_child()
data = bytes(effect_box.effect_name, "UTF-8")
selection_data.set(drag_context.list_targets()[0], 0, data)
def _dndDragBeginCb(self, eventbox, context):
effect_box = eventbox.get_child()
effect_info = self.app.effects.getInfo(effect_box.effect_name)
# Draw drag-icon
icon = effect_info.icon
icon_height = icon.get_height()
icon_width = icon.get_width()
surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, icon_width, icon_height)
ctx = cairo.Context(surface)
# Center the icon around the cursor.
ctx.translate(icon_width / 2, icon_height / 2)
surface.set_device_offset(-icon_width / 2, -icon_height / 2)
Gdk.cairo_set_source_pixbuf(ctx, icon, 0, 0)
ctx.paint_with_alpha(0.35)
Gtk.drag_set_icon_surface(context, surface)
def _apply_selected_effect(self, unused_listbox, row):
"""Adds the selected effect to the single selected clip, if any.""" """Adds the selected effect to the single selected clip, if any."""
effect = self.getSelectedEffect() effect_box = row.get_child().get_child()
effect_info = self.app.effects.getInfo(effect) effect_info = self.app.effects.getInfo(effect_box.effect_name)
if not effect_info: if not effect_info:
return return
timeline = self.app.gui.editor.timeline_ui.timeline timeline = self.app.gui.editor.timeline_ui.timeline
...@@ -553,51 +534,27 @@ class EffectListWidget(Gtk.Box, Loggable): ...@@ -553,51 +534,27 @@ class EffectListWidget(Gtk.Box, Loggable):
toplevel=True): toplevel=True):
clip.ui.add_effect(effect_info) clip.ui.add_effect(effect_info)
def getSelectedEffect(self): def _search_filter(self, row):
if self._draggedItems: effect_box = row.get_child().get_child()
return self._draggedItems label = effect_box.get_children()[1]
model, rows = self.view.get_selection().get_selected_rows()
path = self.model_filter.convert_path_to_child_path(rows[0])
return self.storemodel[path][COL_ELEMENT_NAME]
def _toggleViewTypeCb(self, widget): label_text = label.get_text().lower()
"""Switches the view mode between video and audio. search_key = self.searchEntry.get_text().lower()
This makes the two togglebuttons behave like a group of radiobuttons. return search_key in label_text
"""
if widget is self.video_togglebutton:
self.audio_togglebutton.set_active(not widget.get_active())
else:
assert widget is self.audio_togglebutton
self.video_togglebutton.set_active(not widget.get_active())
if self.video_togglebutton.get_active():
self._effectType = VIDEO_EFFECT
else:
self._effectType = AUDIO_EFFECT
self.populate_categories_widget()
self.model_filter.refilter()
def _categoryChangedCb(self, unused_combobox):
self.model_filter.refilter()
def _searchEntryChangedCb(self, unused_entry): def _searchEntryChangedCb(self, unused_entry):
self.model_filter.refilter() if unused_entry.get_text() == "":
  • Can be written as if unused_entry.get_text():, for a more Pythonic look, as empty strings evaluate to False.

    Edited by Alexandru Băluț
Please register or sign in to reply
self.category_view.show()
self.search_view.hide()
else:
self.search_view.invalidate_filter()
self.search_view.show_all()
self.category_view.hide()
def _searchEntryIconClickedCb(self, entry, unused, unused1): def _searchEntryIconClickedCb(self, entry, unused, unused1):
entry.set_text("") entry.set_text("")
def _setRowVisible(self, model, iter, unused_data):
if not self._effectType == model.get_value(iter, COL_EFFECT_TYPE):
return False
if model.get_value(iter, COL_EFFECT_CATEGORIES) is None:
return False
if self.categoriesWidget.get_active_text() not in model.get_value(iter, COL_EFFECT_CATEGORIES):
return False
text = self.searchEntry.get_text().lower()
return text in model.get_value(iter, COL_DESC_TEXT).lower() or\
text in model.get_value(iter, COL_NAME_TEXT).lower()
PROPS_TO_IGNORE = ['name', 'qos', 'silent', 'message', 'parent'] PROPS_TO_IGNORE = ['name', 'qos', 'silent', 'message', 'parent']
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment