This development deserves a new version number!
[pylooper.git] / looperwidget.py
bloba04a1954731113ea352ac9b1fbfd829d84f9aca3
1 try:
2 import gtk
3 from gtk import gdk
4 except:
5 raise ImportError('You need gtk')
6 try:
7 import gobject
8 except:
9 raise ImportError('You need gobject')
11 import settings
13 class LooperWidget(gtk.DrawingArea):
14 """Creates and manages the interface by which start and end looping points
15 are chosen and manipulated.
17 This is a pretty portable, generic widget that can be used for any
18 application requiring an interval input of some sort. Maybe I should
19 rename it IntervalWidget."""
21 def __init__(self, update_callback, player=None, width=100, height=32):
22 """Registers update_callback and player for future use, and sets up gtk
23 stuff for the widget.
25 - update_callback() is called whenever the endpoints change
26 - player is used for its methods 'position' and 'duration' to display the
27 progress bar"""
29 super(LooperWidget, self).__init__()
31 self.connect('expose-event', LooperWidget.expose_event)
32 self.connect('configure-event', LooperWidget.expose_event)
33 self.connect('motion-notify-event', LooperWidget.motion_notify_event)
34 self.connect('button-release-event', LooperWidget.button_release_event)
36 self.add_events(gdk.EXPOSURE_MASK
37 | gdk.POINTER_MOTION_MASK
38 | gdk.BUTTON1_MOTION_MASK
39 | gdk.BUTTON3_MOTION_MASK
40 | gdk.BUTTON_PRESS_MASK
41 | gdk.BUTTON_RELEASE_MASK)
43 self.set_size_request(width, height)
45 self.update_callback = update_callback
46 self.player = player
48 self.raw_begin = 0.
49 self.raw_end = 1.
51 # expose events ain't too slow, so we can afford this (I think)
52 gobject.timeout_add(settings.GRAPHICSTIMEOUTLENGTH, self.interval_queue_draw)
54 def interval(self, length=None):
55 """Returns the interval selected, scaled by length and then quantized,
56 if length is provided."""
57 if not length:
58 return self.raw_begin, self.raw_end
59 else:
60 return long(length * self.raw_begin), long(length * self.raw_end)
62 def set_interval(self, begin, end, duration):
63 if begin is None or end is None:
64 return False
66 self.update_begin(begin, duration)
67 self.update_end(end, duration)
69 @property
70 def width(self):
71 """Shorthand for getting the widget's width."""
72 return self.get_allocation().width
74 @property
75 def height(self):
76 """Shorthand for getting the widget's height."""
77 return self.get_allocation().height
79 @property
80 def size(self):
81 """Shorthand for getting the widget's size (width, height)."""
82 return self.width, self.height
84 def update_begin(self, begin, duration):
85 self.raw_begin = begin / duration
86 if self.raw_begin < 0:
87 self.raw_begin = 0
88 if self.raw_begin > self.raw_end:
89 self.raw_end = self.raw_begin
91 self.queue_draw()
93 def update_end(self, end, duration):
94 self.raw_end = end / duration
95 if self.raw_end > 1:
96 self.raw_end = 1
97 if self.raw_begin > self.raw_end:
98 self.raw_begin = self.raw_end
100 self.queue_draw()
102 def update_begin_raw(self, x):
103 """Updates the begin endpoint and calls requisite callbacks."""
104 self.raw_begin = x / self.width
105 if self.raw_begin < 0:
106 self.raw_begin = 0
107 if self.raw_begin > self.raw_end:
108 self.raw_end = self.raw_begin
110 self.queue_draw()
112 def update_end_raw(self, x):
113 """Updates the end endpoint and calls requisite callbacks."""
114 self.raw_end = x / self.width
115 if self.raw_end > 1:
116 self.raw_end = 1
117 if self.raw_begin > self.raw_end:
118 self.raw_begin = self.raw_end
120 self.queue_draw()
122 def reset_endpoints(self, raw_begin=0, raw_end=1):
123 """Resets endpoints and calls requisite callbacks."""
124 self.raw_begin = raw_begin
125 self.raw_end = raw_end
127 self.update_callback()
128 self.queue_draw()
130 def interval_queue_draw(self):
131 self.queue_draw()
132 # so gobject will keep calling us
133 return True
135 def expose_event(self, event):
136 """Event handler for expose events. Just draws everything over again,
137 pretty simple really. You can change the color scheme by providing a
138 different graphics context to each command."""
140 self.window.draw_rectangle(self.style.bg_gc[settings.WIDGET_STATE], True, 0, 0, *self.size)
142 self.window.draw_rectangle(self.style.light_gc[settings.WIDGET_STATE], True,
143 int(self.width * self.raw_begin), 0,
144 int(self.width * (self.raw_end - self.raw_begin)),
145 self.height - 1)
147 self.window.draw_rectangle(self.style.dark_gc[settings.WIDGET_STATE], False,
148 int(self.width * self.raw_begin), 0,
149 int(self.width * (self.raw_end - self.raw_begin)),
150 self.height - 1)
152 # If this is being used for something other than PyLooper, we may not
153 # have a player to query, so checking for that first is more portable.
154 if self.player and self.player.duration > 0:
155 position = int(1. * self.width * self.player.position / self.player.duration)
156 self.window.draw_line(self.style.mid_gc[settings.WIDGET_STATE], position, 1, position, self.height - 2)
158 return True
160 def motion_notify_event(self, event):
161 """Event handler for motion events. Gets the x coordinate and updates
162 the correct endpoint based on which button is held down."""
164 # TODO: If you are a gtk wizard, maybe you can condense these four
165 # lines down to something less dumb looking (one obvious way and all
166 # that). I just stole this from a tutorial somewhere.
167 if event.is_hint:
168 x, y, state = event.window.get_pointer()
169 else:
170 x, y, state = event.x, event.y, event.state
172 if (state & gdk.BUTTON1_MASK) and not (state & gdk.BUTTON3_MASK):
173 self.update_begin_raw(x)
174 self.update_callback()
175 elif (state & gdk.BUTTON3_MASK) and not (state & gdk.BUTTON1_MASK):
176 self.update_end_raw(x)
177 self.update_callback()
179 return True
181 def button_release_event(self, event):
182 """Event handler for button release events. Basically the same as
183 motion_notify_event, but the event apis seem very non-uniform, so this
184 is kind of weird. It works for now though."""
186 if len(event.get_coords()) < 2:
187 return False
189 if event.button is 1:
190 self.update_begin_raw(event.get_coords()[0])
191 self.update_callback()
192 elif event.button is 3:
193 self.update_end_raw(event.get_coords()[0])
194 self.update_callback()
196 return True