1 /***************************************************************************
2 * Copyright (C) 2009 by Alex Montgomery and Nedko Arnaudov *
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. *
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. *
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>
23 #include <QTextStream>
26 #include <QCloseEvent>
28 #include "mainWindow.h"
29 #include "validator.h"
30 #include "sequencerThread.h"
33 #include "../../common.h"
36 MainWindow::MainWindow() : m_sequencerThread(0), m_settingsMutex(QMutex::Recursive
)
38 setupUi(this); // use the UI layout generated by qjackmmc.ui
40 // make sure the fps and jitter edit boxes only take positive whole numbers
41 QValidator
* validator
= new Validator(this);
42 fpsEdit
->setValidator(validator
);
43 jitterEdit
->setValidator(validator
);
45 // make sure the device edit box only take positive hexadecimal values
46 validator
= new HexValidator(this);
47 deviceEdit
->setValidator(validator
);
49 // connect all UI editable elements
50 connect(fpsEdit
, SIGNAL(editingFinished()), this, SLOT(onValidateFps()));
51 connect(jitterEdit
, SIGNAL(editingFinished()), this, SLOT(onValidateJitter()));
52 connect(deviceEdit
, SIGNAL(editingFinished()), this, SLOT(onValidateDeviceID()));
53 connect(rtBox
, SIGNAL(clicked(bool)), this, SLOT(onRealtimeChanged(bool)));
54 connect(verboseBox
, SIGNAL(clicked(bool)), this, SLOT(onVerboseChanged(bool)));
55 connect(whatsThisButton
, SIGNAL(clicked()), this, SLOT(on_actionWhat_triggered()));
57 // set the program's icon
58 const QString
iconPath(QString(ICON_DIR
) + "/qjackmmc.png");
59 if (QFile::exists(iconPath
))
60 setWindowIcon(QPixmap(iconPath
));
63 bool MainWindow::init(int argc
, char *argv
[])
65 bool succeeded
= initSound(argc
, argv
); // setup Jack, ALSA, and Lash so the user can connect MIDI to the program
71 // load the default configuration file if it exists
72 QFile
loadFile(QDir::homePath() + QJACKMMC_CONFIG
);
73 if (loadFile
.exists())
78 // start the MMC listener thread
79 Q_ASSERT(!m_sequencerThread
);
80 m_sequencerThread
= new SequencerThread(this, &m_settings
, &m_settingsMutex
);
81 m_sequencerThread
->listen(rtBox
->isChecked());
83 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.");
89 void MainWindow::on_actionQuit_triggered()
94 void MainWindow::on_actionAbout_triggered()
98 #include "../../VERSION"
101 QMessageBox
* aboutBox
= new QMessageBox(this);
102 aboutBox
->setText(message
);
106 void MainWindow::on_actionWhat_triggered()
108 QWhatsThis::enterWhatsThisMode();
111 void MainWindow::on_loadButton_clicked()
113 QFile
loadFile(QDir::homePath() + QJACKMMC_CONFIG
);
114 if (loadFile
.exists())
115 loadConfig(loadFile
);
118 QMessageBox
* fileError
= new QMessageBox(this);
119 fileError
->setText(QDir::homePath() + QString(QJACKMMC_CONFIG
) + " does not exist. You must save a default configuration before you can load one.");
124 void MainWindow::on_saveButton_clicked()
126 QFile
saveFile(QDir::homePath() + QJACKMMC_CONFIG
);
127 if (saveFile
.open(QIODevice::WriteOnly
| QIODevice::Text
))
129 QTextStream
out(&saveFile
);
131 // write out check boxes
133 boolVal
= (verboseBox
->isChecked() ? 1 : 0);
134 out
<< boolVal
<< endl
;
135 boolVal
= (rtBox
->isChecked() ? 1 : 0);
136 out
<< boolVal
<< endl
;
138 // note: this value is ignored but written for backwards compatibility.
139 // It used to signify "listen on startup" but now QJackMMC is always listening.
141 out
<< boolVal
<< endl
;
143 // write out text boxes
144 out
<< fpsEdit
->text() << endl
;
145 out
<< jitterEdit
->text() << endl
;
146 out
<< deviceEdit
->text() << endl
;
148 if (saveFile
.error() != QFile::NoError
)
150 QMessageBox
* jackError
= new QMessageBox(this);
151 jackError
->setText("An error occurred while writing to " + QDir::homePath() + QString(QJACKMMC_CONFIG
));
157 QMessageBox
* fileError
= new QMessageBox(this);
158 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.");
164 void MainWindow::onValidateFps()
166 // validate setting, revert to prior value if not valid
168 int fps
= fpsEdit
->text().toInt(&ok
);
170 // we go to great lengths to make sure that the input boxes only accept positive integers, but check the fields just in case
173 QMessageBox
* inputError
= new QMessageBox(this);
174 inputError
->setText("the frames / sec parameter needs to be a positive integer.");
178 QMutexLocker
settingsLock(&m_settingsMutex
);
179 fpsEdit
->setText(QString::number(m_settings
.frameRate
));
183 QMutexLocker
settingsLock(&m_settingsMutex
);
184 m_settings
.frameRate
= fps
;
188 void MainWindow::onValidateJitter()
190 // validate setting, revert to prior value if not valid
192 int jitterTolerance
= jitterEdit
->text().toInt(&ok
);
193 if (!ok
|| jitterTolerance
< 0)
195 QMessageBox
* inputError
= new QMessageBox(this);
196 inputError
->setText("the jitter tolerance needs to be a positive integer.");
200 QMutexLocker
settingsLock(&m_settingsMutex
);
201 jitterEdit
->setText(QString::number(m_settings
.jitterTolerance
));
205 QMutexLocker
settingsLock(&m_settingsMutex
);
206 m_settings
.jitterTolerance
= jitterTolerance
;
210 void MainWindow::onValidateDeviceID()
212 // validate setting, revert to prior value if not valid
214 int deviceID
= deviceEdit
->text().toInt(&ok
, 16);
215 if (!ok
|| deviceID
> 255 || deviceID
< 0)
217 QMessageBox
* inputError
= new QMessageBox(this);
218 inputError
->setText("the deviceID needs to be a hexadecimal number between 0 and ff.");
222 QMutexLocker
settingsLock(&m_settingsMutex
);
223 deviceEdit
->setText(QString::number(m_settings
.deviceID
));
227 QMutexLocker
settingsLock(&m_settingsMutex
);
228 m_settings
.deviceID
= (uint8_t) deviceID
;
233 void MainWindow::onVerboseChanged(bool checked
)
235 QMutexLocker
settingsLock(&m_settingsMutex
);
236 m_settings
.verbose
= checked
;
239 void MainWindow::onRealtimeChanged(bool checked
)
241 if (m_sequencerThread
)
242 m_sequencerThread
->die(); // die() calls deleteLater and ensures the thread is cleaned up once the listen loop ends
244 m_sequencerThread
= new SequencerThread(this, &m_settings
, &m_settingsMutex
);
245 m_sequencerThread
->listen(checked
);
249 void MainWindow::onMessageReceived(QString message
)
251 messageArea
->append(message
);
254 bool MainWindow::initSound(int argc
, char *argv
[])
256 bool succeeded
= true, alsaPortCreated
= false, jackPortCreated
= false;
257 int ret
= init_alsa_sequencer("QJjackMMC");
260 QMessageBox
* alsaError
= new QMessageBox(this);
261 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.");
265 alsaPortCreated
= true;
267 if (succeeded
&& init_jack("QJjackMMC") < 0)
269 QMessageBox
* jackError
= new QMessageBox(this);
270 jackError
->setText("couldn't connect to the JACK server. Would you like to start one with default parameters? (Answering \"No\" will close this program.)");
271 jackError
->addButton(QMessageBox::Yes
);
272 jackError
->addButton(QMessageBox::No
);
273 if (jackError
->exec() == QMessageBox::No
)
277 init_lash(argc
, argv
);
281 #endif // LASH_SUPPORT
283 #if JACK_MIDI_SUPPORT
286 if (!init_jack_midi(&m_settings
))
288 QMessageBox
* activateError
= new QMessageBox(this);
289 activateError
->setText("couldn't activate JACK midi, you will not be able to connect MIDI devices to this program using JACK midi.");
290 activateError
->exec();
293 jackPortCreated
= true;
295 #endif // JACK_MIDI_SUPPORT
297 if (succeeded
&& activate_jack() != 0)
299 QMessageBox
* activateError
= new QMessageBox(this);
300 activateError
->setText("couldn't activate JACK, Please check your JACK installation and rerun this program.");
301 activateError
->exec();
305 if (jackPortCreated
== false && alsaPortCreated
== false)
307 QMessageBox
* activateError
= new QMessageBox(this);
308 activateError
->setText("Neither JACK midi nor ALSA midi could be initialized, bailing out.");
309 activateError
->exec();
315 void MainWindow::loadConfig(QFile
& loadFile
)
317 if (loadFile
.open(QIODevice::ReadOnly
| QIODevice::Text
))
319 QTextStream
in(&loadFile
);
321 // read in check boxes
324 verboseBox
->setChecked(boolVal
== 1);
325 m_settings
.verbose
= boolVal
== 1;
327 rtBox
->setChecked(boolVal
== 1);
329 // note: this value is ignored but read for backwards compatibility.
330 // It used to signify "listen on startup" but now QJackMMC is always listening.
333 // read in edit boxes
335 in
>> value
; fpsEdit
->setText(value
); onValidateFps();
336 in
>> value
; jitterEdit
->setText(value
); onValidateJitter();
337 in
>> value
; deviceEdit
->setText(value
); onValidateDeviceID();
339 if (loadFile
.error() != QFile::NoError
)
341 QMessageBox
* jackError
= new QMessageBox(this);
342 jackError
->setText("An error occurred while reading " + QDir::homePath() + QString(QJACKMMC_CONFIG
) +
343 ". The file is either nonexistent, corrupt, or from an older version of QJackMMC. Please set the QJackMMC parameters \
344 how you like them and click \"Save as Default settings\".");
350 QMessageBox
* fileError
= new QMessageBox(this);
351 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 void MainWindow::setDefaultSettings()
358 QMutexLocker
settingsLocker(&m_settingsMutex
);
359 m_settings
.deviceID
= 0x7f;
360 m_settings
.frameRate
= 30;
361 m_settings
.jitterTolerance
= 50;
362 m_settings
.verbose
= false;
365 void MainWindow::closeEvent(QCloseEvent
* event
)
367 // separating the clean-up into the close event (instead of the destructor) prevents an XRun on shutdown
368 if (m_sequencerThread
)
370 m_sequencerThread
->die(); // die() calls deleteLater and ensures the thread is cleaned up once the listen loop ends
371 m_sequencerThread
->wait();