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