Fix mising parentheses for method call
[jack_mixer.git] / channel.py
blobe28f4524a39c5ca6dfab961f865d2896ddf08438
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 gi
21 from gi.repository import Gtk
22 from gi.repository import Gdk
23 from gi.repository import GObject
24 from gi.repository import Pango
26 import abspeak
27 import meter
28 import slider
29 from serialization import SerializedObject
31 try:
32 import phat
33 except:
34 phat = None
37 log = logging.getLogger(__name__)
38 button_padding = 1
39 css = b"""
40 .top_label {
41 padding: 0px .1em;
42 min-height: 1.5rem;
45 .wide {
46 font-size: medium
49 .narrow {
50 font-size: smaller
53 button {
54 padding: 0px
56 """
57 css_provider = Gtk.CssProvider()
58 css_provider.load_from_data(css)
59 context = Gtk.StyleContext()
60 screen = Gdk.Screen.get_default()
61 context.add_provider_for_screen(screen, css_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)
64 def get_text_color(background_color):
65 """Calculates the luminance of the given color (GdkRGBA)
66 and returns an appropriate text color."""
67 # luminance coefficients taken from section C-9 from
68 # http://www.faqs.org/faqs/graphics/colorspace-faq/
69 brightess = background_color.red * 0.212671 + \
70 background_color.green * 0.715160 + \
71 background_color.blue * 0.072169
73 if brightess > 0.5:
74 return 'black'
75 else:
76 return 'white'
79 def set_background_color(widget, name, color):
80 color_string = color.to_string()
81 css = """
82 .%s {
83 background-color: %s;
84 color: %s;
86 """ % (name, color_string, get_text_color(color))
88 css_provider = Gtk.CssProvider()
89 css_provider.load_from_data(css.encode('utf-8'))
90 context = Gtk.StyleContext()
91 screen = Gdk.Screen.get_default()
92 context.add_provider_for_screen(screen, css_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)
94 widget_context = widget.get_style_context()
95 widget_context.add_class(name)
98 def random_color():
99 from random import uniform, seed
100 seed()
101 return Gdk.RGBA(uniform(0, 1), uniform(0, 1), uniform(0, 1), 1)
104 class Channel(Gtk.VBox, SerializedObject):
105 '''Widget with slider and meter used as base class for more specific
106 channel widgets'''
107 monitor_button = None
108 num_instances = 0
109 def __init__(self, app, name, stereo, value = None):
110 Gtk.VBox.__init__(self)
111 self.app = app
112 self.mixer = app.mixer
113 self.channel = None
114 self.gui_factory = app.gui_factory
115 self._channel_name = name
116 self.stereo = stereo
117 self.initial_value = value
118 self.meter_scale = self.gui_factory.get_default_meter_scale()
119 self.slider_scale = self.gui_factory.get_default_slider_scale()
120 self.slider_adjustment = slider.AdjustmentdBFS(self.slider_scale, 0.0, 0.02)
121 self.balance_adjustment = slider.BalanceAdjustment()
122 self.post_fader_output_channel = None
123 self.future_out_mute = None
124 self.future_volume_midi_cc = None
125 self.future_balance_midi_cc = None
126 self.future_mute_midi_cc = None
127 self.future_solo_midi_cc = None
128 self.css_name = "css_name_%d" % Channel.num_instances
129 self.label_name = None
130 self.wide = True
131 self.label_chars_wide = 12
132 self.label_chars_narrow = 8
133 Channel.num_instances += 1
135 def get_channel_name(self):
136 return self._channel_name
138 def set_channel_name(self, name):
139 self.app.on_channel_rename(self._channel_name, name);
140 self._channel_name = name
141 if self.label_name:
142 self.label_name.set_text(name)
143 if len(name) > (self.label_chars_wide if self.wide else self.label_chars_narrow):
144 self.label_name.set_tooltip_text(name)
145 if self.channel:
146 self.channel.name = name
147 if self.post_fader_output_channel:
148 self.post_fader_output_channel.name = "%s Out" % name;
149 channel_name = property(get_channel_name, set_channel_name)
151 def realize(self):
152 log.debug('Realizing channel "%s".', self.channel_name)
153 if self.future_out_mute != None:
154 self.channel.out_mute = self.future_out_mute
156 # Widgets
157 # Channel strip label
158 self.vbox = Gtk.VBox()
159 self.pack_start(self.vbox, False, True, 0)
160 self.label_name = Gtk.Label()
161 self.label_name.get_style_context().add_class('top_label')
162 self.label_name.set_text(self.channel_name)
163 self.label_name.set_max_width_chars(self.label_chars_wide if self.wide else
164 self.label_chars_narrow)
165 self.label_name.set_ellipsize(Pango.EllipsizeMode.MIDDLE)
166 self.label_name_event_box = Gtk.EventBox()
167 self.label_name_event_box.connect('button-press-event', self.on_label_mouse)
168 self.label_name_event_box.add(self.label_name)
170 # Volume fader
171 self.slider = None
172 self.create_slider_widget()
174 # Volume entry
175 self.volume_digits = Gtk.Entry()
176 self.volume_digits.set_property('xalign', 0.5)
177 self.volume_digits.connect("key-press-event", self.on_volume_digits_key_pressed)
178 self.volume_digits.connect("focus-out-event", self.on_volume_digits_focus_out)
180 # Peak level label
181 self.abspeak = abspeak.AbspeakWidget()
182 self.abspeak.connect("reset", self.on_abspeak_reset)
183 self.abspeak.connect("volume-adjust", self.on_abspeak_adjust)
185 # Level meter
186 if self.stereo:
187 self.meter = meter.StereoMeterWidget(self.meter_scale)
188 else:
189 self.meter = meter.MonoMeterWidget(self.meter_scale)
191 self.meter.set_events(Gdk.EventMask.SCROLL_MASK)
192 self.on_vumeter_color_changed(self.gui_factory)
194 if self.initial_value != None:
195 if self.initial_value == True:
196 self.slider_adjustment.set_value(0)
197 else:
198 self.slider_adjustment.set_value_db(0)
200 self.slider_adjustment.connect("volume-changed", self.on_volume_changed)
201 self.slider_adjustment.connect("volume-changed-from-midi", self.on_volume_changed_from_midi)
202 self.balance_adjustment.connect("balance-changed", self.on_balance_changed)
204 self.gui_factory.connect("default-meter-scale-changed", self.on_default_meter_scale_changed)
205 self.gui_factory.connect("default-slider-scale-changed", self.on_default_slider_scale_changed)
206 self.gui_factory.connect('vumeter-color-changed', self.on_vumeter_color_changed)
207 self.gui_factory.connect('vumeter-color-scheme-changed', self.on_vumeter_color_changed)
208 self.gui_factory.connect('use-custom-widgets-changed', self.on_custom_widgets_changed)
210 self.connect("key-press-event", self.on_key_pressed)
211 self.connect("scroll-event", self.on_scroll)
213 def unrealize(self):
214 log.debug('Unrealizing channel "%s".', self.channel_name)
215 pass
217 def narrow(self):
218 self.wide = False
219 ctx = self.label_name.get_style_context()
220 ctx.remove_class('wide')
221 ctx.add_class('narrow')
222 label = self.label_name.get_label()
223 self.label_name.set_max_width_chars(self.label_chars_narrow)
225 if len(label) > self.label_chars_narrow:
226 self.label_name.set_tooltip_text(label)
228 def widen(self):
229 self.wide = True
230 self.label_name.set_tooltip_text(None)
231 ctx = self.label_name.get_style_context()
232 ctx.remove_class('narrow')
233 ctx.add_class('wide')
234 label = self.label_name.get_label()
235 self.label_name.set_max_width_chars(self.label_chars_wide)
237 if len(label) > self.label_chars_wide:
238 self.label_name.set_tooltip_text(label)
240 def balance_preferred_width(self):
241 return (20, 20)
243 def _preferred_height(self):
244 return (0, 100)
246 def create_balance_widget(self):
247 if self.gui_factory.use_custom_widgets and phat:
248 self.balance = phat.HFanSlider()
249 self.balance.set_default_value(0)
250 self.balance.set_adjustment(self.balance_adjustment)
251 else:
252 self.balance = Gtk.Scale()
253 self.balance.get_preferred_width = self.balance_preferred_width
254 self.balance.get_preferred_height = self._preferred_height
255 self.balance.set_orientation(Gtk.Orientation.HORIZONTAL)
256 self.balance.set_adjustment(self.balance_adjustment)
257 self.balance.set_has_origin(False)
258 self.balance.set_draw_value(False)
259 self.balance.button_down = False
260 self.balance.connect('button-press-event', self.on_balance_button_press_event)
261 self.balance.connect('button-release-event', self.on_balance_button_release_event)
262 self.balance.connect("motion-notify-event", self.on_balance_motion_notify_event)
263 self.balance.connect("scroll-event", self.on_balance_scroll_event)
266 self.pack_start(self.balance, False, True, 0)
267 if self.monitor_button:
268 self.reorder_child(self.monitor_button, -1)
269 self.balance.show()
271 def on_label_mouse(self, widget, event):
272 if event.type == Gdk.EventType._2BUTTON_PRESS:
273 if event.button == 1:
274 self.on_channel_properties()
275 return True
276 elif (event.state & Gdk.ModifierType.CONTROL_MASK and
277 event.type == Gdk.EventType.BUTTON_PRESS and
278 event.button == 1):
279 if self.wide:
280 self.narrow()
281 else:
282 self.widen()
283 return True
285 def on_balance_button_press_event(self, widget, event):
286 if event.button == 1 and event.type == Gdk.EventType.BUTTON_PRESS:
287 self.balance.button_down = True
288 self.balance.button_down_x = event.x
289 self.balance.button_down_value = self.balance.get_value()
290 return True
291 if event.button == 1 and event.type == Gdk.EventType._2BUTTON_PRESS:
292 self.balance_adjustment.set_balance(0)
293 return True
294 return False
296 def on_balance_button_release_event(self, widget, event):
297 self.balance.button_down = False
298 return False
300 def on_balance_motion_notify_event(self, widget, event):
301 slider_length = widget.get_allocation().width - widget.get_style_context().get_property('min-width', Gtk.StateFlags.NORMAL)
302 if self.balance.button_down:
303 delta_x = (event.x - self.balance.button_down_x) / slider_length
304 x = self.balance.button_down_value + 2 * delta_x
305 if x >= 1:
306 x = 1
307 elif x <= -1:
308 x = -1
309 self.balance_adjustment.set_balance(x)
310 return True
312 def on_balance_scroll_event(self, widget, event):
313 bal = self.balance
314 delta = bal.get_adjustment().get_step_increment()
315 value = bal.get_value()
316 if event.direction == Gdk.ScrollDirection.UP:
317 x = value - delta
318 elif event.direction == Gdk.ScrollDirection.DOWN:
319 x = value + delta
320 elif event.direction == Gdk.ScrollDirection.SMOOTH:
321 x = value - event.delta_y * delta
323 if x >= 1:
324 x = 1
325 elif x <= -1:
326 x = -1
327 bal.set_value(x)
328 return True
330 def create_slider_widget(self):
331 parent = None
332 if self.slider:
333 parent = self.slider.get_parent()
334 self.slider.destroy()
335 if self.gui_factory.use_custom_widgets:
336 self.slider = slider.CustomSliderWidget(self.slider_adjustment)
337 else:
338 self.slider = slider.GtkSlider(self.slider_adjustment)
339 if parent:
340 parent.pack_start(self.slider, True, True, 0)
341 parent.reorder_child(self.slider, 0)
342 self.slider.show()
344 def on_default_meter_scale_changed(self, gui_factory, scale):
345 log.debug("Default meter scale change detected.")
346 self.meter.set_scale(scale)
348 def on_default_slider_scale_changed(self, gui_factory, scale):
349 log.debug("Default slider scale change detected.")
350 self.slider_scale = scale
351 self.slider_adjustment.set_scale(scale)
352 if self.channel:
353 self.channel.midi_scale = self.slider_scale.scale
355 def on_vumeter_color_changed(self, gui_factory, *args):
356 color = gui_factory.get_vumeter_color()
357 color_scheme = gui_factory.get_vumeter_color_scheme()
358 if color_scheme != 'solid':
359 self.meter.set_color(None)
360 else:
361 self.meter.set_color(Gdk.color_parse(color))
363 def on_custom_widgets_changed(self, gui_factory, value):
364 self.balance.destroy()
365 self.create_balance_widget()
366 self.create_slider_widget()
368 def on_abspeak_adjust(self, abspeak, adjust):
369 log.debug("abspeak adjust %f", adjust)
370 self.slider_adjustment.set_value_db(self.slider_adjustment.get_value_db() + adjust)
371 self.channel.abspeak = None
372 #self.update_volume(False) # We want to update gui even if actual decibels have not changed (scale wrap for example)
374 def on_abspeak_reset(self, abspeak):
375 log.debug("abspeak reset")
376 self.channel.abspeak = None
378 def on_volume_digits_key_pressed(self, widget, event):
379 if (event.keyval == Gdk.KEY_Return or event.keyval == Gdk.KEY_KP_Enter):
380 db_text = self.volume_digits.get_text()
381 try:
382 db = float(db_text)
383 log.debug('Volume digits confirmation "%f dBFS".', db)
384 except (ValueError) as e:
385 log.debug("Volume digits confirmation ignore, reset to current.")
386 self.update_volume(False)
387 return
388 self.slider_adjustment.set_value_db(db)
389 #self.grab_focus()
390 #self.update_volume(False) # We want to update gui even if actual decibels have not changed (scale wrap for example)
392 def on_volume_digits_focus_out(self, widget, event):
393 log.debug("Volume digits focus out detected.")
394 self.update_volume(False)
396 def read_meter(self):
397 if not self.channel:
398 return
399 if self.stereo:
400 peak_left, peak_right, rms_left, rms_right = self.channel.kmeter
401 self.meter.set_values(peak_left, peak_right, rms_left, rms_right)
402 else:
403 peak, rms = self.channel.kmeter
404 self.meter.set_values(peak, rms)
406 self.abspeak.set_peak(self.channel.abspeak)
408 def on_scroll(self, widget, event):
409 if event.direction == Gdk.ScrollDirection.DOWN:
410 self.slider_adjustment.step_down()
411 elif event.direction == Gdk.ScrollDirection.UP:
412 self.slider_adjustment.step_up()
413 return True
415 def update_volume(self, update_engine, from_midi = False):
416 db = self.slider_adjustment.get_value_db()
418 db_text = "%.2f" % db
419 self.volume_digits.set_text(db_text)
421 if update_engine:
422 if not from_midi:
423 self.channel.volume = db
424 self.app.update_monitor(self)
426 def on_volume_changed(self, adjustment):
427 self.update_volume(True)
429 def on_volume_changed_from_midi(self, adjustment):
430 self.update_volume(True, from_midi = True)
432 def on_balance_changed(self, adjustment):
433 balance = self.balance_adjustment.get_value()
434 log.debug("%s balance: %f", self.channel_name, balance)
435 self.channel.balance = balance
436 self.app.update_monitor(self)
438 def on_key_pressed(self, widget, event):
439 if (event.keyval == Gdk.KEY_Up):
440 log.debug(self.channel_name + " Up")
441 self.slider_adjustment.step_up()
442 return True
443 elif (event.keyval == Gdk.KEY_Down):
444 log.debug(self.channel_name + " Down")
445 self.slider_adjustment.step_down()
446 return True
448 return False
450 def serialize(self, object_backend):
451 object_backend.add_property("volume", "%f" % self.slider_adjustment.get_value_db())
452 object_backend.add_property("balance", "%f" % self.balance_adjustment.get_value())
453 object_backend.add_property("wide", "%s" % str(self.wide))
455 if hasattr(self.channel, 'out_mute'):
456 object_backend.add_property('out_mute', str(self.channel.out_mute))
457 if self.channel.volume_midi_cc != -1:
458 object_backend.add_property('volume_midi_cc', str(self.channel.volume_midi_cc))
459 if self.channel.balance_midi_cc != -1:
460 object_backend.add_property('balance_midi_cc', str(self.channel.balance_midi_cc))
461 if self.channel.mute_midi_cc != -1:
462 object_backend.add_property('mute_midi_cc', str(self.channel.mute_midi_cc))
463 if self.channel.solo_midi_cc != -1:
464 object_backend.add_property('solo_midi_cc', str(self.channel.solo_midi_cc))
467 def unserialize_property(self, name, value):
468 if name == "volume":
469 self.slider_adjustment.set_value_db(float(value))
470 return True
471 if name == "balance":
472 self.balance_adjustment.set_value(float(value))
473 return True
474 if name == 'out_mute':
475 self.future_out_mute = (value == 'True')
476 return True
477 if name == 'volume_midi_cc':
478 self.future_volume_midi_cc = int(value)
479 return True
480 if name == 'balance_midi_cc':
481 self.future_balance_midi_cc = int(value)
482 return True
483 if name == 'mute_midi_cc':
484 self.future_mute_midi_cc = int(value)
485 return True
486 if name == 'solo_midi_cc':
487 self.future_solo_midi_cc = int(value)
488 return True
489 if name == "wide":
490 self.wide = value == "True"
491 return True
492 return False
494 def on_midi_event_received(self, *args):
495 self.slider_adjustment.set_value_db(self.channel.volume, from_midi = True)
496 self.balance_adjustment.set_balance(self.channel.balance, from_midi = True)
498 def on_monitor_button_toggled(self, button):
499 if button.get_active():
500 for channel in self.app.channels + self.app.output_channels:
501 if channel.monitor_button.get_active() and channel.monitor_button is not button:
502 channel.monitor_button.handler_block_by_func(
503 channel.on_monitor_button_toggled)
504 channel.monitor_button.set_active(False)
505 channel.monitor_button.handler_unblock_by_func(
506 channel.on_monitor_button_toggled)
507 self.app.set_monitored_channel(self)
508 else:
509 if self.app._monitored_channel.channel.name == self.channel.name:
510 self.monitor_button.handler_block_by_func(self.on_monitor_button_toggled)
511 self.monitor_button.set_active(True)
512 self.monitor_button.handler_unblock_by_func(self.on_monitor_button_toggled)
514 def set_monitored(self):
515 if self.channel:
516 self.app.set_monitored_channel(self)
517 self.monitor_button.set_active(True)
519 def set_color(self, color):
520 self.color = color
521 set_background_color(self.label_name_event_box, self.css_name, self.color)
523 class InputChannel(Channel):
524 post_fader_output_channel = None
526 def realize(self):
527 self.channel = self.mixer.add_channel(self.channel_name, self.stereo)
529 if self.channel == None:
530 raise Exception("Cannot create a channel")
532 super().realize()
534 if self.future_volume_midi_cc != None:
535 self.channel.volume_midi_cc = self.future_volume_midi_cc
536 if self.future_balance_midi_cc != None:
537 self.channel.balance_midi_cc = self.future_balance_midi_cc
538 if self.future_mute_midi_cc != None:
539 self.channel.mute_midi_cc = self.future_mute_midi_cc
540 if self.future_solo_midi_cc != None:
541 self.channel.solo_midi_cc = self.future_solo_midi_cc
542 if self.app._init_solo_channels and self.channel_name in self.app._init_solo_channels:
543 self.channel.solo = True
545 self.channel.midi_scale = self.slider_scale.scale
547 self.on_volume_changed(self.slider_adjustment)
548 self.on_balance_changed(self.balance_adjustment)
550 entries = [Gtk.TargetEntry.new("INPUT_CHANNEL", Gtk.TargetFlags.SAME_APP, 0)]
551 self.label_name_event_box.drag_source_set(Gdk.ModifierType.BUTTON1_MASK, entries,
552 Gdk.DragAction.MOVE)
553 self.label_name_event_box.connect("drag-data-get", self.on_drag_data_get)
554 self.drag_dest_set(Gtk.DestDefaults.ALL, entries, Gdk.DragAction.MOVE)
555 self.connect_after("drag-data-received", self.on_drag_data_received)
557 self.vbox.pack_start(self.label_name_event_box, True, True, 0)
558 # self.label_stereo = Gtk.Label()
559 # if self.stereo:
560 # self.label_stereo.set_text("stereo")
561 # else:
562 # self.label_stereo.set_text("mono")
563 # self.label_stereo.set_size_request(0, -1)
564 # self.vbox.pack_start(self.label_stereo, True)
566 frame = Gtk.Frame()
567 frame.set_shadow_type(Gtk.ShadowType.IN)
568 frame.add(self.abspeak);
569 self.pack_start(frame, False, True, 0)
571 # hbox child at lower part
572 self.hbox = Gtk.HBox()
573 self.hbox.pack_start(self.slider, True, True, 0)
574 frame = Gtk.Frame()
575 frame.set_shadow_type(Gtk.ShadowType.IN)
576 frame.add(self.meter);
577 self.hbox.pack_start(frame, True, True, 0)
578 frame = Gtk.Frame()
579 frame.set_shadow_type(Gtk.ShadowType.IN)
580 frame.add(self.hbox);
581 self.pack_start(frame, True, True, 0)
583 self.volume_digits.set_width_chars(6)
584 self.pack_start(self.volume_digits, False, False, 0)
586 self.create_balance_widget()
588 self.hbox_mutesolo = Gtk.Box(False, 0)
590 self.mute = Gtk.ToggleButton()
591 self.mute.set_label("M")
592 self.mute.set_name("mute")
593 self.mute.set_active(self.channel.out_mute)
594 self.mute.connect("toggled", self.on_mute_toggled)
595 self.hbox_mutesolo.pack_start(self.mute, True, True, 0)
597 self.solo = Gtk.ToggleButton()
598 self.solo.set_label("S")
599 self.solo.set_name("solo")
600 self.solo.set_active(self.channel.solo)
601 self.solo.connect("toggled", self.on_solo_toggled)
602 self.hbox_mutesolo.pack_start(self.solo, True, True, 0)
604 self.pack_start(self.hbox_mutesolo, False, False, 0)
606 self.monitor_button = Gtk.ToggleButton('MON')
607 self.monitor_button.connect('toggled', self.on_monitor_button_toggled)
608 self.pack_start(self.monitor_button, False, False, 0)
610 if not self.wide:
611 self.narrow()
613 def narrow(self):
614 super().narrow()
615 for cg in self.get_control_groups():
616 cg.narrow()
618 def widen(self):
619 super().widen()
620 for cg in self.get_control_groups():
621 cg.widen()
623 def on_drag_data_get(self, widget, drag_context, data, info, time):
624 channel = widget.get_parent().get_parent()
625 data.set(data.get_target(), 8, channel._channel_name.encode('utf-8'))
627 def on_drag_data_received(self, widget, drag_context, x, y, data, info, time):
628 source_name = data.get_data().decode('utf-8')
629 if source_name == self._channel_name:
630 return
631 self.emit("input-channel-order-changed", source_name, self._channel_name)
633 def add_control_group(self, channel):
634 control_group = ControlGroup(channel, self)
635 control_group.show_all()
636 self.vbox.pack_start(control_group, True, True, 0)
637 return control_group
639 def remove_control_group(self, channel):
640 ctlgroup = self.get_control_group(channel)
641 self.vbox.remove(ctlgroup)
643 def update_control_group(self, channel):
644 for control_group in self.vbox.get_children():
645 if isinstance(control_group, ControlGroup):
646 if control_group.output_channel is channel:
647 control_group.update()
649 def get_control_group(self, channel):
650 for control_group in self.get_control_groups():
651 if control_group.output_channel is channel:
652 return control_group
653 return None
655 def get_control_groups(self):
656 ctlgroups = []
657 for c in self.vbox.get_children():
658 if isinstance(c, ControlGroup):
659 ctlgroups.append(c)
660 return ctlgroups
662 def unrealize(self):
663 super().unrealize()
664 if self.post_fader_output_channel:
665 self.post_fader_output_channel.remove()
666 self.post_fader_output_channel = None
667 self.channel.remove()
668 self.channel = None
670 channel_properties_dialog = None
672 def on_channel_properties(self):
673 if not self.channel_properties_dialog:
674 self.channel_properties_dialog = ChannelPropertiesDialog(self, self.app)
675 self.channel_properties_dialog.show()
676 self.channel_properties_dialog.present()
678 def on_mute_toggled(self, button):
679 self.channel.out_mute = self.mute.get_active()
681 def on_solo_toggled(self, button):
682 self.channel.solo = self.solo.get_active()
684 def midi_events_check(self):
685 if hasattr(self, 'channel') and self.channel.midi_in_got_events:
686 self.mute.set_active(self.channel.out_mute)
687 self.solo.set_active(self.channel.solo)
688 super().on_midi_event_received()
690 def on_solo_button_pressed(self, button, event, *args):
691 if event.button == 3:
692 # right click on the solo button, act on all output channels
693 if button.get_active(): # was soloed
694 button.set_active(False)
695 if hasattr(button, 'touched_channels'):
696 touched_channels = button.touched_channels
697 for chan in touched_channels:
698 ctlgroup = self.get_control_group(chan)
699 ctlgroup.solo.set_active(False)
700 del button.touched_channels
701 else: # was not soloed
702 button.set_active(True)
703 touched_channels = []
704 for chan in self.app.output_channels:
705 ctlgroup = self.get_control_group(chan)
706 if not ctlgroup.solo.get_active():
707 ctlgroup.solo.set_active(True)
708 touched_channels.append(chan)
709 button.touched_channels = touched_channels
710 return True
711 return False
713 @classmethod
714 def serialization_name(cls):
715 return 'input_channel'
717 def serialize(self, object_backend):
718 object_backend.add_property("name", self.channel_name)
719 if self.stereo:
720 object_backend.add_property("type", "stereo")
721 else:
722 object_backend.add_property("type", "mono")
723 super().serialize(object_backend)
725 def unserialize_property(self, name, value):
726 if name == "name":
727 self.channel_name = str(value)
728 return True
729 if name == "type":
730 if value == "stereo":
731 self.stereo = True
732 return True
733 if value == "mono":
734 self.stereo = False
735 return True
736 return super().unserialize_property(name, value)
738 GObject.signal_new("input-channel-order-changed", InputChannel,
739 GObject.SignalFlags.RUN_FIRST | GObject.SignalFlags.ACTION,
740 None, [GObject.TYPE_STRING, GObject.TYPE_STRING])
742 class OutputChannel(Channel):
743 _display_solo_buttons = False
745 _init_muted_channels = None
746 _init_solo_channels = None
747 _init_prefader_channels = None
749 channel_properties_dialog = None
751 def get_display_solo_buttons(self):
752 return self._display_solo_buttons
754 def set_display_solo_buttons(self, value):
755 self._display_solo_buttons = value
756 # notifying control groups
757 for inputchannel in self.app.channels:
758 inputchannel.update_control_group(self)
760 display_solo_buttons = property(get_display_solo_buttons, set_display_solo_buttons)
762 def realize(self):
763 self.channel = self.mixer.add_output_channel(self.channel_name, self.stereo)
765 if self.channel == None:
766 raise Exception("Cannot create a channel")
768 super().realize()
770 if self.future_volume_midi_cc != None:
771 self.channel.volume_midi_cc = self.future_volume_midi_cc
772 if self.future_balance_midi_cc != None:
773 self.channel.balance_midi_cc = self.future_balance_midi_cc
774 if self.future_mute_midi_cc != None:
775 self.channel.mute_midi_cc = self.future_mute_midi_cc
776 self.channel.midi_scale = self.slider_scale.scale
778 self.on_volume_changed(self.slider_adjustment)
779 self.on_balance_changed(self.balance_adjustment)
781 entries = [Gtk.TargetEntry.new("OUTPUT_CHANNEL", Gtk.TargetFlags.SAME_APP, 0)]
782 self.label_name_event_box.drag_source_set(Gdk.ModifierType.BUTTON1_MASK, entries,
783 Gdk.DragAction.MOVE)
784 self.label_name_event_box.connect("drag-data-get", self.on_drag_data_get)
785 self.drag_dest_set(Gtk.DestDefaults.ALL, entries, Gdk.DragAction.MOVE)
786 self.connect_after("drag-data-received", self.on_drag_data_received)
788 if not hasattr(self, 'color'):
789 self.color = random_color()
790 set_background_color(self.label_name_event_box, self.css_name, self.color)
791 self.vbox.pack_start(self.label_name_event_box, True, True, 0)
792 frame = Gtk.Frame()
793 frame.set_shadow_type(Gtk.ShadowType.IN)
794 frame.add(self.abspeak);
795 self.vbox.pack_start(frame, False, True, 0)
797 # hbox child at lower part
798 self.hbox = Gtk.HBox()
799 self.hbox.pack_start(self.slider, True, True, 0)
800 frame = Gtk.Frame()
801 frame.set_shadow_type(Gtk.ShadowType.IN)
802 frame.add(self.meter);
803 self.hbox.pack_start(frame, True, True, 0)
804 frame = Gtk.Frame()
805 frame.set_shadow_type(Gtk.ShadowType.IN)
806 frame.add(self.hbox);
807 self.pack_start(frame, True, True, 0)
809 self.volume_digits.set_width_chars(6)
810 self.pack_start(self.volume_digits, False, True, 0)
812 self.create_balance_widget()
814 self.mute = Gtk.ToggleButton()
815 self.mute.set_label("M")
816 self.mute.set_name("mute")
817 self.mute.set_active(self.channel.out_mute)
818 self.mute.connect("toggled", self.on_mute_toggled)
820 hbox = Gtk.HBox()
821 hbox.pack_start(self.mute, True, True, 0)
822 self.pack_start(hbox, False, False, 0)
824 self.monitor_button = Gtk.ToggleButton('MON')
825 self.monitor_button.connect('toggled', self.on_monitor_button_toggled)
826 self.pack_start(self.monitor_button, False, False, 0)
828 # add control groups to the input channels, and initialize them
829 # appropriately
830 for input_channel in self.app.channels:
831 ctlgroup = input_channel.add_control_group(self)
832 if self._init_muted_channels and input_channel.channel.name in self._init_muted_channels:
833 ctlgroup.mute.set_active(True)
834 if self._init_solo_channels and input_channel.channel.name in self._init_solo_channels:
835 ctlgroup.solo.set_active(True)
836 if self._init_prefader_channels and input_channel.channel.name in self._init_prefader_channels:
837 ctlgroup.prefader.set_active(True)
838 if not input_channel.wide:
839 ctlgroup.narrow()
841 self._init_muted_channels = None
842 self._init_solo_channels = None
843 self._init_prefader_channels = None
845 if not self.wide:
846 self.narrow()
848 def on_drag_data_get(self, widget, drag_context, data, info, time):
849 channel = widget.get_parent().get_parent()
850 data.set(data.get_target(), 8, channel._channel_name.encode('utf-8'))
852 def on_drag_data_received(self, widget, drag_context, x, y, data, info, time):
853 source_name = data.get_data().decode('utf-8')
854 if source_name == self._channel_name:
855 return
856 self.emit("output-channel-order-changed", source_name, self._channel_name)
858 def on_channel_properties(self):
859 if not self.channel_properties_dialog:
860 self.channel_properties_dialog = OutputChannelPropertiesDialog(self, self.app)
861 self.channel_properties_dialog.show()
862 self.channel_properties_dialog.present()
864 def on_mute_toggled(self, button):
865 self.channel.out_mute = self.mute.get_active()
867 def midi_events_check(self):
868 if self.channel != None and self.channel.midi_in_got_events:
869 self.mute.set_active(self.channel.out_mute)
870 super().on_midi_event_received()
872 def unrealize(self):
873 # remove control groups from input channels
874 for input_channel in self.app.channels:
875 input_channel.remove_control_group(self)
876 # then remove itself
877 super().unrealize()
878 self.channel.remove()
879 self.channel = None
881 @classmethod
882 def serialization_name(cls):
883 return 'output_channel'
885 def serialize(self, object_backend):
886 object_backend.add_property("name", self.channel_name)
887 if self.stereo:
888 object_backend.add_property("type", "stereo")
889 else:
890 object_backend.add_property("type", "mono")
891 if self.display_solo_buttons:
892 object_backend.add_property("solo_buttons", "true")
893 muted_channels = []
894 solo_channels = []
895 prefader_in_channels = []
896 for input_channel in self.app.channels:
897 if self.channel.is_muted(input_channel.channel):
898 muted_channels.append(input_channel)
899 if self.channel.is_solo(input_channel.channel):
900 solo_channels.append(input_channel)
901 if self.channel.is_in_prefader(input_channel.channel):
902 prefader_in_channels.append(input_channel)
903 if muted_channels:
904 object_backend.add_property('muted_channels', '|'.join([x.channel.name for x in muted_channels]))
905 if solo_channels:
906 object_backend.add_property('solo_channels', '|'.join([x.channel.name for x in solo_channels]))
907 if prefader_in_channels:
908 object_backend.add_property('prefader_channels', '|'.join([x.channel.name for x in prefader_in_channels]))
909 object_backend.add_property("color", self.color.to_string())
910 super().serialize(object_backend)
912 def unserialize_property(self, name, value):
913 if name == "name":
914 self.channel_name = str(value)
915 return True
916 if name == "type":
917 if value == "stereo":
918 self.stereo = True
919 return True
920 if value == "mono":
921 self.stereo = False
922 return True
923 if name == "solo_buttons":
924 if value == "true":
925 self.display_solo_buttons = True
926 return True
927 if name == 'muted_channels':
928 self._init_muted_channels = value.split('|')
929 return True
930 if name == 'solo_channels':
931 self._init_solo_channels = value.split('|')
932 return True
933 if name == 'prefader_channels':
934 self._init_prefader_channels = value.split('|')
935 return True
936 if name == 'color':
937 c = Gdk.RGBA()
938 c.parse(value)
939 self.color = c
940 return True
941 return super().unserialize_property(name, value)
943 class ChannelPropertiesDialog(Gtk.Dialog):
944 channel = None
946 def __init__(self, parent, app):
947 self.channel = parent
948 self.app = app
949 self.mixer = self.channel.mixer
950 Gtk.Dialog.__init__(self, 'Channel "%s" Properties' % self.channel.channel_name, app.window)
951 self.set_default_size(365, -1)
953 self.add_button(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL)
954 self.ok_button = self.add_button(Gtk.STOCK_APPLY, Gtk.ResponseType.APPLY)
955 self.set_default_response(Gtk.ResponseType.APPLY);
957 self.create_ui()
958 self.fill_ui()
960 self.connect('response', self.on_response_cb)
961 self.connect('delete-event', self.on_response_cb)
963 def create_frame(self, label, child, padding=8):
964 # need to pass an empty label, otherwise no label widget is created
965 frame = Gtk.Frame(label='')
966 frame.get_label_widget().set_markup('<b>%s</b>' % label)
967 frame.set_border_width(3)
968 frame.set_shadow_type(Gtk.ShadowType.NONE)
970 alignment = Gtk.Alignment.new(0.5, 0, 1, 1)
971 alignment.set_padding(padding, padding, padding, padding)
972 frame.add(alignment)
973 alignment.add(child)
975 return frame
977 def create_ui(self):
978 vbox = self.get_content_area()
980 self.properties_grid = grid = Gtk.Grid()
981 vbox.pack_start(self.create_frame('Properties', grid), True, True, 0)
982 grid.set_row_spacing(8)
983 grid.set_column_spacing(8)
984 grid.set_column_homogeneous(True)
986 name_label = Gtk.Label.new_with_mnemonic('_Name')
987 name_label.set_halign(Gtk.Align.START)
988 grid.attach(name_label, 0, 0, 1, 1)
989 self.entry_name = Gtk.Entry()
990 self.entry_name.set_activates_default(True)
991 self.entry_name.connect('changed', self.on_entry_name_changed)
992 name_label.set_mnemonic_widget(self.entry_name)
993 grid.attach(self.entry_name, 1, 0, 2, 1)
995 grid.attach(Gtk.Label(label='Mode', halign=Gtk.Align.START), 0, 1, 1, 1)
996 self.mono = Gtk.RadioButton.new_with_mnemonic(None, '_Mono')
997 self.stereo = Gtk.RadioButton.new_with_mnemonic_from_widget(self.mono, '_Stereo')
998 grid.attach(self.mono, 1, 1, 1, 1)
999 grid.attach(self.stereo, 2, 1, 1, 1)
1001 grid = Gtk.Grid()
1002 vbox.pack_start(self.create_frame('MIDI Control Changes', grid), True, True, 0)
1003 grid.set_row_spacing(8)
1004 grid.set_column_spacing(8)
1005 grid.set_column_homogeneous(True)
1007 cc_tooltip = "{} MIDI Control Change number (0-127, set to -1 to assign next free CC #)"
1008 volume_label = Gtk.Label.new_with_mnemonic('_Volume')
1009 volume_label.set_halign(Gtk.Align.START)
1010 grid.attach(volume_label, 0, 0, 1, 1)
1011 self.entry_volume_cc = Gtk.SpinButton.new_with_range(-1, 127, 1)
1012 self.entry_volume_cc.set_tooltip_text(cc_tooltip.format("Volume"))
1013 volume_label.set_mnemonic_widget(self.entry_volume_cc)
1014 grid.attach(self.entry_volume_cc, 1, 0, 1, 1)
1015 self.button_sense_midi_volume = Gtk.Button('Learn')
1016 self.button_sense_midi_volume.connect('clicked',
1017 self.on_sense_midi_volume_clicked)
1018 grid.attach(self.button_sense_midi_volume, 2, 0, 1, 1)
1020 balance_label = Gtk.Label.new_with_mnemonic('_Balance')
1021 balance_label.set_halign(Gtk.Align.START)
1022 grid.attach(balance_label, 0, 1, 1, 1)
1023 self.entry_balance_cc = Gtk.SpinButton.new_with_range(-1, 127, 1)
1024 self.entry_balance_cc.set_tooltip_text(cc_tooltip.format("Balance"))
1025 balance_label.set_mnemonic_widget(self.entry_balance_cc)
1026 grid.attach(self.entry_balance_cc, 1, 1, 1, 1)
1027 self.button_sense_midi_balance = Gtk.Button('Learn')
1028 self.button_sense_midi_balance.connect('clicked',
1029 self.on_sense_midi_balance_clicked)
1030 grid.attach(self.button_sense_midi_balance, 2, 1, 1, 1)
1032 mute_label = Gtk.Label.new_with_mnemonic('M_ute')
1033 mute_label.set_halign(Gtk.Align.START)
1034 grid.attach(mute_label, 0, 2, 1, 1)
1035 self.entry_mute_cc = Gtk.SpinButton.new_with_range(-1, 127, 1)
1036 self.entry_mute_cc.set_tooltip_text(cc_tooltip.format("Mute"))
1037 mute_label.set_mnemonic_widget(self.entry_mute_cc)
1038 grid.attach(self.entry_mute_cc, 1, 2, 1, 1)
1039 self.button_sense_midi_mute = Gtk.Button('Learn')
1040 self.button_sense_midi_mute.connect('clicked',
1041 self.on_sense_midi_mute_clicked)
1042 grid.attach(self.button_sense_midi_mute, 2, 2, 1, 1)
1044 if (isinstance(self, NewChannelDialog) or (self.channel and
1045 isinstance(self.channel, InputChannel))):
1046 solo_label = Gtk.Label.new_with_mnemonic('S_olo')
1047 solo_label.set_halign(Gtk.Align.START)
1048 grid.attach(solo_label, 0, 3, 1, 1)
1049 self.entry_solo_cc = Gtk.SpinButton.new_with_range(-1, 127, 1)
1050 self.entry_solo_cc.set_tooltip_text(cc_tooltip.format("Solo"))
1051 solo_label.set_mnemonic_widget(self.entry_solo_cc)
1052 grid.attach(self.entry_solo_cc, 1, 3, 1, 1)
1053 self.button_sense_midi_solo = Gtk.Button('Learn')
1054 self.button_sense_midi_solo.connect('clicked',
1055 self.on_sense_midi_solo_clicked)
1056 grid.attach(self.button_sense_midi_solo, 2, 3, 1, 1)
1058 self.vbox.show_all()
1060 def fill_ui(self):
1061 self.entry_name.set_text(self.channel.channel_name)
1062 if self.channel.channel.is_stereo:
1063 self.stereo.set_active(True)
1064 else:
1065 self.mono.set_active(True)
1066 self.mono.set_sensitive(False)
1067 self.stereo.set_sensitive(False)
1068 self.entry_volume_cc.set_value(self.channel.channel.volume_midi_cc)
1069 self.entry_balance_cc.set_value(self.channel.channel.balance_midi_cc)
1070 self.entry_mute_cc.set_value(self.channel.channel.mute_midi_cc)
1071 if (self.channel and isinstance(self.channel, InputChannel)):
1072 self.entry_solo_cc.set_value(self.channel.channel.solo_midi_cc)
1074 def sense_popup_dialog(self, entry):
1075 window = Gtk.Window.new(Gtk.WindowType.TOPLEVEL)
1076 window.set_destroy_with_parent(True)
1077 window.set_transient_for(self)
1078 window.set_decorated(False)
1079 window.set_modal(True)
1080 window.set_position(Gtk.WindowPosition.CENTER_ON_PARENT)
1081 window.set_border_width(10)
1083 vbox = Gtk.VBox(10)
1084 window.add(vbox)
1085 window.timeout = 5
1086 vbox.pack_start(Gtk.Label(label='Please move the MIDI control you want to use for this function.'), True, True, 0)
1087 timeout_label = Gtk.Label(label='This window will close in 5 seconds')
1088 vbox.pack_start(timeout_label, True, True, 0)
1089 def close_sense_timeout(window, entry):
1090 window.timeout -= 1
1091 timeout_label.set_text('This window will close in %d seconds.' % window.timeout)
1092 if window.timeout == 0:
1093 window.destroy()
1094 entry.set_value(self.mixer.last_midi_channel)
1095 return False
1096 return True
1097 window.show_all()
1098 GObject.timeout_add_seconds(1, close_sense_timeout, window, entry)
1100 def on_sense_midi_volume_clicked(self, *args):
1101 self.mixer.last_midi_channel = int(self.entry_volume_cc.get_value())
1102 self.sense_popup_dialog(self.entry_volume_cc)
1104 def on_sense_midi_balance_clicked(self, *args):
1105 self.mixer.last_midi_channel = int(self.entry_balance_cc.get_value())
1106 self.sense_popup_dialog(self.entry_balance_cc)
1108 def on_sense_midi_mute_clicked(self, *args):
1109 self.mixer.last_midi_channel = int(self.entry_mute_cc.get_value())
1110 self.sense_popup_dialog(self.entry_mute_cc)
1112 def on_sense_midi_solo_clicked(self, *args):
1113 self.mixer.last_midi_channel = int(self.entry_solo_cc.get_value())
1114 self.sense_popup_dialog(self.entry_solo_cc)
1116 def on_response_cb(self, dlg, response_id, *args):
1117 self.channel.channel_properties_dialog = None
1118 name = self.entry_name.get_text()
1119 if response_id == Gtk.ResponseType.APPLY:
1120 if name != self.channel.channel_name:
1121 self.channel.channel_name = name
1122 for control in ('volume', 'balance', 'mute', 'solo'):
1123 widget = getattr(self, 'entry_{}_cc'.format(control), None)
1124 if widget is not None:
1125 value = int(widget.get_value())
1126 if value != -1:
1127 setattr(self.channel.channel, '{}_midi_cc'.format(control), value)
1128 self.destroy()
1130 def on_entry_name_changed(self, entry):
1131 sensitive = False
1132 if len(entry.get_text()):
1133 if self.channel and self.channel.channel.name == entry.get_text():
1134 sensitive = True
1135 elif entry.get_text() not in [x.channel.name for x in self.app.channels] + \
1136 [x.channel.name for x in self.app.output_channels] + ['MAIN']:
1137 sensitive = True
1138 self.ok_button.set_sensitive(sensitive)
1140 GObject.signal_new("output-channel-order-changed", OutputChannel,
1141 GObject.SignalFlags.RUN_FIRST | GObject.SignalFlags.ACTION,
1142 None, [GObject.TYPE_STRING, GObject.TYPE_STRING])
1145 class NewChannelDialog(ChannelPropertiesDialog):
1146 def create_ui(self):
1147 ChannelPropertiesDialog.create_ui(self)
1148 self.add_initial_value_radio()
1149 self.vbox.show_all()
1151 def add_initial_value_radio(self):
1152 grid = self.properties_grid
1153 grid.attach(Gtk.Label(label='Value', halign=Gtk.Align.START), 0, 2, 1, 1)
1154 self.minus_inf = Gtk.RadioButton.new_with_mnemonic(None, '-_Inf')
1155 self.zero_dB = Gtk.RadioButton.new_with_mnemonic_from_widget(self.minus_inf, '_0dB')
1156 grid.attach(self.minus_inf, 1, 2, 1, 1)
1157 grid.attach(self.zero_dB, 2, 2, 1, 1)
1160 class NewInputChannelDialog(NewChannelDialog):
1161 def __init__(self, app):
1162 Gtk.Dialog.__init__(self, 'New Input Channel', app.window)
1163 self.set_default_size(365, -1)
1164 self.mixer = app.mixer
1165 self.app = app
1166 self.create_ui()
1168 self.add_button(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL)
1169 self.ok_button = self.add_button(Gtk.STOCK_ADD, Gtk.ResponseType.OK)
1170 self.ok_button.set_sensitive(False)
1171 self.set_default_response(Gtk.ResponseType.OK);
1173 def fill_ui(self, **values):
1174 self.entry_name.set_text(values.get('name', ''))
1175 # don't set MIDI CCs to previously used values, because they
1176 # would overwrite existing mappings, if accepted.
1177 self.entry_volume_cc.set_value(-1)
1178 self.entry_balance_cc.set_value(-1)
1179 self.entry_mute_cc.set_value(-1)
1180 self.entry_solo_cc.set_value(-1)
1181 self.stereo.set_active(values.get('stereo', True))
1182 self.minus_inf.set_active(values.get('value', False))
1183 self.entry_name.grab_focus()
1185 def get_result(self):
1186 return {
1187 'name': self.entry_name.get_text(),
1188 'stereo': self.stereo.get_active(),
1189 'volume_cc': int(self.entry_volume_cc.get_value()),
1190 'balance_cc': int(self.entry_balance_cc.get_value()),
1191 'mute_cc': int(self.entry_mute_cc.get_value()),
1192 'solo_cc': int(self.entry_solo_cc.get_value()),
1193 'value': self.minus_inf.get_active()
1197 class OutputChannelPropertiesDialog(ChannelPropertiesDialog):
1198 def create_ui(self):
1199 ChannelPropertiesDialog.create_ui(self)
1201 grid = self.properties_grid
1202 color_label = Gtk.Label.new_with_mnemonic('_Color')
1203 color_label.set_halign(Gtk.Align.START)
1204 grid.attach(color_label, 0, 3, 1, 1)
1205 self.color_chooser_button = Gtk.ColorButton()
1206 self.color_chooser_button.set_use_alpha(True)
1207 self.color_chooser_button.set_rgba(Gdk.RGBA(0, 0, 0, 0))
1208 color_label.set_mnemonic_widget(self.color_chooser_button)
1209 grid.attach(self.color_chooser_button, 1, 3, 2, 1)
1211 vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
1212 self.vbox.pack_start(self.create_frame('Input Channels', vbox), True, True, 0)
1214 self.display_solo_buttons = Gtk.CheckButton.new_with_mnemonic('_Display solo buttons')
1215 vbox.pack_start(self.display_solo_buttons, True, True, 0)
1217 self.vbox.show_all()
1219 def fill_ui(self):
1220 ChannelPropertiesDialog.fill_ui(self)
1221 self.display_solo_buttons.set_active(self.channel.display_solo_buttons)
1222 self.color_chooser_button.set_rgba(self.channel.color)
1224 def on_response_cb(self, dlg, response_id, *args):
1225 ChannelPropertiesDialog.on_response_cb(self, dlg, response_id, *args)
1226 if response_id == Gtk.ResponseType.APPLY:
1227 self.channel.display_solo_buttons = self.display_solo_buttons.get_active()
1228 self.channel.set_color(self.color_chooser_button.get_rgba())
1229 for inputchannel in self.app.channels:
1230 inputchannel.update_control_group(self.channel)
1233 class NewOutputChannelDialog(NewChannelDialog, OutputChannelPropertiesDialog):
1234 def __init__(self, app):
1235 Gtk.Dialog.__init__(self, 'New Output Channel', app.window)
1236 self.mixer = app.mixer
1237 self.app = app
1238 OutputChannelPropertiesDialog.create_ui(self)
1239 self.add_initial_value_radio()
1240 self.vbox.show_all()
1241 self.set_default_size(365, -1)
1243 # TODO: disable mode for output channels as mono output channels may
1244 # not be correctly handled yet.
1245 self.mono.set_sensitive(False)
1246 self.stereo.set_sensitive(False)
1248 self.add_button(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL)
1249 self.ok_button = self.add_button(Gtk.STOCK_ADD, Gtk.ResponseType.OK)
1250 self.ok_button.set_sensitive(False)
1251 self.set_default_response(Gtk.ResponseType.OK);
1253 def fill_ui(self, **values):
1254 self.entry_name.set_text(values.get('name', ''))
1255 # don't set MIDI CCs to previously used values, because they
1256 # would overwrite existing mappings, if accepted.
1257 self.entry_volume_cc.set_value(-1)
1258 self.entry_balance_cc.set_value(-1)
1259 self.entry_mute_cc.set_value(-1)
1260 self.stereo.set_active(values.get('stereo', True))
1261 self.minus_inf.set_active(values.get('value', False))
1262 self.color_chooser_button.set_rgba(values.get('color', Gdk.RGBA(0, 0, 0, 0)))
1263 self.display_solo_buttons.set_active(values.get('display_solo_buttons', False))
1264 self.entry_name.grab_focus()
1266 def get_result(self):
1267 return {
1268 'name': self.entry_name.get_text(),
1269 'stereo': self.stereo.get_active(),
1270 'volume_cc': int(self.entry_volume_cc.get_value()),
1271 'balance_cc': int(self.entry_balance_cc.get_value()),
1272 'mute_cc': int(self.entry_mute_cc.get_value()),
1273 'display_solo_buttons': self.display_solo_buttons.get_active(),
1274 'color': self.color_chooser_button.get_rgba(),
1275 'value': self.minus_inf.get_active()
1279 class ControlGroup(Gtk.Alignment):
1280 def __init__(self, output_channel, input_channel):
1281 GObject.GObject.__init__(self)
1282 self.set(0.5, 0.5, 1, 1)
1283 self.output_channel = output_channel
1284 self.input_channel = input_channel
1285 self.app = input_channel.app
1287 self.hbox = Gtk.HBox()
1288 self.vbox = Gtk.VBox()
1289 self.add(self.vbox)
1290 self.buttons_box = Gtk.Box(False, button_padding)
1292 set_background_color(self.vbox, output_channel.css_name, output_channel.color)
1294 self.vbox.pack_start(self.hbox, True, True, button_padding)
1295 css = b"""
1296 .control_group {
1297 min-width: 0px;
1298 padding: 0px;
1301 .control_group #label,
1302 .control_group #mute,
1303 .control_group #pre_fader,
1304 .control_group #solo {
1305 font-size: smaller;
1306 padding: 0px .1em;
1310 css_provider = Gtk.CssProvider()
1311 css_provider.load_from_data(css)
1312 context = Gtk.StyleContext()
1313 screen = Gdk.Screen.get_default()
1314 context.add_provider_for_screen(screen, css_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)
1315 hbox_context = self.hbox.get_style_context()
1316 hbox_context.add_class('control_group')
1318 name = output_channel.channel.name
1319 self.label = Gtk.Label(name)
1320 self.label.set_name("label")
1321 self.label.set_max_width_chars(self.input_channel.label_chars_narrow)
1322 self.label.set_ellipsize(Pango.EllipsizeMode.MIDDLE)
1323 if len(name) > self.input_channel.label_chars_narrow:
1324 self.label.set_tooltip_text(name)
1325 self.hbox.pack_start(self.label, False, False, button_padding)
1326 self.hbox.pack_end(self.buttons_box, False, False, button_padding)
1327 mute = Gtk.ToggleButton()
1328 mute.set_label("M")
1329 mute.set_name("mute")
1330 mute.set_tooltip_text("Mute output channel send")
1331 mute.connect("toggled", self.on_mute_toggled)
1332 self.mute = mute
1333 solo = Gtk.ToggleButton()
1334 solo.set_name("solo")
1335 solo.set_label("S")
1336 solo.set_tooltip_text("Solo output send")
1337 solo.connect("toggled", self.on_solo_toggled)
1338 self.solo = solo
1339 pre = Gtk.ToggleButton("P")
1340 pre.set_name("pre_fader")
1341 pre.set_tooltip_text("Pre (on) / Post (off) fader send")
1342 pre.connect("toggled", self.on_prefader_toggled)
1343 self.prefader = pre
1344 self.buttons_box.pack_start(pre, True, True, button_padding)
1345 self.buttons_box.pack_start(mute, True, True, button_padding)
1346 if self.output_channel.display_solo_buttons:
1347 self.buttons_box.pack_start(solo, True, True, button_padding)
1349 def update(self):
1350 if self.output_channel.display_solo_buttons:
1351 if not self.solo in self.buttons_box.get_children():
1352 self.buttons_box.pack_start(self.solo, True, True, button_padding)
1353 self.solo.show()
1354 else:
1355 if self.solo in self.buttons_box.get_children():
1356 self.buttons_box.remove(self.solo)
1358 name = self.output_channel.channel.name
1359 self.label.set_text(name)
1360 if len(name) > self.input_channel.label_chars_narrow:
1361 self.label.set_tooltip_text(name)
1363 set_background_color(self.vbox, self.output_channel.css_name, self.output_channel.color)
1365 def on_mute_toggled(self, button):
1366 self.output_channel.channel.set_muted(self.input_channel.channel, button.get_active())
1367 self.app.update_monitor(self)
1369 def on_solo_toggled(self, button):
1370 self.output_channel.channel.set_solo(self.input_channel.channel, button.get_active())
1371 self.app.update_monitor(self)
1373 def on_prefader_toggled(self, button):
1374 self.output_channel.channel.set_in_prefader(self.input_channel.channel, button.get_active())
1376 def narrow(self):
1377 self.hbox.remove(self.label)
1378 self.hbox.set_child_packing(self.buttons_box, True, True, button_padding, Gtk.PackType.END)
1380 def widen(self):
1381 self.hbox.pack_start(self.label, False, False, button_padding)
1382 self.hbox.set_child_packing(self.buttons_box, False, False, button_padding, Gtk.PackType.END)