redesigned QJackMMC to be always listening, threadsafe
[jackctlmmc.git] / qt / src / mainWindow.cpp
blobd2f1e2d8c2315727c5d8b5d71ef3a75271f69b00
1 /***************************************************************************
2 * Copyright (C) 2009 by Alex Montgomery and Nedko Arnaudov *
3 * check@Adaon *
4 * *
5 * This program is free software; you can redistribute it and/or modify *
6 * it under the terms of the GNU General Public License as published by *
7 * the Free Software Foundation; either version 2 of the License, or *
8 * (at your option) any later version. *
9 * *
10 * This program is distributed in the hope that it will be useful, *
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13 * GNU General Public License for more details. *
14 * *
15 * You should have received a copy of the GNU General Public License *
16 * along with this program; if not, write to the *
17 * Free Software Foundation, Inc., *
18 * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
19 ***************************************************************************/
21 #include <QMessageBox>
22 #include <QFile>
23 #include <QTextStream>
24 #include <QDir>
25 #include <QWhatsThis>
27 #include "mainWindow.h"
28 #include "validator.h"
29 #include "sequencerThread.h"
31 extern "C" {
32 #include "../../common.h"
35 MainWindow::MainWindow() : m_sequencerThread(0), m_settingsMutex(QMutex::Recursive)
37 setupUi(this); // use the UI layout generated by qjackmmc.ui
39 // make sure the fps and jitter edit boxes only take positive whole numbers
40 QValidator* validator = new Validator(this);
41 fpsEdit->setValidator(validator);
42 jitterEdit->setValidator(validator);
44 // make sure the device edit box only take positive hexadecimal values
45 validator = new HexValidator(this);
46 deviceEdit->setValidator(validator);
48 // connect all UI editable elements
49 connect(fpsEdit, SIGNAL(editingFinished()), this, SLOT(onValidateFps()));
50 connect(jitterEdit, SIGNAL(editingFinished()), this, SLOT(onValidateJitter()));
51 connect(deviceEdit, SIGNAL(editingFinished()), this, SLOT(onValidateDeviceID()));
52 connect(rtBox, SIGNAL(clicked(bool)), this, SLOT(onRealtimeChanged(bool)));
53 connect(verboseBox, SIGNAL(clicked(bool)), this, SLOT(onVerboseChanged(bool)));
54 connect(whatsThisButton, SIGNAL(clicked()), this, SLOT(on_actionWhat_triggered()));
56 // set the program's icon
57 const QString iconPath(QString(ICON_DIR) + "/qjackmmc.png");
58 if (QFile::exists(iconPath))
59 setWindowIcon(QPixmap(iconPath));
62 bool MainWindow::init(int argc, char *argv[])
64 bool succeeded = initSound(argc, argv); // setup Jack, ALSA, and Lash so the user can connect MIDI to the program
66 if (succeeded)
68 setDefaultSettings();
70 // load the default configuration file if it exists
71 QFile loadFile(QDir::homePath() + QJACKMMC_CONFIG);
72 if (loadFile.exists())
73 loadConfig(loadFile);
77 // start the MMC listener thread
78 Q_ASSERT(!m_sequencerThread);
79 m_sequencerThread = new SequencerThread(this, &m_settings, &m_settingsMutex);
80 m_sequencerThread->listen(rtBox->isChecked());
82 printMMCMessage("QJackMMC is actively listening for MMC messages. If you want to see information about them as they come in, make sure \"Verbose output\" is checked.");
85 return succeeded;
88 MainWindow::~MainWindow()
90 cleanup_globals();
93 void MainWindow::on_actionQuit_triggered()
95 close();
98 void MainWindow::on_actionAbout_triggered()
100 QString message;
101 message = QString(
102 #include "../../VERSION"
105 QMessageBox* aboutBox = new QMessageBox(this);
106 aboutBox->setText(message);
107 aboutBox->exec();
110 void MainWindow::on_actionWhat_triggered()
112 QWhatsThis::enterWhatsThisMode();
115 void MainWindow::on_loadButton_clicked()
117 QFile loadFile(QDir::homePath() + QJACKMMC_CONFIG);
118 if (loadFile.exists())
119 loadConfig(loadFile);
120 else
122 QMessageBox* fileError = new QMessageBox(this);
123 fileError->setText(QDir::homePath() + QString(QJACKMMC_CONFIG) + " does not exist. You must save a default configuration before you can load one.");
124 fileError->exec();
128 void MainWindow::on_saveButton_clicked()
130 QFile saveFile(QDir::homePath() + QJACKMMC_CONFIG);
131 if (saveFile.open(QIODevice::WriteOnly | QIODevice::Text))
133 QTextStream out(&saveFile);
135 // write out check boxes
136 int boolVal;
137 boolVal = (verboseBox->isChecked() ? 1 : 0);
138 out << boolVal << endl;
139 boolVal = (rtBox->isChecked() ? 1 : 0);
140 out << boolVal << endl;
142 // note: this value is ignored but written for backwards compatibility.
143 // It used to signify "listen on startup" but now QJackMMC is always listening.
144 boolVal = false;
145 out << boolVal << endl;
147 // write out text boxes
148 out << fpsEdit->text() << endl;
149 out << jitterEdit->text() << endl;
150 out << deviceEdit->text() << endl;
152 if (saveFile.error() != QFile::NoError)
154 QMessageBox* jackError = new QMessageBox(this);
155 jackError->setText("An error occurred while writing to " + QDir::homePath() + QString(QJACKMMC_CONFIG));
156 jackError->exec();
159 else
161 QMessageBox* fileError = new QMessageBox(this);
162 fileError->setText(QDir::homePath() + QString(QJACKMMC_CONFIG) + " cannot be opened for writing. Make sure that you have permission to write to that directory and file.");
163 fileError->exec();
168 void MainWindow::onValidateFps()
170 // validate setting, revert to prior value if not valid
171 bool ok = true;
172 int fps = fpsEdit->text().toInt(&ok);
174 // we go to great lengths to make sure that the input boxes only accept positive integers, but check the fields just in case
175 if (!ok || fps < 0)
177 QMessageBox* inputError = new QMessageBox(this);
178 inputError->setText("the frames / sec parameter needs to be a positive integer.");
179 inputError->exec();
181 // revert value
182 QMutexLocker settingsLock(&m_settingsMutex);
183 fpsEdit->setText(QString::number(m_settings.frameRate));
185 else
187 QMutexLocker settingsLock(&m_settingsMutex);
188 m_settings.frameRate = fps;
192 void MainWindow::onValidateJitter()
194 // validate setting, revert to prior value if not valid
195 bool ok = true;
196 int jitterTolerance = jitterEdit->text().toInt(&ok);
197 if (!ok || jitterTolerance < 0)
199 QMessageBox* inputError = new QMessageBox(this);
200 inputError->setText("the jitter tolerance needs to be a positive integer.");
201 inputError->exec();
203 // revert value
204 QMutexLocker settingsLock(&m_settingsMutex);
205 jitterEdit->setText(QString::number(m_settings.jitterTolerance));
207 else
209 QMutexLocker settingsLock(&m_settingsMutex);
210 m_settings.jitterTolerance = jitterTolerance;
214 void MainWindow::onValidateDeviceID()
216 // validate setting, revert to prior value if not valid
217 bool ok = true;
218 int deviceID = deviceEdit->text().toInt(&ok, 16);
219 if (!ok || deviceID > 255 || deviceID < 0)
221 QMessageBox* inputError = new QMessageBox(this);
222 inputError->setText("the deviceID needs to be a hexadecimal number between 0 and ff.");
223 inputError->exec();
225 // revert value
226 QMutexLocker settingsLock(&m_settingsMutex);
227 deviceEdit->setText(QString::number(m_settings.deviceID));
229 else
231 QMutexLocker settingsLock(&m_settingsMutex);
232 m_settings.deviceID = (uint8_t) deviceID;
237 void MainWindow::onVerboseChanged(bool checked)
239 QMutexLocker settingsLock(&m_settingsMutex);
240 m_settings.verbose = checked;
243 void MainWindow::onRealtimeChanged(bool checked)
245 if (m_sequencerThread)
246 m_sequencerThread->die(); // die() calls deleteLater and ensures the thread is cleaned up once the listen loop ends
248 m_sequencerThread = new SequencerThread(this, &m_settings, &m_settingsMutex);
249 m_sequencerThread->listen(checked);
253 void MainWindow::onMessageReceived(QString message)
255 messageArea->append(message);
258 bool MainWindow::initSound(int argc, char *argv[])
260 bool succeeded = true, alsaPortCreated = false, jackPortCreated = false;
261 int ret = init_alsa_sequencer("QJjackMMC");
262 if (ret < 0)
264 QMessageBox* alsaError = new QMessageBox(this);
265 alsaError->setText("Can't create alsa sequencer. You will not be able to connect MIDI devices to this program using ALSA. Jack Midi might still function.");
266 alsaError->exec();
268 else
269 alsaPortCreated = true;
271 if (succeeded && init_jack("QJjackMMC") < 0)
273 QMessageBox* jackError = new QMessageBox(this);
274 jackError->setText("couldn't connect to the JACK server. Would you like to start one with default parameters? (Answering \"No\" will close this program.)");
275 jackError->addButton(QMessageBox::Yes);
276 jackError->addButton(QMessageBox::No);
277 if (jackError->exec() == QMessageBox::No)
278 succeeded = false;
280 #if LASH_SUPPORT
281 init_lash(argc, argv);
282 #else
283 Q_UNUSED(argc);
284 Q_UNUSED(argv);
285 #endif // LASH_SUPPORT
287 #if JACK_MIDI_SUPPORT
288 if (succeeded)
290 if (!init_jack_midi(&m_settings))
292 QMessageBox* activateError = new QMessageBox(this);
293 activateError->setText("couldn't activate JACK midi, you will not be able to connect MIDI devices to this program using JACK midi.");
294 activateError->exec();
296 else
297 jackPortCreated = true;
299 #endif // JACK_MIDI_SUPPORT
301 if (succeeded && activate_jack() != 0)
303 QMessageBox* activateError = new QMessageBox(this);
304 activateError->setText("couldn't activate JACK, Please check your JACK installation and rerun this program.");
305 activateError->exec();
306 succeeded = false;
309 if (jackPortCreated == false && alsaPortCreated == false)
311 QMessageBox* activateError = new QMessageBox(this);
312 activateError->setText("Neither JACK midi nor ALSA midi could be initialized, bailing out.");
313 activateError->exec();
314 succeeded = false;
316 return succeeded;
319 void MainWindow::loadConfig(QFile& loadFile)
321 if (loadFile.open(QIODevice::ReadOnly | QIODevice::Text))
323 QTextStream in(&loadFile);
325 // read in check boxes
326 int boolVal;
327 in >> boolVal;
328 verboseBox->setChecked(boolVal == 1);
329 m_settings.verbose = boolVal == 1;
330 in >> boolVal;
331 rtBox->setChecked(boolVal == 1);
333 // note: this value is ignored but read for backwards compatibility.
334 // It used to signify "listen on startup" but now QJackMMC is always listening.
335 in >> boolVal;
337 // read in edit boxes
338 QString value;
339 in >> value; fpsEdit->setText(value); onValidateFps();
340 in >> value; jitterEdit->setText(value); onValidateJitter();
341 in >> value; deviceEdit->setText(value); onValidateDeviceID();
343 if (loadFile.error() != QFile::NoError)
345 QMessageBox* jackError = new QMessageBox(this);
346 jackError->setText("An error occurred while reading " + QDir::homePath() + QString(QJACKMMC_CONFIG) +
347 ". The file is either nonexistent, corrupt, or from an older version of QJackMMC. Please set the QJackMMC parameters \
348 how you like them and click \"Save as Default settings\".");
349 jackError->exec();
352 else
354 QMessageBox* fileError = new QMessageBox(this);
355 fileError->setText(QDir::homePath() + QString(QJACKMMC_CONFIG) + " cannot be opened for reading. It's either corrupt, or you don't have permission to read it.");
356 fileError->exec();
360 void MainWindow::setDefaultSettings()
362 QMutexLocker settingsLocker(&m_settingsMutex);
363 m_settings.deviceID = 0x7f;
364 m_settings.frameRate = 30;
365 m_settings.jitterTolerance = 50;
366 m_settings.verbose = false;