~timo-jyrinki/ubuntu/trusty/pitivi/backport_utopic_fixes

« back to all changes in this revision

Viewing changes to pitivi/ui/timelinecanvas.py

  • Committer: Package Import Robot
  • Author(s): Sebastian Dröge
  • Date: 2014-04-05 15:28:16 UTC
  • mfrom: (6.1.13 sid)
  • Revision ID: package-import@ubuntu.com-20140405152816-6lijoax4cngiz5j5
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
 
# PiTiVi , Non-linear video editor
2
 
#
3
 
#       pitivi/ui/timelinecanvas.py
4
 
#
5
 
# Copyright (c) 2009, Brandon Lewis <brandon_lewis@berkeley.edu>
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., 51 Franklin St, Fifth Floor,
20
 
# Boston, MA 02110-1301, USA.
21
 
 
22
 
import gtk
23
 
import goocanvas
24
 
from gettext import gettext as _
25
 
 
26
 
from pitivi.log.loggable import Loggable
27
 
from pitivi.receiver import receiver, handler
28
 
from pitivi.ui.track import Track
29
 
from pitivi.ui.trackobject import TrackObject
30
 
from pitivi.ui.point import Point
31
 
from pitivi.ui.zoominterface import Zoomable
32
 
from pitivi.settings import GlobalSettings
33
 
from pitivi.ui.prefs import PreferencesDialog
34
 
from pitivi.ui.common import TRACK_SPACING, unpack_cairo_pattern, \
35
 
        LAYER_HEIGHT_EXPANDED, LAYER_SPACING
36
 
from pitivi.ui.controller import Controller
37
 
from pitivi.ui.curve import KW_LABEL_Y_OVERFLOW
38
 
from pitivi.ui.common import SPACING
39
 
 
40
 
# cursors to be used for resizing objects
41
 
ARROW = gtk.gdk.Cursor(gtk.gdk.ARROW)
42
 
# TODO: replace this with custom cursor
43
 
PLAYHEAD_CURSOR = gtk.gdk.Cursor(gtk.gdk.SB_H_DOUBLE_ARROW)
44
 
 
45
 
GlobalSettings.addConfigOption('edgeSnapDeadband',
46
 
    section="user-interface",
47
 
    key="edge-snap-deadband",
48
 
    default=5,
49
 
    notify=True)
50
 
 
51
 
PreferencesDialog.addNumericPreference('edgeSnapDeadband',
52
 
    section=_("Behavior"),
53
 
    label=_("Snap distance"),
54
 
    description=_("Threshold (in pixels) at which two clips will snap together "
55
 
        "when dragging or trimming."),
56
 
    lower=0)
57
 
 
58
 
 
59
 
class PlayheadController(Controller, Zoomable):
60
 
 
61
 
    _cursor = PLAYHEAD_CURSOR
62
 
 
63
 
    def __init__(self, *args, **kwargs):
64
 
        Controller.__init__(self, *args, **kwargs)
65
 
 
66
 
    def set_pos(self, item, pos):
67
 
        x, y = pos
68
 
        x += self._hadj.get_value()
69
 
        self._canvas.app.current.seeker.seek(Zoomable.pixelToNs(x))
70
 
 
71
 
 
72
 
class TimelineCanvas(goocanvas.Canvas, Zoomable, Loggable):
73
 
 
74
 
    __gtype_name__ = 'TimelineCanvas'
75
 
    __gsignals__ = {
76
 
        "expose-event": "override",
77
 
    }
78
 
 
79
 
    _tracks = None
80
 
 
81
 
    def __init__(self, instance, timeline=None):
82
 
        goocanvas.Canvas.__init__(self)
83
 
        Zoomable.__init__(self)
84
 
        Loggable.__init__(self)
85
 
        self.app = instance
86
 
        self._selected_sources = []
87
 
        self._tracks = []
88
 
        self.height = 0
89
 
        self._position = 0
90
 
 
91
 
        self._block_size_request = False
92
 
        self.props.integer_layout = True
93
 
        self.props.automatic_bounds = False
94
 
        self.props.clear_background = False
95
 
        self.get_root_item().set_simple_transform(0, 2.0, 1.0, 0)
96
 
 
97
 
        self._createUI()
98
 
        self.timeline = timeline
99
 
        self.settings = instance.settings
100
 
 
101
 
    def _createUI(self):
102
 
        self._cursor = ARROW
103
 
        root = self.get_root_item()
104
 
        self.tracks = goocanvas.Group()
105
 
        self.tracks.set_simple_transform(0, KW_LABEL_Y_OVERFLOW, 1.0, 0)
106
 
        root.add_child(self.tracks)
107
 
        self._marquee = goocanvas.Rect(
108
 
            parent=root,
109
 
            stroke_pattern=unpack_cairo_pattern(0x33CCFF66),
110
 
            fill_pattern=unpack_cairo_pattern(0x33CCFF66),
111
 
            visibility=goocanvas.ITEM_INVISIBLE)
112
 
        self._playhead = goocanvas.Rect(
113
 
            y=-10,
114
 
            parent=root,
115
 
            line_width=1,
116
 
            fill_color_rgba=0x000000FF,
117
 
            stroke_color_rgba=0xFFFFFFFF,
118
 
            width=3)
119
 
        self._playhead_controller = PlayheadController(self._playhead)
120
 
        self.connect("size-allocate", self._size_allocate_cb)
121
 
        root.connect("motion-notify-event", self._selectionDrag)
122
 
        root.connect("button-press-event", self._selectionStart)
123
 
        root.connect("button-release-event", self._selectionEnd)
124
 
        self.height = (LAYER_HEIGHT_EXPANDED + TRACK_SPACING +
125
 
                LAYER_SPACING) * 2
126
 
        # add some padding for the horizontal scrollbar
127
 
        self.height += 21
128
 
        self.set_size_request(-1, self.height)
129
 
 
130
 
    def from_event(self, event):
131
 
        x, y = event.x, event.y
132
 
        x += self.app.gui.timeline.hadj.get_value()
133
 
        return Point(*self.convert_from_pixels(x, y))
134
 
 
135
 
    def setExpanded(self, track_object, expanded):
136
 
        track_ui = None
137
 
        for track in self._tracks:
138
 
            if track.track == track_object:
139
 
                track_ui = track
140
 
                break
141
 
 
142
 
        track_ui.setExpanded(expanded)
143
 
 
144
 
## sets the cursor as appropriate
145
 
 
146
 
    def _mouseEnterCb(self, unused_item, unused_target, event):
147
 
        event.window.set_cursor(self._cursor)
148
 
        return True
149
 
 
150
 
    def do_expose_event(self, event):
151
 
        allocation = self.get_allocation()
152
 
        width = allocation.width
153
 
        height = allocation.height
154
 
        # draw the canvas background
155
 
        # we must have props.clear_background set to False
156
 
 
157
 
        self.style.apply_default_background(event.window,
158
 
            True,
159
 
            gtk.STATE_ACTIVE,
160
 
            event.area,
161
 
            event.area.x, event.area.y,
162
 
            event.area.width, event.area.height)
163
 
 
164
 
        goocanvas.Canvas.do_expose_event(self, event)
165
 
 
166
 
## implements selection marquee
167
 
 
168
 
    _selecting = False
169
 
    _mousedown = None
170
 
    _marquee = None
171
 
    _got_motion_notify = False
172
 
 
173
 
    def getItemsInArea(self, x1, y1, x2, y2):
174
 
        '''
175
 
        Permits to get the Non UI L{Track}/L{TrackObject} in a list of set
176
 
        corresponding to the L{Track}/L{TrackObject} which are in the are
177
 
 
178
 
        @param x1: The horizontal coordinate of the up left corner of the area
179
 
        @type x1: An C{int}
180
 
        @param y1: The vertical coordinate of the up left corner of the area
181
 
        @type y1: An C{int}
182
 
        @param x2: The horizontal coordinate of the down right corner of the
183
 
                   area
184
 
        @type x2: An C{int}
185
 
        @param x2: The vertical coordinate of the down right corner of the area
186
 
        @type x2: An C{int}
187
 
 
188
 
        @returns: A list of L{Track}, L{TrackObject} tuples
189
 
        '''
190
 
        items = self.get_items_in_area(goocanvas.Bounds(x1, y1, x2, y2), True,
191
 
            True, True)
192
 
        if not items:
193
 
            return [], []
194
 
 
195
 
        tracks = set()
196
 
        track_objects = set()
197
 
 
198
 
        for item in items:
199
 
            if isinstance(item, Track):
200
 
                tracks.add(item.track)
201
 
            elif isinstance(item, TrackObject):
202
 
                track_objects.add(item.element)
203
 
 
204
 
        return tracks, track_objects
205
 
 
206
 
    def _normalize(self, p1, p2, adjust=0):
207
 
        w, h = p2 - p1
208
 
        x, y = p1
209
 
        if w - adjust < 0:
210
 
            w = abs(w - adjust)
211
 
            x -= w
212
 
        else:
213
 
            w -= adjust
214
 
        if h < 0:
215
 
            h = abs(h)
216
 
            y -= h
217
 
        return (x, y), (w, h)
218
 
 
219
 
    def _selectionDrag(self, item, target, event):
220
 
        if self._selecting:
221
 
            self._got_motion_notify = True
222
 
            cur = self.from_event(event)
223
 
            pos, size = self._normalize(self._mousedown, cur,
224
 
                self.app.gui.timeline.hadj.get_value())
225
 
            self._marquee.props.x, self._marquee.props.y = pos
226
 
            self._marquee.props.width, self._marquee.props.height = size
227
 
            return True
228
 
        return False
229
 
 
230
 
    def _selectionStart(self, item, target, event):
231
 
        self._selecting = True
232
 
        self._marquee.props.visibility = goocanvas.ITEM_VISIBLE
233
 
        self._mousedown = self.from_event(event)
234
 
        self._marquee.props.width = 0
235
 
        self._marquee.props.height = 0
236
 
        self.pointer_grab(self.get_root_item(), gtk.gdk.POINTER_MOTION_MASK |
237
 
            gtk.gdk.BUTTON_RELEASE_MASK, self._cursor, event.time)
238
 
        return True
239
 
 
240
 
    def _selectionEnd(self, item, target, event):
241
 
        seeker = self.app.current.seeker
242
 
        self.pointer_ungrab(self.get_root_item(), event.time)
243
 
        self._selecting = False
244
 
        self._marquee.props.visibility = goocanvas.ITEM_INVISIBLE
245
 
        if not self._got_motion_notify:
246
 
            self.timeline.setSelectionTo(set(), 0)
247
 
            seeker.seek(Zoomable.pixelToNs(event.x))
248
 
        else:
249
 
            self._got_motion_notify = False
250
 
            mode = 0
251
 
            if event.get_state() & gtk.gdk.SHIFT_MASK:
252
 
                mode = 1
253
 
            if event.get_state() & gtk.gdk.CONTROL_MASK:
254
 
                mode = 2
255
 
            self.timeline.setSelectionTo(self._objectsUnderMarquee(), mode)
256
 
        return True
257
 
 
258
 
    def _objectsUnderMarquee(self):
259
 
        items = self.get_items_in_area(self._marquee.get_bounds(), True, True,
260
 
            True)
261
 
        if items:
262
 
            return set((item.element for item in items if isinstance(item,
263
 
                TrackObject) and item.bg in items))
264
 
        return set()
265
 
 
266
 
## playhead implementation
267
 
 
268
 
    position = 0
269
 
 
270
 
    def timelinePositionChanged(self, position):
271
 
        self.position = position
272
 
        self._playhead.props.x = self.nsToPixel(position)
273
 
 
274
 
    max_duration = 0
275
 
 
276
 
    def setMaxDuration(self, duration):
277
 
        self.max_duration = duration
278
 
        self._request_size()
279
 
 
280
 
    def _request_size(self):
281
 
        alloc = self.get_allocation()
282
 
        self.set_bounds(0, 0, alloc.width, alloc.height)
283
 
        self._playhead.props.height = (self.height + SPACING)
284
 
 
285
 
    def _size_allocate_cb(self, widget, allocation):
286
 
        self._request_size()
287
 
 
288
 
    def zoomChanged(self):
289
 
        self.queue_draw()
290
 
        if self.timeline:
291
 
            self.timeline.dead_band = self.pixelToNs(
292
 
                self.settings.edgeSnapDeadband)
293
 
            self.timelinePositionChanged(self.position)
294
 
 
295
 
## settings callbacks
296
 
 
297
 
    def _setSettings(self):
298
 
        self.zoomChanged()
299
 
 
300
 
    settings = receiver(_setSettings)
301
 
 
302
 
    @handler(settings, "edgeSnapDeadbandChanged")
303
 
    def _edgeSnapDeadbandChangedCb(self, settings):
304
 
        self.zoomChanged()
305
 
 
306
 
## Timeline callbacks
307
 
 
308
 
    def _set_timeline(self):
309
 
        while self._tracks:
310
 
            self._trackRemoved(None, 0)
311
 
        if self.timeline:
312
 
            for track in self.timeline.tracks:
313
 
                self._trackAdded(None, track)
314
 
        self.zoomChanged()
315
 
 
316
 
    timeline = receiver(_set_timeline)
317
 
 
318
 
    @handler(timeline, "track-added")
319
 
    def _trackAdded(self, timeline, track):
320
 
        track = Track(self.app, track, self.timeline)
321
 
        self._tracks.append(track)
322
 
        track.set_canvas(self)
323
 
        self.tracks.add_child(track)
324
 
        self.regroupTracks()
325
 
 
326
 
    @handler(timeline, "track-removed")
327
 
    def _trackRemoved(self, unused_timeline, position):
328
 
        track = self._tracks[position]
329
 
        del self._tracks[position]
330
 
        track.remove()
331
 
        self.regroupTracks()
332
 
 
333
 
    def regroupTracks(self):
334
 
        height = 0
335
 
        for i, track in enumerate(self._tracks):
336
 
            track.set_simple_transform(0, height, 1, 0)
337
 
            height += track.height + TRACK_SPACING
338
 
        self.height = height
339
 
        self._request_size()