1
# PiTiVi , Non-linear video editor
5
# Copyright (C) 2010 Thibault Saunier <tsaunier@gnome.org>
7
# This program is free software; you can redistribute it and/or
8
# modify it under the terms of the GNU Lesser General Public
9
# License as published by the Free Software Foundation; either
10
# version 2.1 of the License, or (at your option) any later version.
12
# This program is distributed in the hope that it will be useful,
13
# but WITHOUT ANY WARRANTY; without even the implied warranty of
14
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15
# Lesser General Public License for more details.
17
# You should have received a copy of the GNU Lesser General Public
18
# License along with this program; if not, write to the
19
# Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
20
# Boston, MA 02110-1301, USA.
23
Class handling the midle pane
31
from gettext import gettext as _
33
from pitivi.log.loggable import Loggable
34
from pitivi.timeline.track import TrackEffect
35
from pitivi.stream import VideoStream
37
from pitivi.ui.gstwidget import GstElementSettingsWidget
38
from pitivi.ui.effectsconfiguration import EffectsPropertiesHandling
39
from pitivi.ui.depsmanager import DepsManager
40
from pitivi.ui.common import PADDING, SPACING
41
from pitivi.configure import get_ui_dir
42
from pitivi.check import soft_deps
43
from pitivi.ui.effectlist import HIDDEN_EFFECTS
49
COL_TRACK_EFFECT) = range(5)
52
class ClipPropertiesError(Exception):
53
"""Base Exception for errors happening in L{ClipProperties}s or L{EffectProperties}s"""
57
class ClipProperties(gtk.ScrolledWindow, Loggable):
59
Widget for configuring clips properties
62
def __init__(self, instance, uiman):
63
gtk.ScrolledWindow.__init__(self)
64
Loggable.__init__(self)
66
self.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
67
self.set_shadow_type(gtk.SHADOW_NONE)
70
vp.set_shadow_type(gtk.SHADOW_NONE)
74
self.settings = instance.settings
76
self.transformation_expander = None
77
self.info_bar_box = gtk.VBox()
82
self.effect_properties_handling = EffectsPropertiesHandling(instance.action_log)
84
self.effect_expander = EffectProperties(instance,
85
self.effect_properties_handling,
88
vbox.pack_start(self.info_bar_box, expand=False, fill=True)
90
self.transformation_expander = TransformationProperties(
91
instance, instance.action_log)
92
vbox.pack_start(self.transformation_expander, expand=False, fill=False)
93
self.transformation_expander.show()
95
vbox.pack_end(self.effect_expander, expand=True, fill=True)
96
vbox.set_spacing(SPACING)
98
self.info_bar_box.show()
99
self.effect_expander.show()
104
def _setProject(self, project):
105
self._project = project
107
self.effect_expander._connectTimelineSelection(self._project.timeline)
108
if self.transformation_expander:
109
self.transformation_expander.timeline = self._project.timeline
111
def _getProject(self):
114
project = property(_getProject, _setProject)
116
def addInfoBar(self, text):
117
info_bar = gtk.InfoBar()
120
label.set_padding(PADDING, PADDING)
121
label.set_line_wrap(True)
122
label.set_line_wrap_mode(pango.WRAP_WORD)
123
label.set_justify(gtk.JUSTIFY_CENTER)
127
self.info_bar_box.pack_start(info_bar, expand=False, fill=False)
129
return label, info_bar
132
class EffectProperties(gtk.Expander, gtk.HBox):
134
Widget for viewing and configuring effects
136
# Note: This should be inherited from gtk.Expander when we get other things
137
# to put in ClipProperties, that is why this is done this way
139
def __init__(self, instance, effect_properties_handling, clip_properties):
140
gtk.Expander.__init__(self)
141
gtk.HBox.__init__(self)
142
#self.set_expanded(True)
144
self.selected_effects = []
145
self.timeline_objects = []
148
self.effectsHandler = self.app.effects
149
self._effect_config_ui = None
151
self.effect_props_handling = effect_properties_handling
152
self.clip_properties = clip_properties
153
self._info_bar = None
154
self._config_ui_h_pos = None
155
self._timeline = None
157
self._vcontent = gtk.VPaned()
158
self.add(self._vcontent)
160
self._table = gtk.Table(3, 1, False)
162
self._toolbar = gtk.Toolbar()
163
self._removeEffectBt = gtk.ToolButton("gtk-delete")
164
self._removeEffectBt.set_label(_("Remove effect"))
165
self._removeEffectBt.set_use_underline(True)
166
self._removeEffectBt.set_is_important(True)
167
self._removeEffectBt.set_sensitive(False)
168
self._toolbar.insert(self._removeEffectBt, 0)
169
self._table.attach(self._toolbar, 0, 1, 0, 1, yoptions=gtk.FILL)
171
self.storemodel = gtk.ListStore(bool, str, str, str, object)
174
self.treeview_scrollwin = gtk.ScrolledWindow()
175
self.treeview_scrollwin.set_policy(gtk.POLICY_NEVER,
176
gtk.POLICY_AUTOMATIC)
177
self.treeview_scrollwin.set_shadow_type(gtk.SHADOW_ETCHED_IN)
180
# Displays name, description
181
self.treeview = gtk.TreeView(self.storemodel)
182
self.treeview_scrollwin.add(self.treeview)
183
self.treeview.set_property("rules_hint", True)
184
self.treeview.set_property("has_tooltip", True)
185
tsel = self.treeview.get_selection()
186
tsel.set_mode(gtk.SELECTION_SINGLE)
188
activatedcell = gtk.CellRendererToggle()
189
activatedcell.props.xpad = PADDING
190
activatedcol = self.treeview.insert_column_with_attributes(-1,
193
active=COL_ACTIVATED)
194
activatedcell.connect("toggled", self._effectActiveToggleCb)
196
typecol = gtk.TreeViewColumn(_("Type"))
197
typecol.set_sort_column_id(COL_TYPE)
198
self.treeview.append_column(typecol)
199
typecol.set_spacing(SPACING)
200
typecol.set_sizing(gtk.TREE_VIEW_COLUMN_AUTOSIZE)
201
typecol.set_min_width(50)
202
typecell = gtk.CellRendererText()
203
typecell.props.xpad = PADDING
204
typecell.set_property("ellipsize", pango.ELLIPSIZE_END)
205
typecol.pack_start(typecell)
206
typecol.add_attribute(typecell, "text", COL_TYPE)
208
namecol = gtk.TreeViewColumn(_("Effect name"))
209
namecol.set_sort_column_id(COL_NAME_TEXT)
210
self.treeview.append_column(namecol)
211
namecol.set_spacing(SPACING)
212
namecell = gtk.CellRendererText()
213
namecell.props.xpad = PADDING
214
namecell.set_property("ellipsize", pango.ELLIPSIZE_END)
215
namecol.pack_start(namecell)
216
namecol.add_attribute(namecell, "text", COL_NAME_TEXT)
218
self.treeview.drag_dest_set(gtk.DEST_DEFAULT_MOTION,
222
self.selection = self.treeview.get_selection()
224
self.selection.connect("changed", self._treeviewSelectionChangedCb)
225
self._removeEffectBt.connect("clicked", self._removeEffectClicked)
227
self.connect("drag-data-received", self._dragDataReceivedCb)
228
self.treeview.connect("drag-leave", self._dragLeaveCb)
229
self.treeview.connect("drag-drop", self._dragDropCb)
230
self.treeview.connect("drag-motion", self._dragMotionCb)
231
self.treeview.connect("query-tooltip", self._treeViewQueryTooltipCb)
232
self._vcontent.connect("notify", self._vcontentNotifyCb)
233
self.treeview.set_headers_clickable(False)
234
self.app.connect("new-project-loaded",
235
self._newProjectLoadedCb)
237
self._table.attach(self.treeview_scrollwin, 0, 1, 2, 3)
239
self._vcontent.pack1(self._table, resize=True, shrink=False)
241
self._vcontent.show()
242
self.set_expanded(True)
243
self.set_label(_("Effects"))
244
self.connect('notify::expanded', self._expandedCb)
246
def _newProjectLoadedCb(self, app, project):
247
self.clip_properties.project = project
248
self.selected_effects = self.timeline.selection.getSelectedTrackEffects()
251
def _vcontentNotifyCb(self, paned, gparamspec):
252
if gparamspec.name == 'position':
253
self._config_ui_h_pos = self._vcontent.get_position()
254
self.app.settings.effectVPanedPosition = self._config_ui_h_pos
256
def _getTimeline(self):
257
return self._timeline
259
def _setTimeline(self, timeline):
260
self._timeline = timeline
262
self.timeline.connect('selection-changed', self._selectionChangedCb)
264
timeline = property(_getTimeline, _setTimeline)
266
def _selectionChangedCb(self, timeline):
267
for timeline_object in self.timeline_objects:
268
timeline_object.disconnect_by_func(self._trackObjectAddedCb)
269
timeline_object.disconnect_by_func(self._trackRemovedRemovedCb)
271
self.selected_effects = timeline.selection.getSelectedTrackEffects()
273
if timeline.selection.selected:
274
self.timeline_objects = list(timeline.selection.selected)
275
for timeline_object in self.timeline_objects:
276
timeline_object.connect("track-object-added", self._trackObjectAddedCb)
277
timeline_object.connect("track-object-removed", self._trackRemovedRemovedCb)
278
self.set_sensitive(True)
280
self.timeline_objects = []
281
self.set_sensitive(False)
284
def _trackObjectAddedCb(self, unused_timeline_object, track_object):
285
if isinstance(track_object, TrackEffect):
286
selec = self.timeline.selection.getSelectedTrackEffects()
287
self.selected_effects = selec
290
def _trackRemovedRemovedCb(self, unused_timeline_object, track_object):
291
if isinstance(track_object, TrackEffect):
292
selec = self.timeline.selection.getSelectedTrackEffects()
293
self.selected_effects = selec
296
def _connectTimelineSelection(self, timeline):
297
self.timeline = timeline
299
def _removeEffectClicked(self, toolbutton):
300
if not self.selection.get_selected()[1]:
303
effect = self.storemodel.get_value(self.selection.get_selected()[1],
305
self._removeEffect(effect)
307
def _removeEffect(self, effect):
308
self.app.action_log.begin("remove effect")
309
self._cleanCache(effect)
310
effect.timeline_object.removeTrackObject(effect)
311
effect.track.removeTrackObject(effect)
312
self.app.action_log.commit()
314
def _cleanCache(self, effect):
315
element = effect.getElement()
316
config_ui = self.effect_props_handling.cleanCache(element)
318
def addEffectToCurrentSelection(self, factory_name):
319
if self.timeline_objects:
320
factory = self.app.effects.getFactoryFromName(factory_name)
321
self.app.action_log.begin("add effect")
322
self.timeline.addEffectFactoryOnObject(factory,
323
self.timeline_objects)
324
self.app.action_log.commit()
326
def _dragDataReceivedCb(self, unused_layout, context, unused_x, unused_y,
327
selection, unused_targetType, unused_timestamp):
328
self._factory = self.app.effects.getFactoryFromName(selection.data)
330
def _dragDropCb(self, unused, context, unused_x, unused_y, unused_timestamp):
332
self.app.action_log.begin("add effect")
333
self.timeline.addEffectFactoryOnObject(self._factory,
334
self.timeline_objects)
335
self.app.action_log.commit()
338
def _dragLeaveCb(self, unused_layout, unused_context, unused_tstamp):
340
self.drag_unhighlight()
342
def _dragMotionCb(self, unused, context, x, y, timestamp):
343
atom = gtk.gdk.atom_intern(dnd.EFFECT_TUPLE[0])
344
if not self._factory:
345
self.drag_get_data(context, atom, timestamp)
346
self.drag_highlight()
348
def _effectActiveToggleCb(self, cellrenderertoggle, path):
349
iter = self.storemodel.get_iter(path)
350
track_effect = self.storemodel.get_value(iter, COL_TRACK_EFFECT)
351
self.app.action_log.begin("change active state")
352
track_effect.active = not track_effect.active
353
self.app.action_log.commit()
355
def _expandedCb(self, expander, params):
358
def _treeViewQueryTooltipCb(self, treeview, x, y, keyboard_mode, tooltip):
359
context = treeview.get_tooltip_context(x, y, keyboard_mode)
364
treeview.set_tooltip_row(tooltip, context[1][0])
365
tooltip.set_text(self.storemodel.get_value(context[2], COL_DESC_TEXT))
369
def _updateAll(self):
370
if self.get_expanded():
371
self._removeEffectBt.set_sensitive(False)
372
if len(self.timeline_objects) == 1:
373
self._setEffectDragable()
374
self._updateTreeview()
375
self._updateEffectConfigUi()
377
self._hideEffectConfig()
378
self.storemodel.clear()
380
self._vcontent.show()
382
self._vcontent.hide()
384
def _activeChangedCb(self, unusedObj, unusedActive):
385
self._updateTreeview()
387
def _updateTreeview(self):
388
self.storemodel.clear()
389
for track_effect in self.selected_effects:
390
if not track_effect.factory.effectname in HIDDEN_EFFECTS:
391
to_append = [track_effect.gnl_object.get_property("active")]
392
track_effect.gnl_object.connect("notify::active",
393
self._activeChangedCb)
394
if isinstance(track_effect.factory.getInputStreams()[0],
396
to_append.append("Video")
398
to_append.append("Audio")
400
to_append.append(track_effect.factory.getHumanName())
401
to_append.append(track_effect.factory.getDescription())
402
to_append.append(track_effect)
404
self.storemodel.append(to_append)
406
def _showInfoBar(self):
407
if self._info_bar is None:
408
self.txtlabel, self._info_bar = self.clip_properties.addInfoBar(
409
_("Select a clip on the timeline "
410
"to configure its associated effects"))
411
self._info_bar.hide_all()
413
self._info_bar.show()
415
self.set_sensitive(False)
416
self._table.show_all()
418
def _setEffectDragable(self):
419
self.set_sensitive(True)
420
self._table.show_all()
421
self._info_bar.hide_all()
423
def _treeviewSelectionChangedCb(self, treeview):
424
if self.selection.count_selected_rows() == 0 and self.timeline_objects:
425
self.app.gui.setActionsSensitive(['DeleteObj'], True)
426
self._removeEffectBt.set_sensitive(False)
428
self.app.gui.setActionsSensitive(['DeleteObj'], False)
429
self._removeEffectBt.set_sensitive(True)
431
self._updateEffectConfigUi()
433
def _updateEffectConfigUi(self):
434
if self._config_ui_h_pos is None:
435
self._config_ui_h_pos =\
436
self.app.gui.settings.effectVPanedPosition
437
if self._config_ui_h_pos is None:
438
self._config_ui_h_pos =\
439
self.app.gui.settings.mainWindowHeight // 3
440
if self.selection.get_selected()[1]:
441
track_effect = self.storemodel.get_value(self.selection.get_selected()[1],
444
for widget in self._vcontent.get_children():
445
if type(widget) in [gtk.ScrolledWindow, GstElementSettingsWidget]:
446
self._vcontent.remove(widget)
448
element = track_effect.getElement()
449
ui = self.effect_props_handling.getEffectConfigurationUI(element)
451
self._effect_config_ui = ui
452
if self._effect_config_ui:
453
self._vcontent.pack2(self._effect_config_ui,
456
self._vcontent.set_position(int(self._config_ui_h_pos))
457
self._effect_config_ui.show_all()
458
self.selected_on_treeview = track_effect
460
self._hideEffectConfig()
462
def _hideEffectConfig(self):
463
if self._effect_config_ui:
464
self._effect_config_ui.hide()
465
self._effect_config_ui = None
468
class TransformationProperties(gtk.Expander):
470
Widget for viewing and configuring speed
473
'selection-changed': []}
475
def __init__(self, app, action_log):
476
gtk.Expander.__init__(self)
477
self.action_log = action_log
479
self._timeline = None
480
self._current_tl_obj = None
481
self.spin_buttons = {}
482
self.default_values = {}
483
self.set_label(_("Transformation"))
484
self.set_sensitive(False)
486
if not "Frei0r" in soft_deps:
487
self.builder = gtk.Builder()
488
self.builder.add_from_file(os.path.join(get_ui_dir(),
489
"cliptransformation.ui"))
491
self.add(self.builder.get_object("transform_box"))
494
self.connect('notify::expanded', self._expandedCb)
496
def _initButtons(self):
497
self.zoom_scale = self.builder.get_object("zoom_scale")
498
self.zoom_scale.connect("value-changed", self._zoomViewerCb)
499
clear_button = self.builder.get_object("clear_button")
500
clear_button.connect("clicked", self._defaultValuesCb)
502
self._getAndConnectToEffect("xpos_spinbtn", "tilt_x")
503
self._getAndConnectToEffect("ypos_spinbtn", "tilt_y")
505
self._getAndConnectToEffect("width_spinbtn", "scale_x")
506
self._getAndConnectToEffect("height_spinbtn", "scale_y")
508
self._getAndConnectToEffect("crop_left_spinbtn", "clip_left")
509
self._getAndConnectToEffect("crop_right_spinbtn", "clip_right")
510
self._getAndConnectToEffect("crop_top_spinbtn", "clip_top")
511
self._getAndConnectToEffect("crop_bottom_spinbtn", "clip_bottom")
512
self.connectSpinButtonsToFlush()
514
def _zoomViewerCb(self, scale):
515
self.app.gui.viewer.setZoom(scale.get_value())
517
def _expandedCb(self, expander, params):
518
if not "Frei0r" in soft_deps:
519
if self._current_tl_obj:
520
self.effect = self._findOrCreateEffect("frei0r-filter-scale0tilt")
521
self._updateSpinButtons()
522
self.set_expanded(self.get_expanded())
523
self._updateBoxVisibility()
524
self.zoom_scale.set_value(1.0)
526
if self.get_expanded():
527
DepsManager(self.app)
528
self.set_expanded(False)
530
def _defaultValuesCb(self, widget):
531
self.disconnectSpinButtonsFromFlush()
532
for name, spinbtn in self.spin_buttons.items():
533
spinbtn.set_value(self.default_values[name])
534
self.app.gui.viewer.pipeline.flushSeekVideo()
535
self.connectSpinButtonsToFlush()
536
self.track_effect.gnl_object.props.active = False
538
def disconnectSpinButtonsFromFlush(self):
539
for spinbtn in self.spin_buttons.values():
540
spinbtn.disconnect_by_func(self._flushPipeLineCb)
542
def connectSpinButtonsToFlush(self):
543
for spinbtn in self.spin_buttons.values():
544
spinbtn.connect("output", self._flushPipeLineCb)
546
def _updateSpinButtons(self):
547
for name, spinbtn in self.spin_buttons.items():
548
spinbtn.set_value(self.effect.get_property(name))
550
def _getAndConnectToEffect(self, widget_name, property_name):
551
spinbtn = self.builder.get_object(widget_name)
552
spinbtn.connect("output",
553
self._onValueChangedCb, property_name)
554
self.spin_buttons[property_name] = spinbtn
555
self.default_values[property_name] = spinbtn.get_value()
557
def _onValueChangedCb(self, spinbtn, prop):
558
value = spinbtn.get_value()
560
if value != self.default_values[prop] and not self.track_effect.gnl_object.props.active:
561
self.track_effect.gnl_object.props.active = True
563
if value != self.effect.get_property(prop):
564
self.action_log.begin("Transformation property change")
565
self.effect.set_property(prop, value)
566
self.action_log.commit()
567
box = self.app.gui.viewer.internal.box
569
# update box when values are changed in the spin boxes,
570
# so no point is selected
571
if box and box.clicked_point == 0:
572
box.update_from_effect(self.effect)
574
def _flushPipeLineCb(self, widget):
575
self.app.gui.viewer.pipeline.flushSeekVideo()
577
def _findEffect(self, name):
578
for track_effect in self._current_tl_obj.track_objects:
579
if isinstance(track_effect, TrackEffect):
580
if name in track_effect.getElement().get_path_string():
581
self.track_effect = track_effect
582
return track_effect.getElement()
584
def _findOrCreateEffect(self, name):
585
effect = self._findEffect(name)
587
factory = self.app.effects.getFactoryFromName(name)
588
self.timeline.addEffectFactoryOnObject(factory, [self._current_tl_obj])
589
effect = self._findEffect(name)
590
# disable the effect on default
591
self.track_effect.gnl_object.props.active = False
592
self.app.gui.viewer.internal.set_transformation_properties(self)
593
effect.freeze_notify()
596
def _selectionChangedCb(self, timeline):
597
if self.timeline and len(self.timeline.selection.selected) > 0:
598
for tl_obj in self.timeline.selection.selected:
601
if tl_obj != self._current_tl_obj:
602
self._current_tl_obj = tl_obj
605
self.set_sensitive(True)
606
if self.get_expanded():
607
self.effect = self._findOrCreateEffect("frei0r-filter-scale0tilt")
608
self._updateSpinButtons()
610
if self._current_tl_obj:
611
self._current_tl_obj = None
612
self.zoom_scale.set_value(1.0)
613
self.app.gui.viewer.pipeline.flushSeekVideo()
615
self.set_sensitive(False)
616
self._updateBoxVisibility()
618
def _updateBoxVisibility(self):
619
if self.get_expanded() and self._current_tl_obj:
620
self.app.gui.viewer.internal.show_box()
622
self.app.gui.viewer.internal.hide_box()
624
def _getTimeline(self):
625
return self._timeline
627
def _setTimeline(self, timeline):
628
self._timeline = timeline
630
self.timeline.connect('selection-changed', self._selectionChangedCb)
632
timeline = property(_getTimeline, _setTimeline)