1 # This file is part of jack_mixer
3 # Copyright (C) 2006 Nedko Arnaudov <nedko@arnaudov.name>
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
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.
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.
21 from gi.repository import Gtk
22 from gi.repository import Gdk
23 from gi.repository import GObject
24 from gi.repository import Pango
29 from serialization import SerializedObject
37 log = logging.getLogger(__name__)
57 css_provider = Gtk.CssProvider()
58 css_provider.load_from_data(css)
59 context = Gtk.StyleContext()
60 screen = Gdk.Screen.get_default()
61 context.add_provider_for_screen(screen, css_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)
64 def get_text_color(background_color):
65 """Calculates the luminance of the given color (GdkRGBA)
66 and returns an appropriate text color."""
67 # luminance coefficients taken from section C-9 from
68 # http://www.faqs.org/faqs/graphics/colorspace-faq/
69 brightess = background_color.red * 0.212671 + \
70 background_color.green * 0.715160 + \
71 background_color.blue * 0.072169
79 def set_background_color(widget, name, color):
80 color_string = color.to_string()
86 """ % (name, color_string, get_text_color(color))
88 css_provider = Gtk.CssProvider()
89 css_provider.load_from_data(css.encode('utf-8'))
90 context = Gtk.StyleContext()
91 screen = Gdk.Screen.get_default()
92 context.add_provider_for_screen(screen, css_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)
94 widget_context = widget.get_style_context()
95 widget_context.add_class(name)
99 from random import uniform, seed
101 return Gdk.RGBA(uniform(0, 1), uniform(0, 1), uniform(0, 1), 1)
104 class Channel(Gtk.VBox, SerializedObject):
105 '''Widget with slider and meter used as base class for more specific
107 monitor_button = None
109 def __init__(self, app, name, stereo, value = None):
110 Gtk.VBox.__init__(self)
112 self.mixer = app.mixer
114 self.gui_factory = app.gui_factory
115 self._channel_name = name
117 self.initial_value = value
118 self.meter_scale = self.gui_factory.get_default_meter_scale()
119 self.slider_scale = self.gui_factory.get_default_slider_scale()
120 self.slider_adjustment = slider.AdjustmentdBFS(self.slider_scale, 0.0, 0.02)
121 self.balance_adjustment = slider.BalanceAdjustment()
122 self.post_fader_output_channel = None
123 self.future_out_mute = None
124 self.future_volume_midi_cc = None
125 self.future_balance_midi_cc = None
126 self.future_mute_midi_cc = None
127 self.future_solo_midi_cc = None
128 self.css_name = "css_name_%d" % Channel.num_instances
129 self.label_name = None
131 self.label_chars_wide = 12
132 self.label_chars_narrow = 8
133 Channel.num_instances += 1
135 def get_channel_name(self):
136 return self._channel_name
138 def set_channel_name(self, name):
139 self.app.on_channel_rename(self._channel_name, name);
140 self._channel_name = name
142 self.label_name.set_text(name)
143 if len(name) > (self.label_chars_wide if self.wide else self.label_chars_narrow):
144 self.label_name.set_tooltip_text(name)
146 self.channel.name = name
147 if self.post_fader_output_channel:
148 self.post_fader_output_channel.name = "%s Out" % name;
149 channel_name = property(get_channel_name, set_channel_name)
152 log.debug('Realizing channel "%s".', self.channel_name)
153 if self.future_out_mute != None:
154 self.channel.out_mute = self.future_out_mute
157 # Channel strip label
158 self.vbox = Gtk.VBox()
159 self.pack_start(self.vbox, False, True, 0)
160 self.label_name = Gtk.Label()
161 self.label_name.get_style_context().add_class('top_label')
162 self.label_name.set_text(self.channel_name)
163 self.label_name.set_max_width_chars(self.label_chars_wide if self.wide else
164 self.label_chars_narrow)
165 self.label_name.set_ellipsize(Pango.EllipsizeMode.MIDDLE)
166 self.label_name_event_box = Gtk.EventBox()
167 self.label_name_event_box.connect('button-press-event', self.on_label_mouse)
168 self.label_name_event_box.add(self.label_name)
172 self.create_slider_widget()
175 self.volume_digits = Gtk.Entry()
176 self.volume_digits.set_property('xalign', 0.5)
177 self.volume_digits.connect("key-press-event", self.on_volume_digits_key_pressed)
178 self.volume_digits.connect("focus-out-event", self.on_volume_digits_focus_out)
181 self.abspeak = abspeak.AbspeakWidget()
182 self.abspeak.connect("reset", self.on_abspeak_reset)
183 self.abspeak.connect("volume-adjust", self.on_abspeak_adjust)
187 self.meter = meter.StereoMeterWidget(self.meter_scale)
189 self.meter = meter.MonoMeterWidget(self.meter_scale)
191 self.meter.set_events(Gdk.EventMask.SCROLL_MASK)
192 self.on_vumeter_color_changed(self.gui_factory)
194 if self.initial_value != None:
195 if self.initial_value == True:
196 self.slider_adjustment.set_value(0)
198 self.slider_adjustment.set_value_db(0)
200 self.slider_adjustment.connect("volume-changed", self.on_volume_changed)
201 self.slider_adjustment.connect("volume-changed-from-midi", self.on_volume_changed_from_midi)
202 self.balance_adjustment.connect("balance-changed", self.on_balance_changed)
204 self.gui_factory.connect("default-meter-scale-changed", self.on_default_meter_scale_changed)
205 self.gui_factory.connect("default-slider-scale-changed", self.on_default_slider_scale_changed)
206 self.gui_factory.connect('vumeter-color-changed', self.on_vumeter_color_changed)
207 self.gui_factory.connect('vumeter-color-scheme-changed', self.on_vumeter_color_changed)
208 self.gui_factory.connect('use-custom-widgets-changed', self.on_custom_widgets_changed)
210 self.connect("key-press-event", self.on_key_pressed)
211 self.connect("scroll-event", self.on_scroll)
214 log.debug('Unrealizing channel "%s".', self.channel_name)
219 ctx = self.label_name.get_style_context()
220 ctx.remove_class('wide')
221 ctx.add_class('narrow')
222 label = self.label_name.get_label()
223 self.label_name.set_max_width_chars(self.label_chars_narrow)
225 if len(label) > self.label_chars_narrow:
226 self.label_name.set_tooltip_text(label)
230 self.label_name.set_tooltip_text(None)
231 ctx = self.label_name.get_style_context()
232 ctx.remove_class('narrow')
233 ctx.add_class('wide')
234 label = self.label_name.get_label()
235 self.label_name.set_max_width_chars(self.label_chars_wide)
237 if len(label) > self.label_chars_wide:
238 self.label_name.set_tooltip_text(label)
240 def balance_preferred_width(self):
243 def _preferred_height(self):
246 def create_balance_widget(self):
247 if self.gui_factory.use_custom_widgets and phat:
248 self.balance = phat.HFanSlider()
249 self.balance.set_default_value(0)
250 self.balance.set_adjustment(self.balance_adjustment)
252 self.balance = Gtk.Scale()
253 self.balance.get_preferred_width = self.balance_preferred_width
254 self.balance.get_preferred_height = self._preferred_height
255 self.balance.set_orientation(Gtk.Orientation.HORIZONTAL)
256 self.balance.set_adjustment(self.balance_adjustment)
257 self.balance.set_has_origin(False)
258 self.balance.set_draw_value(False)
259 self.balance.button_down = False
260 self.balance.connect('button-press-event', self.on_balance_button_press_event)
261 self.balance.connect('button-release-event', self.on_balance_button_release_event)
262 self.balance.connect("motion-notify-event", self.on_balance_motion_notify_event)
263 self.balance.connect("scroll-event", self.on_balance_scroll_event)
266 self.pack_start(self.balance, False, True, 0)
267 if self.monitor_button:
268 self.reorder_child(self.monitor_button, -1)
271 def on_label_mouse(self, widget, event):
272 if event.type == Gdk.EventType._2BUTTON_PRESS:
273 if event.button == 1:
274 self.on_channel_properties()
276 elif (event.state & Gdk.ModifierType.CONTROL_MASK and
277 event.type == Gdk.EventType.BUTTON_PRESS and
285 def on_balance_button_press_event(self, widget, event):
286 if event.button == 1 and event.type == Gdk.EventType.BUTTON_PRESS:
287 self.balance.button_down = True
288 self.balance.button_down_x = event.x
289 self.balance.button_down_value = self.balance.get_value()
291 if event.button == 1 and event.type == Gdk.EventType._2BUTTON_PRESS:
292 self.balance_adjustment.set_balance(0)
296 def on_balance_button_release_event(self, widget, event):
297 self.balance.button_down = False
300 def on_balance_motion_notify_event(self, widget, event):
301 slider_length = widget.get_allocation().width - widget.get_style_context().get_property('min-width', Gtk.StateFlags.NORMAL)
302 if self.balance.button_down:
303 delta_x = (event.x - self.balance.button_down_x) / slider_length
304 x = self.balance.button_down_value + 2 * delta_x
309 self.balance_adjustment.set_balance(x)
312 def on_balance_scroll_event(self, widget, event):
314 delta = bal.get_adjustment().get_step_increment()
315 value = bal.get_value()
316 if event.direction == Gdk.ScrollDirection.UP:
318 elif event.direction == Gdk.ScrollDirection.DOWN:
320 elif event.direction == Gdk.ScrollDirection.SMOOTH:
321 x = value - event.delta_y * delta
330 def create_slider_widget(self):
333 parent = self.slider.get_parent()
334 self.slider.destroy()
335 if self.gui_factory.use_custom_widgets:
336 self.slider = slider.CustomSliderWidget(self.slider_adjustment)
338 self.slider = slider.GtkSlider(self.slider_adjustment)
340 parent.pack_start(self.slider, True, True, 0)
341 parent.reorder_child(self.slider, 0)
344 def on_default_meter_scale_changed(self, gui_factory, scale):
345 log.debug("Default meter scale change detected.")
346 self.meter.set_scale(scale)
348 def on_default_slider_scale_changed(self, gui_factory, scale):
349 log.debug("Default slider scale change detected.")
350 self.slider_scale = scale
351 self.slider_adjustment.set_scale(scale)
353 self.channel.midi_scale = self.slider_scale.scale
355 def on_vumeter_color_changed(self, gui_factory, *args):
356 color = gui_factory.get_vumeter_color()
357 color_scheme = gui_factory.get_vumeter_color_scheme()
358 if color_scheme != 'solid':
359 self.meter.set_color(None)
361 self.meter.set_color(Gdk.color_parse(color))
363 def on_custom_widgets_changed(self, gui_factory, value):
364 self.balance.destroy()
365 self.create_balance_widget()
366 self.create_slider_widget()
368 def on_abspeak_adjust(self, abspeak, adjust):
369 log.debug("abspeak adjust %f", adjust)
370 self.slider_adjustment.set_value_db(self.slider_adjustment.get_value_db() + adjust)
371 self.channel.abspeak = None
372 #self.update_volume(False) # We want to update gui even if actual decibels have not changed (scale wrap for example)
374 def on_abspeak_reset(self, abspeak):
375 log.debug("abspeak reset")
376 self.channel.abspeak = None
378 def on_volume_digits_key_pressed(self, widget, event):
379 if (event.keyval == Gdk.KEY_Return or event.keyval == Gdk.KEY_KP_Enter):
380 db_text = self.volume_digits.get_text()
383 log.debug('Volume digits confirmation "%f dBFS".', db)
384 except (ValueError) as e:
385 log.debug("Volume digits confirmation ignore, reset to current.")
386 self.update_volume(False)
388 self.slider_adjustment.set_value_db(db)
390 #self.update_volume(False) # We want to update gui even if actual decibels have not changed (scale wrap for example)
392 def on_volume_digits_focus_out(self, widget, event):
393 log.debug("Volume digits focus out detected.")
394 self.update_volume(False)
396 def read_meter(self):
400 peak_left, peak_right, rms_left, rms_right = self.channel.kmeter
401 self.meter.set_values(peak_left, peak_right, rms_left, rms_right)
403 peak, rms = self.channel.kmeter
404 self.meter.set_values(peak, rms)
406 self.abspeak.set_peak(self.channel.abspeak)
408 def on_scroll(self, widget, event):
409 if event.direction == Gdk.ScrollDirection.DOWN:
410 self.slider_adjustment.step_down()
411 elif event.direction == Gdk.ScrollDirection.UP:
412 self.slider_adjustment.step_up()
415 def update_volume(self, update_engine, from_midi = False):
416 db = self.slider_adjustment.get_value_db()
418 db_text = "%.2f" % db
419 self.volume_digits.set_text(db_text)
423 self.channel.volume = db
424 self.app.update_monitor(self)
426 def on_volume_changed(self, adjustment):
427 self.update_volume(True)
429 def on_volume_changed_from_midi(self, adjustment):
430 self.update_volume(True, from_midi = True)
432 def on_balance_changed(self, adjustment):
433 balance = self.balance_adjustment.get_value()
434 log.debug("%s balance: %f", self.channel_name, balance)
435 self.channel.balance = balance
436 self.app.update_monitor(self)
438 def on_key_pressed(self, widget, event):
439 if (event.keyval == Gdk.KEY_Up):
440 log.debug(self.channel_name + " Up")
441 self.slider_adjustment.step_up()
443 elif (event.keyval == Gdk.KEY_Down):
444 log.debug(self.channel_name + " Down")
445 self.slider_adjustment.step_down()
450 def serialize(self, object_backend):
451 object_backend.add_property("volume", "%f" % self.slider_adjustment.get_value_db())
452 object_backend.add_property("balance", "%f" % self.balance_adjustment.get_value())
453 object_backend.add_property("wide", "%s" % str(self.wide))
455 if hasattr(self.channel, 'out_mute'):
456 object_backend.add_property('out_mute', str(self.channel.out_mute))
457 if self.channel.volume_midi_cc != -1:
458 object_backend.add_property('volume_midi_cc', str(self.channel.volume_midi_cc))
459 if self.channel.balance_midi_cc != -1:
460 object_backend.add_property('balance_midi_cc', str(self.channel.balance_midi_cc))
461 if self.channel.mute_midi_cc != -1:
462 object_backend.add_property('mute_midi_cc', str(self.channel.mute_midi_cc))
463 if self.channel.solo_midi_cc != -1:
464 object_backend.add_property('solo_midi_cc', str(self.channel.solo_midi_cc))
467 def unserialize_property(self, name, value):
469 self.slider_adjustment.set_value_db(float(value))
471 if name == "balance":
472 self.balance_adjustment.set_value(float(value))
474 if name == 'out_mute':
475 self.future_out_mute = (value == 'True')
477 if name == 'volume_midi_cc':
478 self.future_volume_midi_cc = int(value)
480 if name == 'balance_midi_cc':
481 self.future_balance_midi_cc = int(value)
483 if name == 'mute_midi_cc':
484 self.future_mute_midi_cc = int(value)
486 if name == 'solo_midi_cc':
487 self.future_solo_midi_cc = int(value)
490 self.wide = value == "True"
494 def on_midi_event_received(self, *args):
495 self.slider_adjustment.set_value_db(self.channel.volume, from_midi = True)
496 self.balance_adjustment.set_balance(self.channel.balance, from_midi = True)
498 def on_monitor_button_toggled(self, button):
499 if button.get_active():
500 for channel in self.app.channels + self.app.output_channels:
501 if channel.monitor_button.get_active() and channel.monitor_button is not button:
502 channel.monitor_button.handler_block_by_func(
503 channel.on_monitor_button_toggled)
504 channel.monitor_button.set_active(False)
505 channel.monitor_button.handler_unblock_by_func(
506 channel.on_monitor_button_toggled)
507 self.app.set_monitored_channel(self)
509 if self.app._monitored_channel.channel.name == self.channel.name:
510 self.monitor_button.handler_block_by_func(self.on_monitor_button_toggled)
511 self.monitor_button.set_active(True)
512 self.monitor_button.handler_unblock_by_func(self.on_monitor_button_toggled)
514 def set_monitored(self):
516 self.app.set_monitored_channel(self)
517 self.monitor_button.set_active(True)
519 def set_color(self, color):
521 set_background_color(self.label_name_event_box, self.css_name, self.color)
523 class InputChannel(Channel):
524 post_fader_output_channel = None
527 self.channel = self.mixer.add_channel(self.channel_name, self.stereo)
529 if self.channel == None:
530 raise Exception("Cannot create a channel")
534 if self.future_volume_midi_cc != None:
535 self.channel.volume_midi_cc = self.future_volume_midi_cc
536 if self.future_balance_midi_cc != None:
537 self.channel.balance_midi_cc = self.future_balance_midi_cc
538 if self.future_mute_midi_cc != None:
539 self.channel.mute_midi_cc = self.future_mute_midi_cc
540 if self.future_solo_midi_cc != None:
541 self.channel.solo_midi_cc = self.future_solo_midi_cc
542 if self.app._init_solo_channels and self.channel_name in self.app._init_solo_channels:
543 self.channel.solo = True
545 self.channel.midi_scale = self.slider_scale.scale
547 self.on_volume_changed(self.slider_adjustment)
548 self.on_balance_changed(self.balance_adjustment)
550 entries = [Gtk.TargetEntry.new("INPUT_CHANNEL", Gtk.TargetFlags.SAME_APP, 0)]
551 self.label_name_event_box.drag_source_set(Gdk.ModifierType.BUTTON1_MASK, entries,
553 self.label_name_event_box.connect("drag-data-get", self.on_drag_data_get)
554 self.drag_dest_set(Gtk.DestDefaults.ALL, entries, Gdk.DragAction.MOVE)
555 self.connect_after("drag-data-received", self.on_drag_data_received)
557 self.vbox.pack_start(self.label_name_event_box, True, True, 0)
558 # self.label_stereo = Gtk.Label()
560 # self.label_stereo.set_text("stereo")
562 # self.label_stereo.set_text("mono")
563 # self.label_stereo.set_size_request(0, -1)
564 # self.vbox.pack_start(self.label_stereo, True)
567 frame.set_shadow_type(Gtk.ShadowType.IN)
568 frame.add(self.abspeak);
569 self.pack_start(frame, False, True, 0)
571 # hbox child at lower part
572 self.hbox = Gtk.HBox()
573 self.hbox.pack_start(self.slider, True, True, 0)
575 frame.set_shadow_type(Gtk.ShadowType.IN)
576 frame.add(self.meter);
577 self.hbox.pack_start(frame, True, True, 0)
579 frame.set_shadow_type(Gtk.ShadowType.IN)
580 frame.add(self.hbox);
581 self.pack_start(frame, True, True, 0)
583 self.volume_digits.set_width_chars(6)
584 self.pack_start(self.volume_digits, False, False, 0)
586 self.create_balance_widget()
588 self.hbox_mutesolo = Gtk.Box(False, 0)
590 self.mute = Gtk.ToggleButton()
591 self.mute.set_label("M")
592 self.mute.set_name("mute")
593 self.mute.set_active(self.channel.out_mute)
594 self.mute.connect("toggled", self.on_mute_toggled)
595 self.hbox_mutesolo.pack_start(self.mute, True, True, 0)
597 self.solo = Gtk.ToggleButton()
598 self.solo.set_label("S")
599 self.solo.set_name("solo")
600 self.solo.set_active(self.channel.solo)
601 self.solo.connect("toggled", self.on_solo_toggled)
602 self.hbox_mutesolo.pack_start(self.solo, True, True, 0)
604 self.pack_start(self.hbox_mutesolo, False, False, 0)
606 self.monitor_button = Gtk.ToggleButton('MON')
607 self.monitor_button.connect('toggled', self.on_monitor_button_toggled)
608 self.pack_start(self.monitor_button, False, False, 0)
615 for cg in self.get_control_groups():
620 for cg in self.get_control_groups():
623 def on_drag_data_get(self, widget, drag_context, data, info, time):
624 channel = widget.get_parent().get_parent()
625 data.set(data.get_target(), 8, channel._channel_name.encode('utf-8'))
627 def on_drag_data_received(self, widget, drag_context, x, y, data, info, time):
628 source_name = data.get_data().decode('utf-8')
629 if source_name == self._channel_name:
631 self.emit("input-channel-order-changed", source_name, self._channel_name)
633 def add_control_group(self, channel):
634 control_group = ControlGroup(channel, self)
635 control_group.show_all()
636 self.vbox.pack_start(control_group, True, True, 0)
639 def remove_control_group(self, channel):
640 ctlgroup = self.get_control_group(channel)
641 self.vbox.remove(ctlgroup)
643 def update_control_group(self, channel):
644 for control_group in self.vbox.get_children():
645 if isinstance(control_group, ControlGroup):
646 if control_group.output_channel is channel:
647 control_group.update()
649 def get_control_group(self, channel):
650 for control_group in self.get_control_groups:
651 if control_group.output_channel is channel:
655 def get_control_groups(self):
657 for c in self.vbox.get_children():
658 if isinstance(c, ControlGroup):
664 if self.post_fader_output_channel:
665 self.post_fader_output_channel.remove()
666 self.post_fader_output_channel = None
667 self.channel.remove()
670 channel_properties_dialog = None
672 def on_channel_properties(self):
673 if not self.channel_properties_dialog:
674 self.channel_properties_dialog = ChannelPropertiesDialog(self, self.app)
675 self.channel_properties_dialog.show()
676 self.channel_properties_dialog.present()
678 def on_mute_toggled(self, button):
679 self.channel.out_mute = self.mute.get_active()
681 def on_solo_toggled(self, button):
682 self.channel.solo = self.solo.get_active()
684 def midi_events_check(self):
685 if hasattr(self, 'channel') and self.channel.midi_in_got_events:
686 self.mute.set_active(self.channel.out_mute)
687 self.solo.set_active(self.channel.solo)
688 super().on_midi_event_received()
690 def on_solo_button_pressed(self, button, event, *args):
691 if event.button == 3:
692 # right click on the solo button, act on all output channels
693 if button.get_active(): # was soloed
694 button.set_active(False)
695 if hasattr(button, 'touched_channels'):
696 touched_channels = button.touched_channels
697 for chan in touched_channels:
698 ctlgroup = self.get_control_group(chan)
699 ctlgroup.solo.set_active(False)
700 del button.touched_channels
701 else: # was not soloed
702 button.set_active(True)
703 touched_channels = []
704 for chan in self.app.output_channels:
705 ctlgroup = self.get_control_group(chan)
706 if not ctlgroup.solo.get_active():
707 ctlgroup.solo.set_active(True)
708 touched_channels.append(chan)
709 button.touched_channels = touched_channels
714 def serialization_name(cls):
715 return 'input_channel'
717 def serialize(self, object_backend):
718 object_backend.add_property("name", self.channel_name)
720 object_backend.add_property("type", "stereo")
722 object_backend.add_property("type", "mono")
723 super().serialize(object_backend)
725 def unserialize_property(self, name, value):
727 self.channel_name = str(value)
730 if value == "stereo":
736 return super().unserialize_property(name, value)
738 GObject.signal_new("input-channel-order-changed", InputChannel,
739 GObject.SignalFlags.RUN_FIRST | GObject.SignalFlags.ACTION,
740 None, [GObject.TYPE_STRING, GObject.TYPE_STRING])
742 class OutputChannel(Channel):
743 _display_solo_buttons = False
745 _init_muted_channels = None
746 _init_solo_channels = None
747 _init_prefader_channels = None
749 channel_properties_dialog = None
751 def get_display_solo_buttons(self):
752 return self._display_solo_buttons
754 def set_display_solo_buttons(self, value):
755 self._display_solo_buttons = value
756 # notifying control groups
757 for inputchannel in self.app.channels:
758 inputchannel.update_control_group(self)
760 display_solo_buttons = property(get_display_solo_buttons, set_display_solo_buttons)
763 self.channel = self.mixer.add_output_channel(self.channel_name, self.stereo)
765 if self.channel == None:
766 raise Exception("Cannot create a channel")
770 if self.future_volume_midi_cc != None:
771 self.channel.volume_midi_cc = self.future_volume_midi_cc
772 if self.future_balance_midi_cc != None:
773 self.channel.balance_midi_cc = self.future_balance_midi_cc
774 if self.future_mute_midi_cc != None:
775 self.channel.mute_midi_cc = self.future_mute_midi_cc
776 self.channel.midi_scale = self.slider_scale.scale
778 self.on_volume_changed(self.slider_adjustment)
779 self.on_balance_changed(self.balance_adjustment)
781 entries = [Gtk.TargetEntry.new("OUTPUT_CHANNEL", Gtk.TargetFlags.SAME_APP, 0)]
782 self.label_name_event_box.drag_source_set(Gdk.ModifierType.BUTTON1_MASK, entries,
784 self.label_name_event_box.connect("drag-data-get", self.on_drag_data_get)
785 self.drag_dest_set(Gtk.DestDefaults.ALL, entries, Gdk.DragAction.MOVE)
786 self.connect_after("drag-data-received", self.on_drag_data_received)
788 if not hasattr(self, 'color'):
789 self.color = random_color()
790 set_background_color(self.label_name_event_box, self.css_name, self.color)
791 self.vbox.pack_start(self.label_name_event_box, True, True, 0)
793 frame.set_shadow_type(Gtk.ShadowType.IN)
794 frame.add(self.abspeak);
795 self.vbox.pack_start(frame, False, True, 0)
797 # hbox child at lower part
798 self.hbox = Gtk.HBox()
799 self.hbox.pack_start(self.slider, True, True, 0)
801 frame.set_shadow_type(Gtk.ShadowType.IN)
802 frame.add(self.meter);
803 self.hbox.pack_start(frame, True, True, 0)
805 frame.set_shadow_type(Gtk.ShadowType.IN)
806 frame.add(self.hbox);
807 self.pack_start(frame, True, True, 0)
809 self.volume_digits.set_width_chars(6)
810 self.pack_start(self.volume_digits, False, True, 0)
812 self.create_balance_widget()
814 self.mute = Gtk.ToggleButton()
815 self.mute.set_label("M")
816 self.mute.set_name("mute")
817 self.mute.set_active(self.channel.out_mute)
818 self.mute.connect("toggled", self.on_mute_toggled)
821 hbox.pack_start(self.mute, True, True, 0)
822 self.pack_start(hbox, False, False, 0)
824 self.monitor_button = Gtk.ToggleButton('MON')
825 self.monitor_button.connect('toggled', self.on_monitor_button_toggled)
826 self.pack_start(self.monitor_button, False, False, 0)
828 # add control groups to the input channels, and initialize them
830 for input_channel in self.app.channels:
831 ctlgroup = input_channel.add_control_group(self)
832 if self._init_muted_channels and input_channel.channel.name in self._init_muted_channels:
833 ctlgroup.mute.set_active(True)
834 if self._init_solo_channels and input_channel.channel.name in self._init_solo_channels:
835 ctlgroup.solo.set_active(True)
836 if self._init_prefader_channels and input_channel.channel.name in self._init_prefader_channels:
837 ctlgroup.prefader.set_active(True)
838 if not input_channel.wide:
841 self._init_muted_channels = None
842 self._init_solo_channels = None
843 self._init_prefader_channels = None
848 def on_drag_data_get(self, widget, drag_context, data, info, time):
849 channel = widget.get_parent().get_parent()
850 data.set(data.get_target(), 8, channel._channel_name.encode('utf-8'))
852 def on_drag_data_received(self, widget, drag_context, x, y, data, info, time):
853 source_name = data.get_data().decode('utf-8')
854 if source_name == self._channel_name:
856 self.emit("output-channel-order-changed", source_name, self._channel_name)
858 def on_channel_properties(self):
859 if not self.channel_properties_dialog:
860 self.channel_properties_dialog = OutputChannelPropertiesDialog(self, self.app)
861 self.channel_properties_dialog.show()
862 self.channel_properties_dialog.present()
864 def on_mute_toggled(self, button):
865 self.channel.out_mute = self.mute.get_active()
867 def midi_events_check(self):
868 if self.channel != None and self.channel.midi_in_got_events:
869 self.mute.set_active(self.channel.out_mute)
870 super().on_midi_event_received()
873 # remove control groups from input channels
874 for input_channel in self.app.channels:
875 input_channel.remove_control_group(self)
878 self.channel.remove()
882 def serialization_name(cls):
883 return 'output_channel'
885 def serialize(self, object_backend):
886 object_backend.add_property("name", self.channel_name)
888 object_backend.add_property("type", "stereo")
890 object_backend.add_property("type", "mono")
891 if self.display_solo_buttons:
892 object_backend.add_property("solo_buttons", "true")
895 prefader_in_channels = []
896 for input_channel in self.app.channels:
897 if self.channel.is_muted(input_channel.channel):
898 muted_channels.append(input_channel)
899 if self.channel.is_solo(input_channel.channel):
900 solo_channels.append(input_channel)
901 if self.channel.is_in_prefader(input_channel.channel):
902 prefader_in_channels.append(input_channel)
904 object_backend.add_property('muted_channels', '|'.join([x.channel.name for x in muted_channels]))
906 object_backend.add_property('solo_channels', '|'.join([x.channel.name for x in solo_channels]))
907 if prefader_in_channels:
908 object_backend.add_property('prefader_channels', '|'.join([x.channel.name for x in prefader_in_channels]))
909 object_backend.add_property("color", self.color.to_string())
910 super().serialize(object_backend)
912 def unserialize_property(self, name, value):
914 self.channel_name = str(value)
917 if value == "stereo":
923 if name == "solo_buttons":
925 self.display_solo_buttons = True
927 if name == 'muted_channels':
928 self._init_muted_channels = value.split('|')
930 if name == 'solo_channels':
931 self._init_solo_channels = value.split('|')
933 if name == 'prefader_channels':
934 self._init_prefader_channels = value.split('|')
941 return super().unserialize_property(name, value)
943 class ChannelPropertiesDialog(Gtk.Dialog):
946 def __init__(self, parent, app):
947 self.channel = parent
949 self.mixer = self.channel.mixer
950 Gtk.Dialog.__init__(self, 'Channel "%s" Properties' % self.channel.channel_name, app.window)
951 self.set_default_size(365, -1)
953 self.add_button(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL)
954 self.ok_button = self.add_button(Gtk.STOCK_APPLY, Gtk.ResponseType.APPLY)
955 self.set_default_response(Gtk.ResponseType.APPLY);
960 self.connect('response', self.on_response_cb)
961 self.connect('delete-event', self.on_response_cb)
963 def create_frame(self, label, child):
966 frame.set_border_width(3)
967 #frame.set_shadow_type(Gtk.ShadowType.NONE)
968 frame.get_label_widget().set_markup('<b>%s</b>' % label)
970 alignment = Gtk.Alignment.new(0, 0, 1, 1)
971 alignment.set_padding(0, 0, 12, 0)
981 self.properties_table = table = Gtk.Table(4, 3, False)
982 vbox.pack_start(self.create_frame('Properties', table), True, True, 0)
983 table.set_row_spacings(5)
984 table.set_col_spacings(5)
986 name_label = Gtk.Label.new_with_mnemonic('_Name')
987 table.attach(name_label, 0, 1, 0, 1)
988 self.entry_name = Gtk.Entry()
989 self.entry_name.set_activates_default(True)
990 self.entry_name.connect('changed', self.on_entry_name_changed)
991 name_label.set_mnemonic_widget(self.entry_name)
992 table.attach(self.entry_name, 1, 2, 0, 1)
994 table.attach(Gtk.Label(label='Mode'), 0, 1, 1, 2)
995 self.mode_hbox = Gtk.HBox()
996 table.attach(self.mode_hbox, 1, 2, 1, 2)
997 self.mono = Gtk.RadioButton.new_with_mnemonic(None, '_Mono')
998 self.stereo = Gtk.RadioButton.new_with_mnemonic_from_widget(self.mono, '_Stereo')
999 self.mode_hbox.pack_start(self.mono, True, True, 0)
1000 self.mode_hbox.pack_start(self.stereo, True, True, 0)
1002 table = Gtk.Table(2, 3, False)
1003 vbox.pack_start(self.create_frame('MIDI Control Changes', table), True, True, 0)
1004 table.set_row_spacings(5)
1005 table.set_col_spacings(5)
1007 cc_tooltip = "{} MIDI Control Change number (0-127, set to -1 to assign next free CC #)"
1008 volume_label = Gtk.Label.new_with_mnemonic('_Volume')
1009 table.attach(volume_label, 0, 1, 0, 1)
1010 self.entry_volume_cc = Gtk.SpinButton.new_with_range(-1, 127, 1)
1011 self.entry_volume_cc.set_tooltip_text(cc_tooltip.format("Volume"))
1012 volume_label.set_mnemonic_widget(self.entry_volume_cc)
1013 table.attach(self.entry_volume_cc, 1, 2, 0, 1)
1014 self.button_sense_midi_volume = Gtk.Button('Learn')
1015 self.button_sense_midi_volume.connect('clicked',
1016 self.on_sense_midi_volume_clicked)
1017 table.attach(self.button_sense_midi_volume, 2, 3, 0, 1)
1019 balance_label = Gtk.Label.new_with_mnemonic('_Balance')
1020 table.attach(balance_label, 0, 1, 1, 2)
1021 self.entry_balance_cc = Gtk.SpinButton.new_with_range(-1, 127, 1)
1022 self.entry_balance_cc.set_tooltip_text(cc_tooltip.format("Balance"))
1023 balance_label.set_mnemonic_widget(self.entry_balance_cc)
1024 table.attach(self.entry_balance_cc, 1, 2, 1, 2)
1025 self.button_sense_midi_balance = Gtk.Button('Learn')
1026 self.button_sense_midi_balance.connect('clicked',
1027 self.on_sense_midi_balance_clicked)
1028 table.attach(self.button_sense_midi_balance, 2, 3, 1, 2)
1030 mute_label = Gtk.Label.new_with_mnemonic('M_ute')
1031 table.attach(mute_label, 0, 1, 2, 3)
1032 self.entry_mute_cc = Gtk.SpinButton.new_with_range(-1, 127, 1)
1033 self.entry_mute_cc.set_tooltip_text(cc_tooltip.format("Mute"))
1034 mute_label.set_mnemonic_widget(self.entry_mute_cc)
1035 table.attach(self.entry_mute_cc, 1, 2, 2, 3)
1036 self.button_sense_midi_mute = Gtk.Button('Learn')
1037 self.button_sense_midi_mute.connect('clicked',
1038 self.on_sense_midi_mute_clicked)
1039 table.attach(self.button_sense_midi_mute, 2, 3, 2, 3)
1041 if (isinstance(self, NewChannelDialog) or (self.channel and
1042 isinstance(self.channel, InputChannel))):
1043 solo_label = Gtk.Label.new_with_mnemonic('S_olo')
1044 table.attach(solo_label, 0, 1, 3, 4)
1045 self.entry_solo_cc = Gtk.SpinButton.new_with_range(-1, 127, 1)
1046 self.entry_solo_cc.set_tooltip_text(cc_tooltip.format("Solo"))
1047 solo_label.set_mnemonic_widget(self.entry_solo_cc)
1048 table.attach(self.entry_solo_cc, 1, 2, 3, 4)
1049 self.button_sense_midi_solo = Gtk.Button('Learn')
1050 self.button_sense_midi_solo.connect('clicked',
1051 self.on_sense_midi_solo_clicked)
1052 table.attach(self.button_sense_midi_solo, 2, 3, 3, 4)
1054 self.vbox.show_all()
1057 self.entry_name.set_text(self.channel.channel_name)
1058 if self.channel.channel.is_stereo:
1059 self.stereo.set_active(True)
1061 self.mono.set_active(True)
1062 self.mode_hbox.set_sensitive(False)
1063 self.entry_volume_cc.set_value(self.channel.channel.volume_midi_cc)
1064 self.entry_balance_cc.set_value(self.channel.channel.balance_midi_cc)
1065 self.entry_mute_cc.set_value(self.channel.channel.mute_midi_cc)
1066 if (self.channel and isinstance(self.channel, InputChannel)):
1067 self.entry_solo_cc.set_value(self.channel.channel.solo_midi_cc)
1069 def sense_popup_dialog(self, entry):
1070 window = Gtk.Window.new(Gtk.WindowType.TOPLEVEL)
1071 window.set_destroy_with_parent(True)
1072 window.set_transient_for(self)
1073 window.set_decorated(False)
1074 window.set_modal(True)
1075 window.set_position(Gtk.WindowPosition.CENTER_ON_PARENT)
1076 window.set_border_width(10)
1081 vbox.pack_start(Gtk.Label(label='Please move the MIDI control you want to use for this function.'), True, True, 0)
1082 timeout_label = Gtk.Label(label='This window will close in 5 seconds')
1083 vbox.pack_start(timeout_label, True, True, 0)
1084 def close_sense_timeout(window, entry):
1086 timeout_label.set_text('This window will close in %d seconds.' % window.timeout)
1087 if window.timeout == 0:
1089 entry.set_value(self.mixer.last_midi_channel)
1093 GObject.timeout_add_seconds(1, close_sense_timeout, window, entry)
1095 def on_sense_midi_volume_clicked(self, *args):
1096 self.mixer.last_midi_channel = int(self.entry_volume_cc.get_value())
1097 self.sense_popup_dialog(self.entry_volume_cc)
1099 def on_sense_midi_balance_clicked(self, *args):
1100 self.mixer.last_midi_channel = int(self.entry_balance_cc.get_value())
1101 self.sense_popup_dialog(self.entry_balance_cc)
1103 def on_sense_midi_mute_clicked(self, *args):
1104 self.mixer.last_midi_channel = int(self.entry_mute_cc.get_value())
1105 self.sense_popup_dialog(self.entry_mute_cc)
1107 def on_sense_midi_solo_clicked(self, *args):
1108 self.mixer.last_midi_channel = int(self.entry_solo_cc.get_value())
1109 self.sense_popup_dialog(self.entry_solo_cc)
1111 def on_response_cb(self, dlg, response_id, *args):
1112 self.channel.channel_properties_dialog = None
1113 name = self.entry_name.get_text()
1114 if response_id == Gtk.ResponseType.APPLY:
1115 if name != self.channel.channel_name:
1116 self.channel.channel_name = name
1117 for control in ('volume', 'balance', 'mute', 'solo'):
1118 widget = getattr(self, 'entry_{}_cc'.format(control), None)
1119 if widget is not None:
1120 value = int(widget.get_value())
1122 setattr(self.channel.channel, '{}_midi_cc'.format(control), value)
1125 def on_entry_name_changed(self, entry):
1127 if len(entry.get_text()):
1128 if self.channel and self.channel.channel.name == entry.get_text():
1130 elif entry.get_text() not in [x.channel.name for x in self.app.channels] + \
1131 [x.channel.name for x in self.app.output_channels] + ['MAIN']:
1133 self.ok_button.set_sensitive(sensitive)
1135 GObject.signal_new("output-channel-order-changed", OutputChannel,
1136 GObject.SignalFlags.RUN_FIRST | GObject.SignalFlags.ACTION,
1137 None, [GObject.TYPE_STRING, GObject.TYPE_STRING])
1140 class NewChannelDialog(ChannelPropertiesDialog):
1141 def create_ui(self):
1142 ChannelPropertiesDialog.create_ui(self)
1143 self.add_initial_value_radio()
1144 self.vbox.show_all()
1146 def add_initial_value_radio(self):
1147 self.properties_table.attach(Gtk.Label(label='Value'), 0, 1, 2, 3)
1148 self.value_hbox = Gtk.HBox()
1149 self.properties_table.attach(self.value_hbox, 1, 2, 2, 3)
1150 self.minus_inf = Gtk.RadioButton.new_with_mnemonic(None, '-_Inf')
1151 self.zero_dB = Gtk.RadioButton.new_with_mnemonic_from_widget(self.minus_inf, '_0dB')
1152 self.value_hbox.pack_start(self.minus_inf, True, True, 0)
1153 self.value_hbox.pack_start(self.zero_dB, True, True, 0)
1156 class NewInputChannelDialog(NewChannelDialog):
1157 def __init__(self, app):
1158 Gtk.Dialog.__init__(self, 'New Input Channel', app.window)
1159 self.set_default_size(365, -1)
1160 self.mixer = app.mixer
1164 self.add_button(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL)
1165 self.ok_button = self.add_button(Gtk.STOCK_ADD, Gtk.ResponseType.OK)
1166 self.ok_button.set_sensitive(False)
1167 self.set_default_response(Gtk.ResponseType.OK);
1169 def fill_ui(self, **values):
1170 self.entry_name.set_text(values.get('name', ''))
1171 # don't set MIDI CCs to previously used values, because they
1172 # would overwrite existing mappings, if accepted.
1173 self.entry_volume_cc.set_value(-1)
1174 self.entry_balance_cc.set_value(-1)
1175 self.entry_mute_cc.set_value(-1)
1176 self.entry_solo_cc.set_value(-1)
1177 self.stereo.set_active(values.get('stereo', True))
1178 self.minus_inf.set_active(values.get('value', False))
1179 self.entry_name.grab_focus()
1181 def get_result(self):
1183 'name': self.entry_name.get_text(),
1184 'stereo': self.stereo.get_active(),
1185 'volume_cc': int(self.entry_volume_cc.get_value()),
1186 'balance_cc': int(self.entry_balance_cc.get_value()),
1187 'mute_cc': int(self.entry_mute_cc.get_value()),
1188 'solo_cc': int(self.entry_solo_cc.get_value()),
1189 'value': self.minus_inf.get_active()
1193 class OutputChannelPropertiesDialog(ChannelPropertiesDialog):
1194 def create_ui(self):
1195 ChannelPropertiesDialog.create_ui(self)
1197 table = self.properties_table
1198 color_label = Gtk.Label.new_with_mnemonic('_Color')
1199 table.attach(color_label, 0, 1, 4, 5)
1200 self.color_chooser_button = Gtk.ColorButton()
1201 self.color_chooser_button.set_use_alpha(True)
1202 self.color_chooser_button.set_rgba(Gdk.RGBA(0, 0, 0, 0))
1203 color_label.set_mnemonic_widget(self.color_chooser_button)
1204 table.attach(self.color_chooser_button, 1, 2, 4, 5)
1207 self.vbox.pack_start(self.create_frame('Input Channels', vbox), True, True, 0)
1209 self.display_solo_buttons = Gtk.CheckButton.new_with_mnemonic('_Display solo buttons')
1210 vbox.pack_start(self.display_solo_buttons, True, True, 0)
1212 self.vbox.show_all()
1215 ChannelPropertiesDialog.fill_ui(self)
1216 self.display_solo_buttons.set_active(self.channel.display_solo_buttons)
1217 self.color_chooser_button.set_rgba(self.channel.color)
1219 def on_response_cb(self, dlg, response_id, *args):
1220 ChannelPropertiesDialog.on_response_cb(self, dlg, response_id, *args)
1221 if response_id == Gtk.ResponseType.APPLY:
1222 self.channel.display_solo_buttons = self.display_solo_buttons.get_active()
1223 self.channel.set_color(self.color_chooser_button.get_rgba())
1224 for inputchannel in self.app.channels:
1225 inputchannel.update_control_group(self.channel)
1228 class NewOutputChannelDialog(NewChannelDialog, OutputChannelPropertiesDialog):
1229 def __init__(self, app):
1230 Gtk.Dialog.__init__(self, 'New Output Channel', app.window)
1231 self.mixer = app.mixer
1233 OutputChannelPropertiesDialog.create_ui(self)
1234 self.add_initial_value_radio()
1235 self.vbox.show_all()
1236 self.set_default_size(365, -1)
1238 # TODO: disable mode for output channels as mono output channels may
1239 # not be correctly handled yet.
1240 self.mode_hbox.set_sensitive(False)
1242 self.add_button(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL)
1243 self.ok_button = self.add_button(Gtk.STOCK_ADD, Gtk.ResponseType.OK)
1244 self.ok_button.set_sensitive(False)
1245 self.set_default_response(Gtk.ResponseType.OK);
1247 def fill_ui(self, **values):
1248 self.entry_name.set_text(values.get('name', ''))
1249 # don't set MIDI CCs to previously used values, because they
1250 # would overwrite existing mappings, if accepted.
1251 self.entry_volume_cc.set_value(-1)
1252 self.entry_balance_cc.set_value(-1)
1253 self.entry_mute_cc.set_value(-1)
1254 self.stereo.set_active(values.get('stereo', True))
1255 self.minus_inf.set_active(values.get('value', False))
1256 self.color_chooser_button.set_rgba(values.get('color', Gdk.RGBA(0, 0, 0, 0)))
1257 self.display_solo_buttons.set_active(values.get('display_solo_buttons', False))
1258 self.entry_name.grab_focus()
1260 def get_result(self):
1262 'name': self.entry_name.get_text(),
1263 'stereo': self.stereo.get_active(),
1264 'volume_cc': int(self.entry_volume_cc.get_value()),
1265 'balance_cc': int(self.entry_balance_cc.get_value()),
1266 'mute_cc': int(self.entry_mute_cc.get_value()),
1267 'display_solo_buttons': self.display_solo_buttons.get_active(),
1268 'color': self.color_chooser_button.get_rgba(),
1269 'value': self.minus_inf.get_active()
1273 class ControlGroup(Gtk.Alignment):
1274 def __init__(self, output_channel, input_channel):
1275 GObject.GObject.__init__(self)
1276 self.set(0.5, 0.5, 1, 1)
1277 self.output_channel = output_channel
1278 self.input_channel = input_channel
1279 self.app = input_channel.app
1281 self.hbox = Gtk.HBox()
1282 self.vbox = Gtk.VBox()
1284 self.buttons_box = Gtk.Box(False, button_padding)
1286 set_background_color(self.vbox, output_channel.css_name, output_channel.color)
1288 self.vbox.pack_start(self.hbox, True, True, button_padding)
1295 .control_group #label,
1296 .control_group #mute,
1297 .control_group #pre_fader,
1298 .control_group #solo {
1304 css_provider = Gtk.CssProvider()
1305 css_provider.load_from_data(css)
1306 context = Gtk.StyleContext()
1307 screen = Gdk.Screen.get_default()
1308 context.add_provider_for_screen(screen, css_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)
1309 hbox_context = self.hbox.get_style_context()
1310 hbox_context.add_class('control_group')
1312 name = output_channel.channel.name
1313 self.label = Gtk.Label(name)
1314 self.label.set_name("label")
1315 self.label.set_max_width_chars(self.input_channel.label_chars_narrow)
1316 self.label.set_ellipsize(Pango.EllipsizeMode.MIDDLE)
1317 if len(name) > self.input_channel.label_chars_narrow:
1318 self.label.set_tooltip_text(name)
1319 self.hbox.pack_start(self.label, False, False, button_padding)
1320 self.hbox.pack_end(self.buttons_box, False, False, button_padding)
1321 mute = Gtk.ToggleButton()
1323 mute.set_name("mute")
1324 mute.set_tooltip_text("Mute output channel send")
1325 mute.connect("toggled", self.on_mute_toggled)
1327 solo = Gtk.ToggleButton()
1328 solo.set_name("solo")
1330 solo.set_tooltip_text("Solo output send")
1331 solo.connect("toggled", self.on_solo_toggled)
1333 pre = Gtk.ToggleButton("P")
1334 pre.set_name("pre_fader")
1335 pre.set_tooltip_text("Pre (on) / Post (off) fader send")
1336 pre.connect("toggled", self.on_prefader_toggled)
1338 self.buttons_box.pack_start(pre, True, True, button_padding)
1339 self.buttons_box.pack_start(mute, True, True, button_padding)
1340 if self.output_channel.display_solo_buttons:
1341 self.buttons_box.pack_start(solo, True, True, button_padding)
1344 if self.output_channel.display_solo_buttons:
1345 if not self.solo in self.buttons_box.get_children():
1346 self.buttons_box.pack_start(self.solo, True, True, button_padding)
1349 if self.solo in self.buttons_box.get_children():
1350 self.buttons_box.remove(self.solo)
1352 name = self.output_channel.channel.name
1353 self.label.set_text(name)
1354 if len(name) > self.input_channel.label_chars_narrow:
1355 self.label.set_tooltip_text(name)
1357 set_background_color(self.vbox, self.output_channel.css_name, self.output_channel.color)
1359 def on_mute_toggled(self, button):
1360 self.output_channel.channel.set_muted(self.input_channel.channel, button.get_active())
1361 self.app.update_monitor(self)
1363 def on_solo_toggled(self, button):
1364 self.output_channel.channel.set_solo(self.input_channel.channel, button.get_active())
1365 self.app.update_monitor(self)
1367 def on_prefader_toggled(self, button):
1368 self.output_channel.channel.set_in_prefader(self.input_channel.channel, button.get_active())
1371 self.hbox.remove(self.label)
1372 self.hbox.set_child_packing(self.buttons_box, True, True, button_padding, Gtk.PackType.END)
1375 self.hbox.pack_start(self.label, False, False, button_padding)
1376 self.hbox.set_child_packing(self.buttons_box, False, False, button_padding, Gtk.PackType.END)