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

« back to all changes in this revision

Viewing changes to pitivi/ui/ruler.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/ruler.py
4
 
#
5
 
# Copyright (c) 2006, 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., 51 Franklin St, Fifth Floor,
20
 
# Boston, MA 02110-1301, USA.
21
 
 
22
 
"""
23
 
Widget for the complex view ruler
24
 
"""
25
 
 
26
 
import gobject
27
 
import gtk
28
 
import gst
29
 
import cairo
30
 
from pitivi.ui.zoominterface import Zoomable
31
 
from pitivi.log.loggable import Loggable
32
 
from pitivi.utils import time_to_string
33
 
 
34
 
 
35
 
class ScaleRuler(gtk.DrawingArea, Zoomable, Loggable):
36
 
 
37
 
    __gsignals__ = {
38
 
        "expose-event": "override",
39
 
        "button-press-event": "override",
40
 
        "button-release-event": "override",
41
 
        "motion-notify-event": "override",
42
 
        "scroll-event": "override",
43
 
        "seek": (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
44
 
                [gobject.TYPE_UINT64])
45
 
        }
46
 
 
47
 
    border = 0
48
 
    min_tick_spacing = 3
49
 
    scale = [0, 0, 0, 0.5, 1, 2, 5, 10, 15, 30, 60, 120, 300, 600, 3600]
50
 
    subdivide = ((1, 1.0), (2, 0.5), (10, .25))
51
 
 
52
 
    def __init__(self, instance, hadj):
53
 
        gtk.DrawingArea.__init__(self)
54
 
        Zoomable.__init__(self)
55
 
        Loggable.__init__(self)
56
 
        self.log("Creating new ScaleRule")
57
 
        self.add_events(gtk.gdk.POINTER_MOTION_MASK |
58
 
            gtk.gdk.BUTTON_PRESS_MASK | gtk.gdk.BUTTON_RELEASE_MASK)
59
 
        self.hadj = hadj
60
 
        hadj.connect("value-changed", self._hadjValueChangedCb)
61
 
 
62
 
        # double-buffering properties
63
 
        self.pixmap = None
64
 
        # all values are in pixels
65
 
        self.pixmap_offset = 0
66
 
        self.pixmap_visible_width = 0
67
 
        self.pixmap_allocated_width = 0
68
 
        self.pixmap_old_allocated_width = -1
69
 
        # This is the number of visible_width we allocate for the pixmap
70
 
        self.pixmap_multiples = 2
71
 
 
72
 
        # position is in nanoseconds
73
 
        self.position = 0
74
 
        self.pressed = False
75
 
        self.shaded_duration = gst.CLOCK_TIME_NONE
76
 
        self.max_duration = gst.CLOCK_TIME_NONE
77
 
        self.min_frame_spacing = 5.0
78
 
        self.frame_height = 5.0
79
 
        self.frame_rate = gst.Fraction(1 / 1)
80
 
        self.app = instance
81
 
        self.need_update = True
82
 
 
83
 
    def _hadjValueChangedCb(self, hadj):
84
 
        self.pixmap_offset = self.hadj.get_value()
85
 
        self.need_update = True
86
 
        self.queue_draw()
87
 
 
88
 
## Zoomable interface override
89
 
 
90
 
    def zoomChanged(self):
91
 
        self.need_update = True
92
 
        self.queue_draw()
93
 
 
94
 
## timeline position changed method
95
 
 
96
 
    def timelinePositionChanged(self, value, unused_frame=None):
97
 
        self.debug("value : %r", value)
98
 
        ppos = max(self.nsToPixel(self.position) - 1, 0)
99
 
        self.position = value
100
 
        npos = max(self.nsToPixel(self.position) - 1, 0)
101
 
        self._hadjValueChangedCb(self.hadj)
102
 
        height = self.get_allocation().height
103
 
        self.window.invalidate_rect((ppos, 0, 2, height), True)
104
 
        self.window.invalidate_rect((npos, 0, 2, height), True)
105
 
 
106
 
## gtk.Widget overrides
107
 
 
108
 
    def do_expose_event(self, event):
109
 
        self.debug("exposing ScaleRuler %s", list(event.area))
110
 
        x, y, width, height = event.area
111
 
        # if (x < self.pixmap_offset) or (x+width > self.pixmap_offset + self.pixmap_allocated_width):
112
 
        #     self.debug("exposing outside boundaries !")
113
 
        #     self.pixmap_offset = max(0, x + (width / 2) - (self.pixmap_allocated_width / 2))
114
 
        #     self.debug("offset is now %d", self.pixmap_offset)
115
 
        #     self.doPixmap()
116
 
        #     width = self.pixmap_allocated_width
117
 
 
118
 
        if self.need_update:
119
 
            self.doPixmap()
120
 
            self.need_update = False
121
 
 
122
 
        # double buffering power !
123
 
        self.window.draw_drawable(
124
 
            self.style.fg_gc[gtk.STATE_NORMAL],
125
 
            self.pixmap,
126
 
            x, y,
127
 
            x, y, width, height)
128
 
        # draw the position
129
 
        context = self.window.cairo_create()
130
 
        self.drawPosition(context, self.get_allocation())
131
 
        return False
132
 
 
133
 
    def do_button_press_event(self, event):
134
 
        self.debug("button pressed at x:%d", event.x)
135
 
        if self.getShadedDuration() <= 0:
136
 
            self.debug("no timeline to seek on, ignoring")
137
 
        self.pressed = True
138
 
        # seek at position
139
 
        cur = self.pixelToNs(event.x + self.pixmap_offset)
140
 
        self._doSeek(cur)
141
 
        return True
142
 
 
143
 
    def do_button_release_event(self, event):
144
 
        self.debug("button released at x:%d", event.x)
145
 
        self.pressed = False
146
 
        return False
147
 
 
148
 
    def do_motion_notify_event(self, event):
149
 
        self.debug("motion at event.x %d", event.x)
150
 
        if self.pressed:
151
 
            # seek at position
152
 
            cur = self.pixelToNs(event.x + self.pixmap_offset)
153
 
            self._doSeek(cur)
154
 
        return False
155
 
 
156
 
    def do_scroll_event(self, event):
157
 
        if event.direction == gtk.gdk.SCROLL_UP:
158
 
            Zoomable.zoomIn()
159
 
        elif event.direction == gtk.gdk.SCROLL_DOWN:
160
 
            Zoomable.zoomOut()
161
 
        # TODO: seek timeline back/forward
162
 
        elif event.direction == gtk.gdk.SCROLL_LEFT:
163
 
            pass
164
 
        elif event.direction == gtk.gdk.SCROLL_RIGHT:
165
 
            pass
166
 
 
167
 
## Seeking methods
168
 
 
169
 
    def _seekerSeekCb(self, seeker, position, format):
170
 
        # clamping values within acceptable range
171
 
        duration = self.getShadedDuration()
172
 
        if duration in (0, gst.CLOCK_TIME_NONE):
173
 
            return
174
 
        if position > duration:
175
 
            position = duration - (1 * gst.MSECOND)
176
 
        elif position < 0:
177
 
            position = 0
178
 
 
179
 
        self.emit('seek', position)
180
 
 
181
 
        return False
182
 
 
183
 
    def _doSeek(self, value, format=gst.FORMAT_TIME, on_idle=False):
184
 
        self.app.current.seeker.seek(value, format, on_idle)
185
 
 
186
 
## Drawing methods
187
 
 
188
 
    def doPixmap(self):
189
 
        """ (re)create the buffered drawable for the Widget """
190
 
        # we can't create the pixmap if we're not realized
191
 
        if not self.flags() & gtk.REALIZED:
192
 
            return
193
 
 
194
 
        # We want to benefit from double-buffering (so as not to recreate the
195
 
        # ruler graphics all the time) yet we don't want to allocate insanely
196
 
        # big pixmaps (which would result in big memory usage, or even not being
197
 
        # able to allocate such a big pixmap).
198
 
        #
199
 
        # We therefore create a pixmap with a width of 2 times the maximum viewable
200
 
        # width (allocation.width)
201
 
 
202
 
        allocation = self.get_allocation()
203
 
 
204
 
        if (allocation.width != self.pixmap_old_allocated_width):
205
 
            if self.pixmap:
206
 
                del self.pixmap
207
 
            self.pixmap = gtk.gdk.Pixmap(self.window, allocation.width,
208
 
                                         allocation.height)
209
 
            self.pixmap_old_allocated_width = allocation.width
210
 
 
211
 
        self.drawBackground(allocation)
212
 
        self.drawRuler(allocation)
213
 
 
214
 
    def setProjectFrameRate(self, rate):
215
 
        self.frame_rate = rate
216
 
 
217
 
        # set the lowest scale based on project framerate
218
 
        self.scale[0] = float(2 / rate)
219
 
        self.scale[1] = float(5 / rate)
220
 
        self.scale[2] = float(10 / rate)
221
 
 
222
 
    def setShadedDuration(self, duration):
223
 
        self.info("start/duration changed")
224
 
 
225
 
        self.shaded_duration = duration
226
 
 
227
 
        if duration < self.position:
228
 
            position = duration - gst.NSECOND
229
 
        else:
230
 
            position = self.position
231
 
        self.need_update = True
232
 
        self.queue_draw()
233
 
 
234
 
    def getShadedDuration(self):
235
 
        return self.shaded_duration
236
 
 
237
 
    def getShadedDurationWidth(self):
238
 
        return self.nsToPixel(self.getShadedDuration())
239
 
 
240
 
    def setMaxDuration(self, duration):
241
 
        self.max_duration = duration
242
 
 
243
 
    def drawBackground(self, allocation):
244
 
        self.pixmap.draw_rectangle(
245
 
            self.style.bg_gc[gtk.STATE_NORMAL],
246
 
            True,
247
 
            0, 0,
248
 
            allocation.width, allocation.height)
249
 
 
250
 
        offset = int(Zoomable.nsToPixel(self.getShadedDuration())) - self.pixmap_offset
251
 
        if offset > 0:
252
 
            self.pixmap.draw_rectangle(
253
 
                self.style.bg_gc[gtk.STATE_ACTIVE],
254
 
                True,
255
 
                0, 0,
256
 
                int(offset),
257
 
                int(allocation.height))
258
 
 
259
 
    def drawRuler(self, allocation):
260
 
        layout = self.create_pango_layout(time_to_string(0))
261
 
        textwidth, textheight = layout.get_pixel_size()
262
 
 
263
 
        for scale in self.scale:
264
 
            spacing = Zoomable.zoomratio * scale
265
 
            if spacing >= textwidth * 1.5:
266
 
                break
267
 
 
268
 
        offset = self.pixmap_offset % spacing
269
 
 
270
 
        zoomRatio = self.zoomratio
271
 
        self.drawFrameBoundaries(allocation)
272
 
        self.drawTicks(allocation, offset, spacing, scale)
273
 
        self.drawTimes(allocation, offset, spacing, scale, layout)
274
 
 
275
 
    def drawTick(self, allocation, paintpos, height):
276
 
        paintpos = int(paintpos)
277
 
        height = allocation.height - int(allocation.height * height)
278
 
        self.pixmap.draw_line(
279
 
            self.style.fg_gc[gtk.STATE_NORMAL],
280
 
            paintpos, height, paintpos,
281
 
            allocation.height)
282
 
 
283
 
    def drawTicks(self, allocation, offset, spacing, scale):
284
 
        for subdivide, height in self.subdivide:
285
 
            spc = spacing / float(subdivide)
286
 
            dur = scale / float(subdivide)
287
 
            if spc < self.min_tick_spacing:
288
 
                break
289
 
            paintpos = -spacing + 0.5
290
 
            paintpos += spacing - offset
291
 
            while paintpos < allocation.width:
292
 
                self.drawTick(allocation, paintpos, height)
293
 
                paintpos += spc
294
 
 
295
 
    def drawTimes(self, allocation, offset, spacing, scale, layout):
296
 
        # figure out what the optimal offset is
297
 
        interval = long(gst.SECOND * scale)
298
 
        seconds = self.pixelToNs(self.pixmap_offset)
299
 
        paintpos = float(self.border) + 2
300
 
        if offset > 0:
301
 
            seconds = seconds - (seconds % interval) + interval
302
 
            paintpos += spacing - offset
303
 
        shaded = self.getShadedDurationWidth()
304
 
 
305
 
        while paintpos < allocation.width:
306
 
            timevalue = time_to_string(long(seconds))
307
 
            layout.set_text(timevalue)
308
 
            if paintpos < shaded:
309
 
                state = gtk.STATE_ACTIVE
310
 
            else:
311
 
                state = gtk.STATE_NORMAL
312
 
            self.pixmap.draw_layout(
313
 
                self.style.fg_gc[state],
314
 
                int(paintpos), 0, layout)
315
 
            paintpos += spacing
316
 
            seconds += interval
317
 
 
318
 
    def drawFrameBoundaries(self, allocation):
319
 
        ns_per_frame = float(1 / self.frame_rate) * gst.SECOND
320
 
        frame_width = self.nsToPixel(ns_per_frame)
321
 
        if frame_width >= self.min_frame_spacing:
322
 
            offset = self.pixmap_offset % frame_width
323
 
            paintpos = -frame_width + 0.5
324
 
            height = allocation.height
325
 
            y = int(height - self.frame_height)
326
 
            states = [gtk.STATE_ACTIVE, gtk.STATE_PRELIGHT]
327
 
            paintpos += frame_width - offset
328
 
            frame_num = int(paintpos // frame_width) % 2
329
 
            while paintpos < allocation.width:
330
 
                self.pixmap.draw_rectangle(
331
 
                    self.style.bg_gc[states[frame_num]],
332
 
                    True,
333
 
                    int(paintpos), y, frame_width, height)
334
 
                frame_num = (frame_num + 1) % 2
335
 
                paintpos += frame_width
336
 
 
337
 
    def drawPosition(self, context, allocation):
338
 
        if self.getShadedDuration() <= 0:
339
 
            return
340
 
        # a simple RED line will do for now
341
 
        xpos = self.nsToPixel(self.position) + self.border -\
342
 
            self.pixmap_offset
343
 
        context.save()
344
 
        context.set_line_width(1.5)
345
 
        context.set_source_rgb(1.0, 0, 0)
346
 
 
347
 
        context.move_to(xpos, 0)
348
 
        context.line_to(xpos, allocation.height)
349
 
        context.stroke()
350
 
 
351
 
        context.restore()