Chris@0: /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ Chris@0: Chris@0: /* Chris@0: Sonic Visualiser Chris@0: An audio file viewer and annotation editor. Chris@0: Centre for Digital Music, Queen Mary, University of London. Chris@0: This file copyright 2006 Chris Cannam. Chris@0: Chris@0: This program is free software; you can redistribute it and/or Chris@0: modify it under the terms of the GNU General Public License as Chris@0: published by the Free Software Foundation; either version 2 of the Chris@0: License, or (at your option) any later version. See the file Chris@0: COPYING included with this distribution for more information. Chris@0: */ Chris@0: Chris@0: #include "PreferencesDialog.h" Chris@0: Chris@0: #include Chris@0: #include Chris@0: #include Chris@0: #include Chris@0: #include Chris@0: #include Chris@0: #include Chris@0: #include Chris@0: #include Chris@163: #include Chris@163: #include Chris@180: #include Chris@180: #include Chris@180: #include Chris@225: #include Chris@436: #include Chris@263: #include Chris@0: Chris@436: #include Chris@436: Chris@9: #include "widgets/WindowTypeSelector.h" Chris@180: #include "widgets/IconLoader.h" Chris@1445: #include "widgets/ColourMapComboBox.h" Chris@1448: #include "widgets/ColourComboBox.h" Chris@1812: #include "widgets/PluginPathConfigurator.h" Chris@1812: #include "widgets/WidgetScale.h" Chris@0: #include "base/Preferences.h" Chris@436: #include "base/ResourceFinder.h" Chris@1096: #include "layer/ColourMapper.h" Chris@1448: #include "layer/ColourDatabase.h" Chris@0: Chris@1397: #include "bqaudioio/AudioFactory.h" Chris@1035: Chris@1538: #include "../version.h" Chris@686: Chris@1397: using namespace std; Chris@1397: Chris@528: PreferencesDialog::PreferencesDialog(QWidget *parent) : Chris@528: QDialog(parent), Chris@1397: m_audioImplementation(0), Chris@1397: m_audioPlaybackDevice(0), Chris@1397: m_audioRecordDevice(0), Chris@1413: m_audioDeviceChanged(false), Chris@1448: m_coloursChanged(false), Chris@180: m_changesOnRestart(false) Chris@0: { Chris@163: setWindowTitle(tr("Sonic Visualiser: Application Preferences")); Chris@0: Chris@0: Preferences *prefs = Preferences::getInstance(); Chris@0: Chris@0: QGridLayout *grid = new QGridLayout; Chris@0: setLayout(grid); Chris@180: Chris@436: m_tabs = new QTabWidget; Chris@436: grid->addWidget(m_tabs, 0, 0); Chris@0: Chris@436: m_tabs->setTabPosition(QTabWidget::North); Chris@0: Chris@0: // Create this first, as slots that get called from the ctor will Chris@0: // refer to it Chris@0: m_applyButton = new QPushButton(tr("Apply")); Chris@0: Chris@180: // Create all the preference widgets first, then create the Chris@180: // individual tab widgets and place the preferences in their Chris@180: // appropriate places in one go afterwards Chris@180: Chris@114: int min, max, deflt, i; Chris@0: Chris@9: m_windowType = WindowType(prefs->getPropertyRangeAndValue Chris@114: ("Window Type", &min, &max, &deflt)); Chris@9: m_windowTypeSelector = new WindowTypeSelector(m_windowType); Chris@0: Chris@9: connect(m_windowTypeSelector, SIGNAL(windowTypeChanged(WindowType)), Chris@9: this, SLOT(windowTypeChanged(WindowType))); Chris@0: Chris@1275: QCheckBox *vampProcessSeparation = new QCheckBox; Chris@1275: m_runPluginsInProcess = prefs->getRunPluginsInProcess(); Chris@1275: vampProcessSeparation->setCheckState(m_runPluginsInProcess ? Qt::Unchecked : Chris@1275: Qt::Checked); Chris@1275: connect(vampProcessSeparation, SIGNAL(stateChanged(int)), Chris@1275: this, SLOT(vampProcessSeparationChanged(int))); Chris@1275: Chris@115: QComboBox *smoothing = new QComboBox; Chris@115: Chris@299: int sm = prefs->getPropertyRangeAndValue("Spectrogram Y Smoothing", &min, &max, Chris@115: &deflt); Chris@115: m_spectrogramSmoothing = sm; Chris@0: Chris@115: for (i = min; i <= max; ++i) { Chris@299: smoothing->addItem(prefs->getPropertyValueLabel("Spectrogram Y Smoothing", i)); Chris@115: } Chris@115: Chris@115: smoothing->setCurrentIndex(sm); Chris@115: Chris@115: connect(smoothing, SIGNAL(currentIndexChanged(int)), Chris@115: this, SLOT(spectrogramSmoothingChanged(int))); Chris@0: Chris@299: QComboBox *xsmoothing = new QComboBox; Chris@299: Chris@299: int xsm = prefs->getPropertyRangeAndValue("Spectrogram X Smoothing", &min, &max, Chris@299: &deflt); Chris@299: m_spectrogramXSmoothing = xsm; Chris@299: Chris@299: for (i = min; i <= max; ++i) { Chris@299: xsmoothing->addItem(prefs->getPropertyValueLabel("Spectrogram X Smoothing", i)); Chris@299: } Chris@299: Chris@299: xsmoothing->setCurrentIndex(xsm); Chris@299: Chris@299: connect(xsmoothing, SIGNAL(currentIndexChanged(int)), Chris@299: this, SLOT(spectrogramXSmoothingChanged(int))); Chris@299: Chris@0: QComboBox *propertyLayout = new QComboBox; Chris@114: int pl = prefs->getPropertyRangeAndValue("Property Box Layout", &min, &max, Chris@115: &deflt); Chris@0: m_propertyLayout = pl; Chris@0: Chris@0: for (i = min; i <= max; ++i) { Chris@0: propertyLayout->addItem(prefs->getPropertyValueLabel("Property Box Layout", i)); Chris@0: } Chris@0: Chris@0: propertyLayout->setCurrentIndex(pl); Chris@0: Chris@0: connect(propertyLayout, SIGNAL(currentIndexChanged(int)), Chris@0: this, SLOT(propertyLayoutChanged(int))); Chris@0: Chris@1096: QSettings settings; Chris@1096: settings.beginGroup("Preferences"); Chris@1096: m_spectrogramGColour = (settings.value("spectrogram-colour", Chris@1096: int(ColourMapper::Green)).toInt()); Chris@1096: m_spectrogramMColour = (settings.value("spectrogram-melodic-colour", Chris@1096: int(ColourMapper::Sunset)).toInt()); Chris@1097: m_colour3DColour = (settings.value("colour-3d-plot-colour", Chris@1097: int(ColourMapper::Green)).toInt()); cannam@1463: m_overviewColour = ColourDatabase::getInstance()->getColour(tr("Green")); cannam@1463: if (settings.contains("overview-colour")) { cannam@1463: QString qcolorName = cannam@1463: settings.value("overview-colour", m_overviewColour.name()) cannam@1463: .toString(); cannam@1463: m_overviewColour.setNamedColor(qcolorName); Chris@1769: SVCERR << "loaded colour " << m_overviewColour.name() << " from settings" << endl; cannam@1463: } Chris@1096: settings.endGroup(); Chris@1445: Chris@1445: ColourMapComboBox *spectrogramGColour = new ColourMapComboBox(true); Chris@1445: spectrogramGColour->setCurrentIndex(m_spectrogramGColour); Chris@1445: Chris@1445: ColourMapComboBox *spectrogramMColour = new ColourMapComboBox(true); Chris@1445: spectrogramMColour->setCurrentIndex(m_spectrogramMColour); Chris@1445: Chris@1445: ColourMapComboBox *colour3DColour = new ColourMapComboBox(true); Chris@1445: colour3DColour->setCurrentIndex(m_colour3DColour); Chris@1445: Chris@1448: // can't have "add new colour", as it gets saved in the session not in prefs Chris@1448: ColourComboBox *overviewColour = new ColourComboBox(false); Chris@1448: int overviewColourIndex = Chris@1448: ColourDatabase::getInstance()->getColourIndex(m_overviewColour); Chris@1769: SVCERR << "index = " << overviewColourIndex << " for colour " << m_overviewColour.name() << endl; Chris@1448: if (overviewColourIndex >= 0) { Chris@1448: overviewColour->setCurrentIndex(overviewColourIndex); Chris@1448: } Chris@1448: Chris@1445: connect(spectrogramGColour, SIGNAL(colourMapChanged(int)), Chris@1096: this, SLOT(spectrogramGColourChanged(int))); Chris@1445: connect(spectrogramMColour, SIGNAL(colourMapChanged(int)), Chris@1096: this, SLOT(spectrogramMColourChanged(int))); Chris@1445: connect(colour3DColour, SIGNAL(colourMapChanged(int)), Chris@1097: this, SLOT(colour3DColourChanged(int))); Chris@1448: connect(overviewColour, SIGNAL(colourChanged(int)), Chris@1448: this, SLOT(overviewColourChanged(int))); Chris@702: Chris@0: m_tuningFrequency = prefs->getTuningFrequency(); Chris@0: Chris@0: QDoubleSpinBox *frequency = new QDoubleSpinBox; Chris@0: frequency->setMinimum(100.0); Chris@0: frequency->setMaximum(5000.0); Chris@0: frequency->setSuffix(" Hz"); Chris@0: frequency->setSingleStep(1); Chris@0: frequency->setValue(m_tuningFrequency); Chris@0: frequency->setDecimals(2); Chris@0: Chris@0: connect(frequency, SIGNAL(valueChanged(double)), Chris@0: this, SLOT(tuningFrequencyChanged(double))); Chris@0: Chris@702: QComboBox *octaveSystem = new QComboBox; Chris@702: int oct = prefs->getPropertyRangeAndValue Chris@702: ("Octave Numbering System", &min, &max, &deflt); Chris@702: m_octaveSystem = oct; Chris@702: for (i = min; i <= max; ++i) { Chris@702: octaveSystem->addItem(prefs->getPropertyValueLabel Chris@702: ("Octave Numbering System", i)); Chris@702: } Chris@702: octaveSystem->setCurrentIndex(oct); Chris@702: Chris@702: connect(octaveSystem, SIGNAL(currentIndexChanged(int)), Chris@702: this, SLOT(octaveSystemChanged(int))); Chris@702: Chris@263: settings.beginGroup("Preferences"); Chris@1397: Chris@1397: QComboBox *audioImplementation = new QComboBox; Chris@1397: connect(audioImplementation, SIGNAL(currentIndexChanged(int)), Chris@1397: this, SLOT(audioImplementationChanged(int))); Chris@1397: Chris@1397: m_audioPlaybackDeviceCombo = new QComboBox; Chris@1397: connect(m_audioPlaybackDeviceCombo, SIGNAL(currentIndexChanged(int)), Chris@1397: this, SLOT(audioPlaybackDeviceChanged(int))); Chris@1397: Chris@1397: m_audioRecordDeviceCombo = new QComboBox; Chris@1397: connect(m_audioRecordDeviceCombo, SIGNAL(currentIndexChanged(int)), Chris@1397: this, SLOT(audioRecordDeviceChanged(int))); Chris@1397: Chris@1459: vector implementationNames = Chris@1459: breakfastquay::AudioFactory::getImplementationNames(); Chris@1459: Chris@1397: QString implementationName = settings.value("audio-target", "").toString(); Chris@1397: if (implementationName == "auto") implementationName = ""; Chris@1459: if (implementationName == "" && implementationNames.size() == 1) { Chris@1459: // We won't be showing the implementations menu in this case Chris@1459: implementationName = implementationNames[0].c_str(); Chris@1459: } Chris@1459: Chris@1397: audioImplementation->addItem(tr("(auto)")); Chris@1397: m_audioImplementation = 0; Chris@1459: Chris@1459: for (int i = 0; in_range_for(implementationNames, i); ++i) { Chris@1397: audioImplementation->addItem Chris@1459: (breakfastquay::AudioFactory::getImplementationDescription Chris@1459: (implementationNames[i]).c_str()); Chris@1459: if (implementationName.toStdString() == implementationNames[i]) { Chris@1397: audioImplementation->setCurrentIndex(i+1); Chris@1397: m_audioImplementation = i+1; Chris@1397: } Chris@1397: } Chris@1459: Chris@263: settings.endGroup(); Chris@263: Chris@1397: rebuildDeviceCombos(); Chris@1413: m_audioDeviceChanged = false; // the rebuild will have changed this Chris@32: Chris@180: QCheckBox *resampleOnLoad = new QCheckBox; Chris@180: m_resampleOnLoad = prefs->getResampleOnLoad(); Chris@180: resampleOnLoad->setCheckState(m_resampleOnLoad ? Qt::Checked : Chris@180: Qt::Unchecked); Chris@180: connect(resampleOnLoad, SIGNAL(stateChanged(int)), Chris@180: this, SLOT(resampleOnLoadChanged(int))); Chris@180: Chris@1379: QCheckBox *gaplessMode = new QCheckBox; Chris@1379: m_gapless = prefs->getUseGaplessMode(); Chris@1379: gaplessMode->setCheckState(m_gapless ? Qt::Checked : Qt::Unchecked); Chris@1379: connect(gaplessMode, SIGNAL(stateChanged(int)), Chris@1379: this, SLOT(gaplessModeChanged(int))); Chris@1379: Chris@180: m_tempDirRootEdit = new QLineEdit; Chris@180: QString dir = prefs->getTemporaryDirectoryRoot(); Chris@180: m_tempDirRoot = dir; Chris@180: dir.replace("$HOME", tr("")); Chris@180: m_tempDirRootEdit->setText(dir); Chris@180: m_tempDirRootEdit->setReadOnly(true); Chris@180: QPushButton *tempDirButton = new QPushButton; Chris@180: tempDirButton->setIcon(IconLoader().load("fileopen")); Chris@180: connect(tempDirButton, SIGNAL(clicked()), Chris@180: this, SLOT(tempDirButtonClicked())); Chris@1812: tempDirButton->setFixedSize(WidgetScale::scaleQSize(QSize(24, 24))); Chris@180: Chris@237: QCheckBox *showSplash = new QCheckBox; Chris@237: m_showSplash = prefs->getShowSplash(); Chris@237: showSplash->setCheckState(m_showSplash ? Qt::Checked : Qt::Unchecked); Chris@237: connect(showSplash, SIGNAL(stateChanged(int)), Chris@237: this, SLOT(showSplashChanged(int))); Chris@237: Chris@1092: #ifdef NOT_DEFINED // This no longer works correctly on any platform AFAICS Chris@180: QComboBox *bgMode = new QComboBox; Chris@180: int bg = prefs->getPropertyRangeAndValue("Background Mode", &min, &max, Chris@180: &deflt); Chris@180: m_backgroundMode = bg; Chris@180: for (i = min; i <= max; ++i) { Chris@180: bgMode->addItem(prefs->getPropertyValueLabel("Background Mode", i)); Chris@180: } Chris@180: bgMode->setCurrentIndex(bg); Chris@180: Chris@180: connect(bgMode, SIGNAL(currentIndexChanged(int)), Chris@180: this, SLOT(backgroundModeChanged(int))); Chris@237: #endif Chris@180: Chris@658: settings.beginGroup("Preferences"); Chris@686: Chris@950: #ifdef Q_OS_MAC Chris@950: m_retina = settings.value("scaledHiDpi", true).toBool(); Chris@950: QCheckBox *retina = new QCheckBox; Chris@950: retina->setCheckState(m_retina ? Qt::Checked : Qt::Unchecked); Chris@950: connect(retina, SIGNAL(stateChanged(int)), this, SLOT(retinaChanged(int))); Chris@950: #else Chris@950: m_retina = false; Chris@950: #endif Chris@950: Chris@658: QString userLocale = settings.value("locale", "").toString(); Chris@658: m_currentLocale = userLocale; Chris@686: Chris@686: QString permishTag = QString("network-permission-%1").arg(SV_VERSION); Chris@686: m_networkPermission = settings.value(permishTag, false).toBool(); Chris@686: Chris@658: settings.endGroup(); Chris@658: Chris@658: QComboBox *locale = new QComboBox; Chris@658: QStringList localeFiles = QDir(":i18n").entryList(QStringList() << "*.qm"); Chris@658: locale->addItem(tr("Follow system locale")); Chris@658: m_locales.push_back(""); Chris@658: if (userLocale == "") { Chris@658: locale->setCurrentIndex(0); Chris@658: } Chris@658: foreach (QString f, localeFiles) { Chris@658: QString f0 = f; Chris@658: f.replace("sonic-visualiser_", "").replace(".qm", ""); Chris@658: if (f == f0) { // our expectations about filename format were not met Chris@1769: SVCERR << "INFO: Unexpected filename " << f << " in i18n resource directory" << endl; Chris@658: } else { Chris@658: m_locales.push_back(f); Chris@658: QString displayText; Chris@658: // Add new translations here Chris@658: if (f == "ru") displayText = tr("Russian"); Chris@658: else if (f == "en_GB") displayText = tr("British English"); Chris@658: else if (f == "en_US") displayText = tr("American English"); Chris@658: else if (f == "cs_CZ") displayText = tr("Czech"); Chris@658: else displayText = f; Chris@658: locale->addItem(QString("%1 [%2]").arg(displayText).arg(f)); Chris@658: if (userLocale == f) { Chris@658: locale->setCurrentIndex(locale->count() - 1); Chris@658: } Chris@658: } Chris@658: } Chris@658: connect(locale, SIGNAL(currentIndexChanged(int)), Chris@658: this, SLOT(localeChanged(int))); Chris@658: Chris@686: QCheckBox *networkPermish = new QCheckBox; Chris@686: networkPermish->setCheckState(m_networkPermission ? Qt::Checked : Qt::Unchecked); Chris@686: connect(networkPermish, SIGNAL(stateChanged(int)), Chris@686: this, SLOT(networkPermissionChanged(int))); Chris@686: Chris@225: QSpinBox *fontSize = new QSpinBox; Chris@225: int fs = prefs->getPropertyRangeAndValue("View Font Size", &min, &max, Chris@225: &deflt); Chris@234: m_viewFontSize = fs; Chris@225: fontSize->setMinimum(min); Chris@225: fontSize->setMaximum(max); Chris@225: fontSize->setSuffix(" pt"); Chris@225: fontSize->setSingleStep(1); Chris@225: fontSize->setValue(fs); Chris@225: Chris@225: connect(fontSize, SIGNAL(valueChanged(int)), Chris@225: this, SLOT(viewFontSizeChanged(int))); Chris@225: Chris@337: QComboBox *ttMode = new QComboBox; Chris@337: int tt = prefs->getPropertyRangeAndValue("Time To Text Mode", &min, &max, Chris@337: &deflt); Chris@337: m_timeToTextMode = tt; Chris@337: for (i = min; i <= max; ++i) { Chris@337: ttMode->addItem(prefs->getPropertyValueLabel("Time To Text Mode", i)); Chris@337: } Chris@337: ttMode->setCurrentIndex(tt); Chris@337: Chris@337: connect(ttMode, SIGNAL(currentIndexChanged(int)), Chris@337: this, SLOT(timeToTextModeChanged(int))); Chris@337: Chris@906: QCheckBox *hms = new QCheckBox; Chris@906: int showHMS = prefs->getPropertyRangeAndValue Chris@906: ("Show Hours And Minutes", &min, &max, &deflt); Chris@906: m_showHMS = (showHMS != 0); Chris@906: hms->setCheckState(m_showHMS ? Qt::Checked : Qt::Unchecked); Chris@906: connect(hms, SIGNAL(stateChanged(int)), Chris@906: this, SLOT(showHMSChanged(int))); Chris@180: Chris@2126: QFrame *frame = nullptr; Chris@2126: QGridLayout *subgrid = nullptr; Chris@0: int row = 0; Chris@0: Chris@263: // Appearance tab Chris@263: Chris@263: frame = new QFrame; Chris@263: subgrid = new QGridLayout; Chris@263: frame->setLayout(subgrid); Chris@263: row = 0; Chris@263: Chris@950: #ifdef Q_OS_MAC Chris@950: if (devicePixelRatio() > 1) { Chris@950: subgrid->addWidget(new QLabel(tr("Draw layers at Retina resolution:")), row, 0); Chris@950: subgrid->addWidget(retina, row++, 1, 1, 1); Chris@950: } Chris@950: #endif Chris@950: Chris@906: subgrid->addWidget(new QLabel(tr("%1:").arg(prefs->getPropertyLabel Chris@0: ("Property Box Layout"))), Chris@0: row, 0); Chris@0: subgrid->addWidget(propertyLayout, row++, 1, 1, 2); Chris@0: Chris@1098: subgrid->addWidget(new QLabel(tr("Default spectrogram colour:")), Chris@1096: row, 0); Chris@1096: subgrid->addWidget(spectrogramGColour, row++, 1, 1, 2); Chris@1096: Chris@1098: subgrid->addWidget(new QLabel(tr("Default melodic spectrogram colour:")), Chris@1096: row, 0); Chris@1096: subgrid->addWidget(spectrogramMColour, row++, 1, 1, 2); Chris@1096: Chris@1098: subgrid->addWidget(new QLabel(tr("Default colour 3D plot colour:")), Chris@1097: row, 0); Chris@1097: subgrid->addWidget(colour3DColour, row++, 1, 1, 2); Chris@1097: Chris@1448: subgrid->addWidget(new QLabel(tr("Overview waveform colour:")), Chris@1448: row, 0); Chris@1448: subgrid->addWidget(overviewColour, row++, 1, 1, 2); Chris@1448: Chris@1092: #ifdef NOT_DEFINED // see earlier Chris@0: subgrid->addWidget(new QLabel(tr("%1:").arg(prefs->getPropertyLabel Chris@180: ("Background Mode"))), Chris@0: row, 0); Chris@180: subgrid->addWidget(bgMode, row++, 1, 1, 2); Chris@242: #endif Chris@180: Chris@180: subgrid->addWidget(new QLabel(tr("%1:").arg(prefs->getPropertyLabel Chris@225: ("View Font Size"))), Chris@225: row, 0); Chris@225: subgrid->addWidget(fontSize, row++, 1, 1, 2); Chris@225: Chris@225: subgrid->addWidget(new QLabel(tr("%1:").arg(prefs->getPropertyLabel Chris@337: ("Time To Text Mode"))), Chris@337: row, 0); Chris@337: subgrid->addWidget(ttMode, row++, 1, 1, 2); Chris@337: Chris@337: subgrid->addWidget(new QLabel(tr("%1:").arg(prefs->getPropertyLabel Chris@906: ("Show Hours And Minutes"))), Chris@237: row, 0); Chris@906: subgrid->addWidget(hms, row++, 1, 1, 1); Chris@237: Chris@180: subgrid->setRowStretch(row, 10); Chris@180: Chris@436: m_tabOrdering[AppearanceTab] = m_tabs->count(); Chris@436: m_tabs->addTab(frame, tr("&Appearance")); Chris@180: Chris@180: // Analysis tab Chris@180: Chris@180: frame = new QFrame; Chris@180: subgrid = new QGridLayout; Chris@180: frame->setLayout(subgrid); Chris@180: row = 0; Chris@180: Chris@180: subgrid->addWidget(new QLabel(tr("%1:").arg(prefs->getPropertyLabel Chris@180: ("Tuning Frequency"))), Chris@180: row, 0); Chris@180: subgrid->addWidget(frequency, row++, 1, 1, 2); Chris@180: Chris@702: subgrid->addWidget(new QLabel(tr("%1:").arg(prefs->getPropertyLabel Chris@702: ("Octave Numbering System"))), Chris@702: row, 0); Chris@702: subgrid->addWidget(octaveSystem, row++, 1, 1, 2); Chris@702: Chris@0: subgrid->addWidget(new QLabel(prefs->getPropertyLabel Chris@299: ("Spectrogram Y Smoothing")), Chris@115: row, 0); Chris@115: subgrid->addWidget(smoothing, row++, 1, 1, 2); Chris@0: Chris@299: subgrid->addWidget(new QLabel(prefs->getPropertyLabel Chris@299: ("Spectrogram X Smoothing")), Chris@299: row, 0); Chris@299: subgrid->addWidget(xsmoothing, row++, 1, 1, 2); Chris@299: Chris@0: subgrid->addWidget(new QLabel(tr("%1:").arg(prefs->getPropertyLabel Chris@0: ("Window Type"))), Chris@0: row, 0); Chris@9: subgrid->addWidget(m_windowTypeSelector, row++, 1, 2, 2); Chris@1275: Chris@9: subgrid->setRowStretch(row, 10); Chris@9: row++; Chris@1275: Chris@1275: subgrid->addWidget(new QLabel(tr("Run Vamp plugins in separate process:")), Chris@1275: row, 0); Chris@1275: subgrid->addWidget(vampProcessSeparation, row++, 1, 1, 1); Chris@0: Chris@180: subgrid->setRowStretch(row, 10); Chris@180: Chris@436: m_tabOrdering[AnalysisTab] = m_tabs->count(); Chris@436: m_tabs->addTab(frame, tr("Anal&ysis")); Chris@436: Chris@436: // Template tab Chris@436: Chris@436: frame = new QFrame; Chris@436: subgrid = new QGridLayout; Chris@436: frame->setLayout(subgrid); Chris@436: row = 0; Chris@436: Chris@1436: subgrid->addWidget(new QLabel(tr("Default session template when loading audio files:")), row++, 0); Chris@436: Chris@436: QListWidget *lw = new QListWidget(); Chris@436: subgrid->addWidget(lw, row, 0); Chris@436: subgrid->setRowStretch(row, 10); Chris@436: row++; Chris@436: Chris@1425: subgrid->addWidget(new QLabel(tr("(Use \"%1\" in the File menu to add to these.)") Chris@1425: .arg(tr("Export Session as Template..."))), Chris@1425: row++, 0); Chris@1425: Chris@436: settings.beginGroup("MainWindow"); Chris@436: m_currentTemplate = settings.value("sessiontemplate", "").toString(); Chris@436: settings.endGroup(); Chris@436: Chris@455: lw->addItem(tr("Standard Waveform")); Chris@436: if (m_currentTemplate == "" || m_currentTemplate == "default") { Chris@436: lw->setCurrentRow(lw->count()-1); Chris@436: } Chris@436: m_templates.push_back(""); Chris@436: Chris@436: QStringList templates = ResourceFinder().getResourceFiles("templates", "svt"); Chris@436: Chris@1397: set byName; Chris@436: foreach (QString t, templates) { Chris@436: byName.insert(QFileInfo(t).baseName()); Chris@436: } Chris@436: Chris@436: foreach (QString t, byName) { Chris@436: if (t.toLower() == "default") continue; Chris@436: m_templates.push_back(t); Chris@436: lw->addItem(t); Chris@436: if (m_currentTemplate == t) { Chris@436: lw->setCurrentRow(lw->count()-1); Chris@436: } Chris@436: } Chris@436: Chris@436: connect(lw, SIGNAL(currentRowChanged(int)), this, SLOT(defaultTemplateChanged(int))); Chris@436: Chris@436: m_tabOrdering[TemplateTab] = m_tabs->count(); Chris@436: m_tabs->addTab(frame, tr("Session &Template")); Chris@180: Chris@1436: // Audio IO tab Chris@1436: Chris@1436: frame = new QFrame; Chris@1436: subgrid = new QGridLayout; Chris@1436: frame->setLayout(subgrid); Chris@1436: row = 0; Chris@1436: Chris@1459: if (implementationNames.size() > 1) { Chris@1459: subgrid->addWidget(new QLabel(tr("Audio service:")), row, 0); Chris@1459: subgrid->addWidget(audioImplementation, row++, 1, 1, 2); Chris@1459: } Chris@1436: Chris@1436: subgrid->addWidget(new QLabel(tr("Audio playback device:")), row, 0); Chris@1436: subgrid->addWidget(m_audioPlaybackDeviceCombo, row++, 1, 1, 2); Chris@1436: Chris@1436: subgrid->addWidget(new QLabel(tr("Audio record device:")), row, 0); Chris@1436: subgrid->addWidget(m_audioRecordDeviceCombo, row++, 1, 1, 2); Chris@1436: Chris@1436: subgrid->addWidget(new QLabel(tr("%1:").arg(prefs->getPropertyLabel Chris@1436: ("Use Gapless Mode"))), Chris@1436: row, 0); Chris@1436: subgrid->addWidget(gaplessMode, row++, 1, 1, 1); Chris@1436: Chris@1436: subgrid->addWidget(new QLabel(tr("%1:").arg(prefs->getPropertyLabel Chris@1436: ("Resample On Load"))), Chris@1436: row, 0); Chris@1436: subgrid->addWidget(resampleOnLoad, row++, 1, 1, 1); Chris@1436: Chris@1436: subgrid->setRowStretch(row, 10); Chris@1436: Chris@1436: m_tabOrdering[AudioIOTab] = m_tabs->count(); Chris@1436: m_tabs->addTab(frame, tr("A&udio I/O")); Chris@1812: Chris@1812: // Plugins tab Chris@1812: Chris@1837: m_pluginPathConfigurator = new PluginPathConfigurator(this); Chris@1837: m_pluginPathConfigurator->setPaths(PluginPathSetter::getPaths()); Chris@1837: connect(m_pluginPathConfigurator, SIGNAL(pathsChanged()), Chris@1837: this, SLOT(pluginPathsChanged())); Chris@1812: Chris@1812: m_tabOrdering[PluginTab] = m_tabs->count(); Chris@1837: m_tabs->addTab(m_pluginPathConfigurator, tr("&Plugins")); Chris@1812: Chris@1436: // General tab Chris@1436: Chris@1436: frame = new QFrame; Chris@1436: subgrid = new QGridLayout; Chris@1436: frame->setLayout(subgrid); Chris@1436: row = 0; Chris@1436: Chris@1436: subgrid->addWidget(new QLabel(tr("%1:").arg(tr("User interface language"))), Chris@1436: row, 0); Chris@1436: subgrid->addWidget(locale, row++, 1, 1, 1); Chris@1436: Chris@1436: subgrid->addWidget(new QLabel(tr("%1:").arg(tr("Allow network usage"))), Chris@1436: row, 0); Chris@1436: subgrid->addWidget(networkPermish, row++, 1, 1, 1); Chris@1436: Chris@1436: subgrid->addWidget(new QLabel(tr("%1:").arg(prefs->getPropertyLabel Chris@1436: ("Show Splash Screen"))), Chris@1436: row, 0); Chris@1436: subgrid->addWidget(showSplash, row++, 1, 1, 1); Chris@1436: Chris@1436: subgrid->addWidget(new QLabel(tr("%1:").arg(prefs->getPropertyLabel Chris@1436: ("Temporary Directory Root"))), Chris@1436: row, 0); Chris@1436: subgrid->addWidget(m_tempDirRootEdit, row, 1, 1, 1); Chris@1436: subgrid->addWidget(tempDirButton, row, 2, 1, 1); Chris@1436: row++; Chris@1436: Chris@1436: subgrid->setRowStretch(row, 10); Chris@1436: Chris@1436: m_tabOrdering[GeneralTab] = m_tabs->count(); Chris@1436: m_tabs->addTab(frame, tr("&Other")); Chris@1436: Chris@163: QDialogButtonBox *bb = new QDialogButtonBox(Qt::Horizontal); Chris@163: grid->addWidget(bb, 1, 0); Chris@0: Chris@0: QPushButton *ok = new QPushButton(tr("OK")); Chris@0: QPushButton *cancel = new QPushButton(tr("Cancel")); Chris@163: bb->addButton(ok, QDialogButtonBox::AcceptRole); Chris@163: bb->addButton(m_applyButton, QDialogButtonBox::ApplyRole); Chris@163: bb->addButton(cancel, QDialogButtonBox::RejectRole); Chris@0: connect(ok, SIGNAL(clicked()), this, SLOT(okClicked())); Chris@0: connect(m_applyButton, SIGNAL(clicked()), this, SLOT(applyClicked())); Chris@0: connect(cancel, SIGNAL(clicked()), this, SLOT(cancelClicked())); Chris@0: Chris@0: m_applyButton->setEnabled(false); Chris@0: } Chris@0: Chris@0: PreferencesDialog::~PreferencesDialog() Chris@0: { Chris@438: SVDEBUG << "PreferencesDialog::~PreferencesDialog()" << endl; Chris@0: } Chris@0: Chris@0: void Chris@1397: PreferencesDialog::rebuildDeviceCombos() Chris@1397: { Chris@1397: QSettings settings; Chris@1397: settings.beginGroup("Preferences"); Chris@1397: Chris@1397: vector names = breakfastquay::AudioFactory::getImplementationNames(); Chris@1397: string implementationName; Chris@1459: Chris@1397: if (in_range_for(names, m_audioImplementation-1)) { Chris@1397: implementationName = names[m_audioImplementation-1]; Chris@1397: } Chris@1397: Chris@1397: QString suffix; Chris@1397: if (implementationName != "") { Chris@1397: suffix = "-" + QString(implementationName.c_str()); Chris@1397: } Chris@1397: Chris@1397: names = breakfastquay::AudioFactory::getPlaybackDeviceNames(implementationName); Chris@1397: QString playbackDeviceName = settings.value Chris@1397: ("audio-playback-device" + suffix, "").toString(); Chris@1417: m_audioPlaybackDeviceCombo->clear(); Chris@1397: m_audioPlaybackDeviceCombo->addItem(tr("(auto)")); Chris@1397: m_audioPlaybackDeviceCombo->setCurrentIndex(0); Chris@1397: m_audioPlaybackDevice = 0; Chris@1397: for (int i = 0; in_range_for(names, i); ++i) { Chris@1397: m_audioPlaybackDeviceCombo->addItem(names[i].c_str()); Chris@1397: if (playbackDeviceName.toStdString() == names[i]) { Chris@1397: m_audioPlaybackDeviceCombo->setCurrentIndex(i+1); Chris@1397: m_audioPlaybackDevice = i+1; Chris@1397: } Chris@1397: } Chris@1397: Chris@1397: names = breakfastquay::AudioFactory::getRecordDeviceNames(implementationName); Chris@1397: QString recordDeviceName = settings.value Chris@1397: ("audio-record-device" + suffix, "").toString(); Chris@1417: m_audioRecordDeviceCombo->clear(); Chris@1397: m_audioRecordDeviceCombo->addItem(tr("(auto)")); Chris@1397: m_audioRecordDeviceCombo->setCurrentIndex(0); Chris@1397: m_audioRecordDevice = 0; Chris@1397: for (int i = 0; in_range_for(names, i); ++i) { Chris@1397: m_audioRecordDeviceCombo->addItem(names[i].c_str()); Chris@1397: if (recordDeviceName.toStdString() == names[i]) { Chris@1397: m_audioRecordDeviceCombo->setCurrentIndex(i+1); Chris@1397: m_audioRecordDevice = i+1; Chris@1397: } Chris@1397: } Chris@1397: Chris@1397: settings.endGroup(); Chris@1397: } Chris@1397: Chris@1397: void Chris@436: PreferencesDialog::switchToTab(Tab t) Chris@436: { Chris@436: if (m_tabOrdering.contains(t)) { Chris@436: m_tabs->setCurrentIndex(m_tabOrdering[t]); Chris@436: } Chris@436: } Chris@436: Chris@436: void Chris@9: PreferencesDialog::windowTypeChanged(WindowType type) Chris@0: { Chris@0: m_windowType = type; Chris@0: m_applyButton->setEnabled(true); Chris@0: } Chris@0: Chris@0: void Chris@115: PreferencesDialog::spectrogramSmoothingChanged(int smoothing) Chris@0: { Chris@115: m_spectrogramSmoothing = smoothing; Chris@0: m_applyButton->setEnabled(true); Chris@0: } Chris@0: Chris@0: void Chris@299: PreferencesDialog::spectrogramXSmoothingChanged(int smoothing) Chris@299: { Chris@299: m_spectrogramXSmoothing = smoothing; Chris@299: m_applyButton->setEnabled(true); Chris@299: } Chris@299: Chris@299: void Chris@1096: PreferencesDialog::spectrogramGColourChanged(int colour) Chris@1096: { Chris@1096: m_spectrogramGColour = colour; Chris@1448: m_coloursChanged = true; Chris@1096: m_applyButton->setEnabled(true); Chris@1096: } Chris@1096: Chris@1096: void Chris@1096: PreferencesDialog::spectrogramMColourChanged(int colour) Chris@1096: { Chris@1096: m_spectrogramMColour = colour; Chris@1448: m_coloursChanged = true; Chris@1096: m_applyButton->setEnabled(true); Chris@1096: } Chris@1096: Chris@1096: void Chris@1097: PreferencesDialog::colour3DColourChanged(int colour) Chris@1097: { Chris@1097: m_colour3DColour = colour; Chris@1448: m_coloursChanged = true; Chris@1448: m_applyButton->setEnabled(true); Chris@1448: } Chris@1448: Chris@1448: void Chris@1448: PreferencesDialog::overviewColourChanged(int colour) Chris@1448: { Chris@1448: m_overviewColour = ColourDatabase::getInstance()->getColour(colour); Chris@1448: m_coloursChanged = true; Chris@1097: m_applyButton->setEnabled(true); Chris@1097: } Chris@1097: Chris@1097: void Chris@0: PreferencesDialog::propertyLayoutChanged(int layout) Chris@0: { Chris@0: m_propertyLayout = layout; Chris@0: m_applyButton->setEnabled(true); Chris@0: } Chris@0: Chris@0: void Chris@0: PreferencesDialog::tuningFrequencyChanged(double freq) Chris@0: { Chris@0: m_tuningFrequency = freq; Chris@0: m_applyButton->setEnabled(true); Chris@0: } Chris@0: Chris@0: void Chris@1397: PreferencesDialog::audioImplementationChanged(int s) Chris@263: { Chris@1397: if (m_audioImplementation != s) { Chris@1397: m_audioImplementation = s; Chris@1397: rebuildDeviceCombos(); Chris@1397: m_applyButton->setEnabled(true); Chris@1413: m_audioDeviceChanged = true; Chris@1397: } Chris@1397: } Chris@1397: Chris@1397: void Chris@1397: PreferencesDialog::audioPlaybackDeviceChanged(int s) Chris@1397: { Chris@1397: if (m_audioPlaybackDevice != s) { Chris@1397: m_audioPlaybackDevice = s; Chris@1397: m_applyButton->setEnabled(true); Chris@1413: m_audioDeviceChanged = true; Chris@1397: } Chris@1397: } Chris@1397: Chris@1397: void Chris@1397: PreferencesDialog::audioRecordDeviceChanged(int s) Chris@1397: { Chris@1397: if (m_audioRecordDevice != s) { Chris@1397: m_audioRecordDevice = s; Chris@1397: m_applyButton->setEnabled(true); Chris@1413: m_audioDeviceChanged = true; Chris@1397: } Chris@263: } Chris@263: Chris@263: void Chris@180: PreferencesDialog::resampleOnLoadChanged(int state) Chris@180: { Chris@180: m_resampleOnLoad = (state == Qt::Checked); Chris@180: m_applyButton->setEnabled(true); Chris@180: m_changesOnRestart = true; Chris@180: } Chris@180: Chris@180: void Chris@1379: PreferencesDialog::gaplessModeChanged(int state) Chris@1379: { Chris@1379: m_gapless = (state == Qt::Checked); Chris@1379: m_applyButton->setEnabled(true); Chris@1379: } Chris@1379: Chris@1379: void Chris@1275: PreferencesDialog::vampProcessSeparationChanged(int state) Chris@1275: { Chris@1275: m_runPluginsInProcess = (state == Qt::Unchecked); Chris@1275: m_applyButton->setEnabled(true); Chris@1275: m_changesOnRestart = true; Chris@1275: } Chris@1275: Chris@1275: void Chris@686: PreferencesDialog::networkPermissionChanged(int state) Chris@686: { Chris@686: m_networkPermission = (state == Qt::Checked); Chris@686: m_applyButton->setEnabled(true); Chris@686: m_changesOnRestart = true; Chris@686: } Chris@686: Chris@686: void Chris@950: PreferencesDialog::retinaChanged(int state) Chris@950: { Chris@950: m_retina = (state == Qt::Checked); Chris@950: m_applyButton->setEnabled(true); Chris@950: // Does not require a restart Chris@950: } Chris@950: Chris@950: void Chris@237: PreferencesDialog::showSplashChanged(int state) Chris@237: { Chris@237: m_showSplash = (state == Qt::Checked); Chris@237: m_applyButton->setEnabled(true); Chris@237: m_changesOnRestart = true; Chris@237: } Chris@237: Chris@237: void Chris@436: PreferencesDialog::defaultTemplateChanged(int i) Chris@436: { Chris@436: m_currentTemplate = m_templates[i]; Chris@436: m_applyButton->setEnabled(true); Chris@436: } Chris@436: Chris@436: void Chris@658: PreferencesDialog::localeChanged(int i) Chris@658: { Chris@658: m_currentLocale = m_locales[i]; Chris@658: m_applyButton->setEnabled(true); Chris@658: m_changesOnRestart = true; Chris@658: } Chris@658: Chris@658: void Chris@180: PreferencesDialog::tempDirRootChanged(QString r) Chris@180: { Chris@180: m_tempDirRoot = r; Chris@180: m_applyButton->setEnabled(true); Chris@180: } Chris@180: Chris@180: void Chris@180: PreferencesDialog::tempDirButtonClicked() Chris@180: { Chris@180: QString dir = QFileDialog::getExistingDirectory Chris@180: (this, tr("Select a directory to create cache subdirectory in"), Chris@180: m_tempDirRoot); Chris@180: if (dir == "") return; Chris@180: m_tempDirRootEdit->setText(dir); Chris@180: tempDirRootChanged(dir); Chris@180: m_changesOnRestart = true; Chris@180: } Chris@180: Chris@180: void Chris@180: PreferencesDialog::backgroundModeChanged(int mode) Chris@180: { Chris@180: m_backgroundMode = mode; Chris@180: m_applyButton->setEnabled(true); Chris@180: m_changesOnRestart = true; Chris@180: } Chris@180: Chris@180: void Chris@337: PreferencesDialog::timeToTextModeChanged(int mode) Chris@337: { Chris@337: m_timeToTextMode = mode; Chris@337: m_applyButton->setEnabled(true); Chris@337: } Chris@337: Chris@337: void Chris@906: PreferencesDialog::showHMSChanged(int state) Chris@906: { Chris@906: m_showHMS = (state == Qt::Checked); Chris@906: m_applyButton->setEnabled(true); Chris@906: } Chris@906: Chris@906: void Chris@702: PreferencesDialog::octaveSystemChanged(int system) Chris@702: { Chris@702: m_octaveSystem = system; Chris@702: m_applyButton->setEnabled(true); Chris@702: } Chris@702: Chris@702: void Chris@225: PreferencesDialog::viewFontSizeChanged(int sz) Chris@225: { Chris@225: m_viewFontSize = sz; Chris@225: m_applyButton->setEnabled(true); Chris@225: } Chris@225: Chris@225: void Chris@1837: PreferencesDialog::pluginPathsChanged() Chris@1837: { Chris@1837: m_applyButton->setEnabled(true); Chris@1837: m_changesOnRestart = true; Chris@1837: } Chris@1837: Chris@1837: void Chris@0: PreferencesDialog::okClicked() Chris@0: { Chris@0: applyClicked(); Chris@0: accept(); Chris@0: } Chris@0: Chris@0: void Chris@0: PreferencesDialog::applyClicked() Chris@0: { Chris@0: Preferences *prefs = Preferences::getInstance(); Chris@0: prefs->setWindowType(WindowType(m_windowType)); Chris@115: prefs->setSpectrogramSmoothing(Preferences::SpectrogramSmoothing Chris@115: (m_spectrogramSmoothing)); Chris@299: prefs->setSpectrogramXSmoothing(Preferences::SpectrogramXSmoothing Chris@299: (m_spectrogramXSmoothing)); Chris@0: prefs->setPropertyBoxLayout(Preferences::PropertyBoxLayout Chris@0: (m_propertyLayout)); Chris@0: prefs->setTuningFrequency(m_tuningFrequency); Chris@180: prefs->setResampleOnLoad(m_resampleOnLoad); Chris@1379: prefs->setUseGaplessMode(m_gapless); Chris@1276: prefs->setRunPluginsInProcess(m_runPluginsInProcess); Chris@237: prefs->setShowSplash(m_showSplash); Chris@180: prefs->setTemporaryDirectoryRoot(m_tempDirRoot); Chris@180: prefs->setBackgroundMode(Preferences::BackgroundMode(m_backgroundMode)); Chris@337: prefs->setTimeToTextMode(Preferences::TimeToTextMode(m_timeToTextMode)); Chris@906: prefs->setShowHMS(m_showHMS); Chris@225: prefs->setViewFontSize(m_viewFontSize); Chris@263: Chris@702: prefs->setProperty("Octave Numbering System", m_octaveSystem); Chris@702: Chris@263: QSettings settings; Chris@263: settings.beginGroup("Preferences"); Chris@686: QString permishTag = QString("network-permission-%1").arg(SV_VERSION); Chris@686: settings.setValue(permishTag, m_networkPermission); Chris@1397: Chris@1397: vector names = breakfastquay::AudioFactory::getImplementationNames(); Chris@1397: string implementationName; Chris@1415: if (m_audioImplementation > int(names.size())) { Chris@1415: m_audioImplementation = 0; Chris@1415: } Chris@1397: if (m_audioImplementation > 0) { Chris@1397: implementationName = names[m_audioImplementation-1]; Chris@1397: } Chris@1397: settings.setValue("audio-target", implementationName.c_str()); Chris@1397: Chris@1397: QString suffix; Chris@1397: if (implementationName != "") { Chris@1397: suffix = "-" + QString(implementationName.c_str()); Chris@1397: } Chris@1397: Chris@1397: names = breakfastquay::AudioFactory::getPlaybackDeviceNames(implementationName); Chris@1397: string deviceName; Chris@1415: if (m_audioPlaybackDevice > int(names.size())) { Chris@1415: m_audioPlaybackDevice = 0; Chris@1415: } Chris@1397: if (m_audioPlaybackDevice > 0) { Chris@1397: deviceName = names[m_audioPlaybackDevice-1]; Chris@1397: } Chris@1397: settings.setValue("audio-playback-device" + suffix, deviceName.c_str()); Chris@1397: Chris@1397: names = breakfastquay::AudioFactory::getRecordDeviceNames(implementationName); Chris@1397: deviceName = ""; Chris@1415: if (m_audioRecordDevice > int(names.size())) { Chris@1415: m_audioRecordDevice = 0; Chris@1415: } Chris@1397: if (m_audioRecordDevice > 0) { Chris@1397: deviceName = names[m_audioRecordDevice-1]; Chris@1397: } Chris@1397: settings.setValue("audio-record-device" + suffix, deviceName.c_str()); Chris@1397: Chris@686: settings.setValue("locale", m_currentLocale); Chris@950: #ifdef Q_OS_MAC Chris@950: settings.setValue("scaledHiDpi", m_retina); Chris@950: #endif Chris@1096: settings.setValue("spectrogram-colour", m_spectrogramGColour); Chris@1096: settings.setValue("spectrogram-melodic-colour", m_spectrogramMColour); Chris@1097: settings.setValue("colour-3d-plot-colour", m_colour3DColour); cannam@1463: settings.setValue("overview-colour", m_overviewColour.name()); Chris@263: settings.endGroup(); Chris@180: Chris@436: settings.beginGroup("MainWindow"); Chris@436: settings.setValue("sessiontemplate", m_currentTemplate); Chris@436: settings.endGroup(); Chris@436: Chris@0: m_applyButton->setEnabled(false); Chris@180: Chris@180: if (m_changesOnRestart) { Chris@180: QMessageBox::information(this, tr("Preferences"), Chris@255: tr("Restart required

One or more of the application preferences you have changed may not take full effect until Sonic Visualiser is restarted.

Please exit and restart the application now if you want these changes to take effect immediately.

")); Chris@180: m_changesOnRestart = false; Chris@180: } Chris@1413: Chris@1413: if (m_audioDeviceChanged) { Chris@1413: emit audioDeviceChanged(); Chris@1415: m_audioDeviceChanged = false; Chris@1413: } Chris@1448: Chris@1448: if (m_coloursChanged) { Chris@1448: emit coloursChanged(); Chris@1448: m_coloursChanged = false; Chris@1448: } Chris@1837: Chris@1837: PluginPathSetter::savePathSettings(m_pluginPathConfigurator->getPaths()); Chris@0: } Chris@0: Chris@0: void Chris@0: PreferencesDialog::cancelClicked() Chris@0: { Chris@0: reject(); Chris@0: } Chris@0: Chris@163: void Chris@163: PreferencesDialog::applicationClosing(bool quickly) Chris@163: { Chris@163: if (quickly) { Chris@163: reject(); Chris@163: return; Chris@163: } Chris@163: Chris@163: if (m_applyButton->isEnabled()) { Chris@163: int rv = QMessageBox::warning Chris@163: (this, tr("Preferences Changed"), Chris@163: tr("Some preferences have been changed but not applied.\n" Chris@163: "Apply them before closing?"), Chris@163: QMessageBox::Apply | QMessageBox::Discard, Chris@163: QMessageBox::Discard); Chris@163: if (rv == QMessageBox::Apply) { Chris@163: applyClicked(); Chris@163: accept(); Chris@163: } else { Chris@163: reject(); Chris@163: } Chris@163: } else { Chris@163: accept(); Chris@163: } Chris@163: } Chris@163: