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()
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 # 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 # TreeView # Displays icon, name, type, length self.treeview = gtk.TreeView(self.storemodel) 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(5) 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(5) 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.storemodel) 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_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 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>")) infobar.add(txtlabel) self.infobar = infobar self.txtlabel = txtlabel self.infostub = InfoStub() self.infostub.connect("remove-me", self._removeInfoStub) # 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 # Error dialog box self.errorDialogBox = None # 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.iconview_scrollwin) self.pack_start(self.treeview_scrollwin) # 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 _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) ## Explanatory message methods 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) 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 _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, unused_sourcelist, factory): """ a file was added to the sourcelist """ self._addFactory(factory) if len(self.storemodel): self.infobar.hide_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() def _discoveryErrorCb(self, unused_sourcelist, uri, reason, extra): """ The given uri isn't a media file """ self.infostub.addErrors(uri, reason, extra) def _missingPluginsCb(self, sourcelist, uri, factory, details, descriptions, cb): self.infostub.addErrors(uri, "Missing plugins", "\n".join(descriptions)) def _sourcesStartedImportingCb(self, unused_sourcelist): if not self.infostub.showing: self.pack_start(self.infostub, expand=False) self.infostub.startingImport() def _sourcesStoppedImportingCb(self, unused_sourcelist): self.infostub.stoppingImport() def _removeInfoStub(self, unused_i): self.remove(self.infostub) ## Error Dialog Box callbacks def _errorDialogBoxCloseCb(self, unused_dialog): self.errorDialogBox.destroy() self.errorDialogBox = None def _errorDialogBoxResponseCb(self, unused_dialog, unused_response): self.errorDialogBox.destroy() self.errorDialogBox = 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 _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 _newProjectLoadingCb(self, unused_pitivi, uri): if not self.infostub.showing: self.pack_start(self.infostub, expand=False) self.infostub.startingImport() 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 isfile(path): if path[:7] == "file://": # either it's on local system and we know if it's a directory return os.path.isfile(path[7:]) elif "://" in path: # or it's not, in which case we assume it's a file return True # or it's on local system with "file://" return os.path.isfile(path) self.debug("targettype:%d, selection.data:%r", targettype, selection.data) directories = [] if targettype == dnd.TYPE_URI_LIST: incoming = [ unquote(x.strip('\x00')) for x in selection.data.strip().split("\r\n") if x.strip('\x00') ] filenames = [x for x in incoming if isfile(x)] directories = [x for x in incoming if not isfile(x)] elif targettype == dnd.TYPE_TEXT_PLAIN: incoming = selection.data.strip() if isfile(incoming): filenames = [incoming] else: directories = [incoming] if directories: self.addFolders(directories) 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)
def __init__(self, instance, uiman): gtk.VBox.__init__(self) Loggable.__init__(self) self.app = instance self.settings = instance.settings # 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 # TreeView # Displays icon, name, type, length self.treeview = gtk.TreeView(self.storemodel) 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(5) 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(5) 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.storemodel) 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_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 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>")) infobar.add(txtlabel) self.infobar = infobar self.txtlabel = txtlabel self.infostub = InfoStub() self.infostub.connect("remove-me", self._removeInfoStub) # 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 # Error dialog box self.errorDialogBox = None # 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.iconview_scrollwin) self.pack_start(self.treeview_scrollwin) # display the help text self.clip_view = self.settings.lastClipView self._displayClipView()
def __init__(self, instance, uiman): gtk.VBox.__init__(self) Loggable.__init__(self) self.app = instance # Store # icon, infotext, objectfactory, uri, length self.storemodel = gtk.ListStore(gtk.gdk.Pixbuf, str, object, str, str, str) # Scrolled Window self.scrollwin = gtk.ScrolledWindow() self.scrollwin.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC) self.scrollwin.set_shadow_type(gtk.SHADOW_ETCHED_IN) # Popup Menu self.popup = gtk.Menu() additem = gtk.ImageMenuItem(_("Add Clips...")) image = gtk.Image() image.set_from_stock(gtk.STOCK_ADD, gtk.ICON_SIZE_MENU) additem.set_image(image) remitem = gtk.ImageMenuItem(_("Remove Clip")) image = gtk.Image() image.set_from_stock(gtk.STOCK_REMOVE, gtk.ICON_SIZE_MENU) remitem.set_image(image) playmenuitem = gtk.MenuItem(_("Play Clip")) playmenuitem.connect("activate", self._playButtonClickedCb) additem.connect("activate", self._addButtonClickedCb) remitem.connect("activate", self._removeButtonClickedCb) additem.show() remitem.show() playmenuitem.show() self.popup.append(additem) self.popup.append(remitem) self.popup.append(playmenuitem) # import sources dialogbox self._importDialog = None # TreeView # Displays icon, name, type, length self.treeview = gtk.TreeView(self.storemodel) 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._treeSelectionChanged) pixbufcol = gtk.TreeViewColumn(_("Icon")) pixbufcol.set_expand(False) pixbufcol.set_spacing(5) 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(5) 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) # Start up with tree view self.scrollwin.add(self.treeview) # Explanatory message label textbox = gtk.EventBox() textbox.modify_bg(gtk.STATE_NORMAL, gtk.gdk.color_parse('white')) textbox.show() txtlabel = gtk.Label() txtlabel.set_padding(10, 10) txtlabel.set_line_wrap(True) txtlabel.set_line_wrap_mode(pango.WRAP_WORD) txtlabel.set_justify(gtk.JUSTIFY_CENTER) txtlabel.set_markup( _("<span size='x-large'>Import your clips by dragging them here or " "by using the buttons above.</span>")) textbox.add(txtlabel) self.txtlabel = txtlabel self.textbox = textbox self.pack_start(self.textbox, expand=True, fill=True) self.reorder_child(self.textbox, 0) self.showingTreeView = False self.dragMotionSigId = self.txtlabel.connect("drag-motion", self._dragMotionCb) self.infostub = InfoStub() self.infostub.connect("remove-me", self._removeInfoStub) # 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._dndTreeBeginCb) self.treeview.connect("drag_data_get", self._dndDataGetCb) # Hack so that the views have the same method as self self.treeview.getSelectedItems = self.getSelectedItems # Error dialog box self.errorDialogBox = None # 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"), None, 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)
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 # Store # icon, infotext, objectfactory, uri, length self.storemodel = gtk.ListStore(gtk.gdk.Pixbuf, str, object, str, str, str) # Scrolled Window self.scrollwin = gtk.ScrolledWindow() self.scrollwin.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC) self.scrollwin.set_shadow_type(gtk.SHADOW_ETCHED_IN) # Popup Menu self.popup = gtk.Menu() additem = gtk.ImageMenuItem(_("Add Clips...")) image = gtk.Image() image.set_from_stock(gtk.STOCK_ADD, gtk.ICON_SIZE_MENU) additem.set_image(image) remitem = gtk.ImageMenuItem(_("Remove Clip")) image = gtk.Image() image.set_from_stock(gtk.STOCK_REMOVE, gtk.ICON_SIZE_MENU) remitem.set_image(image) playmenuitem = gtk.MenuItem(_("Play Clip")) playmenuitem.connect("activate", self._playButtonClickedCb) additem.connect("activate", self._addButtonClickedCb) remitem.connect("activate", self._removeButtonClickedCb) additem.show() remitem.show() playmenuitem.show() self.popup.append(additem) self.popup.append(remitem) self.popup.append(playmenuitem) # import sources dialogbox self._importDialog = None # TreeView # Displays icon, name, type, length self.treeview = gtk.TreeView(self.storemodel) 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._treeSelectionChanged) pixbufcol = gtk.TreeViewColumn(_("Icon")) pixbufcol.set_expand(False) pixbufcol.set_spacing(5) 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(5) 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) # Start up with tree view self.scrollwin.add(self.treeview) # Explanatory message label textbox = gtk.EventBox() textbox.modify_bg(gtk.STATE_NORMAL, gtk.gdk.color_parse('white')) textbox.show() txtlabel = gtk.Label() txtlabel.set_padding(10, 10) txtlabel.set_line_wrap(True) txtlabel.set_line_wrap_mode(pango.WRAP_WORD) txtlabel.set_justify(gtk.JUSTIFY_CENTER) txtlabel.set_markup( _("<span size='x-large'>Import your clips by dragging them here or " "by using the buttons above.</span>")) textbox.add(txtlabel) self.txtlabel = txtlabel self.textbox = textbox self.pack_start(self.textbox, expand=True, fill=True) self.reorder_child(self.textbox, 0) self.showingTreeView = False self.dragMotionSigId = self.txtlabel.connect("drag-motion", self._dragMotionCb) self.infostub = InfoStub() self.infostub.connect("remove-me", self._removeInfoStub) # 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._dndTreeBeginCb) self.treeview.connect("drag_data_get", self._dndDataGetCb) # Hack so that the views have the same method as self self.treeview.getSelectedItems = self.getSelectedItems # Error dialog box self.errorDialogBox = None # 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"), None, 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) 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 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 _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) ## Explanatory message methods def _displayTreeView(self, displayed=True, usesignals=True): """ Display the tree view in the scrolled window. If displayed is False, then the default explanation message will be shown. If usesignals is True, then signals on the mesagewindow will be (dis)connected """ if displayed: if self.showingTreeView: return self.debug("displaying tree view") self.remove(self.textbox) self.txtlabel.hide() if usesignals: if self.dragMotionSigId: self.txtlabel.disconnect(self.dragMotionSigId) self.dragMotionSigId = 0 self.pack_start(self.scrollwin) self.reorder_child(self.scrollwin, 0) self.scrollwin.show_all() self.showingTreeView = True else: if not self.showingTreeView: return self.debug("hiding tree view") self.remove(self.scrollwin) self.scrollwin.hide() self.pack_start(self.textbox) self.reorder_child(self.textbox, 0) self.txtlabel.show() self.showingTreeView = False def _dragMotionCb(self, unused_layout, unused_context, unused_x, unused_y, unused_timestamp): self.log("motion") gobject.idle_add(self._displayTreeView, True, False) 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) 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 """ self.app.current.sources.addUris(files) 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 _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 else: desiredheight = int(64 / float(video[0].dar)) thumbnail = pixbuf.scale_simple(64, desiredheight, gtk.gdk.INTERP_BILINEAR) else: if video: thumbnail = self.videofilepixbuf else: thumbnail = self.audiofilepixbuf if not factory.duration or factory.duration == gst.CLOCK_TIME_NONE: duration = '' else: duration = beautify_length(factory.duration) self.storemodel.append([ thumbnail, beautify_factory(factory), factory, factory.uri, duration, factory_name(factory) ]) self._displayTreeView() # sourcelist callbacks def _sourceAddedCb(self, unused_sourcelist, factory): """ a file was added to the sourcelist """ self._addFactory(factory) 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._displayTreeView(False) def _discoveryErrorCb(self, unused_sourcelist, uri, reason, extra): """ The given uri isn't a media file """ self.infostub.addErrors(uri, reason, extra) def _missingPluginsCb(self, sourcelist, uri, details, descriptions): #self.infostub.addErrors(uri, "Missing plugins", "\n".join(descriptions)) pass def _sourcesStartedImportingCb(self, unused_sourcelist): if not self.infostub.showing: self.pack_start(self.infostub, expand=False) self.infostub.startingImport() def _sourcesStoppedImportingCb(self, unused_sourcelist): self.infostub.stoppingImport() def _removeInfoStub(self, unused_i): self.remove(self.infostub) ## Error Dialog Box callbacks def _errorDialogBoxCloseCb(self, unused_dialog): self.errorDialogBox.destroy() self.errorDialogBox = None def _errorDialogBoxResponseCb(self, unused_dialog, unused_response): self.errorDialogBox.destroy() self.errorDialogBox = 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): tsel = self.treeview.get_selection() if tsel.count_selected_rows() < 1: return model, selected = tsel.get_selected_rows() # Sort the list in reverse order so we remove from # the end and make sure that the paths are always valid selected.sort(reverse=True) for path in selected: uri = model[path][COL_URI] self.app.current.sources.removeUri(uri) ## UI Button callbacks def _addButtonClickedCb(self, unused_widget=None): """ called when a user clicks on the add 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): """ Called when a user clicks on the play button """ # get the selected filesourcefactory model, paths = self.treeview.get_selection().get_selected_rows() 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) _dragStarted = False _dragButton = None _dragX = 0 _dragY = 0 _ignoreRelease = False def _rowUnderMouseSelected(self, treeview, event): result = treeview.get_path_at_pos(int(event.x), int(event.y)) if result: path = result[0] selection = treeview.get_selection() return selection.path_is_selected( path) and selection.count_selected_rows() > 1 return False def _nothingUnderMouse(self, treeview, event): return not bool(treeview.get_path_at_pos(int(event.x), int(event.y))) def _treeViewButtonPressEventCb(self, treeview, event): chain_up = True if event.button == 3: self.popup.popup(None, None, None, event.button, event.time) 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._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 _treeSelectionChanged(self, tsel): if self.getSelectedItems(): 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 _newProjectCreatedCb(self, app, project): # clear the storemodel self.storemodel.clear() self._connectToProject(project) def _newProjectLoadingCb(self, unused_pitivi, uri): if not self.infostub.showing: self.pack_start(self.infostub, expand=False) self.infostub.startingImport() 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 isfile(path): if path[:7] == "file://": # either it's on local system and we know if it's a directory return os.path.isfile(path[7:]) elif "://" in path: # or it's not, in which case we assume it's a file return True # or it's on local system with "file://" return os.path.isfile(path) self.debug("targettype:%d, selection.data:%r", targettype, selection.data) directories = [] if targettype == dnd.TYPE_URI_LIST: incoming = [ unquote(x.strip('\x00')) for x in selection.data.strip().split("\r\n") if x.strip('\x00') ] filenames = [x for x in incoming if isfile(x)] directories = [x for x in incoming if not isfile(x)] elif targettype == dnd.TYPE_TEXT_PLAIN: incoming = selection.data.strip() if isfile(incoming): filenames = [incoming] else: directories = [incoming] if directories: self.addFolders(directories) try: self.addUris(filenames) except SourceListError: # filenames already present in the sourcelist pass def _dndTreeBeginCb(self, unused_widget, context): self.info("tree drag_begin") model, paths = self.treeview.get_selection().get_selected_rows() if len(paths) < 1: context.drag_abort(int(time.time())) else: row = model[paths[0]] context.set_icon_pixbuf(row[COL_ICON], 0, 0) def getSelectedItems(self): """ returns a list of selected items uri """ model, rows = self.treeview.get_selection().get_selected_rows() return [model[path][COL_URI] for path in rows] 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)
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()