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

« back to all changes in this revision

Viewing changes to pitivi/ui/ruler.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:
27
27
import gtk
28
28
import gst
29
29
import pitivi.instance as instance
30
 
from complexinterface import ZoomableWidgetInterface
 
30
from zoominterface import Zoomable
 
31
from pitivi.utils import time_to_string
31
32
 
32
 
class ScaleRuler(gtk.Layout, ZoomableWidgetInterface):
 
33
class ScaleRuler(gtk.Layout, Zoomable):
33
34
 
34
35
    __gsignals__ = {
35
36
        "expose-event":"override",
40
41
        "motion-notify-event":"override",
41
42
        }
42
43
 
43
 
    border = 5
 
44
    border = 0
 
45
    min_tick_spacing = 3
44
46
 
45
47
    def __init__(self, hadj):
46
48
        gst.log("Creating new ScaleRule")
47
49
        gtk.Layout.__init__(self)
48
 
        self.add_events(gtk.gdk.POINTER_MOTION_MASK | gtk.gdk.BUTTON_PRESS_MASK | gtk.gdk.BUTTON_RELEASE_MASK)
 
50
        Zoomable.__init__(self)
 
51
        self.add_events(gtk.gdk.POINTER_MOTION_MASK |
 
52
            gtk.gdk.BUTTON_PRESS_MASK | gtk.gdk.BUTTON_RELEASE_MASK)
49
53
        self.set_hadjustment(hadj)
 
54
 
 
55
        # double-buffering properties
50
56
        self.pixmap = None
 
57
        # all values are in pixels
 
58
        self.pixmap_offset = 0
 
59
        self.pixmap_visible_width = 0
 
60
        self.pixmap_allocated_width = 0
 
61
        self.pixmap_old_allocated_width = -1
 
62
        # This is the number of visible_width we allocate for the pixmap
 
63
        self.pixmap_multiples = 2
 
64
 
51
65
        # position is in nanoseconds
52
66
        self.position = 0
53
 
 
54
 
        self.requested_time = long(0)
 
67
        self.requested_time = gst.CLOCK_TIME_NONE
55
68
        self.currentlySeeking = False
56
69
        self.pressed = False
57
70
 
58
 
    ## ZoomableWidgetInterface methods are handled by the container (LayerStack)
59
 
    ## Except for ZoomChanged
 
71
## Zoomable interface override
60
72
 
61
73
    def zoomChanged(self):
 
74
        self.queue_resize()
62
75
        self.doPixmap()
63
76
        self.queue_draw()
64
77
 
65
 
    def getPixelWidth(self):
66
 
        return ZoomableWidgetInterface.getPixelWidth(self) + 2 * self.border
67
 
 
68
 
 
69
 
    ## timeline position changed method
70
 
 
71
 
    def timelinePositionChanged(self, value, unused_frame):
72
 
        previous = self.position
 
78
## timeline position changed method
 
79
 
 
80
    def timelinePositionChanged(self, value, unused_frame=None):
 
81
        gst.debug("value : %r" % value)
 
82
        ppos = max(self.nsToPixel(self.position) - 1, 0)
73
83
        self.position = value
74
 
        self.queue_draw_area(max(self.nsToPixel(min(value, previous)) - 5, 0),
75
 
                             0,
76
 
                             self.nsToPixel(max(value, previous)) + 5,
77
 
                             self.get_allocation().height)
 
84
        npos = max(self.nsToPixel(self.position) - 1, 0)
 
85
        height = self.get_allocation().height
 
86
        self.bin_window.invalidate_rect((ppos, 0, 2, height), True)
 
87
        self.bin_window.invalidate_rect((npos, 0, 2, height), True)
78
88
 
79
 
    ## gtk.Widget overrides
 
89
## gtk.Widget overrides
80
90
 
81
91
    def do_size_allocate(self, allocation):
82
92
        gst.debug("ScaleRuler got %s" % list(allocation))
83
93
        gtk.Layout.do_size_allocate(self, allocation)
84
94
        width = max(self.getPixelWidth(), allocation.width)
85
 
        gst.debug("Setting layout size to %d x %d" % (width, allocation.height))
 
95
        gst.debug("Setting layout size to %d x %d"
 
96
                  % (width, allocation.height))
86
97
        self.set_size(width, allocation.height)
87
98
        # the size has changed, therefore we want to redo our pixmap
88
99
        self.doPixmap()
95
106
    def do_expose_event(self, event):
96
107
        gst.debug("exposing ScaleRuler %s" % list(event.area))
97
108
        x, y, width, height = event.area
 
109
        if (x < self.pixmap_offset) or (x+width > self.pixmap_offset + self.pixmap_allocated_width):
 
110
            gst.debug("exposing outside boundaries !")
 
111
            self.pixmap_offset = max(0, x + (width / 2) - (self.pixmap_allocated_width / 2))
 
112
            gst.debug("offset is now %d" % self.pixmap_offset)
 
113
            self.doPixmap()
 
114
            width = self.pixmap_allocated_width
 
115
 
98
116
        # double buffering power !
99
 
        self.bin_window.draw_drawable(self.style.fg_gc[gtk.STATE_NORMAL],
100
 
                                      self.pixmap,
101
 
                                      x, y, x, y, width, height)
 
117
        self.bin_window.draw_drawable(
 
118
            self.style.fg_gc[gtk.STATE_NORMAL],
 
119
            self.pixmap,
 
120
            x - self.pixmap_offset, y,
 
121
            x, y, width, height)
102
122
        # draw the position
103
123
        context = self.bin_window.cairo_create()
104
124
        self.drawPosition(context, self.get_allocation())
106
126
 
107
127
    def do_button_press_event(self, event):
108
128
        gst.debug("button pressed at x:%d" % event.x)
 
129
        if self.getDuration() <= 0:
 
130
            gst.debug("no timeline to seek on, ignoring")
109
131
        instance.PiTiVi.playground.switchToTimeline()
110
132
        self.pressed = True
111
133
        # seek at position
126
148
            self._doSeek(cur)
127
149
        return False
128
150
 
129
 
    ## Seeking methods
 
151
## Seeking methods
130
152
 
131
153
    def _seekTimeoutCb(self):
132
154
        gst.debug("timeout")
133
155
        self.currentlySeeking = False
134
 
        if not self.position == self.requested_time:
 
156
        if (self.requested_time != gst.CLOCK_TIME_NONE) and (self.position != self.requested_time):
135
157
            self._doSeek(self.requested_time)
 
158
        return False
136
159
 
137
160
    def _doSeek(self, value, format=gst.FORMAT_TIME):
138
 
        gst.debug("seeking to %s" % gst.TIME_ARGS (value))
 
161
        gst.debug("seeking to %s / currentlySeeking %r" % (gst.TIME_ARGS (value),
 
162
                                                           self.currentlySeeking))
 
163
        # clamping values within acceptable range
 
164
        duration = self.getDuration()
 
165
        if duration == gst.CLOCK_TIME_NONE:
 
166
            return
 
167
        if value > duration:
 
168
            value = duration
 
169
        elif value < 0:
 
170
            value = 0
139
171
        if not self.currentlySeeking:
140
172
            self.currentlySeeking = True
141
 
            self.requested_time = value
142
 
            gobject.timeout_add(80, self._seekTimeoutCb)
143
 
            instance.PiTiVi.playground.seekInCurrent(value, format=format)
 
173
            if instance.PiTiVi.playground.seekInCurrent(value, format=format):
 
174
                self.requested_time = gst.CLOCK_TIME_NONE
 
175
                gobject.timeout_add(80, self._seekTimeoutCb)
 
176
                self.timelinePositionChanged(value)
 
177
            else:
 
178
                self.currentlySeeking = False
144
179
        elif format == gst.FORMAT_TIME:
145
180
            self.requested_time = value
146
181
 
147
 
    ## Drawing methods
 
182
## Drawing methods
148
183
 
149
184
    def doPixmap(self):
150
185
        """ (re)create the buffered drawable for the Widget """
151
186
        # we can't create the pixmap if we're not realized
152
187
        if not self.flags() & gtk.REALIZED:
153
188
            return
 
189
 
 
190
        # We want to benefit from double-buffering (so as not to recreate the
 
191
        # ruler graphics all the time) yet we don't want to allocate insanely
 
192
        # big pixmaps (which would result in big memory usage, or even not being
 
193
        # able to allocate such a big pixmap).
 
194
        #
 
195
        # We therefore create a pixmap with a width of 2 times the maximum viewable
 
196
        # width (allocation.width)
 
197
 
154
198
        allocation = self.get_allocation()
155
199
        lwidth, lheight = self.get_size()
156
 
        allocation.width = max(allocation.width, lwidth)
157
 
        gst.debug("Creating pixmap(self.window, width:%d, height:%d)" % (allocation.width, allocation.height))
158
 
        if self.pixmap:
159
 
            del self.pixmap
160
 
        self.pixmap = gtk.gdk.Pixmap(self.bin_window, allocation.width, allocation.height)
 
200
 
 
201
        self.pixmap_visible_width = allocation.width
 
202
        self.pixmap_allocated_width = self.pixmap_visible_width * self.pixmap_multiples
 
203
        allocation.width = self.pixmap_allocated_width
 
204
 
 
205
 
 
206
        if (allocation.width != self.pixmap_old_allocated_width):
 
207
            if self.pixmap:
 
208
                del self.pixmap
 
209
            self.pixmap = gtk.gdk.Pixmap(self.bin_window, allocation.width,
 
210
                                         allocation.height)
 
211
            self.pixmap_old_allocated_width = allocation.width
 
212
 
161
213
        context = self.pixmap.cairo_create()
162
214
        self.drawBackground(context, allocation)
163
215
        self.drawRuler(context, allocation)
168
220
        self.drawBackground(context, rect)
169
221
        self.drawRuler(context, rect)
170
222
 
 
223
    def getDuration(self):
 
224
        if instance.PiTiVi.current:
 
225
            return instance.PiTiVi.current.timeline.getDuration()
 
226
        else:
 
227
            return gst.CLOCK_TIME_NONE
 
228
 
 
229
    def getPixelWidth(self):
 
230
        return self.nsToPixel(self.getDuration())
 
231
 
 
232
    def getPixelPosition(self):
 
233
        return 0
 
234
 
171
235
    def drawBackground(self, context, allocation):
172
236
        context.save()
173
237
 
174
238
        context.set_source_rgb(0.5, 0.5, 0.5)
175
 
        context.rectangle(0, 0,
176
 
                          allocation.width, allocation.height)
 
239
        context.rectangle(0, 0, allocation.width, allocation.height)
177
240
        context.fill()
178
241
        context.stroke()
179
242
 
180
243
        if self.getDuration() > 0:
181
244
            context.set_source_rgb(0.8, 0.8, 0.8)
182
 
            context.rectangle(0, 0,
183
 
                              self.getPixelWidth(), allocation.height)
 
245
            context.rectangle(0, 0, self.getPixelWidth(), allocation.height)
184
246
            context.fill()
185
247
            context.stroke()
186
248
 
187
249
        context.restore()
188
250
 
 
251
    def startDurationChanged(self):
 
252
        gst.info("start/duration changed")
 
253
        self.queue_resize()
 
254
 
189
255
    def drawRuler(self, context, allocation):
190
 
        context.save()
191
 
 
192
 
        zoomRatio = self.getZoomRatio()
193
 
 
194
 
        paintpos = float(self.border) + 0.5
195
 
        seconds = 0
196
 
        secspertic = 1
197
 
 
198
 
        timeprint = 0
199
 
        ticspertime = 1
200
 
 
201
 
        # FIXME : this should be beautified (instead of all the if/elif/else)
202
 
        if zoomRatio < 0.05:
203
 
            #Smallest tic is 10 minutes
204
 
            secspertic = 600
205
 
            if zoomRatio < 0.006:
206
 
                ticspertime = 24
207
 
            elif zoomRatio < 0.0125:
208
 
                ticspertime = 12
209
 
            elif zoomRatio < 0.025:
210
 
                ticspertime = 6
211
 
            else:
212
 
                ticspertime = 3
213
 
        elif zoomRatio < 0.5:
214
 
            #Smallest tic is 1 minute
215
 
            secspertic = 60
216
 
            if zoomRatio < 0.25:
217
 
                ticspertime = 10
218
 
            else:
219
 
                ticspertime = 5
220
 
        elif zoomRatio < 3:
221
 
            #Smallest tic is 10 seconds
222
 
            secspertic = 10
223
 
            if zoomRatio < 1:
224
 
                ticspertime = 12
225
 
            else:
226
 
                ticspertime = 6
227
 
        else:
228
 
            #Smallest tic is 1 second
229
 
            if zoomRatio < 5:
230
 
                ticspertime = 20
231
 
            elif zoomRatio < 10:
232
 
                ticspertime = 10
233
 
            elif zoomRatio < 20:
234
 
                ticspertime = 5
235
 
            elif zoomRatio < 40:
236
 
                ticspertime = 2
237
 
 
238
 
        while paintpos < allocation.width:
 
256
        # there are 4 lengths of tick mark:
 
257
        # full height: largest increments, 1 minute
 
258
        # 3/4 height: 10 seconds
 
259
        # 1/2 height: 1 second
 
260
        # 1/4 height: 1/10 second (might later be changed to 1 frame in
 
261
        #   project framerate)
 
262
 
 
263
        # At the highest level of magnification, all ticmarks are visible. At
 
264
        # the lowest, only the full height tic marks are visible. The
 
265
        # appearance of text is dependent on the spacing between tics: text
 
266
        # only appears when there is enough space between tics for it to be
 
267
        # readable.
 
268
 
 
269
        def textSize(text):
 
270
            return context.text_extents(text)[2:4]
 
271
 
 
272
        def drawTick(paintpos, height):
239
273
            context.move_to(paintpos, 0)
240
 
 
241
 
            if seconds % 600 == 0:
242
 
                context.line_to(paintpos, allocation.height)
243
 
            elif seconds % 60 == 0:
244
 
                context.line_to(paintpos, allocation.height * 3 / 4)
245
 
            elif seconds % 10 == 0:
246
 
                context.line_to(paintpos, allocation.height / 2)
247
 
            else:
248
 
                context.line_to(paintpos, allocation.height / 4)
249
 
 
250
 
            if timeprint == 0:
251
 
                # draw the text position
252
 
                hours = seconds / 3600
253
 
                mins = seconds % 3600 / 60
254
 
                secs = seconds % 60
255
 
                time = "%02d:%02d:%02d" % (hours, mins, secs)
256
 
                txtwidth, txtheight = context.text_extents(time)[2:4]
257
 
                context.move_to( paintpos - txtwidth / 2.0,
258
 
                                 allocation.height - 2 )
259
 
                context.show_text( time )
260
 
                timeprint = ticspertime
261
 
            timeprint -= 1
262
 
 
263
 
            paintpos += zoomRatio * secspertic
264
 
            seconds += secspertic
265
 
 
266
 
        #Since drawing is done in batch we can't use different styles
267
 
        context.set_line_width(1)
268
 
        context.set_source_rgb(0, 0, 0)
269
 
 
 
274
            context.line_to(paintpos, allocation.height * height)
 
275
 
 
276
        def drawText(paintpos, time, txtwidth, txtheight):
 
277
            # draw the text position
 
278
            time = time_to_string(time)
 
279
            context.move_to( paintpos - txtwidth / 2.0,
 
280
                             allocation.height - 2 )
 
281
            context.show_text( time )
 
282
 
 
283
        def drawTicks(interval, height):
 
284
            spacing = zoomRatio * interval
 
285
            offset = self.pixmap_offset % spacing
 
286
            paintpos = float(self.border) + 0.5
 
287
            if offset > 0:
 
288
                paintpos += spacing - offset
 
289
            if spacing >= self.min_tick_spacing:
 
290
                while paintpos < allocation.width:
 
291
                    drawTick(paintpos, height)
 
292
                    paintpos += zoomRatio * interval
 
293
 
 
294
        def drawTimes(interval):
 
295
            # figure out what the optimal offset is
 
296
            spacing = zoomRatio * interval
 
297
            offset = self.pixmap_offset % spacing
 
298
            seconds = self.pixelToNs(self.pixmap_offset)
 
299
            paintpos = float(self.border) + 0.5
 
300
            if offset > 0:
 
301
                seconds += self.pixelToNs(spacing - offset)
 
302
                paintpos += spacing - offset
 
303
            textwidth, textheight = textSize(time_to_string(0))
 
304
            if spacing > textwidth:
 
305
                while paintpos < allocation.width:
 
306
                    timevalue = long(seconds)
 
307
                    drawText(paintpos, timevalue, textwidth, textheight)
 
308
                    paintpos += spacing
 
309
                    seconds += long(interval * gst.SECOND)
 
310
 
 
311
 
 
312
        context.save()
 
313
        zoomRatio = self.zoomratio
 
314
        # looks better largest tick doesn't run into the text label
 
315
        interval_sizes = ((60, 0.80), (10, 0.75), (1, 0.5), (0.1, 0.25))
 
316
        for interval, height in interval_sizes:
 
317
            drawTicks(interval, height)
 
318
            drawTimes(interval)
 
319
 
 
320
        #set a slightly thicker line. This forces anti-aliasing, and gives the
 
321
        #a softer appearance
 
322
        context.set_line_width(1.1)
 
323
        context.set_source_rgb(0.4, 0.4, 0.4)
270
324
        context.stroke()
271
325
        context.restore()
272
326
 
274
328
        if self.getDuration() <= 0:
275
329
            return
276
330
        # a simple RED line will do for now
277
 
        xpos = self.nsToPixel(self.position) + self.border + 0.5
 
331
        xpos = self.nsToPixel(self.position) + self.border
278
332
        context.save()
 
333
        context.set_line_width(1.5)
279
334
        context.set_source_rgb(1.0, 0, 0)
280
335
 
281
336
        context.move_to(xpos, 0)