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