1
# -*- coding: utf-8 -*-
4
# pitivi/transitions.py
6
# Copyright (c) 2012, Jean-François Fortin Tam <nekohayo@gmail.com>
8
# This program is free software; you can redistribute it and/or
9
# modify it under the terms of the GNU Lesser General Public
10
# License as published by the Free Software Foundation; either
11
# version 2.1 of the License, or (at your option) any later version.
13
# This program is distributed in the hope that it will be useful,
14
# but WITHOUT ANY WARRANTY; without even the implied warranty of
15
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16
# Lesser General Public License for more details.
18
# You should have received a copy of the GNU Lesser General Public
19
# License along with this program; if not, write to the
20
# Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
21
# Boston, MA 02110-1301, USA.
25
from gi.repository import GLib
26
from gi.repository import GES
27
from gi.repository import Gtk
28
from gi.repository import GdkPixbuf
30
from gettext import gettext as _
32
from pitivi.configure import get_pixmap_dir
33
from pitivi.utils.loggable import Loggable
34
from pitivi.utils.signal import Signallable
35
from pitivi.utils.ui import SPACING, PADDING
37
(COL_TRANSITION_ASSET,
43
class TransitionsListWidget(Signallable, Gtk.VBox, Loggable):
45
Widget for configuring the selected transition.
48
def __init__(self, instance, unused_uiman):
49
Gtk.VBox.__init__(self)
50
Loggable.__init__(self)
51
Signallable.__init__(self)
55
self._pixdir = os.path.join(get_pixmap_dir(), "transitions")
56
icon_theme = Gtk.IconTheme.get_default()
57
self._question_icon = icon_theme.load_icon("dialog-question", 48, 0)
60
self._current_transition_name = None
61
self._current_tooltip_icon = None
64
self.searchbar = Gtk.HBox()
65
self.searchbar.set_border_width(3) # Prevents being flush against the notebook
66
self.searchEntry = Gtk.Entry()
67
self.searchEntry.set_icon_from_icon_name(Gtk.EntryIconPosition.SECONDARY, "edit-clear-symbolic")
68
self.searchEntry.set_placeholder_text(_("Search..."))
69
self.searchbar.pack_end(self.searchEntry, True, True, 0)
71
self.props_widgets = Gtk.VBox()
72
borderTable = Gtk.Table(n_rows=2, n_columns=3)
74
self.border_mode_normal = Gtk.RadioButton(group=None, label=_("Normal"))
75
self.border_mode_loop = Gtk.RadioButton(group=self.border_mode_normal, label=_("Loop"))
76
self.border_mode_normal.set_active(True)
77
self.borderScale = Gtk.Scale.new(Gtk.Orientation.HORIZONTAL, None)
78
self.borderScale.set_draw_value(False)
80
borderTable.attach(self.border_mode_normal, 0, 1, 0, 1, xoptions=Gtk.AttachOptions.FILL, yoptions=Gtk.AttachOptions.FILL)
81
borderTable.attach(self.border_mode_loop, 1, 2, 0, 1, xoptions=Gtk.AttachOptions.FILL, yoptions=Gtk.AttachOptions.FILL)
82
# The ypadding is a hack to make the slider widget align with the radiobuttons.
83
borderTable.attach(self.borderScale, 2, 3, 0, 2, ypadding=SPACING * 2)
85
self.invert_checkbox = Gtk.CheckButton(label=_("Reverse direction"))
86
self.invert_checkbox.set_border_width(SPACING)
88
self.props_widgets.add(borderTable)
89
self.props_widgets.add(self.invert_checkbox)
91
# Set the default values
92
self._borderTypeChangedCb()
94
self.infobar = Gtk.InfoBar()
95
txtlabel = Gtk.Label()
96
txtlabel.set_padding(PADDING, PADDING)
97
txtlabel.set_line_wrap(True)
99
_("Create a transition by overlapping two adjacent clips on the "
100
"same layer. Click the transition on the timeline to change "
101
"the transition type."))
102
self.infobar.add(txtlabel)
104
self.storemodel = Gtk.ListStore(GES.Asset, str, str, GdkPixbuf.Pixbuf)
106
self.iconview_scrollwin = Gtk.ScrolledWindow()
107
self.iconview_scrollwin.set_shadow_type(Gtk.ShadowType.ETCHED_IN)
108
# FIXME: the "never" horizontal scroll policy in GTK2 messes up iconview
109
# Re-enable this when we switch to GTK3
110
# See also http://python.6.n6.nabble.com/Cannot-shrink-width-of-scrolled-textview-tp1945060.html
111
#self.iconview_scrollwin.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC)
113
self.iconview = Gtk.IconView(model=self.storemodel)
114
self.iconview.set_pixbuf_column(COL_ICON)
115
# We don't show text because we have a searchbar and the names are ugly
116
#self.iconview.set_text_column(COL_NAME_TEXT)
117
self.iconview.set_item_width(48 + 10)
118
self.iconview_scrollwin.add(self.iconview)
119
self.iconview.set_property("has_tooltip", True)
121
self.searchEntry.connect("changed", self._searchEntryChangedCb)
122
self.searchEntry.connect("icon-press", self._searchEntryIconClickedCb)
123
self.iconview.connect("selection-changed", self._transitionSelectedCb)
124
self.iconview.connect("query-tooltip", self._queryTooltipCb)
125
self.borderScale.connect("value-changed", self._borderScaleCb)
126
self.invert_checkbox.connect("toggled", self._invertCheckboxCb)
127
self.border_mode_normal.connect("released", self._borderTypeChangedCb)
128
self.border_mode_loop.connect("released", self._borderTypeChangedCb)
130
# Speed-up startup by only checking available transitions on idle
131
GLib.idle_add(self._loadAvailableTransitionsCb)
133
self.pack_start(self.infobar, False, True, 0)
134
self.pack_start(self.searchbar, False, True, 0)
135
self.pack_start(self.iconview_scrollwin, True, True, 0)
136
self.pack_start(self.props_widgets, False, True, 0)
138
# Create the filterModel for searching
139
self.modelFilter = self.storemodel.filter_new()
140
self.iconview.set_model(self.modelFilter)
142
self.infobar.show_all()
143
self.iconview_scrollwin.show_all()
145
self.props_widgets.set_sensitive(False)
146
self.props_widgets.hide()
147
self.searchbar.hide()
151
def _transitionSelectedCb(self, unused_event):
152
transition_asset = self.getSelectedItem()
153
if not transition_asset:
154
# The user clicked between icons
157
self.debug("New transition type selected: %s", transition_asset.get_id())
158
if transition_asset.get_id() == "crossfade":
159
self.props_widgets.set_sensitive(False)
161
self.props_widgets.set_sensitive(True)
163
self.element.get_parent().set_asset(transition_asset)
164
self.app.current_project.seeker.flush(True)
168
def _borderScaleCb(self, range_changed):
169
value = range_changed.get_value()
170
self.debug("User changed the border property to %s", value)
171
self.element.set_border(int(value))
172
self.app.current_project.seeker.flush(True)
174
def _invertCheckboxCb(self, widget):
175
value = widget.get_active()
176
self.debug("User changed the invert property to %s", value)
177
self.element.set_inverted(value)
178
self.app.current_project.seeker.flush()
180
def _borderTypeChangedCb(self, widget=None):
182
The "border" property in gstreamer is unlimited, but if you go over
183
25 thousand it "loops" the transition instead of smoothing it.
185
if widget == self.border_mode_loop:
186
self.borderScale.set_range(50000, 500000)
187
self.borderScale.clear_marks()
188
self.borderScale.add_mark(50000, Gtk.PositionType.BOTTOM, _("Slow"))
189
self.borderScale.add_mark(200000, Gtk.PositionType.BOTTOM, _("Fast"))
190
self.borderScale.add_mark(500000, Gtk.PositionType.BOTTOM, _("Epileptic"))
192
self.borderScale.set_range(0, 25000)
193
self.borderScale.clear_marks()
194
self.borderScale.add_mark(0, Gtk.PositionType.BOTTOM, _("Sharp"))
195
self.borderScale.add_mark(25000, Gtk.PositionType.BOTTOM, _("Smooth"))
197
def _searchEntryChangedCb(self, unused_entry):
198
self.modelFilter.refilter()
200
def _searchEntryIconClickedCb(self, entry, unused, unused_1):
205
def _transitionTypeChangedCb(self, element, unused_prop):
207
self.iconview.disconnect_by_func(self._transitionSelectedCb)
211
self.selectTransition(element.get_asset())
212
self.iconview.connect("button-release-event", self._transitionSelectedCb)
214
def _borderChangedCb(self, element, unused_prop):
216
The "border" transition property changed in the backend. Update the UI.
218
value = element.get_border()
220
self.borderScale.disconnect_by_func(self._borderScaleCb)
224
self.borderScale.set_value(float(value))
225
self.borderScale.connect("value-changed", self._borderScaleCb)
227
def _invertChangedCb(self, element, unused_prop):
229
The "invert" transition property changed in the backend. Update the UI.
231
value = element.is_inverted()
233
self.invert_checkbox.disconnect_by_func(self._invertCheckboxCb)
237
self.invert_checkbox.set_active(value)
238
self.invert_checkbox.connect("toggled", self._invertCheckboxCb)
242
def _loadAvailableTransitionsCb(self):
244
Get the list of transitions from GES and load the associated thumbnails.
246
for trans_asset in GES.list_assets(GES.BaseTransitionClip):
247
trans_asset.icon = self._getIcon(trans_asset.get_id())
248
self.storemodel.append([trans_asset,
249
str(trans_asset.get_id()),
250
str(trans_asset.get_meta(GES.META_DESCRIPTION)),
253
# Now that the UI is fully ready, enable searching
254
self.modelFilter.set_visible_func(self._setRowVisible, data=None)
255
# Alphabetical/name sorting instead of based on the ID number
256
self.storemodel.set_sort_column_id(COL_NAME_TEXT, Gtk.SortType.ASCENDING)
258
def activate(self, element):
260
Hide the infobar and show the transitions UI.
262
self.element = element
263
self.element.connect("notify::border", self._borderChangedCb)
264
self.element.connect("notify::invert", self._invertChangedCb)
265
self.element.connect("notify::type", self._transitionTypeChangedCb)
266
transition_asset = element.get_parent().get_asset()
267
if transition_asset.get_id() == "crossfade":
268
self.props_widgets.set_sensitive(False)
270
self.props_widgets.set_sensitive(True)
271
self.iconview.show_all()
272
self.props_widgets.show_all()
273
self.searchbar.show_all()
274
self.selectTransition(transition_asset)
275
self.app.gui.switchContextTab(element)
276
# We REALLY want the infobar to be hidden as space is really constrained
277
# and yet GTK 3.10 seems to be racy in showing/hiding infobars, so
278
# this must happen *after* the tab has been made visible/switched to:
281
def selectTransition(self, transition_asset):
283
For a given transition type, select it in the iconview if available.
285
model = self.iconview.get_model()
287
if transition_asset == row[COL_TRANSITION_ASSET]:
288
path = model.get_path(row.iter)
289
self.iconview.select_path(path)
290
self.iconview.scroll_to_path(path, False, 0, 0)
292
def deactivate(self):
294
Show the infobar and hide the transitions UI.
297
self.element.disconnect_by_func(self._borderChangedCb)
298
self.element.disconnect_by_func(self._invertChangedCb)
299
self.element.disconnect_by_func(self._transitionTypeChangedCb)
302
except AttributeError:
303
# This happens when selecting a normal track object before any
304
# transition object has been created. Normal track objects don't
305
# have these signals, so we just ignore them. Anyway, we just want
306
# to deactivate the UI now.
308
self.iconview.unselect_all()
310
self.props_widgets.hide()
311
self.searchbar.hide()
314
def _getIcon(self, transition_nick):
316
If available, return an icon pixbuf for a given transition nickname.
318
name = transition_nick + ".png"
321
icon = GdkPixbuf.Pixbuf.new_from_file(os.path.join(self._pixdir, name))
323
icon = self._question_icon
326
def _queryTooltipCb(self, view, x, y, keyboard_mode, tooltip):
327
is_row, x, y, model, path, iter_ = view.get_tooltip_context(x, y, keyboard_mode)
331
view.set_tooltip_item(tooltip, path)
333
name = model.get_value(iter_, COL_TRANSITION_ASSET).get_id()
334
if self._current_transition_name != name:
335
self._current_transition_name = name
336
icon = model.get_value(iter_, COL_ICON)
337
self._current_tooltip_icon = icon
339
longname = model.get_value(iter_, COL_NAME_TEXT).strip()
340
description = model.get_value(iter_, COL_DESC_TEXT)
341
txt = "<b>%s:</b>\n%s" % (GLib.markup_escape_text(longname),
342
GLib.markup_escape_text(description),)
343
tooltip.set_markup(txt)
346
def getSelectedItem(self):
347
path = self.iconview.get_selected_items()
350
return self.modelFilter[path[0]][COL_TRANSITION_ASSET]
352
def _setRowVisible(self, model, iter, unused_data):
354
Filters the icon view depending on the search results
356
text = self.searchEntry.get_text().lower()
357
return text in model.get_value(iter, COL_DESC_TEXT).lower() or\
358
text in model.get_value(iter, COL_NAME_TEXT).lower()