~timo-jyrinki/ubuntu/trusty/pitivi/backport_utopic_fixes

« back to all changes in this revision

Viewing changes to pitivi/transitions.py

  • Committer: Package Import Robot
  • Author(s): Sebastian Dröge
  • Date: 2014-04-05 15:28:16 UTC
  • mfrom: (6.1.13 sid)
  • Revision ID: package-import@ubuntu.com-20140405152816-6lijoax4cngiz5j5
Tags: 0.93-3
* debian/control:
  + Depend on python-gi (>= 3.10), older versions do not work
    with pitivi (Closes: #732813).
  + Add missing dependency on gir1.2-clutter-gst-2.0 (Closes: #743692).
  + Add suggests on gir1.2-notify-0.7 and gir1.2-gnomedesktop-3.0.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# -*- coding: utf-8 -*-
 
2
# Pitivi video editor
 
3
#
 
4
#       pitivi/transitions.py
 
5
#
 
6
# Copyright (c) 2012, Jean-François Fortin Tam <nekohayo@gmail.com>
 
7
#
 
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.
 
12
#
 
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.
 
17
#
 
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.
 
22
 
 
23
import os
 
24
 
 
25
from gi.repository import GLib
 
26
from gi.repository import GES
 
27
from gi.repository import Gtk
 
28
from gi.repository import GdkPixbuf
 
29
 
 
30
from gettext import gettext as _
 
31
 
 
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
 
36
 
 
37
(COL_TRANSITION_ASSET,
 
38
 COL_NAME_TEXT,
 
39
 COL_DESC_TEXT,
 
40
 COL_ICON) = range(4)
 
41
 
 
42
 
 
43
class TransitionsListWidget(Signallable, Gtk.VBox, Loggable):
 
44
    """
 
45
    Widget for configuring the selected transition.
 
46
    """
 
47
 
 
48
    def __init__(self, instance, unused_uiman):
 
49
        Gtk.VBox.__init__(self)
 
50
        Loggable.__init__(self)
 
51
        Signallable.__init__(self)
 
52
 
 
53
        self.app = instance
 
54
        self.element = None
 
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)
 
58
 
 
59
        #Tooltip handling
 
60
        self._current_transition_name = None
 
61
        self._current_tooltip_icon = None
 
62
 
 
63
        #Searchbox
 
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)
 
70
 
 
71
        self.props_widgets = Gtk.VBox()
 
72
        borderTable = Gtk.Table(n_rows=2, n_columns=3)
 
73
 
 
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)
 
79
 
 
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)
 
84
 
 
85
        self.invert_checkbox = Gtk.CheckButton(label=_("Reverse direction"))
 
86
        self.invert_checkbox.set_border_width(SPACING)
 
87
 
 
88
        self.props_widgets.add(borderTable)
 
89
        self.props_widgets.add(self.invert_checkbox)
 
90
 
 
91
        # Set the default values
 
92
        self._borderTypeChangedCb()
 
93
 
 
94
        self.infobar = Gtk.InfoBar()
 
95
        txtlabel = Gtk.Label()
 
96
        txtlabel.set_padding(PADDING, PADDING)
 
97
        txtlabel.set_line_wrap(True)
 
98
        txtlabel.set_text(
 
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)
 
103
 
 
104
        self.storemodel = Gtk.ListStore(GES.Asset, str, str, GdkPixbuf.Pixbuf)
 
105
 
 
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)
 
112
 
 
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)
 
120
 
 
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)
 
129
 
 
130
        # Speed-up startup by only checking available transitions on idle
 
131
        GLib.idle_add(self._loadAvailableTransitionsCb)
 
132
 
 
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)
 
137
 
 
138
        # Create the filterModel for searching
 
139
        self.modelFilter = self.storemodel.filter_new()
 
140
        self.iconview.set_model(self.modelFilter)
 
141
 
 
142
        self.infobar.show_all()
 
143
        self.iconview_scrollwin.show_all()
 
144
        self.iconview.hide()
 
145
        self.props_widgets.set_sensitive(False)
 
146
        self.props_widgets.hide()
 
147
        self.searchbar.hide()
 
148
 
 
149
# UI callbacks
 
150
 
 
151
    def _transitionSelectedCb(self, unused_event):
 
152
        transition_asset = self.getSelectedItem()
 
153
        if not transition_asset:
 
154
            # The user clicked between icons
 
155
            return False
 
156
 
 
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)
 
160
        else:
 
161
            self.props_widgets.set_sensitive(True)
 
162
 
 
163
        self.element.get_parent().set_asset(transition_asset)
 
164
        self.app.current_project.seeker.flush(True)
 
165
 
 
166
        return True
 
167
 
 
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)
 
173
 
 
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()
 
179
 
 
180
    def _borderTypeChangedCb(self, widget=None):
 
181
        """
 
182
        The "border" property in gstreamer is unlimited, but if you go over
 
183
        25 thousand it "loops" the transition instead of smoothing it.
 
184
        """
 
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"))
 
191
        else:
 
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"))
 
196
 
 
197
    def _searchEntryChangedCb(self, unused_entry):
 
198
        self.modelFilter.refilter()
 
199
 
 
200
    def _searchEntryIconClickedCb(self, entry, unused, unused_1):
 
201
        entry.set_text("")
 
202
 
 
203
# GES callbacks
 
204
 
 
205
    def _transitionTypeChangedCb(self, element, unused_prop):
 
206
        try:
 
207
            self.iconview.disconnect_by_func(self._transitionSelectedCb)
 
208
        except TypeError:
 
209
            pass
 
210
        finally:
 
211
            self.selectTransition(element.get_asset())
 
212
            self.iconview.connect("button-release-event", self._transitionSelectedCb)
 
213
 
 
214
    def _borderChangedCb(self, element, unused_prop):
 
215
        """
 
216
        The "border" transition property changed in the backend. Update the UI.
 
217
        """
 
218
        value = element.get_border()
 
219
        try:
 
220
            self.borderScale.disconnect_by_func(self._borderScaleCb)
 
221
        except TypeError:
 
222
            pass
 
223
        finally:
 
224
            self.borderScale.set_value(float(value))
 
225
            self.borderScale.connect("value-changed", self._borderScaleCb)
 
226
 
 
227
    def _invertChangedCb(self, element, unused_prop):
 
228
        """
 
229
        The "invert" transition property changed in the backend. Update the UI.
 
230
        """
 
231
        value = element.is_inverted()
 
232
        try:
 
233
            self.invert_checkbox.disconnect_by_func(self._invertCheckboxCb)
 
234
        except TypeError:
 
235
            pass
 
236
        finally:
 
237
            self.invert_checkbox.set_active(value)
 
238
            self.invert_checkbox.connect("toggled", self._invertCheckboxCb)
 
239
 
 
240
# UI methods
 
241
 
 
242
    def _loadAvailableTransitionsCb(self):
 
243
        """
 
244
        Get the list of transitions from GES and load the associated thumbnails.
 
245
        """
 
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)),
 
251
                                    trans_asset.icon])
 
252
 
 
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)
 
257
 
 
258
    def activate(self, element):
 
259
        """
 
260
        Hide the infobar and show the transitions UI.
 
261
        """
 
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)
 
269
        else:
 
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:
 
279
        self.infobar.hide()
 
280
 
 
281
    def selectTransition(self, transition_asset):
 
282
        """
 
283
        For a given transition type, select it in the iconview if available.
 
284
        """
 
285
        model = self.iconview.get_model()
 
286
        for row in 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)
 
291
 
 
292
    def deactivate(self):
 
293
        """
 
294
        Show the infobar and hide the transitions UI.
 
295
        """
 
296
        try:
 
297
            self.element.disconnect_by_func(self._borderChangedCb)
 
298
            self.element.disconnect_by_func(self._invertChangedCb)
 
299
            self.element.disconnect_by_func(self._transitionTypeChangedCb)
 
300
        except TypeError:
 
301
            pass
 
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.
 
307
            pass
 
308
        self.iconview.unselect_all()
 
309
        self.iconview.hide()
 
310
        self.props_widgets.hide()
 
311
        self.searchbar.hide()
 
312
        self.infobar.show()
 
313
 
 
314
    def _getIcon(self, transition_nick):
 
315
        """
 
316
        If available, return an icon pixbuf for a given transition nickname.
 
317
        """
 
318
        name = transition_nick + ".png"
 
319
        icon = None
 
320
        try:
 
321
            icon = GdkPixbuf.Pixbuf.new_from_file(os.path.join(self._pixdir, name))
 
322
        except:
 
323
            icon = self._question_icon
 
324
        return icon
 
325
 
 
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)
 
328
        if not is_row:
 
329
            return False
 
330
 
 
331
        view.set_tooltip_item(tooltip, path)
 
332
 
 
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
 
338
 
 
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)
 
344
        return True
 
345
 
 
346
    def getSelectedItem(self):
 
347
        path = self.iconview.get_selected_items()
 
348
        if path == []:
 
349
            return None
 
350
        return self.modelFilter[path[0]][COL_TRANSITION_ASSET]
 
351
 
 
352
    def _setRowVisible(self, model, iter, unused_data):
 
353
        """
 
354
        Filters the icon view depending on the search results
 
355
        """
 
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()
 
359
        return False