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