[pitivi/ges: 165/287] sourcelist: Remove ui/sourcelist and put everything into sourcelist.py



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]