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