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.
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)
34 self.step_increment = step_inc
35 Gtk.Adjustment.__init__(self, self.default_value, 0.0, 1.0, 0.02)
36 self.connect("value-changed", self.on_value_changed)
37 self.disable_value_notify = False
40 self.set_value(self.get_value() + self.step_increment)
43 self.set_value(self.get_value() - self.step_increment)
46 self.set_value(self.default_value)
48 def get_value_db(self):
51 def set_value_db(self, db, from_midi = False):
53 self.disable_value_notify = True
54 self.set_value(self.scale.db_to_scale(db))
55 self.disable_value_notify = False
57 self.emit("volume-changed")
59 self.emit("volume-changed-from-midi")
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):
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("volume-changed", AdjustmentdBFS,
74 GObject.SignalFlags.RUN_FIRST | GObject.SignalFlags.ACTION, None, [])
76 GObject.signal_new("volume-changed-from-midi", AdjustmentdBFS,
77 GObject.SignalFlags.RUN_FIRST | GObject.SignalFlags.ACTION, None, [])
80 class BalanceAdjustment(Gtk.Adjustment):
82 Gtk.Adjustment.__init__(self, 0.0, -1.0, 1.0, 0.02)
83 self.connect("value-changed", self.on_value_changed)
84 self.disable_value_notify = False
86 def set_balance(self, value, from_midi = False):
87 self.disable_value_notify = True
89 self.disable_value_notify = False
91 self.emit("balance-changed")
93 def on_value_changed(self, adjustment):
94 if not self.disable_value_notify:
95 self.emit("balance-changed")
98 GObject.signal_new("balance-changed", BalanceAdjustment,
99 GObject.SignalFlags.RUN_FIRST | GObject.SignalFlags.ACTION, None, [])
102 class VolumeSlider(Gtk.Scale):
103 def __init__(self, adjustment):
104 Gtk.Scale.__init__(self, orientation=Gtk.Orientation.VERTICAL)
105 self.adjustment = adjustment
106 self.set_adjustment(adjustment)
107 self.set_draw_value(False)
108 self.set_inverted(True)
109 self.button_down = False
110 self.button_down_y = 0
111 self.button_down_value = 0
113 self.connect('button-press-event', self.button_press_event)
114 self.connect('button-release-event', self.button_release_event)
115 self.connect("motion-notify-event", self.motion_notify_event)
116 self.connect("scroll-event", self.scroll_event)
118 def button_press_event(self, widget, event):
119 if event.button == 1:
120 if event.state & Gdk.ModifierType.CONTROL_MASK:
121 if event.type == Gdk.EventType.BUTTON_PRESS:
122 self.adjustment.set_value_db(0)
124 elif event.type == Gdk.EventType.BUTTON_PRESS:
125 self.button_down = True
126 self.button_down_y = event.y
127 self.button_down_value = self.adjustment.get_value()
129 elif event.type == Gdk.EventType._2BUTTON_PRESS:
130 self.adjustment.set_value(0)
135 def button_release_event(self, widget, event):
136 self.button_down = False
139 def motion_notify_event(self, widget, event):
141 widget.get_allocation().height -
142 widget.get_style_context().get_property('min-height', Gtk.StateFlags.NORMAL)
145 delta_y = (self.button_down_y - event.y) / slider_length
146 y = self.button_down_value + delta_y
152 self.adjustment.set_value(y)
155 def scroll_event(self, widget, event):
156 delta = self.adjustment.step_increment
157 value = self.adjustment.get_value()
158 if event.direction == Gdk.ScrollDirection.UP:
160 elif event.direction == Gdk.ScrollDirection.DOWN:
162 elif event.direction == Gdk.ScrollDirection.SMOOTH:
163 y = value - event.delta_y * delta
170 self.adjustment.set_value(y)
174 class BalanceSlider(Gtk.Scale):
175 def __init__(self, adjustment, preferred_width, preferred_height):
176 Gtk.Scale.__init__(self, orientation=Gtk.Orientation.HORIZONTAL)
177 self.adjustment = adjustment
178 self.set_adjustment(adjustment)
179 self.set_has_origin(False)
180 self.set_draw_value(False)
181 self.set_property("has-tooltip", True)
182 self._preferred_width = preferred_width
183 self._preferred_height = preferred_height
184 self._button_down = False
186 self.add_mark(-1.0, Gtk.PositionType.TOP)
187 self.add_mark(0.0, Gtk.PositionType.TOP)
188 self.add_mark(1.0, Gtk.PositionType.TOP)
190 self.connect("button-press-event", self.on_button_press_event)
191 self.connect("button-release-event", self.on_button_release_event)
192 self.connect("motion-notify-event", self.on_motion_notify_event)
193 self.connect("scroll-event", self.on_scroll_event)
194 self.connect("query-tooltip", self.on_query_tooltip)
196 def get_preferred_width(self):
197 return self._preferred_width
199 def get_preferred_height(self):
200 return self._preferred_height
202 def on_button_press_event(self, widget, event):
203 if event.button == 1:
204 if event.type == Gdk.EventType.BUTTON_PRESS:
205 self._button_down = True
206 self._button_down_x = event.x
207 self._button_down_value = self.get_value()
209 elif event.type == Gdk.EventType._2BUTTON_PRESS:
210 self.adjustment.set_balance(0)
215 def on_button_release_event(self, widget, event):
216 self._button_down = False
219 def on_motion_notify_event(self, widget, event):
221 widget.get_allocation().width -
222 widget.get_style_context().get_property('min-width', Gtk.StateFlags.NORMAL)
225 if self._button_down:
226 delta_x = (event.x - self._button_down_x) / slider_length
227 x = self._button_down_value + 2 * delta_x
228 self.adjustment.set_balance(min(1, max(x, -1)))
233 def on_query_tooltip(self, widget, x, y, keyboard_mode, tooltip, *args):
234 val = int(self.adjustment.get_value() * 50)
236 tooltip.set_text("Center")
238 tooltip.set_text("Left: %s / Right: %d" % (50 - val, val + 50))
242 def on_scroll_event(self, widget, event):
243 delta = self.get_adjustment().get_step_increment()
244 value = self.get_value()
246 if event.direction == Gdk.ScrollDirection.UP:
248 elif event.direction == Gdk.ScrollDirection.DOWN:
250 elif event.direction == Gdk.ScrollDirection.SMOOTH:
251 x = value - event.delta_y * delta
253 self.set_value(min(1, max(x, -1)))
257 class CustomSliderWidget(Gtk.DrawingArea):
258 def __init__(self, adjustment):
259 Gtk.DrawingArea.__init__(self)
260 self.adjustment = adjustment
262 self.connect("draw", self.on_expose)
263 self.connect("size_allocate", self.on_size_allocate)
264 adjustment.connect("value-changed", self.on_value_changed)
265 self.connect("button-press-event", self.on_mouse)
266 self.connect("motion-notify-event", self.on_mouse)
267 self.connect("scroll-event", self.on_scroll)
268 self.set_events(Gdk.EventMask.BUTTON1_MOTION_MASK |
269 Gdk.EventMask.SCROLL_MASK | Gdk.EventMask.BUTTON_PRESS_MASK)
271 def on_scroll(self, widget, event):
272 delta = self.adjustment.step_increment
273 value = self.adjustment.get_value()
274 if event.direction == Gdk.ScrollDirection.UP:
276 elif event.direction == Gdk.ScrollDirection.DOWN:
282 self.adjustment.set_value(y)
285 def on_mouse(self, widget, event):
286 if event.type == Gdk.EventType.BUTTON_PRESS:
287 log.debug("Mouse button %u pressed %ux%u", event.button, event.x, event.y)
288 if event.button == 1:
289 if event.y >= self.slider_rail_up and event.y < self.slider_rail_up + self.slider_rail_height:
290 self.adjustment.set_value(1 - float(event.y - self.slider_rail_up) / float(self.slider_rail_height))
291 elif event.type == Gdk.EventType.MOTION_NOTIFY:
292 log.debug("Mouse motion %ux%u", event.x, event.y)
293 if event.y < self.slider_rail_up:
294 y = self.slider_rail_up
295 elif event.y > self.slider_rail_up + self.slider_rail_height:
296 y = self.slider_rail_up + self.slider_rail_height
299 self.adjustment.set_value(1 - float(y - self.slider_rail_up) / float(self.slider_rail_height))
303 def on_value_changed(self, adjustment):
304 self.invalidate_all()
306 def on_expose(self, widget, cairo_ctx):
310 def get_preferred_width(self, widget):
311 minimal_width = natural_width = self.width
312 return (minimal_width, natural_width)
314 def get_preferred_height(self, widget):
315 requisition = Gtk.Requisition()
316 on_size_request(self, widget, requisition)
317 minimal_height = natural_heigt = requisition.height
318 return (minimal_height, natural_height)
320 def on_size_allocate(self, widget, allocation):
321 self.width = float(allocation.width)
322 self.height = float(allocation.height)
325 def on_size_request(self, widget, requisition):
326 requisition.width = 20
328 def invalidate_all(self):
329 if hasattr(self, 'width') and hasattr(self, 'height'):
330 self.queue_draw_area(0, 0, int(self.width), int(self.height))
332 def draw(self, cairo_ctx):
334 state = Gtk.StateType.PRELIGHT
336 state = Gtk.StateType.NORMAL
338 #cairo_ctx.rectangle(0, 0, self.width, self.height)
339 #cairo_ctx.set_source_color(self.style.bg[state])
340 #cairo_ctx.fill_preserve()
341 #Gdk.cairo_set_source_color(cairo_ctx,
342 # self.get_style_context().get_color(state).to_color())
345 slider_knob_width = 37.5 if self.width * 3 / 4 > 37.5 else self.width * 3 / 4
346 slider_knob_height = slider_knob_width * 2
347 slider_knob_height -= slider_knob_height % 2
348 slider_knob_height += 1
350 slider_x = self.width/2
352 cairo_ctx.set_line_width(1)
355 Gdk.cairo_set_source_color(cairo_ctx,
356 self.get_style_context().get_color(state).to_color())
357 self.slider_rail_up = slider_knob_height/2 + (self.width - slider_knob_width)/2
358 self.slider_rail_height = self.height - 2 * self.slider_rail_up
359 cairo_ctx.move_to(slider_x, self.slider_rail_up)
360 cairo_ctx.line_to(slider_x, self.slider_rail_height + self.slider_rail_up)
364 slider_y = round(self.slider_rail_up + self.slider_rail_height * (1 - self.adjustment.get_value()))
365 lg = cairo.LinearGradient(slider_x -
366 float(slider_knob_width)/2, slider_y - slider_knob_height/2,
367 slider_x - float(slider_knob_width)/2, slider_y +
368 slider_knob_height/2)
370 lg.add_color_stop_rgba(0, 0.55, 0.55, 0.55, slider_alpha)
371 lg.add_color_stop_rgba(0.1, 0.65, 0.65, 0.65, slider_alpha)
372 lg.add_color_stop_rgba(0.1, 0.75, 0.75, 0.75, slider_alpha)
373 lg.add_color_stop_rgba(0.125, 0.75, 0.75, 0.75, slider_alpha)
374 lg.add_color_stop_rgba(0.125, 0.15, 0.15, 0.15, slider_alpha)
375 lg.add_color_stop_rgba(0.475, 0.35, 0.35, 0.35, slider_alpha)
376 lg.add_color_stop_rgba(0.475, 0, 0, 0, slider_alpha)
377 lg.add_color_stop_rgba(0.525, 0, 0, 0, slider_alpha)
378 lg.add_color_stop_rgba(0.525, 0.35, 0.35, 0.35, slider_alpha)
379 lg.add_color_stop_rgba(0.875, 0.65, 0.65, 0.65, slider_alpha)
380 lg.add_color_stop_rgba(0.875, 0.75, 0.75, 0.75, slider_alpha)
381 lg.add_color_stop_rgba(0.900, 0.75, 0.75, 0.75, slider_alpha)
382 lg.add_color_stop_rgba(0.900, 0.15, 0.15, 0.15, slider_alpha)
383 lg.add_color_stop_rgba(1.000, 0.10, 0.10, 0.10, slider_alpha)
384 cairo_ctx.rectangle(slider_x - float(slider_knob_width)/2,
385 slider_y - slider_knob_height/2,
386 float(slider_knob_width),
388 Gdk.cairo_set_source_color(cairo_ctx,
389 self.get_style_context().get_background_color(state).to_color())
390 cairo_ctx.fill_preserve()
391 cairo_ctx.set_source(lg)