#   preferences.py: Preferences window code for IDJC
#   Copyright (C) 2005-2020 Stephen Fairchild (s-fairchild@users.sourceforge.net)
#
#   This program is free software: you can redistribute it and/or modify
#   it under the terms of the GNU General Public License as published by
#   the Free Software Foundation, either version 2 of the License, or
#   (at your option) any later version.
#
#   This program is distributed in the hope that it will be useful,
#   but WITHOUT ANY WARRANTY; without even the implied warranty of
#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#   GNU General Public License for more details.
#
#   You should have received a copy of the GNU General Public License
#   along with this program in the file entitled COPYING.
#   If not, see <http://www.gnu.org/licenses/>.

import os
import shutil
import gettext
import itertools
import math
import re

import gi
from gi.repository import Gtk
from gi.repository import GdkPixbuf
from gi.repository import GLib
from gi.repository import Pango

from idjc import FGlobs, PGlobs
from . import licence_window
from . import songdb
from . import midicontrols
from .gtkstuff import WindowSizeTracker, DefaultEntry
from .gtkstuff import timeout_add, source_remove
from .prelims import ProfileManager
from .utils import PathStr
from .utils import cmp
from .tooltips import set_tip, MAIN_TIPS

__all__ = ['mixprefs', 'PanPresetChooser']


_ = gettext.translation(FGlobs.package_name, FGlobs.localedir,
                                                        fallback=True).gettext

def N_(text):
    return text


pm = ProfileManager()


class CSLEntry(Gtk.Entry):
    def cb_keypress(self, widget, event):
        if event.string:
            if len(event.string) > 1:
                return True
            if not event.string in "0123456789,":
                return True
        return False
    def __init__(self, max = 0):
        Gtk.Entry.__init__(self, max)
        self.connect("key-press-event", self.cb_keypress)


class InitialPlayerConfig(Gtk.Frame):
    def __init__(self, title, player, prefix):
        self.player = player
        Gtk.Frame.__init__(self)
        self.set_label(" %s " % title)
        vbox = Gtk.VBox()
        vbox.set_border_width(3)
        self.add(vbox)

        pl_label = Gtk.Label.new(_("Playlist Mode"))
        fade_label = Gtk.Label.new(_("Fade"))
        self.pl_mode = Gtk.ComboBoxText(model=player.pl_mode.get_model())
        self.fade = Gtk.ComboBoxText(model=player.pl_delay.get_model())

        for each in (self.pl_mode, self.fade):
            each.set_active(0)

        self.elapsed = Gtk.RadioButton.new_with_label(None, _("Track time elapsed"))
        self.remaining = Gtk.RadioButton.new_with_label_from_widget(self.elapsed, _("Track time remaining"))
        s1 = Gtk.HSeparator()
        self.to_stream = Gtk.CheckButton.new_with_label(_("Audio to stream"))
        self.to_dj = Gtk.CheckButton.new_with_label(_("Audio to DJ"))

        for each in (self.to_stream, self.to_dj):
            each.set_active(True)

        for each in (pl_label, self.pl_mode, fade_label, self.fade,
                self.elapsed, self.remaining, s1, self.to_stream, self.to_dj):
            vbox.pack_start(each, False)
        self.show_all()

        self.activedict = {
            prefix + "pl_mode": self.pl_mode,
            prefix + "fade": self.fade,
            prefix + "timeremaining": self.remaining,
            prefix + "tostream": self.to_stream,
            prefix + "todj": self.to_dj
        }

    def apply(self):
        p = self.player

        p.pl_mode.set_active(self.pl_mode.get_active())
        p.pl_delay.set_active(self.fade.get_active())
        p.stream.set_active(self.to_stream.get_active())
        p.listen.set_active(self.to_dj.get_active())
        if self.remaining.get_active():
            p.digiprogress_click()


class PanWidget(Gtk.Frame):
    _instances = []

    def __init__(self, title, commandname):
        self._instances.append(self)

        Gtk.Frame.__init__(self)
        self.modes = (1, 2, 3)
        set_tip(self, _('Stereo panning is the selection of where an audio '
        'source sits from left to right within the stereo mix.\n\nThis control '
        'maintains constant audio power throughout its range of travel, giving '
        '-3dB attenuation in both audio channels at the half way point.\n\n'
        'If you require 0dB straight down the middle or require a stereo '
        'source remain as stereo then this feature should be turned off.\n\n'
        'Paired channels should be set to 100% left/right unless narrowing of '
        'the stereo field is the intention.'))
        self.valuesdict = {}
        self.activedict = {}
        self._source_id = None

        hbox = Gtk.HBox()
        self.pan_active = Gtk.CheckButton.new_with_label(title)
        self.activedict[commandname + "_pan_active"] = self.pan_active
        hbox.pack_start(self.pan_active, False, False, 0)
        self.pan_active.show()
        self.set_label_widget(hbox)
        hbox.show()

        panvbox = Gtk.VBox()
        panvbox.set_border_width(1)
        self.add(panvbox)

        panadj = Gtk.Adjustment(value=50.0, lower=0.0, upper=100.0, step_increment=1, page_increment=1, page_size=0)
        self.pan = Gtk.Scale(adjustment=panadj)
        self.pan.set_digits(0)
        self.pan.connect("format-value", self._cb_format_value)
        self.valuesdict[commandname + "_pan"] = self.pan
        panvbox.pack_start(self.pan, False, padding=2)

        label = Gtk.Label.new(_('Presets'))
        label.set_xalign(0.0)
        label.set_yalign(0.5)
        label.set_margin_start(3)
        label.set_margin_end(3)
        label.set_margin_top(3)
        label.set_margin_bottom(3)
        panvbox.pack_start(label, False)

        self._presets = []
        for i in range(PGlobs.num_panpresets):
            preadj = Gtk.Adjustment(value=50.0, lower=0.0, upper=100.0, step_increment=1, page_increment=1, page_size=0)
            preset = Gtk.Scale(adjustment=preadj)
            preset.set_digits(0)
            preset.connect("format-value", self._cb_format_value)
            self.valuesdict[commandname + "_panpreset" + str(i)] = preset
            self._presets.append(preset)
            panvbox.pack_start(preset, False, padding=2)

    def load_preset(self, index):
        try:
            target = int(self._presets[index].get_value() + 0.5)
        except IndexError:
            print("Attempt made to load a non existent pan preset")
        else:
            if self._source_id:
                source_remove(self._source_id)

            self._source_id = timeout_add(5, self._timeout, target)

    def _timeout(self, target):
        current_value = int(self.pan.get_value() + 0.5)
        new_value = current_value + cmp(target, current_value)
        self.pan.set_value(new_value)

        return new_value != target

    @classmethod
    def load_presets(cls, _, index):
        for self in cls._instances:
            self.load_preset(index)

    def set_values(self, value):
        self.pan.set_value(value)
        for each in self._presets:
            each.set_value(value)

    def _cb_format_value(self, scale, value):
        if value == 50:
            return u"\u25C8"

        pc = str(abs(int(value) * 2 - 100))
        if value < 50:
            return u"\u25C4 %s%%" % pc

        return u"%s%% \u25BA" % pc


class PanPresetButton(Gtk.Button):
    def __init__(self, labeltext):
        self._labeltext = labeltext
        Gtk.Button.__init__(self)
        self._label = Gtk.Label.new(labeltext)
        self.add(self._label)

    def highlight(self):
        self._label.set_markup("<span foreground='red'>%s</span>" % self._labeltext)

    def unhighlight(self):
        self._label.set_text(self._labeltext)


class PanPresetChooser(Gtk.HBox):
    def __init__(self):
        Gtk.HBox.__init__(self)
        self.set_spacing(1)

        label = Gtk.Label.new(u"\u25C4")
        self.pack_start(label)
        label.show()

        self.buttons = []
        for i in range(PGlobs.num_panpresets):
            button = PanPresetButton(str(i + 1))
            self.pack_start(button, False)
            button.show()
            self.buttons.append(button)
            button.connect_data("clicked", PanWidget.load_presets, i)
            button.connect("clicked", self._cb_clicked)

        label = Gtk.Label.new(u"\u25BA")
        self.pack_start(label)
        label.show()

        set_tip(self, _('The pan preset selection buttons.\n\n'
        'In the stereo image at a click the DJ can be on the left and a guest '
        'on the right and when the guest is gone at a click the DJ can be '
        'central again.\n\n'
        'Note: preconfiguration of pan preset settings is required.'))

    def load_preset(self, index):
        try:
            button = self.buttons[index]
        except IndexError:
            pass
        else:
            button.clicked()

    def _cb_clicked(self, clicked_button):
        for button in self.buttons:
            if button is clicked_button:
                button.highlight()
            else:
                button.unhighlight()


class AGCControl(Gtk.Frame):
    mic_modes = (
        # TC: Microphone mode combobox text.
        N_('Deactivated'),
        # TC: Microphone mode combobox text.
        N_('Basic input'),
        # TC: Microphone mode combobox text.
        N_('Processed input'),
        # TC: Microphone mode combobox text.
        N_('Partnered with channel %s'))


    def sendnewstats(self, widget, wname):
        if wname != NotImplemented:
            if isinstance(widget, (Gtk.SpinButton, Gtk.Scale)):
                value = widget.get_value()
            if isinstance(widget, (Gtk.ToggleButton, Gtk.ComboBox)):
                value = int(widget.get_active())
            stringtosend = "INDX=%d\nAGCP=%s=%s\nACTN=%s\nend\n" % (
                                self.index, wname, str(value), "mic_control")
            self.approot.mixer_write(stringtosend)

    def set_partner(self, partner):
        self.partner = partner
        self.mode.set_cell_data_func(self.mode_cell,
                                        self.mode_cell_data_func, partner.mode)

    def mode_cell_data_func(self, celllayout, cell, model, iter, opposite):
        index = model.get_path(iter)[0]
        oindex = opposite.get_active()
        cell.props.sensitive = not (((index == 0 or index == 3) and oindex == 3)
                                                or (index == 3 and oindex == 0))
        trans = _(model.get_value(iter, 0))
        if index == 3:
            cell.props.text = trans % self.partner.ui_name
        else:
            cell.props.text = trans

    def numline(self, label_text, wname, initial=0, mini=0, maxi=0, step=0,
                                                            digits=0, adj=None):
        hbox = Gtk.HBox()
        label = Gtk.Label.new(label_text)
        if not adj:
            adj = Gtk.Adjustment(value=initial, lower=mini, upper=maxi, step_increment=step)
        sb = Gtk.SpinButton.new(adj, 0, digits)
        sb.connect("value-changed", self.sendnewstats, wname)
        sb.emit("value-changed")
        hbox.pack_start(label, False, False, 0)
        hbox.pack_end(sb, False, False, 0)
        hbox.show_all()
        self.valuesdict[self.commandname + "_" + wname] = sb
        self.fixups.append(lambda: sb.emit("value-changed"))
        return hbox

    def frame(self, label, container):
        frame = Gtk.Frame.new(label)
        container.pack_start(frame, False, False, 0)
        frame.show()
        ivbox = Gtk.VBox()
        ivbox.set_border_width(3)
        frame.add(ivbox)
        ivbox.show()
        return ivbox

    def widget_frame(self, widget, container, tip, modes):
        frame = Gtk.Frame()
        frame.modes = modes
        set_tip(frame, tip)
        frame.set_label_widget(widget)
        container.pack_start(frame, False, False, 0)
        frame.show()
        ivbox = Gtk.VBox()
        ivbox.set_border_width(3)
        frame.add(ivbox)
        ivbox.show()
        return ivbox

    def toggle_frame(self, label_text, wname, container):
        frame = Gtk.Frame()
        cb = Gtk.CheckButton.new_with_label(label_text)
        cb.connect("toggled", self.sendnewstats, wname)
        cb.emit("toggled")
        cbb = Gtk.HBox()
        cbb.pack_start(cb, True, False, 2)
        cb.show()
        frame.set_label_widget(cbb)
        cbb.show()
        container.pack_start(frame, False, False, 0)
        frame.show()
        ivbox = Gtk.VBox()
        ivbox.set_border_width(3)
        frame.add(ivbox)
        ivbox.show()
        self.activedict[self.commandname + "_" + wname] = cb
        self.fixups.append(lambda: cb.emit("toggled"))
        return ivbox

    def check(self, label_text, wname, save=True):
        cb = Gtk.CheckButton.new_with_label(label_text)
        cb.connect("toggled", self.sendnewstats, wname)
        cb.emit("toggled")
        cb.show()
        if save:
            self.activedict[self.commandname + "_" + wname] = cb
        self.fixups.append(lambda: cb.emit("toggled"))
        return cb

    def cb_open(self, widget):
        active = widget.get_active()
        self.meter.set_led(active)
        if Gtk.main_level():
            self.approot.channelstate_changed(self.index, active)

    def cb_mode(self, combobox):
        mode = combobox.get_active()

        # Show pertinent features for each mode.
        def showhide(widget):
            try:
                modes = widget.modes
            except:
                pass
            else:
                if mode in modes:
                    widget.show()
                else:
                    widget.hide()
        self.vbox.foreach(showhide)

        # Meter sensitivity. Deactivated => insensitive.
        sens = mode != 0
        self.meter.set_sensitive(sens)
        if not sens:
             self.open.set_active(False)
        if mode == 3:
            #self.partner.openaction.connect_proxy(self.open)
            self.open.set_related_action(self.partner.openaction)
        else:
            #self.openaction.connect_proxy(self.open)
            self.open.set_related_action(self.openaction)
            self.open.set_sensitive(self.no_front_panel_opener.get_active())

    def __init__(self, approot, ui_name, commandname, index):
        self.approot = approot
        self.ui_name = ui_name
        self.meter = approot.mic_meters[int(ui_name) - 1]
        self.meter.agc = self
        self.commandname = commandname
        self.index = index
        self.valuesdict = {}
        self.activedict = {}
        self.textdict = {}
        self.fixups = []
        Gtk.Frame.__init__(self)
        hbox = Gtk.HBox()
        hbox.set_spacing(3)

        label = Gtk.Label.new('<span weight="600">' + ui_name + "</span>")
        label.set_use_markup(True)
        hbox.pack_start(label, False)
        label.show()

        self.alt_name = Gtk.Entry()
        set_tip(self.alt_name, _('A label so you may describe briefly the '
                                                'role of this audio channel.'))
        self.textdict[self.commandname + "_alt_name"] = self.alt_name
        hbox.pack_start(self.alt_name, True, True)
        self.alt_name.show()
        hbox.show()

        self.set_label_widget(hbox)
        hbox.show()
        self.set_label_align(0.5, 0.5)
        self.set_border_width(3)
        self.vbox = Gtk.VBox()
        self.vbox.set_spacing(2)
        self.vbox.set_border_width(3)
        self.add(self.vbox)
        self.vbox.show()

        mode_liststore = Gtk.ListStore(str)
        self.mode = Gtk.ComboBox.new_with_model(mode_liststore)
        self.mode_cell = Gtk.CellRendererText()
        self.mode.pack_start(self.mode_cell, True)
        self.mode.add_attribute(self.mode_cell, "text", 0)
        self.fixups.append(lambda: self.mode.emit("changed"))

        self.vbox.pack_start(self.mode, False, False)

        for each in self.mic_modes:
            mode_liststore.append((each, ))
        self.mode.connect("changed", self.sendnewstats, "mode")
        self.mode.connect("changed", self.cb_mode)
        self.activedict[self.commandname + "_mode"] = self.mode
        self.mode.show()
        set_tip(self.mode, _('The signal processing mode.'))

        hbox = Gtk.HBox()
        # TC: A frame heading. The channel opener is selected within.
        label = Gtk.Label.new(_('Channel Opener'))
        hbox.pack_start(label, False, False, 3)
        label.show()
        ivbox = self.widget_frame(hbox, self.vbox, _('This controls the '
            'allocation of front panel open/unmute buttons. Having one button '
            'control multiple microphones can save time.'), (1, 2))
        hbox.show()

        hbox = Gtk.HBox()
        # TC: Spinbutton label text.
        self.group = Gtk.RadioButton.new_with_label(None, _('Main Panel Button'))
        self.activedict[self.commandname + "_group"] = self.group
        hbox.pack_start(self.group, False, False, 0)
        self.group.show()
        ivbox.pack_start(hbox, False, False)
        hbox.show()

        self.groups_adj = Gtk.Adjustment(value=1.0, lower=1.0, upper=PGlobs.num_micpairs * 2, step_increment=1.0)
        self.valuesdict[self.commandname + "_groupnum"] = self.groups_adj
        groups_spin = Gtk.SpinButton.new(self.groups_adj, 0.0, 0)
        hbox.pack_end(groups_spin, False)
        groups_spin.show()

        hbox = Gtk.HBox()
        hbox.set_spacing(6)
        ivbox.pack_start(hbox, False)
        hbox.show()
        self.no_front_panel_opener = Gtk.RadioButton.new_with_label_from_widget(self.group, _("This:"))
        self.activedict[self.commandname + "_using_local_opener"] = \
                                                    self.no_front_panel_opener
        self.no_front_panel_opener.connect("toggled",
                            lambda w: self.open.set_sensitive(w.get_active()))
        hbox.pack_start(self.no_front_panel_opener, False)
        self.no_front_panel_opener.show()

        self.openaction = Gtk.ToggleAction(name=None, label=_('Closed'), tooltip=None, stock_id=None)
        self.openaction.connect("toggled", lambda w: w.set_label(_('Open')
                                            if w.get_active() else _('Closed')))

        self.open = Gtk.ToggleButton()
        self.open.connect("toggled", self.cb_open)
        self.open.connect("toggled", self.sendnewstats, "open")
        hbox.pack_start(self.open)
        self.open.show()
        #self.openaction.connect_proxy(self.open)
        self.open.set_related_action(self.openaction)
        self.open.emit("toggled")
        self.open.set_sensitive(False)
        self.fixups.append(lambda: self.open.emit("toggled"))

        self.pan = PanWidget(_('Stereo Panning'), commandname)
        self.pan.pan_active.connect("toggled", self.sendnewstats, "pan_active")
        self.fixups.append(lambda: self.pan.pan_active.emit("toggled"))
        self.pan.pan.connect("value-changed", self.sendnewstats, "pan")
        self.pan.pan.emit("value-changed")
        self.fixups.append(lambda: self.pan.pan.emit("value-changed"))
        self.valuesdict.update(self.pan.valuesdict)
        self.activedict.update(self.pan.activedict)

        self.vbox.pack_start(self.pan, False, False)
        self.pan.show_all()

        # TC: A set of controls that perform audio signal matching.
        pairedframe = Gtk.Frame.new(" %s " % _('Signal Matching'))
        set_tip(pairedframe, _('These controls are provided to obtain a decent '
                                        'match between the two microphones.'))
        pairedframe.modes = (3, )
        self.vbox.pack_start(pairedframe, False)
        pairedvbox = Gtk.VBox()
        pairedvbox.set_border_width(3)
        pairedframe.add(pairedvbox)
        pairedvbox.show()
        pairedmicgainadj = Gtk.Adjustment(value=0.0, lower=-20.0, upper=+20.0, step_increment=0.1, page_increment=2)
        pairedmicgain = self.numline(_('Relative Gain (dB)'), "pairedgain",
                                                digits=1, adj=pairedmicgainadj)
        pairedvbox.pack_start(pairedmicgain, False)
        pairedmicgain.show()
        # TC: Mic audio phase inversion control.
        pairedinvert = self.check(_('Invert Signal'), "pairedinvert")
        pairedvbox.pack_start(pairedinvert, False)
        pairedinvert.show()

        micgainadj = Gtk.Adjustment(value=0.0, lower=-20.0, upper=+30.0, step_increment=0.1, page_increment=2)
        invertaction = Gtk.ToggleAction(name="invert", label=_('Invert Signal'),
                    tooltip=_('Useful for when microphones are cancelling one another '
                                        'out, producing a hollow sound.'), stock_id=None)
        # TC: Control whether to mix microphone audio to the DJ mix.
        indjmixaction = Gtk.ToggleAction(name="indjmix", label=_("In The DJ's Mix"),
                        tooltip=_('Make the microphone audio audible in the DJ mix. '
                        'This may not always be desirable.'), stock_id=None)

        self.simple_box = Gtk.VBox()
        self.simple_box.set_spacing(2)
        self.vbox.pack_start(self.simple_box, False, False)
        self.simple_box.modes = (1, )

        ivbox = self.frame(" " + _('Basic Controls') + " ", self.simple_box)
        micgain = self.numline(_('Boost/Cut (dB)'), "gain",
                                                    digits=1, adj=micgainadj)
        ivbox.pack_start(micgain, False, False)

        invert_simple = self.check("", "invert")
        #invertaction.connect_proxy(invert_simple)
        invert_simple.set_related_action(invertaction)
        ivbox.pack_start(invert_simple, False, False)
        set_tip(invert_simple, _('Useful for when microphones are cancelling '
                                'one another out, producing a hollow sound.'))

        indjmix = self.check("", "indjmix")
        #indjmixaction.connect_proxy(indjmix)
        indjmix.set_related_action(indjmixaction)
        ivbox.pack_start(indjmix, False, False)
        set_tip(indjmix, _('Make the microphone audio audible in the DJ mix. '
                                        'This may not always be desirable.'))

        self.processed_box = Gtk.VBox()
        self.processed_box.modes = (2, )
        self.processed_box.set_spacing(2)
        self.vbox.pack_start(self.processed_box, False, False)

        ivbox = self.frame(" %s " % _('High Pass Filter'), self.processed_box)
        hpcutoff = self.numline(_('Cutoff Frequency'), "hpcutoff",
                                                    100.0, 30.0, 120.0, 1.0, 1)
        ivbox.pack_start(hpcutoff, False, False, 0)
        # TC: User can set the number of filter stages.
        hpstages = self.numline(_('Stages'), "hpstages", 4.0, 1.0, 4.0, 1.0, 0)
        ivbox.pack_start(hpstages, False, False, 0)
        set_tip(ivbox,
            _('Frequency in Hertz above which audio can pass to later stages. '
            'Use this feature to restrict low frequency sounds such as mains '
            'hum. Setting too high a level will make your voice sound thin.'))

        # TC: this is the treble control. HF = high frequency.
        ivbox = self.frame(" " + _('HF Detail') + " ", self.processed_box)
        hfmulti = self.numline(_('Effect'), "hfmulti", 0.0, 0.0, 9.0, 0.1, 1)
        ivbox.pack_start(hfmulti, False, False, 0)
        hfcutoff = self.numline(_('Cutoff Frequency'), "hfcutoff",
                                                2000.0, 900.0, 4000.0, 10.0, 0)
        ivbox.pack_start(hfcutoff, False, False, 0)
        set_tip(ivbox,
            _('You can use this to boost the amount of treble in the audio.'))

        # TC: this is the bass control. LF = low frequency.
        ivbox = self.frame(" " + _('LF Detail') + " ", self.processed_box)
        lfmulti = self.numline(_('Effect'), "lfmulti", 0.0, 0.0, 9.0, 0.1, 1)
        ivbox.pack_start(lfmulti, False, False, 0)
        lfcutoff = self.numline(_('Cutoff Frequency'), "lfcutoff",
                                                    150.0, 50.0, 400.0, 1.0, 0)
        ivbox.pack_start(lfcutoff, False, False, 0)
        set_tip(ivbox,
            _('You can use this to boost the amount of bass in the audio.'))

        # TC: lookahead brick wall limiter.
        ivbox = self.frame(" " + _('Limiter') + " ", self.processed_box)
        micgain = self.numline(_('Boost/Cut (dB)'), "gain",
                                                    digits=1, adj=micgainadj)
        ivbox.pack_start(micgain, False, False, 0)
        # TC: this is the peak signal limit.
        limit = self.numline(_('Upper Limit'), "limit", -3.0, -20.0, 0.0, 0.5, 1)
        ivbox.pack_start(limit, False, False, 0)
        set_tip(ivbox, _('A look-ahead brick-wall limiter. Audio signals are '
                                                'capped at the upper limit.'))

        ivbox = self.frame(" " + _('Noise Gate') + " ", self.processed_box)
        # TC: noise gate triggers at this level.
        ng_thresh = self.numline(_('Threshold'), "ngthresh",
                                                    -30.0, -62.0, -20.0, 1.0, 0)
        ivbox.pack_start(ng_thresh, False, False, 0)
        # TC: negative gain when the noise gate is active.
        ng_gain = self.numline(_('Gain'), "nggain", -6.0, -12.0, 0.0, 1.0, 0)
        ivbox.pack_start(ng_gain, False, False, 0)
        set_tip(ivbox, _("Reduce the unwanted quietest sounds and background "
                "noise which you don't want your listeners to hear with this."))

        ivbox = self.frame(" " + _('De-esser') + " ", self.processed_box)
        # TC: Bias has a numeric setting.
        ds_bias = self.numline(_('Bias'), "deessbias", 0.35, 0.1, 10.0, 0.05, 2)
        ivbox.pack_start(ds_bias, False, False, 0)
        # TC: The de-esser attenuation in ess-detected state.
        ds_gain = self.numline(_('Gain'), "deessgain", -4.5, -10.0, 0.0, 0.5, 1)
        ivbox.pack_start(ds_gain, False, False, 0)
        set_tip(ivbox, _('Reduce the S, T, and P sounds which microphones tend '
        'to exaggerate. Ideally the Bias control will be set low so that the '
        'de-esser is off when there is silence but is set high enough that '
        'mouse clicks are detected and suppressed.'))

        ivbox = self.toggle_frame(_('Ducker'), "duckenable", self.processed_box)
        duckrelease = self.numline(_('Release'), "duckrelease",
                                                400.0, 100.0, 999.0, 10.0, 0)
        ivbox.pack_start(duckrelease, False, False, 0)
        duckhold = self.numline(_('Hold'), "duckhold",
                                                350.0, 0.0, 999.0, 10.0, 0)
        ivbox.pack_start(duckhold, False, False, 0)
        set_tip(ivbox, _('The ducker automatically reduces the level of player '
                'audio when the DJ speaks. These settings allow you to adjust'
                ' the timings of that audio reduction.'))

        ivbox = self.frame(" " + _('Other options') + " ", self.processed_box)

        invert_complex = self.check("", NotImplemented, save=False)
        #invertaction.connect_proxy(invert_complex)
        invert_complex.set_related_action(invertaction)
        ivbox.pack_start(invert_complex, False, False)
        set_tip(invert_complex, _('Useful for when microphones are cancelling '
                                'one another out, producing a hollow sound.'))
        phaserotate = self.check(_('Phase Rotator'), "phaserotate")
        ivbox.pack_start(phaserotate, False, False, 0)
        set_tip(phaserotate,
        _('This feature processes the microphone audio so that it sounds more '
        'even. The effect is particularly noticable on male voices.'))
        indjmix = self.check("", NotImplemented, save=False)
        #indjmixaction.connect_proxy(indjmix)
        indjmix.set_related_action(indjmixaction)
        ivbox.pack_start(indjmix, False, False)
        set_tip(indjmix, _('Make the microphone audio audible in the DJ mix. '
                                        'This may not always be desirable.'))

        self.mode.set_active(0)
        indjmix.set_active(True)
        self.partner = None


class PortGrabPage(Gtk.Grid):
    def __init__(self, port_names, page_number):
        Gtk.Grid.__init__(self)
        self.set_column_homogeneous(True)
        self.page_number = page_number
        self.textdict = {}

        self.regex_toggle = Gtk.CheckButton(label=_('Enable regular expression matching'))
        self.regex_toggle.set_border_width(3)
        self.activedict = {f"port_grab_rexex_{page_number}": self.regex_toggle}
        self.attach(self.regex_toggle, 0, 0, 1, 1)

        for i, port_name in enumerate(port_names, start=1):
            label = Gtk.Label(label=port_name)
            label.set_ellipsize(Pango.EllipsizeMode.END)
            label.set_halign(Gtk.Align.START)
            label.set_margin_start(8)
            if i:
                label.set_margin_top(2)
            self.attach(label, 0, i*2 - 1, 1, 1)
            entry = Gtk.Entry()
            entry.set_margin_start(25)
            self.textdict[f"port_grab_{page_number}:{port_name}"] = entry
            self.attach(entry, 0, i * 2, 1, 1)
            entry.partner = port_name
            set_tip(entry, "\n".join((_('The name of the JACK port to attach to or a regular expression describing the same.'), "",
                           _('The connection is attempted when the main panel port grab button is pressed.'),
                           _('The remote application audio port will be disconnected from its original place(s).'),
                           _('The remote application audio port will be routed to the corresponing IDJC VoIP port.'))))


class PortGrabNotebook(Gtk.Notebook):
    def __init__(self, parent):
        Gtk.Notebook.__init__(self)
        self.port_grab_button = parent.port_grab_button
        self.jack = parent.jack
        self.set_border_width(6)
        self.get_value = self.get_current_page
        self.set_value = self.set_current_page
        voip_ports = parent.jack.voip_ports
        self.port_grab_button.connect("toggled", self.cb_port_grab, parent.jack)
        for i in range(1, 5):
            pgp = PortGrabPage(voip_ports, i)
            pgp.set_border_width(8)
            label = Gtk.Label(label=str(i))
            self.append_page(pgp, label)

    def cb_port_grab(self, button, jack):
        if button.get_active() == False:
            return

        def inner(page):
            client_id = os.environ["client_id"]
            if page.regex_toggle.get_active():
                inputs = jack.list_ports("inputs")
                outputs = jack.list_ports("outputs")
                for entry in page.textdict.values():
                    entry_text = entry.get_text().strip()
                    if entry_text:
                        partner = f"{client_id}:{entry.partner}"
                        try:
                            regex = re.compile(entry_text)
                        except Exception as e:
                            print(e)
                        else:
                            if partner in inputs:
                                matches = filter(regex.match, outputs)
                            elif partner in outputs:
                                matches = filter(regex.match, inputs)
                            else:
                                print(f"a strange error occurred: idjc voip port {partner} is missing")
                                matches = []

                            for match in matches:
                                jack.disconnect(match, "*")
                                jack.connect(match, partner)
            else:
                for entry in page.textdict.values():
                    entry_text = entry.get_text().strip()
                    if entry_text:
                        jack.disconnect(entry_text, "*")
                        jack.connect(entry_text, f"{client_id}:{entry.partner}")

        self.foreach(inner)

    def new_port(self, port_name):
        if not port_name or self.port_grab_button.get_active() == False:
            return

        def inner(page):
            client_id = os.environ["client_id"]
            if page.regex_toggle.get_active():
                for entry in page.textdict.values():
                    entry_text = entry.get_text().strip()
                    if entry_text:
                        partner = f"{client_id}:{entry.partner}"
                        try:
                            if re.match(entry_text, port_name):
                                self.jack.disconnect(port_name, "*")
                                self.jack.connect(port_name, f"{client_id}:{entry.partner}")
                        except re.error as e:
                            print(e)
            else:
                for entry in page.textdict.values():
                    entry_text = entry.get_text().strip()
                    if port_name == entry_text:
                        self.jack.disconnect(port_name, "*")
                        self.jack.connect(port_name, f"{client_id}:{entry.partner}")

        self.foreach(inner)


class mixprefs:
    def send_new_resampler_stats(self):
        self.parent.mixer_write("RSQT=%d\nACTN=resamplequality\nend\n"
                                                        % self.resample_quality)

    def cb_resample_quality(self, widget, data):
        if widget.get_active():
            self.resample_quality = data
            self.send_new_resampler_stats()

    def cb_dither(self, widget, data = None):
        if widget.get_active():
            string_to_send = "ACTN=dither\nend\n"
        else:
            string_to_send = "ACTN=dontdither\nend\n"
        self.parent.mixer_write(string_to_send)

    def cb_vol_changed(self, widget):
        self.parent.send_new_mixer_stats()

    def cb_restore_session(self, widget, data=None):
        state = not widget.get_active()
        for each in (self.lpconfig, self.rpconfig, self.misc_session_frame):
            each.set_sensitive(state)

    def delete_event(self, widget, event, data=None):
        self.window.hide()
        return True

    def save_resource_template(self):
        try:
            with open(pm.basedir / "config", "w") as f:
                f.write("[resource_count]\n")
                for name, widget in self.rrvaluesdict.items():
                    f.write(name + "=" + str(int(widget.get_value())) + "\n")
        except IOError:
            print("Error while writing out player defaults")

    def save_player_prefs(self, where=None):
        try:
            with open((where or pm.basedir) / "playerdefaults", "w") as f:
                for name, widget in self.activedict.items():
                    f.write(name + "=" + str(int(widget.get_active())) + "\n")
                for name, widget in self.valuesdict.items():
                    f.write(name + "=" + str(widget.get_value()) + "\n")
                for name, widget in self.textdict.items():
                    text = widget.get_text()
                    if text is not None:
                        f.write(name + "=" + text + "\n")
                    else:
                        f.write(name + "=\n")
        except IOError:
            print("Error while writing out player defaults")

    def load_player_prefs(self):
        songdb_active = False
        try:
            file = open(pm.basedir / "playerdefaults")

            while 1:
                line = file.readline()
                if line == "":
                    break
                if line.count("=") != 1:
                    continue
                line = line.split("=")
                key = line[0].strip()
                value = line[1][:-1].strip()
                if key in self.activedict:
                    if value == "True":
                        value = True
                    elif value == "False":
                        value = False
                    else:
                        value = int(value)
                    if key == "songdb_active":
                        songdb_active = value
                    else:
                        self.activedict[key].set_active(value)
                elif key in self.valuesdict:
                    self.valuesdict[key].set_value(float(value))
                elif key in self.textdict:
                    self.textdict[key].set_text(value)
            file.close()
        except IOError:
            print("Failed to read playerdefaults file")
        if songdb_active:
            self.activedict["songdb_active"].set_active(songdb_active)
        self.parent.send_new_mixer_stats()

    def apply_player_prefs(self):
        for each in (self.lpconfig, self.rpconfig):
            each.apply()

        if self.startmini.get_active():
            self.mini.clicked()

        if self.tracks_played.get_active():
            self.parent.history_expander.set_expanded(True)
            self.parent.history_vbox.show()
        if self.stream_mon.get_active():
            self.parent.listen_stream.set_active(True)

    def callback(self, widget, data):
        parent = self.parent
        if data == "basic streamer":
            if parent.feature_set.get_active():
                parent.feature_set.set_active(False)
        if data == "fully featured":
            if not parent.feature_set.get_active():
                parent.feature_set.set_active(True)
        if data == "enhanced-crossfader":
            if widget.get_active():
                parent.listen.show()
                parent.passleft.show()
                parent.passright.show()
                parent.passspeed.show()
                parent.passbutton.show()
            else:
                parent.listen.hide()
                parent.passleft.hide()
                parent.passright.hide()
                parent.passspeed.hide()
                parent.passbutton.hide()
                parent.listen.set_active(False)
        if data == "bigger box":
            if widget.get_active():
                self.parent.player_left.digiprogress.set_width_chars(7)
                self.parent.player_right.digiprogress.set_width_chars(7)
            else:
                self.parent.player_left.digiprogress.set_width_chars(6)
                self.parent.player_right.digiprogress.set_width_chars(6)
        if data == "tooltips":
            if widget.get_active():
                MAIN_TIPS.enable()
            else:
                MAIN_TIPS.disable()

    def cb_history_scale(self, widget):
        # Reattach with a different height.
        self.parent.grid1.remove(self.parent.history_vbox)
        self.parent.grid1.attach(self.parent.history_vbox, 0, 16, 1, int(widget.get_value()))

    def cb_history_scale2(self, *args):
        self.parent.history_expander.set_expanded(True)

    def cb_mic_boost(self, widget):
        self.parent.send_new_mixer_stats()

    def cb_pbspeed(self, widget):
        if widget.get_active():
            self.parent.player_left.pbspeedbar.set_value(64.0)
            self.parent.player_right.pbspeedbar.set_value(64.0)
            self.parent.player_left.pbspeedbox.show()
            self.parent.player_right.pbspeedbox.show()
            self.parent.background.player.pbspeedbar.set_value(64.0)
            self.parent.background.player.pbspeedbox.show()
        else:
            self.parent.player_left.pbspeedbox.hide()
            self.parent.player_right.pbspeedbox.hide()
            self.parent.background.player.pbspeedbox.hide()
        self.parent.send_new_mixer_stats()

    def cb_dual_volume(self, widget):
        if widget.get_active():
            self.parent.deck2adj.set_value(self.parent.deckadj.get_value())
            self.parent.deck2vol.show()
            set_tip(self.parent.deckvol,
                            _('The volume control for the left music player.'))
        else:
            if self.parent.player_left.is_playing ^ \
                                            self.parent.player_right.is_playing:
                if self.parent.player_left.is_playing:
                    self.parent.deck2adj.set_value(
                                            self.parent.deckadj.get_value())
                else:
                    self.parent.deckadj.set_value(
                                            self.parent.deck2adj.get_value())
            else:
                halfdelta = (self.parent.deck2adj.get_value() - \
                                            self.parent.deckadj.get_value()) / 2
                self.parent.deck2adj.props.value -= halfdelta
                self.parent.deckadj.props.value += halfdelta

            self.parent.deck2vol.hide()
            set_tip(self.parent.deckvol,
                        _('The volume control shared by both music players.'))

    def cb_rg_indicate(self, widget):
        show = widget.get_active()
        for each in (self.parent.player_left, self.parent.player_right,
                                                self.parent.background.player):
            each.show_replaygain_markers(show)

    def cb_realize(self, window):
        self.wst.apply()

    def show_about(self):
        self.notebook.set_current_page(self.notebook.page_num(self.aboutvbox))
        self.window.present()

    def mic_controls_backend_update(self):
        """Send mic preferences to the backend.

        This needs to be called whenever the backend is restarted.
        """
        for mic in self.mic_controls:
            for fixup in mic.fixups:
                fixup()

    def voip_pan_backend_update(self, widget=None):
        widget = self.voip_pan
        stringtosend = "VPAN=%d\nACTN=voippan\nend\n" % (widget.pan.get_value()
                                    if widget.pan_active.get_active() else -1)
        self.parent.mixer_write(stringtosend)

    def __init__(self, parent):
        self.parent = parent
        self.parent.prefs_window = self
        self.window = Gtk.Window()
        self.window.set_size_request(-1, 480)
        self.window.connect("realize", self.cb_realize)
        self.parent.window_group.add_window(self.window)
        # TC: preferences window title.
        self.window.set_title(_('IDJC Preferences') + pm.title_extra)
        self.window.set_resizable(True)
        self.window.connect("delete_event",self.delete_event)
        self.window.set_destroy_with_parent(True)
        self.notebook = Gtk.Notebook()
        self.window.add(self.notebook)
        self.wst = WindowSizeTracker(self.window)

        # General tab

        generalwindow = Gtk.ScrolledWindow()
        generalwindow.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC)
        outervbox = Gtk.VBox()
        outervbox.set_margin_end(10)
        outervbox.set_spacing(3)
        generalwindow.add(outervbox)
        generalwindow.show()
        outervbox.set_border_width(3)

        aud_rs_hbox = Gtk.HBox()

        # User can use this to set the audio level in the headphones

        # TC: The DJ's sound level controller.
        frame = Gtk.Frame.new(" %s " % _('DJ Audio Level'))
        frame.set_label_align(0.5, 0.5)
        frame.set_border_width(3)
        hbox = Gtk.HBox()
        hbox.set_border_width(5)
        frame.add(hbox)
        hbox.show()
        self.dj_aud_adj = Gtk.Adjustment(value=-27.0, lower=-60.0, upper=0.0, step_increment=0.5, page_increment=1.0)
        dj_aud = Gtk.SpinButton.new(self.dj_aud_adj, 1, 1)
        dj_aud.connect("value-changed", self.cb_vol_changed)
        hbox.pack_start(dj_aud, True, False, 0)
        dj_aud.show()
        set_tip(dj_aud, _('This adjusts the sound level of the DJ audio.'))
        aud_rs_hbox.pack_start(frame, False, False, 0)
        frame.show()

        # TC: The alarm sound level.
        frame = Gtk.Frame.new(" %s " % _('Alarm Level'))
        frame.set_label_align(0.5, 0.5)
        frame.set_border_width(3)
        hbox = Gtk.HBox()
        hbox.set_border_width(5)
        frame.add(hbox)
        hbox.show()
        self.alarm_aud_adj = Gtk.Adjustment(value=-24.5, lower=-60.0, upper=0.0, step_increment=0.5, page_increment=1.0)
        alarm_aud = Gtk.SpinButton.new(self.alarm_aud_adj, 1, 1)
        alarm_aud.connect("value-changed", self.cb_vol_changed)
        hbox.pack_start(alarm_aud, True, False, 0)
        alarm_aud.show()
        set_tip(alarm_aud, _('This adjusts the sound level of the DJ alarm. '
        'Typically this should be set close to the dj audio level when using the \'%s\''
        ' feature, otherwise a bit louder.' % _('Track Loudness Normalization')))
        aud_rs_hbox.pack_start(frame, False, False, 0)
        frame.show()

        # User can use this to set the resampled sound quality

        frame = Gtk.Frame.new(" %s " % _('Player Resample Quality'))
        frame.set_label_align(0.5, 0.5)
        frame.set_border_width(3)
        hbox = Gtk.HBox()
        hbox.set_border_width(5)
        set_tip(hbox,
        _('This adjusts the quality of the audio resampling method '
        'used whenever the sample rate of the music file currently playing does'
        ' not match the sample rate of the JACK sound server. Best mode '
        'offers the best sound quality but also uses the most CPU (not '
        'recommended for systems built before 2006). All these modes provide '
        'adequate sound quality.'))
        frame.add(hbox)
        hbox.show()
        self.best_quality_resample = Gtk.RadioButton.new_with_label(None, _('Best'))
        self.best_quality_resample.connect(
                                        "toggled", self.cb_resample_quality, 0)
        hbox.pack_start(self.best_quality_resample, True, False, 0)
        self.best_quality_resample.show()
        self.good_quality_resample = Gtk.RadioButton.new_with_label_from_widget(
                                        self.best_quality_resample, _('Medium'))
        self.good_quality_resample.connect(
                                        "toggled", self.cb_resample_quality, 1)
        hbox.pack_start(self.good_quality_resample, True, False, 0)
        self.good_quality_resample.show()
        self.fast_resample = Gtk.RadioButton.new_with_label_from_widget(
                                        self.good_quality_resample, _('Fast'))
        self.fast_resample.connect("toggled", self.cb_resample_quality, 2)
        hbox.pack_start(self.fast_resample, True, False, 0)
        self.fast_resample.show()

        aud_rs_hbox.pack_start(frame, True, True, 0)
        frame.show()

        outervbox.pack_start(aud_rs_hbox, False, False, 0)
        aud_rs_hbox.show()

        # Not wanting to add a pane into the main player window as it would look ugly
        # we get this instead. Extreme values produce large application window sizes.
        frame = Gtk.Frame.new(" {} ".format(_("Tracks Played Panel Size (relative to playlist size)")))
        frame.set_label_align(0.5, 0.5)
        frame.set_border_width(3)
        history_scale_hbox = Gtk.HBox()
        history_scale_hbox.set_border_width(3)
        self.history_scale = Gtk.Scale.new_with_range(Gtk.Orientation.HORIZONTAL, 1.0, 24.0, 1.0)
        self.history_scale.set_value(5)
        self.history_scale.connect("value-changed", self.cb_history_scale)
        self.history_scale.connect("change-value", self.cb_history_scale2)
        history_scale_hbox.pack_start(self.history_scale, True, True, 0)
        self.history_scale.show()
        frame.add(history_scale_hbox)
        history_scale_hbox.show()
        outervbox.pack_start(frame, False, False, 0)
        frame.show()

        # TC: the set of features - section heading.
        featuresframe = Gtk.Frame.new(" %s " % _('Feature Set'))
        featuresframe.set_border_width(3)
        featuresvbox = Gtk.VBox()
        hbox = Gtk.HBox()
        hbox.set_border_width(2)
        featuresvbox.pack_start(hbox, False)
        featuresframe.add(featuresvbox)
        featuresvbox.show()
        outervbox.pack_start(featuresframe, False, False, 0)
        featuresframe.show()
        vbox = Gtk.VBox()
        # TC: Start in the full featured user interface mode.
        self.startfull = Gtk.RadioButton.new_with_label(None, _('Start Full'))
        self.startfull.set_border_width(2)
        vbox.pack_start(self.startfull, False, False, 0)
        self.startfull.show()
        set_tip(self.startfull,
                    _('Indicates which mode IDJC will be in when launched.'))

        # TC: Start in a reduced user interface mode.
        self.startmini = Gtk.RadioButton.new_with_label_from_widget(self.startfull, _('Start Mini'))
        self.startmini.set_border_width(2)
        vbox.pack_start(self.startmini, False, False, 0)
        self.startmini.show()
        set_tip(self.startmini,
                    _('Indicates which mode IDJC will be in when launched.'))
        #self.startmini.set_active(True)

        vbox.show()
        hbox2 = Gtk.HBox()
        hbox2.set_border_width(10)
        hbox2.set_spacing(20)
        hbox.pack_start(hbox2, True, False, 0)

        self.maxi = Gtk.Button.new_with_label(" %s " % _('Fully Featured'))
        self.maxi.connect("clicked", self.callback, "fully featured")
        hbox2.pack_start(self.maxi, False, False, 0)
        self.maxi.show()
        set_tip(self.maxi,
                _('Run in full functionality mode which uses more CPU power.'))

        self.mini = Gtk.Button.new_with_label(" %s " % _('Basic Streamer'))
        self.mini.connect("clicked", self.callback, "basic streamer")
        hbox2.pack_start(self.mini, False, False, 0)
        self.mini.show()
        set_tip(self.mini, _('Run in a reduced functionality mode that lowers '
                    'the burden on the CPU and takes up less screen space.'))

        hbox2.show()
        hbox.pack_start(vbox, False, False, 9)
        hbox.show()

        requires_restart = Gtk.Frame.new(" %s " %
                            _('These settings take effect after restarting'))
        requires_restart.set_border_width(7)
        featuresvbox.pack_start(requires_restart, False)
        requires_restart.show()

        rrvbox = Gtk.VBox()
        rrvbox.set_border_width(9)
        rrvbox.set_spacing(4)
        requires_restart.add(rrvbox)
        rrvbox.show()

        def hjoin(*widgets):
            hbox = Gtk.HBox()
            hbox.set_spacing(3)
            for w in widgets:
                hbox.pack_start(w, False)
                w.show()
            hbox.show()
            return hbox

        self.mic_qty_adj = Gtk.Adjustment(value=PGlobs.num_micpairs * 2,
                                          lower=2.0, upper=12.0, step_increment=2.0)
        spin = Gtk.SpinButton.new(self.mic_qty_adj, 0, 0)
        def on_output(w):
            """Coerce spin button to be an even integer."""

            value = w.get_value()
            if value != math.floor(value):
                w.set_value(math.floor(value))
                return
            if value % 2:
                w.set_value(value + 1.0)
        spin.connect("output", on_output)


        rrvbox.pack_start(hjoin(spin, Gtk.Label.new(
                                        _('Audio input channels'))))

        self.stream_qty_adj = Gtk.Adjustment(value=PGlobs.num_streamers,
                                             lower=1.0, upper=9.0, step_increment=1.0)
        spin = Gtk.SpinButton.new(self.stream_qty_adj, 0, 0)
        rrvbox.pack_start(hjoin(spin, Gtk.Label.new(_('Simultaneous stream(s)'))))

        self.recorder_qty_adj = Gtk.Adjustment(value=PGlobs.num_recorders,
                                               lower=0.0, upper=4.0, step_increment=1.0)
        spin = Gtk.SpinButton.new(self.recorder_qty_adj, 0, 0)
        rrvbox.pack_start(hjoin(spin, Gtk.Label.new(
                                            _('Simultaneous recording(s)'))))

        self.rrvaluesdict = {"num_micpairs": self.mic_qty_adj,
                                    "num_streamers": self.stream_qty_adj,
                                    "num_recorders": self.recorder_qty_adj}

        # Meters on/off
        def showhide(toggle, target):
            if toggle.get_active():
                target.show()
            else:
                target.hide()
        frame = Gtk.Frame.new(" %s " % _('View'))
        frame.set_border_width(3)
        hbox = Gtk.HBox(3, True)
        hbox.set_border_width(10)
        frame.add(hbox)
        hbox.show()

        vbox = Gtk.VBox()
        hbox.pack_start(vbox)
        vbox.show()
        self.show_stream_meters = Gtk.CheckButton.new()
        self.show_stream_meters.set_active(True)
        self.show_stream_meters.connect(
                                    "toggled", showhide, parent.streammeterbox)
        vbox.pack_start(self.show_stream_meters, False)
        self.show_stream_meters.show()

        self.show_button_bar = Gtk.CheckButton.new()
        self.show_button_bar.set_active(True)
        self.show_button_bar.connect("toggled", showhide, parent.hbox10)
        self.show_button_bar.connect("toggled", showhide, parent.hbox10spc)
        vbox.pack_start(self.show_button_bar, False)
        self.show_button_bar.show()

        self.show_miniplr_bank3 = Gtk.CheckButton.new()
        self.show_miniplr_bank3.set_active(True)
        for each in parent.miniplrs.pages:
            self.show_miniplr_bank3.connect("toggled", showhide, each.player_banks[-1])
        vbox.pack_start(self.show_miniplr_bank3, False)
        self.show_miniplr_bank3.show()

        vbox = Gtk.VBox()
        hbox.pack_start(vbox)
        vbox.show()
        self.show_microphones = Gtk.CheckButton.new()
        self.show_microphones.set_active(True)
        self.show_microphones.connect("toggled", showhide, parent.micmeterbox)
        vbox.pack_start(self.show_microphones, False)
        self.show_microphones.show()

        self.no_mic_void_space = Gtk.CheckButton.new_with_label(
                                            _('Fill channel meter void space'))
        self.no_mic_void_space.set_active(True)
        for meter in parent.mic_meters:
            self.no_mic_void_space.connect("toggled", meter.always_show)
        vbox.pack_start(self.no_mic_void_space, False)
        self.no_mic_void_space.show()

        outervbox.pack_start(frame, False, False, 0)
        frame.show()

        # ReplayGain controls

        frame = Gtk.Frame.new(" %s " % _('Track Loudness Normalization'))
        frame.set_border_width(3)
        outervbox.pack_start(frame, False, False, 0)
        vbox = Gtk.VBox()
        frame.add(vbox)
        frame.show()
        vbox.set_border_width(10)
        vbox.set_spacing(1)
        vbox.show()

        self.rg_indicate = Gtk.CheckButton.new_with_label(
                            _('Indicate which tracks have loudness metadata'))
        set_tip(self.rg_indicate, _('Shows a marker in the playlists next to'
                    ' each track. Either a green circle or a red triangle.'))
        self.rg_indicate.connect("toggled", self.cb_rg_indicate)
        vbox.pack_start(self.rg_indicate, False, False, 0)
        self.rg_indicate.show()

        self.rg_adjust = Gtk.CheckButton.new_with_label(_('Adjust playback volume in dB'))
        set_tip(self.rg_adjust, _('Effective only on newly started tracks.'))
        vbox.pack_start(self.rg_adjust, False, False, 0)
        self.rg_adjust.show()

        table = Gtk.Grid()
        table.set_column_spacing(3)
        table.set_margin_start(4)

        label = Gtk.Label.new(_('R128'))
        label.set_xalign(1.0)
        r128_boostadj = Gtk.Adjustment(value=4.0, lower=-5.0, upper=25.5, step_increment=0.5)
        self.r128_boost = Gtk.SpinButton.new(r128_boostadj, 0.0, 1)
        set_tip(self.r128_boost, _('It may not be desirable to use the '
                    'default level since it is rather quiet. This should be'
                    ' set 4 or 5 dB higher than the ReplayGain setting.'))
        table.attach(label, 0, 0, 1, 1)
        table.attach(self.r128_boost, 1, 0, 1, 1)
        label = Gtk.Label.new(_('ReplayGain'))
        label.set_xalign(1.0)
        label.set_margin_start(7)
        rg_boostadj = Gtk.Adjustment(value=0.0, lower=-10.0, upper=20.5, step_increment=0.5)
        self.rg_boost = Gtk.SpinButton.new(rg_boostadj, 0.0, 1)
        set_tip(self.rg_boost, _('It may not be desirable to use the default'
                        ' level since it is rather quiet. This should be set'
                        ' 4 or 5 dB lower than the R128 setting.'))
        table.attach(label, 2, 0, 1, 1)
        table.attach(self.rg_boost, 3, 0, 1, 1)
        label = Gtk.Label.new(_('Untagged'))
        label.set_margin_start(7)
        label.set_xalign(1.0)
        rg_defaultgainadj = Gtk.Adjustment(value=-8.0, lower=-30.0, upper=10.0, step_increment=0.5)
        self.rg_defaultgain = Gtk.SpinButton.new(rg_defaultgainadj, 0.0, 1)
        set_tip(self.rg_defaultgain, _('Set this so that any unmarked tracks'
        ' are playing at a roughly similar loudness level as the marked ones.'))
        table.attach(label, 4, 0, 1, 1)
        table.attach(self.rg_defaultgain, 5, 0, 1, 1)

        label = Gtk.Label.new(_('All'))
        label.set_xalign(1.0)
        all_boostadj = Gtk.Adjustment(value=0.0, lower=-10.0, upper=10.0, step_increment=0.5)
        self.all_boost = Gtk.SpinButton.new(all_boostadj, 0.0, 1)
        set_tip(self.all_boost, _('A master level control for the media players.'))
        table.attach(label, 0, 1, 1, 1)
        table.attach(self.all_boost, 1, 1, 1, 1)

        vbox.pack_start(table, False)
        table.set_margin_top(4)
        table.show_all()

        # Controls for setting the loudness meter scale.

        frame = Gtk.Frame.new(" %s " % _('Loudness Meter'))
        frame.set_border_width(3)
        outervbox.pack_start(frame, False, False, 0)
        vbox = Gtk.VBox()
        frame.add(vbox)
        frame.show()
        vbox.set_border_width(10)
        vbox.set_spacing(3)
        vbox.show()

        hbox = Gtk.HBox()
        hbox.set_spacing(4)
        vbox.pack_start(hbox, False, False, 0)
        hbox.pack_start(Gtk.Label.new(_("Scale")), False, False, 0)
        self.loudness_rel_m9 = Gtk.RadioButton.new_with_label(None, _("-9 LU"))
        set_tip(self.loudness_rel_m9, _('For if your radio is supposed to be loud. This is the default.'))
        self.loudness_rel_m18 = Gtk.RadioButton.new_with_label_from_widget(self.loudness_rel_m9, _("-18 LU"))
        set_tip(self.loudness_rel_m18, _('Classical music stations may prefer to use this setting. Can express a wider dynamic range.'))
        self.loudness_absolute = Gtk.RadioButton.new_with_label_from_widget(self.loudness_rel_m18, _("Specified Target in LUFS"))
        tip_text = _('EBU R128 specifies -23 LUFS as enough for any use case.')
        set_tip(self.loudness_absolute, tip_text)
        adj = Gtk.Adjustment.new(-23.0, -27.0, -9.0, 1.0, 3.0, 0.0)
        self.loudness_target = Gtk.SpinButton.new(adj, 0.0, 0)
        set_tip(self.loudness_target, tip_text)
        hbox.pack_start(self.loudness_rel_m9, False, False, 0)
        hbox.pack_start(self.loudness_rel_m18, False, False, 0)
        hbox.pack_start(self.loudness_absolute, False, False, 0)
        hbox.pack_start(self.loudness_target, False, False, 0)
        hbox.show_all()
        self.loudness_rel_m9.connect("toggled", parent.modify_lu_meter_mode, "relm9")
        self.loudness_rel_m18.connect("toggled", parent.modify_lu_meter_mode, "relm18")
        self.loudness_absolute.connect("toggled", parent.modify_lu_meter_mode, "abs")
        self.loudness_target.connect("value-changed", lambda w: self.parent.session_loaded and self.parent.str_l_lu.set_scale_target(int(w.get_value())))
        self.loudness_target.connect("value-changed", lambda w: self.parent.session_loaded and self.parent.str_r_lu.set_scale_target(int(w.get_value())))
        self.loudness_target.connect("focus-in-event", lambda w, e: self.loudness_absolute.set_active(True))

        hbox = Gtk.HBox()
        hbox.set_spacing(4)
        vbox.pack_start(hbox, False, False, 0)
        hbox.pack_start(Gtk.Label.new(_("Dead-Air Threshold (LUFS)")), False, False, 0)
        adj = Gtk.Adjustment.new(-35.0, -50.0, -25.0, 1.0, 3.0, 0.0)
        self.dead_air = Gtk.SpinButton.new(adj, 0.0, 0)
        hbox.pack_start(self.dead_air, False, False, 0)
        hbox.pack_start(Gtk.Label.new("\u00b1"), False, False, 0)
        adj = Gtk.Adjustment.new(2.0, 1.0, 5.0, 1, 1, 0)
        self.dead_air_hyst = Gtk.SpinButton.new(adj, 0.0, 0)
        hbox.pack_start(self.dead_air_hyst, False, False, 0)
        hbox.show_all()

        # Recorder filename format may be desirable to change for FAT32 compatibility

        frame = Gtk.Frame.new(" %s " % _('Recorder Filename (excluding the file extension)'))
        set_tip(frame, _("The specifiers are $r for the number of the "
        "recorder with the rest being documented in the strftime man page.\n"
        "Users may wish to alter this to make filenames that are compatible with particular filesystems."))
        frame.set_border_width(3)
        self.recorder_filename = DefaultEntry("idjc.[%Y-%m-%d][%H:%M:%S].$r")
        self.recorder_filename.set_margin_start(3)
        self.recorder_filename.set_margin_top(3)
        self.recorder_filename.set_margin_end(3)
        self.recorder_filename.set_margin_bottom(3)
        frame.add(self.recorder_filename)
        self.recorder_filename.show()
        outervbox.pack_start(frame, False, False, 0)
        frame.show()

        # Miscellaneous Features

        frame = Gtk.Frame.new(" " + _('Miscellaneous Features') + " ")
        frame.set_border_width(3)
        vbox = Gtk.VBox()
        frame.add(vbox)
        frame.show()
        vbox.set_border_width(10)
        vbox.set_spacing(1)

        self.silence_killer = Gtk.CheckButton.new_with_label(
                            _('Trim quiet song endings and trailing silence'))
        self.silence_killer.set_active(True)
        vbox.pack_start(self.silence_killer, False, False, 0)
        self.silence_killer.show()

        self.bonus_killer = Gtk.CheckButton.new_with_label(
                            _('End tracks containing long passages of silence'))
        self.bonus_killer.set_active(True)
        vbox.pack_start(self.bonus_killer, False, False, 0)
        self.bonus_killer.show()

        self.speed_variance = Gtk.CheckButton.new_with_label(
                            _('Enable the main-player speed/pitch controls'))
        vbox.pack_start(self.speed_variance, False, False, 0)
        self.speed_variance.connect("toggled", self.cb_pbspeed)
        self.speed_variance.show()
        set_tip(self.speed_variance, _('This option causes some extra widgets '
        'to appear below the playlists which allow the playback speed to be '
        'adjusted from 25% to 400% and a normal speed button.'))

        self.player_finish_times = Gtk.CheckButton.new_with_label(
                            _('Display combined main player finish times'))
        vbox.pack_start(self.player_finish_times, False, False, 0)
        self.player_finish_times.connect("toggled", lambda w: self.parent.main_label_extra.set_visible(w.get_active()))
        if os.getenv("BMillham"):
            self.player_finish_times.show()

        self.dual_volume = Gtk.CheckButton.new_with_label(
                                _('Separate left/right player volume faders'))
        vbox.pack_start(self.dual_volume, False, False, 0)
        self.dual_volume.connect("toggled", self.cb_dual_volume)
        self.dual_volume.show()
        set_tip(self.dual_volume, _('Select this option to use an independent '
                        'volume fader for the left and right music players.'))

        self.bigger_box_toggle = Gtk.CheckButton.new_with_label(
                            _('Enlarge the time elapsed/remaining windows'))
        vbox.pack_start(self.bigger_box_toggle, False, False, 0)
        self.bigger_box_toggle.connect("toggled", self.callback, "bigger box")
        self.bigger_box_toggle.show()
        set_tip(self.bigger_box_toggle, _("The time elapsed/remaining windows "
        "sometimes don't appear big enough for the text that appears in them "
        "due to unusual DPI settings or the use of a different rendering "
        "engine. This option serves to fix that."))

        self.djalarm = Gtk.CheckButton.new_with_label(
                            _('Sound an alarm when the music is due to end'))
        vbox.pack_start(self.djalarm, False, False, 0)
        self.djalarm.show()
        set_tip(self.djalarm, _('An alarm tone alerting the DJ that dead-air is'
        ' just nine seconds away. This also works when monitoring stream audio '
        'but the alarm tone is not sent to the stream.\n\n'
        'JACK freewheel mode will also be automatically disengaged.'))

        freewheel_show = self.parent.freewheel_button.enabler
        vbox.pack_start(freewheel_show, False, False, 0)
        freewheel_show.show()

        self.dither = Gtk.CheckButton.new_with_label(
                                    _('Apply dither to 16 bit PCM playback'))
        vbox.pack_start(self.dither, False, False, 0)
        self.dither.connect("toggled", self.cb_dither)
        self.dither.show()
        set_tip(self.dither, _('This feature maybe improves the sound quality '
                            'a little when listening on a 24 bit sound card.'))

        self.enable_tooltips = Gtk.CheckButton.new_with_label(_('Enable tooltips'))
        self.enable_tooltips.connect("toggled", self.callback, "tooltips")
        vbox.pack_start(self.enable_tooltips, False, False, 0)
        self.enable_tooltips.show()
        set_tip(self.enable_tooltips, _('This, what you are currently reading,'
                        ' is a tooltip. This feature turns them on or off.'))

        vbox.show()

        outervbox.pack_start(frame, False)

        # Song database preferences and connect button.
        self.songdbprefs = self.parent.topleftpane.prefs_controls
        #self.parent.menu.songdbmenu_a.connect_proxy(self.songdbprefs.dbtoggle)
        self.songdbprefs.db_switch.set_related_action(self.parent.menu.songdbmenu_a)

        outervbox.pack_start(self.songdbprefs, False)

        # Widget for user interface label renaming.
        label_subst = self.parent.label_subst
        outervbox.pack_start(label_subst, False)
        label_subst.set_border_width(3)
        label_subst.show_all()

        # Session to be saved, or initial settings preferences.
        frame = Gtk.Frame.new(" %s " % _('Player Settings At Startup'))
        frame.set_label_align(0.5, 0.5)
        frame.set_border_width(3)
        vbox = Gtk.VBox()
        frame.add(vbox)
        vbox.show()

        restoresessionhbox = Gtk.HBox()
        restoresessionhbox.set_border_width(8)
        restoresessionhbox.show()
        self.restore_session_option = Gtk.CheckButton.new_with_label(
                                            _('Restore the previous session'))
        vbox.pack_start(restoresessionhbox, False, False, 0)
        restoresessionhbox.pack_start(self.restore_session_option, False)
        self.restore_session_option.show()
        set_tip(self.restore_session_option,
        _('When starting IDJC most of the main window settings will be as they '
        'were left. As an alternative you may specify below how you want the '
        'various settings to be when IDJC starts.'))

        hbox = Gtk.HBox(True)
        vbox.add(hbox)
        hbox.set_border_width(6)
        hbox.set_spacing(3)

        self.lpconfig = InitialPlayerConfig(
                                        _("Player 1"), parent.player_left, "l")
        self.rpconfig = InitialPlayerConfig(
                                        _("Player 2"), parent.player_right, "r")
        for each in self.lpconfig, self.rpconfig:
            hbox.pack_start(each, True, True)

        hbox.show()

        self.misc_session_frame = Gtk.Frame()
        self.misc_session_frame.set_border_width(4)
        misc_startup = Gtk.HBox(True, 20)
        self.misc_session_frame.add(misc_startup)
        misc_startup.show()
        hbox2 = Gtk.HBox()
        hbox2.pack_start(self.misc_session_frame, True, True, 7)
        hbox2.show()

        vbox.pack_start(hbox2, False, False, 2)
        self.misc_session_frame.show()
        misc_startupl = Gtk.VBox()
        misc_startup.pack_start(misc_startupl, True, True, 5)
        misc_startupl.show()
        misc_startupr = Gtk.VBox()
        misc_startup.pack_start(misc_startupr, True, True, 5)
        misc_startupr.show()

        self.tracks_played = Gtk.CheckButton.new_with_label(_('Tracks Played'))
        misc_startupl.add(self.tracks_played)
        self.tracks_played.show()
        # TC: DJ hears the stream mix.
        self.stream_mon = Gtk.CheckButton.new_with_label(_('Monitor Stream Mix'))
        misc_startupr.add(self.stream_mon)
        self.stream_mon.show()

        self.restore_session_option.connect("toggled", self.cb_restore_session)
        self.restore_session_option.set_active(True)

        outervbox.pack_start(frame, False)
        frame.show()

        # TC: A heading label for miscellaneous settings.
        features_label = Gtk.Label.new(_('General'))
        self.notebook.append_page(generalwindow, features_label)
        features_label.show()
        outervbox.show()

        # Channels tab

        scrolled_window = Gtk.ScrolledWindow()
        scrolled_window.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.ALWAYS)
        panevbox = Gtk.VBox()
        panevbox.set_margin_end(10)
        scrolled_window.add(panevbox)
        scrolled_window.show()
        panevbox.set_border_width(3)
        panevbox.set_spacing(3)
        panevbox.get_parent().set_shadow_type(Gtk.ShadowType.NONE)
        panevbox.show()

        # Opener buttons for channels

        opener_settings = parent.mic_opener.opener_settings
        panevbox.pack_start(opener_settings, False, padding=3)

        # Individual channel settings

        self.mic_controls = mic_controls = []
        vbox = Gtk.VBox()
        for i in range(PGlobs.num_micpairs):
            uhbox = Gtk.HBox(True)
            vbox.pack_start(uhbox, False, False, 0)
            uhbox.show()
            lhbox = Gtk.HBox()
            vbox.pack_start(lhbox, False, False, 0)
            lhbox.show()
            for j in range(2):
                n = i * 2 + j
                micname = "mic_control_%d" % n
                c = AGCControl(self.parent, str(n + 1), micname, n)
                setattr(self, micname, c)
                uhbox.add(c)
                c.show()
                parent.mic_opener.add_mic(c)
                mic_controls.append(c)
            mic_controls[-2].set_partner(mic_controls[-1])
            mic_controls[-1].set_partner(mic_controls[-2])
        parent.mic_opener.finalise()

        panevbox.pack_start(vbox, False)
        vbox.show()

        self.voip_pan = PanWidget(_('VoIP Panning + Mono Downmix'), "voip_pan_widget")
        self.voip_pan.pan_active.connect("toggled", self.voip_pan_backend_update)
        self.voip_pan.pan.connect("value-changed", self.voip_pan_backend_update)
        self.voip_pan.set_border_width(3)
        panevbox.pack_start(self.voip_pan, False)
        self.voip_pan.show_all()

        # Ports to grab when main panel port grab button is pressed.
        frame = Gtk.Frame.new(_('VoIP Application Port Capture Settings'))
        frame.set_border_width(3)
        box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
        frame.add(box)
        port_grab_checkbutton = Gtk.CheckButton.new_with_label(_("Show the VoIP application port capture button in the main application window"))
        port_grab_checkbutton.connect("toggled", lambda w: parent.port_grab_button.set_visible(w.get_active()))
        port_grab_checkbutton.set_border_width(3)
        port_grab_checkbutton.set_margin_start(6)
        box.pack_start(port_grab_checkbutton, False, False, 0)
        self.port_grab_notebook = PortGrabNotebook(self.parent)
        box.pack_start(self.port_grab_notebook, False, False, 0)
        panevbox.pack_start(frame, False)
        frame.show_all()

        label = Gtk.Label.new(_('Channels'))
        self.notebook.append_page(scrolled_window, label)
        label.show()

        # Controls tab
        tab= midicontrols.ControlsUI(self.parent.controls)
        # TC: Keyboard and MIDI bindings configuration.
        label= Gtk.Label.new(_('Bindings'))
        self.notebook.append_page(tab, label)
        tab.show()
        label.show()

        # about tab

        self.aboutvbox = vbox = Gtk.VBox()
        label = Gtk.Label.new()
        label.set_markup('<span font_desc="sans italic 20">' +
                                                self.parent.appname + '</span>')
        vbox.pack_start(label, False, False, 13)
        label.show()
        label = Gtk.Label.new()
        label.set_markup('<span font_desc="sans 13">Version ' +
                                                self.parent.version + '</span>')
        vbox.pack_start(label, False, False, 0)
        label.show()

        pixbuf = GdkPixbuf.Pixbuf.new_from_file(FGlobs.pkgdatadir / "logo.png")
        image = Gtk.Image()
        image.set_from_pixbuf(pixbuf)
        vbox.pack_start(image, False, False, 8)
        image.show()

        label = Gtk.Label.new()
        label.set_markup(u'<span font_desc="sans 13">' +
                                            self.parent.copyright + u'</span>')
        vbox.pack_start(label, False, False, 12)
        label.show()

        label = Gtk.Label.new()
        label.set_markup(
        '<span font_desc="sans 10">' + PGlobs.license + '</span>')
        vbox.pack_start(label, False, False, 1)
        label.show()

        nb = Gtk.Notebook()
        nb.set_margin_top(3)
        nb.set_border_width(5)
        vbox.pack_start(nb, True, True, 0)
        nb.show()

        lw = licence_window.LicenceWindow()
        lw.set_border_width(1)
        lw.set_shadow_type(Gtk.ShadowType.ETCHED_IN)
        label = Gtk.Label.new(_('License'))
        nb.append_page(lw, label)
        lw.show()
        label.show()

        def contribs_page(title, content):
            sw = Gtk.ScrolledWindow()
            sw.set_border_width(1)
            sw.set_shadow_type(Gtk.ShadowType.NONE)
            sw.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC)
            label = Gtk.Label.new(title)
            nb.append_page(sw, label)
            sw.show()
            lw.show()
            ivbox = Gtk.VBox()
            sw.add(ivbox)
            ivbox.show()
            for each in content:
                label = Gtk.Label.new(each)
                label.set_vexpand(True)
                label.set_use_markup(True)
                ivbox.add(label)
                label.show()

        contribs_page(_('Contributors'),
            ("Stephen Fairchild (s-fairchild@users.sourceforge.net)",
            "And Clover (and@doxdesk.com)",
            "Dario Abatianni (eisfuchs@users.sourceforge.net)",
            "Stefan Fendt (stefan@sfendt.de)",
            "Brian Millham (bmillham@users.sourceforge.net)"))

        contribs_page(_('Translators'),
            ("<b>es</b> Blank Frank (frank@rhizomatica.org)",
            "<b>fr</b> nvignot (nicotux@users.sf.net)",
            "<b>it</b>  Raffaele Morelli (raffaele.morelli@gmail.com)"))

        label = Gtk.Label.new(_('Build Info'))
        ivbox = Gtk.VBox()
        ivbox.set_spacing(10)
        ivbox.set_border_width(10)
        nb.append_page(ivbox, label)
        ivbox.show()

        with open(FGlobs.pkgdatadir / "buildinfo") as f:
            for each in f:
                label = Gtk.Label.new(each.rstrip())
                label.set_use_markup(True)
                label.set_selectable(True)
                ivbox.pack_start(label, False)
                label.show()

        vbox.show()

        aboutlabel = Gtk.Label.new(_('About'))
        self.notebook.append_page(self.aboutvbox, aboutlabel)
        aboutlabel.show()
        self.aboutvbox.show()

        self.notebook.show()

        # These on by default
        self.djalarm.set_active(True)
        self.dither.set_active(True)
        self.fast_resample.set_active(True)
        self.enable_tooltips.set_active(True)

        # Default mic/aux configuration
        mic_controls[0].mode.set_active(2)
        mic_controls[0].alt_name.set_text("DJ")
        t = parent.mic_opener.ix2button[1].opener_tab
        t.button_text.set_text("DJ")
        t.icb.set_filename(FGlobs.pkgdatadir / "mic4.png")
        t.headroom.set_value(3)
        t.has_reminder_flash.set_active(True)
        t.is_microphone.set_active(True)
        t.freewheel_cancel.set_active(True)
        for cb, state in zip(t.open_triggers.values(), (1, 1, 0, 1)):
            cb.set_active(state)
        if len(mic_controls) >= 4:
            mic_controls[2].mode.set_active(1)
            mic_controls[2].alt_name.set_text("Aux L")
            mic_controls[2].groups_adj.set_value(2)
            mic_controls[2].pan.pan_active.set_active(True)
            mic_controls[2].pan.set_values(0)
            mic_controls[3].mode.set_active(3)
            mic_controls[3].alt_name.set_text("Aux R")
            mic_controls[3].pan.pan_active.set_active(True)
            mic_controls[3].pan.set_values(100)
            t = parent.mic_opener.ix2button[2].opener_tab
            t.button_text.set_text("Aux")
            t.icb.set_filename(FGlobs.pkgdatadir / "jack2.png")
            tuple(t.open_triggers.values())[2].set_active(True)

        self.show_stream_meters.set_related_action(self.parent.menu.strmetersmenu_a)
        self.show_microphones.set_related_action(self.parent.menu.chmetersmenu_a)
        self.show_button_bar.set_related_action(self.parent.menu.buttonbarmenu_a)
        self.show_miniplr_bank3.set_related_action(self.parent.menu.minibank3menu_a)

        self.show_stream_meters.set_active(True)
        self.show_microphones.set_active(True)
        self.show_button_bar.set_active(True)
        self.show_miniplr_bank3.set_active(True)

        self.activedict = {  # Widgets to save that have the get_active method.
            "startmini"   : self.startmini,
            "dsp_toggle"  : self.parent.dsp_button,
            "djalarm"     : self.djalarm,
            "trxpld"      : self.tracks_played,
            "strmon"      : self.stream_mon,
            "bigdigibox"  : self.bigger_box_toggle,
            "dither"      : self.dither,
            "recallsession" : self.restore_session_option,
            "best_rs"       : self.best_quality_resample,
            "good_rs"       : self.good_quality_resample,
            "fast_rs"       : self.fast_resample,
            "speed_var"     : self.speed_variance,
            "player_finish_times": self.player_finish_times,
            "dual_volume"   : self.dual_volume,
            "showtips"      : self.enable_tooltips,
            "silencekiller" : self.silence_killer,
            "bonuskiller"   : self.bonus_killer,
            "rg_indicate"   : self.rg_indicate,
            "rg_adjust"     : self.rg_adjust,
            "str_meters"    : self.show_stream_meters,
            "mic_meters"    : self.show_microphones,
            "btn_bar"       : self.show_button_bar,
            "relm18"        : self.loudness_rel_m18,
            "abs"           : self.loudness_absolute,
            "mic_meters_no_void" : self.no_mic_void_space,
            "players_visible"    : self.parent.menu.playersmenu_i,
            "port_grab"     : port_grab_checkbutton,
            "mini_bank3"    : self.show_miniplr_bank3
            }

        for each in itertools.chain(mic_controls,
                            self.port_grab_notebook.get_children(),
                            (self.parent.freewheel_button, self.songdbprefs,
                            self.lpconfig, self.rpconfig, opener_settings,
                            label_subst, self.voip_pan)):
            self.activedict.update(each.activedict)

        self.valuesdict = {  # These widgets all have the get_value method.
            "voiplevel"     : self.parent.voipgainadj,
            "voipmixback"   : self.parent.mixbackadj,
            "interlude_vol" : self.parent.background.ivol_adj,
            "passspeed"     : self.parent.passspeed_adj,
            "loudness_targ" : self.loudness_target,
            "dead_air"      : self.dead_air,
            "dead_air_hyst" : self.dead_air_hyst,
            "djvolume"      : self.dj_aud_adj,
            "alarmvolume"   : self.alarm_aud_adj,
            "rg_default"    : self.rg_defaultgain,
            "rg_boost"      : self.rg_boost,
            "r128_boost"    : self.r128_boost,
            "all_boost"     : self.all_boost,
            "hist_scale"    : self.history_scale,
            "voip_nb_page"  : self.port_grab_notebook
            }

        for each in itertools.chain(mic_controls, (opener_settings,
                                                            self.voip_pan)):
            self.valuesdict.update(each.valuesdict)

        self.textdict = {  # These widgets all have the get_text method.
            "ltfilerqdir"   : self.parent.player_left.file_requester_start_dir,
            "rtfilerqdir"   : self.parent.player_right.file_requester_start_dir,
            "main_full_wst" : self.parent.full_wst,
            "main_min_wst"  : self.parent.min_wst,
            "prefs_wst"     : self.wst,
            "rec_filename"  : self.recorder_filename
            }

        for each in itertools.chain(self.port_grab_notebook.get_children(),
                                    mic_controls,
                                    (opener_settings, label_subst, self.songdbprefs)):
            self.textdict.update(each.textdict)

        self.rangewidgets = (self.parent.deckadj,)

