def _questionButtonClickedCb(self, unused_button): msgs = (_("Error List"), _("The following errors have been reported:")) # show error dialog dbox = FileListErrorDialog(*msgs) dbox.connect("close", self._errorDialogBoxCloseCb) dbox.connect("response", self._errorDialogBoxResponseCb) for reason, extra in self.errors: dbox.addFailedFile(None, reason, extra) dbox.show() # reset error list self.errors = [] self.hide()
def _questionButtonClickedCb(self, unused_button): 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.")) # show error dialog dbox = FileListErrorDialog(*msgs) dbox.connect("close", self._errorDialogBoxCloseCb) dbox.connect("response", self._errorDialogBoxResponseCb) for uri, reason, extra in self.errors: dbox.addFailedFile(uri, reason, extra) dbox.show() # reset error list self.errors = [] self.hide() self.emit("remove-me")
class SourceList(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 = [] # 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 clips...")) 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(106) # 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_markup( _("<span>Import your clips by dragging them here or " "by using the buttons above.</span>")) 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 clips..."), None, _("Import clips to use"), self._importSourcesCb), ("ImportSourcesFolder", gtk.STOCK_ADD, _("Import _folder of clips..."), None, _("Import folder of clips to use"), self._importSourcesFolderCb), ) # 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 _insertEndCb(self, unused_action): self.app.action_log.begin("add clip") timeline = self.app.current.timeline sources = self.app.current.sources start = timeline.duration self.app.current.seeker.seek(start) for uri in self.getSelectedItems(): factory = sources.getUri(uri) source = timeline.addSourceFactory(factory) source.setStart(start) start += source.duration self.app.action_log.commit() 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, "missing-plugins", None, self._missingPluginsCb) 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 = _("Import a folder") else: chooser_action = gtk.FILE_CHOOSER_ACTION_OPEN dialogtitle = _("Import a clip") 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 addUris(self, files): """ Add files to the list """ try: self.app.current.sources.addUris(files) except SourceListError as error: disclaimer, uri = error.args self.error("'%s' is already present in the source list." + uri) 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 _addFactory(self, factory): video = factory.getOutputStreams(VideoStream) if video and video[0].thumbnail: thumbnail_file = video[0].thumbnail try: self.debug("attempting to open thumbnail file '%s'", thumbnail_file) pixbuf = gtk.gdk.pixbuf_new_from_file(thumbnail_file) except: self.error("Failure to create thumbnail from file '%s'", thumbnail_file) thumbnail = self.videofilepixbuf thumbnail_large = self.videofilepixbuf else: desiredheight = int(64 / float(video[0].dar)) thumbnail = pixbuf.scale_simple(64, desiredheight, gtk.gdk.INTERP_BILINEAR) desiredheight = int(96 / float(video[0].dar)) thumbnail_large = pixbuf.scale_simple(96, desiredheight, gtk.gdk.INTERP_BILINEAR) else: if video: thumbnail = self.videofilepixbuf thumbnail_large = self.videofilepixbuf else: thumbnail = self.audiofilepixbuf thumbnail_large = self.audiofilepixbuf if not factory.duration or factory.duration == gst.CLOCK_TIME_NONE: duration = '' else: duration = beautify_length(factory.duration) short_text = None uni = unicode(factory_name(factory), 'utf-8') if len(uni) > 34: short_uni = uni[0:29] short_uni += unicode('...') short_text = short_uni.encode('utf-8') else: short_text = factory_name(factory) self.storemodel.append([ thumbnail, thumbnail_large, beautify_factory(factory), factory, factory.uri, duration, factory_name(factory), short_text ]) self._displayClipView() # sourcelist callbacks def _sourceAddedCb(self, sourcelist, factory): """ a file was added to the sourcelist """ self._updateProgressbar() self._addFactory(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() 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 _missingPluginsCb(self, sourcelist, uri, factory, details, descriptions, cb): error = (uri, "Missing plugins", "\n".join(descriptions)) 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 occured while importing.")) self._view_error_btn.set_label(_("View errors")) else: self._warning_label.set_text( _("An error occured 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.window.destroy() self._error_dialogbox = None def _errorDialogBoxResponseCb(self, unused_dialog, unused_response): self._error_dialogbox.window.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.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() ## 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 path = paths[0] factory = model[path][COL_FACTORY] self.debug("Let's play %s", factory.uri) self.emit('play', factory) def _hideInfoBarClickedCb(self, unused_button): 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() self._errors = [ ] # Reset the error list (since the user has read them) self._import_warning_infobar.hide() 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): factory = self.storemodel[path][COL_FACTORY] self.emit('play', factory) 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): # clear the storemodel self.storemodel.clear() self._connectToProject(project) def _newProjectLoadedCb(self, unused_pitivi, project): pass def _newProjectFailedCb(self, unused_pitivi, unused_reason, unused_uri): self.storemodel.clear() self.project_signals.disconnectAll() ## 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 try: self.addUris([quote_uri(uri) for uri in filenames]) except SourceListError: # filenames already present in the sourcelist pass #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)
class SourceList(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 = [] # 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(106) # 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") timeline = self.app.current.timeline sources = self.app.current.sources start = timeline.duration self.app.current.seeker.seek(start) for uri in self.getSelectedItems(): factory = sources.getUri(uri) source = timeline.addSourceFactory(factory) source.setStart(start) start += source.duration self.app.action_log.commit() 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, "missing-plugins", None, self._missingPluginsCb) 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 _addFactory(self, factory): video = factory.getOutputStreams(VideoStream) if video and video[0].thumbnail: thumbnail_file = video[0].thumbnail try: self.debug("attempting to open thumbnail file '%s'", thumbnail_file) pixbuf = gtk.gdk.pixbuf_new_from_file(thumbnail_file) except: self.error("Failure to create thumbnail from file '%s'", thumbnail_file) thumbnail = self.videofilepixbuf thumbnail_large = self.videofilepixbuf else: desiredheight = int(64 / float(video[0].dar)) thumbnail = pixbuf.scale_simple(64, desiredheight, gtk.gdk.INTERP_BILINEAR) desiredheight = int(96 / float(video[0].dar)) thumbnail_large = pixbuf.scale_simple(96, desiredheight, gtk.gdk.INTERP_BILINEAR) else: if video: thumbnail = self.videofilepixbuf thumbnail_large = self.videofilepixbuf else: thumbnail = self.audiofilepixbuf thumbnail_large = self.audiofilepixbuf if not factory.duration or factory.duration == gst.CLOCK_TIME_NONE: duration = '' else: duration = beautify_length(factory.duration) short_text = None uni = unicode(factory_name(factory), 'utf-8') if len(uni) > 34: short_uni = uni[0:29] short_uni += unicode('...') short_text = short_uni.encode('utf-8') else: short_text = factory_name(factory) self.storemodel.append([ thumbnail, thumbnail_large, beautify_factory(factory), factory, factory.uri, duration, factory_name(factory), short_text ]) self._displayClipView() # sourcelist callbacks def _sourceAddedCb(self, sourcelist, factory): """ a file was added to the sourcelist """ self._updateProgressbar() self._addFactory(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() 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 _missingPluginsCb(self, sourcelist, uri, factory, details, descriptions, cb): error = (uri, "Missing plugins", "\n".join(descriptions)) 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.window.destroy() self._error_dialogbox = None def _errorDialogBoxResponseCb(self, unused_dialog, unused_response): self._error_dialogbox.window.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 path = paths[0] factory = model[path][COL_FACTORY] self.debug("Let's play %s", factory.uri) self.emit('play', factory) 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): factory = self.storemodel[path][COL_FACTORY] self.emit('play', factory) 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): self._resetErrorList() self.storemodel.clear() self._connectToProject(project) def _newProjectLoadedCb(self, unused_pitivi, project): pass def _newProjectFailedCb(self, unused_pitivi, unused_reason, unused_uri): self.storemodel.clear() self.project_signals.disconnectAll() ## 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)