~timo-jyrinki/ubuntu/trusty/pitivi/merge_debian_0.93-3

« back to all changes in this revision

Viewing changes to pitivi/ui/trackobject.py

  • Committer: Timo Jyrinki
  • Author(s): Sebastian Dröge
  • Date: 2014-04-05 13:28:16 UTC
  • mfrom: (1.5.8)
  • Revision ID: timo.jyrinki@canonical.com-20140405132816-wmv1rhbtmlxmx3ag
Tags: 0.93-3
* debian/control:
  + Depend on python-gi (>= 3.10), older versions do not work
    with pitivi (Closes: #732813).
  + Add missing dependency on gir1.2-clutter-gst-2.0 (Closes: #743692).
  + Add suggests on gir1.2-notify-0.7 and gir1.2-gnomedesktop-3.0.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
import goocanvas
2
 
import gobject
3
 
import gtk
4
 
import os.path
5
 
import pango
6
 
import cairo
7
 
import pitivi.configure as configure
8
 
from urllib import unquote
9
 
from gettext import gettext as _
10
 
from pitivi.receiver import receiver, handler
11
 
from view import View
12
 
import controller
13
 
from zoominterface import Zoomable
14
 
from pitivi.timeline.track import TrackError
15
 
from pitivi.timeline.timeline import SELECT, SELECT_ADD, UNSELECT, \
16
 
    SELECT_BETWEEN, MoveContext, TrimStartContext, TrimEndContext
17
 
from preview import Preview
18
 
from pitivi.ui.curve import Curve
19
 
import gst
20
 
from common import LAYER_HEIGHT_EXPANDED, LAYER_HEIGHT_COLLAPSED
21
 
from common import LAYER_SPACING, unpack_cairo_pattern, unpack_cairo_gradient
22
 
from pitivi.ui.point import Point
23
 
from pitivi.ui.prefs import PreferencesDialog
24
 
from pitivi.settings import GlobalSettings
25
 
from pitivi.stream import AudioStream, VideoStream
26
 
 
27
 
LEFT_SIDE = gtk.gdk.Cursor(gtk.gdk.LEFT_SIDE)
28
 
RIGHT_SIDE = gtk.gdk.Cursor(gtk.gdk.RIGHT_SIDE)
29
 
ARROW = gtk.gdk.Cursor(gtk.gdk.ARROW)
30
 
TRIMBAR_PIXBUF = gtk.gdk.pixbuf_new_from_file(
31
 
    os.path.join(configure.get_pixmap_dir(), "trimbar-normal.png"))
32
 
TRIMBAR_PIXBUF_FOCUS = gtk.gdk.pixbuf_new_from_file(
33
 
    os.path.join(configure.get_pixmap_dir(), "trimbar-focused.png"))
34
 
NAME_HOFFSET = 10
35
 
NAME_VOFFSET = 5
36
 
NAME_PADDING = 2
37
 
NAME_PADDING2X = 2 * NAME_PADDING
38
 
 
39
 
import gst
40
 
 
41
 
GlobalSettings.addConfigOption('videoClipBg',
42
 
    section='user-interface',
43
 
    key='videoclip-background',
44
 
    default=0x000000A0,
45
 
    notify=True)
46
 
 
47
 
PreferencesDialog.addColorPreference('videoClipBg',
48
 
    section=_("Appearance"),
49
 
    label=_("Color for video clips"),
50
 
    description=_("The background color for clips in video tracks."))
51
 
 
52
 
GlobalSettings.addConfigOption('audioClipBg',
53
 
    section='user-interface',
54
 
    key='audioclip-background',
55
 
    default=0x4E9A06C0,
56
 
    notify=True)
57
 
 
58
 
PreferencesDialog.addColorPreference('audioClipBg',
59
 
    section=_("Appearance"),
60
 
    label=_("Color for audio clips"),
61
 
    description=_("The background color for clips in audio tracks."))
62
 
 
63
 
GlobalSettings.addConfigOption('selectedColor',
64
 
    section='user-interface',
65
 
    key='selected-color',
66
 
    default=0x00000077,
67
 
    notify=True)
68
 
 
69
 
PreferencesDialog.addColorPreference('selectedColor',
70
 
    section=_("Appearance"),
71
 
    label=_("Selection color"),
72
 
    description=_("Selected clips will be tinted with this color."))
73
 
 
74
 
GlobalSettings.addConfigOption('clipFontDesc',
75
 
    section='user-interface',
76
 
    key='clip-font-name',
77
 
    default="Sans 9",
78
 
    notify=True)
79
 
 
80
 
PreferencesDialog.addFontPreference('clipFontDesc',
81
 
    section=_('Appearance'),
82
 
    label=_("Clip font"),
83
 
    description=_("The font to use for clip titles"))
84
 
 
85
 
GlobalSettings.addConfigOption('clipFontColor',
86
 
    section='user-interface',
87
 
    key='clip-font-color',
88
 
    default=0xFFFFFFAA,
89
 
    notify=True)
90
 
 
91
 
 
92
 
def text_size(text):
93
 
    ink, logical = text.get_natural_extents()
94
 
    x1, y1, x2, y2 = [pango.PIXELS(x) for x in logical]
95
 
    return x2 - x1, y2 - y1
96
 
 
97
 
 
98
 
class TimelineController(controller.Controller):
99
 
 
100
 
    _cursor = ARROW
101
 
    _context = None
102
 
    _handle_enter_leave = False
103
 
 
104
 
    def enter(self, unused, unused2):
105
 
        self._view.focus()
106
 
 
107
 
    def leave(self, unused, unused2):
108
 
        self._view.unfocus()
109
 
 
110
 
    def drag_start(self, item, target, event):
111
 
        if not self._view.element.selected:
112
 
            self._view.timeline.selection.setToObj(self._view.element, SELECT)
113
 
        tx = self._view.props.parent.get_transform()
114
 
        # store y offset for later priority calculation
115
 
        self._y_offset = tx[5]
116
 
        # zero y component of mousdown coordiante
117
 
        self._mousedown = Point(self._mousedown[0], 0)
118
 
 
119
 
    def drag_end(self, item, target, event):
120
 
        self._context.finish()
121
 
        self._context = None
122
 
        self._view.app.action_log.commit()
123
 
 
124
 
    def set_pos(self, item, pos):
125
 
        x, y = pos
126
 
        position = Zoomable.pixelToNs(x + self._hadj.get_value())
127
 
        priority = int((y - self._y_offset + self._vadj.get_value()) //
128
 
            (LAYER_HEIGHT_EXPANDED + LAYER_SPACING))
129
 
        self._context.setMode(self._getMode())
130
 
        self._context.editTo(position, priority)
131
 
 
132
 
    def _getMode(self):
133
 
        if self._shift_down:
134
 
            return self._context.RIPPLE
135
 
        elif self._control_down:
136
 
            return self._context.ROLL
137
 
        return self._context.DEFAULT
138
 
 
139
 
    def key_press(self, keyval):
140
 
        if self._context:
141
 
            self._context.setMode(self._getMode())
142
 
 
143
 
    def key_release(self, keyval):
144
 
        if self._context:
145
 
            self._context.setMode(self._getMode())
146
 
 
147
 
 
148
 
class TrimHandle(View, goocanvas.Image, Zoomable):
149
 
 
150
 
    """A component of a TrackObject which manage's the source's edit
151
 
    points"""
152
 
 
153
 
    element = receiver()
154
 
 
155
 
    def __init__(self, instance, element, timeline, **kwargs):
156
 
        self.app = instance
157
 
        self.element = element
158
 
        self.timeline = timeline
159
 
        goocanvas.Image.__init__(self,
160
 
            pixbuf=TRIMBAR_PIXBUF,
161
 
            line_width=0,
162
 
            pointer_events=goocanvas.EVENTS_FILL,
163
 
            **kwargs
164
 
        )
165
 
        View.__init__(self)
166
 
        Zoomable.__init__(self)
167
 
 
168
 
    def focus(self):
169
 
        self.props.pixbuf = TRIMBAR_PIXBUF_FOCUS
170
 
 
171
 
    def unfocus(self):
172
 
        self.props.pixbuf = TRIMBAR_PIXBUF
173
 
 
174
 
 
175
 
class StartHandle(TrimHandle):
176
 
 
177
 
    """Subclass of TrimHandle wich sets the object's start time"""
178
 
 
179
 
    class Controller(TimelineController):
180
 
 
181
 
        _cursor = LEFT_SIDE
182
 
 
183
 
        def drag_start(self, item, target, event):
184
 
            TimelineController.drag_start(self, item, target, event)
185
 
            self._context = TrimStartContext(self._view.timeline,
186
 
                self._view.element,
187
 
                self._view.timeline.selection.getSelectedTrackObjs())
188
 
            self._view.app.action_log.begin("trim object")
189
 
 
190
 
 
191
 
class EndHandle(TrimHandle):
192
 
 
193
 
    """Subclass of TrimHandle which sets the objects's end time"""
194
 
 
195
 
    class Controller(TimelineController):
196
 
 
197
 
        _cursor = RIGHT_SIDE
198
 
 
199
 
        def drag_start(self, item, target, event):
200
 
            TimelineController.drag_start(self, item, target, event)
201
 
            self._context = TrimEndContext(self._view.timeline,
202
 
                self._view.element,
203
 
                self._view.timeline.selection.getSelectedTrackObjs())
204
 
            self._view.app.action_log.begin("trim object")
205
 
 
206
 
 
207
 
class TrackObject(View, goocanvas.Group, Zoomable):
208
 
 
209
 
    class Controller(TimelineController):
210
 
 
211
 
        _handle_enter_leave = True
212
 
 
213
 
        def drag_start(self, item, target, event):
214
 
            TimelineController.drag_start(self, item, target, event)
215
 
            self._context = MoveContext(self._view.timeline,
216
 
                self._view.element,
217
 
                self._view.timeline.selection.getSelectedTrackObjs())
218
 
            self._view.app.action_log.begin("move object")
219
 
 
220
 
        def _getMode(self):
221
 
            if self._shift_down:
222
 
                return self._context.RIPPLE
223
 
            return self._context.DEFAULT
224
 
 
225
 
        def click(self, pos):
226
 
            timeline = self._view.timeline
227
 
            element = self._view.element
228
 
            element_end = element.start + element.duration
229
 
            if self._last_event.get_state() & gtk.gdk.SHIFT_MASK:
230
 
                timeline.setSelectionToObj(element, SELECT_BETWEEN)
231
 
            elif self._last_event.get_state() & gtk.gdk.CONTROL_MASK:
232
 
                if element.selected:
233
 
                    mode = UNSELECT
234
 
                else:
235
 
                    mode = SELECT_ADD
236
 
                timeline.setSelectionToObj(element, mode)
237
 
            else:
238
 
                x, y = pos
239
 
                x += self._hadj.get_value()
240
 
                self._view.app.current.seeker.seek(Zoomable.pixelToNs(x))
241
 
                timeline.setSelectionToObj(element, SELECT)
242
 
 
243
 
    def __init__(self, instance, element, track, timeline):
244
 
        goocanvas.Group.__init__(self)
245
 
        View.__init__(self)
246
 
        Zoomable.__init__(self)
247
 
        self.app = instance
248
 
        self.track = track
249
 
        self.timeline = timeline
250
 
        self.namewidth = 0
251
 
        self.nameheight = 0
252
 
 
253
 
        self.bg = goocanvas.Rect(
254
 
            height=self.height,
255
 
            line_width=1)
256
 
 
257
 
        self.content = Preview(self.app, element)
258
 
 
259
 
        self.name = goocanvas.Text(
260
 
            x=NAME_HOFFSET + NAME_PADDING,
261
 
            y=NAME_VOFFSET + NAME_PADDING,
262
 
            operator=cairo.OPERATOR_ADD,
263
 
            alignment=pango.ALIGN_LEFT)
264
 
        self.namebg = goocanvas.Rect(
265
 
            radius_x=2,
266
 
            radius_y=2,
267
 
            x=NAME_HOFFSET,
268
 
            y=NAME_VOFFSET,
269
 
            line_width=0)
270
 
 
271
 
        self.start_handle = StartHandle(self.app, element, timeline,
272
 
            height=self.height)
273
 
        self.end_handle = EndHandle(self.app, element, timeline,
274
 
            height=self.height)
275
 
 
276
 
        self.selection_indicator = goocanvas.Rect(
277
 
            visibility=goocanvas.ITEM_INVISIBLE,
278
 
            line_width=0.0,
279
 
            height=self.height)
280
 
 
281
 
        for thing in (self.bg, self.content, self.selection_indicator,
282
 
            self.start_handle, self.end_handle, self.namebg, self.name):
283
 
            self.add_child(thing)
284
 
 
285
 
        for prop, interpolator in element.getInterpolators().itervalues():
286
 
            self.add_child(Curve(instance, element, interpolator))
287
 
 
288
 
        self.element = element
289
 
        self.settings = instance.settings
290
 
        self.unfocus()
291
 
 
292
 
## Properties
293
 
 
294
 
    _height = LAYER_HEIGHT_EXPANDED
295
 
 
296
 
    def setHeight(self, height):
297
 
        self._height = height
298
 
        self.start_handle.props.height = height
299
 
        self.end_handle.props.height = height
300
 
        self._update()
301
 
 
302
 
    def getHeight(self):
303
 
        return self._height
304
 
 
305
 
    height = property(getHeight, setHeight)
306
 
 
307
 
    _expanded = True
308
 
 
309
 
    def setExpanded(self, expanded):
310
 
        self._expanded = expanded
311
 
        if not self._expanded:
312
 
            self.height = LAYER_HEIGHT_COLLAPSED
313
 
            self.content.props.visibility = goocanvas.ITEM_INVISIBLE
314
 
            self.namebg.props.visibility = goocanvas.ITEM_INVISIBLE
315
 
            self.bg.props.height = LAYER_HEIGHT_COLLAPSED
316
 
            self.name.props.y = 0
317
 
        else:
318
 
            self.height = LAYER_HEIGHT_EXPANDED
319
 
            self.content.props.visibility = goocanvas.ITEM_VISIBLE
320
 
            self.namebg.props.visibility = goocanvas.ITEM_VISIBLE
321
 
            self.bg.props.height = LAYER_HEIGHT_EXPANDED
322
 
            self.height = LAYER_HEIGHT_EXPANDED
323
 
            self.name.props.y = NAME_VOFFSET + NAME_PADDING
324
 
 
325
 
    def getExpanded(self):
326
 
        return self._expanded
327
 
 
328
 
    expanded = property(getExpanded, setExpanded)
329
 
 
330
 
## Public API
331
 
 
332
 
    def focus(self):
333
 
        self.start_handle.props.visibility = goocanvas.ITEM_VISIBLE
334
 
        self.end_handle.props.visibility = goocanvas.ITEM_VISIBLE
335
 
        self.raise_(None)
336
 
 
337
 
    def unfocus(self):
338
 
        self.start_handle.props.visibility = goocanvas.ITEM_INVISIBLE
339
 
        self.end_handle.props.visibility = goocanvas.ITEM_INVISIBLE
340
 
 
341
 
    def zoomChanged(self):
342
 
        self._update()
343
 
 
344
 
## settings signals
345
 
 
346
 
    def _setSettings(self):
347
 
        if self.settings:
348
 
            self.clipAppearanceSettingsChanged()
349
 
 
350
 
    settings = receiver(_setSettings)
351
 
 
352
 
    @handler(settings, "audioClipBgChanged")
353
 
    @handler(settings, "videoClipBgChanged")
354
 
    @handler(settings, "selectedColorChanged")
355
 
    @handler(settings, "clipFontDescChanged")
356
 
    def clipAppearanceSettingsChanged(self, *args):
357
 
        if isinstance(self.element.stream, VideoStream):
358
 
            color = self.settings.videoClipBg
359
 
        elif isinstance(self.element.stream, AudioStream):
360
 
            color = self.settings.audioClipBg
361
 
        pattern = unpack_cairo_gradient(color)
362
 
        self.bg.props.fill_pattern = pattern
363
 
 
364
 
        self.namebg.props.fill_pattern = pattern
365
 
 
366
 
        self.selection_indicator.props.fill_pattern = unpack_cairo_pattern(
367
 
            self.settings.selectedColor)
368
 
 
369
 
        self.name.props.font = self.settings.clipFontDesc
370
 
        self.name.props.fill_pattern = unpack_cairo_pattern(
371
 
            self.settings.clipFontColor)
372
 
        twidth, theight = text_size(self.name)
373
 
        self.namewidth = twidth
374
 
        self.nameheight = theight
375
 
        self._update()
376
 
 
377
 
## element signals
378
 
 
379
 
    def _setElement(self):
380
 
        if self.element:
381
 
            self.name.props.text = os.path.basename(unquote(
382
 
                self.element.factory.name))
383
 
            twidth, theight = text_size(self.name)
384
 
            self.namewidth = twidth
385
 
            self.nameheight = theight
386
 
            self._update()
387
 
 
388
 
    element = receiver(_setElement)
389
 
 
390
 
    @handler(element, "start-changed")
391
 
    @handler(element, "duration-changed")
392
 
    def startChangedCb(self, track_object, start):
393
 
        self._update()
394
 
 
395
 
    @handler(element, "selected-changed")
396
 
    def selected_changed(self, element, state):
397
 
        if element.selected:
398
 
            self.selection_indicator.props.visibility = goocanvas.ITEM_VISIBLE
399
 
        else:
400
 
            self.selection_indicator.props.visibility = \
401
 
                goocanvas.ITEM_INVISIBLE
402
 
 
403
 
    @handler(element, "priority-changed")
404
 
    def priority_changed(self, element, priority):
405
 
        self._update()
406
 
 
407
 
    def _update(self):
408
 
        try:
409
 
            x = self.nsToPixel(self.element.start)
410
 
        except Exception, e:
411
 
            print self.element.start
412
 
            raise Exception(e)
413
 
        y = (self.height + LAYER_SPACING) * self.element.priority
414
 
        self.set_simple_transform(x, y, 1, 0)
415
 
        width = self.nsToPixel(self.element.duration)
416
 
        min_width = self.start_handle.props.width * 2
417
 
        if width < min_width:
418
 
            width = min_width
419
 
        w = width - self.end_handle.props.width
420
 
        self.name.props.clip_path = "M%g,%g h%g v%g h-%g z" % (
421
 
            0, 0, w, self.height, w)
422
 
        self.bg.props.width = width
423
 
        self.selection_indicator.props.width = width
424
 
        self.end_handle.props.x = w
425
 
        if self.expanded:
426
 
            if w - NAME_HOFFSET > 0:
427
 
                self.namebg.props.height = self.nameheight + NAME_PADDING2X
428
 
                self.namebg.props.width = min(w - NAME_HOFFSET,
429
 
                    self.namewidth + NAME_PADDING2X)
430
 
                self.namebg.props.visibility = goocanvas.ITEM_VISIBLE
431
 
            else:
432
 
                self.namebg.props.visibility = goocanvas.ITEM_INVISIBLE