]> git.0d.be Git - jack_mixer.git/blob - channel.py
Fix monitor channel clicking off if clicked when on
[jack_mixer.git] / channel.py
1 # This file is part of jack_mixer
2 #
3 # Copyright (C) 2006 Nedko Arnaudov <nedko@arnaudov.name>
4 #
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
8 #
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.
13 #
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.
17
18 import logging
19
20 import gi
21 from gi.repository import Gtk
22 from gi.repository import Gdk
23 from gi.repository import GObject
24
25 import abspeak
26 import meter
27 import slider
28 from serialization import SerializedObject
29
30 try:
31     import phat
32 except:
33     phat = None
34
35
36 log = logging.getLogger(__name__)
37 button_padding = 1
38 css = b"""
39 .top_label {min-width: 100px;}
40 button {padding: 0px}
41 """
42 css_provider = Gtk.CssProvider()
43 css_provider.load_from_data(css)
44 context = Gtk.StyleContext()
45 screen = Gdk.Screen.get_default()
46 context.add_provider_for_screen(screen, css_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)
47
48
49 def set_background_color(widget, name, color_string):
50     css = """
51     .%s {
52         background-color: %s
53     }
54 """ % (name, color_string)
55
56     css_provider = Gtk.CssProvider()
57     css_provider.load_from_data(css.encode('utf-8'))
58     context = Gtk.StyleContext()
59     screen = Gdk.Screen.get_default()
60     context.add_provider_for_screen(screen, css_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)
61
62     widget_context = widget.get_style_context()
63     widget_context.add_class(name)
64
65
66 def random_color():
67     from random import uniform, seed
68     seed()
69     return Gdk.RGBA(uniform(0, 1), uniform(0, 1), uniform(0, 1), 1)
70
71
72 class Channel(Gtk.VBox, SerializedObject):
73     '''Widget with slider and meter used as base class for more specific
74        channel widgets'''
75     monitor_button = None
76     num_instances = 0
77     def __init__(self, app, name, stereo, value = None):
78         Gtk.VBox.__init__(self)
79         self.app = app
80         self.mixer = app.mixer
81         self.gui_factory = app.gui_factory
82         self._channel_name = name
83         self.stereo = stereo
84         self.initial_value = value
85         self.meter_scale = self.gui_factory.get_default_meter_scale()
86         self.slider_scale = self.gui_factory.get_default_slider_scale()
87         self.slider_adjustment = slider.AdjustmentdBFS(self.slider_scale, 0.0, 0.02)
88         self.balance_adjustment = slider.BalanceAdjustment()
89         self.future_out_mute = None
90         self.future_volume_midi_cc = None
91         self.future_balance_midi_cc = None
92         self.future_mute_midi_cc = None
93         self.future_solo_midi_cc = None
94         self.css_name = "css_name_%d" % Channel.num_instances
95         Channel.num_instances += 1
96
97     def get_channel_name(self):
98         return self._channel_name
99
100     label_name = None
101     channel = None
102     post_fader_output_channel = None
103     def set_channel_name(self, name):
104         self.app.on_channel_rename(self._channel_name, name);
105         self._channel_name = name
106         if self.label_name:
107             self.label_name.set_text(name)
108         if self.channel:
109             self.channel.name = name
110         if self.post_fader_output_channel:
111             self.post_fader_output_channel.name = "%s Out" % name;
112     channel_name = property(get_channel_name, set_channel_name)
113
114     def realize(self):
115         log.debug('Realizing channel "%s".', self.channel_name)
116         if self.future_out_mute != None:
117             self.channel.out_mute = self.future_out_mute
118
119         self.slider_adjustment.connect("volume-changed", self.on_volume_changed)
120         self.slider_adjustment.connect("volume-changed-from-midi", self.on_volume_changed_from_midi)
121         self.balance_adjustment.connect("balance-changed", self.on_balance_changed)
122
123         self.slider = None
124         self.create_slider_widget()
125
126         if self.stereo:
127             self.meter = meter.StereoMeterWidget(self.meter_scale)
128         else:
129             self.meter = meter.MonoMeterWidget(self.meter_scale)
130         self.on_vumeter_color_changed(self.gui_factory)
131
132         self.meter.set_events(Gdk.EventMask.SCROLL_MASK)
133
134         self.gui_factory.connect("default-meter-scale-changed", self.on_default_meter_scale_changed)
135         self.gui_factory.connect("default-slider-scale-changed", self.on_default_slider_scale_changed)
136         self.gui_factory.connect('vumeter-color-changed', self.on_vumeter_color_changed)
137         self.gui_factory.connect('vumeter-color-scheme-changed', self.on_vumeter_color_changed)
138         self.gui_factory.connect('use-custom-widgets-changed', self.on_custom_widgets_changed)
139
140         self.abspeak = abspeak.AbspeakWidget()
141         self.abspeak.connect("reset", self.on_abspeak_reset)
142         self.abspeak.connect("volume-adjust", self.on_abspeak_adjust)
143
144         self.volume_digits = Gtk.Entry()
145         self.volume_digits.set_property('xalign', 0.5)
146         self.volume_digits.connect("key-press-event", self.on_volume_digits_key_pressed)
147         self.volume_digits.connect("focus-out-event", self.on_volume_digits_focus_out)
148
149         if self.initial_value != None:
150             if self.initial_value == True:
151                 self.slider_adjustment.set_value(0)
152             else:
153                 self.slider_adjustment.set_value_db(0)
154
155         self.connect("key-press-event", self.on_key_pressed)
156         self.connect("scroll-event", self.on_scroll)
157
158     def unrealize(self):
159         log.debug('Unrealizing channel "%s".', self.channel_name)
160         pass
161
162     def balance_preferred_width(self):
163         return (20, 20)
164
165     def _preferred_height(self):
166         return (0, 100)
167
168     def create_balance_widget(self):
169         if self.gui_factory.use_custom_widgets and phat:
170             self.balance = phat.HFanSlider()
171             self.balance.set_default_value(0)
172             self.balance.set_adjustment(self.balance_adjustment)
173         else:
174             self.balance = Gtk.Scale()
175             self.balance.get_preferred_width = self.balance_preferred_width
176             self.balance.get_preferred_height = self._preferred_height
177             self.balance.set_orientation(Gtk.Orientation.HORIZONTAL)
178             self.balance.set_adjustment(self.balance_adjustment)
179             self.balance.set_has_origin(False)
180             self.balance.set_draw_value(False)
181             self.balance.button_down = False
182             self.balance.connect('button-press-event', self.on_balance_button_press_event)
183             self.balance.connect('button-release-event', self.on_balance_button_release_event)
184             self.balance.connect("motion-notify-event", self.on_balance_motion_notify_event)
185             self.balance.connect("scroll-event", self.on_balance_scroll_event)
186
187
188         self.pack_start(self.balance, False, True, 0)
189         if self.monitor_button:
190             self.reorder_child(self.monitor_button, -1)
191         self.balance.show()
192
193     def on_balance_button_press_event(self, widget, event):
194         if event.button == 1 and event.type == Gdk.EventType.BUTTON_PRESS:
195             self.balance.button_down = True
196             self.balance.button_down_x = event.x
197             self.balance.button_down_value = self.balance.get_value()
198             return True
199         if event.button == 1 and event.type == Gdk.EventType._2BUTTON_PRESS:
200             self.balance_adjustment.set_balance(0)
201             return True
202         return False
203
204     def on_balance_button_release_event(self, widget, event):
205         self.balance.button_down = False
206         return False
207
208     def on_balance_motion_notify_event(self, widget, event):
209         slider_length = widget.get_allocation().width - widget.get_style_context().get_property('min-width', Gtk.StateFlags.NORMAL)
210         if self.balance.button_down:
211             delta_x = (event.x - self.balance.button_down_x) / slider_length
212             x = self.balance.button_down_value + 2 * delta_x
213             if x >= 1:
214                 x = 1
215             elif x <= -1:
216                 x = -1
217             self.balance_adjustment.set_balance(x)
218             return True
219
220     def on_balance_scroll_event(self, widget, event):
221         bal = self.balance
222         delta = bal.get_adjustment().get_step_increment()
223         value = bal.get_value()
224         if event.direction == Gdk.ScrollDirection.UP:
225             x = value - delta
226         elif event.direction == Gdk.ScrollDirection.DOWN:
227             x = value + delta
228         elif event.direction == Gdk.ScrollDirection.SMOOTH:
229             x = value - event.delta_y * delta
230
231         if x >= 1:
232             x = 1
233         elif x <= -1:
234             x = -1
235         bal.set_value(x)
236         return True
237
238     def create_slider_widget(self):
239         parent = None
240         if self.slider:
241             parent = self.slider.get_parent()
242             self.slider.destroy()
243         if self.gui_factory.use_custom_widgets:
244             self.slider = slider.CustomSliderWidget(self.slider_adjustment)
245         else:
246             self.slider = slider.GtkSlider(self.slider_adjustment)
247         if parent:
248             parent.pack_start(self.slider, True, True, 0)
249             parent.reorder_child(self.slider, 0)
250         self.slider.show()
251
252     def on_default_meter_scale_changed(self, gui_factory, scale):
253         log.debug("Default meter scale change detected.")
254         self.meter.set_scale(scale)
255
256     def on_default_slider_scale_changed(self, gui_factory, scale):
257         log.debug("Default slider scale change detected.")
258         self.slider_scale = scale
259         self.slider_adjustment.set_scale(scale)
260         if self.channel:
261             self.channel.midi_scale = self.slider_scale.scale
262
263     def on_vumeter_color_changed(self, gui_factory, *args):
264         color = gui_factory.get_vumeter_color()
265         color_scheme = gui_factory.get_vumeter_color_scheme()
266         if color_scheme != 'solid':
267             self.meter.set_color(None)
268         else:
269             self.meter.set_color(Gdk.color_parse(color))
270
271     def on_custom_widgets_changed(self, gui_factory, value):
272         self.balance.destroy()
273         self.create_balance_widget()
274         self.create_slider_widget()
275
276     def on_abspeak_adjust(self, abspeak, adjust):
277         log.debug("abspeak adjust %f", adjust)
278         self.slider_adjustment.set_value_db(self.slider_adjustment.get_value_db() + adjust)
279         self.channel.abspeak = None
280         #self.update_volume(False)   # We want to update gui even if actual decibels have not changed (scale wrap for example)
281
282     def on_abspeak_reset(self, abspeak):
283         log.debug("abspeak reset")
284         self.channel.abspeak = None
285
286     def on_volume_digits_key_pressed(self, widget, event):
287         if (event.keyval == Gdk.KEY_Return or event.keyval == Gdk.KEY_KP_Enter):
288             db_text = self.volume_digits.get_text()
289             try:
290                 db = float(db_text)
291                 log.debug('Volume digits confirmation "%f dBFS".', db)
292             except (ValueError) as e:
293                 log.debug("Volume digits confirmation ignore, reset to current.")
294                 self.update_volume(False)
295                 return
296             self.slider_adjustment.set_value_db(db)
297             #self.grab_focus()
298             #self.update_volume(False)   # We want to update gui even if actual decibels have not changed (scale wrap for example)
299
300     def on_volume_digits_focus_out(self, widget, event):
301         log.debug("Volume digits focus out detected.")
302         self.update_volume(False)
303
304     def read_meter(self):
305         if not self.channel:
306             return
307         if self.stereo:
308             peak_left, peak_right, rms_left, rms_right = self.channel.kmeter
309             self.meter.set_values(peak_left, peak_right, rms_left, rms_right)
310         else:
311             peak, rms = self.channel.kmeter
312             self.meter.set_values(peak, rms)
313
314         self.abspeak.set_peak(self.channel.abspeak)
315
316     def on_scroll(self, widget, event):
317         if event.direction == Gdk.ScrollDirection.DOWN:
318             self.slider_adjustment.step_down()
319         elif event.direction == Gdk.ScrollDirection.UP:
320             self.slider_adjustment.step_up()
321         return True
322
323     def update_volume(self, update_engine, from_midi = False):
324         db = self.slider_adjustment.get_value_db()
325
326         db_text = "%.2f" % db
327         self.volume_digits.set_text(db_text)
328
329         if update_engine:
330             if not from_midi:
331                 self.channel.volume = db
332             self.app.update_monitor(self)
333
334     def on_volume_changed(self, adjustment):
335         self.update_volume(True)
336
337     def on_volume_changed_from_midi(self, adjustment):
338         self.update_volume(True, from_midi = True)
339
340     def on_balance_changed(self, adjustment):
341         balance = self.balance_adjustment.get_value()
342         log.debug("%s balance: %f", self.channel_name, balance)
343         self.channel.balance = balance
344         self.app.update_monitor(self)
345
346     def on_key_pressed(self, widget, event):
347         if (event.keyval == Gdk.KEY_Up):
348             log.debug(self.channel_name + " Up")
349             self.slider_adjustment.step_up()
350             return True
351         elif (event.keyval == Gdk.KEY_Down):
352             log.debug(self.channel_name + " Down")
353             self.slider_adjustment.step_down()
354             return True
355
356         return False
357
358     def serialize(self, object_backend):
359         object_backend.add_property("volume", "%f" % self.slider_adjustment.get_value_db())
360         object_backend.add_property("balance", "%f" % self.balance_adjustment.get_value())
361
362         if hasattr(self.channel, 'out_mute'):
363             object_backend.add_property('out_mute', str(self.channel.out_mute))
364         if self.channel.volume_midi_cc != -1:
365             object_backend.add_property('volume_midi_cc', str(self.channel.volume_midi_cc))
366         if self.channel.balance_midi_cc != -1:
367             object_backend.add_property('balance_midi_cc', str(self.channel.balance_midi_cc))
368         if self.channel.mute_midi_cc != -1:
369             object_backend.add_property('mute_midi_cc', str(self.channel.mute_midi_cc))
370         if self.channel.solo_midi_cc != -1:
371             object_backend.add_property('solo_midi_cc', str(self.channel.solo_midi_cc))
372
373
374     def unserialize_property(self, name, value):
375         if name == "volume":
376             self.slider_adjustment.set_value_db(float(value))
377             return True
378         if name == "balance":
379             self.balance_adjustment.set_value(float(value))
380             return True
381         if name == 'out_mute':
382             self.future_out_mute = (value == 'True')
383             return True
384         if name == 'volume_midi_cc':
385             self.future_volume_midi_cc = int(value)
386             return True
387         if name == 'balance_midi_cc':
388             self.future_balance_midi_cc = int(value)
389             return True
390         if name == 'mute_midi_cc':
391             self.future_mute_midi_cc = int(value)
392             return True
393         if name == 'solo_midi_cc':
394             self.future_solo_midi_cc = int(value)
395             return True
396         return False
397
398     def on_midi_event_received(self, *args):
399         self.slider_adjustment.set_value_db(self.channel.volume, from_midi = True)
400         self.balance_adjustment.set_balance(self.channel.balance, from_midi = True)
401
402     def on_monitor_button_toggled(self, button):
403         if button.get_active():
404             for channel in self.app.channels + self.app.output_channels:
405                 if channel.monitor_button.get_active() and channel.monitor_button is not button:
406                     channel.monitor_button.handler_block_by_func(
407                                 channel.on_monitor_button_toggled)
408                     channel.monitor_button.set_active(False)
409                     channel.monitor_button.handler_unblock_by_func(
410                                 channel.on_monitor_button_toggled)
411             self.app.set_monitored_channel(self)
412         else:
413             if self.app._monitored_channel.channel.name == self.channel.name:
414                 self.monitor_button.handler_block_by_func(self.on_monitor_button_toggled)
415                 self.monitor_button.set_active(True)
416                 self.monitor_button.handler_unblock_by_func(self.on_monitor_button_toggled)
417
418     def set_monitored(self):
419         if self.channel:
420             self.app.set_monitored_channel(self)
421         self.monitor_button.set_active(True)
422
423     def set_color(self, color):
424         self.color = color
425         set_background_color(self.label_name_event_box, self.css_name, self.color.to_string())
426
427 class InputChannel(Channel):
428     post_fader_output_channel = None
429
430     def __init__(self, app, name, stereo, value = None):
431         Channel.__init__(self, app, name, stereo, value)
432
433     def realize(self):
434         self.channel = self.mixer.add_channel(self.channel_name, self.stereo)
435
436         if self.channel == None:
437             raise Exception("Cannot create a channel")
438         Channel.realize(self)
439         if self.future_volume_midi_cc != None:
440             self.channel.volume_midi_cc = self.future_volume_midi_cc
441         if self.future_balance_midi_cc != None:
442             self.channel.balance_midi_cc = self.future_balance_midi_cc
443         if self.future_mute_midi_cc != None:
444             self.channel.mute_midi_cc = self.future_mute_midi_cc
445         if self.future_solo_midi_cc != None:
446             self.channel.solo_midi_cc = self.future_solo_midi_cc
447         if self.app._init_solo_channels and self.channel_name in self.app._init_solo_channels:
448             self.channel.solo = True
449
450         self.channel.midi_scale = self.slider_scale.scale
451
452         self.on_volume_changed(self.slider_adjustment)
453         self.on_balance_changed(self.balance_adjustment)
454
455         # vbox child at upper part
456         self.vbox = Gtk.VBox()
457         self.pack_start(self.vbox, False, True, 0)
458         self.label_name = Gtk.Label()
459         self.label_name.get_style_context().add_class('top_label')
460         self.label_name.set_text(self.channel_name)
461         self.label_name.set_width_chars(0)
462         self.label_name_event_box = Gtk.EventBox()
463         self.label_name_event_box.connect("button-press-event", self.on_label_mouse)
464         self.label_name_event_box.add(self.label_name)
465
466         entries = [Gtk.TargetEntry.new("INPUT_CHANNEL", Gtk.TargetFlags.SAME_APP, 0)]
467         self.label_name_event_box.drag_source_set(Gdk.ModifierType.BUTTON1_MASK, entries,
468                 Gdk.DragAction.MOVE)
469         self.label_name_event_box.connect("drag-data-get", self.on_drag_data_get)
470         self.drag_dest_set(Gtk.DestDefaults.ALL, entries, Gdk.DragAction.MOVE)
471         self.connect_after("drag-data-received", self.on_drag_data_received)
472
473         self.vbox.pack_start(self.label_name_event_box, True, True, 0)
474 #         self.label_stereo = Gtk.Label()
475 #         if self.stereo:
476 #             self.label_stereo.set_text("stereo")
477 #         else:
478 #             self.label_stereo.set_text("mono")
479 #         self.label_stereo.set_size_request(0, -1)
480 #         self.vbox.pack_start(self.label_stereo, True)
481
482         frame = Gtk.Frame()
483         frame.set_shadow_type(Gtk.ShadowType.IN)
484         frame.add(self.abspeak);
485         self.pack_start(frame, False, True, 0)
486
487         # hbox child at lower part
488         self.hbox = Gtk.HBox()
489         self.hbox.pack_start(self.slider, True, True, 0)
490         frame = Gtk.Frame()
491         frame.set_shadow_type(Gtk.ShadowType.IN)
492         frame.add(self.meter);
493         self.hbox.pack_start(frame, True, True, 0)
494         frame = Gtk.Frame()
495         frame.set_shadow_type(Gtk.ShadowType.IN)
496         frame.add(self.hbox);
497         self.pack_start(frame, True, True, 0)
498
499         self.volume_digits.set_width_chars(6)
500         self.pack_start(self.volume_digits, False, False, 0)
501
502         self.create_balance_widget()
503
504         self.hbox_mutesolo = Gtk.Box(False, 0)
505
506         self.mute = Gtk.ToggleButton()
507         self.mute.set_label("M")
508         self.mute.set_name("mute")
509         self.mute.set_active(self.channel.out_mute)
510         self.mute.connect("toggled", self.on_mute_toggled)
511         self.hbox_mutesolo.pack_start(self.mute, True, True, 0)
512
513         self.solo = Gtk.ToggleButton()
514         self.solo.set_label("S")
515         self.solo.set_name("solo")
516         self.solo.set_active(self.channel.solo)
517         self.solo.connect("toggled", self.on_solo_toggled)
518         self.hbox_mutesolo.pack_start(self.solo, True, True, 0)
519
520         self.pack_start(self.hbox_mutesolo, False, False, 0)
521
522         self.monitor_button = Gtk.ToggleButton('MON')
523         self.monitor_button.connect('toggled', self.on_monitor_button_toggled)
524         self.pack_start(self.monitor_button, False, False, 0)
525
526     def on_drag_data_get(self, widget, drag_context, data, info, time):
527         channel = widget.get_parent().get_parent()
528         data.set(data.get_target(), 8, channel._channel_name.encode('utf-8'))
529
530     def on_drag_data_received(self, widget, drag_context, x, y, data, info, time):
531         source_name = data.get_data().decode('utf-8')
532         if source_name == self._channel_name:
533             return
534         self.emit("input-channel-order-changed", source_name, self._channel_name)
535
536     def add_control_group(self, channel):
537         control_group = ControlGroup(channel, self)
538         control_group.show_all()
539         self.vbox.pack_start(control_group, True, True, 0)
540         return control_group
541
542     def remove_control_group(self, channel):
543         ctlgroup = self.get_control_group(channel)
544         self.vbox.remove(ctlgroup)
545
546     def update_control_group(self, channel):
547         for control_group in self.vbox.get_children():
548             if isinstance(control_group, ControlGroup):
549                 if control_group.output_channel is channel:
550                     control_group.update()
551
552     def get_control_group(self, channel):
553         for control_group in self.vbox.get_children():
554             if isinstance(control_group, ControlGroup):
555                 if control_group.output_channel is channel:
556                     return control_group
557         return None
558
559     def unrealize(self):
560         Channel.unrealize(self)
561         if self.post_fader_output_channel:
562             self.post_fader_output_channel.remove()
563             self.post_fader_output_channel = None
564         self.channel.remove()
565         self.channel = None
566
567     channel_properties_dialog = None
568
569     def on_channel_properties(self):
570         if not self.channel_properties_dialog:
571             self.channel_properties_dialog = ChannelPropertiesDialog(self, self.app)
572         self.channel_properties_dialog.show()
573         self.channel_properties_dialog.present()
574
575     def on_label_mouse(self, widget, event):
576         if event.type == Gdk.EventType._2BUTTON_PRESS:
577             if event.button == 1:
578                 self.on_channel_properties()
579
580     def on_mute_toggled(self, button):
581         self.channel.out_mute = self.mute.get_active()
582
583     def on_solo_toggled(self, button):
584         self.channel.solo = self.solo.get_active()
585
586     def midi_events_check(self):
587         if hasattr(self, 'channel') and self.channel.midi_in_got_events:
588             self.mute.set_active(self.channel.out_mute)
589             self.solo.set_active(self.channel.solo)
590             Channel.on_midi_event_received(self)
591
592     def on_solo_button_pressed(self, button, event, *args):
593         if event.button == 3:
594             # right click on the solo button, act on all output channels
595             if button.get_active(): # was soloed
596                 button.set_active(False)
597                 if hasattr(button, 'touched_channels'):
598                     touched_channels = button.touched_channels
599                     for chan in touched_channels:
600                         ctlgroup = self.get_control_group(chan)
601                         ctlgroup.solo.set_active(False)
602                     del button.touched_channels
603             else: # was not soloed
604                 button.set_active(True)
605                 touched_channels = []
606                 for chan in self.app.output_channels:
607                     ctlgroup = self.get_control_group(chan)
608                     if not ctlgroup.solo.get_active():
609                         ctlgroup.solo.set_active(True)
610                         touched_channels.append(chan)
611                 button.touched_channels = touched_channels
612             return True
613         return False
614
615     @classmethod
616     def serialization_name(cls):
617         return 'input_channel'
618
619     def serialize(self, object_backend):
620         object_backend.add_property("name", self.channel_name)
621         if self.stereo:
622             object_backend.add_property("type", "stereo")
623         else:
624             object_backend.add_property("type", "mono")
625         Channel.serialize(self, object_backend)
626
627     def unserialize_property(self, name, value):
628         if name == "name":
629             self.channel_name = str(value)
630             return True
631         if name == "type":
632             if value == "stereo":
633                 self.stereo = True
634                 return True
635             if value == "mono":
636                 self.stereo = False
637                 return True
638         return Channel.unserialize_property(self, name, value)
639
640 GObject.signal_new("input-channel-order-changed", InputChannel,
641                 GObject.SignalFlags.RUN_FIRST | GObject.SignalFlags.ACTION,
642                 None, [GObject.TYPE_STRING, GObject.TYPE_STRING])
643
644 class OutputChannel(Channel):
645     _display_solo_buttons = False
646
647     _init_muted_channels = None
648     _init_solo_channels = None
649     _init_prefader_channels = None
650
651     def __init__(self, app, name, stereo, value = None):
652         Channel.__init__(self, app, name, stereo, value)
653
654     def get_display_solo_buttons(self):
655         return self._display_solo_buttons
656
657     def set_display_solo_buttons(self, value):
658         self._display_solo_buttons = value
659         # notifying control groups
660         for inputchannel in self.app.channels:
661             inputchannel.update_control_group(self)
662
663     display_solo_buttons = property(get_display_solo_buttons, set_display_solo_buttons)
664
665     def realize(self):
666         self.channel = self.mixer.add_output_channel(self.channel_name, self.stereo)
667         if self.channel == None:
668             raise Exception("Cannot create a channel")
669         Channel.realize(self)
670         if self.future_volume_midi_cc != None:
671             self.channel.volume_midi_cc = self.future_volume_midi_cc
672         if self.future_balance_midi_cc != None:
673             self.channel.balance_midi_cc = self.future_balance_midi_cc
674         if self.future_mute_midi_cc != None:
675             self.channel.mute_midi_cc = self.future_mute_midi_cc
676         self.channel.midi_scale = self.slider_scale.scale
677
678         self.on_volume_changed(self.slider_adjustment)
679         self.on_balance_changed(self.balance_adjustment)
680
681         # vbox child at upper part
682         self.vbox = Gtk.VBox()
683         self.pack_start(self.vbox, False, True, 0)
684         self.label_name = Gtk.Label()
685         self.label_name.get_style_context().add_class('top_label')
686         self.label_name.set_text(self.channel_name)
687         self.label_name.set_width_chars(0)
688         self.label_name_event_box = Gtk.EventBox()
689         self.label_name_event_box.connect('button-press-event', self.on_label_mouse)
690         self.label_name_event_box.add(self.label_name)
691
692         entries = [Gtk.TargetEntry.new("OUTPUT_CHANNEL", Gtk.TargetFlags.SAME_APP, 0)]
693         self.label_name_event_box.drag_source_set(Gdk.ModifierType.BUTTON1_MASK, entries,
694                 Gdk.DragAction.MOVE)
695         self.label_name_event_box.connect("drag-data-get", self.on_drag_data_get)
696         self.drag_dest_set(Gtk.DestDefaults.ALL, entries, Gdk.DragAction.MOVE)
697         self.connect_after("drag-data-received", self.on_drag_data_received)
698
699         if not hasattr(self, 'color'):
700             self.color = random_color()
701         set_background_color(self.label_name_event_box, self.css_name,
702                self.color.to_string())
703         self.vbox.pack_start(self.label_name_event_box, True, True, 0)
704         frame = Gtk.Frame()
705         frame.set_shadow_type(Gtk.ShadowType.IN)
706         frame.add(self.abspeak);
707         self.vbox.pack_start(frame, False, True, 0)
708
709         # hbox child at lower part
710         self.hbox = Gtk.HBox()
711         self.hbox.pack_start(self.slider, True, True, 0)
712         frame = Gtk.Frame()
713         frame.set_shadow_type(Gtk.ShadowType.IN)
714         frame.add(self.meter);
715         self.hbox.pack_start(frame, True, True, 0)
716         frame = Gtk.Frame()
717         frame.set_shadow_type(Gtk.ShadowType.IN)
718         frame.add(self.hbox);
719         self.pack_start(frame, True, True, 0)
720
721         self.volume_digits.set_width_chars(6)
722         self.pack_start(self.volume_digits, False, True, 0)
723
724         self.create_balance_widget()
725
726         self.mute = Gtk.ToggleButton()
727         self.mute.set_label("M")
728         self.mute.set_name("mute")
729         self.mute.set_active(self.channel.out_mute)
730         self.mute.connect("toggled", self.on_mute_toggled)
731
732         hbox = Gtk.HBox()
733         hbox.pack_start(self.mute, True, True, 0)
734         self.pack_start(hbox, False, False, 0)
735
736         self.monitor_button = Gtk.ToggleButton('MON')
737         self.monitor_button.connect('toggled', self.on_monitor_button_toggled)
738         self.pack_start(self.monitor_button, False, False, 0)
739
740         # add control groups to the input channels, and initialize them
741         # appropriately
742         for input_channel in self.app.channels:
743             ctlgroup = input_channel.add_control_group(self)
744             if self._init_muted_channels and input_channel.channel.name in self._init_muted_channels:
745                 ctlgroup.mute.set_active(True)
746             if self._init_solo_channels and input_channel.channel.name in self._init_solo_channels:
747                 ctlgroup.solo.set_active(True)
748             if self._init_prefader_channels and input_channel.channel.name in self._init_prefader_channels:
749                 ctlgroup.prefader.set_active(True)
750         self._init_muted_channels = None
751         self._init_solo_channels = None
752         self._init_prefader_channels = None
753
754     channel_properties_dialog = None
755
756     def on_drag_data_get(self, widget, drag_context, data, info, time):
757         channel = widget.get_parent().get_parent()
758         data.set(data.get_target(), 8, channel._channel_name.encode('utf-8'))
759
760     def on_drag_data_received(self, widget, drag_context, x, y, data, info, time):
761         source_name = data.get_data().decode('utf-8')
762         if source_name == self._channel_name:
763             return
764         self.emit("output-channel-order-changed", source_name, self._channel_name)
765
766     def on_channel_properties(self):
767         if not self.channel_properties_dialog:
768             self.channel_properties_dialog = OutputChannelPropertiesDialog(self, self.app)
769         self.channel_properties_dialog.show()
770         self.channel_properties_dialog.present()
771
772     def on_label_mouse(self, widget, event):
773         if event.type == Gdk.EventType._2BUTTON_PRESS:
774             if event.button == 1:
775                 self.on_channel_properties()
776
777     def on_mute_toggled(self, button):
778         self.channel.out_mute = self.mute.get_active()
779
780     def midi_events_check(self):
781         if self.channel != None and self.channel.midi_in_got_events:
782             self.mute.set_active(self.channel.out_mute)
783             Channel.on_midi_event_received(self)
784
785     def unrealize(self):
786         # remove control groups from input channels
787         for input_channel in self.app.channels:
788             input_channel.remove_control_group(self)
789         # then remove itself
790         Channel.unrealize(self)
791         self.channel.remove()
792         self.channel = None
793
794     @classmethod
795     def serialization_name(cls):
796         return 'output_channel'
797
798     def serialize(self, object_backend):
799         object_backend.add_property("name", self.channel_name)
800         if self.stereo:
801             object_backend.add_property("type", "stereo")
802         else:
803             object_backend.add_property("type", "mono")
804         if self.display_solo_buttons:
805             object_backend.add_property("solo_buttons", "true")
806         muted_channels = []
807         solo_channels = []
808         prefader_in_channels = []
809         for input_channel in self.app.channels:
810             if self.channel.is_muted(input_channel.channel):
811                 muted_channels.append(input_channel)
812             if self.channel.is_solo(input_channel.channel):
813                 solo_channels.append(input_channel)
814             if self.channel.is_in_prefader(input_channel.channel):
815                 prefader_in_channels.append(input_channel)
816         if muted_channels:
817             object_backend.add_property('muted_channels', '|'.join([x.channel.name for x in muted_channels]))
818         if solo_channels:
819             object_backend.add_property('solo_channels', '|'.join([x.channel.name for x in solo_channels]))
820         if prefader_in_channels:
821             object_backend.add_property('prefader_channels', '|'.join([x.channel.name for x in prefader_in_channels]))
822         object_backend.add_property("color", self.color.to_string())
823         Channel.serialize(self, object_backend)
824
825     def unserialize_property(self, name, value):
826         if name == "name":
827             self.channel_name = str(value)
828             return True
829         if name == "type":
830             if value == "stereo":
831                 self.stereo = True
832                 return True
833             if value == "mono":
834                 self.stereo = False
835                 return True
836         if name == "solo_buttons":
837             if value == "true":
838                 self.display_solo_buttons = True
839                 return True
840         if name == 'muted_channels':
841             self._init_muted_channels = value.split('|')
842             return True
843         if name == 'solo_channels':
844             self._init_solo_channels = value.split('|')
845             return True
846         if name == 'prefader_channels':
847             self._init_prefader_channels = value.split('|')
848             return True
849         if name == 'color':
850             c = Gdk.RGBA()
851             c.parse(value)
852             self.color = c
853             return True
854         return Channel.unserialize_property(self, name, value)
855
856 class ChannelPropertiesDialog(Gtk.Dialog):
857     channel = None
858
859     def __init__(self, parent, app):
860         self.channel = parent
861         self.app = app
862         self.mixer = self.channel.mixer
863         Gtk.Dialog.__init__(self, 'Channel "%s" Properties' % self.channel.channel_name, app.window)
864         self.set_default_size(365, -1)
865
866         self.add_button(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL)
867         self.ok_button = self.add_button(Gtk.STOCK_APPLY, Gtk.ResponseType.APPLY)
868         self.set_default_response(Gtk.ResponseType.APPLY);
869
870         self.create_ui()
871         self.fill_ui()
872
873         self.connect('response', self.on_response_cb)
874         self.connect('delete-event', self.on_response_cb)
875
876     def create_frame(self, label, child):
877         frame = Gtk.Frame()
878         frame.set_label('')
879         frame.set_border_width(3)
880         #frame.set_shadow_type(Gtk.ShadowType.NONE)
881         frame.get_label_widget().set_markup('<b>%s</b>' % label)
882
883         alignment = Gtk.Alignment.new(0, 0, 1, 1)
884         alignment.set_padding(0, 0, 12, 0)
885         frame.add(alignment)
886         alignment.add(child)
887
888         return frame
889
890     def create_ui(self):
891         vbox = Gtk.VBox()
892         self.vbox.add(vbox)
893
894         self.properties_table = table = Gtk.Table(4, 3, False)
895         vbox.pack_start(self.create_frame('Properties', table), True, True, 0)
896         table.set_row_spacings(5)
897         table.set_col_spacings(5)
898
899         table.attach(Gtk.Label(label='Name'), 0, 1, 0, 1)
900         self.entry_name = Gtk.Entry()
901         self.entry_name.set_activates_default(True)
902         self.entry_name.connect('changed', self.on_entry_name_changed)
903         table.attach(self.entry_name, 1, 2, 0, 1)
904
905         table.attach(Gtk.Label(label='Mode'), 0, 1, 1, 2)
906         self.mode_hbox = Gtk.HBox()
907         table.attach(self.mode_hbox, 1, 2, 1, 2)
908         self.mono = Gtk.RadioButton(label='Mono')
909         self.stereo = Gtk.RadioButton(label='Stereo', group=self.mono)
910         self.mode_hbox.pack_start(self.mono, True, True, 0)
911         self.mode_hbox.pack_start(self.stereo, True, True, 0)
912
913         table = Gtk.Table(2, 3, False)
914         vbox.pack_start(self.create_frame('MIDI Control Changes', table), True, True, 0)
915         table.set_row_spacings(5)
916         table.set_col_spacings(5)
917
918         cc_tooltip = "{} MIDI Control Change number (0-127, set to -1 to assign next free CC #)"
919         table.attach(Gtk.Label(label='Volume'), 0, 1, 0, 1)
920         self.entry_volume_cc = Gtk.SpinButton.new_with_range(-1, 127, 1)
921         self.entry_volume_cc.set_tooltip_text(cc_tooltip.format("Volume"))
922         table.attach(self.entry_volume_cc, 1, 2, 0, 1)
923         self.button_sense_midi_volume = Gtk.Button('Learn')
924         self.button_sense_midi_volume.connect('clicked',
925                         self.on_sense_midi_volume_clicked)
926         table.attach(self.button_sense_midi_volume, 2, 3, 0, 1)
927
928         table.attach(Gtk.Label(label='Balance'), 0, 1, 1, 2)
929         self.entry_balance_cc = Gtk.SpinButton.new_with_range(-1, 127, 1)
930         self.entry_balance_cc.set_tooltip_text(cc_tooltip.format("Balance"))
931         table.attach(self.entry_balance_cc, 1, 2, 1, 2)
932         self.button_sense_midi_balance = Gtk.Button('Learn')
933         self.button_sense_midi_balance.connect('clicked',
934                         self.on_sense_midi_balance_clicked)
935         table.attach(self.button_sense_midi_balance, 2, 3, 1, 2)
936
937         table.attach(Gtk.Label(label='Mute'), 0, 1, 2, 3)
938         self.entry_mute_cc = Gtk.SpinButton.new_with_range(-1, 127, 1)
939         self.entry_mute_cc.set_tooltip_text(cc_tooltip.format("Mute"))
940         table.attach(self.entry_mute_cc, 1, 2, 2, 3)
941         self.button_sense_midi_mute = Gtk.Button('Learn')
942         self.button_sense_midi_mute.connect('clicked',
943                         self.on_sense_midi_mute_clicked)
944         table.attach(self.button_sense_midi_mute, 2, 3, 2, 3)
945
946         if (isinstance(self, NewChannelDialog) or (self.channel and
947             isinstance(self.channel, InputChannel))):
948             table.attach(Gtk.Label(label='Solo'), 0, 1, 3, 4)
949             self.entry_solo_cc = Gtk.SpinButton.new_with_range(-1, 127, 1)
950             self.entry_solo_cc.set_tooltip_text(cc_tooltip.format("Solo"))
951             table.attach(self.entry_solo_cc, 1, 2, 3, 4)
952             self.button_sense_midi_solo = Gtk.Button('Learn')
953             self.button_sense_midi_solo.connect('clicked',
954                             self.on_sense_midi_solo_clicked)
955             table.attach(self.button_sense_midi_solo, 2, 3, 3, 4)
956
957         self.vbox.show_all()
958
959     def fill_ui(self):
960         self.entry_name.set_text(self.channel.channel_name)
961         if self.channel.channel.is_stereo:
962             self.stereo.set_active(True)
963         else:
964             self.mono.set_active(True)
965         self.mode_hbox.set_sensitive(False)
966         self.entry_volume_cc.set_value(self.channel.channel.volume_midi_cc)
967         self.entry_balance_cc.set_value(self.channel.channel.balance_midi_cc)
968         self.entry_mute_cc.set_value(self.channel.channel.mute_midi_cc)
969         if (self.channel and isinstance(self.channel, InputChannel)):
970             self.entry_solo_cc.set_value(self.channel.channel.solo_midi_cc)
971
972     def sense_popup_dialog(self, entry):
973         window = Gtk.Window.new(Gtk.WindowType.TOPLEVEL)
974         window.set_destroy_with_parent(True)
975         window.set_transient_for(self)
976         window.set_decorated(False)
977         window.set_modal(True)
978         window.set_position(Gtk.WindowPosition.CENTER_ON_PARENT)
979         window.set_border_width(10)
980
981         vbox = Gtk.VBox(10)
982         window.add(vbox)
983         window.timeout = 5
984         vbox.pack_start(Gtk.Label(label='Please move the MIDI control you want to use for this function.'), True, True, 0)
985         timeout_label = Gtk.Label(label='This window will close in 5 seconds')
986         vbox.pack_start(timeout_label, True, True, 0)
987         def close_sense_timeout(window, entry):
988             window.timeout -= 1
989             timeout_label.set_text('This window will close in %d seconds.' % window.timeout)
990             if window.timeout == 0:
991                 window.destroy()
992                 entry.set_value(self.mixer.last_midi_channel)
993                 return False
994             return True
995         window.show_all()
996         GObject.timeout_add_seconds(1, close_sense_timeout, window, entry)
997
998     def on_sense_midi_volume_clicked(self, *args):
999         self.mixer.last_midi_channel = int(self.entry_volume_cc.get_value())
1000         self.sense_popup_dialog(self.entry_volume_cc)
1001
1002     def on_sense_midi_balance_clicked(self, *args):
1003         self.mixer.last_midi_channel = int(self.entry_balance_cc.get_value())
1004         self.sense_popup_dialog(self.entry_balance_cc)
1005
1006     def on_sense_midi_mute_clicked(self, *args):
1007         self.mixer.last_midi_channel = int(self.entry_mute_cc.get_value())
1008         self.sense_popup_dialog(self.entry_mute_cc)
1009
1010     def on_sense_midi_solo_clicked(self, *args):
1011         self.mixer.last_midi_channel = int(self.entry_solo_cc.get_value())
1012         self.sense_popup_dialog(self.entry_solo_cc)
1013
1014     def on_response_cb(self, dlg, response_id, *args):
1015         self.channel.channel_properties_dialog = None
1016         name = self.entry_name.get_text()
1017         if response_id == Gtk.ResponseType.APPLY:
1018             if name != self.channel.channel_name:
1019                 self.channel.channel_name = name
1020             for control in ('volume', 'balance', 'mute', 'solo'):
1021                 widget = getattr(self, 'entry_{}_cc'.format(control), None)
1022                 if widget is not None:
1023                     value = int(widget.get_value())
1024                     if value != -1:
1025                         setattr(self.channel.channel, '{}_midi_cc'.format(control), value)
1026         self.destroy()
1027
1028     def on_entry_name_changed(self, entry):
1029         sensitive = False
1030         if len(entry.get_text()):
1031             if self.channel and self.channel.channel.name == entry.get_text():
1032                 sensitive = True
1033             elif entry.get_text() not in [x.channel.name for x in self.app.channels] + \
1034                         [x.channel.name for x in self.app.output_channels] + ['MAIN']:
1035                 sensitive = True
1036         self.ok_button.set_sensitive(sensitive)
1037
1038 GObject.signal_new("output-channel-order-changed", OutputChannel,
1039                 GObject.SignalFlags.RUN_FIRST | GObject.SignalFlags.ACTION,
1040                 None, [GObject.TYPE_STRING, GObject.TYPE_STRING])
1041
1042
1043 class NewChannelDialog(ChannelPropertiesDialog):
1044     def create_ui(self):
1045         ChannelPropertiesDialog.create_ui(self)
1046         self.add_initial_value_radio()
1047         self.vbox.show_all()
1048
1049     def add_initial_value_radio(self):
1050         self.properties_table.attach(Gtk.Label(label='Value'), 0, 1, 2, 3)
1051         self.value_hbox = Gtk.HBox()
1052         self.properties_table.attach(self.value_hbox, 1, 2, 2, 3)
1053         self.minus_inf = Gtk.RadioButton(label='-Inf')
1054         self.zero_dB = Gtk.RadioButton(label='0dB', group=self.minus_inf)
1055         self.value_hbox.pack_start(self.minus_inf, True, True, 0)
1056         self.value_hbox.pack_start(self.zero_dB, True, True, 0)
1057
1058
1059 class NewInputChannelDialog(NewChannelDialog):
1060     def __init__(self, app):
1061         Gtk.Dialog.__init__(self, 'New Input Channel', app.window)
1062         self.set_default_size(365, -1)
1063         self.mixer = app.mixer
1064         self.app = app
1065         self.create_ui()
1066         self.fill_ui()
1067
1068         self.stereo.set_active(True) # default to stereo
1069
1070         self.add_button(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL)
1071         self.ok_button = self.add_button(Gtk.STOCK_ADD, Gtk.ResponseType.OK)
1072         self.ok_button.set_sensitive(False)
1073         self.set_default_response(Gtk.ResponseType.OK);
1074
1075     def fill_ui(self):
1076         self.entry_volume_cc.set_value(-1)
1077         self.entry_balance_cc.set_value(-1)
1078         self.entry_mute_cc.set_value(-1)
1079         self.entry_solo_cc.set_value(-1)
1080
1081     def get_result(self):
1082         log.debug('minus_inf active?: %s', self.zero_dB.get_active())
1083         return {'name': self.entry_name.get_text(),
1084                 'stereo': self.stereo.get_active(),
1085                 'volume_cc': int(self.entry_volume_cc.get_value()),
1086                 'balance_cc': int(self.entry_balance_cc.get_value()),
1087                 'mute_cc': int(self.entry_mute_cc.get_value()),
1088                 'solo_cc': int(self.entry_solo_cc.get_value()),
1089                 'value': self.minus_inf.get_active()
1090                }
1091
1092
1093 class OutputChannelPropertiesDialog(ChannelPropertiesDialog):
1094     def create_ui(self):
1095         ChannelPropertiesDialog.create_ui(self)
1096
1097         table = self.properties_table
1098         table.attach(Gtk.Label(label='Color'), 0, 1, 4, 5)
1099         self.color_chooser_button = Gtk.ColorButton()
1100         self.color_chooser_button.set_use_alpha(True)
1101         self.color_chooser_button.set_rgba(Gdk.RGBA(0, 0, 0, 0))
1102         table.attach(self.color_chooser_button, 1, 2, 4, 5)
1103
1104         vbox = Gtk.VBox()
1105         self.vbox.pack_start(self.create_frame('Input Channels', vbox), True, True, 0)
1106
1107         self.display_solo_buttons = Gtk.CheckButton('Display solo buttons')
1108         vbox.pack_start(self.display_solo_buttons, True, True, 0)
1109
1110         self.vbox.show_all()
1111
1112     def fill_ui(self):
1113         ChannelPropertiesDialog.fill_ui(self)
1114         self.display_solo_buttons.set_active(self.channel.display_solo_buttons)
1115         self.color_chooser_button.set_rgba(self.channel.color)
1116
1117     def on_response_cb(self, dlg, response_id, *args):
1118         ChannelPropertiesDialog.on_response_cb(self, dlg, response_id, *args)
1119         if response_id == Gtk.ResponseType.APPLY:
1120             self.channel.display_solo_buttons = self.display_solo_buttons.get_active()
1121             self.channel.set_color(self.color_chooser_button.get_rgba())
1122             for inputchannel in self.app.channels:
1123                 inputchannel.update_control_group(self.channel)
1124
1125
1126 class NewOutputChannelDialog(NewChannelDialog, OutputChannelPropertiesDialog):
1127     def __init__(self, app):
1128         Gtk.Dialog.__init__(self, 'New Output Channel', app.window)
1129         self.mixer = app.mixer
1130         self.app = app
1131         OutputChannelPropertiesDialog.create_ui(self)
1132         self.add_initial_value_radio()
1133         self.vbox.show_all()
1134         self.fill_ui()
1135         self.set_default_size(365, -1)
1136
1137         # TODO: disable mode for output channels as mono output channels may
1138         # not be correctly handled yet.
1139         self.mode_hbox.set_sensitive(False)
1140         self.stereo.set_active(True) # default to stereo
1141
1142         self.add_button(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL)
1143         self.ok_button = self.add_button(Gtk.STOCK_ADD, Gtk.ResponseType.OK)
1144         self.ok_button.set_sensitive(False)
1145         self.set_default_response(Gtk.ResponseType.OK);
1146
1147     def fill_ui(self):
1148         self.entry_volume_cc.set_value(-1)
1149         self.entry_balance_cc.set_value(-1)
1150         self.entry_mute_cc.set_value(-1)
1151
1152     def get_result(self):
1153         return {'name': self.entry_name.get_text(),
1154                 'stereo': self.stereo.get_active(),
1155                 'volume_cc': int(self.entry_volume_cc.get_value()),
1156                 'balance_cc': int(self.entry_balance_cc.get_value()),
1157                 'mute_cc': int(self.entry_mute_cc.get_value()),
1158                 'display_solo_buttons': self.display_solo_buttons.get_active(),
1159                 'color': self.color_chooser_button.get_rgba(),
1160                 'value': self.minus_inf.get_active()
1161                 }
1162
1163
1164 class ControlGroup(Gtk.Alignment):
1165     def __init__(self, output_channel, input_channel):
1166         GObject.GObject.__init__(self)
1167         self.set(0.5, 0.5, 1, 1)
1168         self.output_channel = output_channel
1169         self.input_channel = input_channel
1170         self.app = input_channel.app
1171
1172         self.hbox = Gtk.HBox()
1173         self.vbox = Gtk.VBox()
1174         self.add(self.vbox)
1175         self.buttons_box = Gtk.Box(False, button_padding)
1176
1177         set_background_color(self.vbox, output_channel.css_name,
1178                 output_channel.color.to_string())
1179
1180         self.vbox.pack_start(self.hbox, True, True, button_padding)
1181         css = b"""
1182 .control_group {
1183     min-width: 0px;
1184     padding: 0px;
1185 }
1186
1187 .control_group #label,
1188 .control_group #mute,
1189 .control_group #pre_fader,
1190 .control_group #solo {
1191     font-size: smaller;
1192     padding: 0px .1em;
1193 }
1194 """
1195
1196         css_provider = Gtk.CssProvider()
1197         css_provider.load_from_data(css)
1198         context = Gtk.StyleContext()
1199         screen = Gdk.Screen.get_default()
1200         context.add_provider_for_screen(screen, css_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)
1201         hbox_context = self.hbox.get_style_context()
1202         hbox_context.add_class('control_group')
1203
1204         self.label = Gtk.Label(output_channel.channel.name)
1205         self.label.set_name("label")
1206         self.hbox.pack_start(self.label, False, False, button_padding)
1207         self.hbox.pack_end(self.buttons_box, False, False, button_padding)
1208         mute = Gtk.ToggleButton()
1209         mute.set_label("M")
1210         mute.set_name("mute")
1211         mute.set_tooltip_text("Mute output channel send")
1212         mute.connect("toggled", self.on_mute_toggled)
1213         self.mute = mute
1214         solo = Gtk.ToggleButton()
1215         solo.set_name("solo")
1216         solo.set_label("S")
1217         solo.set_tooltip_text("Solo output send")
1218         solo.connect("toggled", self.on_solo_toggled)
1219         self.solo = solo
1220         pre = Gtk.ToggleButton("P")
1221         pre.set_name("pre_fader")
1222         pre.set_tooltip_text("Pre (on) / Post (off) fader send")
1223         pre.connect("toggled", self.on_prefader_toggled)
1224         self.prefader = pre
1225         self.buttons_box.pack_start(pre, True, True, button_padding)
1226         self.buttons_box.pack_start(mute, True, True, button_padding)
1227         if self.output_channel.display_solo_buttons:
1228             self.buttons_box.pack_start(solo, True, True, button_padding)
1229
1230     def update(self):
1231         if self.output_channel.display_solo_buttons:
1232             if not self.solo in self.buttons_box.get_children():
1233                 self.buttons_box.pack_start(self.solo, True, True, button_padding)
1234                 self.solo.show()
1235         else:
1236             if self.solo in self.buttons_box.get_children():
1237                 self.buttons_box.remove(self.solo)
1238
1239         self.label.set_text(self.output_channel.channel.name)
1240         set_background_color(self.vbox, self.output_channel.css_name, self.output_channel.color.to_string())
1241
1242     def on_mute_toggled(self, button):
1243         self.output_channel.channel.set_muted(self.input_channel.channel, button.get_active())
1244         self.app.update_monitor(self)
1245
1246     def on_solo_toggled(self, button):
1247         self.output_channel.channel.set_solo(self.input_channel.channel, button.get_active())
1248         self.app.update_monitor(self)
1249
1250     def on_prefader_toggled(self, button):
1251         self.output_channel.channel.set_in_prefader(self.input_channel.channel, button.get_active())