~ubuntu-branches/ubuntu/lucid/pitivi/lucid

« back to all changes in this revision

Viewing changes to pitivi/ui/timelineobjects.py

  • Committer: Bazaar Package Importer
  • Author(s): Sebastian Dröge
  • Date: 2009-05-27 14:22:49 UTC
  • mfrom: (1.2.1 upstream) (3.1.13 experimental)
  • Revision ID: james.westby@ubuntu.com-20090527142249-tj0qnkc37320ylml
New upstream release.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# PiTiVi , Non-linear video editor
2
 
#
3
 
#       pitivi/ui/timelineobjects.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
 
Simple view timeline widgets
24
 
"""
25
 
 
26
 
import os.path
27
 
from urllib import unquote
28
 
import gobject
29
 
import gtk
30
 
import gst
31
 
 
32
 
import pitivi.instance as instance
33
 
from pitivi.timeline.source import TimelineFileSource, TimelineSource, TimelineBlankSource
34
 
from pitivi.timeline.effects import TimelineTransition
35
 
from pitivi.timeline.objects import MEDIA_TYPE_AUDIO, MEDIA_TYPE_VIDEO
36
 
from pitivi.configure import get_pixmap_dir
37
 
import pitivi.dnd as dnd
38
 
from pitivi.signalgroup import SignalGroup
39
 
 
40
 
from sourcefactories import beautify_length
41
 
from gettext import gettext as _
42
 
 
43
 
# Default width / height ratio for simple elements
44
 
DEFAULT_SIMPLE_SIZE_RATIO = 1.0 # default width / height ratio
45
 
 
46
 
# Default simple elements size
47
 
DEFAULT_SIMPLE_ELEMENT_WIDTH = 100
48
 
DEFAULT_SIMPLE_ELEMENT_HEIGHT = DEFAULT_SIMPLE_ELEMENT_WIDTH * DEFAULT_SIMPLE_SIZE_RATIO
49
 
 
50
 
# Default spacing between/above elements in simple timeline
51
 
DEFAULT_SIMPLE_SPACING = 10
52
 
 
53
 
# Simple Timeline's default values
54
 
DEFAULT_HEIGHT = DEFAULT_SIMPLE_ELEMENT_HEIGHT + 2 * DEFAULT_SIMPLE_SPACING
55
 
DEFAULT_WIDTH = 3 * DEFAULT_SIMPLE_SPACING # borders (2) + one holding place
56
 
MINIMUM_HEIGHT = DEFAULT_HEIGHT
57
 
MINIMUM_WIDTH = 3 * DEFAULT_HEIGHT
58
 
 
59
 
class SimpleTimeline(gtk.Layout):
60
 
    """ Simple Timeline representation """
61
 
 
62
 
    def __init__(self, **kw):
63
 
        gobject.GObject.__init__(self, **kw)
64
 
 
65
 
        self.hadjustment = self.get_property("hadjustment")
66
 
 
67
 
        # timeline and top level compositions
68
 
        self.timeline = instance.PiTiVi.current.timeline
69
 
        self.condensed = self.timeline.videocomp.condensed
70
 
 
71
 
        # TODO : connect signals for when the timeline changes
72
 
 
73
 
        # widgets correspondance dictionnary
74
 
        # MAPPING timelineobject => widget
75
 
        self.widgets = {}
76
 
 
77
 
        # edit-mode
78
 
        # True when in editing mode
79
 
        self._editingMode = False
80
 
        self.editingWidget = SimpleEditingWidget()
81
 
        self.editingWidget.connect("hide-me", self._editingWidgetHideMeCb)
82
 
 
83
 
        # Connect to timeline.  We must remove and reset the callbacks when
84
 
        # changing project.
85
 
        self.project_signals = SignalGroup()
86
 
        self._connectToTimeline(instance.PiTiVi.current.timeline)
87
 
        instance.PiTiVi.connect("new-project", self._newProjectCb)
88
 
 
89
 
        # size
90
 
        self.width = int(DEFAULT_WIDTH)
91
 
        self.height = int(DEFAULT_HEIGHT)
92
 
        self.realWidth = 0 # displayed width of the layout
93
 
        self.childheight = int(DEFAULT_SIMPLE_ELEMENT_HEIGHT)
94
 
        self.set_size_request(int(MINIMUM_WIDTH), int(MINIMUM_HEIGHT))
95
 
        self.set_property("width", int(DEFAULT_WIDTH))
96
 
        self.set_property("height", int(DEFAULT_HEIGHT))
97
 
 
98
 
        # event callbacks
99
 
        self.connect("expose-event", self._exposeEventCb)
100
 
        self.connect("notify::width", self._widthChangedCb)
101
 
        self.connect("size-allocate", self._sizeAllocateCb)
102
 
        self.connect("realize", self._realizeCb)
103
 
 
104
 
        # drag and drop
105
 
        self.drag_dest_set(gtk.DEST_DEFAULT_DROP | gtk.DEST_DEFAULT_MOTION,
106
 
                           [dnd.FILESOURCE_TUPLE],
107
 
                           gtk.gdk.ACTION_COPY)
108
 
        self.connect("drag-data-received", self._dragDataReceivedCb)
109
 
        self.connect("drag-leave", self._dragLeaveCb)
110
 
        self.connect("drag-motion", self._dragMotionCb)
111
 
        self.slotposition = -1
112
 
 
113
 
        self.draggedelement = None
114
 
 
115
 
        self.show_all()
116
 
 
117
 
 
118
 
    ## Project callbacks
119
 
 
120
 
    def _connectToTimeline(self, timeline):
121
 
        self.timeline = timeline
122
 
        self.condensed = self.timeline.videocomp.condensed
123
 
 
124
 
        self.project_signals.connect(self.timeline.videocomp,
125
 
                                     "condensed-list-changed",
126
 
                                     None, self._condensedListChangedCb)
127
 
 
128
 
    def _newProjectCb(self, unused_pitivi, project):
129
 
        assert(instance.PiTiVi.current == project)
130
 
 
131
 
        for widget in self.widgets.itervalues():
132
 
            self.remove(widget)
133
 
        self.widgets = {}
134
 
 
135
 
        self._connectToTimeline(instance.PiTiVi.current.timeline)
136
 
 
137
 
 
138
 
    ## Timeline callbacks
139
 
 
140
 
    def _condensedListChangedCb(self, unused_videocomp, clist):
141
 
        """ add/remove the widgets """
142
 
        gst.debug("condensed list changed in videocomp")
143
 
 
144
 
        current = self.widgets.keys()
145
 
        self.condensed = clist
146
 
 
147
 
        new = [x for x in clist if not x in current]
148
 
        removed = [x for x in current if not x in clist]
149
 
 
150
 
        # new elements
151
 
        for element in new:
152
 
            # add the widget to self.widget
153
 
            gst.debug("Adding new element to the layout")
154
 
            if isinstance(element, TimelineFileSource):
155
 
                widget = SimpleSourceWidget(element)
156
 
                widget.connect("delete-me", self._sourceDeleteMeCb, element)
157
 
                widget.connect("edit-me", self._sourceEditMeCb, element)
158
 
                widget.connect("drag-begin", self._sourceDragBeginCb, element)
159
 
                widget.connect("drag-end", self._sourceDragEndCb, element)
160
 
            else:
161
 
                widget = SimpleTransitionWidget(element)
162
 
            self.widgets[element] = widget
163
 
            self.put(widget, 0, 0)
164
 
            widget.show()
165
 
 
166
 
        # removed elements
167
 
        for element in removed:
168
 
            self.remove(self.widgets[element])
169
 
            del self.widgets[element]
170
 
 
171
 
        self._resizeChildrens()
172
 
 
173
 
 
174
 
    ## Utility methods
175
 
 
176
 
    def _getNearestSourceSlot(self, x):
177
 
        """
178
 
        returns the nearest file slot position available for the given position
179
 
        Returns the value in condensed list position
180
 
        Returns n , the element before which it should go
181
 
        Return -1 if it's meant to go last
182
 
        """
183
 
        if not self.condensed or x < 0:
184
 
            return 0
185
 
        if x > self.width - DEFAULT_SIMPLE_SPACING:
186
 
            return -1
187
 
 
188
 
        pos = DEFAULT_SIMPLE_SPACING
189
 
        order = 0
190
 
        # TODO Need to avoid getting position between source and transition
191
 
        for source in self.condensed:
192
 
            if isinstance(source, TimelineSource):
193
 
                spacing = self.childheight
194
 
            elif isinstance(source, TimelineTransition):
195
 
                spacing = self.childheight / 2
196
 
            else:
197
 
                # this shouldn't happen !! The condensed list only contains
198
 
                # sources and/or transitions
199
 
                pass
200
 
            if x <= pos + spacing / 2:
201
 
                return order
202
 
            pos = pos + spacing + DEFAULT_SIMPLE_SPACING
203
 
            order = order + 1
204
 
        return -1
205
 
 
206
 
    def _getNearestSourceSlotPixels(self, x):
207
 
        """
208
 
        returns the nearest file slot position available for the given position
209
 
        Returns the value in pixels
210
 
        """
211
 
        if not self.condensed or x < 0:
212
 
            return DEFAULT_SIMPLE_SPACING
213
 
        if x > self.width - DEFAULT_SIMPLE_SPACING:
214
 
            return self.width - 2 * DEFAULT_SIMPLE_SPACING
215
 
 
216
 
        pos = DEFAULT_SIMPLE_SPACING
217
 
        # TODO Need to avoid getting position between source and transition
218
 
        for source in self.condensed:
219
 
            if isinstance(source, TimelineSource):
220
 
                spacing = self.childheight
221
 
            elif isinstance(source, TimelineTransition):
222
 
                spacing = self.childheight / 2
223
 
            else:
224
 
                # this shouldn't happen !! The condensed list only contains
225
 
                # sources and/or transitions
226
 
                pass
227
 
            if x <= pos + spacing / 2:
228
 
                return pos
229
 
            pos = pos + spacing + DEFAULT_SIMPLE_SPACING
230
 
        return pos
231
 
 
232
 
 
233
 
    ## Drawing
234
 
 
235
 
    def _drawDragSlot(self):
236
 
        if self.slotposition == -1:
237
 
            return
238
 
        self.bin_window.draw_rectangle(self.style.black_gc, True,
239
 
                                       self.slotposition, DEFAULT_SIMPLE_SPACING,
240
 
                                       DEFAULT_SIMPLE_SPACING, self.childheight)
241
 
 
242
 
    def _eraseDragSlot(self):
243
 
        if self.slotposition == -1:
244
 
            return
245
 
        self.bin_window.draw_rectangle(self.style.white_gc, True,
246
 
                                       self.slotposition, DEFAULT_SIMPLE_SPACING,
247
 
                                       DEFAULT_SIMPLE_SPACING, self.childheight)
248
 
 
249
 
    def _gotFileFactory(self, filefactory, x, unused_y):
250
 
        """ got a filefactory at the given position """
251
 
        # remove the slot
252
 
        self._eraseDragSlot()
253
 
        self.slotposition = -1
254
 
        if not filefactory or not filefactory.is_video:
255
 
            return
256
 
        pos = self._getNearestSourceSlot(x)
257
 
 
258
 
        gst.debug("_got_filefactory pos : %d" % pos)
259
 
 
260
 
        # we just add it here, the drawing will be done in the condensed_list
261
 
        # callback
262
 
        source = TimelineFileSource(factory=filefactory,
263
 
                                    media_type=MEDIA_TYPE_VIDEO,
264
 
                                    name=filefactory.name)
265
 
 
266
 
        # ONLY FOR SIMPLE TIMELINE : if video-only, we link a blank audio object
267
 
        if not filefactory.is_audio:
268
 
            audiobrother = TimelineBlankSource(factory=filefactory,
269
 
                                               media_type=MEDIA_TYPE_AUDIO,
270
 
                                               name=filefactory.name)
271
 
            source.setBrother(audiobrother)
272
 
 
273
 
        if pos == -1:
274
 
            self.timeline.videocomp.appendSource(source)
275
 
        elif pos:
276
 
            self.timeline.videocomp.insertSourceAfter(source, self.condensed[pos - 1])
277
 
        else:
278
 
            self.timeline.videocomp.prependSource(source)
279
 
 
280
 
    def _moveElement(self, element, x):
281
 
        gst.debug("TimelineSource, move %s to x:%d" % (element, x))
282
 
        # remove the slot
283
 
        self._eraseDragSlot()
284
 
        self.slotposition = -1
285
 
        pos = self._getNearestSourceSlot(x)
286
 
 
287
 
        self.timeline.videocomp.moveSource(element, pos)
288
 
 
289
 
    def _widthChangedCb(self, unused_layout, property):
290
 
        if not property.name == "width":
291
 
            return
292
 
        self.width = self.get_property("width")
293
 
 
294
 
    def _motionNotifyEventCb(self, layout, event):
295
 
        pass
296
 
 
297
 
 
298
 
    ## Drag and Drop callbacks
299
 
 
300
 
    def _dragMotionCb(self, unused_layout, unused_context, x, unused_y,
301
 
                      unused_timestamp):
302
 
        # TODO show where the dragged item would go
303
 
        pos = self._getNearestSourceSlotPixels(x + (self.hadjustment.get_value()))
304
 
        rpos = self._getNearestSourceSlot(x + self.hadjustment.get_value())
305
 
        gst.log("SimpleTimeline x:%d , source would go at %d" % (x, rpos))
306
 
        if not pos == self.slotposition:
307
 
            if not self.slotposition == -1:
308
 
                # erase previous slot position
309
 
                self._eraseDragSlot()
310
 
            # draw new slot position
311
 
            self.slotposition = pos
312
 
            self._drawDragSlot()
313
 
 
314
 
    def _dragLeaveCb(self, unused_layout, unused_context, unused_timestamp):
315
 
        gst.log("SimpleTimeline")
316
 
        self._eraseDragSlot()
317
 
        self.slotposition = -1
318
 
        # TODO remove the drag emplacement
319
 
 
320
 
    def _dragDataReceivedCb(self, unused_layout, context, x, y, selection,
321
 
                            targetType, timestamp):
322
 
        gst.log("SimpleTimeline, targetType:%d, selection.data:%s" % (targetType, selection.data))
323
 
        if targetType == dnd.TYPE_PITIVI_FILESOURCE:
324
 
            uri = selection.data
325
 
        else:
326
 
            context.finish(False, False, timestamp)
327
 
        x = x + int(self.hadjustment.get_value())
328
 
        if self.draggedelement:
329
 
            self._moveElement(self.draggedelement, x)
330
 
        else:
331
 
            self._gotFileFactory(instance.PiTiVi.current.sources[uri], x, y)
332
 
        context.finish(True, False, timestamp)
333
 
        instance.PiTiVi.playground.switchToTimeline()
334
 
 
335
 
 
336
 
    ## Drawing
337
 
 
338
 
    def _realizeCb(self, unused_layout):
339
 
        self.modify_bg(gtk.STATE_NORMAL, self.style.white)
340
 
 
341
 
    def _areaIntersect(self, x, y, w, h, x2, y2, w2, h2):
342
 
        """ returns True if the area intersects, else False """
343
 
        # is zone to the left of zone2
344
 
        z1 = gtk.gdk.Rectangle(x, y, w, h)
345
 
        z2 = gtk.gdk.Rectangle(x2, y2, w2, h2)
346
 
        r = z1.intersect(z2)
347
 
        a, b, c, d = r
348
 
        if a or b or c or d:
349
 
            return True
350
 
        return False
351
 
 
352
 
    def _exposeEventCb(self, unused_layout, event):
353
 
        x, y, w, h = event.area
354
 
        # redraw the slot rectangle if there's one
355
 
        if not self.slotposition == -1:
356
 
            if self._areaIntersect(x, y, w, h,
357
 
                                   self.slotposition, DEFAULT_SIMPLE_SPACING,
358
 
                                   DEFAULT_SIMPLE_SPACING, self.childheight):
359
 
                self.bin_window.draw_rectangle(self.style.black_gc, True,
360
 
                                               self.slotposition, DEFAULT_SIMPLE_SPACING,
361
 
                                               DEFAULT_SIMPLE_SPACING, self.childheight)
362
 
 
363
 
        return False
364
 
 
365
 
    def _sizeAllocateCb(self, unused_layout, allocation):
366
 
        if not self.height == allocation.height:
367
 
            self.height = allocation.height
368
 
            self.childheight = self.height - 2 * DEFAULT_SIMPLE_SPACING
369
 
            self._resizeChildrens()
370
 
        self.realWidth = allocation.width
371
 
        if self._editingMode:
372
 
            self.editingWidget.set_size_request(self.realWidth - 20,
373
 
                                                self.height - 20)
374
 
 
375
 
 
376
 
    def _resizeChildrens(self):
377
 
        # resize the childrens to self.height
378
 
        # also need to move them to their correct position
379
 
        # TODO : check if there already at the given position
380
 
        # TODO : check if they already have the good size
381
 
        if self._editingMode:
382
 
            return
383
 
        pos = 2 * DEFAULT_SIMPLE_SPACING
384
 
        for source in self.condensed:
385
 
            widget = self.widgets[source]
386
 
            if isinstance(source, TimelineFileSource):
387
 
                widget.set_size_request(self.childheight, self.childheight)
388
 
                self.move(widget, pos, DEFAULT_SIMPLE_SPACING)
389
 
                pos = pos + self.childheight + DEFAULT_SIMPLE_SPACING
390
 
            elif isinstance(source, SimpleTransitionWidget):
391
 
                widget.set_size_request(self.childheight / 2, self.childheight)
392
 
                self.move(widget, pos, DEFAULT_SIMPLE_SPACING)
393
 
                pos = pos + self.childheight + DEFAULT_SIMPLE_SPACING
394
 
        newwidth = pos + DEFAULT_SIMPLE_SPACING
395
 
        self.set_property("width", newwidth)
396
 
 
397
 
 
398
 
    ## Child callbacks
399
 
 
400
 
    def _sourceDeleteMeCb(self, unused_widget, element):
401
 
        # remove this element from the timeline
402
 
        self.timeline.videocomp.removeSource(element, collapse_neighbours=True)
403
 
 
404
 
    def _sourceEditMeCb(self, unused_widget, element):
405
 
        self.switchToEditingMode(element)
406
 
 
407
 
    def _sourceDragBeginCb(self, unused_widget, unused_context, element):
408
 
        gst.log("Timeline drag beginning on %s" % element)
409
 
        if self.draggedelement:
410
 
            gst.error("We were already doing a DnD ???")
411
 
        self.draggedelement = element
412
 
        # this element is starting to be dragged
413
 
 
414
 
    def _sourceDragEndCb(self, unused_widget, unused_context, element):
415
 
        gst.log("Timeline drag ending on %s" % element)
416
 
        if not self.draggedelement == element:
417
 
            gst.error("The DnD that ended is not the one that started before ???")
418
 
        self.draggedelement = None
419
 
        # this element is no longer dragged
420
 
 
421
 
    def _editingWidgetHideMeCb(self, unused_widget):
422
 
        self.switchToNormalMode()
423
 
 
424
 
 
425
 
    ## Editing mode
426
 
 
427
 
    def _switchEditingMode(self, source, mode=True):
428
 
        """ Switch editing mode for the given TimelineSource """
429
 
        gst.log("source:%s , mode:%s" % (source, mode))
430
 
 
431
 
        if self._editingMode == mode:
432
 
            gst.warning("We were already in the correct editing mode : %s" % mode)
433
 
            return
434
 
 
435
 
        if mode and not source:
436
 
            gst.warning("You need to specify a valid TimelineSource")
437
 
            return
438
 
 
439
 
        if mode:
440
 
            # switching TO editing mode
441
 
            gst.log("Switching TO editing mode")
442
 
 
443
 
            # 1. Hide all sources
444
 
            for widget in self.widgets.itervalues():
445
 
                widget.hide()
446
 
                self.remove(widget)
447
 
 
448
 
            self._editingMode = mode
449
 
 
450
 
            # 2. Show editing widget
451
 
            self.editingWidget.setSource(source)
452
 
            self.put(self.editingWidget, 10, 10)
453
 
            self.props.width = self.realWidth
454
 
            self.editingWidget.set_size_request(self.realWidth - 20, self.height - 20)
455
 
            self.editingWidget.show()
456
 
 
457
 
        else:
458
 
            gst.log("Switching back to normal mode")
459
 
            # switching FROM editing mode
460
 
 
461
 
            # 1. Hide editing widget
462
 
            self.editingWidget.hide()
463
 
            self.remove(self.editingWidget)
464
 
 
465
 
            self._editingMode = mode
466
 
 
467
 
            # 2. Show all sources
468
 
            for widget in self.widgets.itervalues():
469
 
                self.put(widget, 0, 0)
470
 
                widget.show()
471
 
            self._resizeChildrens()
472
 
 
473
 
    def switchToEditingMode(self, source):
474
 
        """ Switch to Editing mode for the given TimelineSource """
475
 
        self._switchEditingMode(source)
476
 
 
477
 
    def switchToNormalMode(self):
478
 
        """ Switch back to normal timeline mode """
479
 
        self._switchEditingMode(None, False)
480
 
 
481
 
 
482
 
class SimpleEditingWidget(gtk.DrawingArea):
483
 
    """
484
 
    Widget for editing a source in the SimpleTimeline
485
 
    """
486
 
 
487
 
    __gsignals__ = {
488
 
        "hide-me" : (gobject.SIGNAL_RUN_LAST,
489
 
                     gobject.TYPE_NONE,
490
 
                     ( ))
491
 
        }
492
 
 
493
 
    def __init__(self):
494
 
        gtk.DrawingArea.__init__(self)
495
 
        self.gc = None
496
 
        self.add_events(gtk.gdk.BUTTON_RELEASE_MASK | gtk.gdk.BUTTON_PRESS_MASK)
497
 
        self.connect("realize", self._realizeCb)
498
 
        self.connect("expose-event", self._exposeEventCb)
499
 
        self.connect("button-press-event", self._buttonPressEventCb)
500
 
        self._source = None
501
 
 
502
 
        #popup menu
503
 
        self._popupMenu = gtk.Menu()
504
 
        closeitem = gtk.MenuItem(_("Close"))
505
 
        closeitem.connect("activate", self._closeMenuItemCb)
506
 
        closeitem.show()
507
 
        self._popupMenu.append(closeitem)
508
 
 
509
 
    def setSource(self, source):
510
 
        self._source = source
511
 
 
512
 
    def _realizeCb(self, unused_widget):
513
 
        gst.log("realize")
514
 
        self.gc = self.window.new_gc()
515
 
        self.gc.set_background(self.style.black)
516
 
 
517
 
    def _exposeEventCb(self, unused_widget, event):
518
 
        x, y, w, h = event.area
519
 
        gst.log("expose %s" % ([x,y,w,h]))
520
 
 
521
 
    def _closeMenuItemCb(self, unused_menuitem):
522
 
        self.emit("hide-me")
523
 
 
524
 
    def _buttonPressEventCb(self, unused_widget, event):
525
 
        if event.button == 3:
526
 
            self._popupMenu.popup(None, None, None, event.button,
527
 
                                  event.time)
528
 
 
529
 
 
530
 
 
531
 
class SimpleSourceWidget(gtk.DrawingArea):
532
 
    """
533
 
    Widget for representing a source in simple timeline view
534
 
    Takes a TimelineFileSource
535
 
    """
536
 
 
537
 
    __gsignals__ = {
538
 
        'delete-me' : (gobject.SIGNAL_RUN_LAST,
539
 
                       gobject.TYPE_NONE,
540
 
                       ( )),
541
 
        'edit-me' : (gobject.SIGNAL_RUN_LAST,
542
 
                     gobject.TYPE_NONE,
543
 
                     ( ))
544
 
        }
545
 
 
546
 
    border = 10
547
 
 
548
 
    # TODO change the factory argument into a TimelineFileSource
549
 
    def __init__(self, filesource):
550
 
        gobject.GObject.__init__(self)
551
 
        self.gc = None
552
 
        self.add_events(gtk.gdk.POINTER_MOTION_MASK | gtk.gdk.ENTER_NOTIFY_MASK
553
 
                        | gtk.gdk.LEAVE_NOTIFY_MASK | gtk.gdk.BUTTON_PRESS_MASK
554
 
                        | gtk.gdk.BUTTON_RELEASE_MASK) # enter, leave, pointer-motion
555
 
        self.width = 0
556
 
        self.height = 0
557
 
        self.filesource = filesource
558
 
        if self.filesource.factory.thumbnail:
559
 
            self.thumbnail = gtk.gdk.pixbuf_new_from_file(self.filesource.factory.thumbnail)
560
 
        else:
561
 
            self.thumbnail = gtk.gdk.pixbuf_new_from_file(os.path.join(get_pixmap_dir(), "pitivi-video.png"))
562
 
        self.thratio = float(self.thumbnail.get_width()) / float(self.thumbnail.get_height())
563
 
        self.pixmap = None
564
 
        self.namelayout = self.create_pango_layout(os.path.basename(unquote(self.filesource.factory.name)))
565
 
        self.lengthlayout = self.create_pango_layout(beautify_length(self.filesource.factory.length))
566
 
 
567
 
        self.connect("expose-event", self._exposeEventCb)
568
 
        self.connect("realize", self._realizeCb)
569
 
        self.connect("configure-event", self._configureEventCb)
570
 
        self.connect("button-press-event", self._buttonPressCb)
571
 
 
572
 
        # popup menus
573
 
        self._popupMenu = gtk.Menu()
574
 
        deleteitem = gtk.MenuItem(_("Remove"))
575
 
        deleteitem.connect("activate", self._deleteMenuItemCb)
576
 
        deleteitem.show()
577
 
        # temporarily deactivate editing for 0.10.3 release !
578
 
#         edititem = gtk.MenuItem(_("Edit"))
579
 
#         edititem.connect("activate", self._editMenuItemCb)
580
 
#         edititem.show()
581
 
        self._popupMenu.append(deleteitem)
582
 
#        self._popupMenu.append(edititem)
583
 
 
584
 
        # drag and drop
585
 
        self.drag_source_set(gtk.gdk.BUTTON1_MASK,
586
 
                             [dnd.URI_TUPLE, dnd.FILESOURCE_TUPLE],
587
 
                             gtk.gdk.ACTION_COPY)
588
 
        self.connect("drag_data_get", self._dragDataGetCb)
589
 
 
590
 
        if not self.filesource.factory.video_info_stream:
591
 
            height = 64 * self.thumbnail.get_height() / self.thumbnail.get_width()
592
 
        else:
593
 
            vi = self.filesource.factory.video_info_stream
594
 
            height = 64 * vi.dar.denom / vi.dar.num
595
 
        smallthumbnail = self.thumbnail.scale_simple(64, height, gtk.gdk.INTERP_BILINEAR)
596
 
 
597
 
        self.drag_source_set_icon_pixbuf(smallthumbnail)
598
 
 
599
 
 
600
 
    ## Drawing
601
 
 
602
 
    def _drawData(self):
603
 
        # actually do the drawing in the pixmap here
604
 
        if self.gc:
605
 
            self.pixmap = gtk.gdk.Pixmap(self.window, self.width, self.height)
606
 
            # background and border
607
 
            self.pixmap.draw_rectangle(self.style.bg_gc[gtk.STATE_NORMAL], True,
608
 
                                       0, 0, self.width, self.height)
609
 
            self.pixmap.draw_rectangle(self.gc, False,
610
 
                                       1, 1, self.width - 2, self.height - 2)
611
 
 
612
 
            namewidth, nameheight = self.namelayout.get_pixel_size()
613
 
            lengthwidth, lengthheight = self.lengthlayout.get_pixel_size()
614
 
 
615
 
            # maximal space left for thumbnail
616
 
            tw = self.width - 2 * self.border
617
 
            th = self.height - 4 * self.border - nameheight - lengthheight
618
 
 
619
 
            # try calculating the desired height using tw
620
 
            sw = tw
621
 
            sh = int(tw / self.thratio)
622
 
            if sh > tw or sh > th:
623
 
                #   calculate the width using th
624
 
                sw = int(th * self.thratio)
625
 
                sh = th
626
 
            if sw < 1 or sh < 1:
627
 
                return
628
 
 
629
 
            # draw name
630
 
            self.pixmap.draw_layout(self.gc, self.border, self.border, self.namelayout)
631
 
 
632
 
            # draw pixbuf
633
 
            subpixbuf = self.thumbnail.scale_simple(sw, sh, gtk.gdk.INTERP_BILINEAR)
634
 
            self.pixmap.draw_pixbuf(self.gc, subpixbuf, 0, 0,
635
 
                                    (self.width - sw) / 2,
636
 
                                    (self.height - sh) / 2,
637
 
                                    sw, sh)
638
 
 
639
 
            # draw length
640
 
            self.pixmap.draw_layout(self.gc,
641
 
                                    self.width - self.border - lengthwidth,
642
 
                                    self.height - self.border - lengthheight,
643
 
                                    self.lengthlayout)
644
 
 
645
 
 
646
 
    def _configureEventCb(self, unused_layout, event):
647
 
        self.width = event.width
648
 
        self.height = event.height
649
 
        self.border = event.width / 20
650
 
        # draw background pixmap
651
 
        if self.gc:
652
 
            self._drawData()
653
 
        return False
654
 
 
655
 
    def _realizeCb(self, unused_widget):
656
 
        self.gc = self.window.new_gc()
657
 
        self.gc.set_line_attributes(2, gtk.gdk.LINE_SOLID,
658
 
                                    gtk.gdk.CAP_ROUND, gtk.gdk.JOIN_ROUND)
659
 
        self.gc.set_background(self.style.white)
660
 
        self._drawData()
661
 
 
662
 
    def _exposeEventCb(self, unused_widget, event):
663
 
        x, y, w, h = event.area
664
 
        self.window.draw_drawable(self.gc, self.pixmap,
665
 
                                  x, y, x, y, w, h)
666
 
        return True
667
 
 
668
 
    def _deleteMenuItemCb(self, unused_menuitem):
669
 
        self.emit('delete-me')
670
 
 
671
 
    def _editMenuItemCb(self, unused_menuitem):
672
 
        self.emit('edit-me')
673
 
 
674
 
    def _buttonPressCb(self, unused_widget, event):
675
 
        gst.debug("button %d" % event.button)
676
 
        if event.button == 3:
677
 
            self._popupMenu.popup(None,None,None,event.button,event.time)
678
 
        else:
679
 
            # FIXME: mark as being selected
680
 
            pass
681
 
 
682
 
    ## Drag and Drop
683
 
 
684
 
    def _dragDataGetCb(self, unused_widget, unused_context, selection,
685
 
                       targetType, unused_eventTime):
686
 
        gst.info("TimelineSource data get, type:%d" % targetType)
687
 
        if targetType in [dnd.TYPE_PITIVI_FILESOURCE, dnd.TYPE_URI_LIST]:
688
 
            selection.set(selection.target, 8, self.filesource.factory.name)
689
 
 
690
 
 
691
 
class SimpleTransitionWidget(gtk.DrawingArea):
692
 
    """ Widget for representing a transition in simple timeline view """
693
 
 
694
 
    # Change to use a TimelineTransitionEffect
695
 
    def __init__(self, transitionfactory):
696
 
        gobject.GObject.__init__(self)
697
 
        self.gc = None
698
 
        self.width = 0
699
 
        self.height = 0
700
 
        self.pixmap = None
701
 
        self.factory = transitionfactory
702
 
        self.connect("expose-event", self._exposeEventCb)
703
 
        self.connect("realize", self._realizeCb)
704
 
        self.connect("configure-event", self._configureEventCb)
705
 
 
706
 
    def _drawData(self):
707
 
        # actually do the drawing in the pixmap here
708
 
        if self.gc:
709
 
            self.pixmap = gtk.gdk.Pixmap(self.window, self.width, self.height)
710
 
            # background and border
711
 
            self.pixmap.draw_rectangle(self.style.white_gc, True,
712
 
                                       0, 0, self.width, self.height)
713
 
            self.pixmap.draw_rectangle(self.gc, False,
714
 
                                       1, 1, self.width - 2, self.height - 2)
715
 
            # draw name
716
 
 
717
 
    def _configureEventCb(self, unused_layout, event):
718
 
        self.width = event.width
719
 
        self.height = event.height
720
 
        # draw background pixmap
721
 
        if self.gc:
722
 
            self._drawData()
723
 
        return False
724
 
 
725
 
    def _realizeCb(self, unused_widget):
726
 
        self.gc = self.window.new_gc()
727
 
        self.gc.set_line_attributes(2, gtk.gdk.LINE_SOLID,
728
 
                                    gtk.gdk.CAP_ROUND, gtk.gdk.JOIN_ROUND)
729
 
        self.gc.set_background(self.style.white)
730
 
        self._drawData()
731
 
 
732
 
    def _exposeEventCb(self, unused_widget, event):
733
 
        x, y, w, h = event.area
734
 
        self.window.draw_drawable(self.gc, self.pixmap,
735
 
                                  x, y, x, y, w, h)
736
 
        return True