5 raise ImportError('You need gtk')
9 raise ImportError('You need gobject')
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
25 - update_callback() is called whenever the endpoints change
26 - player is used for its methods 'position' and 'duration' to display the
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
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."""
58 return self
.raw_begin
, self
.raw_end
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:
66 self
.update_begin(begin
, duration
)
67 self
.update_end(end
, duration
)
71 """Shorthand for getting the widget's width."""
72 return self
.get_allocation().width
76 """Shorthand for getting the widget's height."""
77 return self
.get_allocation().height
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:
88 if self
.raw_begin
> self
.raw_end
:
89 self
.raw_end
= self
.raw_begin
93 def update_end(self
, end
, duration
):
94 self
.raw_end
= end
/ duration
97 if self
.raw_begin
> self
.raw_end
:
98 self
.raw_begin
= self
.raw_end
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:
107 if self
.raw_begin
> self
.raw_end
:
108 self
.raw_end
= self
.raw_begin
112 def update_end_raw(self
, x
):
113 """Updates the end endpoint and calls requisite callbacks."""
114 self
.raw_end
= x
/ self
.width
117 if self
.raw_begin
> self
.raw_end
:
118 self
.raw_begin
= self
.raw_end
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()
130 def interval_queue_draw(self
):
132 # so gobject will keep calling us
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
)),
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
)),
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)
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.
168 x
, y
, state
= event
.window
.get_pointer()
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()
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:
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()