~ubuntu-branches/ubuntu/quantal/pitivi/quantal

« back to all changes in this revision

Viewing changes to pitivi/ui/sourcefactories.py

  • Committer: Bazaar Package Importer
  • Author(s): Sebastian Dröge
  • Date: 2008-12-12 10:22:29 UTC
  • mfrom: (1.1.6 upstream)
  • mto: (3.2.2 jaunty) (1.2.2 upstream)
  • mto: This revision was merged to the branch mainline in revision 6.
  • Revision ID: james.westby@ubuntu.com-20081212102229-7c3etvaoy9ys0x28
Tags: upstream-0.11.3
Import upstream version 0.11.3

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# PiTiVi , Non-linear video editor
2
 
#
3
 
#       ui/sourcefactories.py
4
 
#
5
 
# Copyright (c) 2005, Edward Hervey <bilboed@bilboed.com>
6
 
#
7
 
# This program is free software; you can redistribute it and/or
8
 
# modify it under the terms of the GNU Lesser General Public
9
 
# License as published by the Free Software Foundation; either
10
 
# version 2.1 of the License, or (at your option) any later version.
11
 
#
12
 
# This program is distributed in the hope that it will be useful,
13
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
14
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15
 
# Lesser General Public License for more details.
16
 
#
17
 
# You should have received a copy of the GNU Lesser General Public
18
 
# License along with this program; if not, write to the
19
 
# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
20
 
# Boston, MA 02111-1307, USA.
21
 
 
22
 
"""
23
 
Source and effects list widgets
24
 
"""
25
 
 
26
 
import os
27
 
import time
28
 
import string
29
 
import gobject
30
 
import gtk
31
 
import gst
32
 
import pango
33
 
import threading
34
 
from urllib import unquote
35
 
 
36
 
import pitivi.instance as instance
37
 
import pitivi.dnd as dnd
38
 
from pitivi.configure import get_pixmap_dir
39
 
from pitivi.signalgroup import SignalGroup
40
 
from pitivi.threads import Thread
41
 
 
42
 
from filelisterrordialog import FileListErrorDialog
43
 
 
44
 
from gettext import gettext as _
45
 
 
46
 
def beautify_length(length):
47
 
    sec = length / gst.SECOND
48
 
    mins = sec / 60
49
 
    sec = sec % 60
50
 
    if mins < 60:
51
 
        return "%02dm%02ds" % (mins, sec)
52
 
    hours = mins / 60
53
 
    mins = mins % 60
54
 
    return "%02dh%02dm%02ds" % (hours, mins, sec)
55
 
 
56
 
class SourceFactoriesWidget(gtk.Notebook):
57
 
    """
58
 
    Widget for the various source factories (files, effects, live,...)
59
 
    """
60
 
 
61
 
    def __init__(self):
62
 
        """ initialize """
63
 
        gtk.Notebook.__init__(self)
64
 
        self._createUi()
65
 
 
66
 
    def _createUi(self):
67
 
        """ set up the gui """
68
 
        self.set_tab_pos(gtk.POS_TOP)
69
 
        self.sourcelist = SourceListWidget()
70
 
        self.append_page(self.sourcelist, gtk.Label(_("Clips")))
71
 
 
72
 
        ## FIXME: The following are deactivated until they do more than just
73
 
        ##      display things.
74
 
 
75
 
##         self.audiofxlist = AudioFxListWidget()
76
 
##         #self.audiofxlist.set_sensitive(False)
77
 
##         self.append_page(self.audiofxlist, gtk.Label("Audio FX"))
78
 
##         self.videofxlist = VideoFxListWidget()
79
 
##         #self.videofxlist.set_sensitive(False)
80
 
##         self.append_page(self.videofxlist, gtk.Label("Video FX"))
81
 
##         self.transitionlist = TransitionListWidget()
82
 
##         self.transitionlist.set_sensitive(False)
83
 
##         self.append_page(self.transitionlist, gtk.Label("Transitions"))
84
 
 
85
 
 
86
 
(COL_ICON,
87
 
 COL_INFOTEXT,
88
 
 COL_FACTORY,
89
 
 COL_URI,
90
 
 COL_LENGTH) = range(5)
91
 
 
92
 
class SourceListWidget(gtk.VBox):
93
 
    """ Widget for listing sources """
94
 
 
95
 
    def __init__(self):
96
 
        gtk.VBox.__init__(self)
97
 
 
98
 
        # Store
99
 
        # icon, infotext, objectfactory, uri, length
100
 
        self.storemodel = gtk.ListStore(gtk.gdk.Pixbuf, str, object, str, str)
101
 
 
102
 
        self.set_border_width(5)
103
 
        self.set_spacing(6)
104
 
 
105
 
        # Scrolled Window
106
 
        self.scrollwin = gtk.ScrolledWindow()
107
 
        self.scrollwin.set_policy(gtk.POLICY_NEVER,gtk.POLICY_AUTOMATIC)
108
 
        self.scrollwin.set_shadow_type(gtk.SHADOW_ETCHED_IN)
109
 
 
110
 
        # Popup Menu
111
 
        self.popup = gtk.Menu()
112
 
        additem = gtk.ImageMenuItem(_("Add Clips..."))
113
 
        image = gtk.Image()
114
 
        image.set_from_stock(gtk.STOCK_ADD, gtk.ICON_SIZE_MENU)
115
 
        additem.set_image(image)
116
 
 
117
 
        remitem = gtk.ImageMenuItem(_("Remove Clip"))
118
 
        image = gtk.Image()
119
 
        image.set_from_stock(gtk.STOCK_REMOVE, gtk.ICON_SIZE_MENU)
120
 
        remitem.set_image(image)
121
 
        playmenuitem = gtk.MenuItem(_("Play Clip"))
122
 
        playmenuitem.connect("activate", self._playButtonClickedCb)
123
 
        additem.connect("activate", self._addButtonClickedCb)
124
 
        remitem.connect("activate", self._removeButtonClickedCb)
125
 
        additem.show()
126
 
        remitem.show()
127
 
        playmenuitem.show()
128
 
        self.popup.append(additem)
129
 
        self.popup.append(remitem)
130
 
        self.popup.append(playmenuitem)
131
 
 
132
 
        # import sources dialogbox
133
 
        self._importDialog = None
134
 
        self._lastFolder = None
135
 
 
136
 
        # TreeView
137
 
        # Displays icon, name, type, length
138
 
        self.treeview = gtk.TreeView(self.storemodel)
139
 
        self.treeview.connect("button-press-event", self._treeViewButtonPressEventCb)
140
 
        self.treeview.connect("row-activated", self._rowActivatedCb)
141
 
        self.treeview.set_property("rules_hint", True)
142
 
        self.treeview.set_headers_visible(False)
143
 
        tsel = self.treeview.get_selection()
144
 
        tsel.set_mode(gtk.SELECTION_MULTIPLE)
145
 
 
146
 
        pixbufcol = gtk.TreeViewColumn(_("Icon"))
147
 
        pixbufcol.set_expand(False)
148
 
        pixbufcol.set_spacing(5)
149
 
        self.treeview.append_column(pixbufcol)
150
 
        pixcell = gtk.CellRendererPixbuf()
151
 
        pixcell.props.xpad = 6
152
 
        pixbufcol.pack_start(pixcell)
153
 
        pixbufcol.add_attribute(pixcell, 'pixbuf', COL_ICON)
154
 
 
155
 
        namecol = gtk.TreeViewColumn(_("Information"))
156
 
        self.treeview.append_column(namecol)
157
 
        namecol.set_expand(True)
158
 
        namecol.set_spacing(5)
159
 
        namecol.set_sizing(gtk.TREE_VIEW_COLUMN_GROW_ONLY)
160
 
        namecol.set_min_width(150)
161
 
        txtcell = gtk.CellRendererText()
162
 
        txtcell.set_property("ellipsize", pango.ELLIPSIZE_END)
163
 
        namecol.pack_start(txtcell)
164
 
        namecol.add_attribute(txtcell, "markup", COL_INFOTEXT)
165
 
 
166
 
        namecol = gtk.TreeViewColumn(_("Duration"))
167
 
        namecol.set_expand(False)
168
 
        self.treeview.append_column(namecol)
169
 
        txtcell = gtk.CellRendererText()
170
 
        txtcell.set_property("yalign", 0.0)
171
 
        namecol.pack_start(txtcell)
172
 
        namecol.add_attribute(txtcell, "markup", COL_LENGTH)
173
 
 
174
 
        # Start up with tree view
175
 
        self.scrollwin.add(self.treeview)
176
 
 
177
 
        # Explanatory message label
178
 
        textbox = gtk.EventBox()
179
 
        textbox.modify_bg(gtk.STATE_NORMAL, gtk.gdk.color_parse('white'))
180
 
        textbox.show()
181
 
 
182
 
        txtlabel = gtk.Label()
183
 
        txtlabel.set_padding(10, 10)
184
 
        txtlabel.set_line_wrap(True)
185
 
        txtlabel.set_line_wrap_mode(pango.WRAP_WORD)
186
 
        txtlabel.set_justify(gtk.JUSTIFY_CENTER)
187
 
        txtlabel.set_markup(
188
 
            _("<span size='x-large'>Import your clips by dragging them here or "
189
 
              "by using the buttons above.</span>"))
190
 
        textbox.add(txtlabel)
191
 
        self.txtlabel = txtlabel
192
 
 
193
 
        self.textbox = textbox
194
 
 
195
 
        self.pack_start(self.textbox, expand=True, fill=True)
196
 
        self.reorder_child(self.textbox, 0)
197
 
        self.showingTreeView = False
198
 
 
199
 
        self.dragMotionSigId = self.txtlabel.connect("drag-motion",
200
 
                                                     self._dragMotionCb)
201
 
 
202
 
        self.infostub = InfoStub()
203
 
        self.infostub.connect("remove-me", self._removeInfoStub)
204
 
 
205
 
        # Connect to project.  We must remove and reset the callbacks when
206
 
        # changing project.
207
 
        self.project_signals = SignalGroup()
208
 
        self._connectToProject(instance.PiTiVi.current)
209
 
        instance.PiTiVi.connect("new-project-loaded",
210
 
            self._newProjectLoadedCb)
211
 
        instance.PiTiVi.connect("new-project-failed",
212
 
            self._newProjectFailedCb)
213
 
 
214
 
        # default pixbufs
215
 
        icontheme = gtk.icon_theme_get_default()
216
 
        self.filepixbuf = icontheme.load_icon("misc", 32, 0)
217
 
        if not self.filepixbuf:
218
 
            self.filepixbuf = gtk.gdk.pixbuf_new_from_file(os.path.join(pixdir, "pitivi-file.png"))
219
 
        self.audiofilepixbuf = icontheme.load_icon("audio-x-generic", 32, 0)
220
 
        if not self.audiofilepixbuf:
221
 
            self.audiofilepixbuf = gtk.gdk.pixbuf_new_from_file(os.path.join(pixdir, "pitivi-sound.png"))
222
 
        self.videofilepixbuf = icontheme.load_icon("video-x-generic", 32, 0)
223
 
        if not self.videofilepixbuf:
224
 
            self.videofilepixbuf = gtk.gdk.pixbuf_new_from_file(os.path.join(pixdir, "pitivi-video.png"))
225
 
 
226
 
        # Drag and Drop
227
 
        self.drag_dest_set(gtk.DEST_DEFAULT_DROP | gtk.DEST_DEFAULT_MOTION,
228
 
                           [dnd.URI_TUPLE, dnd.FILE_TUPLE],
229
 
                           gtk.gdk.ACTION_COPY)
230
 
        self.connect("drag_data_received", self._dndDataReceivedCb)
231
 
 
232
 
        self.treeview.drag_source_set(gtk.gdk.BUTTON1_MASK,
233
 
                                      [dnd.URI_TUPLE, dnd.FILESOURCE_TUPLE],
234
 
                                      gtk.gdk.ACTION_COPY)
235
 
        self.treeview.connect("drag_begin", self._dndTreeBeginCb)
236
 
        self.treeview.connect("drag_data_get", self._dndDataGetCb)
237
 
 
238
 
        # Hack so that the views have the same method as self
239
 
        self.treeview.getSelectedItems = self.getSelectedItems
240
 
 
241
 
        # Error dialog box
242
 
        self.errorDialogBox = None
243
 
 
244
 
    def _connectToProject(self, project):
245
 
        """Connect signal handlers to a project.
246
 
 
247
 
        This first disconnects any handlers connected to an old project.
248
 
        If project is None, this just disconnects any connected handlers.
249
 
 
250
 
        """
251
 
        self.project_signals.connect(
252
 
            project.sources, "file_added", None, self._fileAddedCb)
253
 
        self.project_signals.connect(
254
 
            project.sources, "file_removed", None, self._fileRemovedCb)
255
 
        self.project_signals.connect(
256
 
            project.sources, "not_media_file", None, self._notMediaFileCb)
257
 
        self.project_signals.connect(
258
 
            project.sources, "ready", None, self._sourcesStoppedImportingCb)
259
 
        self.project_signals.connect(
260
 
            project.sources, "starting", None, self._sourcesStartedImportingCb)
261
 
 
262
 
 
263
 
    ## Explanatory message methods
264
 
 
265
 
    def _displayTreeView(self, displayed=True, usesignals=True):
266
 
        """ Display the tree view in the scrolled window.
267
 
        If displayed is False, then the default explanation message will be
268
 
        shown.
269
 
        If usesignals is True, then signals on the mesagewindow will be
270
 
        (dis)connected
271
 
        """
272
 
        if displayed:
273
 
            if self.showingTreeView:
274
 
                return
275
 
            gst.debug("displaying tree view")
276
 
            self.remove(self.textbox)
277
 
            self.txtlabel.hide()
278
 
            if usesignals:
279
 
                if self.dragMotionSigId:
280
 
                    self.txtlabel.disconnect(self.dragMotionSigId)
281
 
                    self.dragMotionSigId = 0
282
 
            self.pack_start(self.scrollwin)
283
 
            self.reorder_child(self.scrollwin, 0)
284
 
            self.scrollwin.show_all()
285
 
            self.showingTreeView = True
286
 
        else:
287
 
            if not self.showingTreeView:
288
 
                return
289
 
            gst.debug("hiding tree view")
290
 
            self.remove(self.scrollwin)
291
 
            self.scrollwin.hide()
292
 
            self.pack_start(self.textbox)
293
 
            self.reorder_child(self.textbox, 0)
294
 
            self.txtlabel.show()
295
 
            self.showingTreeView = False
296
 
 
297
 
    def _dragMotionCb(self, unused_layout, unused_context, x, unused_y,
298
 
                      unused_timestamp):
299
 
        gst.log("motion")
300
 
        gobject.idle_add(self._displayTreeView, True, False)
301
 
 
302
 
    def showImportSourcesDialog(self, select_folders=False):
303
 
        if self._importDialog:
304
 
            return
305
 
 
306
 
        if select_folders:
307
 
            chooser_action = gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER
308
 
            dialogtitle = _("Import a folder")
309
 
        else:
310
 
            chooser_action = gtk.FILE_CHOOSER_ACTION_OPEN
311
 
            dialogtitle = _("Import a clip")
312
 
 
313
 
        self._importDialog = gtk.FileChooserDialog(dialogtitle, None,
314
 
                                                   chooser_action,
315
 
                                                   (gtk.STOCK_CLOSE, gtk.RESPONSE_CLOSE,
316
 
                                                    gtk.STOCK_ADD, gtk.RESPONSE_OK))
317
 
 
318
 
        self._importDialog.set_default_response(gtk.RESPONSE_OK)
319
 
        self._importDialog.set_select_multiple(True)
320
 
        self._importDialog.set_modal(False)
321
 
        if self._lastFolder:
322
 
            self._importDialog.set_current_folder(self._lastFolder)
323
 
        self._importDialog.connect('response', self._dialogBoxResponseCb, select_folders)
324
 
        self._importDialog.connect('close', self._dialogBoxCloseCb)
325
 
        self._importDialog.show()
326
 
 
327
 
    def addFiles(self, list):
328
 
        """ Add files to the list """
329
 
        instance.PiTiVi.current.sources.addUris(list)
330
 
 
331
 
    def addFolders(self, list):
332
 
        """ walks the trees of the folders in the list and adds the files it finds """
333
 
        instance.PiTiVi.threads.addThread(PathWalker, list, instance.PiTiVi.current.sources.addUris)
334
 
 
335
 
    def _addFactory(self, factory):
336
 
        try:
337
 
            pixbuf = gtk.gdk.pixbuf_new_from_file(factory.thumbnail)
338
 
        except:
339
 
            if factory.is_video:
340
 
                thumbnail = self.videofilepixbuf
341
 
            elif factory.is_audio:
342
 
                thumbnail = self.audiofilepixbuf
343
 
        #FIXME: should this be continue??
344
 
        else:
345
 
            if not factory.video_info_stream:
346
 
                desiredheight = 64 * pixbuf.get_height() / pixbuf.get_width()
347
 
            else:
348
 
                vi = factory.video_info_stream
349
 
                desiredheight = int(64 / float(vi.dar))
350
 
            thumbnail = pixbuf.scale_simple(64, desiredheight, gtk.gdk.INTERP_BILINEAR)
351
 
        self.storemodel.append([thumbnail,
352
 
                                factory.getPrettyInfo(),
353
 
                                factory,
354
 
                                factory.name,
355
 
                                "<b>%s</b>" % beautify_length(factory.length)])
356
 
        self._displayTreeView()
357
 
 
358
 
    # sourcelist callbacks
359
 
 
360
 
    def _fileAddedCb(self, unused_sourcelist, factory):
361
 
        """ a file was added to the sourcelist """
362
 
        self._addFactory(factory)
363
 
 
364
 
    def _fileRemovedCb(self, unused_sourcelist, uri):
365
 
        """ the given uri was removed from the sourcelist """
366
 
        # find the good line in the storemodel and remove it
367
 
        model = self.storemodel
368
 
        for row in model:
369
 
            if uri == row[COL_URI]:
370
 
                model.remove(row.iter)
371
 
                break
372
 
        if not len(model):
373
 
            self._displayTreeView(False)
374
 
 
375
 
    def _notMediaFileCb(self, unused_sourcelist, uri, reason, extra):
376
 
        """ The given uri isn't a media file """
377
 
        self.infostub.addErrors(uri, reason, extra)
378
 
 
379
 
    def _sourcesStartedImportingCb(self, unused_sourcelist):
380
 
        if not self.infostub.showing:
381
 
            self.pack_start(self.infostub, expand=False)
382
 
        self.infostub.startingImport()
383
 
 
384
 
    def _sourcesStoppedImportingCb(self, unused_sourcelist):
385
 
        self.infostub.stoppingImport()
386
 
 
387
 
    def _removeInfoStub(self, infostub):
388
 
        self.remove(self.infostub)
389
 
 
390
 
    ## Error Dialog Box callbacks
391
 
 
392
 
    def _errorDialogBoxCloseCb(self, unused_dialog):
393
 
        self.errorDialogBox.destroy()
394
 
        self.errorDialogBox = None
395
 
 
396
 
    def _errorDialogBoxResponseCb(self, unused_dialog, unused_response):
397
 
        self.errorDialogBox.destroy()
398
 
        self.errorDialogBox = None
399
 
 
400
 
 
401
 
    ## Import Sources Dialog Box callbacks
402
 
 
403
 
    def _dialogBoxResponseCb(self, dialogbox, response, select_folders):
404
 
        gst.debug("response:%r" % response)
405
 
        if response == gtk.RESPONSE_OK:
406
 
            self._lastFolder = dialogbox.get_current_folder()
407
 
            filenames = dialogbox.get_uris()
408
 
            if select_folders:
409
 
                self.addFolders(filenames)
410
 
            else:
411
 
                self.addFiles(filenames)
412
 
        else:
413
 
            dialogbox.destroy()
414
 
            self._importDialog = None
415
 
 
416
 
    def _dialogBoxCloseCb(self, unused_dialogbox):
417
 
        gst.debug("closing")
418
 
        self._importDialog = None
419
 
 
420
 
 
421
 
    ## UI Button callbacks
422
 
 
423
 
    def _addButtonClickedCb(self, unused_widget=None):
424
 
        """ called when a user clicks on the add button """
425
 
        self.showImportSourcesDialog()
426
 
 
427
 
    def _removeButtonClickedCb(self, unused_widget=None):
428
 
        """ Called when a user clicks on the remove button """
429
 
        tsel = self.treeview.get_selection()
430
 
        if tsel.count_selected_rows() < 1:
431
 
            return
432
 
        model, selected = tsel.get_selected_rows()
433
 
        # Sort the list in reverse order so we remove from
434
 
        # the end and make sure that the paths are always valid
435
 
        selected.sort(reverse=True)
436
 
        for path in selected:
437
 
            uri = model[path][COL_URI]
438
 
            del instance.PiTiVi.current.sources[uri]
439
 
 
440
 
    def _errorDialogButtonClickedCb(self, unused_widget=None):
441
 
        """ called when the user click on the import errors button """
442
 
        if self.errorDialogBox:
443
 
            self.errorDialogBox.show()
444
 
        if self.errorDialogButton.parent:
445
 
            self.bothbox.remove(self.errorDialogButton)
446
 
 
447
 
    def _playButtonClickedCb(self, unused_widget):
448
 
        """ Called when a user clicks on the play button """
449
 
        # get the selected filesourcefactory
450
 
        model, paths = self.treeview.get_selection().get_selected_rows()
451
 
        if len(paths) < 1:
452
 
            return
453
 
        path = paths[0]
454
 
        factory = model[path][COL_FACTORY]
455
 
        gst.debug("Let's play %s" % factory.name)
456
 
        instance.PiTiVi.playground.playTemporaryFilesourcefactory(factory)
457
 
 
458
 
    def _treeViewButtonPressEventCb(self, unused_treeview, event):
459
 
        if event.button == 3:
460
 
            self.popup.popup(None, None, None, event.button, event.time)
461
 
 
462
 
    def _rowActivatedCb(self, unused_treeview, path, unused_column):
463
 
        factory = self.storemodel[path][COL_FACTORY]
464
 
        instance.PiTiVi.playground.playTemporaryFilesourcefactory(factory)
465
 
 
466
 
    def _newProjectLoadedCb(self, unused_pitivi, project):
467
 
        # clear the storemodel
468
 
        self.storemodel.clear()
469
 
        self._connectToProject(project)
470
 
        # synchronize the storemodel with the new project's sourcelist
471
 
        for uri, factory in project.sources:
472
 
            gst.log("loading uri %s" % uri)
473
 
            self._addFactory(factory)
474
 
 
475
 
    def _newProjectFailedCb(self, unused_pitivi, unused_reason,
476
 
        unused_uri):
477
 
        self.storemodel.clear()
478
 
        self.project_signals.disconnectAll()
479
 
 
480
 
 
481
 
    ## Drag and Drop
482
 
 
483
 
    def _dndDataReceivedCb(self, unused_widget, unused_context, unused_x,
484
 
                           unused_y, selection, targetType, unused_time):
485
 
        def isfile(path):
486
 
            if path[:7] == "file://":
487
 
                # either it's on local system and we know if it's a directory
488
 
                return os.path.isfile(path[7:])
489
 
            elif "://" in path:
490
 
                # or it's not, in which case we assume it's a file
491
 
                return True
492
 
            # or it's on local system with "file://"
493
 
            return os.path.isfile(path)
494
 
 
495
 
        gst.debug("targetType:%d, selection.data:%r" % (targetType, selection.data))
496
 
        directories = []
497
 
        if targetType == dnd.TYPE_URI_LIST:
498
 
            incoming = [unquote(x.strip('\x00')) for x in selection.data.strip().split("\r\n") if x.strip('\x00')]
499
 
            filenames = [x for x in incoming if isfile(x)]
500
 
            directories = [x for x in incoming if not isfile(x)]
501
 
        elif targetType == dnd.TYPE_TEXT_PLAIN:
502
 
            incoming = selection.data.strip()
503
 
            if isfile(incoming):
504
 
                filenames = [incoming]
505
 
            else:
506
 
                directories = [incoming]
507
 
        if directories:
508
 
            self.addFolders(directories)
509
 
        self.addFiles(filenames)
510
 
 
511
 
    def _dndTreeBeginCb(self, unused_widget, context):
512
 
        gst.info("tree drag_begin")
513
 
        model, paths = self.treeview.get_selection().get_selected_rows()
514
 
        if len(paths) < 1:
515
 
            context.drag_abort(int(time.time()))
516
 
        else:
517
 
            row = model[paths[0]]
518
 
            self.treeview.drag_source_set_icon_pixbuf(row[COL_ICON])
519
 
 
520
 
    def getSelectedItems(self):
521
 
        """ returns a list of selected items uri """
522
 
        model, rows = self.treeview.get_selection().get_selected_rows()
523
 
        return [model[path][COL_URI] for path in rows]
524
 
 
525
 
    def _dndDataGetCb(self, unused_widget, unused_context, selection,
526
 
                      targetType, unused_eventTime):
527
 
        gst.info("data get, type:%d" % targetType)
528
 
        uris = self.getSelectedItems()
529
 
        if len(uris) < 1:
530
 
            return
531
 
        if targetType == dnd.TYPE_PITIVI_FILESOURCE:
532
 
            selection.set(selection.target, 8,
533
 
                          uris[0])
534
 
        elif targetType == dnd.TYPE_URI_LIST:
535
 
            selection.set(selection.target, 8,
536
 
                          string.join(uris, "\n"))
537
 
 
538
 
class AudioFxListWidget(gtk.VBox):
539
 
    """ Widget for listing video effects """
540
 
 
541
 
    def __init__(self):
542
 
        gtk.VBox.__init__(self)
543
 
        self.set_border_width(5)
544
 
 
545
 
        # model
546
 
        self.storemodel = gtk.ListStore(str, str, object)
547
 
 
548
 
        self.scrollwin = gtk.ScrolledWindow()
549
 
        self.scrollwin.set_policy(gtk.POLICY_NEVER,
550
 
                                  gtk.POLICY_AUTOMATIC)
551
 
        self.pack_start(self.scrollwin)
552
 
 
553
 
        self.iconview = gtk.IconView(self.storemodel)
554
 
        self.treeview = gtk.TreeView(self.storemodel)
555
 
 
556
 
        namecol = gtk.TreeViewColumn(_("Name"))
557
 
        self.treeview.append_column(namecol)
558
 
        namecell = gtk.CellRendererText()
559
 
        namecol.pack_start(namecell)
560
 
        namecol.add_attribute(namecell, "text", 0)
561
 
 
562
 
        namecol = gtk.TreeViewColumn(_("Description"))
563
 
        self.treeview.append_column(namecol)
564
 
        namecell = gtk.CellRendererText()
565
 
        namecell.set_property("ellipsize", pango.ELLIPSIZE_END)
566
 
        namecol.pack_start(namecell)
567
 
        namecol.add_attribute(namecell, "text", 1)
568
 
 
569
 
        self.scrollwin.add(self.treeview)
570
 
 
571
 
        self._fillUpModel()
572
 
 
573
 
    def _fillUpModel(self):
574
 
        for factory in instance.PiTiVi.effects.simple_audio:
575
 
            self.storemodel.append([factory.get_longname(),
576
 
                                    factory.get_description(),
577
 
                                    factory])
578
 
 
579
 
(COL_NAME,
580
 
 COL_DESCRIPTION,
581
 
 COL_FACTORY) = range(3)
582
 
 
583
 
class VideoFxListWidget(gtk.VBox):
584
 
    """ Widget for listing video effects """
585
 
 
586
 
    def __init__(self):
587
 
        gtk.VBox.__init__(self)
588
 
        self.set_border_width(5)
589
 
 
590
 
        # model
591
 
        self.storemodel = gtk.ListStore(str, str, object)
592
 
 
593
 
        self.scrollwin = gtk.ScrolledWindow()
594
 
        self.scrollwin.set_policy(gtk.POLICY_NEVER,
595
 
                                  gtk.POLICY_AUTOMATIC)
596
 
        self.pack_start(self.scrollwin)
597
 
 
598
 
        self.iconview = gtk.IconView(self.storemodel)
599
 
        self.treeview = gtk.TreeView(self.storemodel)
600
 
 
601
 
        namecol = gtk.TreeViewColumn(_("Name"))
602
 
        self.treeview.append_column(namecol)
603
 
        namecell = gtk.CellRendererText()
604
 
        namecol.pack_start(namecell)
605
 
        namecol.add_attribute(namecell, "text", COL_NAME)
606
 
 
607
 
        namecol = gtk.TreeViewColumn(_("Description"))
608
 
        self.treeview.append_column(namecol)
609
 
        namecell = gtk.CellRendererText()
610
 
        namecell.set_property("ellipsize", pango.ELLIPSIZE_END)
611
 
        namecol.pack_start(namecell)
612
 
        namecol.add_attribute(namecell, "text", COL_DESCRIPTION)
613
 
 
614
 
        self.scrollwin.add(self.treeview)
615
 
 
616
 
        self._fillUpModel()
617
 
 
618
 
    def _fillUpModel(self):
619
 
        for factory in instance.PiTiVi.effects.simple_video:
620
 
            self.storemodel.append([factory.get_longname(),
621
 
                                    factory.get_description(),
622
 
                                    factory])
623
 
 
624
 
 
625
 
class TransitionListWidget(gtk.VBox):
626
 
    """ Widget for listing transitions """
627
 
 
628
 
    def __init__(self):
629
 
        gtk.VBox.__init__(self)
630
 
        self.iconview = gtk.IconView()
631
 
        self.treeview = gtk.TreeView()
632
 
        self.pack_start(self.iconview)
633
 
 
634
 
class InfoStub(gtk.HBox):
635
 
    """
636
 
    Box used to display information on the current state of the lists
637
 
    """
638
 
 
639
 
    __gsignals__ = {
640
 
        "remove-me" : (gobject.SIGNAL_RUN_LAST,
641
 
                       gobject.TYPE_NONE,
642
 
                       ( ))
643
 
        }
644
 
 
645
 
    def __init__(self):
646
 
        gtk.HBox.__init__(self)
647
 
        self.errors = []
648
 
        self.showing = False
649
 
        self._importingmessage = _("Importing clips...")
650
 
        self._errorsmessage = _("Error(s) occured while importing")
651
 
        self._errormessage = _("An error occured while importing")
652
 
        self._makeUI()
653
 
 
654
 
    def _makeUI(self):
655
 
        self.set_spacing(6)
656
 
        anim = gtk.gdk.PixbufAnimation(get_pixmap_dir() + "/busy.gif")
657
 
        self.busyanim = gtk.image_new_from_animation(anim)
658
 
 
659
 
        self.erroricon = gtk.image_new_from_stock(gtk.STOCK_DIALOG_WARNING,
660
 
                                                  gtk.ICON_SIZE_SMALL_TOOLBAR)
661
 
 
662
 
        self.infolabel = gtk.Label(self._importingmessage)
663
 
        self.infolabel.set_alignment(0, 0.5)
664
 
 
665
 
        self.questionbutton = gtk.Button()
666
 
        self.questionbutton.set_image(gtk.image_new_from_stock(gtk.STOCK_INFO,
667
 
                                                               gtk.ICON_SIZE_SMALL_TOOLBAR))
668
 
        self.questionbutton.connect("clicked", self._questionButtonClickedCb)
669
 
        self._questionshowing = False
670
 
 
671
 
        self.pack_start(self.busyanim, expand=False)
672
 
        self._busyshowing = True
673
 
        self.pack_start(self.infolabel, expand=True, fill=True)
674
 
 
675
 
    def startingImport(self):
676
 
        if self.showing:
677
 
            if self.errors:
678
 
                # if we're already showing and we have errors, show spinner
679
 
                self._showBusyAnim()
680
 
        else:
681
 
            self._showBusyAnim()
682
 
            self.infolabel.set_text(self._importingmessage)
683
 
            self._showQuestionButton(False)
684
 
            self.show()
685
 
 
686
 
    def stoppingImport(self):
687
 
        if self.errors:
688
 
            self._showErrorIcon()
689
 
            if len(self.errors) > 1:
690
 
                self.infolabel.set_text(self._errorsmessage)
691
 
            else:
692
 
                self.infolabel.set_text(self._errormessage)
693
 
            self._showQuestionButton()
694
 
        else:
695
 
            self.hide()
696
 
            self.emit("remove-me")
697
 
 
698
 
    def addErrors(self, *args):
699
 
        self.errors.append(args)
700
 
 
701
 
    def _showBusyAnim(self):
702
 
        if self._busyshowing:
703
 
            return
704
 
        self.remove(self.erroricon)
705
 
        self.pack_start(self.busyanim, expand=False)
706
 
        self.reorder_child(self.busyanim, 0)
707
 
        self.busyanim.show()
708
 
        self._busyshowing = True
709
 
 
710
 
    def _showErrorIcon(self):
711
 
        if not self._busyshowing:
712
 
            return
713
 
        self.remove(self.busyanim)
714
 
        self.pack_start(self.erroricon, expand=False)
715
 
        self.reorder_child(self.erroricon, 0)
716
 
        self.erroricon.show()
717
 
        self._busyshowing = False
718
 
 
719
 
    def _showQuestionButton(self, visible=True):
720
 
        if visible and not self._questionshowing:
721
 
            self.pack_start(self.questionbutton, expand=False)
722
 
            self.questionbutton.show()
723
 
            self._questionshowing = True
724
 
        elif not visible and self._questionshowing:
725
 
            self.remove(self.questionbutton)
726
 
            self._questionshowing = False
727
 
 
728
 
    def _errorDialogBoxCloseCb(self, dialog):
729
 
        dialog.destroy()
730
 
 
731
 
    def _errorDialogBoxResponseCb(self, dialog, unused_response):
732
 
        dialog.destroy()
733
 
 
734
 
    def _questionButtonClickedCb(self, unused_button):
735
 
        if len(self.errors) > 1:
736
 
            msgs = (_("Error while analyzing files"),
737
 
                    _("The following files can not be used with PiTiVi."))
738
 
        else:
739
 
            msgs = (_("Error while analyzing a file"),
740
 
                    _("The following file can not be used with PiTiVi."))
741
 
        # show error dialog
742
 
        dbox = FileListErrorDialog(*msgs)
743
 
        dbox.connect("close", self._errorDialogBoxCloseCb)
744
 
        dbox.connect("response", self._errorDialogBoxResponseCb)
745
 
        for uri,reason,extra in self.errors:
746
 
            dbox.addFailedFile(uri, reason, extra)
747
 
        dbox.show()
748
 
        # reset error list
749
 
        self.errors = []
750
 
        self.hide()
751
 
        self.emit("remove-me")
752
 
 
753
 
    def show(self):
754
 
        gst.log("showing")
755
 
        self.show_all()
756
 
        self.showing = True
757
 
 
758
 
    def hide(self):
759
 
        gst.log("hiding")
760
 
        gtk.VBox.hide(self)
761
 
        self.showing = False
762
 
 
763
 
 
764
 
class PathWalker(Thread):
765
 
    """
766
 
    Thread for recursively searching in a list of directories
767
 
    """
768
 
 
769
 
    def __init__(self, paths, callback):
770
 
        Thread.__init__(self)
771
 
        gst.log("New PathWalker for %s" % paths)
772
 
        self.paths = paths
773
 
        self.callback = callback
774
 
        self.stopme = threading.Event()
775
 
 
776
 
    def process(self):
777
 
        for folder in self.paths:
778
 
            gst.log("folder %s" % folder)
779
 
            if folder.startswith("file://"):
780
 
                folder = folder[len("file://"):]
781
 
            for path, dirs, files in os.walk(folder):
782
 
                if self.stopme.isSet():
783
 
                    return
784
 
                uriList = []
785
 
                for afile in files:
786
 
                    uriList.append("file://%s" % os.path.join(path, afile))
787
 
                if uriList:
788
 
                    self.callback(uriList)
789
 
 
790
 
    def abort(self):
791
 
        self.stopme.set()