1
# PiTiVi , Non-linear video editor
5
# Copyright (c) 2005, Edward Hervey <bilboed@bilboed.com>
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.
29
from urllib import unquote
30
from gettext import gettext as _
31
from gettext import ngettext
33
import pitivi.ui.dnd as dnd
34
from pitivi.ui.pathwalker import PathWalker, quote_uri
35
from pitivi.ui.filelisterrordialog import FileListErrorDialog
36
from pitivi.configure import get_pixmap_dir
37
from pitivi.signalgroup import SignalGroup
38
from pitivi.stream import VideoStream, AudioStream, TextStream, \
40
from pitivi.settings import GlobalSettings
41
from pitivi.utils import beautify_length
42
from pitivi.ui.common import beautify_factory, factory_name, \
43
beautify_stream, SPACING, PADDING
44
from pitivi.log.loggable import Loggable
45
from pitivi.sourcelist import SourceListError
46
from pitivi.ui.filechooserpreview import PreviewWidget
51
GlobalSettings.addConfigSection('clip-library')
52
GlobalSettings.addConfigOption('lastImportFolder',
53
section='clip-library',
55
environment='PITIVI_IMPORT_FOLDER',
56
default=os.path.expanduser("~"))
57
GlobalSettings.addConfigOption('closeImportDialog',
58
section='clip-library',
59
key='close-import-dialog-after-import',
61
GlobalSettings.addConfigOption('lastClipView',
62
section='clip-library',
65
default=SHOW_ICONVIEW)
74
COL_SHORT_TEXT) = range(8)
79
NOT_A_FILE) = range(4)
83
<menubar name="MainMenuBar">
84
<menu action="Library">
85
<placeholder name="SourceList" >
86
<menuitem action="ImportSources" />
87
<menuitem action="ImportSourcesFolder" />
89
<menuitem action="SelectUnusedSources" />
90
<menuitem action="RemoveSources" />
92
<menuitem action="InsertEnd" />
96
<toolbar name="MainToolBar">
97
<placeholder name="SourceList">
98
<toolitem action="ImportSources" />
104
INVISIBLE = gtk.gdk.pixbuf_new_from_file(os.path.join(get_pixmap_dir(),
108
class SourceList(gtk.VBox, Loggable):
109
""" Widget for listing sources """
112
'play': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
113
(gobject.TYPE_PYOBJECT,))}
115
def __init__(self, instance, uiman):
116
gtk.VBox.__init__(self)
117
Loggable.__init__(self)
120
self.settings = instance.settings
124
# icon, infotext, objectfactory, uri, length
125
self.storemodel = gtk.ListStore(gtk.gdk.Pixbuf, gtk.gdk.Pixbuf,
126
str, object, str, str, str, str)
129
self.treeview_scrollwin = gtk.ScrolledWindow()
130
self.treeview_scrollwin.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
131
self.treeview_scrollwin.set_shadow_type(gtk.SHADOW_ETCHED_IN)
133
self.iconview_scrollwin = gtk.ScrolledWindow()
134
self.iconview_scrollwin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
135
self.iconview_scrollwin.set_shadow_type(gtk.SHADOW_ETCHED_IN)
138
self.popup = gtk.Menu()
139
self.popup_importitem = gtk.ImageMenuItem(_("Import Files..."))
141
image.set_from_stock(gtk.STOCK_ADD, gtk.ICON_SIZE_MENU)
142
self.popup_importitem.set_image(image)
144
self.popup_remitem = gtk.ImageMenuItem(_("Remove Clip"))
146
image.set_from_stock(gtk.STOCK_REMOVE, gtk.ICON_SIZE_MENU)
147
self.popup_remitem.set_image(image)
148
self.popup_playmenuitem = gtk.MenuItem(_("Play Clip"))
149
self.popup_importitem.connect("activate", self._importButtonClickedCb)
150
self.popup_remitem.connect("activate", self._removeButtonClickedCb)
151
self.popup_playmenuitem.connect("activate", self._playButtonClickedCb)
152
self.popup_importitem.show()
153
self.popup_remitem.show()
154
self.popup_playmenuitem.show()
155
self.popup.append(self.popup_importitem)
156
self.popup.append(self.popup_remitem)
157
self.popup.append(self.popup_playmenuitem)
159
# import sources dialogbox
160
self._importDialog = None
163
self.search_hbox = gtk.HBox()
164
self.search_hbox.set_spacing(SPACING)
165
self.search_hbox.set_border_width(3) # Prevents being flush against the notebook
166
searchLabel = gtk.Label(_("Search:"))
167
searchEntry = gtk.Entry()
168
searchEntry.set_icon_from_stock(gtk.ENTRY_ICON_SECONDARY, "gtk-clear")
169
searchEntry.connect("changed", self.searchEntryChangedCb)
170
searchEntry.connect("button-press-event", self.searchEntryActivateCb)
171
searchEntry.connect("focus-out-event", self.searchEntryDeactivateCb)
172
searchEntry.connect("icon-press", self.searchEntryIconClickedCb)
173
self.search_hbox.pack_start(searchLabel, expand=False)
174
self.search_hbox.pack_end(searchEntry, expand=True)
175
# Filtering model for the search box.
176
# Use this instead of using self.storemodel directly
177
self.modelFilter = self.storemodel.filter_new()
178
self.modelFilter.set_visible_func(self._setRowVisible, data=searchEntry)
181
# Displays icon, name, type, length
182
self.treeview = gtk.TreeView(self.modelFilter)
183
self.treeview_scrollwin.add(self.treeview)
184
self.treeview.connect("button-press-event", self._treeViewButtonPressEventCb)
185
self.treeview.connect("row-activated", self._rowActivatedCb)
186
self.treeview.set_property("rules_hint", True)
187
self.treeview.set_headers_visible(False)
188
self.treeview.set_property("search_column", COL_SEARCH_TEXT)
189
tsel = self.treeview.get_selection()
190
tsel.set_mode(gtk.SELECTION_MULTIPLE)
191
tsel.connect("changed", self._viewSelectionChangedCb)
193
pixbufcol = gtk.TreeViewColumn(_("Icon"))
194
pixbufcol.set_expand(False)
195
pixbufcol.set_spacing(SPACING)
196
self.treeview.append_column(pixbufcol)
197
pixcell = gtk.CellRendererPixbuf()
198
pixcell.props.xpad = 6
199
pixbufcol.pack_start(pixcell)
200
pixbufcol.add_attribute(pixcell, 'pixbuf', COL_ICON)
202
namecol = gtk.TreeViewColumn(_("Information"))
203
self.treeview.append_column(namecol)
204
namecol.set_expand(True)
205
namecol.set_spacing(SPACING)
206
namecol.set_sizing(gtk.TREE_VIEW_COLUMN_GROW_ONLY)
207
namecol.set_min_width(150)
208
txtcell = gtk.CellRendererText()
209
txtcell.set_property("ellipsize", pango.ELLIPSIZE_END)
210
namecol.pack_start(txtcell)
211
namecol.add_attribute(txtcell, "markup", COL_INFOTEXT)
213
namecol = gtk.TreeViewColumn(_("Duration"))
214
namecol.set_expand(False)
215
self.treeview.append_column(namecol)
216
txtcell = gtk.CellRendererText()
217
txtcell.set_property("yalign", 0.0)
218
namecol.pack_start(txtcell)
219
namecol.add_attribute(txtcell, "markup", COL_LENGTH)
222
self.iconview = gtk.IconView(self.modelFilter)
223
self.iconview_scrollwin.add(self.iconview)
224
self.iconview.connect("button-press-event", self._iconViewButtonPressEventCb)
225
self.iconview.connect("selection-changed", self._viewSelectionChangedCb)
226
self.iconview.set_orientation(gtk.ORIENTATION_VERTICAL)
227
self.iconview.set_property("has_tooltip", True)
228
self.iconview.set_tooltip_column(COL_INFOTEXT)
229
self.iconview.set_text_column(COL_SHORT_TEXT)
230
self.iconview.set_pixbuf_column(COL_ICON_LARGE)
231
self.iconview.set_selection_mode(gtk.SELECTION_MULTIPLE)
232
self.iconview.set_item_width(106)
234
# Explanatory message InfoBar
235
self.infobar = gtk.InfoBar()
237
txtlabel = gtk.Label()
238
txtlabel.set_padding(PADDING, PADDING)
239
txtlabel.set_line_wrap(True)
240
txtlabel.set_line_wrap_mode(pango.WRAP_WORD)
241
txtlabel.set_justify(gtk.JUSTIFY_CENTER)
243
_('Add media to your project by dragging files and folders here or '
244
'by using the "Import Files..." button.'))
245
self.infobar.add(txtlabel)
246
self.txtlabel = txtlabel
248
# The infobar that shows up if there are _errors when importing clips
249
self._import_warning_infobar = gtk.InfoBar()
250
self._import_warning_infobar.set_message_type(gtk.MESSAGE_WARNING)
251
content_area = self._import_warning_infobar.get_content_area()
252
actions_area = self._import_warning_infobar.get_action_area()
253
self._warning_label = gtk.Label()
254
self._warning_label.set_line_wrap(True)
255
self._warning_label.set_line_wrap_mode(pango.WRAP_WORD)
256
self._warning_label.set_justify(gtk.JUSTIFY_CENTER)
257
self._view_error_btn = gtk.Button()
258
self._hide_infobar_btn = gtk.Button()
259
self._hide_infobar_btn.set_label(_("Hide"))
260
self._view_error_btn.connect("clicked", self._viewErrorsButtonClickedCb)
261
self._hide_infobar_btn.connect("clicked", self._hideInfoBarClickedCb)
262
content_area.add(self._warning_label)
263
actions_area.add(self._view_error_btn)
264
actions_area.add(self._hide_infobar_btn)
266
# The _progressbar that shows up when importing clips
267
self._progressbar = gtk.ProgressBar()
269
# Connect to project. We must remove and reset the callbacks when
271
self.project_signals = SignalGroup()
272
self.app.connect("new-project-created", self._newProjectCreatedCb)
273
self.app.connect("new-project-loaded", self._newProjectLoadedCb)
274
self.app.connect("new-project-failed", self._newProjectFailedCb)
277
self.audiofilepixbuf = self._getIcon("audio-x-generic", "pitivi-sound.png")
278
self.videofilepixbuf = self._getIcon("video-x-generic", "pitivi-video.png")
281
self.drag_dest_set(gtk.DEST_DEFAULT_DROP | gtk.DEST_DEFAULT_MOTION,
282
[dnd.URI_TUPLE, dnd.FILE_TUPLE],
284
self.connect("drag_data_received", self._dndDataReceivedCb)
286
self.treeview.drag_source_set(0, [], gtk.gdk.ACTION_COPY)
287
self.treeview.connect("motion-notify-event",
288
self._treeViewMotionNotifyEventCb)
289
self.treeview.connect("button-release-event",
290
self._treeViewButtonReleaseCb)
291
self.treeview.connect("drag_begin", self._dndDragBeginCb)
292
self.treeview.connect("drag_data_get", self._dndDataGetCb)
294
self.iconview.drag_source_set(0, [], gtk.gdk.ACTION_COPY)
295
self.iconview.connect("motion-notify-event",
296
self._iconViewMotionNotifyEventCb)
297
self.iconview.connect("button-release-event",
298
self._iconViewButtonReleaseCb)
299
self.iconview.connect("drag_begin", self._dndDragBeginCb)
300
self.iconview.connect("drag_data_get", self._dndDataGetCb)
302
# Hack so that the views have the same method as self
303
self.treeview.getSelectedItems = self.getSelectedItems
307
("ImportSources", gtk.STOCK_ADD, _("_Import Files..."),
308
None, _("Add media files to your project"),
309
self._importSourcesCb),
310
("ImportSourcesFolder", gtk.STOCK_ADD,
311
_("Import _Folders..."), None,
312
_("Add the contents of a folder as clips in your project"),
313
self._importSourcesFolderCb),
314
("SelectUnusedSources", None, _("Select Unused Media"), None,
315
_("Select clips that have not been used in the project"),
316
self._selectUnusedSourcesCb),
319
# only available when selection is non-empty
320
selection_actions = (
321
("RemoveSources", gtk.STOCK_DELETE,
322
_("_Remove from Project"), "<Control>Delete", None,
323
self._removeSourcesCb),
324
("InsertEnd", gtk.STOCK_COPY,
325
_("Insert at _End of Timeline"), "Insert", None,
329
actiongroup = gtk.ActionGroup("sourcelistpermanent")
330
actiongroup.add_actions(actions)
331
actiongroup.get_action("ImportSources").props.is_important = True
332
uiman.insert_action_group(actiongroup, 0)
334
self.selection_actions = gtk.ActionGroup("sourcelistselection")
335
self.selection_actions.add_actions(selection_actions)
336
self.selection_actions.set_sensitive(False)
337
uiman.insert_action_group(self.selection_actions, 0)
338
uiman.add_ui_from_string(ui)
340
# clip view menu items
341
view_menu_item = uiman.get_widget('/MainMenuBar/View')
342
view_menu = view_menu_item.get_submenu()
343
seperator = gtk.SeparatorMenuItem()
344
self.treeview_menuitem = gtk.RadioMenuItem(None,
345
_("Show Clips as a List"))
346
self.iconview_menuitem = gtk.RadioMenuItem(self.treeview_menuitem,
347
_("Show Clips as Icons"))
349
# update menu items with current clip view before we connect to item
351
if self.settings.lastClipView == SHOW_TREEVIEW:
352
self.treeview_menuitem.set_active(True)
353
self.iconview_menuitem.set_active(False)
355
self.treeview_menuitem.set_active(False)
356
self.iconview_menuitem.set_active(True)
358
# we only need to connect to one menu item because we get a signal
359
# from each radio item in the group
360
self.treeview_menuitem.connect("toggled", self._treeViewMenuItemToggledCb)
362
view_menu.append(seperator)
363
view_menu.append(self.treeview_menuitem)
364
view_menu.append(self.iconview_menuitem)
365
self.treeview_menuitem.show()
366
self.iconview_menuitem.show()
369
# add all child widgets
370
self.pack_start(self.infobar, expand=False, fill=False)
371
self.pack_start(self._import_warning_infobar, expand=False, fill=False)
372
self.pack_start(self.search_hbox, expand=False)
373
self.pack_start(self.iconview_scrollwin)
374
self.pack_start(self.treeview_scrollwin)
375
self.pack_start(self._progressbar, expand=False)
377
# display the help text
378
self.clip_view = self.settings.lastClipView
379
self._displayClipView()
381
def _importSourcesCb(self, unused_action):
382
self.showImportSourcesDialog()
384
def _importSourcesFolderCb(self, unused_action):
385
self.showImportSourcesDialog(True)
387
def _removeSourcesCb(self, unused_action):
388
self._removeSources()
390
def _selectUnusedSourcesCb(self, widget):
391
self._selectUnusedSources()
393
def _insertEndCb(self, unused_action):
394
self.app.action_log.begin("add clip")
395
timeline = self.app.current.timeline
396
sources = self.app.current.sources
397
start = timeline.duration
398
self.app.current.seeker.seek(start)
399
for uri in self.getSelectedItems():
400
factory = sources.getUri(uri)
401
source = timeline.addSourceFactory(factory)
402
source.setStart(start)
403
start += source.duration
404
self.app.action_log.commit()
406
def searchEntryChangedCb(self, entry):
407
self.modelFilter.refilter()
409
def searchEntryIconClickedCb(self, entry, unused, unsed1):
412
def searchEntryDeactivateCb(self, entry, event):
413
self.app.gui.setActionsSensitive("default", True)
415
def searchEntryActivateCb(self, entry, event):
416
self.app.gui.setActionsSensitive("default", False)
418
def _setRowVisible(self, model, iter, data):
420
Toggle the visibility of a liststore row.
421
Used for the search box.
423
text = data.get_text().lower()
425
return True # Avoid silly warnings
427
return text in model.get_value(iter, COL_INFOTEXT).lower()
429
def _getIcon(self, iconname, alternate):
430
icontheme = gtk.icon_theme_get_default()
431
pixdir = get_pixmap_dir()
434
icon = icontheme.load_icon(iconname, 32, 0)
436
# empty except clause is bad but load_icon raises gio.Error.
439
icon = gtk.gdk.pixbuf_new_from_file(os.path.join(pixdir, alternate))
442
def _connectToProject(self, project):
443
"""Connect signal handlers to a project.
445
This first disconnects any handlers connected to an old project.
446
If project is None, this just disconnects any connected handlers.
449
self.project_signals.connect(
450
project.sources, "source-added", None, self._sourceAddedCb)
451
self.project_signals.connect(
452
project.sources, "source-removed", None, self._sourceRemovedCb)
453
self.project_signals.connect(
454
project.sources, "discovery-error", None, self._discoveryErrorCb)
455
self.project_signals.connect(
456
project.sources, "missing-plugins", None, self._missingPluginsCb)
457
self.project_signals.connect(
458
project.sources, "ready", None, self._sourcesStoppedImportingCb)
459
self.project_signals.connect(
460
project.sources, "starting", None, self._sourcesStartedImportingCb)
462
def _setClipView(self, show):
463
""" Set which clip view to use when sourcelist is showing clips. If
464
none is given, the current one is used. Show: one of SHOW_TREEVIEW or
467
# save current selection
468
paths = self.getSelectedPaths()
470
# update saved clip view
471
self.settings.lastClipView = show
472
self.clip_view = show
474
# transfer selection to next view
475
self._viewUnselectAll()
477
self._viewSelectPath(path)
479
self._displayClipView()
481
def _displayClipView(self):
483
# first hide all the child widgets
484
self.treeview_scrollwin.hide()
485
self.iconview_scrollwin.hide()
487
# pick the widget we're actually showing
488
if self.clip_view == SHOW_TREEVIEW:
489
self.debug("displaying tree view")
490
widget = self.treeview_scrollwin
491
elif self.clip_view == SHOW_ICONVIEW:
492
self.debug("displaying icon view")
493
widget = self.iconview_scrollwin
495
if not len(self.storemodel):
496
self._displayHelpText()
498
# now un-hide the view
501
def _displayHelpText(self):
502
"""Display the InfoBar help message"""
503
self.infobar.hide_all()
507
def showImportSourcesDialog(self, select_folders=False):
508
"""Pop up the "Import Sources" dialog box"""
509
if self._importDialog:
513
chooser_action = gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER
514
dialogtitle = _("Select One or More Folders")
516
chooser_action = gtk.FILE_CHOOSER_ACTION_OPEN
517
dialogtitle = _("Select One or More Files")
518
close_after = gtk.CheckButton(_("Close after importing files"))
519
close_after.set_active(self.app.settings.closeImportDialog)
521
self._importDialog = gtk.FileChooserDialog(dialogtitle, None,
523
(gtk.STOCK_CLOSE, gtk.RESPONSE_CLOSE,
524
gtk.STOCK_ADD, gtk.RESPONSE_OK))
525
self._importDialog.set_icon_name("pitivi")
526
self._importDialog.props.extra_widget = close_after
527
self._importDialog.set_default_response(gtk.RESPONSE_OK)
528
self._importDialog.set_select_multiple(True)
529
self._importDialog.set_modal(False)
530
pw = PreviewWidget(self.app)
531
self._importDialog.set_preview_widget(pw)
532
self._importDialog.set_use_preview_label(False)
533
self._importDialog.connect('update-preview', pw.add_preview_request)
534
self._importDialog.set_current_folder(self.app.settings.lastImportFolder)
536
self._importDialog.connect('response', self._dialogBoxResponseCb, select_folders)
537
self._importDialog.connect('close', self._dialogBoxCloseCb)
538
self._importDialog.show()
540
def addFolders(self, folders):
541
""" walks the trees of the folders in the list and adds the files it finds """
542
self.app.threads.addThread(PathWalker, folders, self.app.current.sources.addUris)
544
def _updateProgressbar(self):
546
Update the _progressbar with the ratio of clips imported vs the total
548
current_clip_iter = self.app.current.sources.nb_imported_files
549
total_clips = self.app.current.sources.nb_file_to_import
550
progressbar_text = _("Importing clip %(current_clip)d of %(total)d" %
551
{"current_clip": current_clip_iter,
552
"total": total_clips})
553
self._progressbar.set_text(progressbar_text)
554
if current_clip_iter == 0:
555
self._progressbar.set_fraction(0.0)
556
elif total_clips != 0:
557
self._progressbar.set_fraction((current_clip_iter - 1) / float(total_clips))
559
def _addFactory(self, factory):
560
video = factory.getOutputStreams(VideoStream)
561
if video and video[0].thumbnail:
562
thumbnail_file = video[0].thumbnail
564
self.debug("attempting to open thumbnail file '%s'",
566
pixbuf = gtk.gdk.pixbuf_new_from_file(thumbnail_file)
568
self.error("Failure to create thumbnail from file '%s'",
570
thumbnail = self.videofilepixbuf
571
thumbnail_large = self.videofilepixbuf
573
desiredheight = int(64 / float(video[0].dar))
574
thumbnail = pixbuf.scale_simple(64,
575
desiredheight, gtk.gdk.INTERP_BILINEAR)
576
desiredheight = int(96 / float(video[0].dar))
577
thumbnail_large = pixbuf.scale_simple(96,
578
desiredheight, gtk.gdk.INTERP_BILINEAR)
581
thumbnail = self.videofilepixbuf
582
thumbnail_large = self.videofilepixbuf
584
thumbnail = self.audiofilepixbuf
585
thumbnail_large = self.audiofilepixbuf
587
if not factory.duration or factory.duration == gst.CLOCK_TIME_NONE:
590
duration = beautify_length(factory.duration)
593
uni = unicode(factory_name(factory), 'utf-8')
596
short_uni = uni[0:29]
597
short_uni += unicode('...')
598
short_text = short_uni.encode('utf-8')
600
short_text = factory_name(factory)
602
self.storemodel.append([thumbnail,
604
beautify_factory(factory),
608
factory_name(factory),
610
self._displayClipView()
612
# sourcelist callbacks
614
def _sourceAddedCb(self, sourcelist, factory):
615
""" a file was added to the sourcelist """
616
self._updateProgressbar()
617
self._addFactory(factory)
618
if len(self.storemodel):
619
self.infobar.hide_all()
620
self.search_hbox.show_all()
622
def _sourceRemovedCb(self, sourcelist, uri, factory):
623
""" the given uri was removed from the sourcelist """
624
# find the good line in the storemodel and remove it
625
model = self.storemodel
627
if uri == row[COL_URI]:
628
model.remove(row.iter)
631
self._displayHelpText()
632
self.search_hbox.hide()
634
def _discoveryErrorCb(self, unused_sourcelist, uri, reason, extra):
635
""" The given uri isn't a media file """
636
error = (uri, reason, extra)
637
self._errors.append(error)
639
def _missingPluginsCb(self, sourcelist, uri, factory, details, descriptions, cb):
640
error = (uri, "Missing plugins", "\n".join(descriptions))
641
self._errors.append(error)
643
def _sourcesStartedImportingCb(self, sourcelist):
644
self._progressbar.show()
645
self._updateProgressbar()
647
def _sourcesStoppedImportingCb(self, unused_sourcelist):
648
self._progressbar.hide()
650
if len(self._errors) > 1:
651
self._warning_label.set_text(_("Errors occurred while importing."))
652
self._view_error_btn.set_label(_("View errors"))
654
self._warning_label.set_text(_("An error occurred while importing."))
655
self._view_error_btn.set_label(_("View error"))
657
self._import_warning_infobar.show_all()
659
## Error Dialog Box callbacks
661
def _errorDialogBoxCloseCb(self, unused_dialog):
662
self._error_dialogbox.destroy()
663
self._error_dialogbox = None
665
def _errorDialogBoxResponseCb(self, unused_dialog, unused_response):
666
self._error_dialogbox.destroy()
667
self._error_dialogbox = None
669
## Import Sources Dialog Box callbacks
671
def _dialogBoxResponseCb(self, dialogbox, response, select_folders):
672
self.debug("response:%r", response)
673
if response == gtk.RESPONSE_OK:
674
lastfolder = dialogbox.get_current_folder()
675
self.app.settings.lastImportFolder = lastfolder
676
self.app.settings.closeImportDialog = \
677
dialogbox.props.extra_widget.get_active()
678
filenames = dialogbox.get_uris()
680
self.addFolders(filenames)
682
self.app.current.sources.addUris(filenames)
683
if self.app.settings.closeImportDialog:
685
self._importDialog = None
688
self._importDialog = None
690
def _dialogBoxCloseCb(self, unused_dialogbox):
691
self.debug("closing")
692
self._importDialog = None
694
def _removeSources(self):
695
model = self.storemodel
696
paths = self.getSelectedPaths()
697
if paths == None or paths < 1:
699
# use row references so we don't have to care if a path has been removed
702
row = gtk.TreeRowReference(model, path)
705
self.app.action_log.begin("remove clip from source list")
707
uri = model[row.get_path()][COL_URI]
708
self.app.current.sources.removeUri(uri)
709
self.app.action_log.commit()
711
def _selectUnusedSources(self):
713
Select, in the media library, unused sources in the project.
715
sources = self.app.current.sources.getSources()
716
unused_sources_uris = []
718
model = self.storemodel
719
selection = self.treeview.get_selection()
720
for source in sources:
721
if not self.app.current.timeline.usesFactory(source):
722
unused_sources_uris.append(source.uri)
724
# Hack around the fact that making selections (in a treeview/iconview)
725
# deselects what was previously selected
726
if self.clip_view == SHOW_TREEVIEW:
727
self.treeview.get_selection().select_all()
728
elif self.clip_view == SHOW_ICONVIEW:
729
self.iconview.select_all()
732
if row[COL_URI] not in unused_sources_uris:
733
if self.clip_view == SHOW_TREEVIEW:
734
selection.unselect_iter(row.iter)
736
self.iconview.unselect_path(row.path)
738
## UI Button callbacks
740
def _importButtonClickedCb(self, unused_widget=None):
741
""" Called when a user clicks on the import button """
742
self.showImportSourcesDialog()
744
def _removeButtonClickedCb(self, unused_widget=None):
745
""" Called when a user clicks on the remove button """
746
self._removeSources()
748
def _playButtonClickedCb(self, unused_widget=None):
749
""" Called when a user clicks on the play button """
750
# get the selected filesourcefactory
751
paths = self.getSelectedPaths()
752
model = self.storemodel
756
factory = model[path][COL_FACTORY]
757
self.debug("Let's play %s", factory.uri)
758
self.emit('play', factory)
760
def _hideInfoBarClickedCb(self, unused_button):
761
self._resetErrorList()
763
def _resetErrorList(self):
765
self._import_warning_infobar.hide()
767
def _viewErrorsButtonClickedCb(self, unused_button):
769
Show a FileListErrorDialog to display import _errors.
771
if len(self._errors) > 1:
772
msgs = (_("Error while analyzing files"),
773
_("The following files can not be used with PiTiVi."))
775
msgs = (_("Error while analyzing a file"),
776
_("The following file can not be used with PiTiVi."))
777
self._error_dialogbox = FileListErrorDialog(*msgs)
778
self._error_dialogbox.connect("close", self._errorDialogBoxCloseCb)
779
self._error_dialogbox.connect("response", self._errorDialogBoxResponseCb)
780
for uri, reason, extra in self._errors:
781
self._error_dialogbox.addFailedFile(uri, reason, extra)
782
self._error_dialogbox.window.show()
783
# Reset the error list, since the user has read them.
784
self._resetErrorList()
786
def _treeViewMenuItemToggledCb(self, unused_widget):
787
if self.treeview_menuitem.get_active():
791
self._setClipView(show)
794
_dragSelection = False
798
_ignoreRelease = False
800
def _rowUnderMouseSelected(self, view, event):
801
result = view.get_path_at_pos(int(event.x), int(event.y))
804
if isinstance(view, gtk.TreeView):
805
selection = view.get_selection()
807
return selection.path_is_selected(path) and selection.count_selected_rows() > 0
808
elif isinstance(view, gtk.IconView):
809
selection = view.get_selected_items()
811
return view.path_is_selected(path) and len(selection)
817
def _nothingUnderMouse(self, view, event):
818
return not bool(view.get_path_at_pos(int(event.x), int(event.y)))
820
def _viewShowPopup(self, view, event):
821
if view != None and self._rowUnderMouseSelected(view, event):
822
self.popup_remitem.set_sensitive(True)
823
self.popup_playmenuitem.set_sensitive(True)
824
elif view != None and (not self._nothingUnderMouse(view, event)):
825
if not event.state & (gtk.gdk.CONTROL_MASK | gtk.gdk.SHIFT_MASK):
826
self._viewUnselectAll()
827
elif self.clip_view == SHOW_TREEVIEW and self._viewHasSelection() \
828
and (event.state & gtk.gdk.SHIFT_MASK):
829
selection = self.treeview.get_selection()
830
start_path = self._viewGetFirstSelected()
831
end_path = self._viewGetPathAtPos(event)
832
self._viewUnselectAll()
833
selection.select_range(start_path, end_path)
835
self._viewSelectPath(self._viewGetPathAtPos(event))
836
self.popup_remitem.set_sensitive(True)
837
self.popup_playmenuitem.set_sensitive(True)
839
self.popup_remitem.set_sensitive(False)
840
self.popup_playmenuitem.set_sensitive(False)
842
self.popup.popup(None, None, None, event.button, event.time)
844
def _viewGetFirstSelected(self):
845
paths = self.getSelectedPaths()
848
def _viewHasSelection(self):
849
paths = self.getSelectedPaths()
850
return bool(len(paths))
852
def _viewGetPathAtPos(self, event):
853
if self.clip_view == SHOW_TREEVIEW:
854
pathinfo = self.treeview.get_path_at_pos(int(event.x), int(event.y))
856
elif self.clip_view == SHOW_ICONVIEW:
857
return self.iconview.get_path_at_pos(int(event.x), int(event.y))
859
def _viewSelectPath(self, path):
860
if self.clip_view == SHOW_TREEVIEW:
861
selection = self.treeview.get_selection()
862
selection.select_path(path)
863
elif self.clip_view == SHOW_ICONVIEW:
864
self.iconview.select_path(path)
866
def _viewUnselectAll(self):
867
if self.clip_view == SHOW_TREEVIEW:
868
selection = self.treeview.get_selection()
869
selection.unselect_all()
870
elif self.clip_view == SHOW_ICONVIEW:
871
self.iconview.unselect_all()
873
def _treeViewButtonPressEventCb(self, treeview, event):
876
if event.type == gtk.gdk._2BUTTON_PRESS:
877
self._playButtonClickedCb()
879
elif event.button == 3:
880
self._viewShowPopup(treeview, event)
885
if not event.state & (gtk.gdk.CONTROL_MASK | gtk.gdk.SHIFT_MASK):
886
chain_up = not self._rowUnderMouseSelected(treeview, event)
888
self._dragStarted = False
889
self._dragSelection = False
890
self._dragButton = event.button
891
self._dragX = int(event.x)
892
self._dragY = int(event.y)
895
gtk.TreeView.do_button_press_event(treeview, event)
897
treeview.grab_focus()
899
self._ignoreRelease = chain_up
903
def _treeViewMotionNotifyEventCb(self, treeview, event):
904
if not self._dragButton:
907
if self._nothingUnderMouse(treeview, event):
910
if treeview.drag_check_threshold(self._dragX, self._dragY,
911
int(event.x), int(event.y)):
912
context = treeview.drag_begin(
913
[dnd.URI_TUPLE, dnd.FILESOURCE_TUPLE],
917
self._dragStarted = True
920
def _treeViewButtonReleaseCb(self, treeview, event):
921
if event.button == self._dragButton:
922
self._dragButton = None
923
if (not self._ignoreRelease) and (not self._dragStarted):
924
treeview.get_selection().unselect_all()
925
result = treeview.get_path_at_pos(int(event.x), int(event.y))
928
treeview.get_selection().select_path(path)
931
def _viewSelectionChangedCb(self, unused):
932
if self._viewHasSelection():
933
self.selection_actions.set_sensitive(True)
935
self.selection_actions.set_sensitive(False)
937
def _rowActivatedCb(self, unused_treeview, path, unused_column):
938
factory = self.storemodel[path][COL_FACTORY]
939
self.emit('play', factory)
941
def _iconViewMotionNotifyEventCb(self, iconview, event):
942
if not self._dragButton:
945
if self._dragSelection:
948
if self._nothingUnderMouse(iconview, event):
951
if iconview.drag_check_threshold(self._dragX, self._dragY,
952
int(event.x), int(event.y)):
953
context = iconview.drag_begin(
954
[dnd.URI_TUPLE, dnd.FILESOURCE_TUPLE],
958
self._dragStarted = True
961
def _iconViewButtonPressEventCb(self, iconview, event):
964
if event.type == gtk.gdk._2BUTTON_PRESS:
965
self._playButtonClickedCb()
967
elif event.button == 3:
968
self._viewShowPopup(iconview, event)
971
if not event.state & (gtk.gdk.CONTROL_MASK | gtk.gdk.SHIFT_MASK):
972
chain_up = not self._rowUnderMouseSelected(iconview, event)
974
self._dragStarted = False
975
self._dragSelection = self._nothingUnderMouse(iconview, event)
976
self._dragButton = event.button
977
self._dragX = int(event.x)
978
self._dragY = int(event.y)
981
gtk.IconView.do_button_press_event(iconview, event)
983
iconview.grab_focus()
985
self._ignoreRelease = chain_up
989
def _iconViewButtonReleaseCb(self, iconview, event):
990
if event.button == self._dragButton:
991
self._dragButton = None
992
self._dragSelection = False
993
if (not self._ignoreRelease) and (not self._dragStarted):
994
iconview.unselect_all()
995
path = iconview.get_path_at_pos(int(event.x), int(event.y))
997
iconview.select_path(path)
1000
def _newProjectCreatedCb(self, app, project):
1001
self._resetErrorList()
1002
self.storemodel.clear()
1003
self._connectToProject(project)
1005
def _newProjectLoadedCb(self, unused_pitivi, project):
1008
def _newProjectFailedCb(self, unused_pitivi, unused_reason, unused_uri):
1009
self.storemodel.clear()
1010
self.project_signals.disconnectAll()
1013
def _dndDataReceivedCb(self, unused_widget, unused_context, unused_x,
1014
unused_y, selection, targettype, unused_time):
1015
def get_file_type(path):
1016
if path[:7] == "file://":
1017
if os.path.isfile(path[7:]):
1020
elif "://" in path: # we concider it is a remote file
1024
self.debug("targettype:%d, selection.data:%r", targettype, selection.data)
1026
if targettype == dnd.TYPE_URI_LIST:
1030
incoming = [unquote(x.strip('\x00')) for x in selection.data.strip().split("\r\n")
1033
filetype = get_file_type(x)
1034
if filetype == LOCAL_FILE:
1036
elif filetype == LOCAL_DIR:
1037
directories.append(x)
1038
elif filetype == REMOTE_FILE:
1039
remote_files.append(x)
1040
elif targettype == dnd.TYPE_TEXT_PLAIN:
1041
incoming = selection.data.strip()
1042
file_type = get_file_type(incoming)
1043
if file_type == LOCAL_FILE:
1044
filenames = [incoming]
1045
elif file_type == LOCAL_DIR:
1046
directories = [incoming]
1048
self.addFolders(directories)
1051
#TODO waiting for remote files downloader support to be implemented
1054
uris = [quote_uri(uri) for uri in filenames]
1055
self.app.current.sources.addUris(uris)
1057
#used with TreeView and IconView
1058
def _dndDragBeginCb(self, view, context):
1059
self.info("tree drag_begin")
1060
paths = self.getSelectedPaths()
1063
context.drag_abort(int(time.time()))
1065
row = self.storemodel[paths[0]]
1066
context.set_icon_pixbuf(row[COL_ICON], 0, 0)
1068
def getSelectedPaths(self):
1069
""" returns a list of selected items uri """
1070
if self.clip_view == SHOW_TREEVIEW:
1071
return self.getSelectedPathsTreeView()
1072
elif self.clip_view == SHOW_ICONVIEW:
1073
return self.getSelectedPathsIconView()
1075
def getSelectedPathsTreeView(self):
1076
model, rows = self.treeview.get_selection().get_selected_rows()
1079
def getSelectedPathsIconView(self):
1080
paths = self.iconview.get_selected_items()
1084
def getSelectedItems(self):
1085
return [self.storemodel[path][COL_URI]
1086
for path in self.getSelectedPaths()]
1088
def _dndDataGetCb(self, unused_widget, context, selection,
1089
targettype, unused_eventtime):
1090
self.info("data get, type:%d", targettype)
1091
uris = self.getSelectedItems()
1094
selection.set(selection.target, 8, '\n'.join(uris))
1095
context.set_icon_pixbuf(INVISIBLE, 0, 0)
1097
gobject.type_register(SourceList)