1 /*!
2 * @file mainwindow.cpp
3 * @brief Implements the MainWindow top-level UI class.
4 *
5 *
6 * Copyright 2009 - 2017 <qmidiarp-devel@lists.sourceforge.net>
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
21 * MA 02110-1301, USA.
22 *
23 */
24 #include <QDir>
25 #include <QFile>
26 #include <QFileDialog>
27 #include <QPixmap>
28 #include <QInputDialog>
29 #include <QMenu>
30 #include <QMenuBar>
31 #include <QMetaType>
32 #include <QSocketNotifier>
33 #include <QStringList>
34 #include <QSpinBox>
35 #include <QStyle>
36 #include <QTextStream>
37 #include <QXmlStreamReader>
38 #include <QXmlStreamWriter>
39
40 #include <cerrno> // for errno
41 #include <csignal> // for sigaction()
42 #include <cstring> // for strerror()
43 #include <unistd.h> // for pipe()
44
45 #include <iostream>
46
47 #include "mainwindow.h"
48
49 #include "pixmaps/qmidiarp2.xpm"
50 #include "pixmaps/arpadd.xpm"
51 #include "pixmaps/lfoadd.xpm"
52 #include "pixmaps/seqadd.xpm"
53 #include "pixmaps/settings.xpm"
54 #include "pixmaps/eventlog.xpm"
55 #include "pixmaps/globtog.xpm"
56 #include "pixmaps/groovetog.xpm"
57 #include "pixmaps/play.xpm"
58 #include "pixmaps/midiclock.xpm"
59 #include "pixmaps/jacktr.xpm"
60 #include "pixmaps/fileopen.xpm"
61 #include "pixmaps/filenew.xpm"
62 #include "pixmaps/filesave.xpm"
63 #include "pixmaps/filesaveas.xpm"
64 #include "pixmaps/filequit.xpm"
65 #include "pixmaps/iopanelshow.xpm"
66 #include "pixmaps/iopanelhide.xpm"
67 #include "pixmaps/midicontrol.xpm"
68
69
70 static const char FILEEXT[] = ".qmax";
71
72 int MainWindow::sigpipe[2];
73 #ifdef NSM
74 nsm_client_t *MainWindow::nsm = 0;
75 #endif
76
MainWindow(int p_portCount,bool p_alsamidi,char * execName)77 MainWindow::MainWindow(int p_portCount, bool p_alsamidi, char *execName)
78 {
79 #ifndef NSM
80 (void)execName;
81 #endif
82 jackFailed = false;
83 filename = "";
84 lastDir = QDir::homePath();
85 alsaMidi = p_alsamidi;
86
87 grooveWidget = new GrooveWidget;
88 grooveWindow = new QDockWidget(tr("Groove"));
89 grooveWindow->setFeatures(QDockWidget::DockWidgetClosable
90 | QDockWidget::DockWidgetMovable
91 | QDockWidget::DockWidgetFloatable);
92 grooveWindow->setWidget(grooveWidget);
93 grooveWindow->setObjectName("grooveWidget");
94 grooveWindow->setVisible(true);
95
96 globStore = new GlobStore(this);
97 globStoreWindow = new QDockWidget(tr("Global Store"));
98 globStoreWindow->setFeatures(QDockWidget::DockWidgetClosable
99 | QDockWidget::DockWidgetMovable
100 | QDockWidget::DockWidgetFloatable);
101 globStoreWindow->setWidget(globStore);
102 globStoreWindow->setObjectName("globStore");
103 globStoreWindow->setVisible(true);
104
105 #ifdef NSM
106 const char *nsm_url = getenv( "NSM_URL" );
107
108 if ( nsm_url )
109 {
110 nsm = nsm_new();
111
112 nsm_set_open_callback( nsm, cb_nsm_open, this );
113 nsm_set_save_callback( nsm, cb_nsm_save, this );
114
115 if ( 0 == nsm_init_thread( nsm, nsm_url ) )
116 {
117 connect(this, SIGNAL(nsmOpenFile(const QString &)), this,
118 SLOT(openFile(const QString &)));
119 nsm_send_announce( nsm, APP_NAME, ":switch:", execName );
120 nsm_thread_start(nsm);
121 }
122 else
123 {
124 nsm_free( nsm );
125 nsm = 0;
126 }
127 }
128 #else
129 bool nsm = 0;
130 #endif
131
132 engine = new Engine(globStore, grooveWidget, p_portCount, alsaMidi, this);
133
134 if (alsaMidi) {
135 connect(engine->jackSync, SIGNAL(j_shutdown()), this, SLOT(jackShutdown()));
136 }
137 else {
138 connect(engine->driver, SIGNAL(jsEvent(int)), this, SLOT(jsAction(int)));
139 connect(engine->driver, SIGNAL(j_shutdown()), this, SLOT(jackShutdown()));
140 if (!nsm && engine->driver->callJack(p_portCount, PACKAGE)) jackFailed = true;
141 }
142 connect(engine, SIGNAL(tempoUpdated(double)), this,
143 SLOT(displayTempo(double)));
144
145 connect(globStore, SIGNAL(store(int)), engine,
146 SLOT(store(int)));
147 connect(globStore, SIGNAL(requestRestore(int)), engine,
148 SLOT(requestRestore(int)));
149 connect(globStore, SIGNAL(updateGlobRestoreTimeModule(int)), engine,
150 SLOT(updateGlobRestoreTimeModule(int)));
151 connect(globStore, SIGNAL(removeParStores(int)), engine,
152 SLOT(removeParStores(int)));
153
154 midiCCTable = new MidiCCTable(engine, this);
155
156 logWidget = new LogWidget(this);
157 logWindow = new QDockWidget(tr("Event Log"), this);
158 logWindow->setFeatures(QDockWidget::DockWidgetClosable
159 | QDockWidget::DockWidgetMovable
160 | QDockWidget::DockWidgetFloatable);
161 logWindow->setWidget(logWidget);
162 logWindow->setObjectName("logWidget");
163 qRegisterMetaType<MidiEvent>("MidiEvent");
164 connect(engine, SIGNAL(midiEventReceived(MidiEvent, int)),
165 logWidget, SLOT(appendEvent(MidiEvent, int)));
166
167 connect(logWidget, SIGNAL(sendLogEvents(bool)),
168 engine, SLOT(setSendLogEvents(bool)));
169
170 addDockWidget(Qt::BottomDockWidgetArea, globStoreWindow);
171 addDockWidget(Qt::BottomDockWidgetArea, grooveWindow);
172 addDockWidget(Qt::BottomDockWidgetArea, logWindow);
173
174 passWidget = new PassWidget(engine, p_portCount, this);
175
176 addArpAction = new QAction(QPixmap(arpadd_xpm), tr("&New Arp..."), this);
177 addArpAction->setShortcut(QKeySequence(tr("Ctrl+A", "Module|New Arp")));
178 addArpAction->setToolTip(tr("Add new arpeggiator to tab bar"));
179 connect(addArpAction, SIGNAL(triggered()), this, SLOT(arpNew()));
180
181 addLfoAction = new QAction(QPixmap(lfoadd_xpm), tr("&New LFO..."), this);
182 addLfoAction->setShortcut(QKeySequence(tr("Ctrl+L", "Module|New LFO")));
183 addLfoAction->setToolTip(tr("Add new LFO to tab bar"));
184 connect(addLfoAction, SIGNAL(triggered()), this, SLOT(lfoNew()));
185
186 addSeqAction = new QAction(QPixmap(seqadd_xpm), tr("&New Sequencer..."), this);
187 addSeqAction->setShortcut(QKeySequence(tr("Ctrl+T", "Module|New Sequencer")));
188 addSeqAction->setToolTip(tr("Add new Sequencer to tab bar"));
189 connect(addSeqAction, SIGNAL(triggered()), this, SLOT(seqNew()));
190
191
192 fileNewAction = new QAction(QPixmap(filenew_xpm), tr("&New"), this);
193 fileNewAction->setShortcut(QKeySequence(QKeySequence::New));
194 fileNewAction->setToolTip(tr("Create new QMidiArp session"));
195 connect(fileNewAction, SIGNAL(triggered()), this, SLOT(fileNew()));
196
197 fileOpenAction = new QAction(QPixmap(fileopen_xpm), tr("&Open..."), this);
198 fileOpenAction->setShortcut(QKeySequence(QKeySequence::Open));
199 fileOpenAction->setToolTip(tr("Open QMidiArp file"));
200 connect(fileOpenAction, SIGNAL(triggered()), this, SLOT(fileOpen()));
201
202 fileSaveAction = new QAction(QPixmap(filesave_xpm), tr("&Save"), this);
203 fileSaveAction->setShortcut(QKeySequence(QKeySequence::Save));
204 fileSaveAction->setToolTip(tr("Save current QMidiArp session"));
205 connect(fileSaveAction, SIGNAL(triggered()), this, SLOT(fileSave()));
206 fileSaveAction->setDisabled(true);
207
208 fileSaveAsAction = new QAction(QPixmap(filesaveas_xpm), tr("Save &as..."),
209 this);
210 fileSaveAsAction->setToolTip(
211 tr("Save current QMidiArp session with new name"));
212 connect(fileSaveAsAction, SIGNAL(triggered()), this, SLOT(fileSaveAs()));
213 fileSaveAsAction->setDisabled(true);
214
215 fileQuitAction = new QAction(QPixmap(filequit_xpm), tr("&Quit"), this);
216 fileQuitAction->setShortcut(QKeySequence(tr("Ctrl+Q", "File|Quit")));
217 fileQuitAction->setToolTip(tr("Quit application"));
218 connect(fileQuitAction, SIGNAL(triggered()), this, SLOT(close()));
219
220 runAction = new QAction(QPixmap(play_xpm), tr("&Run with internal clock"), this);
221 connect(runAction, SIGNAL(toggled(bool)), this, SLOT(updateTransportStatus(bool)));
222 runAction->setCheckable(true);
223 runAction->setChecked(false);
224 runAction->setDisabled(true);
225
226 tempoSpin = new QSpinBox(this);
227 tempoSpin->setRange(10, 400);
228 tempoSpin->setValue(120);
229 tempoSpin->setKeyboardTracking(false);
230 tempoSpin->setToolTip(tr("Tempo of internal clock"));
231 connect(tempoSpin, SIGNAL(valueChanged(int)), this,
232 SLOT(updateTempo(int)));
233 engine->midiControl->addMidiLearnMenu("Tempo", tempoSpin, 0);
234
235 midiClockAction = new QAction(QPixmap(midiclock_xpm),
236 tr("&Use incoming MIDI Clock"), this);
237 midiClockAction->setCheckable(true);
238 midiClockAction->setChecked(false);
239 midiClockAction->setDisabled(true);
240 connect(midiClockAction, SIGNAL(toggled(bool)), this,
241 SLOT(midiClockToggle(bool)));
242
243
244 jackSyncAction = new QAction(QPixmap(jacktr_xpm),
245 tr("&Connect to Jack Transport"), this);
246 jackSyncAction->setCheckable(true);
247 connect(jackSyncAction, SIGNAL(toggled(bool)), this,
248 SLOT(jackSyncToggle(bool)));
249 jackSyncAction->setChecked(false);
250 jackSyncAction->setDisabled(true);
251
252 updateTransportStatus(false);
253
254 showAllIOAction = new QAction(tr("&Show all IO panels"), this);
255 showAllIOAction->setIcon(QPixmap(iopanelshow_xpm));
256 connect(showAllIOAction, SIGNAL(triggered()), this, SLOT(showIO()));
257 showAllIOAction->setDisabled(true);
258
259 hideAllIOAction = new QAction(tr("&Hide all IO panels"), this);
260 hideAllIOAction->setIcon(QPixmap(iopanelhide_xpm));
261 connect(hideAllIOAction, SIGNAL(triggered()), this, SLOT(hideIO()));
262 hideAllIOAction->setDisabled(true);
263
264 QAction* viewLogAction = logWindow->toggleViewAction();
265 viewLogAction->setIcon(QPixmap(eventlog_xpm));
266 viewLogAction->setText(tr("&Event Log"));
267 viewLogAction->setShortcut(QKeySequence(tr("Ctrl+H", "View|Event Log")));
268
269 QAction* viewGrooveAction = grooveWindow->toggleViewAction();
270 viewGrooveAction->setIcon(QPixmap(groovetog_xpm));
271 viewGrooveAction->setText(tr("&Groove Settings"));
272 viewGrooveAction->setShortcut(QKeySequence(tr("Ctrl+G", "View|Groove")));
273
274 QAction* viewSettingsAction = new QAction(tr("&Settings"), this);
275 viewSettingsAction->setIcon(QPixmap(settings_xpm));
276 viewSettingsAction->setShortcut(QKeySequence(tr("Ctrl+P",
277 "View|Settings")));
278 connect(viewSettingsAction, SIGNAL(triggered()), passWidget, SLOT(show()));
279
280 QAction* viewGlobAction = globStoreWindow->toggleViewAction();
281 viewGlobAction->setIcon(QPixmap(globtog_xpm));
282 viewGlobAction->setText(tr("&Global Store"));
283 viewGlobAction->setShortcut(QKeySequence(tr("Ctrl+$",
284 "View|GlobalStore")));
285
286 QMenuBar *menuBar = new QMenuBar;
287 QMenu *fileMenu = new QMenu(tr("&File"), this);
288 QMenu *viewMenu = new QMenu(tr("&View"), this);
289 QMenu *arpMenu = new QMenu(tr("Mod&ule"), this);
290 QMenu *helpMenu = new QMenu(tr("&Help"), this);
291
292 fileMenu->addAction(fileNewAction);
293 fileMenu->addAction(fileOpenAction);
294
295 fileRecentlyOpenedFiles = fileMenu->addMenu(tr("&Recently opened files"));
296
297 fileMenu->addAction(fileSaveAction);
298 fileMenu->addAction(fileSaveAsAction);
299 fileMenu->addSeparator();
300 fileMenu->addAction(fileQuitAction);
301 connect(fileMenu, SIGNAL(aboutToShow()), this,
302 SLOT(setupRecentFilesMenu()));
303 connect(fileRecentlyOpenedFiles, SIGNAL(triggered(QAction*)), this,
304 SLOT(recentFileActivated(QAction*)));
305
306 viewMenu->addAction(showAllIOAction);
307 viewMenu->addAction(hideAllIOAction);
308 viewMenu->addAction(viewLogAction);
309 viewMenu->addAction(viewGrooveAction);
310 viewMenu->addAction(viewGlobAction);
311 viewMenu->addAction(QPixmap(midicontrol_xpm), tr("&MIDI Controllers..."),
312 this, SLOT(showMidiCCDialog()))
313 ->setShortcut(QKeySequence(tr("Ctrl+M", "View|MidiControllers")));
314 viewMenu->addAction(viewSettingsAction);
315
316 arpMenu->addAction(addArpAction);
317 arpMenu->addAction(addLfoAction);
318 arpMenu->addAction(addSeqAction);
319 arpMenu->addSeparator();
320
321 helpMenu->addAction(tr("&About %1...").arg(APP_NAME), this,
322 SLOT(helpAbout()));
323 helpMenu->addAction(tr("&About Qt..."), this,
324 SLOT(helpAboutQt()));
325
326 fileToolBar = new QToolBar(tr("&File Toolbar"), this);
327 fileToolBar->addAction(fileNewAction);
328 fileToolBar->addAction(fileOpenAction);
329 fileToolBar->addAction(fileSaveAction);
330 fileToolBar->addAction(fileSaveAsAction);
331 fileToolBar->setObjectName("fileToolBar");
332 fileToolBar->setMaximumHeight(30);
333 connect(fileToolBar, SIGNAL(orientationChanged(Qt::Orientation)), this,
334 SLOT(ftb_update_orientation(Qt::Orientation)));
335
336 controlToolBar = new QToolBar(tr("&Control Toolbar"), this);
337 controlToolBar->addAction(showAllIOAction);
338 controlToolBar->addAction(hideAllIOAction);
339 controlToolBar->addSeparator();
340 controlToolBar->addAction(viewLogAction);
341 controlToolBar->addAction(viewGrooveAction);
342 controlToolBar->addAction(viewGlobAction);
343 controlToolBar->addSeparator();
344 controlToolBar->addAction(addArpAction);
345 controlToolBar->addAction(addLfoAction);
346 controlToolBar->addAction(addSeqAction);
347 controlToolBar->addSeparator();
348 controlToolBar->addWidget(tempoSpin);
349 controlToolBar->addAction(runAction);
350 controlToolBar->addAction(midiClockAction);
351 controlToolBar->addAction(jackSyncAction);
352 controlToolBar->setObjectName("controlToolBar");
353 controlToolBar->setMaximumHeight(30);
354 connect(controlToolBar, SIGNAL(orientationChanged(Qt::Orientation)), this,
355 SLOT(ctb_update_orientation(Qt::Orientation)));
356
357 menuBar->addMenu(fileMenu);
358 menuBar->addMenu(viewMenu);
359 menuBar->addMenu(arpMenu);
360 menuBar->addMenu(helpMenu);
361
362 setMenuBar(menuBar);
363 addToolBar(fileToolBar);
364 addToolBar(controlToolBar);
365
366 setWindowIcon(QPixmap(qmidiarp2_xpm));
367
368 setCentralWidget(new QWidget(this));
369 setDockNestingEnabled(true);
370 updateWindowTitle();
371
372 if (checkRcFile())
373 readRcFile();
374
375 passWidget->setModified(false);
376
377 if (!installSignalHandlers())
378 qWarning("%s", "Signal handlers not installed!");
379
380 if (!jackFailed || alsaMidi) show();
381
382 #ifdef NSM
383 if (nsm && nsm_is_active(nsm))
384 {
385 fileNewAction->setText(tr("Clear"));
386 fileNewAction->setToolTip(tr("Clear QMidiArp session"));
387 fileOpenAction->setText(tr("Import file..."));
388 fileOpenAction->setToolTip(tr("Import QMidiArp file to NSM session"));
389 fileSaveAsAction->setText(tr("Export session..."));
390 fileSaveAsAction->setToolTip(tr("Export QMidiArp NSM session to file"));
391 fileRecentlyOpenedFiles->setDisabled(true);
392 }
393 #endif
394 }
395
~MainWindow()396 MainWindow::~MainWindow()
397 {
398 clear();
399 }
400
updateWindowTitle()401 void MainWindow::updateWindowTitle()
402 {
403 if (filename.isEmpty())
404 setWindowTitle(QString("%1 (%2)")
405 .arg(APP_NAME)
406 .arg(engine->getClientId()));
407 else
408 setWindowTitle(QString("%1 - %2 (%3)")
409 .arg(filename)
410 .arg(APP_NAME)
411 .arg(engine->getClientId()));
412 }
413
helpAbout()414 void MainWindow::helpAbout()
415 {
416 QMessageBox::about(this, tr("About %1").arg(APP_NAME), ABOUTMSG);
417 }
418
helpAboutQt()419 void MainWindow::helpAboutQt()
420 {
421 QMessageBox::aboutQt(this, tr("About Qt"));
422 }
423
arpNew()424 void MainWindow::arpNew()
425 {
426 QString name;
427 bool ok;
428
429 name = QInputDialog::getText(this, APP_NAME,
430 tr("Add MIDI Arpeggiator"), QLineEdit::Normal,
431 tr("%1").arg(engine->midiArpCount() + 1), &ok);
432 if (ok && !name.isEmpty()) {
433 addArp("Arp:"+name);
434 }
435 }
436
lfoNew()437 void MainWindow::lfoNew()
438 {
439 QString name;
440 bool ok;
441
442 name = QInputDialog::getText(this, APP_NAME,
443 tr("Add MIDI LFO"), QLineEdit::Normal,
444 tr("%1").arg(engine->midiLfoCount() + 1), &ok);
445 if (ok && !name.isEmpty()) {
446 addLfo("LFO:"+name);
447 }
448 }
449
seqNew()450 void MainWindow::seqNew()
451 {
452 QString name;
453 bool ok;
454
455 name = QInputDialog::getText(this, APP_NAME,
456 tr("Add Step Sequencer"), QLineEdit::Normal,
457 tr("%1").arg(engine->midiSeqCount() + 1), &ok);
458 if (ok && !name.isEmpty()) {
459 addSeq("Seq:"+name);
460 }
461 }
462
addArp(const QString & p_name,bool fromfile,bool inOutVisible)463 void MainWindow::addArp(const QString& p_name, bool fromfile, bool inOutVisible)
464 {
465 int count, widgetID;
466 MidiArp *midiArp = new MidiArp();
467 ArpWidget *moduleWidget = new ArpWidget(midiArp, globStore,
468 engine->getPortCount(), passWidget->compactStyle,
469 passWidget->mutedAdd, inOutVisible, p_name);
470 connect(moduleWidget, SIGNAL(presetsChanged(const QString&, const
471 QString&, int)),
472 this, SLOT(updatePatternPresets(const QString&, const
473 QString&, int)));
474 connect(moduleWidget, SIGNAL(moduleRemove(int)),
475 this, SLOT(removeArp(int)));
476 connect(moduleWidget, SIGNAL(dockRename(const QString&, int)),
477 engine, SLOT(renameDock(const QString&, int)));
478 connect(moduleWidget->midiControl, SIGNAL(setMidiLearn(int, int)),
479 engine, SLOT(setMidiLearn(int, int)));
480
481 widgetID = engine->arpWidgetCount();
482 moduleWidget->ID = widgetID;
483 moduleWidget->midiControl->ID = widgetID;
484
485 // if the module is added at a time when global stores are already
486 // present we fill up the new global parameter storage list with dummies
487 // and tag them empty
488 if (!fromfile) for (int l1 = 0; l1 < (globStore->widgetList.count() - 1); l1++) {
489 moduleWidget->storeParams(l1, true);
490 }
491
492 engine->addMidiArp(midiArp);
493 engine->addArpWidget(moduleWidget);
494
495 count = engine->moduleWindowCount();
496 moduleWidget->parentDockID = count;
497 moduleWidget->midiControl->parentDockID = count;
498 appendDock(moduleWidget, p_name, count);
499 connect(moduleWidget->parStore->topButton, SIGNAL(pressed())
500 , engine->moduleWindow(count), SLOT(raise()));
501 checkIfFirstModule();
502 }
503
addLfo(const QString & p_name,bool fromfile,int clonefrom,bool inOutVisible)504 void MainWindow::addLfo(const QString& p_name, bool fromfile, int clonefrom, bool inOutVisible)
505 {
506 int widgetID, count;
507
508 MidiLfo *midiLfo = new MidiLfo();
509 LfoWidget *moduleWidget = new LfoWidget(midiLfo, globStore,
510 engine->getPortCount(), passWidget->compactStyle,
511 passWidget->mutedAdd, inOutVisible, p_name);
512 connect(moduleWidget, SIGNAL(moduleRemove(int)),
513 this, SLOT(removeLfo(int)));
514 connect(moduleWidget, SIGNAL(moduleClone(int)), this, SLOT(cloneLfo(int)));
515 connect(moduleWidget, SIGNAL(dockRename(const QString&, int)),
516 engine, SLOT(renameDock(const QString&, int)));
517 connect(moduleWidget->midiControl, SIGNAL(setMidiLearn(int, int)),
518 engine, SLOT(setMidiLearn(int, int)));
519
520 widgetID = engine->lfoWidgetCount();
521 if (clonefrom >= 0) {
522 moduleWidget->copyParamsFrom(engine->lfoWidget(clonefrom));
523 }
524
525 //TODO: transfer these items to constructor
526 moduleWidget->ID = widgetID;
527 moduleWidget->midiControl->ID = widgetID;
528
529 // if the module is added at a time when global stores are already
530 // present we fill up the new global parameter storage list with dummies
531 // and tag them empty
532 if (!fromfile) for (int l1 = 0; l1 < (globStore->widgetList.count() - 1); l1++) {
533 moduleWidget->storeParams(l1, true);
534 }
535
536 if (clonefrom >= 0) {
537 midiLfo->reverse = engine->lfoWidget(clonefrom)->getReverse();
538 midiLfo->setNextTick(engine->lfoWidget(clonefrom)->getNextTick());
539 }
540 else if (engine->lfoWidgetCount())
541 midiLfo->setNextTick(engine->lfoWidget(0)->getNextTick());
542
543 engine->addMidiLfo(midiLfo);
544 engine->addLfoWidget(moduleWidget);
545
546 count = engine->moduleWindowCount();
547 moduleWidget->parentDockID = count;
548 moduleWidget->midiControl->parentDockID = count;
549 appendDock(moduleWidget, p_name, count);
550 connect(moduleWidget->parStore->topButton, SIGNAL(pressed())
551 , engine->moduleWindow(count), SLOT(raise()));
552
553 checkIfFirstModule();
554 }
555
addSeq(const QString & p_name,bool fromfile,int clonefrom,bool inOutVisible)556 void MainWindow::addSeq(const QString& p_name, bool fromfile, int clonefrom, bool inOutVisible)
557 {
558 int widgetID, count;
559
560 MidiSeq *midiSeq = new MidiSeq();
561 SeqWidget *moduleWidget = new SeqWidget(midiSeq, globStore,
562 engine->getPortCount(), passWidget->compactStyle,
563 passWidget->mutedAdd, inOutVisible, p_name);
564 connect(moduleWidget, SIGNAL(moduleRemove(int)), this, SLOT(removeSeq(int)));
565 connect(moduleWidget, SIGNAL(moduleClone(int)), this, SLOT(cloneSeq(int)));
566 connect(moduleWidget, SIGNAL(dockRename(const QString&, int)),
567 engine, SLOT(renameDock(const QString&, int)));
568 connect(moduleWidget->midiControl, SIGNAL(setMidiLearn(int, int)),
569 engine, SLOT(setMidiLearn(int, int)));
570
571 widgetID = engine->seqWidgetCount();
572 if (clonefrom >= 0) {
573 moduleWidget->copyParamsFrom(engine->seqWidget(clonefrom));
574 }
575 moduleWidget->ID = widgetID;
576 moduleWidget->midiControl->ID = widgetID;
577
578 // if the module is added at a time when global stores are already
579 // present we fill up the new global parameter storage list with dummies
580 // and tag them empty
581 if (!fromfile) for (int l1 = 0; l1 < (globStore->widgetList.count() - 1); l1++) {
582 moduleWidget->storeParams(l1, true);
583 }
584
585 if (clonefrom >= 0) {
586 midiSeq->reverse = engine->seqWidget(clonefrom)->getReverse();
587 midiSeq->setNextTick(engine->seqWidget(clonefrom)->getNextTick());
588 }
589 else if (engine->seqWidgetCount())
590 midiSeq->setNextTick(engine->seqWidget(0)->getNextTick());
591
592 engine->addMidiSeq(midiSeq);
593 engine->addSeqWidget(moduleWidget);
594
595 count = engine->moduleWindowCount();
596 moduleWidget->parentDockID = count;
597 moduleWidget->midiControl->parentDockID = count;
598 appendDock(moduleWidget, p_name, count);
599 connect(moduleWidget->parStore->topButton, SIGNAL(pressed())
600 , engine->moduleWindow(count), SLOT(raise()));
601 checkIfFirstModule();
602 }
603
cloneLfo(int ID)604 void MainWindow::cloneLfo(int ID)
605 {
606 QString name;
607 name = engine->lfoWidget(ID)->name + "_0";
608 addLfo(name, false, ID);
609 }
610
cloneSeq(int ID)611 void MainWindow::cloneSeq(int ID)
612 {
613 QString name;
614 name = engine->seqWidget(ID)->name + "_0";
615 addSeq(name, false, ID);
616 }
617
appendDock(QWidget * moduleWidget,const QString & name,int count)618 void MainWindow::appendDock(QWidget *moduleWidget, const QString &name, int count)
619 {
620 QDockWidget *moduleWindow = new QDockWidget(name, this);
621 moduleWindow->setFeatures(QDockWidget::DockWidgetMovable
622 | QDockWidget::DockWidgetFloatable);
623 moduleWindow->setWidget(moduleWidget);
624 moduleWindow->setObjectName(name);
625 addDockWidget(Qt::TopDockWidgetArea, moduleWindow);
626 if (passWidget->compactStyle) moduleWindow->setStyleSheet(COMPACT_STYLE);
627
628 if (count) tabifyDockWidget(engine->moduleWindow(count - 1), moduleWindow);
629 engine->addModuleWindow(moduleWindow);
630 globStore->addModule(name);
631 }
632
removeArp(int index)633 void MainWindow::removeArp(int index)
634 {
635 int parentDockID;
636 ArpWidget *arpWidget = engine->arpWidget(index);
637
638 parentDockID = arpWidget->parentDockID;
639 QDockWidget *dockWidget = engine->moduleWindow(parentDockID);
640 globStore->removeModule(parentDockID);
641
642 engine->removeMidiArp(arpWidget->getMidiWorker());
643 engine->removeArpWidget(arpWidget);
644 delete arpWidget;
645 engine->removeModuleWindow(dockWidget);
646 engine->updateIDs(parentDockID);
647 checkIfLastModule();
648 }
649
removeLfo(int index)650 void MainWindow::removeLfo(int index)
651 {
652 int parentDockID;
653 LfoWidget *lfoWidget = engine->lfoWidget(index);
654
655 parentDockID = lfoWidget->parentDockID;
656 QDockWidget *dockWidget = engine->moduleWindow(parentDockID);
657 globStore->removeModule(parentDockID);
658
659 engine->removeMidiLfo(lfoWidget->getMidiWorker());
660 engine->removeLfoWidget(lfoWidget);
661 delete lfoWidget;
662 engine->removeModuleWindow(dockWidget);
663 engine->updateIDs(parentDockID);
664 checkIfLastModule();
665 }
666
removeSeq(int index)667 void MainWindow::removeSeq(int index)
668 {
669 int parentDockID;
670 SeqWidget *seqWidget = engine->seqWidget(index);
671
672 parentDockID = seqWidget->parentDockID;
673 QDockWidget *dockWidget = engine->moduleWindow(parentDockID);
674 globStore->removeModule(parentDockID);
675
676 engine->removeMidiSeq(seqWidget->getMidiWorker());
677 engine->removeSeqWidget(seqWidget);
678 delete seqWidget;
679 engine->removeModuleWindow(dockWidget);
680 engine->updateIDs(parentDockID);
681 checkIfLastModule();
682 }
683
clear()684 void MainWindow::clear()
685 {
686 updateTransportStatus(false);
687 jackSyncToggle(false);
688
689 for (int l1 = globStore->widgetList.count() - 1; l1 > 0; l1--) {
690 globStore->removeLocation(l1);
691 }
692 globStore->setDispState(0, 0);
693 globStore->midiControl->ccList.clear();
694 while (engine->midiArpCount()) {
695 removeArp(engine->midiArpCount() - 1);
696 }
697
698 while (engine->midiLfoCount()) {
699 removeLfo(engine->midiLfoCount() - 1);
700 }
701
702 while (engine->midiSeqCount()) {
703 removeSeq(engine->midiSeqCount() - 1);
704 }
705
706 grooveWidget->midiControl->ccList.clear();
707
708 }
709
fileNew()710 void MainWindow::fileNew()
711 {
712 if (isSave()) {
713 clear();
714 #ifdef NSM
715 if (nsm) {
716 filename = configFile;
717 }
718 else {
719 filename = "";
720 }
721 #else
722 filename = "";
723 #endif
724 updateWindowTitle();
725 engine->setModified(false);
726 }
727 }
728
fileOpen()729 void MainWindow::fileOpen()
730 {
731 if (isSave()) {
732 chooseFile();
733 #ifdef NSM
734 if (nsm) {
735 filename = configFile;
736 updateWindowTitle();
737 }
738 #endif
739 }
740 }
741
chooseFile()742 void MainWindow::chooseFile()
743 {
744 QString fn = QFileDialog::getOpenFileName(this,
745 tr("Open arpeggiator file"), lastDir,
746 tr("QMidiArp XML files") + " (*" + FILEEXT + ")");
747 if (fn.isEmpty())
748 return;
749
750 if (fn.endsWith(FILEEXT))
751 openFile(fn);
752 }
753
openFile(const QString & fn)754 void MainWindow::openFile(const QString& fn)
755 {
756
757 lastDir = fn.left(fn.lastIndexOf('/'));
758
759 QFile f(fn);
760 if (!f.open(QIODevice::ReadOnly)) {
761 #ifdef NSM
762 if (nsm && nsm_is_active(nsm)) {
763 filename = fn;
764 //updateWindowTitle();
765 return;
766 }
767 else {
768 #endif
769 QMessageBox::warning(this, APP_NAME,
770 tr("Could not read from file '%1'.").arg(fn));
771 return;
772 #ifdef NSM
773 }
774 #endif
775 }
776
777 clear();
778 filename = fn;
779 updateWindowTitle();
780
781 QXmlStreamReader xml(&f);
782 while (!xml.atEnd()) {
783 xml.readNext();
784 if (xml.isStartElement()) {
785 if (xml.isEndElement())
786 break;
787
788 if (xml.name() != "session") {
789 xml.raiseError(tr("Not a QMidiArp xml file."));
790 QMessageBox::warning(this, APP_NAME,
791 tr("This is not a valid xml file for ")+APP_NAME);
792 return;
793 }
794 while (!xml.atEnd()) {
795 xml.readNext();
796
797 if (xml.isEndElement())
798 break;
799
800 if ((xml.isStartElement()) && (xml.name() == "global"))
801 readFilePartGlobal(xml);
802 else if (xml.isStartElement() && (xml.name() == "modules"))
803 readFilePartModules(xml);
804 else if (xml.isStartElement() && (xml.name() == "GUI"))
805 readFilePartGUI(xml);
806 else if (xml.isStartElement() && (xml.name() == "globalstorage"))
807 globStore->readData(xml);
808 else skipXmlElement(xml);
809 }
810 }
811 else skipXmlElement(xml);
812 }
813
814 addRecentlyOpenedFile(filename, recentFiles);
815 engine->setModified(false);
816 }
817
readFilePartGlobal(QXmlStreamReader & xml)818 void MainWindow::readFilePartGlobal(QXmlStreamReader& xml)
819 {
820 while (!xml.atEnd()) {
821 xml.readNext();
822 if (xml.isEndElement()) {
823 break;
824 }
825 if (xml.name() == "tempo") {
826 tempoSpin->setValue(xml.readElementText().toInt());
827 }
828 if (xml.isStartElement() && (xml.name() == "settings")) {
829 while (!xml.atEnd()) {
830 xml.readNext();
831 if (xml.isEndElement())
832 break;
833 if (xml.name() == "midiControlEnabled")
834 passWidget->cbuttonCheck->setChecked(xml.readElementText().toInt());
835 else if (xml.name() == "midiClockEnabled") {
836 bool tmp = xml.readElementText().toInt();
837 if (alsaMidi) midiClockAction->setChecked(tmp);
838 }
839 else if (xml.name() == "jackSyncEnabled") {
840 bool tmp = xml.readElementText().toInt();
841 jackSyncAction->setChecked(tmp);
842 }
843 else if (xml.name() == "forwardUnmatched")
844 passWidget->setForward(xml.readElementText().toInt());
845 else if (xml.name() == "forwardPort")
846 passWidget->setPortUnmatched(xml.readElementText().toInt());
847 else skipXmlElement(xml);
848 }
849 }
850 else if (xml.isStartElement() && (xml.name() == "groove"))
851 grooveWidget->readData(xml);
852 else if (xml.isStartElement() && (xml.name() == "midiControllers"))
853 engine->midiControl->readData(xml);
854 else skipXmlElement(xml);
855 }
856 passWidget->setModified(false);
857 }
858
readFilePartModules(QXmlStreamReader & xml)859 void MainWindow::readFilePartModules(QXmlStreamReader& xml)
860 {
861 int count = 0;
862
863 while (!xml.atEnd()) {
864 bool iovis = true;
865 xml.readNext();
866 if (xml.isEndElement())
867 break;
868 if (xml.isStartElement() && (xml.name() == "Arp")) {
869 if (xml.attributes().hasAttribute("inOutVisible"))
870 iovis = xml.attributes().value("inOutVisible").toString().toInt();
871 addArp("Arp:" + xml.attributes().value("name").toString(), true, iovis);
872 engine->arpWidget(engine->midiArpCount() - 1)
873 ->readData(xml);
874 count++;
875 if (count == 1) {
876 for (int l1 = 0; l1 < engine->arpWidget(0)->parStore->list.count(); l1++) {
877 globStore->addLocation();
878 }
879 }
880 }
881 else if (xml.isStartElement() && (xml.name() == "LFO")) {
882 if (xml.attributes().hasAttribute("inOutVisible"))
883 iovis = xml.attributes().value("inOutVisible").toString().toInt();
884 addLfo("LFO:" + xml.attributes().value("name").toString(), true, -1, iovis);
885 engine->lfoWidget(engine->midiLfoCount() - 1)
886 ->readData(xml);
887 count++;
888 if (count == 1) {
889 for (int l1 = 0; l1 < engine->lfoWidget(0)->parStore->list.count(); l1++) {
890 globStore->addLocation();
891 }
892 }
893 }
894 else if (xml.isStartElement() && (xml.name() == "Seq")) {
895 if (xml.attributes().hasAttribute("inOutVisible"))
896 iovis = xml.attributes().value("inOutVisible").toString().toInt();
897 addSeq("Seq:" + xml.attributes().value("name").toString(), true, -1, iovis);
898 engine->seqWidget(engine->midiSeqCount() - 1)
899 ->readData(xml);
900 count++;
901 if (count == 1) {
902 for (int l1 = 0; l1 < engine->seqWidget(0)->parStore->list.count(); l1++) {
903 globStore->addLocation();
904 }
905 }
906 }
907 else skipXmlElement(xml);
908 }
909 }
910
readFilePartGUI(QXmlStreamReader & xml)911 void MainWindow::readFilePartGUI(QXmlStreamReader& xml)
912 {
913 while (!xml.atEnd()) {
914 xml.readNext();
915 if (xml.isEndElement())
916 break;
917 if (xml.name() == "windowState") {
918 restoreState(QByteArray::fromHex(
919 xml.readElementText().toLatin1()));
920 }
921 else skipXmlElement(xml);
922 }
923 }
924
skipXmlElement(QXmlStreamReader & xml)925 void MainWindow::skipXmlElement(QXmlStreamReader& xml)
926 {
927 if (xml.isStartElement()) {
928 qWarning("Unknown Element in XML File: %s",qPrintable(xml.name().toString()));
929 while (!xml.atEnd()) {
930 xml.readNext();
931
932 if (xml.isEndElement())
933 break;
934
935 if (xml.isStartElement()) {
936 skipXmlElement(xml);
937 }
938 }
939 }
940 }
941
fileSave()942 void MainWindow::fileSave()
943 {
944 if (filename.isEmpty())
945 saveFileAs();
946 else
947 saveFile();
948 }
949
saveFile()950 bool MainWindow::saveFile()
951 {
952 int l1;
953 int ns = 0;
954 int nl = 0;
955 int na = 0;
956
957 QFile f(filename);
958 QString nameTest;
959
960 if (!f.open(QIODevice::WriteOnly)) {
961 QMessageBox::warning(this, APP_NAME,
962 tr("Could not write to file '%1'.").arg(filename));
963 return false;
964 }
965 QXmlStreamWriter xml(&f);
966 xml.setAutoFormatting(true);
967 xml.writeStartDocument();
968 xml.writeDTD("<!DOCTYPE qmidiarpSession>");
969 xml.writeStartElement("session");
970 xml.writeAttribute("version", PACKAGE_VERSION);
971 xml.writeAttribute("name", filename.mid(filename.lastIndexOf('/') + 1,
972 filename.count() - filename.lastIndexOf('/') - 6));
973
974 xml.writeStartElement("global");
975
976 xml.writeTextElement("tempo", QString::number(tempoSpin->value()));
977
978 xml.writeStartElement("settings");
979 xml.writeTextElement("midiControlEnabled",
980 QString::number((int)passWidget->cbuttonCheck->isChecked()));
981 xml.writeTextElement("midiClockEnabled",
982 QString::number((int)midiClockAction->isChecked()));
983 xml.writeTextElement("jackSyncEnabled",
984 QString::number((int)jackSyncAction->isChecked()));
985 xml.writeTextElement("forwardUnmatched",
986 QString::number((int)passWidget->forwardCheck->isChecked()));
987 xml.writeTextElement("forwardPort",
988 QString::number(passWidget->portUnmatchedSpin->currentIndex()));
989 xml.writeEndElement();
990
991 grooveWidget->writeData(xml);
992 engine->midiControl->writeData(xml);
993
994 xml.writeEndElement();
995
996 xml.writeStartElement("modules");
997
998 for (l1 = 0; l1 < engine->moduleWindowCount(); l1++) {
999
1000 nameTest = engine->moduleWindow(l1)->objectName();
1001
1002 if (nameTest.startsWith('S')) {
1003 engine->seqWidget(ns)->writeData(xml);
1004 ns++;
1005 }
1006 if (nameTest.startsWith('L')) {
1007 engine->lfoWidget(nl)->writeData(xml);
1008 nl++;
1009 }
1010 if (nameTest.startsWith('A')) {
1011 engine->arpWidget(na)->writeData(xml);
1012 na++;
1013 }
1014 }
1015
1016 xml.writeEndElement();
1017
1018 xml.writeStartElement("GUI");
1019 xml.writeTextElement("windowState", saveState().toHex());
1020 xml.writeEndElement();
1021
1022 globStore->writeData(xml);
1023
1024 xml.writeEndElement();
1025 xml.writeEndDocument();
1026
1027
1028 engine->setModified(false);
1029 return true;
1030 }
1031
fileSaveAs()1032 void MainWindow::fileSaveAs()
1033 {
1034 saveFileAs();
1035 #ifdef NSM
1036 if (nsm) {
1037 filename = configFile;
1038 updateWindowTitle();
1039 }
1040 #endif
1041 }
1042
saveFileAs()1043 bool MainWindow::saveFileAs()
1044 {
1045 bool result = false;
1046
1047 QString fn = QFileDialog::getSaveFileName(this,
1048 tr("Save arpeggiator"), lastDir, tr("QMidiArp files")
1049 + " (*" + FILEEXT + ")");
1050
1051 if (!fn.isEmpty()) {
1052 if (!fn.endsWith(FILEEXT))
1053 fn.append(FILEEXT);
1054 lastDir = fn.left(fn.lastIndexOf('/'));
1055
1056 filename = fn;
1057 updateWindowTitle();
1058 result = saveFile();
1059 }
1060 return result;
1061 }
1062
isSave()1063 bool MainWindow::isSave()
1064 {
1065 bool result = false;
1066 QString queryStr;
1067
1068 if (isModified()) {
1069 if (filename.isEmpty())
1070 queryStr = tr("Unnamed file was changed.\nSave changes?");
1071 else
1072 queryStr = tr("File '%1' was changed.\n"
1073 "Save changes?").arg(filename);
1074
1075 QMessageBox::StandardButton choice = QMessageBox::warning(this,
1076 tr("Save changes"), queryStr,
1077 QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel,
1078 QMessageBox::Yes);
1079
1080 switch (choice) {
1081 case QMessageBox::Yes:
1082 if (filename.isEmpty())
1083 result = saveFileAs();
1084 else
1085 result = saveFile();
1086 break;
1087 case QMessageBox::No:
1088 result = true;
1089 break;
1090 case QMessageBox::Cancel:
1091 default:
1092 break;
1093 }
1094 }
1095 else
1096 result = true;
1097
1098 return result;
1099 }
1100
closeEvent(QCloseEvent * e)1101 void MainWindow::closeEvent(QCloseEvent* e)
1102 {
1103 #ifdef NSM
1104 if (nsm) {
1105 writeRcFile();
1106 e->accept();
1107 } else
1108 #endif
1109 if (isSave()) {
1110 writeRcFile();
1111 e->accept();
1112 }
1113 else
1114 e->ignore();
1115 }
1116
isModified()1117 bool MainWindow::isModified()
1118 {
1119 return (engine->isModified() || passWidget->isModified());
1120 }
1121
updateTempo(int p_tempo)1122 void MainWindow::updateTempo(int p_tempo)
1123 {
1124 if (!midiClockAction->isChecked())
1125 engine->setTempo(p_tempo);
1126 }
1127
displayTempo(double p_tempo)1128 void MainWindow::displayTempo(double p_tempo)
1129 {
1130 tempoSpin->setValue(p_tempo);
1131 }
1132
updateTransportStatus(bool on)1133 void MainWindow::updateTransportStatus(bool on)
1134 {
1135 engine->setStatus(on);
1136 //if (alsaMidi) tempoSpin->setDisabled(on);
1137 }
1138
midiClockToggle(bool on)1139 void MainWindow::midiClockToggle(bool on)
1140 {
1141 if (on) jackSyncAction->setChecked(false);
1142 engine->setUseMidiClock(on);
1143 setGUIforExtSync(on);
1144 }
1145
jackSyncToggle(bool on)1146 void MainWindow::jackSyncToggle(bool on)
1147 {
1148 if (on) midiClockAction->setChecked(false);
1149 setGUIforExtSync(on);
1150 engine->setUseJackTransport(on);
1151 }
1152
showIO()1153 void MainWindow::showIO()
1154 {
1155 engine->showAllIOPanels(true);
1156 }
1157
hideIO()1158 void MainWindow::hideIO()
1159 {
1160 engine->showAllIOPanels(false);
1161 resize(10, 10);
1162 }
1163
jackShutdown()1164 void MainWindow::jackShutdown()
1165 {
1166 if (!alsaMidi) {
1167 QMessageBox::warning(this, PACKAGE,
1168 tr("JACK has shut down or could not be started, but you are trying\n"
1169 "to run QMidiArp with JACK MIDI backend.\n\n"
1170 "Alternatively you can use the ALSA MIDI backend \n"
1171 "by calling qmidiarp -a"));
1172 }
1173 else {
1174 engine->setStatus(false);
1175 jackSyncAction->setChecked(false);
1176 }
1177 }
1178
setGUIforExtSync(bool on)1179 void MainWindow::setGUIforExtSync(bool on)
1180 {
1181 runAction->setDisabled(on);
1182 tempoSpin->setDisabled(on);
1183 }
1184
checkRcFile()1185 bool MainWindow::checkRcFile()
1186 {
1187 QDir qmahome = QDir(QDir::homePath());
1188 bool retval = true;
1189 if (!qmahome.exists(QMARCNAME)) {
1190
1191 patternNames
1192 << " "
1193 << "Simple 4"
1194 << "Simple 8"
1195 << "Simple 16"
1196 << "Simple 32"
1197 << "Chord 8"
1198 << "Chord+Bass 16"
1199 << "Chord Oct 16 A"
1200 << "Chord Oct 16 B"
1201 << "Chord Oct 16 C"
1202 << "Fast Chords1"
1203 << "Fast Chords2"
1204 << "Fast Chords3"
1205 << "Chords/Glissando 16";
1206
1207 patternPresets
1208 << ""
1209 << "0"
1210 << ">0"
1211 << ">>0"
1212 << ">>>0"
1213 << ">(0123456789)"
1214 << ">>(01234)0(01234)0"
1215 << ">>////(0123456789)\\ \\ \\ +(0123456789)"
1216 << ">>///0\\ \\ \\ 0+////0\\ \\ \\ \\ -00+0-00+0-00+0-00+0-0"
1217 << ">>///0\\ \\ \\ 0+////(0123)\\ \\ \\ \\ -00+(1234)-00+0-00+0-00+0-0"
1218 << ">>////(0123456789)\\ \\ \\ +(0123456789) ////hh(0123456789)ddd\\ \\ \\ ////(0123456789)"
1219 << ">>(0123456)p+(0123456)-(01234)(234567)(56789)"
1220 << ">>////(0123456789)\\ \\ \\ +(0123456789) ////hh(0123456789)ddd\\ \\ \\ ////(0123456789)-\\(0123456789)\\ \\ \\ (0123456789) ////hh+(0123456789)dd\\ \\ \\ ////-(0123456789)"
1221 << "d(012)>h(123)>d(012)<d(234)>hh(23)(42)(12)(43)>d012342";
1222
1223 writeRcFile();
1224 retval = false;
1225 }
1226 return retval;
1227 }
1228
readRcFile()1229 void MainWindow::readRcFile()
1230 {
1231 QString qs;
1232 QStringList value;
1233
1234 QDir qmahome = QDir(QDir::homePath());
1235 QString qmarcpath = qmahome.filePath(QMARCNAME);
1236 QFile f(qmarcpath);
1237
1238 if (!f.open(QIODevice::ReadOnly)) {
1239 QMessageBox::warning(this, PACKAGE,
1240 tr("Could not read from resource file"));
1241 return;
1242 }
1243 QTextStream loadText(&f);
1244 patternNames.clear();
1245 patternPresets.clear();
1246
1247 while (!loadText.atEnd()) {
1248 qs = loadText.readLine();
1249
1250 if (qs.startsWith('#')) {
1251 value.clear();
1252 value = qs.split('%');
1253 if ((value.at(0) == "#Pattern") && (value.count() > 2)) {
1254 patternNames << value.at(1);
1255 patternPresets << value.at(2);
1256 }
1257 else if ((value.at(0) == "#CompactStyle"))
1258 passWidget->compactStyleCheck->setChecked(value.at(1).toInt());
1259 else if ((value.at(0) == "#MutedAdd"))
1260 passWidget->mutedAddCheck->setChecked(value.at(1).toInt());
1261 else if ((value.at(0) == "#EnableLog"))
1262 logWidget->enableLog->setChecked(value.at(1).toInt());
1263 else if ((value.at(0) == "#LogMidiClock"))
1264 logWidget->logMidiClock->setChecked(value.at(1).toInt());
1265 else if ((value.at(0) == "#GUIState"))
1266 restoreState(QByteArray::fromHex(value.at(1).toUtf8()));
1267 else if ((value.at(0) == "#LastDir"))
1268 lastDir = value.at(1);
1269 else if ((value.at(0) == "#RecentFile"))
1270 recentFiles << value.at(1);
1271 }
1272 }
1273 }
1274
writeRcFile()1275 void MainWindow::writeRcFile()
1276 {
1277 int l1;
1278
1279 QDir qmahome = QDir(QDir::homePath());
1280 QString qmarcpath = qmahome.filePath(QMARCNAME);
1281 QFile f(qmarcpath);
1282
1283 if (!f.open(QIODevice::WriteOnly)) {
1284 QMessageBox::warning(this, PACKAGE,
1285 tr("Could not write to resource file"));
1286 return;
1287 }
1288 QTextStream writeText(&f);
1289
1290 for (l1 = 0; l1 < patternNames.count(); l1++)
1291 {
1292 writeText << "#Pattern%";
1293 writeText << qPrintable(patternNames.at(l1)) << "%";
1294 writeText << qPrintable(patternPresets.at(l1)) << endl;
1295 }
1296
1297 writeText << "#CompactStyle%";
1298 writeText << passWidget->compactStyle << endl;
1299 writeText << "#MutedAdd%";
1300 writeText << passWidget->mutedAdd << endl;
1301 writeText << "#EnableLog%";
1302 writeText << logWidget->enableLog->isChecked() << endl;
1303 writeText << "#LogMidiClock%";
1304 writeText << logWidget->logMidiClock->isChecked() << endl;
1305 writeText << "#GUIState%";
1306 writeText << saveState().toHex() << endl;
1307
1308 writeText << "#LastDir%";
1309 writeText << lastDir << endl;
1310
1311 // save recently opened files (all recent files code taken from AMS)
1312 if (recentFiles.count() > 0) {
1313 QStringList::Iterator it = recentFiles.begin();
1314 for (; it != recentFiles.end(); ++it) {
1315 writeText << "#RecentFile%";
1316 writeText << *it << endl;
1317 }
1318 }
1319 }
1320
setupRecentFilesMenu()1321 void MainWindow::setupRecentFilesMenu()
1322 {
1323 fileRecentlyOpenedFiles->clear();
1324
1325 if (recentFiles.count() > 0) {
1326 if (!midiClockAction->isChecked()) fileRecentlyOpenedFiles->setEnabled(true);
1327 QStringList::Iterator it = recentFiles.begin();
1328 for (; it != recentFiles.end(); ++it) {
1329 fileRecentlyOpenedFiles->addAction(*it);
1330 }
1331 }
1332 else {
1333 fileRecentlyOpenedFiles->setEnabled(false);
1334 }
1335 #ifdef NSM
1336 if (nsm && nsm_is_active(nsm))
1337 fileRecentlyOpenedFiles->setEnabled(false);
1338 #endif
1339 }
1340
recentFileActivated(QAction * action)1341 void MainWindow::recentFileActivated(QAction *action)
1342 {
1343 if (!action->text().isEmpty()) {
1344 if (isSave())
1345 openFile(action->text().remove('&'));
1346 }
1347 }
1348
addRecentlyOpenedFile(const QString & fn,QStringList & lst)1349 void MainWindow::addRecentlyOpenedFile(const QString &fn, QStringList &lst)
1350 {
1351 QFileInfo fi(fn);
1352 if (lst.contains(fi.absoluteFilePath()))
1353 return;
1354 if (lst.count() >= 6 )
1355 lst.removeLast();
1356
1357 lst.prepend(fi.absoluteFilePath());
1358 }
1359
updatePatternPresets(const QString & n,const QString & p,int index)1360 void MainWindow::updatePatternPresets(const QString& n, const QString& p,
1361 int index)
1362 {
1363 if (index > 0) {
1364 patternNames.removeAt(index);
1365 patternPresets.removeAt(index);
1366
1367 } else {
1368 patternNames.append(n);
1369 patternPresets.append(p);
1370 }
1371 engine->updatePatternPresets(n, p, index);
1372 writeRcFile();
1373 }
1374
checkIfLastModule()1375 void MainWindow::checkIfLastModule()
1376 {
1377 if (!engine->moduleWindowCount()) {
1378 runAction->setDisabled(true);
1379 runAction->setChecked(false);
1380 midiClockAction->setDisabled(true);
1381 midiClockAction->setChecked(false);
1382 jackSyncAction->setDisabled(true);
1383 jackSyncAction->setChecked(false);
1384 fileSaveAction->setDisabled(true);
1385 fileSaveAsAction->setDisabled(true);
1386 showAllIOAction->setDisabled(true);
1387 hideAllIOAction->setDisabled(true);
1388 }
1389 }
1390
checkIfFirstModule()1391 void MainWindow::checkIfFirstModule()
1392 {
1393 if (engine->moduleWindowCount() == 1) {
1394 if (alsaMidi) midiClockAction->setEnabled(true);
1395 jackSyncAction->setEnabled(true);
1396 fileSaveAction->setEnabled(true);
1397 fileSaveAsAction->setEnabled(true);
1398 showAllIOAction->setEnabled(true);
1399 hideAllIOAction->setEnabled(true);
1400 runAction->setEnabled(!(midiClockAction->isChecked()
1401 || jackSyncAction->isChecked()));
1402 }
1403 }
1404
showMidiCCDialog()1405 void MainWindow::showMidiCCDialog()
1406 {
1407 midiCCTable->revert();
1408 midiCCTable->show();
1409 }
1410
handleSignal(int sig)1411 void MainWindow::handleSignal(int sig)
1412 {
1413 if (write(sigpipe[1], &sig, sizeof(sig)) == -1) {
1414 qWarning("write() failed: %s", std::strerror(errno));
1415 }
1416 }
1417
installSignalHandlers()1418 bool MainWindow::installSignalHandlers()
1419 {
1420 #ifdef SIGUSR1
1421 /*install pipe to forward received system signals*/
1422 if (pipe(sigpipe) < 0) {
1423 qWarning("pipe() failed: %s", std::strerror(errno));
1424 return false;
1425 }
1426
1427 /*install notifier to handle pipe messages*/
1428 QSocketNotifier* signalNotifier = new QSocketNotifier(sigpipe[0],
1429 QSocketNotifier::Read, this);
1430 connect(signalNotifier, SIGNAL(activated(int)),
1431 this, SLOT(signalAction(int)));
1432
1433 /*install signal handlers*/
1434 struct sigaction action;
1435 memset(&action, 0, sizeof(action));
1436 action.sa_handler = handleSignal;
1437
1438 if (sigaction(SIGUSR1, &action, NULL) == -1) {
1439 qWarning("sigaction() failed: %s", std::strerror(errno));
1440 return false;
1441 }
1442
1443 if (sigaction(SIGINT, &action, NULL) == -1) {
1444 qWarning("sigaction() failed: %s", std::strerror(errno));
1445 return false;
1446 }
1447 #ifdef NSM
1448 if (nsm && nsm_is_active(nsm)) {
1449 if (sigaction(SIGTERM, &action, NULL) == -1) {
1450 qWarning("sigaction() failed: %s", std::strerror(errno));
1451 return false;
1452 }
1453 }
1454 #endif
1455
1456 #endif
1457 return true;
1458 }
1459
signalAction(int fd)1460 void MainWindow::signalAction(int fd)
1461 {
1462 #ifndef SIGUSR1
1463 (void)fd;
1464 #else
1465 int message;
1466
1467 if (read(fd, &message, sizeof(message)) == -1) {
1468 qWarning("read() failed: %s", std::strerror(errno));
1469 return;
1470 }
1471
1472 switch (message) {
1473 case SIGUSR1:
1474 fileSave();
1475 break;
1476
1477 case SIGINT:
1478 #ifdef NSM
1479 case SIGTERM:
1480 #endif
1481 close();
1482 break;
1483
1484 default:
1485 qWarning("Unexpected signal received: %d", message);
1486 break;
1487 }
1488 #endif
1489 }
1490
jsAction(int evtype)1491 void MainWindow::jsAction(int evtype)
1492 {
1493 if (!evtype) {
1494 filename = engine->driver->jsFilename;
1495 qWarning("JACK Session request to save");
1496 lastDir = filename.left(filename.lastIndexOf('/'));
1497 updateWindowTitle();
1498 bool result = saveFile();
1499 if (!result) qWarning("Warning: JACK Session File save failed");
1500 }
1501 else if (evtype == 1)
1502 {
1503 close();
1504 }
1505 }
1506
ctb_update_orientation(Qt::Orientation orient)1507 void MainWindow::ctb_update_orientation(Qt::Orientation orient)
1508 {
1509 if (orient == Qt::Vertical) {
1510 controlToolBar->setMinimumHeight(controlToolBar->iconSize().height() * 15);
1511 if (fileToolBar->orientation() == Qt::Vertical)
1512 fileToolBar->setMinimumWidth(controlToolBar->minimumWidth());
1513 }
1514 else {
1515 controlToolBar->setMinimumHeight(0);
1516 if (fileToolBar->orientation() == Qt::Vertical)
1517 fileToolBar->setMinimumHeight(controlToolBar->minimumHeight());
1518 }
1519
1520 }
1521
ftb_update_orientation(Qt::Orientation orient)1522 void MainWindow::ftb_update_orientation(Qt::Orientation orient)
1523 {
1524 if (orient == Qt::Vertical) {
1525 fileToolBar->setMinimumHeight(fileToolBar->iconSize().height() * 7);
1526 }
1527 else {
1528 fileToolBar->setMinimumHeight(0);
1529 }
1530 }
1531
1532 #ifdef NSM
cb_nsm_open(const char * name,const char * display_name,const char * client_id,char ** out_msg,void * userdata)1533 int MainWindow::cb_nsm_open(const char *name, const char *display_name, const char *client_id, char **out_msg, void *userdata)
1534 {
1535 return ((MainWindow *)userdata)->nsm_open(name, display_name, client_id, out_msg);
1536 }
1537
cb_nsm_save(char ** out_msg,void * userdata)1538 int MainWindow::cb_nsm_save ( char **out_msg, void *userdata )
1539 {
1540 return ((MainWindow *)userdata)->nsm_save(out_msg);
1541 }
1542
nsm_open(const char * name,const char * display_name,const char * client_id,char ** out_msg)1543 int MainWindow::nsm_open(const char *name, const char *display_name, const char *client_id, char **out_msg)
1544 {
1545 (void)out_msg;
1546 (void)display_name;
1547
1548 configFile = name;
1549 if (!alsaMidi) {
1550 engine->driver->callJack(-1);
1551 engine->driver->callJack(engine->getPortCount(), client_id);
1552 }
1553 configFile.append(".qmax");
1554 emit nsmOpenFile(configFile);
1555 return ERR_OK;
1556 }
1557
nsm_save(char ** out_msg)1558 int MainWindow::nsm_save(char **out_msg)
1559 {
1560 (void)out_msg;
1561
1562 int err = ERR_OK;
1563 if (!saveFile()) err = ERR_GENERAL;
1564 return err;
1565 }
1566 #endif
1567