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