Minor code re-ordering for clarity
[jack_mixer.git] / slider.py
blobfcc316ac8b40f946ac196f2e02aff844e8d8474f
1 # This file is part of jack_mixer
3 # Copyright (C) 2006 Nedko Arnaudov <nedko@arnaudov.name>
5 # This program is free software; you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation; version 2 of the License
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18 import logging
20 import cairo
21 from gi.repository import Gtk
22 from gi.repository import Gdk
23 from gi.repository import GObject
26 log = logging.getLogger(__name__)
29 class AdjustmentdBFS(Gtk.Adjustment):
30 def __init__(self, scale, default_db, step_inc):
31 self.default_value = scale.db_to_scale(default_db)
32 self.db = default_db
33 self.scale = scale
34 self.step_increment = step_inc
35 super().__init__(self.default_value, 0.0, 1.0, step_inc)
36 self.connect("value-changed", self.on_value_changed)
37 self.disable_value_notify = False
39 def step_up(self):
40 self.set_value(self.get_value() + self.step_increment)
42 def step_down(self):
43 self.set_value(self.get_value() - self.step_increment)
45 def reset(self):
46 self.set_value(self.default_value)
48 def get_value_db(self):
49 return self.db
51 def set_value_db(self, db, from_midi=False):
52 self.db = db
53 self.disable_value_notify = True
54 self.set_value(self.scale.db_to_scale(db))
55 self.disable_value_notify = False
56 if from_midi:
57 self.emit("volume-changed-from-midi")
58 else:
59 self.emit("volume-changed")
61 def on_value_changed(self, adjustment):
62 if not self.disable_value_notify:
63 self.db = self.scale.scale_to_db(self.get_value())
64 self.emit("volume-changed")
66 def set_scale(self, scale):
67 self.scale = scale
68 self.disable_value_notify = True
69 self.set_value(self.scale.db_to_scale(self.db))
70 self.disable_value_notify = False
73 GObject.signal_new(
74 "volume-changed",
75 AdjustmentdBFS,
76 GObject.SignalFlags.RUN_FIRST | GObject.SignalFlags.ACTION,
77 None,
78 [],
81 GObject.signal_new(
82 "volume-changed-from-midi",
83 AdjustmentdBFS,
84 GObject.SignalFlags.RUN_FIRST | GObject.SignalFlags.ACTION,
85 None,
86 [],
90 class BalanceAdjustment(Gtk.Adjustment):
91 def __init__(self):
92 super().__init__(0.0, -1.0, 1.0, 0.1)
93 self.connect("value-changed", self.on_value_changed)
94 self.disable_value_notify = False
96 def set_balance(self, value, from_midi=False):
97 self.disable_value_notify = True
98 self.set_value(value)
99 self.disable_value_notify = False
100 if not from_midi:
101 self.emit("balance-changed")
103 def on_value_changed(self, adjustment):
104 if not self.disable_value_notify:
105 self.emit("balance-changed")
108 GObject.signal_new(
109 "balance-changed",
110 BalanceAdjustment,
111 GObject.SignalFlags.RUN_FIRST | GObject.SignalFlags.ACTION,
112 None,
117 class VolumeSlider(Gtk.Scale):
118 def __init__(self, adjustment):
119 super().__init__(orientation=Gtk.Orientation.VERTICAL)
120 self.adjustment = adjustment
121 self.set_adjustment(adjustment)
122 self.set_draw_value(False)
123 self.set_inverted(True)
124 self._button_down = False
125 self._button_down_y = 0
126 self._button_down_value = 0
128 self.connect("button-press-event", self.button_press_event)
129 self.connect("button-release-event", self.button_release_event)
130 self.connect("motion-notify-event", self.motion_notify_event)
131 self.connect("scroll-event", self.scroll_event)
133 def button_press_event(self, widget, event):
134 if event.button == 1:
135 if event.state & Gdk.ModifierType.CONTROL_MASK:
136 if event.type == Gdk.EventType.BUTTON_PRESS:
137 self.adjustment.set_value_db(0)
138 return True
139 elif event.type == Gdk.EventType.BUTTON_PRESS:
140 self._button_down = True
141 self._button_down_y = event.y
142 self._button_down_value = self.adjustment.get_value()
143 return True
144 elif event.type == Gdk.EventType._2BUTTON_PRESS:
145 self.adjustment.set_value(0)
146 return True
148 return False
150 def button_release_event(self, widget, event):
151 self._button_down = False
152 return False
154 def motion_notify_event(self, widget, event):
155 slider_length = widget.get_allocation().height - widget.get_style_context().get_property(
156 "min-height", Gtk.StateFlags.NORMAL
158 if self._button_down:
159 delta_y = (self._button_down_y - event.y) / slider_length
160 y = self._button_down_value + delta_y
161 if y >= 1:
162 y = 1
163 elif y <= 0:
164 y = 0
166 self.adjustment.set_value(y)
167 return True
169 def scroll_event(self, widget, event):
170 delta = self.adjustment.step_increment
171 value = self.adjustment.get_value()
172 if event.direction == Gdk.ScrollDirection.UP:
173 y = value + delta
174 elif event.direction == Gdk.ScrollDirection.DOWN:
175 y = value - delta
176 elif event.direction == Gdk.ScrollDirection.SMOOTH:
177 y = value - event.delta_y * delta
179 if y >= 1:
180 y = 1
181 elif y <= 0:
182 y = 0
184 self.adjustment.set_value(y)
185 return True
188 class BalanceSlider(Gtk.Scale):
189 def __init__(self, adjustment, preferred_width, preferred_height):
190 super().__init__(orientation=Gtk.Orientation.HORIZONTAL)
191 self.adjustment = adjustment
192 self.set_adjustment(adjustment)
193 self.set_has_origin(False)
194 self.set_draw_value(False)
195 self.set_property("has-tooltip", True)
196 self._preferred_width = preferred_width
197 self._preferred_height = preferred_height
198 self._button_down = False
200 self.add_mark(-1.0, Gtk.PositionType.TOP)
201 self.add_mark(0.0, Gtk.PositionType.TOP)
202 self.add_mark(1.0, Gtk.PositionType.TOP)
204 self.connect("button-press-event", self.on_button_press_event)
205 self.connect("button-release-event", self.on_button_release_event)
206 self.connect("motion-notify-event", self.on_motion_notify_event)
207 self.connect("scroll-event", self.on_scroll_event)
208 self.connect("query-tooltip", self.on_query_tooltip)
210 def get_preferred_width(self):
211 return self._preferred_width
213 def get_preferred_height(self):
214 return self._preferred_height
216 def on_button_press_event(self, widget, event):
217 if event.button == 1:
218 if event.type == Gdk.EventType.BUTTON_PRESS:
219 self._button_down = True
220 self._button_down_x = event.x
221 self._button_down_value = self.get_value()
222 return True
223 elif event.type == Gdk.EventType._2BUTTON_PRESS:
224 self.adjustment.set_balance(0)
225 return True
227 return False
229 def on_button_release_event(self, widget, event):
230 self._button_down = False
231 return False
233 def on_motion_notify_event(self, widget, event):
234 slider_length = widget.get_allocation().width - widget.get_style_context().get_property(
235 "min-width", Gtk.StateFlags.NORMAL
238 if self._button_down:
239 delta_x = (event.x - self._button_down_x) / slider_length
240 x = self._button_down_value + 2 * delta_x
241 self.adjustment.set_balance(min(1, max(x, -1)))
242 return True
244 return False
246 def on_query_tooltip(self, widget, x, y, keyboard_mode, tooltip, *args):
247 val = int(self.adjustment.get_value() * 50)
248 if val == 0:
249 tooltip.set_text("Center")
250 else:
251 tooltip.set_text("Left: %s / Right: %d" % (50 - val, val + 50))
253 return True
255 def on_scroll_event(self, widget, event):
256 delta = self.get_adjustment().get_step_increment()
257 value = self.get_value()
259 if event.direction == Gdk.ScrollDirection.UP:
260 x = value - delta
261 elif event.direction == Gdk.ScrollDirection.DOWN:
262 x = value + delta
263 elif event.direction == Gdk.ScrollDirection.SMOOTH:
264 x = value - event.delta_y * delta
266 self.set_value(min(1, max(x, -1)))
267 return True
270 class CustomSliderWidget(Gtk.DrawingArea):
271 def __init__(self, adjustment):
272 super().__init__()
273 self.adjustment = adjustment
275 self.connect("draw", self.on_expose)
276 self.connect("size_allocate", self.on_size_allocate)
277 adjustment.connect("value-changed", self.on_value_changed)
278 self.connect("button-press-event", self.on_mouse)
279 self.connect("motion-notify-event", self.on_mouse)
280 self.connect("scroll-event", self.on_scroll)
281 self.set_events(
282 Gdk.EventMask.BUTTON1_MOTION_MASK
283 | Gdk.EventMask.SCROLL_MASK
284 | Gdk.EventMask.BUTTON_PRESS_MASK
287 def on_scroll(self, widget, event):
288 delta = self.adjustment.step_increment
289 value = self.adjustment.get_value()
290 if event.direction == Gdk.ScrollDirection.UP:
291 y = value + delta
292 elif event.direction == Gdk.ScrollDirection.DOWN:
293 y = value - delta
294 if y >= 1:
295 y = 1
296 elif y <= 0:
297 y = 0
298 self.adjustment.set_value(y)
299 return True
301 def on_mouse(self, widget, event):
302 if event.type == Gdk.EventType.BUTTON_PRESS:
303 log.debug("Mouse button %u pressed %ux%u", event.button, event.x, event.y)
304 if event.button == 1:
305 if (
306 event.y >= self.slider_rail_up
307 and event.y < self.slider_rail_up + self.slider_rail_height
309 self.adjustment.set_value(
310 1 - float(event.y - self.slider_rail_up) / float(self.slider_rail_height)
312 elif event.type == Gdk.EventType.MOTION_NOTIFY:
313 log.debug("Mouse motion %ux%u", event.x, event.y)
314 if event.y < self.slider_rail_up:
315 y = self.slider_rail_up
316 elif event.y > self.slider_rail_up + self.slider_rail_height:
317 y = self.slider_rail_up + self.slider_rail_height
318 else:
319 y = event.y
320 self.adjustment.set_value(
321 1 - float(y - self.slider_rail_up) / float(self.slider_rail_height)
324 return False
326 def on_value_changed(self, adjustment):
327 self.invalidate_all()
329 def on_expose(self, widget, cairo_ctx):
330 self.draw(cairo_ctx)
331 return False
333 def get_preferred_width(self, widget):
334 minimal_width = natural_width = self.width
335 return (minimal_width, natural_width)
337 def get_preferred_height(self, widget):
338 requisition = Gtk.Requisition()
339 self.on_size_request(self, widget, requisition)
340 minimal_height = natural_height = requisition.height
341 return (minimal_height, natural_height)
343 def on_size_allocate(self, widget, allocation):
344 self.width = float(allocation.width)
345 self.height = float(allocation.height)
346 self.font_size = 10
348 def on_size_request(self, widget, requisition):
349 requisition.width = 20
351 def invalidate_all(self):
352 if hasattr(self, "width") and hasattr(self, "height"):
353 self.queue_draw_area(0, 0, int(self.width), int(self.height))
355 def draw(self, cairo_ctx):
356 if self.has_focus():
357 state = Gtk.StateType.PRELIGHT
358 else:
359 state = Gtk.StateType.NORMAL
361 # cairo_ctx.rectangle(0, 0, self.width, self.height)
362 # cairo_ctx.set_source_color(self.style.bg[state])
363 # cairo_ctx.fill_preserve()
364 # Gdk.cairo_set_source_color(cairo_ctx,
365 # self.get_style_context().get_color(state).to_color())
366 # cairo_ctx.stroke()
368 slider_knob_width = 37.5 if self.width * 3 / 4 > 37.5 else self.width * 3 / 4
369 slider_knob_height = slider_knob_width * 2
370 slider_knob_height -= slider_knob_height % 2
371 slider_knob_height += 1
373 slider_x = self.width / 2
375 cairo_ctx.set_line_width(1)
377 # slider rail
378 Gdk.cairo_set_source_color(cairo_ctx, self.get_style_context().get_color(state).to_color())
379 self.slider_rail_up = slider_knob_height / 2 + (self.width - slider_knob_width) / 2
380 self.slider_rail_height = self.height - 2 * self.slider_rail_up
381 cairo_ctx.move_to(slider_x, self.slider_rail_up)
382 cairo_ctx.line_to(slider_x, self.slider_rail_height + self.slider_rail_up)
383 cairo_ctx.stroke()
385 # slider knob
386 slider_y = round(
387 self.slider_rail_up + self.slider_rail_height * (1 - self.adjustment.get_value())
389 lg = cairo.LinearGradient(
390 slider_x - float(slider_knob_width) / 2,
391 slider_y - slider_knob_height / 2,
392 slider_x - float(slider_knob_width) / 2,
393 slider_y + slider_knob_height / 2,
395 slider_alpha = 1.0
396 lg.add_color_stop_rgba(0, 0.55, 0.55, 0.55, slider_alpha)
397 lg.add_color_stop_rgba(0.1, 0.65, 0.65, 0.65, slider_alpha)
398 lg.add_color_stop_rgba(0.1, 0.75, 0.75, 0.75, slider_alpha)
399 lg.add_color_stop_rgba(0.125, 0.75, 0.75, 0.75, slider_alpha)
400 lg.add_color_stop_rgba(0.125, 0.15, 0.15, 0.15, slider_alpha)
401 lg.add_color_stop_rgba(0.475, 0.35, 0.35, 0.35, slider_alpha)
402 lg.add_color_stop_rgba(0.475, 0, 0, 0, slider_alpha)
403 lg.add_color_stop_rgba(0.525, 0, 0, 0, slider_alpha)
404 lg.add_color_stop_rgba(0.525, 0.35, 0.35, 0.35, slider_alpha)
405 lg.add_color_stop_rgba(0.875, 0.65, 0.65, 0.65, slider_alpha)
406 lg.add_color_stop_rgba(0.875, 0.75, 0.75, 0.75, slider_alpha)
407 lg.add_color_stop_rgba(0.900, 0.75, 0.75, 0.75, slider_alpha)
408 lg.add_color_stop_rgba(0.900, 0.15, 0.15, 0.15, slider_alpha)
409 lg.add_color_stop_rgba(1.000, 0.10, 0.10, 0.10, slider_alpha)
410 cairo_ctx.rectangle(
411 slider_x - float(slider_knob_width) / 2,
412 slider_y - slider_knob_height / 2,
413 float(slider_knob_width),
414 slider_knob_height,
416 Gdk.cairo_set_source_color(
417 cairo_ctx, self.get_style_context().get_background_color(state).to_color()
419 cairo_ctx.fill_preserve()
420 cairo_ctx.set_source(lg)
421 cairo_ctx.fill()