Minor fix for flake8 complaint about comment whitespace
[jack_mixer.git] / channel.py
blobe66c51e542c159577031a75996e00db56c6b0223
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 # noqa: F401
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
30 from styling import set_background_color, random_color
33 log = logging.getLogger(__name__)
34 BUTTON_PADDING = 1
37 class Channel(Gtk.Box, SerializedObject):
38 """Widget with slider and meter used as base class for more specific
39 channel widgets"""
41 num_instances = 0
43 def __init__(self, app, name, stereo, value=None):
44 super().__init__(orientation=Gtk.Orientation.VERTICAL)
45 self.app = app
46 self.mixer = app.mixer
47 self.channel = None
48 self.gui_factory = app.gui_factory
49 self._channel_name = name
50 self.stereo = stereo
51 self.initial_value = value
52 self.meter_scale = self.gui_factory.get_default_meter_scale()
53 self.slider_scale = self.gui_factory.get_default_slider_scale()
54 self.slider_adjustment = slider.AdjustmentdBFS(self.slider_scale, 0.0, 0.02)
55 self.balance_adjustment = slider.BalanceAdjustment()
56 self.post_fader_output_channel = None
57 self.future_out_mute = None
58 self.future_volume_midi_cc = None
59 self.future_balance_midi_cc = None
60 self.future_mute_midi_cc = None
61 self.future_solo_midi_cc = None
62 self.css_name = "css_name_%d" % Channel.num_instances
63 self.label_name = None
64 self.wide = True
65 self.label_chars_wide = 12
66 self.label_chars_narrow = 7
67 self.channel_properties_dialog = None
68 self.monitor_button = None
69 Channel.num_instances += 1
71 # ---------------------------------------------------------------------------------------------
72 # Properties
74 @property
75 def channel_name(self):
76 return self._channel_name
78 @channel_name.setter
79 def channel_name(self, name):
80 self.app.on_channel_rename(self._channel_name, name)
81 self._channel_name = name
82 if self.label_name:
83 self.label_name.set_text(name)
84 if len(name) > (self.label_chars_wide if self.wide else self.label_chars_narrow):
85 self.label_name.set_tooltip_text(name)
86 if self.channel:
87 self.channel.name = name
88 if self.post_fader_output_channel:
89 self.post_fader_output_channel.name = "%s Out" % name
91 # ---------------------------------------------------------------------------------------------
92 # UI creation and (de-)initialization
94 def create_balance_widget(self):
95 parent = None
96 if self.balance:
97 parent = self.balance.get_parent()
98 self.balance.destroy()
100 self.balance = slider.BalanceSlider(self.balance_adjustment, (20, 20), (0, 100))
102 if parent:
103 parent.pack_end(self.balance, False, True, 0)
105 self.balance.show()
107 def create_buttons(self):
108 # Mute, Solo and Monitor buttons
109 self.hbox_mutesolo = Gtk.Box(False, 0, orientation=Gtk.Orientation.HORIZONTAL)
111 self.mute = Gtk.ToggleButton()
112 self.mute.set_label("M")
113 self.mute.get_style_context().add_class("mute")
114 self.mute.set_active(self.channel.out_mute)
115 self.mute.connect("toggled", self.on_mute_toggled)
116 self.hbox_mutesolo.pack_start(self.mute, True, True, 0)
118 self.pack_start(self.hbox_mutesolo, False, False, 0)
120 self.monitor_button = Gtk.ToggleButton("MON")
121 self.monitor_button.get_style_context().add_class("monitor")
122 self.monitor_button.connect("toggled", self.on_monitor_button_toggled)
123 self.pack_start(self.monitor_button, False, False, 0)
125 def create_fader(self):
126 # HBox for fader and meter
127 self.vbox_fader = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
128 self.vbox_fader.get_style_context().add_class("vbox_fader")
130 self.hbox_readouts = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
131 self.hbox_readouts.set_homogeneous(True)
132 self.hbox_readouts.pack_start(self.volume_digits, False, True, 0)
133 self.hbox_readouts.pack_start(self.abspeak, False, True, 0)
134 self.vbox_fader.pack_start(self.hbox_readouts, False, False, 0)
136 self.hbox_fader = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
137 self.hbox_fader.pack_start(self.slider, True, True, 0)
138 self.hbox_fader.pack_start(self.meter, True, True, 0)
139 self.vbox_fader.pack_start(self.hbox_fader, True, True, 0)
140 self.vbox_fader.pack_end(self.balance, False, True, 0)
142 self.pack_start(self.vbox_fader, True, True, 0)
144 def create_slider_widget(self):
145 parent = None
146 if self.slider:
147 parent = self.slider.get_parent()
148 self.slider.destroy()
150 if self.gui_factory.use_custom_widgets:
151 self.slider = slider.CustomSliderWidget(self.slider_adjustment)
152 else:
153 self.slider = slider.VolumeSlider(self.slider_adjustment)
155 if parent:
156 parent.pack_start(self.slider, True, True, 0)
157 parent.reorder_child(self.slider, 0)
159 self.slider.show()
161 def realize(self):
162 log.debug('Realizing channel "%s".', self.channel_name)
163 if self.future_out_mute is not None:
164 self.channel.out_mute = self.future_out_mute
166 # Widgets
167 # Channel strip label
168 self.vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
169 self.pack_start(self.vbox, False, True, 0)
170 self.label_name = Gtk.Label()
171 self.label_name.get_style_context().add_class("top_label")
172 self.label_name.set_text(self.channel_name)
173 self.label_name.set_max_width_chars(
174 self.label_chars_wide if self.wide else self.label_chars_narrow
176 self.label_name.set_ellipsize(Pango.EllipsizeMode.MIDDLE)
177 self.label_name_event_box = Gtk.EventBox()
178 self.label_name_event_box.connect("button-press-event", self.on_label_mouse)
179 self.label_name_event_box.add(self.label_name)
181 # Volume slider
182 self.slider = None
183 self.create_slider_widget()
184 # Balance slider
185 self.balance = None
186 self.create_balance_widget()
188 # Volume entry
189 self.volume_digits = Gtk.Entry()
190 self.volume_digits.set_has_frame(False)
191 self.volume_digits.set_width_chars(5)
192 self.volume_digits.set_property("xalign", 0.5)
193 self.volume_digits.connect("key-press-event", self.on_volume_digits_key_pressed)
194 self.volume_digits.connect("focus-out-event", self.on_volume_digits_focus_out)
195 self.volume_digits.get_style_context().add_class("readout")
197 # Peak level label
198 self.abspeak = abspeak.AbspeakWidget()
199 self.abspeak.connect("reset", self.on_abspeak_reset)
200 self.abspeak.connect("volume-adjust", self.on_abspeak_adjust)
201 self.abspeak.get_style_context().add_class("readout")
203 # Level meter
204 if self.stereo:
205 self.meter = meter.StereoMeterWidget(self.meter_scale)
206 else:
207 self.meter = meter.MonoMeterWidget(self.meter_scale)
209 self.meter.set_events(Gdk.EventMask.SCROLL_MASK)
210 self.on_vumeter_color_changed(self.gui_factory)
212 if self.initial_value is not None:
213 if self.initial_value is True:
214 self.slider_adjustment.set_value(0)
215 else:
216 self.slider_adjustment.set_value_db(0)
218 self.slider_adjustment.connect("volume-changed", self.on_volume_changed)
219 self.slider_adjustment.connect(
220 "volume-changed-from-midi", self.on_volume_changed_from_midi
222 self.balance_adjustment.connect("balance-changed", self.on_balance_changed)
224 self.gui_factory.connect(
225 "default-meter-scale-changed", self.on_default_meter_scale_changed
227 self.gui_factory.connect(
228 "default-slider-scale-changed", self.on_default_slider_scale_changed
230 self.gui_factory.connect("vumeter-color-changed", self.on_vumeter_color_changed)
231 self.gui_factory.connect("vumeter-color-scheme-changed", self.on_vumeter_color_changed)
232 self.gui_factory.connect("use-custom-widgets-changed", self.on_custom_widgets_changed)
234 self.connect("key-press-event", self.on_key_pressed)
235 self.connect("scroll-event", self.on_scroll)
237 entries = [Gtk.TargetEntry.new(self.__class__.__name__, Gtk.TargetFlags.SAME_APP, 0)]
238 self.label_name_event_box.drag_source_set(
239 Gdk.ModifierType.BUTTON1_MASK, entries, Gdk.DragAction.MOVE
241 self.label_name_event_box.connect("drag-data-get", self.on_drag_data_get)
242 self.drag_dest_set(Gtk.DestDefaults.ALL, entries, Gdk.DragAction.MOVE)
243 self.connect_after("drag-data-received", self.on_drag_data_received)
245 self.vbox.pack_start(self.label_name_event_box, True, True, 0)
247 def unrealize(self):
248 log.debug('Unrealizing channel "%s".', self.channel_name)
250 # ---------------------------------------------------------------------------------------------
251 # Signal/event handlers
253 def on_label_mouse(self, widget, event):
254 if event.type == Gdk.EventType._2BUTTON_PRESS:
255 if event.button == 1:
256 self.on_channel_properties()
257 return True
258 elif (
259 event.state & Gdk.ModifierType.CONTROL_MASK
260 and event.type == Gdk.EventType.BUTTON_PRESS
261 and event.button == 1
263 if self.wide:
264 self.narrow()
265 else:
266 self.widen()
267 return True
269 def on_channel_properties(self):
270 if not self.channel_properties_dialog:
271 self.channel_properties_dialog = self.properties_dialog_class(self.app, self)
272 self.channel_properties_dialog.fill_and_show()
274 def on_default_meter_scale_changed(self, gui_factory, scale):
275 log.debug("Default meter scale change detected.")
276 self.meter.set_scale(scale)
278 def on_default_slider_scale_changed(self, gui_factory, scale):
279 log.debug("Default slider scale change detected.")
280 self.slider_scale = scale
281 self.slider_adjustment.set_scale(scale)
282 if self.channel:
283 self.channel.midi_scale = self.slider_scale.scale
285 def on_vumeter_color_changed(self, gui_factory, *args):
286 color = gui_factory.get_vumeter_color()
287 color_scheme = gui_factory.get_vumeter_color_scheme()
288 if color_scheme != "solid":
289 self.meter.set_color(None)
290 else:
291 self.meter.set_color(Gdk.color_parse(color))
293 def on_custom_widgets_changed(self, gui_factory, value):
294 self.create_slider_widget()
295 # balance slider has no custom variant, no need to re-create it.
297 def on_abspeak_adjust(self, abspeak, adjust):
298 log.debug("abspeak adjust %f", adjust)
299 self.slider_adjustment.set_value_db(self.slider_adjustment.get_value_db() + adjust)
300 self.channel.abspeak = None
301 # We want to update gui even if actual decibels have not changed (scale wrap for example)
302 # self.update_volume(False)
304 def on_abspeak_reset(self, abspeak):
305 log.debug("abspeak reset")
306 self.channel.abspeak = None
308 def on_volume_digits_key_pressed(self, widget, event):
309 if event.keyval == Gdk.KEY_Return or event.keyval == Gdk.KEY_KP_Enter:
310 db_text = self.volume_digits.get_text()
311 try:
312 db = float(db_text)
313 log.debug('Volume digits confirmation "%f dBFS".', db)
314 except ValueError:
315 log.debug("Volume digits confirmation ignore, reset to current.")
316 self.update_volume(False)
317 return
318 self.slider_adjustment.set_value_db(db)
319 # self.grab_focus()
320 # We want to update gui even if actual decibels have not changed
321 # (scale wrap for example)
322 # self.update_volume(False)
324 def on_volume_digits_focus_out(self, widget, event):
325 log.debug("Volume digits focus out detected.")
326 self.update_volume(False)
328 def on_scroll(self, widget, event):
329 if event.direction == Gdk.ScrollDirection.DOWN:
330 self.slider_adjustment.step_down()
331 elif event.direction == Gdk.ScrollDirection.UP:
332 self.slider_adjustment.step_up()
333 return True
335 def on_volume_changed(self, adjustment):
336 self.update_volume(True)
338 def on_volume_changed_from_midi(self, adjustment):
339 self.update_volume(True, from_midi=True)
341 def on_balance_changed(self, adjustment):
342 balance = self.balance_adjustment.get_value()
343 log.debug("%s balance: %f", self.channel_name, balance)
344 self.channel.balance = balance
345 self.app.update_monitor(self)
347 def on_key_pressed(self, widget, event):
348 if event.keyval == Gdk.KEY_Up:
349 log.debug(self.channel_name + " Up")
350 self.slider_adjustment.step_up()
351 return True
352 elif event.keyval == Gdk.KEY_Down:
353 log.debug(self.channel_name + " Down")
354 self.slider_adjustment.step_down()
355 return True
357 return False
359 def on_drag_data_get(self, widget, drag_context, data, info, time):
360 channel = widget.get_parent().get_parent()
361 data.set(data.get_target(), 8, channel._channel_name.encode("utf-8"))
363 def on_drag_data_received(self, widget, drag_context, x, y, data, info, time):
364 pass
366 def on_midi_event_received(self, *args):
367 self.slider_adjustment.set_value_db(self.channel.volume, from_midi=True)
368 self.balance_adjustment.set_balance(self.channel.balance, from_midi=True)
370 def on_mute_toggled(self, button):
371 self.channel.out_mute = self.mute.get_active()
373 def on_monitor_button_toggled(self, button):
374 if button.get_active():
375 for channel in self.app.channels + self.app.output_channels:
376 if channel is not self and channel.monitor_button.get_active():
377 channel.monitor_button.handler_block_by_func(channel.on_monitor_button_toggled)
378 channel.monitor_button.set_active(False)
379 channel.monitor_button.handler_unblock_by_func(
380 channel.on_monitor_button_toggled
382 self.app.monitored_channel = self
383 else:
384 if self.app.monitored_channel is self:
385 self.app.monitored_channel = None
387 def assign_midi_ccs(self, volume_cc, balance_cc, mute_cc, solo_cc=None):
388 try:
389 if volume_cc != -1:
390 self.channel.volume_midi_cc = volume_cc
391 else:
392 volume_cc = self.channel.autoset_volume_midi_cc()
394 log.debug("Channel '%s' volume assigned to CC #%s.", self.channel.name, volume_cc)
395 except Exception as exc:
396 log.error("Channel '%s' volume CC assignment failed: %s", self.channel.name, exc)
398 try:
399 if balance_cc != -1:
400 self.channel.balance_midi_cc = balance_cc
401 else:
402 balance_cc = self.channel.autoset_balance_midi_cc()
404 log.debug("Channel '%s' balance assigned to CC #%s.", self.channel.name, balance_cc)
405 except Exception as exc:
406 log.error("Channel '%s' balance CC assignment failed: %s", self.channel.name, exc)
408 try:
409 if mute_cc != -1:
410 self.channel.mute_midi_cc = mute_cc
411 else:
412 mute_cc = self.channel.autoset_mute_midi_cc()
414 log.debug("Channel '%s' mute assigned to CC #%s.", self.channel.name, mute_cc)
415 except Exception as exc:
416 log.error("Channel '%s' mute CC assignment failed: %s", self.channel.name, exc)
418 if solo_cc is not None:
419 try:
420 if solo_cc != -1:
421 self.channel.solo_midi_cc = solo_cc
422 else:
423 solo_cc = self.channel.autoset_solo_midi_cc()
425 log.debug("Channel '%s' solo assigned to CC #%s.", self.channel.name, solo_cc)
426 except Exception as exc:
427 log.error("Channel '%s' solo CC assignment failed: %s", self.channel.name, exc)
429 # ---------------------------------------------------------------------------------------------
430 # Channel operations
432 def set_monitored(self):
433 if self.channel:
434 self.app.set_monitored_channel(self)
435 self.monitor_button.set_active(True)
437 def set_color(self, color):
438 self.color = color
439 set_background_color(self.label_name_event_box, self.css_name, self.color)
441 def widen(self, flag=True):
442 self.wide = flag
443 ctx = self.label_name.get_style_context()
445 if flag:
446 ctx.remove_class("narrow")
447 ctx.add_class("wide")
448 else:
449 ctx.remove_class("wide")
450 ctx.add_class("narrow")
452 label = self.label_name.get_label()
453 label_width = self.label_chars_wide if flag else self.label_chars_narrow
454 self.label_name.set_max_width_chars(label_width)
456 if len(label) > label_width:
457 self.label_name.set_tooltip_text(label)
459 self.meter.widen(flag)
460 self.hbox_readouts.set_orientation(
461 Gtk.Orientation.HORIZONTAL if flag else Gtk.Orientation.VERTICAL
464 def narrow(self):
465 self.widen(False)
467 def read_meter(self):
468 if not self.channel:
469 return
470 if self.stereo:
471 peak_left, peak_right, rms_left, rms_right = self.channel.kmeter
472 self.meter.set_values(peak_left, peak_right, rms_left, rms_right)
473 else:
474 peak, rms = self.channel.kmeter
475 self.meter.set_values(peak, rms)
477 self.abspeak.set_peak(self.channel.abspeak)
479 def update_volume(self, update_engine, from_midi=False):
480 db = self.slider_adjustment.get_value_db()
482 db_text = "%.2f" % db
483 self.volume_digits.set_text(db_text)
485 if update_engine:
486 if not from_midi:
487 self.channel.volume = db
488 self.app.update_monitor(self)
490 # ---------------------------------------------------------------------------------------------
491 # Channel (de-)serialization
493 def serialize(self, object_backend):
494 object_backend.add_property("volume", "%f" % self.slider_adjustment.get_value_db())
495 object_backend.add_property("balance", "%f" % self.balance_adjustment.get_value())
496 object_backend.add_property("wide", "%s" % str(self.wide))
498 if hasattr(self.channel, "out_mute"):
499 object_backend.add_property("out_mute", str(self.channel.out_mute))
500 if self.channel.volume_midi_cc != -1:
501 object_backend.add_property("volume_midi_cc", str(self.channel.volume_midi_cc))
502 if self.channel.balance_midi_cc != -1:
503 object_backend.add_property("balance_midi_cc", str(self.channel.balance_midi_cc))
504 if self.channel.mute_midi_cc != -1:
505 object_backend.add_property("mute_midi_cc", str(self.channel.mute_midi_cc))
506 if self.channel.solo_midi_cc != -1:
507 object_backend.add_property("solo_midi_cc", str(self.channel.solo_midi_cc))
509 def unserialize_property(self, name, value):
510 if name == "volume":
511 self.slider_adjustment.set_value_db(float(value))
512 return True
513 if name == "balance":
514 self.balance_adjustment.set_value(float(value))
515 return True
516 if name == "out_mute":
517 self.future_out_mute = value == "True"
518 return True
519 if name == "volume_midi_cc":
520 self.future_volume_midi_cc = int(value)
521 return True
522 if name == "balance_midi_cc":
523 self.future_balance_midi_cc = int(value)
524 return True
525 if name == "mute_midi_cc":
526 self.future_mute_midi_cc = int(value)
527 return True
528 if name == "solo_midi_cc":
529 self.future_solo_midi_cc = int(value)
530 return True
531 if name == "wide":
532 self.wide = value == "True"
533 return True
534 return False
537 class InputChannel(Channel):
538 def __init__(self, *args, **kwargs):
539 super().__init__(*args, **kwargs)
540 self.properties_dialog_class = ChannelPropertiesDialog
542 def create_buttons(self):
543 super().create_buttons()
544 self.solo = Gtk.ToggleButton()
545 self.solo.set_label("S")
546 self.solo.get_style_context().add_class("solo")
547 self.solo.set_active(self.channel.solo)
548 self.solo.connect("toggled", self.on_solo_toggled)
549 self.solo.connect("button-press-event", self.on_solo_button_pressed)
550 self.mute.connect("button-press-event", self.on_mute_button_pressed)
551 self.hbox_mutesolo.pack_start(self.solo, True, True, 0)
553 def realize(self):
554 self.channel = self.mixer.add_channel(self.channel_name, self.stereo)
556 if self.channel is None:
557 raise Exception("Cannot create a channel")
559 super().realize()
561 if self.future_volume_midi_cc is not None:
562 self.channel.volume_midi_cc = self.future_volume_midi_cc
563 if self.future_balance_midi_cc is not None:
564 self.channel.balance_midi_cc = self.future_balance_midi_cc
565 if self.future_mute_midi_cc is not None:
566 self.channel.mute_midi_cc = self.future_mute_midi_cc
567 if self.future_solo_midi_cc is not None:
568 self.channel.solo_midi_cc = self.future_solo_midi_cc
569 if self.app._init_solo_channels and self.channel_name in self.app._init_solo_channels:
570 self.channel.solo = True
572 self.channel.midi_scale = self.slider_scale.scale
574 self.on_volume_changed(self.slider_adjustment)
575 self.on_balance_changed(self.balance_adjustment)
577 self.create_fader()
578 self.create_buttons()
580 if not self.wide:
581 self.narrow()
583 def unrealize(self):
584 super().unrealize()
585 if self.post_fader_output_channel:
586 self.post_fader_output_channel.remove()
587 self.post_fader_output_channel = None
588 self.channel.remove()
589 self.channel = None
591 def narrow(self):
592 super().narrow()
593 for cg in self.get_control_groups():
594 cg.narrow()
596 def widen(self, flag=True):
597 super().widen(flag)
598 for cg in self.get_control_groups():
599 cg.widen()
601 def on_drag_data_received(self, widget, drag_context, x, y, data, info, time):
602 source_name = data.get_data().decode("utf-8")
603 if source_name == self._channel_name:
604 return
605 self.emit("input-channel-order-changed", source_name, self._channel_name)
607 def add_control_group(self, channel):
608 control_group = ControlGroup(channel, self)
609 control_group.show_all()
610 self.vbox.pack_start(control_group, True, True, 0)
611 return control_group
613 def remove_control_group(self, channel):
614 ctlgroup = self.get_control_group(channel)
615 self.vbox.remove(ctlgroup)
617 def update_control_group(self, channel):
618 for control_group in self.vbox.get_children():
619 if isinstance(control_group, ControlGroup):
620 if control_group.output_channel is channel:
621 control_group.update()
623 def get_control_group(self, channel):
624 for control_group in self.get_control_groups():
625 if control_group.output_channel is channel:
626 return control_group
627 return None
629 def get_control_groups(self):
630 ctlgroups = []
631 for c in self.vbox.get_children():
632 if isinstance(c, ControlGroup):
633 ctlgroups.append(c)
634 return ctlgroups
636 def midi_events_check(self):
637 if self.channel is not None and self.channel.midi_in_got_events:
638 self.mute.set_active(self.channel.out_mute)
639 self.solo.set_active(self.channel.solo)
640 super().on_midi_event_received()
642 def on_mute_button_pressed(self, button, event, *args):
643 if event.button == 3:
644 # right click on the mute button, act on all output channels
645 if button.get_active(): # was muted
646 button.set_active(False)
647 if hasattr(button, 'touched_channels'):
648 touched_channels = button.touched_channels
649 for chan in touched_channels:
650 ctlgroup = self.get_control_group(chan)
651 ctlgroup.mute.set_active(False)
652 del button.touched_channels
653 else: # was not muted
654 button.set_active(True)
655 touched_channels = []
656 for chan in self.app.output_channels:
657 ctlgroup = self.get_control_group(chan)
658 if not ctlgroup.mute.get_active():
659 ctlgroup.mute.set_active(True)
660 touched_channels.append(chan)
661 button.touched_channels = touched_channels
662 return True
663 return False
665 def on_solo_toggled(self, button):
666 self.channel.solo = self.solo.get_active()
668 def on_solo_button_pressed(self, button, event, *args):
669 if event.button == 3:
670 # right click on the solo button, act on all output channels
671 if button.get_active(): # was soloed
672 button.set_active(False)
673 if hasattr(button, "touched_channels"):
674 touched_channels = button.touched_channels
675 for chan in touched_channels:
676 ctlgroup = self.get_control_group(chan)
677 ctlgroup.solo.set_active(False)
678 del button.touched_channels
679 else: # was not soloed
680 button.set_active(True)
681 touched_channels = []
682 for chan in self.app.output_channels:
683 ctlgroup = self.get_control_group(chan)
684 if not ctlgroup.solo.get_active():
685 ctlgroup.solo.set_active(True)
686 touched_channels.append(chan)
687 button.touched_channels = touched_channels
688 return True
689 return False
691 @classmethod
692 def serialization_name(cls):
693 return "input_channel"
695 def serialize(self, object_backend):
696 object_backend.add_property("name", self.channel_name)
697 if self.stereo:
698 object_backend.add_property("type", "stereo")
699 else:
700 object_backend.add_property("type", "mono")
701 super().serialize(object_backend)
703 def unserialize_property(self, name, value):
704 if name == "name":
705 self.channel_name = str(value)
706 return True
707 if name == "type":
708 if value == "stereo":
709 self.stereo = True
710 return True
711 if value == "mono":
712 self.stereo = False
713 return True
714 return super().unserialize_property(name, value)
717 class OutputChannel(Channel):
718 def __init__(self, *args, **kwargs):
719 super().__init__(*args, **kwargs)
720 self.properties_dialog_class = OutputChannelPropertiesDialog
721 self._display_solo_buttons = False
722 self._init_muted_channels = None
723 self._init_solo_channels = None
724 self._init_prefader_channels = None
726 @property
727 def display_solo_buttons(self):
728 return self._display_solo_buttons
730 @display_solo_buttons.setter
731 def display_solo_buttons(self, value):
732 self._display_solo_buttons = value
733 # notifying control groups
734 for inputchannel in self.app.channels:
735 inputchannel.update_control_group(self)
737 def realize(self):
738 self.channel = self.mixer.add_output_channel(self.channel_name, self.stereo)
740 if self.channel is None:
741 raise Exception("Cannot create a channel")
743 super().realize()
745 if self.future_volume_midi_cc is not None:
746 self.channel.volume_midi_cc = self.future_volume_midi_cc
747 if self.future_balance_midi_cc is not None:
748 self.channel.balance_midi_cc = self.future_balance_midi_cc
749 if self.future_mute_midi_cc is not None:
750 self.channel.mute_midi_cc = self.future_mute_midi_cc
751 self.channel.midi_scale = self.slider_scale.scale
753 self.on_volume_changed(self.slider_adjustment)
754 self.on_balance_changed(self.balance_adjustment)
756 set_background_color(self.label_name_event_box, self.css_name, self.color)
758 self.create_fader()
759 self.create_buttons()
761 # add control groups to the input channels, and initialize them
762 # appropriately
763 for input_channel in self.app.channels:
764 ctlgroup = input_channel.add_control_group(self)
765 name = input_channel.channel.name
766 if self._init_muted_channels and name in self._init_muted_channels:
767 ctlgroup.mute.set_active(True)
768 if self._init_solo_channels and name in self._init_solo_channels:
769 ctlgroup.solo.set_active(True)
770 if self._init_prefader_channels and name in self._init_prefader_channels:
771 ctlgroup.prefader.set_active(True)
772 if not input_channel.wide:
773 ctlgroup.narrow()
775 self._init_muted_channels = None
776 self._init_solo_channels = None
777 self._init_prefader_channels = None
779 if not self.wide:
780 self.narrow()
782 def unrealize(self):
783 # remove control groups from input channels
784 for input_channel in self.app.channels:
785 input_channel.remove_control_group(self)
786 # then remove itself
787 super().unrealize()
788 self.channel.remove()
789 self.channel = None
791 def on_drag_data_received(self, widget, drag_context, x, y, data, info, time):
792 source_name = data.get_data().decode("utf-8")
793 if source_name == self._channel_name:
794 return
795 self.emit("output-channel-order-changed", source_name, self._channel_name)
797 def midi_events_check(self):
798 if self.channel is not None and self.channel.midi_in_got_events:
799 self.mute.set_active(self.channel.out_mute)
800 super().on_midi_event_received()
802 @classmethod
803 def serialization_name(cls):
804 return "output_channel"
806 def serialize(self, object_backend):
807 object_backend.add_property("name", self.channel_name)
808 if self.stereo:
809 object_backend.add_property("type", "stereo")
810 else:
811 object_backend.add_property("type", "mono")
812 if self.display_solo_buttons:
813 object_backend.add_property("solo_buttons", "true")
814 muted_channels = []
815 solo_channels = []
816 prefader_in_channels = []
817 for input_channel in self.app.channels:
818 if self.channel.is_muted(input_channel.channel):
819 muted_channels.append(input_channel)
820 if self.channel.is_solo(input_channel.channel):
821 solo_channels.append(input_channel)
822 if self.channel.is_in_prefader(input_channel.channel):
823 prefader_in_channels.append(input_channel)
824 if muted_channels:
825 object_backend.add_property(
826 "muted_channels", "|".join([x.channel.name for x in muted_channels])
828 if solo_channels:
829 object_backend.add_property(
830 "solo_channels", "|".join([x.channel.name for x in solo_channels])
832 if prefader_in_channels:
833 object_backend.add_property(
834 "prefader_channels", "|".join([x.channel.name for x in prefader_in_channels])
836 object_backend.add_property("color", self.color.to_string())
837 super().serialize(object_backend)
839 def unserialize_property(self, name, value):
840 if name == "name":
841 self.channel_name = str(value)
842 return True
843 if name == "type":
844 if value == "stereo":
845 self.stereo = True
846 return True
847 if value == "mono":
848 self.stereo = False
849 return True
850 if name == "solo_buttons":
851 if value == "true":
852 self.display_solo_buttons = True
853 return True
854 if name == "muted_channels":
855 self._init_muted_channels = value.split("|")
856 return True
857 if name == "solo_channels":
858 self._init_solo_channels = value.split("|")
859 return True
860 if name == "prefader_channels":
861 self._init_prefader_channels = value.split("|")
862 return True
863 if name == "color":
864 c = Gdk.RGBA()
865 c.parse(value)
866 self.color = c
867 return True
868 return super().unserialize_property(name, value)
871 class ChannelPropertiesDialog(Gtk.Dialog):
872 def __init__(self, app, channel=None, title=None):
873 if not title:
874 if not channel:
875 raise ValueError("Either 'title' or 'channel' must be passed.")
876 title = 'Channel "%s" Properties' % channel.channel_name
878 super().__init__(title, app.window)
879 self.channel = channel
880 self.app = app
881 self.mixer = app.mixer
882 self.set_default_size(365, -1)
884 self.create_ui()
886 def fill_and_show(self):
887 self.fill_ui()
888 self.present()
890 def add_buttons(self):
891 self.add_button(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL)
892 self.ok_button = self.add_button(Gtk.STOCK_APPLY, Gtk.ResponseType.APPLY)
893 self.set_default_response(Gtk.ResponseType.APPLY)
895 self.connect("response", self.on_response_cb)
896 self.connect("delete-event", self.on_response_cb)
898 def create_frame(self, label, child, padding=8):
899 # need to pass an empty label, otherwise no label widget is created
900 frame = Gtk.Frame(label="")
901 frame.get_label_widget().set_markup("<b>%s</b>" % label)
902 frame.set_border_width(3)
903 frame.set_shadow_type(Gtk.ShadowType.NONE)
905 alignment = Gtk.Alignment.new(0.5, 0, 1, 1)
906 alignment.set_padding(padding, padding, padding, padding)
907 frame.add(alignment)
908 alignment.add(child)
910 return frame
912 def create_ui(self):
913 self.add_buttons()
914 vbox = self.get_content_area()
916 self.properties_grid = grid = Gtk.Grid()
917 vbox.pack_start(self.create_frame("Properties", grid), True, True, 0)
918 grid.set_row_spacing(8)
919 grid.set_column_spacing(8)
920 grid.set_column_homogeneous(True)
922 name_label = Gtk.Label.new_with_mnemonic("_Name")
923 name_label.set_halign(Gtk.Align.START)
924 grid.attach(name_label, 0, 0, 1, 1)
925 self.entry_name = Gtk.Entry()
926 self.entry_name.set_activates_default(True)
927 self.entry_name.connect("changed", self.on_entry_name_changed)
928 name_label.set_mnemonic_widget(self.entry_name)
929 grid.attach(self.entry_name, 1, 0, 2, 1)
931 grid.attach(Gtk.Label(label="Mode", halign=Gtk.Align.START), 0, 1, 1, 1)
932 self.mono = Gtk.RadioButton.new_with_mnemonic(None, "_Mono")
933 self.stereo = Gtk.RadioButton.new_with_mnemonic_from_widget(self.mono, "_Stereo")
934 grid.attach(self.mono, 1, 1, 1, 1)
935 grid.attach(self.stereo, 2, 1, 1, 1)
937 grid = Gtk.Grid()
938 vbox.pack_start(self.create_frame("MIDI Control Changes", grid), True, True, 0)
939 grid.set_row_spacing(8)
940 grid.set_column_spacing(8)
941 grid.set_column_homogeneous(True)
943 cc_tooltip = "{} MIDI Control Change number (0-127, set to -1 to assign next free CC #)"
944 volume_label = Gtk.Label.new_with_mnemonic("_Volume")
945 volume_label.set_halign(Gtk.Align.START)
946 grid.attach(volume_label, 0, 0, 1, 1)
947 self.entry_volume_cc = Gtk.SpinButton.new_with_range(-1, 127, 1)
948 self.entry_volume_cc.set_tooltip_text(cc_tooltip.format("Volume"))
949 volume_label.set_mnemonic_widget(self.entry_volume_cc)
950 grid.attach(self.entry_volume_cc, 1, 0, 1, 1)
951 self.button_sense_midi_volume = Gtk.Button("Learn")
952 self.button_sense_midi_volume.connect("clicked", self.on_sense_midi_volume_clicked)
953 grid.attach(self.button_sense_midi_volume, 2, 0, 1, 1)
955 balance_label = Gtk.Label.new_with_mnemonic("_Balance")
956 balance_label.set_halign(Gtk.Align.START)
957 grid.attach(balance_label, 0, 1, 1, 1)
958 self.entry_balance_cc = Gtk.SpinButton.new_with_range(-1, 127, 1)
959 self.entry_balance_cc.set_tooltip_text(cc_tooltip.format("Balance"))
960 balance_label.set_mnemonic_widget(self.entry_balance_cc)
961 grid.attach(self.entry_balance_cc, 1, 1, 1, 1)
962 self.button_sense_midi_balance = Gtk.Button("Learn")
963 self.button_sense_midi_balance.connect("clicked", self.on_sense_midi_balance_clicked)
964 grid.attach(self.button_sense_midi_balance, 2, 1, 1, 1)
966 mute_label = Gtk.Label.new_with_mnemonic("M_ute")
967 mute_label.set_halign(Gtk.Align.START)
968 grid.attach(mute_label, 0, 2, 1, 1)
969 self.entry_mute_cc = Gtk.SpinButton.new_with_range(-1, 127, 1)
970 self.entry_mute_cc.set_tooltip_text(cc_tooltip.format("Mute"))
971 mute_label.set_mnemonic_widget(self.entry_mute_cc)
972 grid.attach(self.entry_mute_cc, 1, 2, 1, 1)
973 self.button_sense_midi_mute = Gtk.Button("Learn")
974 self.button_sense_midi_mute.connect("clicked", self.on_sense_midi_mute_clicked)
975 grid.attach(self.button_sense_midi_mute, 2, 2, 1, 1)
977 if isinstance(self, NewChannelDialog) or (
978 self.channel and isinstance(self.channel, InputChannel)
980 solo_label = Gtk.Label.new_with_mnemonic("S_olo")
981 solo_label.set_halign(Gtk.Align.START)
982 grid.attach(solo_label, 0, 3, 1, 1)
983 self.entry_solo_cc = Gtk.SpinButton.new_with_range(-1, 127, 1)
984 self.entry_solo_cc.set_tooltip_text(cc_tooltip.format("Solo"))
985 solo_label.set_mnemonic_widget(self.entry_solo_cc)
986 grid.attach(self.entry_solo_cc, 1, 3, 1, 1)
987 self.button_sense_midi_solo = Gtk.Button("Learn")
988 self.button_sense_midi_solo.connect("clicked", self.on_sense_midi_solo_clicked)
989 grid.attach(self.button_sense_midi_solo, 2, 3, 1, 1)
991 self.vbox.show_all()
993 def fill_ui(self):
994 self.entry_name.set_text(self.channel.channel_name)
995 if self.channel.channel.is_stereo:
996 self.stereo.set_active(True)
997 else:
998 self.mono.set_active(True)
999 self.mono.set_sensitive(False)
1000 self.stereo.set_sensitive(False)
1001 self.entry_volume_cc.set_value(self.channel.channel.volume_midi_cc)
1002 self.entry_balance_cc.set_value(self.channel.channel.balance_midi_cc)
1003 self.entry_mute_cc.set_value(self.channel.channel.mute_midi_cc)
1004 if self.channel and isinstance(self.channel, InputChannel):
1005 self.entry_solo_cc.set_value(self.channel.channel.solo_midi_cc)
1007 def sense_popup_dialog(self, entry):
1008 window = Gtk.Window.new(Gtk.WindowType.TOPLEVEL)
1009 window.set_destroy_with_parent(True)
1010 window.set_transient_for(self)
1011 window.set_decorated(False)
1012 window.set_modal(True)
1013 window.set_position(Gtk.WindowPosition.CENTER_ON_PARENT)
1014 window.set_border_width(10)
1016 vbox = Gtk.Box(10, orientation=Gtk.Orientation.VERTICAL)
1017 window.add(vbox)
1018 window.timeout = 5
1019 label = Gtk.Label(label="Please move the MIDI control you want to use for this function.")
1020 vbox.pack_start(label, True, True, 0)
1021 timeout_label = Gtk.Label(label="This window will close in 5 seconds")
1022 vbox.pack_start(timeout_label, True, True, 0)
1024 def close_sense_timeout(window, entry):
1025 window.timeout -= 1
1026 timeout_label.set_text("This window will close in %d seconds." % window.timeout)
1027 if window.timeout == 0:
1028 window.destroy()
1029 entry.set_value(self.mixer.last_midi_cc)
1030 return False
1031 return True
1033 window.show_all()
1034 GObject.timeout_add_seconds(1, close_sense_timeout, window, entry)
1036 def on_sense_midi_volume_clicked(self, *args):
1037 self.mixer.last_midi_cc = int(self.entry_volume_cc.get_value())
1038 self.sense_popup_dialog(self.entry_volume_cc)
1040 def on_sense_midi_balance_clicked(self, *args):
1041 self.mixer.last_midi_cc = int(self.entry_balance_cc.get_value())
1042 self.sense_popup_dialog(self.entry_balance_cc)
1044 def on_sense_midi_mute_clicked(self, *args):
1045 self.mixer.last_midi_cc = int(self.entry_mute_cc.get_value())
1046 self.sense_popup_dialog(self.entry_mute_cc)
1048 def on_sense_midi_solo_clicked(self, *args):
1049 self.mixer.last_midi_cc = int(self.entry_solo_cc.get_value())
1050 self.sense_popup_dialog(self.entry_solo_cc)
1052 def on_response_cb(self, dlg, response_id, *args):
1053 name = self.entry_name.get_text()
1054 if response_id == Gtk.ResponseType.APPLY:
1055 if name != self.channel.channel_name:
1056 self.channel.channel_name = name
1057 for control in ("volume", "balance", "mute", "solo"):
1058 widget = getattr(self, "entry_{}_cc".format(control), None)
1059 if widget is not None:
1060 value = int(widget.get_value())
1061 if value != -1:
1062 setattr(self.channel.channel, "{}_midi_cc".format(control), value)
1064 self.hide()
1065 return True
1067 def on_entry_name_changed(self, entry):
1068 sensitive = False
1069 if len(entry.get_text()):
1070 if self.channel and self.channel.channel.name == entry.get_text():
1071 sensitive = True
1072 elif entry.get_text() not in [x.channel.name for x in self.app.channels] + [
1073 x.channel.name for x in self.app.output_channels
1074 ] + ["MAIN"]:
1075 sensitive = True
1076 self.ok_button.set_sensitive(sensitive)
1079 class NewChannelDialog(ChannelPropertiesDialog):
1080 def create_ui(self):
1081 super().create_ui()
1082 self.add_initial_value_radio()
1083 self.vbox.show_all()
1085 def add_buttons(self):
1086 self.add_button(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL)
1087 self.ok_button = self.add_button(Gtk.STOCK_ADD, Gtk.ResponseType.OK)
1088 self.ok_button.set_sensitive(False)
1089 self.set_default_response(Gtk.ResponseType.OK)
1091 def add_initial_value_radio(self):
1092 grid = self.properties_grid
1093 grid.attach(Gtk.Label(label="Value", halign=Gtk.Align.START), 0, 2, 1, 1)
1094 self.minus_inf = Gtk.RadioButton.new_with_mnemonic(None, "-_Inf")
1095 self.zero_dB = Gtk.RadioButton.new_with_mnemonic_from_widget(self.minus_inf, "_0dB")
1096 grid.attach(self.minus_inf, 1, 2, 1, 1)
1097 grid.attach(self.zero_dB, 2, 2, 1, 1)
1100 class NewInputChannelDialog(NewChannelDialog):
1101 def __init__(self, app, title="New Input Channel"):
1102 super().__init__(app, title=title)
1104 def fill_ui(self, **values):
1105 self.entry_name.set_text(values.get("name", ""))
1106 # don't set MIDI CCs to previously used values, because they
1107 # would overwrite existing mappings, if accepted.
1108 self.entry_volume_cc.set_value(-1)
1109 self.entry_balance_cc.set_value(-1)
1110 self.entry_mute_cc.set_value(-1)
1111 self.entry_solo_cc.set_value(-1)
1112 self.stereo.set_active(values.get("stereo", True))
1113 self.minus_inf.set_active(values.get("value", False))
1114 self.entry_name.grab_focus()
1116 def get_result(self):
1117 return {
1118 "name": self.entry_name.get_text(),
1119 "stereo": self.stereo.get_active(),
1120 "volume_cc": int(self.entry_volume_cc.get_value()),
1121 "balance_cc": int(self.entry_balance_cc.get_value()),
1122 "mute_cc": int(self.entry_mute_cc.get_value()),
1123 "solo_cc": int(self.entry_solo_cc.get_value()),
1124 "value": self.minus_inf.get_active(),
1128 class OutputChannelPropertiesDialog(ChannelPropertiesDialog):
1129 def create_ui(self):
1130 super().create_ui()
1132 grid = self.properties_grid
1133 color_label = Gtk.Label.new_with_mnemonic("_Color")
1134 color_label.set_halign(Gtk.Align.START)
1135 grid.attach(color_label, 0, 3, 1, 1)
1136 self.color_chooser_button = Gtk.ColorButton()
1137 self.color_chooser_button.set_use_alpha(True)
1138 color_label.set_mnemonic_widget(self.color_chooser_button)
1139 grid.attach(self.color_chooser_button, 1, 3, 2, 1)
1141 vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
1142 self.vbox.pack_start(self.create_frame("Input Channels", vbox), True, True, 0)
1144 self.display_solo_buttons = Gtk.CheckButton.new_with_mnemonic("_Display solo buttons")
1145 vbox.pack_start(self.display_solo_buttons, True, True, 0)
1147 self.vbox.show_all()
1149 def fill_ui(self):
1150 super().fill_ui()
1151 self.display_solo_buttons.set_active(self.channel.display_solo_buttons)
1152 self.color_chooser_button.set_rgba(self.channel.color)
1154 def on_response_cb(self, dlg, response_id, *args):
1155 if response_id == Gtk.ResponseType.APPLY:
1156 self.channel.display_solo_buttons = self.display_solo_buttons.get_active()
1157 self.channel.set_color(self.color_chooser_button.get_rgba())
1158 for inputchannel in self.app.channels:
1159 inputchannel.update_control_group(self.channel)
1161 return super().on_response_cb(dlg, response_id, *args)
1164 class NewOutputChannelDialog(NewChannelDialog, OutputChannelPropertiesDialog):
1165 def __init__(self, app, title="New Output Channel"):
1166 super().__init__(app, title=title)
1168 def fill_ui(self, **values):
1169 self.entry_name.set_text(values.get("name", ""))
1171 # TODO: disable mode for output channels as mono output channels may
1172 # not be correctly handled yet.
1173 self.mono.set_sensitive(False)
1174 self.stereo.set_sensitive(False)
1176 # don't set MIDI CCs to previously used values, because they
1177 # would overwrite existing mappings, if accepted.
1178 self.entry_volume_cc.set_value(-1)
1179 self.entry_balance_cc.set_value(-1)
1180 self.entry_mute_cc.set_value(-1)
1181 self.stereo.set_active(values.get("stereo", True))
1182 self.minus_inf.set_active(values.get("value", False))
1183 # choose a new random color for each new output channel
1184 self.color_chooser_button.set_rgba(random_color())
1185 self.display_solo_buttons.set_active(values.get("display_solo_buttons", False))
1186 self.entry_name.grab_focus()
1188 def get_result(self):
1189 return {
1190 "name": self.entry_name.get_text(),
1191 "stereo": self.stereo.get_active(),
1192 "volume_cc": int(self.entry_volume_cc.get_value()),
1193 "balance_cc": int(self.entry_balance_cc.get_value()),
1194 "mute_cc": int(self.entry_mute_cc.get_value()),
1195 "display_solo_buttons": self.display_solo_buttons.get_active(),
1196 "color": self.color_chooser_button.get_rgba(),
1197 "value": self.minus_inf.get_active(),
1201 class ControlGroup(Gtk.Alignment):
1202 def __init__(self, output_channel, input_channel):
1203 super().__init__()
1204 self.set(0.5, 0.5, 1, 1)
1205 self.output_channel = output_channel
1206 self.input_channel = input_channel
1207 self.app = input_channel.app
1209 self.hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
1210 self.vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
1211 self.add(self.vbox)
1212 self.buttons_box = Gtk.Box(False, BUTTON_PADDING, orientation=Gtk.Orientation.HORIZONTAL)
1214 set_background_color(self.vbox, output_channel.css_name, output_channel.color)
1216 self.vbox.pack_start(self.hbox, True, True, BUTTON_PADDING)
1217 hbox_context = self.hbox.get_style_context()
1218 hbox_context.add_class("control_group")
1220 name = output_channel.channel.name
1221 self.label = Gtk.Label(name)
1222 self.label.get_style_context().add_class("label")
1223 self.label.set_max_width_chars(self.input_channel.label_chars_narrow)
1224 self.label.set_ellipsize(Pango.EllipsizeMode.MIDDLE)
1225 if len(name) > self.input_channel.label_chars_narrow:
1226 self.label.set_tooltip_text(name)
1227 self.hbox.pack_start(self.label, False, False, BUTTON_PADDING)
1228 self.hbox.pack_end(self.buttons_box, False, False, BUTTON_PADDING)
1229 mute = Gtk.ToggleButton("M")
1230 mute.get_style_context().add_class("mute")
1231 mute.set_tooltip_text("Mute output channel send")
1232 mute.connect("toggled", self.on_mute_toggled)
1233 self.mute = mute
1234 solo = Gtk.ToggleButton("S")
1235 solo.get_style_context().add_class("solo")
1236 solo.set_tooltip_text("Solo output send")
1237 solo.connect("toggled", self.on_solo_toggled)
1238 self.solo = solo
1239 pre = Gtk.ToggleButton("P")
1240 pre.get_style_context().add_class("prefader")
1241 pre.set_tooltip_text("Pre (on) / Post (off) fader send")
1242 pre.connect("toggled", self.on_prefader_toggled)
1243 self.prefader = pre
1244 self.buttons_box.pack_start(pre, True, True, BUTTON_PADDING)
1245 self.buttons_box.pack_start(mute, True, True, BUTTON_PADDING)
1246 if self.output_channel.display_solo_buttons:
1247 self.buttons_box.pack_start(solo, True, True, BUTTON_PADDING)
1249 def update(self):
1250 if self.output_channel.display_solo_buttons:
1251 if self.solo not in self.buttons_box.get_children():
1252 self.buttons_box.pack_start(self.solo, True, True, BUTTON_PADDING)
1253 self.solo.show()
1254 else:
1255 if self.solo in self.buttons_box.get_children():
1256 self.buttons_box.remove(self.solo)
1258 name = self.output_channel.channel.name
1259 self.label.set_text(name)
1260 if len(name) > self.input_channel.label_chars_narrow:
1261 self.label.set_tooltip_text(name)
1263 set_background_color(self.vbox, self.output_channel.css_name, self.output_channel.color)
1265 def on_mute_toggled(self, button):
1266 self.output_channel.channel.set_muted(self.input_channel.channel, button.get_active())
1267 self.app.update_monitor(self.output_channel)
1269 def on_solo_toggled(self, button):
1270 self.output_channel.channel.set_solo(self.input_channel.channel, button.get_active())
1271 self.app.update_monitor(self.output_channel)
1273 def on_prefader_toggled(self, button):
1274 self.output_channel.channel.set_in_prefader(
1275 self.input_channel.channel, button.get_active()
1278 def narrow(self):
1279 self.hbox.remove(self.label)
1280 self.hbox.set_child_packing(self.buttons_box, True, True, BUTTON_PADDING, Gtk.PackType.END)
1282 def widen(self):
1283 self.hbox.pack_start(self.label, False, False, BUTTON_PADDING)
1284 self.hbox.set_child_packing(
1285 self.buttons_box, False, False, BUTTON_PADDING, Gtk.PackType.END
1289 GObject.signal_new(
1290 "input-channel-order-changed",
1291 InputChannel,
1292 GObject.SignalFlags.RUN_FIRST | GObject.SignalFlags.ACTION,
1293 None,
1294 [GObject.TYPE_STRING, GObject.TYPE_STRING],
1297 GObject.signal_new(
1298 "output-channel-order-changed",
1299 OutputChannel,
1300 GObject.SignalFlags.RUN_FIRST | GObject.SignalFlags.ACTION,
1301 None,
1302 [GObject.TYPE_STRING, GObject.TYPE_STRING],