[pitivi/ges: 165/287] sourcelist: Remove ui/sourcelist and put everything into sourcelist.py
- From: Jean-FranÃois Fortin Tam <jfft src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [pitivi/ges: 165/287] sourcelist: Remove ui/sourcelist and put everything into sourcelist.py
- Date: Thu, 15 Mar 2012 16:39:42 +0000 (UTC)
commit 96ea43a8d748d42b0e0b14a7fb04afae0c609f40
Author: Thibault Saunier <thibault saunier collabora com>
Date: Mon Jan 9 15:54:20 2012 -0300
sourcelist: Remove ui/sourcelist and put everything into sourcelist.py
pitivi/sourcelist.py | 1112 +++++++++++++++++++++++++++++++++++++++++++++-
pitivi/ui/Makefile.am | 1 -
pitivi/ui/mainwindow.py | 5 +-
pitivi/ui/sourcelist.py | 1136 -----------------------------------------------
po/POTFILES.in | 3 +-
5 files changed, 1114 insertions(+), 1143 deletions(-)
---
diff --git a/pitivi/sourcelist.py b/pitivi/sourcelist.py
index 7fe8859..b639c06 100644
--- a/pitivi/sourcelist.py
+++ b/pitivi/sourcelist.py
@@ -24,12 +24,92 @@
Handles the list of source for a project
"""
-import urllib
import gst
+import ges
+import gobject
+import gtk
+import pango
+import os
+import time
+from urllib import unquote
+from gettext import gettext as _
+from hashlib import md5
+
+from pitivi.configure import get_pixmap_dir
+from pitivi.settings import GlobalSettings
+
+from pitivi.utils.misc import beautify_length
+from pitivi.utils.signal import SignalGroup
from pitivi.utils.signal import Signallable
from pitivi.utils.loggable import Loggable
+import pitivi.utils.ui as dnd
+from pitivi.ui.pathwalker import PathWalker, quote_uri
+from pitivi.ui.filechooserpreview import PreviewWidget
+from pitivi.ui.filelisterrordialog import FileListErrorDialog
+from pitivi.utils.ui import beautify_info, info_name, \
+ SPACING, PADDING
+
+SHOW_TREEVIEW = 1
+SHOW_ICONVIEW = 2
+
+GlobalSettings.addConfigSection('clip-library')
+GlobalSettings.addConfigOption('lastImportFolder',
+ section='clip-library',
+ key='last-folder',
+ environment='PITIVI_IMPORT_FOLDER',
+ default=os.path.expanduser("~"))
+GlobalSettings.addConfigOption('closeImportDialog',
+ section='clip-library',
+ key='close-import-dialog-after-import',
+ default=True)
+GlobalSettings.addConfigOption('lastClipView',
+ section='clip-library',
+ key='last-clip-view',
+ type_=int,
+ default=SHOW_ICONVIEW)
+
+(COL_ICON,
+ COL_ICON_LARGE,
+ COL_INFOTEXT,
+ COL_FACTORY,
+ COL_URI,
+ COL_LENGTH,
+ COL_SEARCH_TEXT,
+ COL_SHORT_TEXT) = range(8)
+
+(LOCAL_FILE,
+ LOCAL_DIR,
+ REMOTE_FILE,
+ NOT_A_FILE) = range(4)
+
+ui = '''
+<ui>
+ <menubar name="MainMenuBar">
+ <menu action="Library">
+ <placeholder name="SourceList" >
+ <menuitem action="ImportSources" />
+ <menuitem action="ImportSourcesFolder" />
+ <separator />
+ <menuitem action="SelectUnusedSources" />
+ <menuitem action="RemoveSources" />
+ <separator />
+ <menuitem action="InsertEnd" />
+ </placeholder>
+ </menu>
+ </menubar>
+ <toolbar name="MainToolBar">
+ <placeholder name="SourceList">
+ <toolitem action="ImportSources" />
+ </placeholder>
+ </toolbar>
+</ui>
+'''
+
+INVISIBLE = gtk.gdk.pixbuf_new_from_file(os.path.join(get_pixmap_dir(),
+ "invisible.png"))
+
class SourceListError(Exception):
pass
@@ -157,3 +237,1033 @@ class SourceList(Signallable, Loggable):
@return: A list of SourceFactory objects which must not be changed.
"""
return self._ordered_sources
+
+
+class SourceListWidget(gtk.VBox, Loggable):
+ """ Widget for listing sources """
+
+ __gsignals__ = {
+ 'play': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
+ (gobject.TYPE_PYOBJECT,))}
+
+ def __init__(self, instance, uiman):
+ gtk.VBox.__init__(self)
+ Loggable.__init__(self)
+
+ self.app = instance
+ self.settings = instance.settings
+ self._errors = []
+ self._project = None
+ self._sources_to_add = []
+ self.dummy_selected = []
+
+ # Store
+ # icon, infotext, objectfactory, uri, length
+ self.storemodel = gtk.ListStore(gtk.gdk.Pixbuf, gtk.gdk.Pixbuf,
+ str, object, str, str, str, str)
+
+ # Scrolled Windows
+ self.treeview_scrollwin = gtk.ScrolledWindow()
+ self.treeview_scrollwin.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
+ self.treeview_scrollwin.set_shadow_type(gtk.SHADOW_ETCHED_IN)
+
+ self.iconview_scrollwin = gtk.ScrolledWindow()
+ self.iconview_scrollwin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
+ self.iconview_scrollwin.set_shadow_type(gtk.SHADOW_ETCHED_IN)
+
+ # Popup Menu
+ self.popup = gtk.Menu()
+ self.popup_importitem = gtk.ImageMenuItem(_("Import Files..."))
+ image = gtk.Image()
+ image.set_from_stock(gtk.STOCK_ADD, gtk.ICON_SIZE_MENU)
+ self.popup_importitem.set_image(image)
+
+ self.popup_remitem = gtk.ImageMenuItem(_("Remove Clip"))
+ image = gtk.Image()
+ image.set_from_stock(gtk.STOCK_REMOVE, gtk.ICON_SIZE_MENU)
+ self.popup_remitem.set_image(image)
+ self.popup_playmenuitem = gtk.MenuItem(_("Play Clip"))
+ self.popup_importitem.connect("activate", self._importButtonClickedCb)
+ self.popup_remitem.connect("activate", self._removeButtonClickedCb)
+ self.popup_playmenuitem.connect("activate", self._playButtonClickedCb)
+ self.popup_importitem.show()
+ self.popup_remitem.show()
+ self.popup_playmenuitem.show()
+ self.popup.append(self.popup_importitem)
+ self.popup.append(self.popup_remitem)
+ self.popup.append(self.popup_playmenuitem)
+
+ # import sources dialogbox
+ self._importDialog = None
+
+ # Search/filter box
+ self.search_hbox = gtk.HBox()
+ self.search_hbox.set_spacing(SPACING)
+ self.search_hbox.set_border_width(3) # Prevents being flush against the notebook
+ searchLabel = gtk.Label(_("Search:"))
+ searchEntry = gtk.Entry()
+ searchEntry.set_icon_from_stock(gtk.ENTRY_ICON_SECONDARY, "gtk-clear")
+ searchEntry.connect("changed", self.searchEntryChangedCb)
+ searchEntry.connect("button-press-event", self.searchEntryActivateCb)
+ searchEntry.connect("focus-out-event", self.searchEntryDeactivateCb)
+ searchEntry.connect("icon-press", self.searchEntryIconClickedCb)
+ self.search_hbox.pack_start(searchLabel, expand=False)
+ self.search_hbox.pack_end(searchEntry, expand=True)
+ # Filtering model for the search box.
+ # Use this instead of using self.storemodel directly
+ self.modelFilter = self.storemodel.filter_new()
+ self.modelFilter.set_visible_func(self._setRowVisible, data=searchEntry)
+
+ # TreeView
+ # Displays icon, name, type, length
+ self.treeview = gtk.TreeView(self.modelFilter)
+ self.treeview_scrollwin.add(self.treeview)
+ self.treeview.connect("button-press-event", self._treeViewButtonPressEventCb)
+ self.treeview.connect("row-activated", self._rowActivatedCb)
+ self.treeview.set_property("rules_hint", True)
+ self.treeview.set_headers_visible(False)
+ self.treeview.set_property("search_column", COL_SEARCH_TEXT)
+ tsel = self.treeview.get_selection()
+ tsel.set_mode(gtk.SELECTION_MULTIPLE)
+ tsel.connect("changed", self._viewSelectionChangedCb)
+
+ pixbufcol = gtk.TreeViewColumn(_("Icon"))
+ pixbufcol.set_expand(False)
+ pixbufcol.set_spacing(SPACING)
+ self.treeview.append_column(pixbufcol)
+ pixcell = gtk.CellRendererPixbuf()
+ pixcell.props.xpad = 6
+ pixbufcol.pack_start(pixcell)
+ pixbufcol.add_attribute(pixcell, 'pixbuf', COL_ICON)
+
+ namecol = gtk.TreeViewColumn(_("Information"))
+ self.treeview.append_column(namecol)
+ namecol.set_expand(True)
+ namecol.set_spacing(SPACING)
+ namecol.set_sizing(gtk.TREE_VIEW_COLUMN_GROW_ONLY)
+ namecol.set_min_width(150)
+ txtcell = gtk.CellRendererText()
+ txtcell.set_property("ellipsize", pango.ELLIPSIZE_END)
+ namecol.pack_start(txtcell)
+ namecol.add_attribute(txtcell, "markup", COL_INFOTEXT)
+
+ namecol = gtk.TreeViewColumn(_("Duration"))
+ namecol.set_expand(False)
+ self.treeview.append_column(namecol)
+ txtcell = gtk.CellRendererText()
+ txtcell.set_property("yalign", 0.0)
+ namecol.pack_start(txtcell)
+ namecol.add_attribute(txtcell, "markup", COL_LENGTH)
+
+ # IconView
+ self.iconview = gtk.IconView(self.modelFilter)
+ self.iconview_scrollwin.add(self.iconview)
+ self.iconview.connect("button-press-event", self._iconViewButtonPressEventCb)
+ self.iconview.connect("selection-changed", self._viewSelectionChangedCb)
+ self.iconview.set_orientation(gtk.ORIENTATION_VERTICAL)
+ self.iconview.set_property("has_tooltip", True)
+ self.iconview.set_tooltip_column(COL_INFOTEXT)
+ self.iconview.set_text_column(COL_SHORT_TEXT)
+ self.iconview.set_pixbuf_column(COL_ICON_LARGE)
+ self.iconview.set_selection_mode(gtk.SELECTION_MULTIPLE)
+ self.iconview.set_item_width(138) # Needs to be icon width +10
+
+ # Explanatory message InfoBar
+ self.infobar = gtk.InfoBar()
+
+ txtlabel = gtk.Label()
+ txtlabel.set_padding(PADDING, PADDING)
+ txtlabel.set_line_wrap(True)
+ txtlabel.set_line_wrap_mode(pango.WRAP_WORD)
+ txtlabel.set_justify(gtk.JUSTIFY_CENTER)
+ txtlabel.set_text(
+ _('Add media to your project by dragging files and folders here or '
+ 'by using the "Import Files..." button.'))
+ self.infobar.add(txtlabel)
+ self.txtlabel = txtlabel
+
+ # The infobar that shows up if there are _errors when importing clips
+ self._import_warning_infobar = gtk.InfoBar()
+ self._import_warning_infobar.set_message_type(gtk.MESSAGE_WARNING)
+ content_area = self._import_warning_infobar.get_content_area()
+ actions_area = self._import_warning_infobar.get_action_area()
+ self._warning_label = gtk.Label()
+ self._warning_label.set_line_wrap(True)
+ self._warning_label.set_line_wrap_mode(pango.WRAP_WORD)
+ self._warning_label.set_justify(gtk.JUSTIFY_CENTER)
+ self._view_error_btn = gtk.Button()
+ self._hide_infobar_btn = gtk.Button()
+ self._hide_infobar_btn.set_label(_("Hide"))
+ self._view_error_btn.connect("clicked", self._viewErrorsButtonClickedCb)
+ self._hide_infobar_btn.connect("clicked", self._hideInfoBarClickedCb)
+ content_area.add(self._warning_label)
+ actions_area.add(self._view_error_btn)
+ actions_area.add(self._hide_infobar_btn)
+
+ # The _progressbar that shows up when importing clips
+ self._progressbar = gtk.ProgressBar()
+
+ # Connect to project. We must remove and reset the callbacks when
+ # changing project.
+ self.project_signals = SignalGroup()
+ self.app.connect("new-project-created", self._newProjectCreatedCb)
+ self.app.connect("new-project-loaded", self._newProjectLoadedCb)
+ self.app.connect("new-project-failed", self._newProjectFailedCb)
+
+ # default pixbufs
+ self.audiofilepixbuf = self._getIcon("audio-x-generic", "pitivi-sound.png")
+ self.videofilepixbuf = self._getIcon("video-x-generic", "pitivi-video.png")
+
+ # Drag and Drop
+ self.drag_dest_set(gtk.DEST_DEFAULT_DROP | gtk.DEST_DEFAULT_MOTION,
+ [dnd.URI_TUPLE, dnd.FILE_TUPLE],
+ gtk.gdk.ACTION_COPY)
+ self.connect("drag_data_received", self._dndDataReceivedCb)
+
+ self.treeview.drag_source_set(0, [], gtk.gdk.ACTION_COPY)
+ self.treeview.connect("motion-notify-event",
+ self._treeViewMotionNotifyEventCb)
+ self.treeview.connect("button-release-event",
+ self._treeViewButtonReleaseCb)
+ self.treeview.connect("drag_begin", self._dndDragBeginCb)
+ self.treeview.connect("drag_data_get", self._dndDataGetCb)
+
+ self.iconview.drag_source_set(0, [], gtk.gdk.ACTION_COPY)
+ self.iconview.connect("motion-notify-event",
+ self._iconViewMotionNotifyEventCb)
+ self.iconview.connect("button-release-event",
+ self._iconViewButtonReleaseCb)
+ self.iconview.connect("drag_begin", self._dndDragBeginCb)
+ self.iconview.connect("drag_data_get", self._dndDataGetCb)
+
+ # Hack so that the views have the same method as self
+ self.treeview.getSelectedItems = self.getSelectedItems
+
+ # always available
+ actions = (
+ ("ImportSources", gtk.STOCK_ADD, _("_Import Files..."),
+ None, _("Add media files to your project"),
+ self._importSourcesCb),
+ ("ImportSourcesFolder", gtk.STOCK_ADD,
+ _("Import _Folders..."), None,
+ _("Add the contents of a folder as clips in your project"),
+ self._importSourcesFolderCb),
+ ("SelectUnusedSources", None, _("Select Unused Media"), None,
+ _("Select clips that have not been used in the project"),
+ self._selectUnusedSourcesCb),
+ )
+
+ # only available when selection is non-empty
+ selection_actions = (
+ ("RemoveSources", gtk.STOCK_DELETE,
+ _("_Remove from Project"), "<Control>Delete", None,
+ self._removeSourcesCb),
+ ("InsertEnd", gtk.STOCK_COPY,
+ _("Insert at _End of Timeline"), "Insert", None,
+ self._insertEndCb),
+ )
+
+ actiongroup = gtk.ActionGroup("sourcelistpermanent")
+ actiongroup.add_actions(actions)
+ actiongroup.get_action("ImportSources").props.is_important = True
+ uiman.insert_action_group(actiongroup, 0)
+
+ self.selection_actions = gtk.ActionGroup("sourcelistselection")
+ self.selection_actions.add_actions(selection_actions)
+ self.selection_actions.set_sensitive(False)
+ uiman.insert_action_group(self.selection_actions, 0)
+ uiman.add_ui_from_string(ui)
+
+ # clip view menu items
+ view_menu_item = uiman.get_widget('/MainMenuBar/View')
+ view_menu = view_menu_item.get_submenu()
+ seperator = gtk.SeparatorMenuItem()
+ self.treeview_menuitem = gtk.RadioMenuItem(None,
+ _("Show Clips as a List"))
+ self.iconview_menuitem = gtk.RadioMenuItem(self.treeview_menuitem,
+ _("Show Clips as Icons"))
+
+ # update menu items with current clip view before we connect to item
+ # signals
+ if self.settings.lastClipView == SHOW_TREEVIEW:
+ self.treeview_menuitem.set_active(True)
+ self.iconview_menuitem.set_active(False)
+ else:
+ self.treeview_menuitem.set_active(False)
+ self.iconview_menuitem.set_active(True)
+
+ # we only need to connect to one menu item because we get a signal
+ # from each radio item in the group
+ self.treeview_menuitem.connect("toggled", self._treeViewMenuItemToggledCb)
+
+ view_menu.append(seperator)
+ view_menu.append(self.treeview_menuitem)
+ view_menu.append(self.iconview_menuitem)
+ self.treeview_menuitem.show()
+ self.iconview_menuitem.show()
+ seperator.show()
+
+ # add all child widgets
+ self.pack_start(self.infobar, expand=False, fill=False)
+ self.pack_start(self._import_warning_infobar, expand=False, fill=False)
+ self.pack_start(self.search_hbox, expand=False)
+ self.pack_start(self.iconview_scrollwin)
+ self.pack_start(self.treeview_scrollwin)
+ self.pack_start(self._progressbar, expand=False)
+
+ # display the help text
+ self.clip_view = self.settings.lastClipView
+ self._displayClipView()
+
+ def _importSourcesCb(self, unused_action):
+ self.showImportSourcesDialog()
+
+ def _importSourcesFolderCb(self, unused_action):
+ self.showImportSourcesDialog(True)
+
+ def _removeSourcesCb(self, unused_action):
+ self._removeSources()
+
+ def _selectUnusedSourcesCb(self, widget):
+ self._selectUnusedSources()
+
+ def _insertEndCb(self, unused_action):
+ self.app.action_log.begin("add clip")
+ self.app.current.timeline.enable_update(False)
+
+ # Handle the case of a blank project
+ self.app.gui.timeline._ensureLayer()
+
+ self._sources_to_add = self.getSelectedItems()
+ # Start adding sources in the timeline
+ self._addNextSource()
+
+ def _addNextSource(self):
+ """ Insert a source at the end of the timeline's first track """
+ timeline = self.app.current.timeline
+
+ if not self._sources_to_add:
+ # OK, we added all the sources!
+ timeline.enable_update(True)
+ self.app.current.seeker.seek(timeline.props.duration)
+ self.app.action_log.commit()
+ return
+
+ uri = self._sources_to_add.pop()
+ source = ges.TimelineFileSource(uri)
+ layer = timeline.get_layers()[0] # FIXME Get the longest layer
+ layer.add_object(source)
+
+ # Waiting for the TrackObject to be created because of a race
+ # condition, and to know the real length of the timeline when
+ # adding several sources at a time.
+ source.connect("track-object-added", self._trackObjectAddedCb)
+
+ def _trackObjectAddedCb(self, source, trackobj):
+ """ After an object has been added to the first track, position it
+ correctly and request the next source to be processed. """
+ timeline = self.app.current.timeline
+ layer = timeline.get_layers()[0] # FIXME Get the longest layer
+
+ # Handle the case where we just inserted the first clip
+ if len(layer.get_objects()) == 1:
+ source.props.start = 0
+ else:
+ source.props.start = timeline.props.duration
+
+ # We only need one TrackObject to estimate the new duration.
+ # Process the next source.
+ source.disconnect_by_func(self._trackObjectAddedCb)
+ self._addNextSource()
+
+ def searchEntryChangedCb(self, entry):
+ self.modelFilter.refilter()
+
+ def searchEntryIconClickedCb(self, entry, unused, unsed1):
+ entry.set_text("")
+
+ def searchEntryDeactivateCb(self, entry, event):
+ self.app.gui.setActionsSensitive("default", True)
+
+ def searchEntryActivateCb(self, entry, event):
+ self.app.gui.setActionsSensitive("default", False)
+
+ def _setRowVisible(self, model, iter, data):
+ """
+ Toggle the visibility of a liststore row.
+ Used for the search box.
+ """
+ text = data.get_text().lower()
+ if text == "":
+ return True # Avoid silly warnings
+ else:
+ return text in model.get_value(iter, COL_INFOTEXT).lower()
+
+ def _getIcon(self, iconname, alternate):
+ icontheme = gtk.icon_theme_get_default()
+ pixdir = get_pixmap_dir()
+ icon = None
+ try:
+ icon = icontheme.load_icon(iconname, 32, 0)
+ except:
+ # empty except clause is bad but load_icon raises gio.Error.
+ # Right, *gio*.
+ if not icon:
+ icon = gtk.gdk.pixbuf_new_from_file(os.path.join(pixdir, alternate))
+ return icon
+
+ def _connectToProject(self, project):
+ """Connect signal handlers to a project.
+
+ This first disconnects any handlers connected to an old project.
+ If project is None, this just disconnects any connected handlers.
+
+ """
+ self.project_signals.connect(
+ project.sources, "source-added", None, self._sourceAddedCb)
+ self.project_signals.connect(
+ project.sources, "source-removed", None, self._sourceRemovedCb)
+ self.project_signals.connect(
+ project.sources, "discovery-error", None, self._discoveryErrorCb)
+ self.project_signals.connect(
+ project.sources, "ready", None, self._sourcesStoppedImportingCb)
+ self.project_signals.connect(
+ project.sources, "starting", None, self._sourcesStartedImportingCb)
+
+ def _setClipView(self, show):
+ """ Set which clip view to use when sourcelist is showing clips. If
+ none is given, the current one is used. Show: one of SHOW_TREEVIEW or
+ SHOW_ICONVIEW """
+
+ # save current selection
+ paths = self.getSelectedPaths()
+
+ # update saved clip view
+ self.settings.lastClipView = show
+ self.clip_view = show
+
+ # transfer selection to next view
+ self._viewUnselectAll()
+ for path in paths:
+ self._viewSelectPath(path)
+
+ self._displayClipView()
+
+ def _displayClipView(self):
+
+ # first hide all the child widgets
+ self.treeview_scrollwin.hide()
+ self.iconview_scrollwin.hide()
+
+ # pick the widget we're actually showing
+ if self.clip_view == SHOW_TREEVIEW:
+ self.debug("displaying tree view")
+ widget = self.treeview_scrollwin
+ elif self.clip_view == SHOW_ICONVIEW:
+ self.debug("displaying icon view")
+ widget = self.iconview_scrollwin
+
+ if not len(self.storemodel):
+ self._displayHelpText()
+
+ # now un-hide the view
+ widget.show_all()
+
+ def _displayHelpText(self):
+ """Display the InfoBar help message"""
+ self.infobar.hide_all()
+ self.txtlabel.show()
+ self.infobar.show()
+
+ def showImportSourcesDialog(self, select_folders=False):
+ """Pop up the "Import Sources" dialog box"""
+ if self._importDialog:
+ return
+
+ if select_folders:
+ chooser_action = gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER
+ dialogtitle = _("Select One or More Folders")
+ else:
+ chooser_action = gtk.FILE_CHOOSER_ACTION_OPEN
+ dialogtitle = _("Select One or More Files")
+ close_after = gtk.CheckButton(_("Close after importing files"))
+ close_after.set_active(self.app.settings.closeImportDialog)
+
+ self._importDialog = gtk.FileChooserDialog(dialogtitle, None,
+ chooser_action,
+ (gtk.STOCK_CLOSE, gtk.RESPONSE_CLOSE,
+ gtk.STOCK_ADD, gtk.RESPONSE_OK))
+ self._importDialog.set_icon_name("pitivi")
+ self._importDialog.props.extra_widget = close_after
+ self._importDialog.set_default_response(gtk.RESPONSE_OK)
+ self._importDialog.set_select_multiple(True)
+ self._importDialog.set_modal(False)
+ pw = PreviewWidget(self.app)
+ self._importDialog.set_preview_widget(pw)
+ self._importDialog.set_use_preview_label(False)
+ self._importDialog.connect('update-preview', pw.add_preview_request)
+ self._importDialog.set_current_folder(self.app.settings.lastImportFolder)
+
+ self._importDialog.connect('response', self._dialogBoxResponseCb, select_folders)
+ self._importDialog.connect('close', self._dialogBoxCloseCb)
+ self._importDialog.show()
+
+ def addFolders(self, folders):
+ """ walks the trees of the folders in the list and adds the files it finds """
+ self.app.threads.addThread(PathWalker, folders, self.app.current.sources.addUris)
+
+ def _updateProgressbar(self):
+ """
+ Update the _progressbar with the ratio of clips imported vs the total
+ """
+ current_clip_iter = self.app.current.sources.nb_imported_files
+ total_clips = self.app.current.sources.nb_file_to_import
+ progressbar_text = _("Importing clip %(current_clip)d of %(total)d" %
+ {"current_clip": current_clip_iter,
+ "total": total_clips})
+ self._progressbar.set_text(progressbar_text)
+ if current_clip_iter == 0:
+ self._progressbar.set_fraction(0.0)
+ elif total_clips != 0:
+ self._progressbar.set_fraction((current_clip_iter - 1) / float(total_clips))
+
+ def _addDiscovererInfo(self, info):
+ # The code below tries to read existing thumbnails from the freedesktop
+ # thumbnails directory (~/.thumbnails). The filenames are simply
+ # the file URI hashed with md5, so we can retrieve them easily.
+ if [i for i in info.get_stream_list() if\
+ isinstance(i, gst.pbutils.DiscovererVideoInfo)]:
+ thumbnail_hash = md5(info.get_uri()).hexdigest()
+ thumb_dir = os.path.expanduser("~/.thumbnails/")
+ thumb_path_normal = thumb_dir + "normal/" + thumbnail_hash + ".png"
+ thumb_path_large = thumb_dir + "large/" + thumbnail_hash + ".png"
+ # Pitivi used to consider 64 pixels as normal and 96 as large
+ # However, the fdo spec specifies 128 as normal and 256 as large.
+ # We will thus simply use the "normal" size and scale it down.
+ try:
+ thumbnail = gtk.gdk.pixbuf_new_from_file(thumb_path_normal)
+ thumbnail_large = thumbnail
+ thumbnail_height = int(thumbnail.get_height() / 2)
+ thumbnail = thumbnail.scale_simple(64, thumbnail_height, \
+ gtk.gdk.INTERP_BILINEAR)
+ except:
+ # TODO gst discoverer should create missing thumbnails.
+ thumbnail = self.videofilepixbuf
+ thumbnail_large = thumbnail
+ else:
+ thumbnail = self.audiofilepixbuf
+ thumbnail_large = self.audiofilepixbuf
+
+ if info.get_duration() == gst.CLOCK_TIME_NONE:
+ duration = ''
+ else:
+ duration = beautify_length(info.get_duration())
+
+ short_text = None
+ uni = unicode(info_name(info), 'utf-8')
+
+ if len(uni) > 34:
+ short_uni = uni[0:29]
+ short_uni += unicode('...')
+ short_text = short_uni.encode('utf-8')
+ else:
+ short_text = info_name(info)
+
+ self.storemodel.append([thumbnail,
+ thumbnail_large,
+ beautify_info(info),
+ info,
+ info.get_uri(),
+ duration,
+ info_name(info),
+ short_text])
+ self._displayClipView()
+
+ # sourcelist callbacks
+
+ def _sourceAddedCb(self, sourcelist, factory):
+ """ a file was added to the sourcelist """
+ self._updateProgressbar()
+ self._addDiscovererInfo(factory)
+ if len(self.storemodel):
+ self.infobar.hide_all()
+ self.search_hbox.show_all()
+
+ def _sourceRemovedCb(self, sourcelist, uri, factory):
+ """ the given uri was removed from the sourcelist """
+ # find the good line in the storemodel and remove it
+ model = self.storemodel
+ for row in model:
+ if uri == row[COL_URI]:
+ model.remove(row.iter)
+ break
+ if not len(model):
+ self._displayHelpText()
+ self.search_hbox.hide()
+ self.debug("Removing %s", uri)
+
+ def _discoveryErrorCb(self, unused_sourcelist, uri, reason, extra):
+ """ The given uri isn't a media file """
+ error = (uri, reason, extra)
+ self._errors.append(error)
+
+ def _sourcesStartedImportingCb(self, sourcelist):
+ self._progressbar.show()
+ self._updateProgressbar()
+
+ def _sourcesStoppedImportingCb(self, unused_sourcelist):
+ self._progressbar.hide()
+ if self._errors:
+ if len(self._errors) > 1:
+ self._warning_label.set_text(_("Errors occurred while importing."))
+ self._view_error_btn.set_label(_("View errors"))
+ else:
+ self._warning_label.set_text(_("An error occurred while importing."))
+ self._view_error_btn.set_label(_("View error"))
+
+ self._import_warning_infobar.show_all()
+
+ ## Error Dialog Box callbacks
+
+ def _errorDialogBoxCloseCb(self, unused_dialog):
+ self._error_dialogbox.destroy()
+ self._error_dialogbox = None
+
+ def _errorDialogBoxResponseCb(self, unused_dialog, unused_response):
+ self._error_dialogbox.destroy()
+ self._error_dialogbox = None
+
+ ## Import Sources Dialog Box callbacks
+
+ def _dialogBoxResponseCb(self, dialogbox, response, select_folders):
+ self.debug("response:%r", response)
+ if response == gtk.RESPONSE_OK:
+ lastfolder = dialogbox.get_current_folder()
+ self.app.settings.lastImportFolder = lastfolder
+ self.app.settings.closeImportDialog = \
+ dialogbox.props.extra_widget.get_active()
+ filenames = dialogbox.get_uris()
+ if select_folders:
+ self.addFolders(filenames)
+ else:
+ self.app.current.sources.addUris(filenames)
+ if self.app.settings.closeImportDialog:
+ dialogbox.destroy()
+ self._importDialog = None
+ else:
+ dialogbox.destroy()
+ self._importDialog = None
+
+ def _dialogBoxCloseCb(self, unused_dialogbox):
+ self.debug("closing")
+ self._importDialog = None
+
+ def _removeSources(self):
+ model = self.storemodel
+ paths = self.getSelectedPaths()
+ if paths == None or paths < 1:
+ return
+ # use row references so we don't have to care if a path has been removed
+ rows = []
+ for path in paths:
+ row = gtk.TreeRowReference(model, path)
+ rows.append(row)
+
+ self.app.action_log.begin("remove clip from source list")
+ for row in rows:
+ uri = model[row.get_path()][COL_URI]
+ self.app.current.sources.removeUri(uri)
+ self.app.action_log.commit()
+
+ def _selectUnusedSources(self):
+ """
+ Select, in the media library, unused sources in the project.
+ """
+ sources = self.app.current.sources.getSources()
+ unused_sources_uris = []
+
+ model = self.storemodel
+ selection = self.treeview.get_selection()
+ for source in sources:
+ if not self.app.current.timeline.usesFactory(source):
+ unused_sources_uris.append(source.uri)
+
+ # Hack around the fact that making selections (in a treeview/iconview)
+ # deselects what was previously selected
+ if self.clip_view == SHOW_TREEVIEW:
+ self.treeview.get_selection().select_all()
+ elif self.clip_view == SHOW_ICONVIEW:
+ self.iconview.select_all()
+
+ for row in model:
+ if row[COL_URI] not in unused_sources_uris:
+ if self.clip_view == SHOW_TREEVIEW:
+ selection.unselect_iter(row.iter)
+ else:
+ self.iconview.unselect_path(row.path)
+
+ ## UI Button callbacks
+
+ def _importButtonClickedCb(self, unused_widget=None):
+ """ Called when a user clicks on the import button """
+ self.showImportSourcesDialog()
+
+ def _removeButtonClickedCb(self, unused_widget=None):
+ """ Called when a user clicks on the remove button """
+ self._removeSources()
+
+ def _playButtonClickedCb(self, unused_widget=None):
+ """ Called when a user clicks on the play button """
+ # get the selected filesourcefactory
+ paths = self.getSelectedPaths()
+ model = self.storemodel
+ if len(paths) < 1:
+ return
+ paths = paths[0]
+ self.debug("Let's play %s", model[paths][COL_URI])
+ self.emit('play', model[paths][COL_URI])
+
+ def _hideInfoBarClickedCb(self, unused_button):
+ self._resetErrorList()
+
+ def _resetErrorList(self):
+ self._errors = []
+ self._import_warning_infobar.hide()
+
+ def _viewErrorsButtonClickedCb(self, unused_button):
+ """
+ Show a FileListErrorDialog to display import _errors.
+ """
+ if len(self._errors) > 1:
+ msgs = (_("Error while analyzing files"),
+ _("The following files can not be used with PiTiVi."))
+ else:
+ msgs = (_("Error while analyzing a file"),
+ _("The following file can not be used with PiTiVi."))
+ self._error_dialogbox = FileListErrorDialog(*msgs)
+ self._error_dialogbox.connect("close", self._errorDialogBoxCloseCb)
+ self._error_dialogbox.connect("response", self._errorDialogBoxResponseCb)
+ for uri, reason, extra in self._errors:
+ self._error_dialogbox.addFailedFile(uri, reason, extra)
+ self._error_dialogbox.window.show()
+ # Reset the error list, since the user has read them.
+ self._resetErrorList()
+
+ def _treeViewMenuItemToggledCb(self, unused_widget):
+ if self.treeview_menuitem.get_active():
+ show = SHOW_TREEVIEW
+ else:
+ show = SHOW_ICONVIEW
+ self._setClipView(show)
+
+ _dragStarted = False
+ _dragSelection = False
+ _dragButton = None
+ _dragX = 0
+ _dragY = 0
+ _ignoreRelease = False
+
+ def _rowUnderMouseSelected(self, view, event):
+ result = view.get_path_at_pos(int(event.x), int(event.y))
+ if result:
+ path = result[0]
+ if isinstance(view, gtk.TreeView):
+ selection = view.get_selection()
+
+ return selection.path_is_selected(path) and selection.count_selected_rows() > 0
+ elif isinstance(view, gtk.IconView):
+ selection = view.get_selected_items()
+
+ return view.path_is_selected(path) and len(selection)
+ else:
+ assert False
+
+ return False
+
+ def _nothingUnderMouse(self, view, event):
+ return not bool(view.get_path_at_pos(int(event.x), int(event.y)))
+
+ def _viewShowPopup(self, view, event):
+ if view != None and self._rowUnderMouseSelected(view, event):
+ self.popup_remitem.set_sensitive(True)
+ self.popup_playmenuitem.set_sensitive(True)
+ elif view != None and (not self._nothingUnderMouse(view, event)):
+ if not event.state & (gtk.gdk.CONTROL_MASK | gtk.gdk.SHIFT_MASK):
+ self._viewUnselectAll()
+ elif self.clip_view == SHOW_TREEVIEW and self._viewHasSelection() \
+ and (event.state & gtk.gdk.SHIFT_MASK):
+ selection = self.treeview.get_selection()
+ start_path = self._viewGetFirstSelected()
+ end_path = self._viewGetPathAtPos(event)
+ self._viewUnselectAll()
+ selection.select_range(start_path, end_path)
+
+ self._viewSelectPath(self._viewGetPathAtPos(event))
+ self.popup_remitem.set_sensitive(True)
+ self.popup_playmenuitem.set_sensitive(True)
+ else:
+ self.popup_remitem.set_sensitive(False)
+ self.popup_playmenuitem.set_sensitive(False)
+
+ self.popup.popup(None, None, None, event.button, event.time)
+
+ def _viewGetFirstSelected(self):
+ paths = self.getSelectedPaths()
+ return paths[0]
+
+ def _viewHasSelection(self):
+ paths = self.getSelectedPaths()
+ return bool(len(paths))
+
+ def _viewGetPathAtPos(self, event):
+ if self.clip_view == SHOW_TREEVIEW:
+ pathinfo = self.treeview.get_path_at_pos(int(event.x), int(event.y))
+ return pathinfo[0]
+ elif self.clip_view == SHOW_ICONVIEW:
+ return self.iconview.get_path_at_pos(int(event.x), int(event.y))
+
+ def _viewSelectPath(self, path):
+ if self.clip_view == SHOW_TREEVIEW:
+ selection = self.treeview.get_selection()
+ selection.select_path(path)
+ elif self.clip_view == SHOW_ICONVIEW:
+ self.iconview.select_path(path)
+
+ def _viewUnselectAll(self):
+ if self.clip_view == SHOW_TREEVIEW:
+ selection = self.treeview.get_selection()
+ selection.unselect_all()
+ elif self.clip_view == SHOW_ICONVIEW:
+ self.iconview.unselect_all()
+
+ def _treeViewButtonPressEventCb(self, treeview, event):
+ chain_up = True
+
+ if event.type == gtk.gdk._2BUTTON_PRESS:
+ self._playButtonClickedCb()
+ chain_up = False
+ elif event.button == 3:
+ self._viewShowPopup(treeview, event)
+ chain_up = False
+
+ else:
+
+ if not event.state & (gtk.gdk.CONTROL_MASK | gtk.gdk.SHIFT_MASK):
+ chain_up = not self._rowUnderMouseSelected(treeview, event)
+
+ self._dragStarted = False
+ self._dragSelection = False
+ self._dragButton = event.button
+ self._dragX = int(event.x)
+ self._dragY = int(event.y)
+
+ if chain_up:
+ gtk.TreeView.do_button_press_event(treeview, event)
+ else:
+ treeview.grab_focus()
+
+ self._ignoreRelease = chain_up
+
+ return True
+
+ def _treeViewMotionNotifyEventCb(self, treeview, event):
+ if not self._dragButton:
+ return True
+
+ if self._nothingUnderMouse(treeview, event):
+ return True
+
+ if treeview.drag_check_threshold(self._dragX, self._dragY,
+ int(event.x), int(event.y)):
+ context = treeview.drag_begin(
+ [dnd.URI_TUPLE, dnd.FILESOURCE_TUPLE],
+ gtk.gdk.ACTION_COPY,
+ self._dragButton,
+ event)
+ self._dragStarted = True
+ return False
+
+ def _treeViewButtonReleaseCb(self, treeview, event):
+ if event.button == self._dragButton:
+ self._dragButton = None
+ if (not self._ignoreRelease) and (not self._dragStarted):
+ treeview.get_selection().unselect_all()
+ result = treeview.get_path_at_pos(int(event.x), int(event.y))
+ if result:
+ path = result[0]
+ treeview.get_selection().select_path(path)
+ return False
+
+ def _viewSelectionChangedCb(self, unused):
+ if self._viewHasSelection():
+ self.selection_actions.set_sensitive(True)
+ else:
+ self.selection_actions.set_sensitive(False)
+
+ def _rowActivatedCb(self, unused_treeview, path, unused_column):
+ path = self.storemodel[path][COL_URI]
+ self.emit('play', path)
+
+ def _iconViewMotionNotifyEventCb(self, iconview, event):
+ if not self._dragButton:
+ return True
+
+ if self._dragSelection:
+ return False
+
+ if self._nothingUnderMouse(iconview, event):
+ return True
+
+ if iconview.drag_check_threshold(self._dragX, self._dragY,
+ int(event.x), int(event.y)):
+ context = iconview.drag_begin(
+ [dnd.URI_TUPLE, dnd.FILESOURCE_TUPLE],
+ gtk.gdk.ACTION_COPY,
+ self._dragButton,
+ event)
+ self._dragStarted = True
+ return False
+
+ def _iconViewButtonPressEventCb(self, iconview, event):
+ chain_up = True
+
+ if event.type == gtk.gdk._2BUTTON_PRESS:
+ self._playButtonClickedCb()
+ chain_up = False
+ elif event.button == 3:
+ self._viewShowPopup(iconview, event)
+ chain_up = False
+ else:
+ if not event.state & (gtk.gdk.CONTROL_MASK | gtk.gdk.SHIFT_MASK):
+ chain_up = not self._rowUnderMouseSelected(iconview, event)
+
+ self._dragStarted = False
+ self._dragSelection = self._nothingUnderMouse(iconview, event)
+ self._dragButton = event.button
+ self._dragX = int(event.x)
+ self._dragY = int(event.y)
+
+ if chain_up:
+ gtk.IconView.do_button_press_event(iconview, event)
+ else:
+ iconview.grab_focus()
+
+ self._ignoreRelease = chain_up
+
+ return True
+
+ def _iconViewButtonReleaseCb(self, iconview, event):
+ if event.button == self._dragButton:
+ self._dragButton = None
+ self._dragSelection = False
+ if (not self._ignoreRelease) and (not self._dragStarted):
+ iconview.unselect_all()
+ path = iconview.get_path_at_pos(int(event.x), int(event.y))
+ if path:
+ iconview.select_path(path)
+ return False
+
+ def _newProjectCreatedCb(self, app, project):
+ if not self._project is project:
+ self._project = project
+ self._resetErrorList()
+ self.storemodel.clear()
+ self._connectToProject(project)
+
+ def _newProjectLoadedCb(self, unused_pitivi, project):
+ if not self._project is project:
+ self._project = project
+ self.storemodel.clear()
+ self._connectToProject(project)
+
+ def _newProjectFailedCb(self, unused_pitivi, unused_reason, unused_uri):
+ self.storemodel.clear()
+ self.project_signals.disconnectAll()
+ self._project = None
+
+ ## Drag and Drop
+ def _dndDataReceivedCb(self, unused_widget, unused_context, unused_x,
+ unused_y, selection, targettype, unused_time):
+ def get_file_type(path):
+ if path[:7] == "file://":
+ if os.path.isfile(path[7:]):
+ return LOCAL_FILE
+ return LOCAL_DIR
+ elif "://" in path: # we concider it is a remote file
+ return REMOTE_FILE
+ return NOT_A_FILE
+
+ self.debug("targettype:%d, selection.data:%r", targettype, selection.data)
+ directories = []
+ if targettype == dnd.TYPE_URI_LIST:
+ filenames = []
+ directories = []
+ remote_files = []
+ incoming = [unquote(x.strip('\x00')) for x in selection.data.strip().split("\r\n")
+ if x.strip('\x00')]
+ for x in incoming:
+ filetype = get_file_type(x)
+ if filetype == LOCAL_FILE:
+ filenames.append(x)
+ elif filetype == LOCAL_DIR:
+ directories.append(x)
+ elif filetype == REMOTE_FILE:
+ remote_files.append(x)
+ elif targettype == dnd.TYPE_TEXT_PLAIN:
+ incoming = selection.data.strip()
+ file_type = get_file_type(incoming)
+ if file_type == LOCAL_FILE:
+ filenames = [incoming]
+ elif file_type == LOCAL_DIR:
+ directories = [incoming]
+ if directories:
+ self.addFolders(directories)
+
+ if remote_files:
+ #TODO waiting for remote files downloader support to be implemented
+ pass
+
+ uris = [quote_uri(uri) for uri in filenames]
+ self.app.current.sources.addUris(uris)
+
+ #used with TreeView and IconView
+ def _dndDragBeginCb(self, view, context):
+ self.info("tree drag_begin")
+ paths = self.getSelectedPaths()
+
+ if len(paths) < 1:
+ context.drag_abort(int(time.time()))
+ else:
+ row = self.storemodel[paths[0]]
+ context.set_icon_pixbuf(row[COL_ICON], 0, 0)
+
+ def getSelectedPaths(self):
+ """ returns a list of selected items uri """
+ if self.clip_view == SHOW_TREEVIEW:
+ return self.getSelectedPathsTreeView()
+ elif self.clip_view == SHOW_ICONVIEW:
+ return self.getSelectedPathsIconView()
+
+ def getSelectedPathsTreeView(self):
+ model, rows = self.treeview.get_selection().get_selected_rows()
+ return rows
+
+ def getSelectedPathsIconView(self):
+ paths = self.iconview.get_selected_items()
+ paths.reverse()
+ return paths
+
+ def getSelectedItems(self):
+ return [self.storemodel[path][COL_URI]
+ for path in self.getSelectedPaths()]
+
+ def _dndDataGetCb(self, unused_widget, context, selection,
+ targettype, unused_eventtime):
+ self.info("data get, type:%d", targettype)
+ uris = self.getSelectedItems()
+ if len(uris) < 1:
+ return
+ selection.set(selection.target, 8, '\n'.join(uris))
+ context.set_icon_pixbuf(INVISIBLE, 0, 0)
+
+gobject.type_register(SourceListWidget)
diff --git a/pitivi/ui/Makefile.am b/pitivi/ui/Makefile.am
index 4717a9d..285b438 100644
--- a/pitivi/ui/Makefile.am
+++ b/pitivi/ui/Makefile.am
@@ -22,7 +22,6 @@ ui_PYTHON = \
ripple_update_group.py \
basetabs.py \
ruler.py \
- sourcelist.py \
startupwizard.py \
timelinecanvas.py \
timelinecontrols.py \
diff --git a/pitivi/ui/mainwindow.py b/pitivi/ui/mainwindow.py
index 94e2f7b..1123f70 100644
--- a/pitivi/ui/mainwindow.py
+++ b/pitivi/ui/mainwindow.py
@@ -37,7 +37,6 @@ from gtk import RecentManager
from pitivi.utils.loggable import Loggable
from pitivi.settings import GlobalSettings
-from pitivi.sourcelist import SourceListError
from pitivi.utils.misc import show_user_manual
from pitivi.utils.ui import SPACING, info_name, FILESOURCE_TUPLE, URI_TUPLE, \
@@ -46,7 +45,7 @@ from pitivi.utils.ui import SPACING, info_name, FILESOURCE_TUPLE, URI_TUPLE, \
from pitivi.ui.timeline import Timeline
from pitivi.ui.basetabs import BaseTabs
from pitivi.ui.viewer import PitiviViewer
-from pitivi.ui.sourcelist import SourceList
+from pitivi.sourcelist import SourceListWidget, SourceListError
from pitivi.ui.effectlist import EffectList
from pitivi.ui.zoominterface import Zoomable
from pitivi.ui.clipproperties import ClipProperties
@@ -376,7 +375,7 @@ class PitiviMainWindow(gtk.Window, Loggable):
self.projecttabs = BaseTabs(instance)
- self.sourcelist = SourceList(instance, self.uimanager)
+ self.sourcelist = SourceListWidget(instance, self.uimanager)
self.projecttabs.append_page(self.sourcelist, gtk.Label(_("Media Library")))
self._connectToSourceList()
self.sourcelist.show()
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 24716d8..9f682f8 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -18,7 +18,7 @@ pitivi/effects.py
pitivi/projectmanager.py
pitivi/project.py
pitivi/settings.py
-pitivi/ui/alignmentprogress.py
+pitivi/sourcelist.py
pitivi/ui/basetabs.py
pitivi/ui/clipproperties.py
pitivi/ui/dynamic.py
@@ -33,7 +33,6 @@ pitivi/ui/mainwindow.py
pitivi/ui/prefs.py
pitivi/ui/previewer.py
pitivi/ui/projectsettings.py
-pitivi/ui/sourcelist.py
pitivi/ui/startupwizard.py
pitivi/ui/timelinecanvas.py
pitivi/ui/timelinecontrols.py
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]