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