]> git.0d.be Git - jack_mixer.git/blob - channel.py
e354536ce5023990b19a105aa0e76d194ff66a5f
[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 {padding: 0px .1em}
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         self.wide = True
433
434     def realize(self):
435         self.channel = self.mixer.add_channel(self.channel_name, self.stereo)
436
437         if self.channel == None:
438             raise Exception("Cannot create a channel")
439         Channel.realize(self)
440         if self.future_volume_midi_cc != None:
441             self.channel.volume_midi_cc = self.future_volume_midi_cc
442         if self.future_balance_midi_cc != None:
443             self.channel.balance_midi_cc = self.future_balance_midi_cc
444         if self.future_mute_midi_cc != None:
445             self.channel.mute_midi_cc = self.future_mute_midi_cc
446         if self.future_solo_midi_cc != None:
447             self.channel.solo_midi_cc = self.future_solo_midi_cc
448         if self.app._init_solo_channels and self.channel_name in self.app._init_solo_channels:
449             self.channel.solo = True
450
451         self.channel.midi_scale = self.slider_scale.scale
452
453         self.on_volume_changed(self.slider_adjustment)
454         self.on_balance_changed(self.balance_adjustment)
455
456         # vbox child at upper part
457         self.vbox = Gtk.VBox()
458         self.pack_start(self.vbox, False, True, 0)
459         self.label_name = Gtk.Label()
460         self.label_name.get_style_context().add_class('top_label')
461         self.label_name.set_text(self.channel_name)
462         self.label_name.set_width_chars(0)
463         self.label_name_event_box = Gtk.EventBox()
464         self.label_name_event_box.connect("button-press-event", self.on_label_mouse)
465         self.label_name_event_box.add(self.label_name)
466
467         entries = [Gtk.TargetEntry.new("INPUT_CHANNEL", Gtk.TargetFlags.SAME_APP, 0)]
468         self.label_name_event_box.drag_source_set(Gdk.ModifierType.BUTTON1_MASK, entries,
469                 Gdk.DragAction.MOVE)
470         self.label_name_event_box.connect("drag-data-get", self.on_drag_data_get)
471         self.drag_dest_set(Gtk.DestDefaults.ALL, entries, Gdk.DragAction.MOVE)
472         self.connect_after("drag-data-received", self.on_drag_data_received)
473
474         self.vbox.pack_start(self.label_name_event_box, True, True, 0)
475 #         self.label_stereo = Gtk.Label()
476 #         if self.stereo:
477 #             self.label_stereo.set_text("stereo")
478 #         else:
479 #             self.label_stereo.set_text("mono")
480 #         self.label_stereo.set_size_request(0, -1)
481 #         self.vbox.pack_start(self.label_stereo, True)
482
483         frame = Gtk.Frame()
484         frame.set_shadow_type(Gtk.ShadowType.IN)
485         frame.add(self.abspeak);
486         self.pack_start(frame, False, True, 0)
487
488         # hbox child at lower part
489         self.hbox = Gtk.HBox()
490         self.hbox.pack_start(self.slider, True, True, 0)
491         frame = Gtk.Frame()
492         frame.set_shadow_type(Gtk.ShadowType.IN)
493         frame.add(self.meter);
494         self.hbox.pack_start(frame, True, True, 0)
495         frame = Gtk.Frame()
496         frame.set_shadow_type(Gtk.ShadowType.IN)
497         frame.add(self.hbox);
498         self.pack_start(frame, True, True, 0)
499
500         self.volume_digits.set_width_chars(6)
501         self.pack_start(self.volume_digits, False, False, 0)
502
503         self.create_balance_widget()
504
505         self.hbox_mutesolo = Gtk.Box(False, 0)
506
507         self.mute = Gtk.ToggleButton()
508         self.mute.set_label("M")
509         self.mute.set_name("mute")
510         self.mute.set_active(self.channel.out_mute)
511         self.mute.connect("toggled", self.on_mute_toggled)
512         self.hbox_mutesolo.pack_start(self.mute, True, True, 0)
513
514         self.solo = Gtk.ToggleButton()
515         self.solo.set_label("S")
516         self.solo.set_name("solo")
517         self.solo.set_active(self.channel.solo)
518         self.solo.connect("toggled", self.on_solo_toggled)
519         self.hbox_mutesolo.pack_start(self.solo, True, True, 0)
520
521         self.pack_start(self.hbox_mutesolo, False, False, 0)
522
523         self.monitor_button = Gtk.ToggleButton('MON')
524         self.monitor_button.connect('toggled', self.on_monitor_button_toggled)
525         self.pack_start(self.monitor_button, False, False, 0)
526         if not self.wide:
527             self.narrow()
528
529
530     def narrow(self):
531         for cg in self.get_control_groups():
532             cg.narrow()
533         self.wide = False
534
535     def widen(self):
536         for cg in self.get_control_groups():
537             cg.widen()
538         self.wide = True
539
540     def on_drag_data_get(self, widget, drag_context, data, info, time):
541         channel = widget.get_parent().get_parent()
542         data.set(data.get_target(), 8, channel._channel_name.encode('utf-8'))
543
544     def on_drag_data_received(self, widget, drag_context, x, y, data, info, time):
545         source_name = data.get_data().decode('utf-8')
546         if source_name == self._channel_name:
547             return
548         self.emit("input-channel-order-changed", source_name, self._channel_name)
549
550     def add_control_group(self, channel):
551         control_group = ControlGroup(channel, self)
552         control_group.show_all()
553         self.vbox.pack_start(control_group, True, True, 0)
554         return control_group
555
556     def remove_control_group(self, channel):
557         ctlgroup = self.get_control_group(channel)
558         self.vbox.remove(ctlgroup)
559
560     def update_control_group(self, channel):
561         for control_group in self.vbox.get_children():
562             if isinstance(control_group, ControlGroup):
563                 if control_group.output_channel is channel:
564                     control_group.update()
565
566     def get_control_group(self, channel):
567         for control_group in self.get_control_groups:
568             if control_group.output_channel is channel:
569                 return control_group
570         return None
571
572     def get_control_groups(self):
573         ctlgroups = []
574         for c in self.vbox.get_children():
575             if isinstance(c, ControlGroup):
576                 ctlgroups.append(c)
577         return ctlgroups
578
579     def unrealize(self):
580         Channel.unrealize(self)
581         if self.post_fader_output_channel:
582             self.post_fader_output_channel.remove()
583             self.post_fader_output_channel = None
584         self.channel.remove()
585         self.channel = None
586
587     channel_properties_dialog = None
588
589     def on_channel_properties(self):
590         if not self.channel_properties_dialog:
591             self.channel_properties_dialog = ChannelPropertiesDialog(self, self.app)
592         self.channel_properties_dialog.show()
593         self.channel_properties_dialog.present()
594
595     def on_label_mouse(self, widget, event):
596         if event.type == Gdk.EventType._2BUTTON_PRESS:
597             if event.button == 1:
598                 self.on_channel_properties()
599             return True
600         elif event.state & Gdk.ModifierType.CONTROL_MASK and event.type == Gdk.EventType.BUTTON_PRESS and event.button == 1:
601             if self.wide:
602                 self.narrow()
603             else:
604                 self.widen()
605             return True
606
607     def on_mute_toggled(self, button):
608         self.channel.out_mute = self.mute.get_active()
609
610     def on_solo_toggled(self, button):
611         self.channel.solo = self.solo.get_active()
612
613     def midi_events_check(self):
614         if hasattr(self, 'channel') and self.channel.midi_in_got_events:
615             self.mute.set_active(self.channel.out_mute)
616             self.solo.set_active(self.channel.solo)
617             Channel.on_midi_event_received(self)
618
619     def on_solo_button_pressed(self, button, event, *args):
620         if event.button == 3:
621             # right click on the solo button, act on all output channels
622             if button.get_active(): # was soloed
623                 button.set_active(False)
624                 if hasattr(button, 'touched_channels'):
625                     touched_channels = button.touched_channels
626                     for chan in touched_channels:
627                         ctlgroup = self.get_control_group(chan)
628                         ctlgroup.solo.set_active(False)
629                     del button.touched_channels
630             else: # was not soloed
631                 button.set_active(True)
632                 touched_channels = []
633                 for chan in self.app.output_channels:
634                     ctlgroup = self.get_control_group(chan)
635                     if not ctlgroup.solo.get_active():
636                         ctlgroup.solo.set_active(True)
637                         touched_channels.append(chan)
638                 button.touched_channels = touched_channels
639             return True
640         return False
641
642     @classmethod
643     def serialization_name(cls):
644         return 'input_channel'
645
646     def serialize(self, object_backend):
647         object_backend.add_property("name", self.channel_name)
648         object_backend.add_property("wide", "%s" % str(self.wide))
649         if self.stereo:
650             object_backend.add_property("type", "stereo")
651         else:
652             object_backend.add_property("type", "mono")
653         Channel.serialize(self, object_backend)
654
655     def unserialize_property(self, name, value):
656         if name == "name":
657             self.channel_name = str(value)
658             return True
659         if name == "wide":
660             self.wide = value == "True"
661             return True
662         if name == "type":
663             if value == "stereo":
664                 self.stereo = True
665                 return True
666             if value == "mono":
667                 self.stereo = False
668                 return True
669         return Channel.unserialize_property(self, name, value)
670
671 GObject.signal_new("input-channel-order-changed", InputChannel,
672                 GObject.SignalFlags.RUN_FIRST | GObject.SignalFlags.ACTION,
673                 None, [GObject.TYPE_STRING, GObject.TYPE_STRING])
674
675 class OutputChannel(Channel):
676     _display_solo_buttons = False
677
678     _init_muted_channels = None
679     _init_solo_channels = None
680     _init_prefader_channels = None
681
682     def __init__(self, app, name, stereo, value = None):
683         Channel.__init__(self, app, name, stereo, value)
684
685     def get_display_solo_buttons(self):
686         return self._display_solo_buttons
687
688     def set_display_solo_buttons(self, value):
689         self._display_solo_buttons = value
690         # notifying control groups
691         for inputchannel in self.app.channels:
692             inputchannel.update_control_group(self)
693
694     display_solo_buttons = property(get_display_solo_buttons, set_display_solo_buttons)
695
696     def realize(self):
697         self.channel = self.mixer.add_output_channel(self.channel_name, self.stereo)
698         if self.channel == None:
699             raise Exception("Cannot create a channel")
700         Channel.realize(self)
701         if self.future_volume_midi_cc != None:
702             self.channel.volume_midi_cc = self.future_volume_midi_cc
703         if self.future_balance_midi_cc != None:
704             self.channel.balance_midi_cc = self.future_balance_midi_cc
705         if self.future_mute_midi_cc != None:
706             self.channel.mute_midi_cc = self.future_mute_midi_cc
707         self.channel.midi_scale = self.slider_scale.scale
708
709         self.on_volume_changed(self.slider_adjustment)
710         self.on_balance_changed(self.balance_adjustment)
711
712         # vbox child at upper part
713         self.vbox = Gtk.VBox()
714         self.pack_start(self.vbox, False, True, 0)
715         self.label_name = Gtk.Label()
716         self.label_name.get_style_context().add_class('top_label')
717         self.label_name.set_text(self.channel_name)
718         self.label_name.set_width_chars(0)
719         self.label_name_event_box = Gtk.EventBox()
720         self.label_name_event_box.connect('button-press-event', self.on_label_mouse)
721         self.label_name_event_box.add(self.label_name)
722
723         entries = [Gtk.TargetEntry.new("OUTPUT_CHANNEL", Gtk.TargetFlags.SAME_APP, 0)]
724         self.label_name_event_box.drag_source_set(Gdk.ModifierType.BUTTON1_MASK, entries,
725                 Gdk.DragAction.MOVE)
726         self.label_name_event_box.connect("drag-data-get", self.on_drag_data_get)
727         self.drag_dest_set(Gtk.DestDefaults.ALL, entries, Gdk.DragAction.MOVE)
728         self.connect_after("drag-data-received", self.on_drag_data_received)
729
730         if not hasattr(self, 'color'):
731             self.color = random_color()
732         set_background_color(self.label_name_event_box, self.css_name,
733                self.color.to_string())
734         self.vbox.pack_start(self.label_name_event_box, True, True, 0)
735         frame = Gtk.Frame()
736         frame.set_shadow_type(Gtk.ShadowType.IN)
737         frame.add(self.abspeak);
738         self.vbox.pack_start(frame, False, True, 0)
739
740         # hbox child at lower part
741         self.hbox = Gtk.HBox()
742         self.hbox.pack_start(self.slider, True, True, 0)
743         frame = Gtk.Frame()
744         frame.set_shadow_type(Gtk.ShadowType.IN)
745         frame.add(self.meter);
746         self.hbox.pack_start(frame, True, True, 0)
747         frame = Gtk.Frame()
748         frame.set_shadow_type(Gtk.ShadowType.IN)
749         frame.add(self.hbox);
750         self.pack_start(frame, True, True, 0)
751
752         self.volume_digits.set_width_chars(6)
753         self.pack_start(self.volume_digits, False, True, 0)
754
755         self.create_balance_widget()
756
757         self.mute = Gtk.ToggleButton()
758         self.mute.set_label("M")
759         self.mute.set_name("mute")
760         self.mute.set_active(self.channel.out_mute)
761         self.mute.connect("toggled", self.on_mute_toggled)
762
763         hbox = Gtk.HBox()
764         hbox.pack_start(self.mute, True, True, 0)
765         self.pack_start(hbox, False, False, 0)
766
767         self.monitor_button = Gtk.ToggleButton('MON')
768         self.monitor_button.connect('toggled', self.on_monitor_button_toggled)
769         self.pack_start(self.monitor_button, False, False, 0)
770
771         # add control groups to the input channels, and initialize them
772         # appropriately
773         for input_channel in self.app.channels:
774             ctlgroup = input_channel.add_control_group(self)
775             if self._init_muted_channels and input_channel.channel.name in self._init_muted_channels:
776                 ctlgroup.mute.set_active(True)
777             if self._init_solo_channels and input_channel.channel.name in self._init_solo_channels:
778                 ctlgroup.solo.set_active(True)
779             if self._init_prefader_channels and input_channel.channel.name in self._init_prefader_channels:
780                 ctlgroup.prefader.set_active(True)
781             if not input_channel.wide:
782                 ctlgroup.narrow()
783
784         self._init_muted_channels = None
785         self._init_solo_channels = None
786         self._init_prefader_channels = None
787
788     channel_properties_dialog = None
789
790     def on_drag_data_get(self, widget, drag_context, data, info, time):
791         channel = widget.get_parent().get_parent()
792         data.set(data.get_target(), 8, channel._channel_name.encode('utf-8'))
793
794     def on_drag_data_received(self, widget, drag_context, x, y, data, info, time):
795         source_name = data.get_data().decode('utf-8')
796         if source_name == self._channel_name:
797             return
798         self.emit("output-channel-order-changed", source_name, self._channel_name)
799
800     def on_channel_properties(self):
801         if not self.channel_properties_dialog:
802             self.channel_properties_dialog = OutputChannelPropertiesDialog(self, self.app)
803         self.channel_properties_dialog.show()
804         self.channel_properties_dialog.present()
805
806     def on_label_mouse(self, widget, event):
807         if event.type == Gdk.EventType._2BUTTON_PRESS:
808             if event.button == 1:
809                 self.on_channel_properties()
810
811     def on_mute_toggled(self, button):
812         self.channel.out_mute = self.mute.get_active()
813
814     def midi_events_check(self):
815         if self.channel != None and self.channel.midi_in_got_events:
816             self.mute.set_active(self.channel.out_mute)
817             Channel.on_midi_event_received(self)
818
819     def unrealize(self):
820         # remove control groups from input channels
821         for input_channel in self.app.channels:
822             input_channel.remove_control_group(self)
823         # then remove itself
824         Channel.unrealize(self)
825         self.channel.remove()
826         self.channel = None
827
828     @classmethod
829     def serialization_name(cls):
830         return 'output_channel'
831
832     def serialize(self, object_backend):
833         object_backend.add_property("name", self.channel_name)
834         if self.stereo:
835             object_backend.add_property("type", "stereo")
836         else:
837             object_backend.add_property("type", "mono")
838         if self.display_solo_buttons:
839             object_backend.add_property("solo_buttons", "true")
840         muted_channels = []
841         solo_channels = []
842         prefader_in_channels = []
843         for input_channel in self.app.channels:
844             if self.channel.is_muted(input_channel.channel):
845                 muted_channels.append(input_channel)
846             if self.channel.is_solo(input_channel.channel):
847                 solo_channels.append(input_channel)
848             if self.channel.is_in_prefader(input_channel.channel):
849                 prefader_in_channels.append(input_channel)
850         if muted_channels:
851             object_backend.add_property('muted_channels', '|'.join([x.channel.name for x in muted_channels]))
852         if solo_channels:
853             object_backend.add_property('solo_channels', '|'.join([x.channel.name for x in solo_channels]))
854         if prefader_in_channels:
855             object_backend.add_property('prefader_channels', '|'.join([x.channel.name for x in prefader_in_channels]))
856         object_backend.add_property("color", self.color.to_string())
857         Channel.serialize(self, object_backend)
858
859     def unserialize_property(self, name, value):
860         if name == "name":
861             self.channel_name = str(value)
862             return True
863         if name == "type":
864             if value == "stereo":
865                 self.stereo = True
866                 return True
867             if value == "mono":
868                 self.stereo = False
869                 return True
870         if name == "solo_buttons":
871             if value == "true":
872                 self.display_solo_buttons = True
873                 return True
874         if name == 'muted_channels':
875             self._init_muted_channels = value.split('|')
876             return True
877         if name == 'solo_channels':
878             self._init_solo_channels = value.split('|')
879             return True
880         if name == 'prefader_channels':
881             self._init_prefader_channels = value.split('|')
882             return True
883         if name == 'color':
884             c = Gdk.RGBA()
885             c.parse(value)
886             self.color = c
887             return True
888         return Channel.unserialize_property(self, name, value)
889
890 class ChannelPropertiesDialog(Gtk.Dialog):
891     channel = None
892
893     def __init__(self, parent, app):
894         self.channel = parent
895         self.app = app
896         self.mixer = self.channel.mixer
897         Gtk.Dialog.__init__(self, 'Channel "%s" Properties' % self.channel.channel_name, app.window)
898         self.set_default_size(365, -1)
899
900         self.add_button(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL)
901         self.ok_button = self.add_button(Gtk.STOCK_APPLY, Gtk.ResponseType.APPLY)
902         self.set_default_response(Gtk.ResponseType.APPLY);
903
904         self.create_ui()
905         self.fill_ui()
906
907         self.connect('response', self.on_response_cb)
908         self.connect('delete-event', self.on_response_cb)
909
910     def create_frame(self, label, child):
911         frame = Gtk.Frame()
912         frame.set_label('')
913         frame.set_border_width(3)
914         #frame.set_shadow_type(Gtk.ShadowType.NONE)
915         frame.get_label_widget().set_markup('<b>%s</b>' % label)
916
917         alignment = Gtk.Alignment.new(0, 0, 1, 1)
918         alignment.set_padding(0, 0, 12, 0)
919         frame.add(alignment)
920         alignment.add(child)
921
922         return frame
923
924     def create_ui(self):
925         vbox = Gtk.VBox()
926         self.vbox.add(vbox)
927
928         self.properties_table = table = Gtk.Table(4, 3, False)
929         vbox.pack_start(self.create_frame('Properties', table), True, True, 0)
930         table.set_row_spacings(5)
931         table.set_col_spacings(5)
932
933         table.attach(Gtk.Label(label='Name'), 0, 1, 0, 1)
934         self.entry_name = Gtk.Entry()
935         self.entry_name.set_activates_default(True)
936         self.entry_name.connect('changed', self.on_entry_name_changed)
937         table.attach(self.entry_name, 1, 2, 0, 1)
938
939         table.attach(Gtk.Label(label='Mode'), 0, 1, 1, 2)
940         self.mode_hbox = Gtk.HBox()
941         table.attach(self.mode_hbox, 1, 2, 1, 2)
942         self.mono = Gtk.RadioButton(label='Mono')
943         self.stereo = Gtk.RadioButton(label='Stereo', group=self.mono)
944         self.mode_hbox.pack_start(self.mono, True, True, 0)
945         self.mode_hbox.pack_start(self.stereo, True, True, 0)
946
947         table = Gtk.Table(2, 3, False)
948         vbox.pack_start(self.create_frame('MIDI Control Changes', table), True, True, 0)
949         table.set_row_spacings(5)
950         table.set_col_spacings(5)
951
952         cc_tooltip = "{} MIDI Control Change number (0-127, set to -1 to assign next free CC #)"
953         table.attach(Gtk.Label(label='Volume'), 0, 1, 0, 1)
954         self.entry_volume_cc = Gtk.SpinButton.new_with_range(-1, 127, 1)
955         self.entry_volume_cc.set_tooltip_text(cc_tooltip.format("Volume"))
956         table.attach(self.entry_volume_cc, 1, 2, 0, 1)
957         self.button_sense_midi_volume = Gtk.Button('Learn')
958         self.button_sense_midi_volume.connect('clicked',
959                         self.on_sense_midi_volume_clicked)
960         table.attach(self.button_sense_midi_volume, 2, 3, 0, 1)
961
962         table.attach(Gtk.Label(label='Balance'), 0, 1, 1, 2)
963         self.entry_balance_cc = Gtk.SpinButton.new_with_range(-1, 127, 1)
964         self.entry_balance_cc.set_tooltip_text(cc_tooltip.format("Balance"))
965         table.attach(self.entry_balance_cc, 1, 2, 1, 2)
966         self.button_sense_midi_balance = Gtk.Button('Learn')
967         self.button_sense_midi_balance.connect('clicked',
968                         self.on_sense_midi_balance_clicked)
969         table.attach(self.button_sense_midi_balance, 2, 3, 1, 2)
970
971         table.attach(Gtk.Label(label='Mute'), 0, 1, 2, 3)
972         self.entry_mute_cc = Gtk.SpinButton.new_with_range(-1, 127, 1)
973         self.entry_mute_cc.set_tooltip_text(cc_tooltip.format("Mute"))
974         table.attach(self.entry_mute_cc, 1, 2, 2, 3)
975         self.button_sense_midi_mute = Gtk.Button('Learn')
976         self.button_sense_midi_mute.connect('clicked',
977                         self.on_sense_midi_mute_clicked)
978         table.attach(self.button_sense_midi_mute, 2, 3, 2, 3)
979
980         if (isinstance(self, NewChannelDialog) or (self.channel and
981             isinstance(self.channel, InputChannel))):
982             table.attach(Gtk.Label(label='Solo'), 0, 1, 3, 4)
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             table.attach(self.entry_solo_cc, 1, 2, 3, 4)
986             self.button_sense_midi_solo = Gtk.Button('Learn')
987             self.button_sense_midi_solo.connect('clicked',
988                             self.on_sense_midi_solo_clicked)
989             table.attach(self.button_sense_midi_solo, 2, 3, 3, 4)
990
991         self.vbox.show_all()
992
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.mode_hbox.set_sensitive(False)
1000         self.entry_volume_cc.set_value(self.channel.channel.volume_midi_cc)
1001         self.entry_balance_cc.set_value(self.channel.channel.balance_midi_cc)
1002         self.entry_mute_cc.set_value(self.channel.channel.mute_midi_cc)
1003         if (self.channel and isinstance(self.channel, InputChannel)):
1004             self.entry_solo_cc.set_value(self.channel.channel.solo_midi_cc)
1005
1006     def sense_popup_dialog(self, entry):
1007         window = Gtk.Window.new(Gtk.WindowType.TOPLEVEL)
1008         window.set_destroy_with_parent(True)
1009         window.set_transient_for(self)
1010         window.set_decorated(False)
1011         window.set_modal(True)
1012         window.set_position(Gtk.WindowPosition.CENTER_ON_PARENT)
1013         window.set_border_width(10)
1014
1015         vbox = Gtk.VBox(10)
1016         window.add(vbox)
1017         window.timeout = 5
1018         vbox.pack_start(Gtk.Label(label='Please move the MIDI control you want to use for this function.'), True, True, 0)
1019         timeout_label = Gtk.Label(label='This window will close in 5 seconds')
1020         vbox.pack_start(timeout_label, True, True, 0)
1021         def close_sense_timeout(window, entry):
1022             window.timeout -= 1
1023             timeout_label.set_text('This window will close in %d seconds.' % window.timeout)
1024             if window.timeout == 0:
1025                 window.destroy()
1026                 entry.set_value(self.mixer.last_midi_channel)
1027                 return False
1028             return True
1029         window.show_all()
1030         GObject.timeout_add_seconds(1, close_sense_timeout, window, entry)
1031
1032     def on_sense_midi_volume_clicked(self, *args):
1033         self.mixer.last_midi_channel = int(self.entry_volume_cc.get_value())
1034         self.sense_popup_dialog(self.entry_volume_cc)
1035
1036     def on_sense_midi_balance_clicked(self, *args):
1037         self.mixer.last_midi_channel = int(self.entry_balance_cc.get_value())
1038         self.sense_popup_dialog(self.entry_balance_cc)
1039
1040     def on_sense_midi_mute_clicked(self, *args):
1041         self.mixer.last_midi_channel = int(self.entry_mute_cc.get_value())
1042         self.sense_popup_dialog(self.entry_mute_cc)
1043
1044     def on_sense_midi_solo_clicked(self, *args):
1045         self.mixer.last_midi_channel = int(self.entry_solo_cc.get_value())
1046         self.sense_popup_dialog(self.entry_solo_cc)
1047
1048     def on_response_cb(self, dlg, response_id, *args):
1049         self.channel.channel_properties_dialog = None
1050         name = self.entry_name.get_text()
1051         if response_id == Gtk.ResponseType.APPLY:
1052             if name != self.channel.channel_name:
1053                 self.channel.channel_name = name
1054             for control in ('volume', 'balance', 'mute', 'solo'):
1055                 widget = getattr(self, 'entry_{}_cc'.format(control), None)
1056                 if widget is not None:
1057                     value = int(widget.get_value())
1058                     if value != -1:
1059                         setattr(self.channel.channel, '{}_midi_cc'.format(control), value)
1060         self.destroy()
1061
1062     def on_entry_name_changed(self, entry):
1063         sensitive = False
1064         if len(entry.get_text()):
1065             if self.channel and self.channel.channel.name == entry.get_text():
1066                 sensitive = True
1067             elif entry.get_text() not in [x.channel.name for x in self.app.channels] + \
1068                         [x.channel.name for x in self.app.output_channels] + ['MAIN']:
1069                 sensitive = True
1070         self.ok_button.set_sensitive(sensitive)
1071
1072 GObject.signal_new("output-channel-order-changed", OutputChannel,
1073                 GObject.SignalFlags.RUN_FIRST | GObject.SignalFlags.ACTION,
1074                 None, [GObject.TYPE_STRING, GObject.TYPE_STRING])
1075
1076
1077 class NewChannelDialog(ChannelPropertiesDialog):
1078     def create_ui(self):
1079         ChannelPropertiesDialog.create_ui(self)
1080         self.add_initial_value_radio()
1081         self.vbox.show_all()
1082
1083     def add_initial_value_radio(self):
1084         self.properties_table.attach(Gtk.Label(label='Value'), 0, 1, 2, 3)
1085         self.value_hbox = Gtk.HBox()
1086         self.properties_table.attach(self.value_hbox, 1, 2, 2, 3)
1087         self.minus_inf = Gtk.RadioButton(label='-Inf')
1088         self.zero_dB = Gtk.RadioButton(label='0dB', group=self.minus_inf)
1089         self.value_hbox.pack_start(self.minus_inf, True, True, 0)
1090         self.value_hbox.pack_start(self.zero_dB, True, True, 0)
1091
1092
1093 class NewInputChannelDialog(NewChannelDialog):
1094     def __init__(self, app):
1095         Gtk.Dialog.__init__(self, 'New Input Channel', app.window)
1096         self.set_default_size(365, -1)
1097         self.mixer = app.mixer
1098         self.app = app
1099         self.create_ui()
1100         self.fill_ui()
1101
1102         self.stereo.set_active(True) # default to stereo
1103
1104         self.add_button(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL)
1105         self.ok_button = self.add_button(Gtk.STOCK_ADD, Gtk.ResponseType.OK)
1106         self.ok_button.set_sensitive(False)
1107         self.set_default_response(Gtk.ResponseType.OK);
1108
1109     def fill_ui(self):
1110         self.entry_volume_cc.set_value(-1)
1111         self.entry_balance_cc.set_value(-1)
1112         self.entry_mute_cc.set_value(-1)
1113         self.entry_solo_cc.set_value(-1)
1114
1115     def get_result(self):
1116         log.debug('minus_inf active?: %s', self.zero_dB.get_active())
1117         return {'name': self.entry_name.get_text(),
1118                 'stereo': self.stereo.get_active(),
1119                 'volume_cc': int(self.entry_volume_cc.get_value()),
1120                 'balance_cc': int(self.entry_balance_cc.get_value()),
1121                 'mute_cc': int(self.entry_mute_cc.get_value()),
1122                 'solo_cc': int(self.entry_solo_cc.get_value()),
1123                 'value': self.minus_inf.get_active()
1124                }
1125
1126
1127 class OutputChannelPropertiesDialog(ChannelPropertiesDialog):
1128     def create_ui(self):
1129         ChannelPropertiesDialog.create_ui(self)
1130
1131         table = self.properties_table
1132         table.attach(Gtk.Label(label='Color'), 0, 1, 4, 5)
1133         self.color_chooser_button = Gtk.ColorButton()
1134         self.color_chooser_button.set_use_alpha(True)
1135         self.color_chooser_button.set_rgba(Gdk.RGBA(0, 0, 0, 0))
1136         table.attach(self.color_chooser_button, 1, 2, 4, 5)
1137
1138         vbox = Gtk.VBox()
1139         self.vbox.pack_start(self.create_frame('Input Channels', vbox), True, True, 0)
1140
1141         self.display_solo_buttons = Gtk.CheckButton('Display solo buttons')
1142         vbox.pack_start(self.display_solo_buttons, True, True, 0)
1143
1144         self.vbox.show_all()
1145
1146     def fill_ui(self):
1147         ChannelPropertiesDialog.fill_ui(self)
1148         self.display_solo_buttons.set_active(self.channel.display_solo_buttons)
1149         self.color_chooser_button.set_rgba(self.channel.color)
1150
1151     def on_response_cb(self, dlg, response_id, *args):
1152         ChannelPropertiesDialog.on_response_cb(self, dlg, response_id, *args)
1153         if response_id == Gtk.ResponseType.APPLY:
1154             self.channel.display_solo_buttons = self.display_solo_buttons.get_active()
1155             self.channel.set_color(self.color_chooser_button.get_rgba())
1156             for inputchannel in self.app.channels:
1157                 inputchannel.update_control_group(self.channel)
1158
1159
1160 class NewOutputChannelDialog(NewChannelDialog, OutputChannelPropertiesDialog):
1161     def __init__(self, app):
1162         Gtk.Dialog.__init__(self, 'New Output Channel', app.window)
1163         self.mixer = app.mixer
1164         self.app = app
1165         OutputChannelPropertiesDialog.create_ui(self)
1166         self.add_initial_value_radio()
1167         self.vbox.show_all()
1168         self.fill_ui()
1169         self.set_default_size(365, -1)
1170
1171         # TODO: disable mode for output channels as mono output channels may
1172         # not be correctly handled yet.
1173         self.mode_hbox.set_sensitive(False)
1174         self.stereo.set_active(True) # default to stereo
1175
1176         self.add_button(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL)
1177         self.ok_button = self.add_button(Gtk.STOCK_ADD, Gtk.ResponseType.OK)
1178         self.ok_button.set_sensitive(False)
1179         self.set_default_response(Gtk.ResponseType.OK);
1180
1181     def fill_ui(self):
1182         self.entry_volume_cc.set_value(-1)
1183         self.entry_balance_cc.set_value(-1)
1184         self.entry_mute_cc.set_value(-1)
1185
1186     def get_result(self):
1187         return {'name': self.entry_name.get_text(),
1188                 'stereo': self.stereo.get_active(),
1189                 'volume_cc': int(self.entry_volume_cc.get_value()),
1190                 'balance_cc': int(self.entry_balance_cc.get_value()),
1191                 'mute_cc': int(self.entry_mute_cc.get_value()),
1192                 'display_solo_buttons': self.display_solo_buttons.get_active(),
1193                 'color': self.color_chooser_button.get_rgba(),
1194                 'value': self.minus_inf.get_active()
1195                 }
1196
1197
1198 class ControlGroup(Gtk.Alignment):
1199     def __init__(self, output_channel, input_channel):
1200         GObject.GObject.__init__(self)
1201         self.set(0.5, 0.5, 1, 1)
1202         self.output_channel = output_channel
1203         self.input_channel = input_channel
1204         self.app = input_channel.app
1205
1206         self.hbox = Gtk.HBox()
1207         self.vbox = Gtk.VBox()
1208         self.add(self.vbox)
1209         self.buttons_box = Gtk.Box(False, button_padding)
1210
1211         set_background_color(self.vbox, output_channel.css_name,
1212                 output_channel.color.to_string())
1213
1214         self.vbox.pack_start(self.hbox, True, True, button_padding)
1215         css = b"""
1216 .control_group {
1217     min-width: 0px;
1218     padding: 0px;
1219 }
1220
1221 .control_group #label,
1222 .control_group #mute,
1223 .control_group #pre_fader,
1224 .control_group #solo {
1225     font-size: smaller;
1226     padding: 0px .1em;
1227 }
1228 """
1229
1230         css_provider = Gtk.CssProvider()
1231         css_provider.load_from_data(css)
1232         context = Gtk.StyleContext()
1233         screen = Gdk.Screen.get_default()
1234         context.add_provider_for_screen(screen, css_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)
1235         hbox_context = self.hbox.get_style_context()
1236         hbox_context.add_class('control_group')
1237
1238         self.label = Gtk.Label(output_channel.channel.name)
1239         self.label.set_name("label")
1240         self.hbox.pack_start(self.label, False, False, button_padding)
1241         self.hbox.pack_end(self.buttons_box, False, False, button_padding)
1242         mute = Gtk.ToggleButton()
1243         mute.set_label("M")
1244         mute.set_name("mute")
1245         mute.set_tooltip_text("Mute output channel send")
1246         mute.connect("toggled", self.on_mute_toggled)
1247         self.mute = mute
1248         solo = Gtk.ToggleButton()
1249         solo.set_name("solo")
1250         solo.set_label("S")
1251         solo.set_tooltip_text("Solo output send")
1252         solo.connect("toggled", self.on_solo_toggled)
1253         self.solo = solo
1254         pre = Gtk.ToggleButton("P")
1255         pre.set_name("pre_fader")
1256         pre.set_tooltip_text("Pre (on) / Post (off) fader send")
1257         pre.connect("toggled", self.on_prefader_toggled)
1258         self.prefader = pre
1259         self.buttons_box.pack_start(pre, True, True, button_padding)
1260         self.buttons_box.pack_start(mute, True, True, button_padding)
1261         if self.output_channel.display_solo_buttons:
1262             self.buttons_box.pack_start(solo, True, True, button_padding)
1263
1264     def update(self):
1265         if self.output_channel.display_solo_buttons:
1266             if not self.solo in self.buttons_box.get_children():
1267                 self.buttons_box.pack_start(self.solo, True, True, button_padding)
1268                 self.solo.show()
1269         else:
1270             if self.solo in self.buttons_box.get_children():
1271                 self.buttons_box.remove(self.solo)
1272
1273         self.label.set_text(self.output_channel.channel.name)
1274         set_background_color(self.vbox, self.output_channel.css_name, self.output_channel.color.to_string())
1275
1276     def on_mute_toggled(self, button):
1277         self.output_channel.channel.set_muted(self.input_channel.channel, button.get_active())
1278         self.app.update_monitor(self)
1279
1280     def on_solo_toggled(self, button):
1281         self.output_channel.channel.set_solo(self.input_channel.channel, button.get_active())
1282         self.app.update_monitor(self)
1283
1284     def on_prefader_toggled(self, button):
1285         self.output_channel.channel.set_in_prefader(self.input_channel.channel, button.get_active())
1286
1287     def narrow(self):
1288         self.hbox.remove(self.label)
1289         self.hbox.set_child_packing(self.buttons_box, True, True, button_padding, Gtk.PackType.END)
1290
1291     def widen(self):
1292         self.hbox.pack_start(self.label, False, False, button_padding)
1293         self.hbox.set_child_packing(self.buttons_box, False, False, button_padding, Gtk.PackType.END)