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