From 03aa34508e04239f11efc57e144f64c13d0ce333 Mon Sep 17 00:00:00 2001 From: yatinmaan Date: Thu, 13 Jun 2019 23:24:14 +0200 Subject: [PATCH 1/5] effects: Refactor the Effects Library to show expandable categories --- data/pixmaps/effects/defaultthumbnail.svg | 863 +++++++++++----------- data/ui/effectslibrary.ui | 68 +- pitivi/effects.py | 425 +++++------ 3 files changed, 598 insertions(+), 758 deletions(-) diff --git a/data/pixmaps/effects/defaultthumbnail.svg b/data/pixmaps/effects/defaultthumbnail.svg index b2bc02bde..0d593758e 100644 --- a/data/pixmaps/effects/defaultthumbnail.svg +++ b/data/pixmaps/effects/defaultthumbnail.svg @@ -1,5 +1,6 @@ + + width="21.166298mm" + height="11.90625mm" + viewBox="0 0 21.166298 11.90625" + version="1.1" + id="svg8" + inkscape:version="0.92.4 5da689c313, 2019-01-14" + sodipodi:docname="defaultthumbnail.svg"> + id="defs2"> + id="linearGradient6010" + inkscape:collect="always"> + style="stop-color:#e06666;stop-opacity:1;" /> + style="stop-color:#e06666;stop-opacity:0;" /> + id="linearGradient5994" + inkscape:collect="always"> + style="stop-color:#8b0000;stop-opacity:1;" /> + style="stop-color:#af0000;stop-opacity:1" /> + id="linearGradient5966" + inkscape:collect="always"> + style="stop-color:#eeeeec;stop-opacity:1;" /> + style="stop-color:#cbcbc3;stop-opacity:1" /> + id="linearGradient5958" + inkscape:collect="always"> + style="stop-color:#df0000;stop-opacity:1" /> + style="stop-color:#ba0000;stop-opacity:1" /> + id="linearGradient5172" + inkscape:collect="always"> + style="stop-color:#cc0000;stop-opacity:1;" /> - - - - + style="stop-color:#ba0000;stop-opacity:1" /> + sodipodi:type="inkscape:persp3d" /> + style="stop-color:black;stop-opacity:0;" /> + id="stop3710" /> + style="stop-color:black;stop-opacity:0;" /> - + + fx="4.9929786" + cy="43.5" + cx="4.9929786" + gradientTransform="matrix(2.003784,0,0,1.4,-20.01187,-104.4)" + gradientUnits="userSpaceOnUse" + id="radialGradient2096" + xlink:href="#linearGradient3688" + inkscape:collect="always" /> + id="linearGradient3688" + inkscape:collect="always"> + style="stop-color:black;stop-opacity:1;" /> + style="stop-color:black;stop-opacity:0;" /> + fx="4.9929786" + cy="43.5" + cx="4.9929786" + gradientTransform="matrix(2.003784,0,0,1.4,27.98813,-17.4)" + gradientUnits="userSpaceOnUse" + id="radialGradient2094" + xlink:href="#linearGradient3688" + inkscape:collect="always" /> + id="linearGradient9372" + inkscape:collect="always"> + style="stop-color:#fce94f;stop-opacity:1;" /> + style="stop-color:#fce94f;stop-opacity:0;" /> + id="linearGradient6739" + inkscape:collect="always"> + style="stop-color:#c00;stop-opacity:1" /> + style="stop-color:#730000;stop-opacity:1" /> + y2="9.0032368" + x2="11" + y1="5.7221918" + x1="11" + id="linearGradient6745" + xlink:href="#linearGradient6739" + inkscape:collect="always" /> + - - + inkscape:collect="always" /> - - + + + - + id="radialGradient10283" + xlink:href="#linearGradient9372" + inkscape:collect="always" /> + + x2="3.2703688" + y1="14.189748" + x1="5.6568542" + gradientUnits="userSpaceOnUse" + id="linearGradient5952" + xlink:href="#linearGradient5172" + inkscape:collect="always" /> + y2="19.545048" + x2="9.982995" + y1="25.639849" + x1="9.7178297" + id="linearGradient5964" + xlink:href="#linearGradient5958" + inkscape:collect="always" /> + x2="8.4977846" + y1="13.208529" + x1="8.859499" + id="linearGradient5972" + xlink:href="#linearGradient5966" + inkscape:collect="always" /> + x2="25.749866" + y1="35.811172" + x1="25.749866" + id="linearGradient6000" + xlink:href="#linearGradient5994" + inkscape:collect="always" /> - + + x2="45.068298" + y1="25.116896" + x1="39.439995" + id="linearGradient6016" + xlink:href="#linearGradient6010" + inkscape:collect="always" /> - - + showgrid="true" + showguides="false" + inkscape:window-width="1850" + inkscape:window-height="1016" + inkscape:window-x="70" + inkscape:window-y="27" + inkscape:window-maximized="1" /> + id="metadata5"> image/svg+xml + + inkscape:groupmode="layer" + id="layer1" + transform="translate(-128.50053,-98.142981)"> + inkscape:label="Layer 1" + id="layer1-6" + transform="matrix(0.27164802,0,0,0.27580143,132.55853,97.257168)"> + transform="matrix(1.0206646,0,0,1.1351534,-0.426478,-6.9707553)" + inkscape:label="Shadow" + id="g2043"> - - - + inkscape:label="Shadow" + id="g2036" + style="display:inline"> + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - diff --git a/data/ui/effectslibrary.ui b/data/ui/effectslibrary.ui index 99310fa37..e8f93ee8d 100644 --- a/data/ui/effectslibrary.ui +++ b/data/ui/effectslibrary.ui @@ -1,5 +1,5 @@ - + @@ -7,60 +7,12 @@ False False 1 - - - True - False - Show video effects - True - video-x-generic - True - - - - effects library video togglebutton - - - - - False - True - - - - - True - False - Show audio effects - True - audio-x-generic - - - - effects library audio togglebutton - - - - - False - True - - True False - - True - False - - - - effect category combobox - - - + @@ -74,22 +26,16 @@ False 5 - + True True True - 3 - + False + edit-find-symbolic edit-clear-symbolic - Clear the current search Search... - - - - - effects library search entry - - + + diff --git a/pitivi/effects.py b/pitivi/effects.py index 9affbb4d4..c58483568 100644 --- a/pitivi/effects.py +++ b/pitivi/effects.py @@ -33,6 +33,7 @@ import sys import threading from gettext import gettext as _ +import cairo from gi.repository import Gdk from gi.repository import GdkPixbuf from gi.repository import GES @@ -40,7 +41,6 @@ from gi.repository import GLib from gi.repository import GObject from gi.repository import Gst from gi.repository import Gtk -from gi.repository import Pango from pitivi.configure import get_pixmap_dir from pitivi.configure import get_ui_dir @@ -53,75 +53,83 @@ from pitivi.utils.widgets import GstElementSettingsWidget (VIDEO_EFFECT, AUDIO_EFFECT) = list(range(1, 3)) -AUDIO_EFFECTS_CATEGORIES = () - ALLOWED_ONLY_ONCE_EFFECTS = ['videoflip'] -VIDEO_EFFECTS_CATEGORIES = ( +EFFECTS_CATEGORIES = ( (_("Colors"), ( # Mostly "serious" stuff that relates to correction/adjustments # Fancier stuff goes into the "fancy" category - "cogcolorspace", "videobalance", "chromahold", "gamma", - "coloreffects", "exclusion", "burn", "dodge", "videomedian", - "frei0r-filter-color-distance", "frei0r-filter-threshold0r", - "frei0r-filter-contrast0r", "frei0r-filter-saturat0r", - "frei0r-filter-white-balance", "frei0r-filter-brightness", - "frei0r-filter-gamma", "frei0r-filter-invert0r", - "frei0r-filter-hueshift0r", "frei0r-filter-equaliz0r", - "frei0r-filter-bw0r", "frei0r-filter-glow", - "frei0r-filter-twolay0r", "frei0r-filter-3-point-color-balance", - "frei0r-filter-coloradj-rgb", "frei0r-filter-curves", - "frei0r-filter-levels", "frei0r-filter-primaries", - "frei0r-filter-sop-sat", "frei0r-filter-threelay0r", - "frei0r-filter-tint0r", + 'burn', 'chromahold', 'cogcolorspace', 'coloreffects', 'dodge', + 'exclusion', 'frei0r-filter-3-point-color-balance', + 'frei0r-filter-brightness', 'frei0r-filter-bw0r', + 'frei0r-filter-color-distance', 'frei0r-filter-coloradj-rgb', + 'frei0r-filter-contrast0r', 'frei0r-filter-curves', + 'frei0r-filter-equaliz0r', 'frei0r-filter-gamma', 'frei0r-filter-glow', + 'frei0r-filter-hueshift0r', 'frei0r-filter-invert0r', + 'frei0r-filter-levels', 'frei0r-filter-primaries', + 'frei0r-filter-saturat0r', 'frei0r-filter-sop-sat', + 'frei0r-filter-threelay0r', 'frei0r-filter-threshold0r', + 'frei0r-filter-tint0r', 'frei0r-filter-twolay0r', + 'frei0r-filter-white-balance', 'gamma', 'videobalance', 'videomedian', )), (_("Compositing"), ( - "alpha", "alphacolor", "gdkpixbufoverlay", - "frei0r-filter-transparency", "frei0r-filter-mask0mate", - "frei0r-filter-alpha0ps", "frei0r-filter-alphagrad", - "frei0r-filter-alphaspot", "frei0r-filter-bluescreen0r", - "frei0r-filter-select0r", + 'alpha', 'alphacolor', 'frei0r-filter-alpha0ps', + 'frei0r-filter-alphagrad', 'frei0r-filter-alphaspot', + 'frei0r-filter-bluescreen0r', 'frei0r-filter-mask0mate', + 'frei0r-filter-select0r', 'frei0r-filter-transparency', + 'gdkpixbufoverlay', )), (_("Noise & blur"), ( - "gaussianblur", "diffuse", "dilate", "marble", "smooth", - "frei0r-filter-hqdn3d", "frei0r-filter-squareblur", - "frei0r-filter-sharpness", "frei0r-filter-edgeglow", - "frei0r-filter-facebl0r", + 'diffuse', 'dilate', 'frei0r-filter-edgeglow', 'frei0r-filter-facebl0r', + 'frei0r-filter-hqdn3d', 'frei0r-filter-sharpness', + 'frei0r-filter-squareblur', 'gaussianblur', 'marble', 'smooth', )), (_("Analysis"), ( - "videoanalyse", "videodetect", "videomark", "revtv", - "navigationtest", "frei0r-filter-rgb-parade", - "frei0r-filter-r", "frei0r-filter-g", "frei0r-filter-b", - "frei0r-filter-vectorscope", "frei0r-filter-luminance", - "frei0r-filter-opencvfacedetect", "frei0r-filter-pr0be", - "frei0r-filter-pr0file", + 'frei0r-filter-b', 'frei0r-filter-g', 'frei0r-filter-luminance', + 'frei0r-filter-opencvfacedetect', 'frei0r-filter-pr0be', + 'frei0r-filter-pr0file', 'frei0r-filter-r', 'frei0r-filter-rgb-parade', + 'frei0r-filter-vectorscope', 'navigationtest', 'revtv', 'videoanalyse', + 'videodetect', 'videomark', )), (_("Geometry"), ( - "cogscale", "aspectratiocrop", "cogdownsample", "videoscale", - "videocrop", "videoflip", "videobox", "gdkpixbufscale", - "kaleidoscope", "mirror", "pinch", "sphere", "square", "fisheye", - "stretch", "twirl", "waterriple", "rotate", "bulge", "circle", - "frei0r-filter-letterb0xed", "frei0r-filter-k-means-clustering", - "frei0r-filter-lens-correction", "frei0r-filter-defish0r", - "frei0r-filter-perspective", "frei0r-filter-c0rners", - "frei0r-filter-scale0tilt", "frei0r-filter-pixeliz0r", - "frei0r-filter-flippo", "frei0r-filter-3dflippo", + 'aspectratiocrop', 'bulge', 'circle', 'cogdownsample', 'cogscale', + 'fisheye', 'frei0r-filter-3dflippo', 'frei0r-filter-c0rners', + 'frei0r-filter-defish0r', 'frei0r-filter-flippo', + 'frei0r-filter-k-means-clustering', 'frei0r-filter-lens-correction', + 'frei0r-filter-letterb0xed', 'frei0r-filter-perspective', + 'frei0r-filter-pixeliz0r', 'frei0r-filter-scale0tilt', 'gdkpixbufscale', + 'kaleidoscope', 'mirror', 'pinch', 'rotate', 'sphere', 'square', + 'stretch', 'twirl', 'videobox', 'videocrop', 'videoflip', 'videoscale', + 'waterriple', )), (_("Fancy"), ( - "rippletv", "streaktv", "radioactv", "optv", "solarize", - "quarktv", "vertigotv", "shagadelictv", "warptv", "dicetv", - "agingtv", "edgetv", "bulge", "circle", "fisheye", "tunnel", - "kaleidoscope", "mirror", "pinch", "sphere", "square", - "stretch", "twirl", "waterripple", "glfiltersobel", "chromium", - "frei0r-filter-sobel", "frei0r-filter-cartoon", - "frei0r-filter-water", "frei0r-filter-nosync0r", - "frei0r-filter-k-means-clustering", "frei0r-filter-delay0r", - "frei0r-filter-distort0r", "frei0r-filter-light-graffiti", - "frei0r-filter-tehroxx0r", "frei0r-filter-vertigo", + 'agingtv', 'bulge', 'chromium', 'circle', 'dicetv', 'edgetv', 'fisheye', + 'frei0r-filter-cartoon', 'frei0r-filter-delay0r', + 'frei0r-filter-distort0r', 'frei0r-filter-k-means-clustering', + 'frei0r-filter-light-graffiti', 'frei0r-filter-nosync0r', + 'frei0r-filter-sobel', 'frei0r-filter-tehroxx0r', + 'frei0r-filter-vertigo', 'frei0r-filter-water', 'glfiltersobel', + 'kaleidoscope', 'mirror', 'optv', 'pinch', 'quarktv', 'radioactv', + 'rippletv', 'shagadelictv', 'solarize', 'sphere', 'square', 'streaktv', + 'stretch', 'tunnel', 'twirl', 'vertigotv', 'warptv', 'waterripple', )), (_("Time"), ( - "videorate", "frei0r-filter-delay0r", "frei0r-filter-baltan", - "frei0r-filter-nervous", + 'frei0r-filter-baltan', 'frei0r-filter-delay0r', + 'frei0r-filter-nervous', 'videorate', + )), + (_("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", + 'audioamplify', 'audiochebband', 'audiocheblimit', 'audiodynamic', + 'audioecho', 'audiofirfilter', 'audioiirfilter', 'audioinvert', + 'audiokaraoke', 'audiopanorama', 'audiorate', 'audiowsincband', + 'audiowsinclimit', 'equalizer-10bands', 'equalizer-3bands', + 'equalizer-nbands', 'festival', 'freeverb', 'pitch', 'removesilence', + 'rglimiter', 'rgvolume', 'scaletempo', 'speed', 'stereo', 'volume', )), ) @@ -147,14 +155,8 @@ HIDDEN_EFFECTS = [ GlobalSettings.add_config_section('effect-library') -(COL_NAME_TEXT, - COL_DESC_TEXT, - COL_EFFECT_TYPE, - COL_EFFECT_CATEGORIES, - COL_ELEMENT_NAME, - COL_ICON) = list(range(6)) - -ICON_WIDTH = 48 + 2 * 6 # 48 pixels, plus a margin on each side +ICON_WIDTH = 80 +ICON_HEIGHT = 45 class EffectInfo: @@ -182,8 +184,9 @@ class EffectInfo: os.path.join(pixdir, self.effect_name + ".png"), ICON_WIDTH, ICON_WIDTH) except GLib.Error: - icon = GdkPixbuf.Pixbuf.new_from_file( - os.path.join(pixdir, "defaultthumbnail.svg")) + icon = GdkPixbuf.Pixbuf.new_from_file_at_size( + os.path.join(pixdir, "defaultthumbnail.svg"), ICON_WIDTH, ICON_HEIGHT) + return icon @property @@ -323,32 +326,22 @@ class EffectsManager(Loggable): List[str]: The categories which contain the effect. """ categories = [] - for category_name, effects in AUDIO_EFFECTS_CATEGORIES: - if effect_name in effects: - categories.append(category_name) - for category_name, effects in VIDEO_EFFECTS_CATEGORIES: + for category_name, effects in EFFECTS_CATEGORIES: if effect_name in effects: categories.append(category_name) if not categories: categories.append(_("Uncategorized")) - categories.insert(0, _("All effects")) return categories @property - def video_categories(self): - """Gets all video effect categories names.""" - return EffectsManager._get_categories_names(VIDEO_EFFECTS_CATEGORIES) - - @property - def audio_categories(self): - """Gets all audio effect categories names.""" - return EffectsManager._get_categories_names(AUDIO_EFFECTS_CATEGORIES) + def categories(self): + """Gets the name of all effect categories.""" + return EffectsManager._get_categories_names(EFFECTS_CATEGORIES) @staticmethod def _get_categories_names(categories): ret = [category_name for category_name, unused_effects in categories] ret.sort() - ret.insert(0, _("All effects")) if categories: # Add Uncategorized only if there are other categories defined. ret.append(_("Uncategorized")) @@ -367,8 +360,9 @@ class EffectListWidget(Gtk.Box, Loggable): self.app = instance - self._dragged_items = None - self._effect_type = VIDEO_EFFECT + self._drag_icon = GdkPixbuf.Pixbuf.new_from_file_at_size( + os.path.join(get_pixmap_dir(), "effects", "defaultthumbnail.svg"), + ICON_WIDTH, ICON_HEIGHT) self.set_orientation(Gtk.Orientation.VERTICAL) builder = Gtk.Builder() @@ -376,64 +370,21 @@ class EffectListWidget(Gtk.Box, Loggable): builder.connect_signals(self) toolbar = builder.get_object("effectslibrary_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.categories_widget = builder.get_object("categories") self.search_entry = builder.get_object("search_entry") - # Store - self.storemodel = Gtk.ListStore( - str, str, int, object, str, GdkPixbuf.Pixbuf) - self.storemodel.set_sort_column_id( - COL_NAME_TEXT, Gtk.SortType.ASCENDING) - - # Create the filter for searching the storemodel. - self.model_filter = self.storemodel.filter_new() - self.model_filter.set_visible_func(self._set_row_visible_func, 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.view_description_cell_data_func, None) - - self.view.append_column(icon_col) - self.view.append_column(text_col) - - self.view.connect("query-tooltip", self._tree_view_query_tooltip_cb) - 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._button_press_event_cb) - self.view.connect("select-cursor-row", self._enter_press_event_cb) - self.view.connect("drag-data-get", self._dnd_drag_data_get_cb) + self.main_view = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) + self.category_view = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) + self.search_view = Gtk.ListBox(activate_on_single_click=False) + self.search_view.connect("row-activated", self.effects_listbox_row_activated_cb) + self.search_view.set_filter_func(self._search_filter) + + self.main_view.pack_start(self.category_view, True, True, 0) + self.main_view.pack_start(self.search_view, True, True, 0) scrollwin = Gtk.ScrolledWindow() scrollwin.props.hscrollbar_policy = Gtk.PolicyType.NEVER 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(scrollwin, True, True, 0) @@ -441,108 +392,116 @@ class EffectListWidget(Gtk.Box, Loggable): # Delay the loading of the available effects so the application # starts faster. GLib.idle_add(self._load_available_effects_cb) - self.populate_categories_widget() - # Individually show the tab's widgets. - # If you use self.show_all(), the tab will steal focus on startup. scrollwin.show_all() toolbar.show_all() + self.search_view.hide() - def _tree_view_query_tooltip_cb(self, view, x, y, keyboard_mode, tooltip): - is_row, x, y, model, path, tree_iter = view.get_tooltip_context( - x, y, keyboard_mode) - if not is_row: - return False + def _load_available_effects_cb(self): + self._set_up_category_view() + self._add_effects_to_listbox(self.search_view) - view.set_tooltip_row(tooltip, path) - tooltip.set_markup(self.format_description(model, tree_iter)) - return True + def _set_up_category_view(self): + # Add category expanders + for category in self.app.effects.categories: + widget = self._create_category_widget(category) + self.category_view.add(widget) - def view_description_cell_data_func(self, unused_column, cell, model, iter_, unused_data): - cell.props.markup = self.format_description(model, iter_) + # Add effects to category expanders + for expander in self.category_view.get_children(): + listbox = expander.get_child() + category_name = expander.get_label() - def format_description(self, model, iter_): - name, desc = model.get(iter_, COL_NAME_TEXT, COL_DESC_TEXT) - escape = GLib.markup_escape_text - return "%s\n%s" % (escape(name), escape(desc)) + self._add_effects_to_listbox(listbox, category_name) - def _load_available_effects_cb(self): - self._add_factories(self.app.effects.video_effects, VIDEO_EFFECT) - self._add_factories(self.app.effects.audio_effects, AUDIO_EFFECT) - return False + self.category_view.show_all() + + def _add_effects_to_listbox(self, listbox, category=None): + effects = self.app.effects.video_effects + self.app.effects.audio_effects + for effect in effects: + name = effect.get_name() - def _add_factories(self, elements, effect_type): - for element in elements: - name = element.get_name() if name in HIDDEN_EFFECTS: continue + effect_info = self.app.effects.get_info(name) - self.storemodel.append([effect_info.human_name, - effect_info.description, - effect_type, - effect_info.categories, - name, - effect_info.icon]) - - def populate_categories_widget(self): - self.categories_widget.get_model().clear() - icon_column = self.view.get_column(0) - - if self._effect_type is VIDEO_EFFECT: - for category in self.app.effects.video_categories: - self.categories_widget.append_text(category) - icon_column.props.visible = True - else: - for category in self.app.effects.audio_categories: - self.categories_widget.append_text(category) - icon_column.props.visible = False - self.categories_widget.set_active(0) + if not category or category in effect_info.categories: + widget = self._create_effect_widget(name) + listbox.add(widget) + + def _create_category_widget(self, category): + expander = Gtk.Expander(label=category, margin=SPACING) + + listbox = Gtk.ListBox(activate_on_single_click=False) + listbox.connect("row-activated", self.effects_listbox_row_activated_cb) + + expander.add(listbox) + + return expander - def _dnd_drag_data_get_cb(self, unused_view, drag_context, selection_data, unused_info, unused_timestamp): - data = bytes(self.get_selected_effect(), "UTF-8") + def _create_effect_widget(self, effect_name): + effect_info = self.app.effects.get_info(effect_name) + + effect_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, margin=SPACING / 2) + effect_box.effect_name = effect_name + effect_box.set_tooltip_text(effect_info.description) + label = Gtk.Label(effect_info.human_name, xalign=0) + icon = Gtk.Image.new_from_pixbuf(effect_info.icon) + + effect_box.pack_start(icon, False, True, SPACING / 2) + effect_box.pack_start(label, True, True, 0) + + # 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._drag_data_get_cb) + eventbox.connect("drag-begin", self._drag_begin_cb) + eventbox.add(effect_box) + + row = Gtk.ListBoxRow(selectable=False) + row.add(eventbox) + + return row + + def _drag_data_get_cb(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 _row_under_mouse_selected(self, view, event): - result = view.get_path_at_pos(int(event.x), int(event.y)) - if result: - path = result[0] - selection = view.get_selection() - return selection.path_is_selected(path) and\ - selection.count_selected_rows() > 0 - return False - - def _enter_press_event_cb(self, unused_view, unused_event=None): - self._add_selected_effect() - - def _button_press_event_cb(self, view, event): - chain_up = True - - if event.button == 3: - chain_up = False - elif event.type == getattr(Gdk.EventType, '2BUTTON_PRESS'): - self._add_selected_effect() - else: - chain_up = not self._row_under_mouse_selected(view, event) + def _drag_begin_cb(self, eventbox, context): + # Draw drag-icon + icon = self._drag_icon + icon_height = icon.get_height() + icon_width = icon.get_width() - if chain_up: - self._dragged_items = None - else: - self._dragged_items = self.get_selected_effect() + 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) - Gtk.TreeView.do_button_press_event(view, event) - return True + def effects_listbox_row_activated_cb(self, listbox, row): + """Handles the activation of a row representing an effect.""" + effect_box = row.get_child().get_child() + self.apply_effect(effect_box.effect_name) - def _add_selected_effect(self): + def apply_effect(self, effect_name): """Adds the selected effect to the single selected clip, if any.""" - effect = self.get_selected_effect() - effect_info = self.app.effects.get_info(effect) + effect_info = self.app.effects.get_info(effect_name) if not effect_info: return + timeline = self.app.gui.editor.timeline_ui.timeline clip = timeline.selection.get_single_clip() if not clip: return + pipeline = timeline.ges_timeline.get_parent() from pitivi.undo.timeline import CommitTimelineFinalizingAction with self.app.action_log.started("add effect", @@ -550,51 +509,27 @@ class EffectListWidget(Gtk.Box, Loggable): toplevel=True): clip.ui.add_effect(effect_info) - def get_selected_effect(self): - if self._dragged_items: - return self._dragged_items - unused_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 _search_filter(self, row): + effect_box = row.get_child().get_child() + label = effect_box.get_children()[1] - def _toggle_view_type_cb(self, widget): - """Switches the view mode between video and audio. + label_text = label.get_text().lower() + search_key = self.search_entry.get_text().lower() - This makes the two togglebuttons behave like a group of radiobuttons. - """ - 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()) + return search_key in label_text - if self.video_togglebutton.get_active(): - self._effect_type = VIDEO_EFFECT + def _search_entry_changed_cb(self, search_entry): + if search_entry.get_text(): + self.search_view.invalidate_filter() + self.search_view.show_all() + self.category_view.hide() else: - self._effect_type = AUDIO_EFFECT - self.populate_categories_widget() - self.model_filter.refilter() - - def _category_changed_cb(self, combobox): - self.model_filter.refilter() + self.category_view.show() + self.search_view.hide() - def _search_entry_changed_cb(self, unused_entry): - self.model_filter.refilter() - - def _search_entry_icon_press_cb(self, entry, icon_pos, event): + def _search_entry_icon_release_cb(self, entry, icon_pos, event): entry.set_text("") - def _set_row_visible_func(self, model, model_iter, data): - if not self._effect_type == model.get_value(model_iter, COL_EFFECT_TYPE): - return False - if model.get_value(model_iter, COL_EFFECT_CATEGORIES) is None: - return False - if self.categories_widget.get_active_text() not in model.get_value(model_iter, COL_EFFECT_CATEGORIES): - return False - text = self.search_entry.get_text().lower() - return text in model.get_value(model_iter, COL_DESC_TEXT).lower() or\ - text in model.get_value(model_iter, COL_NAME_TEXT).lower() - PROPS_TO_IGNORE = ['name', 'qos', 'silent', 'message', 'parent'] -- GitLab From 13f89a5188543372f01cd58e4348df1806b7eab8 Mon Sep 17 00:00:00 2001 From: yatinmaan Date: Tue, 2 Jul 2019 01:42:02 +0530 Subject: [PATCH 2/5] effects: Add Favourites feature --- data/pixmaps/star-regular.svg | 5 ++ data/pixmaps/star-solid.svg | 5 ++ data/ui/effectslibrary.ui | 9 ++- pitivi/effects.py | 111 ++++++++++++++++++++++++++++++++-- 4 files changed, 124 insertions(+), 6 deletions(-) create mode 100644 data/pixmaps/star-regular.svg create mode 100644 data/pixmaps/star-solid.svg diff --git a/data/pixmaps/star-regular.svg b/data/pixmaps/star-regular.svg new file mode 100644 index 000000000..8ea949761 --- /dev/null +++ b/data/pixmaps/star-regular.svg @@ -0,0 +1,5 @@ + + diff --git a/data/pixmaps/star-solid.svg b/data/pixmaps/star-solid.svg new file mode 100644 index 000000000..d45e927b5 --- /dev/null +++ b/data/pixmaps/star-solid.svg @@ -0,0 +1,5 @@ + + diff --git a/data/ui/effectslibrary.ui b/data/ui/effectslibrary.ui index e8f93ee8d..cad46a664 100644 --- a/data/ui/effectslibrary.ui +++ b/data/ui/effectslibrary.ui @@ -12,7 +12,14 @@ True False - + + Favourites + True + True + True + True + + diff --git a/pitivi/effects.py b/pitivi/effects.py index c58483568..d3657b7d8 100644 --- a/pitivi/effects.py +++ b/pitivi/effects.py @@ -154,6 +154,10 @@ HIDDEN_EFFECTS = [ "gdkpixbufoverlay"] GlobalSettings.add_config_section('effect-library') +GlobalSettings.add_config_option('favourite_effects', + section='effect-library', + key='favourite-effects', + default=[]) ICON_WIDTH = 80 ICON_HEIGHT = 45 @@ -363,6 +367,10 @@ class EffectListWidget(Gtk.Box, Loggable): self._drag_icon = GdkPixbuf.Pixbuf.new_from_file_at_size( os.path.join(get_pixmap_dir(), "effects", "defaultthumbnail.svg"), ICON_WIDTH, ICON_HEIGHT) + self._star_icon_regular = GdkPixbuf.Pixbuf.new_from_file_at_size( + os.path.join(get_pixmap_dir(), "star-regular.svg"), 15, 15) + self._star_icon_solid = GdkPixbuf.Pixbuf.new_from_file_at_size( + os.path.join(get_pixmap_dir(), "star-solid.svg"), 15, 15) self.set_orientation(Gtk.Orientation.VERTICAL) builder = Gtk.Builder() @@ -371,12 +379,20 @@ class EffectListWidget(Gtk.Box, Loggable): toolbar = builder.get_object("effectslibrary_toolbar") toolbar.get_style_context().add_class(Gtk.STYLE_CLASS_INLINE_TOOLBAR) self.search_entry = builder.get_object("search_entry") + self.fav_view_toggle = builder.get_object("favourites_toggle") + self.fav_view_toggle.set_image(Gtk.Image.new_from_pixbuf(self._star_icon_solid)) self.main_view = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) + self.category_view = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) + + # Used for showing search results and favourites self.search_view = Gtk.ListBox(activate_on_single_click=False) self.search_view.connect("row-activated", self.effects_listbox_row_activated_cb) - self.search_view.set_filter_func(self._search_filter) + + placeholder_text = Gtk.Label(_("No effects")) + placeholder_text.props.visible = True + self.search_view.set_placeholder(placeholder_text) self.main_view.pack_start(self.category_view, True, True, 0) self.main_view.pack_start(self.search_view, True, True, 0) @@ -402,6 +418,7 @@ class EffectListWidget(Gtk.Box, Loggable): self._add_effects_to_listbox(self.search_view) def _set_up_category_view(self): + """Adds expanders and effects to the category view.""" # Add category expanders for category in self.app.effects.categories: widget = self._create_category_widget(category) @@ -417,6 +434,7 @@ class EffectListWidget(Gtk.Box, Loggable): self.category_view.show_all() def _add_effects_to_listbox(self, listbox, category=None): + """Adds effect rows to the given listbox.""" effects = self.app.effects.video_effects + self.app.effects.audio_effects for effect in effects: name = effect.get_name() @@ -431,6 +449,7 @@ class EffectListWidget(Gtk.Box, Loggable): listbox.add(widget) def _create_category_widget(self, category): + """Creates an expander for the given category.""" expander = Gtk.Expander(label=category, margin=SPACING) listbox = Gtk.ListBox(activate_on_single_click=False) @@ -441,6 +460,7 @@ class EffectListWidget(Gtk.Box, Loggable): return expander def _create_effect_widget(self, effect_name): + """Creates list box row for the given effect.""" effect_info = self.app.effects.get_info(effect_name) effect_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, margin=SPACING / 2) @@ -449,8 +469,20 @@ class EffectListWidget(Gtk.Box, Loggable): label = Gtk.Label(effect_info.human_name, xalign=0) icon = Gtk.Image.new_from_pixbuf(effect_info.icon) + # Set up favourite button + fav_button = Gtk.Button() + fav_button.props.relief = Gtk.ReliefStyle.NONE + fav_button.props.halign = Gtk.Align.CENTER + fav_button.props.valign = Gtk.Align.CENTER + fav_button.set_tooltip_text(_("Add to Favourites")) + + starred = effect_name in self.app.settings.favourite_effects + self._set_fav_button_state(fav_button, starred) + fav_button.connect("clicked", self._fav_button_clicked_cb, effect_box.effect_name) + effect_box.pack_start(icon, False, True, SPACING / 2) effect_box.pack_start(label, True, True, 0) + effect_box.pack_end(fav_button, False, True, SPACING / 2) # Set up drag behavoir eventbox = Gtk.EventBox(visible_window=False) @@ -461,6 +493,7 @@ class EffectListWidget(Gtk.Box, Loggable): row = Gtk.ListBoxRow(selectable=False) row.add(eventbox) + row.show_all() return row @@ -509,7 +542,65 @@ class EffectListWidget(Gtk.Box, Loggable): toplevel=True): clip.ui.add_effect(effect_info) + def _set_fav_button_state(self, button, is_active): + """Manages the state of the favourite button.""" + button.active = is_active + + if button.active: + image = Gtk.Image.new_from_pixbuf(self._star_icon_solid) + else: + image = Gtk.Image.new_from_pixbuf(self._star_icon_regular) + + button.props.image = image + + def _fav_button_clicked_cb(self, clicked_button, effect): + """Adds effect to favourites and syncs the state of favourite button.""" + # Toggle the state of clicked button + self._set_fav_button_state(clicked_button, not clicked_button.active) + + # Get all listboxes which contain the effect + effect_info = self.app.effects.get_info(effect) + all_effect_listboxes = [category_expander.get_child() + for category_expander in self.category_view.get_children() + if category_expander.get_label() in effect_info.categories] + all_effect_listboxes.append(self.search_view) + + # Find and sync state in other listboxes + for listbox in all_effect_listboxes: + for row in listbox.get_children(): + effect_box = row.get_child().get_child() + if effect == effect_box.effect_name: + fav_button = effect_box.get_children()[2] + # Sync the state with the clicked button + self._set_fav_button_state(fav_button, clicked_button.active) + + # Update the favourites list + if clicked_button.active: + self.app.settings.favourite_effects.append(effect) + else: + self.app.settings.favourite_effects = \ + [fav for fav in self.app.settings.favourite_effects if fav != effect] + self.search_view.invalidate_filter() + + def _favourites_filter(self, row): + """Filters search_view to show favourites.""" + effect_box = row.get_child().get_child() + effect_name = effect_box.effect_name + + return effect_name in self.app.settings.favourite_effects + + def _favourites_toggle_cb(self, toggle): + """Manages the visiblity and filtering of Favourites in search_view.""" + if toggle.get_active(): + self.search_entry.set_text("") + self.search_view.set_filter_func(self._favourites_filter) + self.search_view.invalidate_filter() + self._switch_to_view(self.search_view) + else: + self._switch_to_view(self.category_view) + def _search_filter(self, row): + """Filters search_view to show search results.""" effect_box = row.get_child().get_child() label = effect_box.get_children()[1] @@ -519,17 +610,27 @@ class EffectListWidget(Gtk.Box, Loggable): return search_key in label_text def _search_entry_changed_cb(self, search_entry): + """Manages the visiblity and filtering search results in search_view.""" if search_entry.get_text(): + self.fav_view_toggle.props.active = False + self.search_view.set_filter_func(self._search_filter) self.search_view.invalidate_filter() - self.search_view.show_all() - self.category_view.hide() + self._switch_to_view(self.search_view) else: - self.category_view.show() - self.search_view.hide() + self._switch_to_view(self.category_view) def _search_entry_icon_release_cb(self, entry, icon_pos, event): entry.set_text("") + def _switch_to_view(self, next_view): + """Shows next_view and hides all other views.""" + if next_view.props.visible: + # It's already visible, no need to switch to it. + return + + for child_view in self.main_view.get_children(): + child_view.props.visible = child_view == next_view + PROPS_TO_IGNORE = ['name', 'qos', 'silent', 'message', 'parent'] -- GitLab From 47744d7333af0ef5038864b1564b6c8f222dec05 Mon Sep 17 00:00:00 2001 From: yatinmaan Date: Wed, 17 Jul 2019 22:36:38 +0530 Subject: [PATCH 3/5] effects: Add Effect Popover and Shortcut --- pitivi/clipproperties.py | 14 ++++++ pitivi/effects.py | 94 ++++++++++++++++++++++++++++++------- pitivi/timeline/timeline.py | 15 ++++++ 3 files changed, 106 insertions(+), 17 deletions(-) diff --git a/pitivi/clipproperties.py b/pitivi/clipproperties.py index 3c65ce1ae..97870c1a2 100644 --- a/pitivi/clipproperties.py +++ b/pitivi/clipproperties.py @@ -31,6 +31,7 @@ from pitivi.clip_properties.alignment import AlignmentEditor from pitivi.clip_properties.color import ColorProperties from pitivi.clip_properties.title import TitleProperties from pitivi.configure import get_ui_dir +from pitivi.effects import EffectsPopover from pitivi.effects import EffectsPropertiesManager from pitivi.effects import HIDDEN_EFFECTS from pitivi.undo.timeline import CommitTimelineFinalizingAction @@ -321,6 +322,12 @@ class EffectProperties(Gtk.Expander, Loggable): self.treeview_selection = self.treeview.get_selection() self.treeview_selection.set_mode(Gtk.SelectionMode.SINGLE) + # 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 + # Prepare the main container widgets and lay out everything self._expander_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) self._vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) @@ -334,6 +341,7 @@ class EffectProperties(Gtk.Expander, Loggable): self._vbox.show_all() self._expander_box.pack_start(self.no_effect_infobar, expand=False, fill=False, padding=0) self._expander_box.pack_start(self._vbox, expand=False, fill=False, padding=0) + self._expander_box.pack_end(self.add_effect_button, False, False, PADDING) self._expander_box.show_all() self.add(self._expander_box) self.hide() @@ -368,6 +376,12 @@ class EffectProperties(Gtk.Expander, Loggable): self.app.project_manager.connect_after( "new-project-loaded", self._new_project_loaded_cb) self.connect('notify::expanded', self._expanded_cb) + self.add_effect_button.connect("toggled", self.add_effect_button_cb) + + 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 _new_project_loaded_cb(self, unused_app, project): if self._selection is not None: diff --git a/pitivi/effects.py b/pitivi/effects.py index d3657b7d8..e70e1a944 100644 --- a/pitivi/effects.py +++ b/pitivi/effects.py @@ -47,6 +47,7 @@ from pitivi.configure import get_ui_dir from pitivi.settings import GlobalSettings from pitivi.utils.loggable import Loggable from pitivi.utils.ui import EFFECT_TARGET_ENTRY +from pitivi.utils.ui import PADDING from pitivi.utils.ui import SPACING from pitivi.utils.widgets import FractionWidget from pitivi.utils.widgets import GstElementSettingsWidget @@ -415,7 +416,7 @@ class EffectListWidget(Gtk.Box, Loggable): def _load_available_effects_cb(self): self._set_up_category_view() - self._add_effects_to_listbox(self.search_view) + self.add_effects_to_listbox(self.search_view) def _set_up_category_view(self): """Adds expanders and effects to the category view.""" @@ -429,11 +430,11 @@ class EffectListWidget(Gtk.Box, Loggable): listbox = expander.get_child() category_name = expander.get_label() - self._add_effects_to_listbox(listbox, category_name) + self.add_effects_to_listbox(listbox, category_name) self.category_view.show_all() - def _add_effects_to_listbox(self, listbox, category=None): + def add_effects_to_listbox(self, listbox, category=None, only_text=False): """Adds effect rows to the given listbox.""" effects = self.app.effects.video_effects + self.app.effects.audio_effects for effect in effects: @@ -445,7 +446,7 @@ class EffectListWidget(Gtk.Box, Loggable): effect_info = self.app.effects.get_info(name) if not category or category in effect_info.categories: - widget = self._create_effect_widget(name) + widget = self._create_effect_widget(name, only_text) listbox.add(widget) def _create_category_widget(self, category): @@ -459,7 +460,7 @@ class EffectListWidget(Gtk.Box, Loggable): return expander - def _create_effect_widget(self, effect_name): + def _create_effect_widget(self, effect_name, only_text): """Creates list box row for the given effect.""" effect_info = self.app.effects.get_info(effect_name) @@ -467,22 +468,25 @@ class EffectListWidget(Gtk.Box, Loggable): effect_box.effect_name = effect_name effect_box.set_tooltip_text(effect_info.description) label = Gtk.Label(effect_info.human_name, xalign=0) - icon = Gtk.Image.new_from_pixbuf(effect_info.icon) - # Set up favourite button - fav_button = Gtk.Button() - fav_button.props.relief = Gtk.ReliefStyle.NONE - fav_button.props.halign = Gtk.Align.CENTER - fav_button.props.valign = Gtk.Align.CENTER - fav_button.set_tooltip_text(_("Add to Favourites")) + if not only_text: + # Show effect thumbnail + icon = Gtk.Image.new_from_pixbuf(effect_info.icon) + effect_box.pack_start(icon, False, True, SPACING / 2) - starred = effect_name in self.app.settings.favourite_effects - self._set_fav_button_state(fav_button, starred) - fav_button.connect("clicked", self._fav_button_clicked_cb, effect_box.effect_name) + # Set up favourite button + fav_button = Gtk.Button() + fav_button.props.relief = Gtk.ReliefStyle.NONE + fav_button.props.halign = Gtk.Align.CENTER + fav_button.props.valign = Gtk.Align.CENTER + fav_button.set_tooltip_text(_("Add to Favourites")) + + starred = effect_name in self.app.settings.favourite_effects + self._set_fav_button_state(fav_button, starred) + fav_button.connect("clicked", self._fav_button_clicked_cb, effect_box.effect_name) + effect_box.pack_end(fav_button, False, True, SPACING / 2) - effect_box.pack_start(icon, False, True, SPACING / 2) effect_box.pack_start(label, True, True, 0) - effect_box.pack_end(fav_button, False, True, SPACING / 2) # Set up drag behavoir eventbox = Gtk.EventBox(visible_window=False) @@ -632,6 +636,62 @@ class EffectListWidget(Gtk.Box, Loggable): child_view.props.visible = child_view == next_view +class EffectsPopover(Gtk.Popover, Loggable): + """Popover for adding effects.""" + + def __init__(self, app): + Gtk.Popover.__init__(self) + Loggable.__init__(self) + + self.app = app + + vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, margin=PADDING) + + self.search_entry = Gtk.SearchEntry() + self.search_entry.connect("search-changed", self._search_entry_cb) + + scroll_window = Gtk.ScrolledWindow() + scroll_window.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC) + scroll_window.props.max_content_height = 350 + scroll_window.props.propagate_natural_height = True + + self.listbox = Gtk.ListBox() + self.listbox.connect("row-activated", self._effect_row_activate_cb) + self.listbox.set_filter_func(self._search_filter) + placeholder_text = Gtk.Label(_("No effects")) + placeholder_text.props.visible = True + self.listbox.set_placeholder(placeholder_text) + + self.app.gui.editor.effectlist.add_effects_to_listbox(self.listbox, only_text=True) + scroll_window.add(self.listbox) + + vbox.pack_start(self.search_entry, False, False, 0) + vbox.pack_end(scroll_window, True, True, 0) + vbox.show_all() + + self.add(vbox) + + def _effect_row_activate_cb(self, listbox, row): + effect_box = row.get_child().get_child() + self.app.gui.editor.effectlist.apply_effect(effect_box.effect_name) + self.hide() + + def _search_entry_cb(self, search_entry): + self.listbox.invalidate_filter() + + def _search_filter(self, row): + effect_box = row.get_child().get_child() + label = effect_box.get_children()[0] + + label_text = label.get_text().lower() + search_key = self.search_entry.get_text().lower() + + return search_key in label_text + + def popup(self): + self.search_entry.set_text("") + Gtk.Popover.popup(self) + PROPS_TO_IGNORE = ['name', 'qos', 'silent', 'message', 'parent'] diff --git a/pitivi/timeline/timeline.py b/pitivi/timeline/timeline.py index 3e2f5d559..c261d8036 100644 --- a/pitivi/timeline/timeline.py +++ b/pitivi/timeline/timeline.py @@ -30,6 +30,7 @@ from pitivi.autoaligner import AutoAligner from pitivi.configure import get_ui_dir from pitivi.configure import in_devel from pitivi.dialogs.prefs import PreferencesDialog +from pitivi.effects import EffectsPopover from pitivi.settings import GlobalSettings from pitivi.timeline.elements import Clip from pitivi.timeline.elements import TransitionClip @@ -1619,6 +1620,7 @@ class TimelineContainer(Gtk.Grid, Zoomable, Loggable): left_size_group.add_widget(self.zoom_box) self.timeline = Timeline(self.app, left_size_group, self.editor_state) + self.effects_popover = EffectsPopover(self.app) # Vertical Scrollbar. It will be displayed only when needed. self.vscrollbar = Gtk.Scrollbar(orientation=Gtk.Orientation.VERTICAL, @@ -1758,6 +1760,13 @@ class TimelineContainer(Gtk.Grid, Zoomable, Loggable): self.seek_backward_clip_action, _("Seeks to the first clip edge before the playhead.")) + self.add_effect_action = Gio.SimpleAction.new("add-effect", None) + self.add_effect_action.connect("activate", self.__add_effect_cb) + group.add_action(self.add_effect_action) + self.app.shortcuts.add("timeline.add-effect", ["e"], + self.add_effect_action, + _("Add an effect to the selected clip")) + if in_devel(): self.gapless_action = Gio.SimpleAction.new("toggle-gapless-mode", None) self.gapless_action.connect("activate", self._gaplessmode_toggled_cb) @@ -2038,6 +2047,12 @@ class TimelineContainer(Gtk.Grid, Zoomable, Loggable): self._project.pipeline.simple_seek(position) self.timeline.scroll_to_playhead(align=Gtk.Align.CENTER, when_not_in_view=True) + def __add_effect_cb(self, unused_action, unused_parameter): + clip = self.timeline.selection.getSingleClip() + if clip: + self.effects_popover.set_relative_to(clip.ui) + self.effects_popover.popup() + def _align_selected_cb(self, unused_action, unused_parameter): if not self.ges_timeline: return -- GitLab From 73bc4cfb8adba68d3ff030ae3aa5f4b0a2be08c0 Mon Sep 17 00:00:00 2001 From: yatinmaan Date: Wed, 31 Jul 2019 18:52:36 +0530 Subject: [PATCH 4/5] clipproperties: Add drag indicator --- data/pixmaps/grip-lines-solid.svg | 66 +++++++++++++++++++++++++++++++ pitivi/clipproperties.py | 10 +++++ 2 files changed, 76 insertions(+) create mode 100644 data/pixmaps/grip-lines-solid.svg diff --git a/data/pixmaps/grip-lines-solid.svg b/data/pixmaps/grip-lines-solid.svg new file mode 100644 index 000000000..baca56eb4 --- /dev/null +++ b/data/pixmaps/grip-lines-solid.svg @@ -0,0 +1,66 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + diff --git a/pitivi/clipproperties.py b/pitivi/clipproperties.py index 97870c1a2..27104e3e3 100644 --- a/pitivi/clipproperties.py +++ b/pitivi/clipproperties.py @@ -20,6 +20,7 @@ import os from gettext import gettext as _ from gi.repository import Gdk +from gi.repository import GdkPixbuf from gi.repository import GES from gi.repository import Gio from gi.repository import Gst @@ -30,6 +31,7 @@ from gi.repository import Pango from pitivi.clip_properties.alignment import AlignmentEditor from pitivi.clip_properties.color import ColorProperties from pitivi.clip_properties.title import TitleProperties +from pitivi.configure import get_pixmap_dir from pitivi.configure import get_ui_dir from pitivi.effects import EffectsPopover from pitivi.effects import EffectsPropertiesManager @@ -278,6 +280,14 @@ class EffectProperties(Gtk.Expander, Loggable): # I should file a bug about this. self.treeview.props.margin_right = 1 + drag_icon_cell = Gtk.CellRendererPixbuf() + drag_icon = GdkPixbuf.Pixbuf.new_from_file_at_size( + os.path.join(get_pixmap_dir(), "grip-lines-solid.svg"), + 10, 5) + drag_icon_cell.props.pixbuf = drag_icon + self.treeview.insert_column_with_attributes(-1, + _("Drag"), drag_icon_cell) + activated_cell = Gtk.CellRendererToggle() activated_cell.props.xalign = 0 activated_cell.props.xpad = 0 -- GitLab From 6fa98c5e7b87ab884c83e99bffc3175ffd5cc84e Mon Sep 17 00:00:00 2001 From: yatinmaan Date: Mon, 19 Aug 2019 02:48:37 +0530 Subject: [PATCH 5/5] clipproperties: Refactor UI --- data/pixmaps/grip-lines-solid.svg | 63 +--- pitivi/clipproperties.py | 588 +++++++++++------------------- pitivi/effects.py | 28 +- tests/test_clipproperties.py | 42 --- 4 files changed, 235 insertions(+), 486 deletions(-) diff --git a/data/pixmaps/grip-lines-solid.svg b/data/pixmaps/grip-lines-solid.svg index baca56eb4..7d8101c63 100644 --- a/data/pixmaps/grip-lines-solid.svg +++ b/data/pixmaps/grip-lines-solid.svg @@ -1,65 +1,4 @@ - - - - - - - - - - image/svg+xml - - - - - - - - - +