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