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