/[svn]/qsampler/trunk/src/qsamplerMainForm.cpp
ViewVC logotype

Contents of /qsampler/trunk/src/qsamplerMainForm.cpp

Parent Directory Parent Directory | Revision Log Revision Log


Revision 4070 - (show annotations) (download)
Wed Jan 3 15:38:52 2024 UTC (2 months, 3 weeks ago) by capela
File size: 91899 byte(s)
- Updated copyright headers into the New Year (2024).
1 // qsamplerMainForm.cpp
2 //
3 /****************************************************************************
4 Copyright (C) 2004-2024, rncbc aka Rui Nuno Capela. All rights reserved.
5 Copyright (C) 2007-2019 Christian Schoenebeck
6
7 This program is free software; you can redistribute it and/or
8 modify it under the terms of the GNU General Public License
9 as published by the Free Software Foundation; either version 2
10 of the License, or (at your option) any later version.
11
12 This program is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
16
17 You should have received a copy of the GNU General Public License along
18 with this program; if not, write to the Free Software Foundation, Inc.,
19 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20
21 *****************************************************************************/
22
23 #include "qsamplerAbout.h"
24 #include "qsamplerMainForm.h"
25
26 #include "qsamplerOptions.h"
27 #include "qsamplerChannel.h"
28 #include "qsamplerMessages.h"
29
30 #include "qsamplerChannelStrip.h"
31 #include "qsamplerInstrumentList.h"
32
33 #include "qsamplerInstrumentListForm.h"
34 #include "qsamplerDeviceForm.h"
35 #include "qsamplerOptionsForm.h"
36 #include "qsamplerDeviceStatusForm.h"
37
38 #include "qsamplerPaletteForm.h"
39
40 #include <QStyleFactory>
41
42 #include <QMdiArea>
43 #include <QMdiSubWindow>
44
45 #include <QApplication>
46 #include <QProcess>
47 #include <QMessageBox>
48
49 #include <QTextStream>
50 #include <QFileDialog>
51 #include <QFileInfo>
52 #include <QFile>
53 #include <QUrl>
54
55 #include <QDragEnterEvent>
56
57 #include <QStatusBar>
58 #include <QSpinBox>
59 #include <QSlider>
60 #include <QLabel>
61 #include <QTimer>
62 #include <QDateTime>
63
64 #include <QElapsedTimer>
65
66 #if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
67 #include <QMimeData>
68 #endif
69
70 #if QT_VERSION < QT_VERSION_CHECK(4, 5, 0)
71 namespace Qt {
72 const WindowFlags WindowCloseButtonHint = WindowFlags(0x08000000);
73 }
74 #endif
75
76 #ifdef CONFIG_LIBGIG
77 #if defined(Q_CC_GNU) || defined(Q_CC_MINGW)
78 #pragma GCC diagnostic push
79 #pragma GCC diagnostic ignored "-Wunused-parameter"
80 #endif
81 #include <gig.h>
82 #if defined(Q_CC_GNU) || defined(Q_CC_MINGW)
83 #pragma GCC diagnostic pop
84 #endif
85 #endif
86
87 // Deprecated QTextStreamFunctions/Qt namespaces workaround.
88 #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
89 #define endl Qt::endl
90 #endif
91
92 // Needed for lroundf()
93 #ifdef CONFIG_ROUND
94 #include <cmath>
95 #else
96 static inline long lroundf ( float x )
97 {
98 if (x >= 0.0f)
99 return long(x + 0.5f);
100 else
101 return long(x - 0.5f);
102 }
103 #endif
104
105
106 // All winsock apps needs this.
107 #if defined(__WIN32__) || defined(_WIN32) || defined(WIN32)
108 static WSADATA _wsaData;
109 #undef HAVE_SIGNAL_H
110 #endif
111
112
113 //-------------------------------------------------------------------------
114 // LADISH Level 1 support stuff.
115
116 #if defined(HAVE_SIGNAL_H) && defined(HAVE_SYS_SOCKET_H)
117
118 #include <QSocketNotifier>
119
120 #include <unistd.h>
121 #include <sys/types.h>
122 #include <sys/socket.h>
123 #include <signal.h>
124
125 // File descriptor for SIGUSR1 notifier.
126 static int g_fdSigusr1[2] = { -1, -1 };
127
128 // Unix SIGUSR1 signal handler.
129 static void qsampler_sigusr1_handler ( int /* signo */ )
130 {
131 char c = 1;
132
133 (void) (::write(g_fdSigusr1[0], &c, sizeof(c)) > 0);
134 }
135
136 // File descriptor for SIGTERM notifier.
137 static int g_fdSigterm[2] = { -1, -1 };
138
139 // Unix SIGTERM signal handler.
140 static void qsampler_sigterm_handler ( int /* signo */ )
141 {
142 char c = 1;
143
144 (void) (::write(g_fdSigterm[0], &c, sizeof(c)) > 0);
145 }
146
147 #endif // HAVE_SIGNAL_H
148
149
150 //-------------------------------------------------------------------------
151 // QSampler -- namespace
152
153
154 namespace QSampler {
155
156 // Timer constant stuff.
157 #define QSAMPLER_TIMER_MSECS 200
158
159 // Status bar item indexes
160 #define QSAMPLER_STATUS_CLIENT 0 // Client connection state.
161 #define QSAMPLER_STATUS_SERVER 1 // Currenr server address (host:port)
162 #define QSAMPLER_STATUS_CHANNEL 2 // Active channel caption.
163 #define QSAMPLER_STATUS_SESSION 3 // Current session modification state.
164
165
166 // Specialties for thread-callback comunication.
167 #define QSAMPLER_LSCP_EVENT QEvent::Type(QEvent::User + 1)
168
169
170 //-------------------------------------------------------------------------
171 // QSampler::LscpEvent -- specialty for LSCP callback comunication.
172
173 class LscpEvent : public QEvent
174 {
175 public:
176
177 // Constructor.
178 LscpEvent(lscp_event_t event, const char *pchData, int cchData)
179 : QEvent(QSAMPLER_LSCP_EVENT)
180 {
181 m_event = event;
182 m_data = QString::fromUtf8(pchData, cchData);
183 }
184
185 // Accessors.
186 lscp_event_t event() { return m_event; }
187 const QString& data() { return m_data; }
188
189 private:
190
191 // The proper event type.
192 lscp_event_t m_event;
193 // The event data as a string.
194 QString m_data;
195 };
196
197
198 //-------------------------------------------------------------------------
199 // QSampler::Workspace -- Main window workspace (MDI Area) decl.
200
201 class Workspace : public QMdiArea
202 {
203 public:
204
205 Workspace(MainForm *pMainForm) : QMdiArea(pMainForm) {}
206
207 protected:
208
209 void resizeEvent(QResizeEvent *)
210 {
211 MainForm *pMainForm = static_cast<MainForm *> (parentWidget());
212 if (pMainForm)
213 pMainForm->channelsArrangeAuto();
214 }
215 };
216
217
218 //-------------------------------------------------------------------------
219 // QSampler::MainForm -- Main window form implementation.
220
221 // Kind of singleton reference.
222 MainForm *MainForm::g_pMainForm = nullptr;
223
224 MainForm::MainForm ( QWidget *pParent )
225 : QMainWindow(pParent)
226 {
227 m_ui.setupUi(this);
228 #if QT_VERSION < QT_VERSION_CHECK(6, 1, 0)
229 QMainWindow::setWindowIcon(QIcon(":/images/qsampler.png"));
230 #endif
231 // Pseudo-singleton reference setup.
232 g_pMainForm = this;
233
234 // Initialize some pointer references.
235 m_pOptions = nullptr;
236
237 // All child forms are to be created later, not earlier than setup.
238 m_pMessages = nullptr;
239 m_pInstrumentListForm = nullptr;
240 m_pDeviceForm = nullptr;
241
242 // We'll start clean.
243 m_iUntitled = 0;
244 m_iDirtySetup = 0;
245 m_iDirtyCount = 0;
246
247 m_pServer = nullptr;
248 m_pClient = nullptr;
249
250 m_iStartDelay = 0;
251 m_iTimerDelay = 0;
252
253 m_iTimerSlot = 0;
254
255 #if defined(HAVE_SIGNAL_H) && defined(HAVE_SYS_SOCKET_H)
256
257 // Set to ignore any fatal "Broken pipe" signals.
258 ::signal(SIGPIPE, SIG_IGN);
259
260 // LADISH Level 1 suport.
261
262 // Initialize file descriptors for SIGUSR1 socket notifier.
263 ::socketpair(AF_UNIX, SOCK_STREAM, 0, g_fdSigusr1);
264 m_pSigusr1Notifier
265 = new QSocketNotifier(g_fdSigusr1[1], QSocketNotifier::Read, this);
266
267 QObject::connect(m_pSigusr1Notifier,
268 SIGNAL(activated(int)),
269 SLOT(handle_sigusr1()));
270
271 // Install SIGUSR1 signal handler.
272 struct sigaction sigusr1;
273 sigusr1.sa_handler = qsampler_sigusr1_handler;
274 sigemptyset(&sigusr1.sa_mask);
275 sigusr1.sa_flags = 0;
276 sigusr1.sa_flags |= SA_RESTART;
277 ::sigaction(SIGUSR1, &sigusr1, nullptr);
278
279 // Initialize file descriptors for SIGTERM socket notifier.
280 ::socketpair(AF_UNIX, SOCK_STREAM, 0, g_fdSigterm);
281 m_pSigtermNotifier
282 = new QSocketNotifier(g_fdSigterm[1], QSocketNotifier::Read, this);
283
284 QObject::connect(m_pSigtermNotifier,
285 SIGNAL(activated(int)),
286 SLOT(handle_sigterm()));
287
288 // Install SIGTERM signal handler.
289 struct sigaction sigterm;
290 sigterm.sa_handler = qsampler_sigterm_handler;
291 sigemptyset(&sigterm.sa_mask);
292 sigterm.sa_flags = 0;
293 sigterm.sa_flags |= SA_RESTART;
294 ::sigaction(SIGTERM, &sigterm, nullptr);
295 ::sigaction(SIGQUIT, &sigterm, nullptr);
296
297 // Ignore SIGHUP/SIGINT signals.
298 ::signal(SIGHUP, SIG_IGN);
299 ::signal(SIGINT, SIG_IGN);
300
301 #else // HAVE_SIGNAL_H
302
303 m_pSigusr1Notifier = nullptr;
304 m_pSigtermNotifier = nullptr;
305
306 #endif // !HAVE_SIGNAL_H
307
308 #ifdef CONFIG_VOLUME
309 // Make some extras into the toolbar...
310 const QString& sVolumeText = tr("Master volume");
311 m_iVolumeChanging = 0;
312 // Volume slider...
313 m_ui.channelsToolbar->addSeparator();
314 m_pVolumeSlider = new QSlider(Qt::Horizontal, m_ui.channelsToolbar);
315 m_pVolumeSlider->setTickPosition(QSlider::TicksBothSides);
316 m_pVolumeSlider->setTickInterval(10);
317 m_pVolumeSlider->setPageStep(10);
318 m_pVolumeSlider->setSingleStep(10);
319 m_pVolumeSlider->setMinimum(0);
320 m_pVolumeSlider->setMaximum(100);
321 m_pVolumeSlider->setMaximumHeight(26);
322 m_pVolumeSlider->setMinimumWidth(160);
323 m_pVolumeSlider->setToolTip(sVolumeText);
324 QObject::connect(m_pVolumeSlider,
325 SIGNAL(valueChanged(int)),
326 SLOT(volumeChanged(int)));
327 m_ui.channelsToolbar->addWidget(m_pVolumeSlider);
328 // Volume spin-box
329 m_ui.channelsToolbar->addSeparator();
330 m_pVolumeSpinBox = new QSpinBox(m_ui.channelsToolbar);
331 m_pVolumeSpinBox->setSuffix(" %");
332 m_pVolumeSpinBox->setMinimum(0);
333 m_pVolumeSpinBox->setMaximum(100);
334 m_pVolumeSpinBox->setToolTip(sVolumeText);
335 QObject::connect(m_pVolumeSpinBox,
336 SIGNAL(valueChanged(int)),
337 SLOT(volumeChanged(int)));
338 m_ui.channelsToolbar->addWidget(m_pVolumeSpinBox);
339 #endif
340
341 // Make it an MDI workspace.
342 m_pWorkspace = new Workspace(this);
343 m_pWorkspace->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
344 m_pWorkspace->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded);
345 // Set the activation connection.
346 QObject::connect(m_pWorkspace,
347 SIGNAL(subWindowActivated(QMdiSubWindow *)),
348 SLOT(activateStrip(QMdiSubWindow *)));
349 // Make it shine :-)
350 setCentralWidget(m_pWorkspace);
351
352 // Create some statusbar labels...
353 QLabel *pLabel;
354 // Client status.
355 pLabel = new QLabel(tr("Connected"), this);
356 pLabel->setAlignment(Qt::AlignLeft);
357 pLabel->setMinimumSize(pLabel->sizeHint());
358 m_statusItem[QSAMPLER_STATUS_CLIENT] = pLabel;
359 statusBar()->addWidget(pLabel);
360 // Server address.
361 pLabel = new QLabel(this);
362 pLabel->setAlignment(Qt::AlignLeft);
363 m_statusItem[QSAMPLER_STATUS_SERVER] = pLabel;
364 statusBar()->addWidget(pLabel, 1);
365 // Channel title.
366 pLabel = new QLabel(this);
367 pLabel->setAlignment(Qt::AlignLeft);
368 m_statusItem[QSAMPLER_STATUS_CHANNEL] = pLabel;
369 statusBar()->addWidget(pLabel, 2);
370 // Session modification status.
371 pLabel = new QLabel(tr("MOD"), this);
372 pLabel->setAlignment(Qt::AlignHCenter);
373 pLabel->setMinimumSize(pLabel->sizeHint());
374 m_statusItem[QSAMPLER_STATUS_SESSION] = pLabel;
375 statusBar()->addWidget(pLabel);
376
377 #if defined(__WIN32__) || defined(_WIN32) || defined(WIN32)
378 WSAStartup(MAKEWORD(1, 1), &_wsaData);
379 #endif
380
381 // Some actions surely need those
382 // shortcuts firmly attached...
383 addAction(m_ui.viewMenubarAction);
384 addAction(m_ui.viewToolbarAction);
385
386 QObject::connect(m_ui.fileNewAction,
387 SIGNAL(triggered()),
388 SLOT(fileNew()));
389 QObject::connect(m_ui.fileOpenAction,
390 SIGNAL(triggered()),
391 SLOT(fileOpen()));
392 QObject::connect(m_ui.fileSaveAction,
393 SIGNAL(triggered()),
394 SLOT(fileSave()));
395 QObject::connect(m_ui.fileSaveAsAction,
396 SIGNAL(triggered()),
397 SLOT(fileSaveAs()));
398 QObject::connect(m_ui.fileResetAction,
399 SIGNAL(triggered()),
400 SLOT(fileReset()));
401 QObject::connect(m_ui.fileRestartAction,
402 SIGNAL(triggered()),
403 SLOT(fileRestart()));
404 QObject::connect(m_ui.fileExitAction,
405 SIGNAL(triggered()),
406 SLOT(fileExit()));
407 QObject::connect(m_ui.editAddChannelAction,
408 SIGNAL(triggered()),
409 SLOT(editAddChannel()));
410 QObject::connect(m_ui.editRemoveChannelAction,
411 SIGNAL(triggered()),
412 SLOT(editRemoveChannel()));
413 QObject::connect(m_ui.editSetupChannelAction,
414 SIGNAL(triggered()),
415 SLOT(editSetupChannel()));
416 QObject::connect(m_ui.editEditChannelAction,
417 SIGNAL(triggered()),
418 SLOT(editEditChannel()));
419 QObject::connect(m_ui.editResetChannelAction,
420 SIGNAL(triggered()),
421 SLOT(editResetChannel()));
422 QObject::connect(m_ui.editResetAllChannelsAction,
423 SIGNAL(triggered()),
424 SLOT(editResetAllChannels()));
425 QObject::connect(m_ui.viewMenubarAction,
426 SIGNAL(toggled(bool)),
427 SLOT(viewMenubar(bool)));
428 QObject::connect(m_ui.viewToolbarAction,
429 SIGNAL(toggled(bool)),
430 SLOT(viewToolbar(bool)));
431 QObject::connect(m_ui.viewStatusbarAction,
432 SIGNAL(toggled(bool)),
433 SLOT(viewStatusbar(bool)));
434 QObject::connect(m_ui.viewMessagesAction,
435 SIGNAL(toggled(bool)),
436 SLOT(viewMessages(bool)));
437 QObject::connect(m_ui.viewInstrumentsAction,
438 SIGNAL(triggered()),
439 SLOT(viewInstruments()));
440 QObject::connect(m_ui.viewDevicesAction,
441 SIGNAL(triggered()),
442 SLOT(viewDevices()));
443 QObject::connect(m_ui.viewOptionsAction,
444 SIGNAL(triggered()),
445 SLOT(viewOptions()));
446 QObject::connect(m_ui.channelsArrangeAction,
447 SIGNAL(triggered()),
448 SLOT(channelsArrange()));
449 QObject::connect(m_ui.channelsAutoArrangeAction,
450 SIGNAL(toggled(bool)),
451 SLOT(channelsAutoArrange(bool)));
452 QObject::connect(m_ui.helpAboutAction,
453 SIGNAL(triggered()),
454 SLOT(helpAbout()));
455 QObject::connect(m_ui.helpAboutQtAction,
456 SIGNAL(triggered()),
457 SLOT(helpAboutQt()));
458
459 QObject::connect(m_ui.fileMenu,
460 SIGNAL(aboutToShow()),
461 SLOT(updateRecentFilesMenu()));
462 QObject::connect(m_ui.channelsMenu,
463 SIGNAL(aboutToShow()),
464 SLOT(channelsMenuAboutToShow()));
465 #ifdef CONFIG_VOLUME
466 QObject::connect(m_ui.channelsToolbar,
467 SIGNAL(orientationChanged(Qt::Orientation)),
468 SLOT(channelsToolbarOrientation(Qt::Orientation)));
469 #endif
470 }
471
472 // Destructor.
473 MainForm::~MainForm()
474 {
475 // Do final processing anyway.
476 processServerExit();
477
478 #if defined(__WIN32__) || defined(_WIN32) || defined(WIN32)
479 WSACleanup();
480 #endif
481
482 #if defined(HAVE_SIGNAL_H) && defined(HAVE_SYS_SOCKET_H)
483 if (m_pSigusr1Notifier)
484 delete m_pSigusr1Notifier;
485 if (m_pSigtermNotifier)
486 delete m_pSigtermNotifier;
487 #endif
488
489 // Finally drop any widgets around...
490 if (m_pDeviceForm)
491 delete m_pDeviceForm;
492 if (m_pInstrumentListForm)
493 delete m_pInstrumentListForm;
494 if (m_pMessages)
495 delete m_pMessages;
496 if (m_pWorkspace)
497 delete m_pWorkspace;
498
499 // Delete status item labels one by one.
500 if (m_statusItem[QSAMPLER_STATUS_CLIENT])
501 delete m_statusItem[QSAMPLER_STATUS_CLIENT];
502 if (m_statusItem[QSAMPLER_STATUS_SERVER])
503 delete m_statusItem[QSAMPLER_STATUS_SERVER];
504 if (m_statusItem[QSAMPLER_STATUS_CHANNEL])
505 delete m_statusItem[QSAMPLER_STATUS_CHANNEL];
506 if (m_statusItem[QSAMPLER_STATUS_SESSION])
507 delete m_statusItem[QSAMPLER_STATUS_SESSION];
508
509 #ifdef CONFIG_VOLUME
510 delete m_pVolumeSpinBox;
511 delete m_pVolumeSlider;
512 #endif
513
514 // Pseudo-singleton reference shut-down.
515 g_pMainForm = nullptr;
516 }
517
518
519 // Make and set a proper setup options step.
520 void MainForm::setup ( Options *pOptions )
521 {
522 // We got options?
523 m_pOptions = pOptions;
524
525 // What style do we create these forms?
526 Qt::WindowFlags wflags = Qt::Window
527 | Qt::CustomizeWindowHint
528 | Qt::WindowTitleHint
529 | Qt::WindowSystemMenuHint
530 | Qt::WindowMinMaxButtonsHint
531 | Qt::WindowCloseButtonHint;
532 if (m_pOptions->bKeepOnTop)
533 wflags |= Qt::Tool;
534
535 // Some child forms are to be created right now.
536 m_pMessages = new Messages(this);
537 m_pDeviceForm = new DeviceForm(this, wflags);
538 #ifdef CONFIG_MIDI_INSTRUMENT
539 m_pInstrumentListForm = new InstrumentListForm(this, wflags);
540 #else
541 m_ui.viewInstrumentsAction->setEnabled(false);
542 #endif
543
544 // Setup messages logging appropriately...
545 m_pMessages->setLogging(
546 m_pOptions->bMessagesLog,
547 m_pOptions->sMessagesLogPath);
548
549 // Set message defaults...
550 updateMessagesFont();
551 updateMessagesLimit();
552 updateMessagesCapture();
553
554 // Set the visibility signal.
555 QObject::connect(m_pMessages,
556 SIGNAL(visibilityChanged(bool)),
557 SLOT(stabilizeForm()));
558
559 // Initial decorations toggle state.
560 m_ui.viewMenubarAction->setChecked(m_pOptions->bMenubar);
561 m_ui.viewToolbarAction->setChecked(m_pOptions->bToolbar);
562 m_ui.viewStatusbarAction->setChecked(m_pOptions->bStatusbar);
563 m_ui.channelsAutoArrangeAction->setChecked(m_pOptions->bAutoArrange);
564
565 // Initial decorations visibility state.
566 viewMenubar(m_pOptions->bMenubar);
567 viewToolbar(m_pOptions->bToolbar);
568 viewStatusbar(m_pOptions->bStatusbar);
569
570 addDockWidget(Qt::BottomDockWidgetArea, m_pMessages);
571
572 // Restore whole dock windows state.
573 QByteArray aDockables = m_pOptions->settings().value(
574 "/Layout/DockWindows").toByteArray();
575 if (!aDockables.isEmpty()) {
576 restoreState(aDockables);
577 }
578
579 // Try to restore old window positioning and initial visibility.
580 m_pOptions->loadWidgetGeometry(this, true);
581 m_pOptions->loadWidgetGeometry(m_pInstrumentListForm);
582 m_pOptions->loadWidgetGeometry(m_pDeviceForm);
583
584 // Final startup stabilization...
585 updateMaxVolume();
586 updateRecentFilesMenu();
587 stabilizeForm();
588
589 // Make it ready :-)
590 statusBar()->showMessage(tr("Ready"), 3000);
591
592 // We'll try to start immediately...
593 startSchedule(0);
594
595 // Register the first timer slot.
596 QTimer::singleShot(QSAMPLER_TIMER_MSECS, this, SLOT(timerSlot()));
597 }
598
599
600 // Window close event handlers.
601 bool MainForm::queryClose (void)
602 {
603 bool bQueryClose = closeSession(false);
604
605 // Try to save current general state...
606 if (m_pOptions) {
607 // Some windows default fonts is here on demand too.
608 if (bQueryClose && m_pMessages)
609 m_pOptions->sMessagesFont = m_pMessages->messagesFont().toString();
610 // Try to save current positioning.
611 if (bQueryClose) {
612 // Save decorations state.
613 m_pOptions->bMenubar = m_ui.MenuBar->isVisible();
614 m_pOptions->bToolbar = (m_ui.fileToolbar->isVisible()
615 || m_ui.editToolbar->isVisible()
616 || m_ui.channelsToolbar->isVisible());
617 m_pOptions->bStatusbar = statusBar()->isVisible();
618 // Save the dock windows state.
619 m_pOptions->settings().setValue("/Layout/DockWindows", saveState());
620 // And the children, and the main windows state,.
621 m_pOptions->saveWidgetGeometry(m_pDeviceForm);
622 m_pOptions->saveWidgetGeometry(m_pInstrumentListForm);
623 m_pOptions->saveWidgetGeometry(this, true);
624 // Close popup widgets.
625 if (m_pInstrumentListForm)
626 m_pInstrumentListForm->close();
627 if (m_pDeviceForm)
628 m_pDeviceForm->close();
629 // Stop client and/or server, gracefully.
630 stopServer(true /*interactive*/);
631 }
632 }
633
634 return bQueryClose;
635 }
636
637
638 void MainForm::closeEvent ( QCloseEvent *pCloseEvent )
639 {
640 if (queryClose()) {
641 DeviceStatusForm::deleteAllInstances();
642 pCloseEvent->accept();
643 } else
644 pCloseEvent->ignore();
645 }
646
647
648 // Window drag-n-drop event handlers.
649 void MainForm::dragEnterEvent ( QDragEnterEvent* pDragEnterEvent )
650 {
651 // Accept external drags only...
652 if (pDragEnterEvent->source() == nullptr
653 && pDragEnterEvent->mimeData()->hasUrls()) {
654 pDragEnterEvent->accept();
655 } else {
656 pDragEnterEvent->ignore();
657 }
658 }
659
660
661 void MainForm::dropEvent ( QDropEvent *pDropEvent )
662 {
663 // Accept externally originated drops only...
664 if (pDropEvent->source())
665 return;
666
667 const QMimeData *pMimeData = pDropEvent->mimeData();
668 if (pMimeData->hasUrls()) {
669 QListIterator<QUrl> iter(pMimeData->urls());
670 while (iter.hasNext()) {
671 const QString& sPath = iter.next().toLocalFile();
672 // if (Channel::isDlsInstrumentFile(sPath)) {
673 if (QFileInfo(sPath).exists()) {
674 // Try to create a new channel from instrument file...
675 Channel *pChannel = new Channel();
676 if (pChannel == nullptr)
677 return;
678 // Start setting the instrument filename...
679 pChannel->setInstrument(sPath, 0);
680 // Before we show it up, may be we'll
681 // better ask for some initial values?
682 if (!pChannel->channelSetup(this)) {
683 delete pChannel;
684 return;
685 }
686 // Finally, give it to a new channel strip...
687 if (!createChannelStrip(pChannel)) {
688 delete pChannel;
689 return;
690 }
691 // Make that an overall update.
692 m_iDirtyCount++;
693 stabilizeForm();
694 } // Otherwise, load an usual session file (LSCP script)...
695 else if (closeSession(true)) {
696 loadSessionFile(sPath);
697 break;
698 }
699 }
700 // Make it look responsive...:)
701 QApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
702 }
703 }
704
705
706 // Custome event handler.
707 void MainForm::customEvent ( QEvent* pEvent )
708 {
709 // For the time being, just pump it to messages.
710 if (pEvent->type() == QSAMPLER_LSCP_EVENT) {
711 LscpEvent *pLscpEvent = static_cast<LscpEvent *> (pEvent);
712 switch (pLscpEvent->event()) {
713 case LSCP_EVENT_CHANNEL_COUNT:
714 updateAllChannelStrips(true);
715 break;
716 case LSCP_EVENT_CHANNEL_INFO: {
717 const int iChannelID = pLscpEvent->data().toInt();
718 ChannelStrip *pChannelStrip = channelStrip(iChannelID);
719 if (pChannelStrip)
720 channelStripChanged(pChannelStrip);
721 break;
722 }
723 case LSCP_EVENT_MIDI_INPUT_DEVICE_COUNT:
724 if (m_pDeviceForm) m_pDeviceForm->refreshDevices();
725 DeviceStatusForm::onDevicesChanged();
726 updateViewMidiDeviceStatusMenu();
727 break;
728 case LSCP_EVENT_MIDI_INPUT_DEVICE_INFO: {
729 if (m_pDeviceForm) m_pDeviceForm->refreshDevices();
730 const int iDeviceID = pLscpEvent->data().section(' ', 0, 0).toInt();
731 DeviceStatusForm::onDeviceChanged(iDeviceID);
732 break;
733 }
734 case LSCP_EVENT_AUDIO_OUTPUT_DEVICE_COUNT:
735 if (m_pDeviceForm) m_pDeviceForm->refreshDevices();
736 break;
737 case LSCP_EVENT_AUDIO_OUTPUT_DEVICE_INFO:
738 if (m_pDeviceForm) m_pDeviceForm->refreshDevices();
739 break;
740 #if CONFIG_EVENT_CHANNEL_MIDI
741 case LSCP_EVENT_CHANNEL_MIDI: {
742 const int iChannelID = pLscpEvent->data().section(' ', 0, 0).toInt();
743 ChannelStrip *pChannelStrip = channelStrip(iChannelID);
744 if (pChannelStrip)
745 pChannelStrip->midiActivityLedOn();
746 break;
747 }
748 #endif
749 #if CONFIG_EVENT_DEVICE_MIDI
750 case LSCP_EVENT_DEVICE_MIDI: {
751 const int iDeviceID = pLscpEvent->data().section(' ', 0, 0).toInt();
752 const int iPortID = pLscpEvent->data().section(' ', 1, 1).toInt();
753 DeviceStatusForm *pDeviceStatusForm
754 = DeviceStatusForm::getInstance(iDeviceID);
755 if (pDeviceStatusForm)
756 pDeviceStatusForm->midiArrived(iPortID);
757 break;
758 }
759 #endif
760 default:
761 appendMessagesColor(tr("LSCP Event: %1 data: %2")
762 .arg(::lscp_event_to_text(pLscpEvent->event()))
763 .arg(pLscpEvent->data()), "#996699");
764 }
765 }
766 }
767
768
769 // LADISH Level 1 -- SIGUSR1 signal handler.
770 void MainForm::handle_sigusr1 (void)
771 {
772 #if defined(HAVE_SIGNAL_H) && defined(HAVE_SYS_SOCKET_H)
773
774 char c;
775
776 if (::read(g_fdSigusr1[1], &c, sizeof(c)) > 0)
777 saveSession(false);
778
779 #endif
780 }
781
782
783 void MainForm::handle_sigterm (void)
784 {
785 #if defined(HAVE_SIGNAL_H) && defined(HAVE_SYS_SOCKET_H)
786
787 char c;
788
789 if (::read(g_fdSigterm[1], &c, sizeof(c)) > 0)
790 close();
791
792 #endif
793 }
794
795
796 void MainForm::updateViewMidiDeviceStatusMenu (void)
797 {
798 m_ui.viewMidiDeviceStatusMenu->clear();
799 const std::map<int, DeviceStatusForm *> statusForms
800 = DeviceStatusForm::getInstances();
801 std::map<int, DeviceStatusForm *>::const_iterator iter
802 = statusForms.begin();
803 for ( ; iter != statusForms.end(); ++iter) {
804 DeviceStatusForm *pStatusForm = iter->second;
805 m_ui.viewMidiDeviceStatusMenu->addAction(
806 pStatusForm->visibleAction());
807 }
808 }
809
810
811 // Context menu event handler.
812 void MainForm::contextMenuEvent( QContextMenuEvent *pEvent )
813 {
814 stabilizeForm();
815
816 m_ui.editMenu->exec(pEvent->globalPos());
817 }
818
819
820 //-------------------------------------------------------------------------
821 // QSampler::MainForm -- Brainless public property accessors.
822
823 // The global options settings property.
824 Options *MainForm::options (void) const
825 {
826 return m_pOptions;
827 }
828
829
830 // The LSCP client descriptor property.
831 lscp_client_t *MainForm::client (void) const
832 {
833 return m_pClient;
834 }
835
836
837 // The pseudo-singleton instance accessor.
838 MainForm *MainForm::getInstance (void)
839 {
840 return g_pMainForm;
841 }
842
843
844 //-------------------------------------------------------------------------
845 // QSampler::MainForm -- Session file stuff.
846
847 // Format the displayable session filename.
848 QString MainForm::sessionName ( const QString& sFilename )
849 {
850 const bool bCompletePath = (m_pOptions && m_pOptions->bCompletePath);
851 QString sSessionName = sFilename;
852 if (sSessionName.isEmpty())
853 sSessionName = tr("Untitled") + QString::number(m_iUntitled);
854 else if (!bCompletePath)
855 sSessionName = QFileInfo(sSessionName).fileName();
856 return sSessionName;
857 }
858
859
860 // Create a new session file from scratch.
861 bool MainForm::newSession (void)
862 {
863 // Check if we can do it.
864 if (!closeSession(true))
865 return false;
866
867 // Give us what the server has, right now...
868 updateSession();
869
870 // Ok increment untitled count.
871 m_iUntitled++;
872
873 // Stabilize form.
874 m_sFilename = QString();
875 m_iDirtyCount = 0;
876 appendMessages(tr("New session: \"%1\".").arg(sessionName(m_sFilename)));
877 stabilizeForm();
878
879 return true;
880 }
881
882
883 // Open an existing sampler session.
884 bool MainForm::openSession (void)
885 {
886 if (m_pOptions == nullptr)
887 return false;
888
889 // Ask for the filename to open...
890 QString sFilename = QFileDialog::getOpenFileName(this,
891 tr("Open Session"), // Caption.
892 m_pOptions->sSessionDir, // Start here.
893 tr("LSCP Session files") + " (*.lscp)" // Filter (LSCP files)
894 );
895
896 // Have we cancelled?
897 if (sFilename.isEmpty())
898 return false;
899
900 // Check if we're going to discard safely the current one...
901 if (!closeSession(true))
902 return false;
903
904 // Load it right away.
905 return loadSessionFile(sFilename);
906 }
907
908
909 // Save current sampler session with another name.
910 bool MainForm::saveSession ( bool bPrompt )
911 {
912 if (m_pOptions == nullptr)
913 return false;
914
915 QString sFilename = m_sFilename;
916
917 // Ask for the file to save, if there's none...
918 if (bPrompt || sFilename.isEmpty()) {
919 // If none is given, assume default directory.
920 if (sFilename.isEmpty())
921 sFilename = m_pOptions->sSessionDir;
922 // Prompt the guy...
923 sFilename = QFileDialog::getSaveFileName(this,
924 tr("Save Session"), // Caption.
925 sFilename, // Start here.
926 tr("LSCP Session files") + " (*.lscp)" // Filter (LSCP files)
927 );
928 // Have we cancelled it?
929 if (sFilename.isEmpty())
930 return false;
931 // Enforce .lscp extension...
932 if (QFileInfo(sFilename).suffix().isEmpty())
933 sFilename += ".lscp";
934 #if 0
935 // Check if already exists...
936 if (sFilename != m_sFilename && QFileInfo(sFilename).exists()) {
937 if (QMessageBox::warning(this,
938 tr("Warning"),
939 tr("The file already exists:\n\n"
940 "\"%1\"\n\n"
941 "Do you want to replace it?")
942 .arg(sFilename),
943 QMessageBox::Yes | QMessageBox::No)
944 == QMessageBox::No)
945 return false;
946 }
947 #endif
948 }
949
950 // Save it right away.
951 return saveSessionFile(sFilename);
952 }
953
954
955 // Close current session.
956 bool MainForm::closeSession ( bool bForce )
957 {
958 bool bClose = true;
959
960 // Are we dirty enough to prompt it?
961 if (m_iDirtyCount > 0) {
962 switch (QMessageBox::warning(this,
963 tr("Warning"),
964 tr("The current session has been changed:\n\n"
965 "\"%1\"\n\n"
966 "Do you want to save the changes?")
967 .arg(sessionName(m_sFilename)),
968 QMessageBox::Save |
969 QMessageBox::Discard |
970 QMessageBox::Cancel)) {
971 case QMessageBox::Save:
972 bClose = saveSession(false);
973 // Fall thru....
974 case QMessageBox::Discard:
975 break;
976 default: // Cancel.
977 bClose = false;
978 break;
979 }
980 }
981
982 // If we may close it, dot it.
983 if (bClose) {
984 // Remove all channel strips from sight...
985 m_pWorkspace->setUpdatesEnabled(false);
986 const QList<QMdiSubWindow *>& wlist
987 = m_pWorkspace->subWindowList();
988 foreach (QMdiSubWindow *pMdiSubWindow, wlist) {
989 ChannelStrip *pChannelStrip
990 = static_cast<ChannelStrip *> (pMdiSubWindow->widget());
991 if (pChannelStrip) {
992 Channel *pChannel = pChannelStrip->channel();
993 if (bForce && pChannel)
994 pChannel->removeChannel();
995 delete pChannelStrip;
996 }
997 delete pMdiSubWindow;
998 }
999 m_pWorkspace->setUpdatesEnabled(true);
1000 // We're now clean, for sure.
1001 m_iDirtyCount = 0;
1002 }
1003
1004 return bClose;
1005 }
1006
1007
1008 // Load a session from specific file path.
1009 bool MainForm::loadSessionFile ( const QString& sFilename )
1010 {
1011 if (m_pClient == nullptr)
1012 return false;
1013
1014 // Open and read from real file.
1015 QFile file(sFilename);
1016 if (!file.open(QIODevice::ReadOnly)) {
1017 appendMessagesError(
1018 tr("Could not open \"%1\" session file.\n\nSorry.")
1019 .arg(sFilename));
1020 return false;
1021 }
1022
1023 // Tell the world we'll take some time...
1024 QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
1025
1026 // Read the file.
1027 int iLine = 0;
1028 int iErrors = 0;
1029 QTextStream ts(&file);
1030 while (!ts.atEnd()) {
1031 // Read the line.
1032 QString sCommand = ts.readLine().trimmed();
1033 iLine++;
1034 // If not empty, nor a comment, call the server...
1035 if (!sCommand.isEmpty() && sCommand[0] != '#') {
1036 // Remember that, no matter what,
1037 // all LSCP commands are CR/LF terminated.
1038 sCommand += "\r\n";
1039 if (::lscp_client_query(m_pClient, sCommand.toUtf8().constData())
1040 != LSCP_OK) {
1041 appendMessagesColor(QString("%1(%2): %3")
1042 .arg(QFileInfo(sFilename).fileName()).arg(iLine)
1043 .arg(sCommand.simplified()), "#996633");
1044 appendMessagesClient("lscp_client_query");
1045 iErrors++;
1046 }
1047 }
1048 // Try to make it snappy :)
1049 QApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
1050 }
1051
1052 // Ok. we've read it.
1053 file.close();
1054
1055 // Now we'll try to create (update) the whole GUI session.
1056 updateSession();
1057
1058 // We're fornerly done.
1059 QApplication::restoreOverrideCursor();
1060
1061 // Have we any errors?
1062 if (iErrors > 0) {
1063 appendMessagesError(
1064 tr("Session loaded with errors\nfrom \"%1\".\n\nSorry.")
1065 .arg(sFilename));
1066 }
1067
1068 // Save as default session directory.
1069 if (m_pOptions)
1070 m_pOptions->sSessionDir = QFileInfo(sFilename).dir().absolutePath();
1071 // We're not dirty anymore, if loaded without errors,
1072 m_iDirtyCount = iErrors;
1073 // Stabilize form...
1074 m_sFilename = sFilename;
1075 updateRecentFiles(sFilename);
1076 appendMessages(tr("Open session: \"%1\".").arg(sessionName(m_sFilename)));
1077
1078 // Make that an overall update.
1079 stabilizeForm();
1080 return true;
1081 }
1082
1083
1084 // Save current session to specific file path.
1085 bool MainForm::saveSessionFile ( const QString& sFilename )
1086 {
1087 if (m_pClient == nullptr)
1088 return false;
1089
1090 // Check whether server is apparently OK...
1091 if (::lscp_get_channels(m_pClient) < 0) {
1092 appendMessagesClient("lscp_get_channels");
1093 return false;
1094 }
1095
1096 // Open and write into real file.
1097 QFile file(sFilename);
1098 if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
1099 appendMessagesError(
1100 tr("Could not open \"%1\" session file.\n\nSorry.")
1101 .arg(sFilename));
1102 return false;
1103 }
1104
1105 // Tell the world we'll take some time...
1106 QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
1107
1108 // Write the file.
1109 int iErrors = 0;
1110 QTextStream ts(&file);
1111 ts << "# " << QSAMPLER_TITLE " - " << tr(QSAMPLER_SUBTITLE) << endl;
1112 ts << "# " << tr("Version") << ": " PROJECT_VERSION << endl;
1113 // ts << "# " << tr("Build") << ": " CONFIG_BUILD_DATE << endl;
1114 ts << "#" << endl;
1115 ts << "# " << tr("File")
1116 << ": " << QFileInfo(sFilename).fileName() << endl;
1117 ts << "# " << tr("Date")
1118 << ": " << QDate::currentDate().toString("MMM dd yyyy")
1119 << " " << QTime::currentTime().toString("hh:mm:ss") << endl;
1120 ts << "#" << endl;
1121 ts << endl;
1122
1123 // It is assumed that this new kind of device+session file
1124 // will be loaded from a complete initialized server...
1125 int *piDeviceIDs;
1126 int i, iDevice;
1127 ts << "RESET" << endl;
1128
1129 // Audio device mapping.
1130 QMap<int, int> audioDeviceMap; iDevice = 0;
1131 piDeviceIDs = Device::getDevices(m_pClient, Device::Audio);
1132 for (i = 0; piDeviceIDs && piDeviceIDs[i] >= 0; ++i) {
1133 Device device(Device::Audio, piDeviceIDs[i]);
1134 // Avoid plug-in driver devices...
1135 if (device.driverName().toUpper() == "PLUGIN")
1136 continue;
1137 // Audio device specification...
1138 ts << endl;
1139 ts << "# " << device.deviceTypeName() << " " << device.driverName()
1140 << " " << tr("Device") << " " << iDevice << endl;
1141 ts << "CREATE AUDIO_OUTPUT_DEVICE " << device.driverName();
1142 DeviceParamMap::ConstIterator deviceParam;
1143 for (deviceParam = device.params().begin();
1144 deviceParam != device.params().end();
1145 ++deviceParam) {
1146 const DeviceParam& param = deviceParam.value();
1147 if (param.value.isEmpty()) ts << "# ";
1148 ts << " " << deviceParam.key() << "='" << param.value << "'";
1149 }
1150 ts << endl;
1151 // Audio channel parameters...
1152 int iPort = 0;
1153 QListIterator<DevicePort *> iter(device.ports());
1154 while (iter.hasNext()) {
1155 DevicePort *pPort = iter.next();
1156 DeviceParamMap::ConstIterator portParam;
1157 for (portParam = pPort->params().begin();
1158 portParam != pPort->params().end();
1159 ++portParam) {
1160 const DeviceParam& param = portParam.value();
1161 if (param.fix || param.value.isEmpty()) ts << "# ";
1162 ts << "SET AUDIO_OUTPUT_CHANNEL_PARAMETER " << iDevice
1163 << " " << iPort << " " << portParam.key()
1164 << "='" << param.value << "'" << endl;
1165 }
1166 iPort++;
1167 }
1168 // Audio device index/id mapping.
1169 audioDeviceMap.insert(device.deviceID(), iDevice++);
1170 // Try to keep it snappy :)
1171 QApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
1172 }
1173
1174 // MIDI device mapping.
1175 QMap<int, int> midiDeviceMap; iDevice = 0;
1176 piDeviceIDs = Device::getDevices(m_pClient, Device::Midi);
1177 for (i = 0; piDeviceIDs && piDeviceIDs[i] >= 0; ++i) {
1178 Device device(Device::Midi, piDeviceIDs[i]);
1179 // Avoid plug-in driver devices...
1180 if (device.driverName().toUpper() == "PLUGIN")
1181 continue;
1182 // MIDI device specification...
1183 ts << endl;
1184 ts << "# " << device.deviceTypeName() << " " << device.driverName()
1185 << " " << tr("Device") << " " << iDevice << endl;
1186 ts << "CREATE MIDI_INPUT_DEVICE " << device.driverName();
1187 DeviceParamMap::ConstIterator deviceParam;
1188 for (deviceParam = device.params().begin();
1189 deviceParam != device.params().end();
1190 ++deviceParam) {
1191 const DeviceParam& param = deviceParam.value();
1192 if (param.value.isEmpty()) ts << "# ";
1193 ts << " " << deviceParam.key() << "='" << param.value << "'";
1194 }
1195 ts << endl;
1196 // MIDI port parameters...
1197 int iPort = 0;
1198 QListIterator<DevicePort *> iter(device.ports());
1199 while (iter.hasNext()) {
1200 DevicePort *pPort = iter.next();
1201 DeviceParamMap::ConstIterator portParam;
1202 for (portParam = pPort->params().begin();
1203 portParam != pPort->params().end();
1204 ++portParam) {
1205 const DeviceParam& param = portParam.value();
1206 if (param.fix || param.value.isEmpty()) ts << "# ";
1207 ts << "SET MIDI_INPUT_PORT_PARAMETER " << iDevice
1208 << " " << iPort << " " << portParam.key()
1209 << "='" << param.value << "'" << endl;
1210 }
1211 iPort++;
1212 }
1213 // MIDI device index/id mapping.
1214 midiDeviceMap.insert(device.deviceID(), iDevice++);
1215 // Try to keep it snappy :)
1216 QApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
1217 }
1218 ts << endl;
1219
1220 #ifdef CONFIG_MIDI_INSTRUMENT
1221 // MIDI instrument mapping...
1222 QMap<int, int> midiInstrumentMap;
1223 int *piMaps = ::lscp_list_midi_instrument_maps(m_pClient);
1224 for (int iMap = 0; piMaps && piMaps[iMap] >= 0; iMap++) {
1225 const int iMidiMap = piMaps[iMap];
1226 const char *pszMapName
1227 = ::lscp_get_midi_instrument_map_name(m_pClient, iMidiMap);
1228 ts << "# " << tr("MIDI instrument map") << " " << iMap;
1229 if (pszMapName)
1230 ts << " - " << pszMapName;
1231 ts << endl;
1232 ts << "ADD MIDI_INSTRUMENT_MAP";
1233 if (pszMapName)
1234 ts << " '" << pszMapName << "'";
1235 ts << endl;
1236 // MIDI instrument mapping...
1237 lscp_midi_instrument_t *pInstrs
1238 = ::lscp_list_midi_instruments(m_pClient, iMidiMap);
1239 for (int iInstr = 0; pInstrs && pInstrs[iInstr].map >= 0; iInstr++) {
1240 lscp_midi_instrument_info_t *pInstrInfo
1241 = ::lscp_get_midi_instrument_info(m_pClient, &pInstrs[iInstr]);
1242 if (pInstrInfo) {
1243 ts << "MAP MIDI_INSTRUMENT "
1244 << iMap << " "
1245 << pInstrs[iInstr].bank << " "
1246 << pInstrs[iInstr].prog << " "
1247 << pInstrInfo->engine_name << " '"
1248 << pInstrInfo->instrument_file << "' "
1249 << pInstrInfo->instrument_nr << " "
1250 << pInstrInfo->volume << " ";
1251 switch (pInstrInfo->load_mode) {
1252 case LSCP_LOAD_PERSISTENT:
1253 ts << "PERSISTENT";
1254 break;
1255 case LSCP_LOAD_ON_DEMAND_HOLD:
1256 ts << "ON_DEMAND_HOLD";
1257 break;
1258 case LSCP_LOAD_ON_DEMAND:
1259 case LSCP_LOAD_DEFAULT:
1260 default:
1261 ts << "ON_DEMAND";
1262 break;
1263 }
1264 if (pInstrInfo->name)
1265 ts << " '" << pInstrInfo->name << "'";
1266 ts << endl;
1267 } // Check for errors...
1268 else if (::lscp_client_get_errno(m_pClient)) {
1269 appendMessagesClient("lscp_get_midi_instrument_info");
1270 iErrors++;
1271 }
1272 // Try to keep it snappy :)
1273 QApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
1274 }
1275 ts << endl;
1276 // Check for errors...
1277 if (pInstrs == nullptr && ::lscp_client_get_errno(m_pClient)) {
1278 appendMessagesClient("lscp_list_midi_instruments");
1279 iErrors++;
1280 }
1281 // MIDI strument index/id mapping.
1282 midiInstrumentMap.insert(iMidiMap, iMap);
1283 }
1284 // Check for errors...
1285 if (piMaps == nullptr && ::lscp_client_get_errno(m_pClient)) {
1286 appendMessagesClient("lscp_list_midi_instrument_maps");
1287 iErrors++;
1288 }
1289 #endif // CONFIG_MIDI_INSTRUMENT
1290
1291 // Sampler channel mapping...
1292 int iChannelID = 0;
1293 const QList<QMdiSubWindow *>& wlist
1294 = m_pWorkspace->subWindowList();
1295 foreach (QMdiSubWindow *pMdiSubWindow, wlist) {
1296 ChannelStrip *pChannelStrip
1297 = static_cast<ChannelStrip *> (pMdiSubWindow->widget());
1298 if (pChannelStrip) {
1299 Channel *pChannel = pChannelStrip->channel();
1300 if (pChannel) {
1301 // Avoid "artifial" plug-in devices...
1302 const int iAudioDevice = pChannel->audioDevice();
1303 if (!audioDeviceMap.contains(iAudioDevice))
1304 continue;
1305 const int iMidiDevice = pChannel->midiDevice();
1306 if (!midiDeviceMap.contains(iMidiDevice))
1307 continue;
1308 // Go for regular, canonical devices...
1309 ts << "# " << tr("Channel") << " " << iChannelID << endl;
1310 ts << "ADD CHANNEL" << endl;
1311 if (audioDeviceMap.isEmpty()) {
1312 ts << "SET CHANNEL AUDIO_OUTPUT_TYPE " << iChannelID
1313 << " " << pChannel->audioDriver() << endl;
1314 } else {
1315 ts << "SET CHANNEL AUDIO_OUTPUT_DEVICE " << iChannelID
1316 << " " << audioDeviceMap.value(iAudioDevice) << endl;
1317 }
1318 if (midiDeviceMap.isEmpty()) {
1319 ts << "SET CHANNEL MIDI_INPUT_TYPE " << iChannelID
1320 << " " << pChannel->midiDriver() << endl;
1321 } else {
1322 ts << "SET CHANNEL MIDI_INPUT_DEVICE " << iChannelID
1323 << " " << midiDeviceMap.value(iMidiDevice) << endl;
1324 }
1325 ts << "SET CHANNEL MIDI_INPUT_PORT " << iChannelID
1326 << " " << pChannel->midiPort() << endl;
1327 ts << "SET CHANNEL MIDI_INPUT_CHANNEL " << iChannelID << " ";
1328 if (pChannel->midiChannel() == LSCP_MIDI_CHANNEL_ALL)
1329 ts << "ALL";
1330 else
1331 ts << pChannel->midiChannel();
1332 ts << endl;
1333 ts << "LOAD ENGINE " << pChannel->engineName()
1334 << " " << iChannelID << endl;
1335 if (pChannel->instrumentStatus() < 100) ts << "# ";
1336 ts << "LOAD INSTRUMENT NON_MODAL '"
1337 << pChannel->instrumentFile() << "' "
1338 << pChannel->instrumentNr() << " " << iChannelID << endl;
1339 ChannelRoutingMap::ConstIterator audioRoute;
1340 for (audioRoute = pChannel->audioRouting().begin();
1341 audioRoute != pChannel->audioRouting().end();
1342 ++audioRoute) {
1343 ts << "SET CHANNEL AUDIO_OUTPUT_CHANNEL " << iChannelID
1344 << " " << audioRoute.key()
1345 << " " << audioRoute.value() << endl;
1346 }
1347 ts << "SET CHANNEL VOLUME " << iChannelID
1348 << " " << pChannel->volume() << endl;
1349 if (pChannel->channelMute())
1350 ts << "SET CHANNEL MUTE " << iChannelID << " 1" << endl;
1351 if (pChannel->channelSolo())
1352 ts << "SET CHANNEL SOLO " << iChannelID << " 1" << endl;
1353 #ifdef CONFIG_MIDI_INSTRUMENT
1354 const int iMidiMap = pChannel->midiMap();
1355 if (midiInstrumentMap.contains(iMidiMap)) {
1356 ts << "SET CHANNEL MIDI_INSTRUMENT_MAP " << iChannelID
1357 << " " << midiInstrumentMap.value(iMidiMap) << endl;
1358 }
1359 #endif
1360 #ifdef CONFIG_FXSEND
1361 int *piFxSends = ::lscp_list_fxsends(m_pClient, iChannelID);
1362 for (int iFxSend = 0;
1363 piFxSends && piFxSends[iFxSend] >= 0;
1364 iFxSend++) {
1365 lscp_fxsend_info_t *pFxSendInfo = ::lscp_get_fxsend_info(
1366 m_pClient, iChannelID, piFxSends[iFxSend]);
1367 if (pFxSendInfo) {
1368 ts << "CREATE FX_SEND " << iChannelID
1369 << " " << pFxSendInfo->midi_controller;
1370 if (pFxSendInfo->name)
1371 ts << " '" << pFxSendInfo->name << "'";
1372 ts << endl;
1373 int *piRouting = pFxSendInfo->audio_routing;
1374 for (int iAudioSrc = 0;
1375 piRouting && piRouting[iAudioSrc] >= 0;
1376 iAudioSrc++) {
1377 ts << "SET FX_SEND AUDIO_OUTPUT_CHANNEL "
1378 << iChannelID
1379 << " " << iFxSend
1380 << " " << iAudioSrc
1381 << " " << piRouting[iAudioSrc] << endl;
1382 }
1383 #ifdef CONFIG_FXSEND_LEVEL
1384 ts << "SET FX_SEND LEVEL " << iChannelID
1385 << " " << iFxSend
1386 << " " << pFxSendInfo->level << endl;
1387 #endif
1388 } // Check for errors...
1389 else if (::lscp_client_get_errno(m_pClient)) {
1390 appendMessagesClient("lscp_get_fxsend_info");
1391 iErrors++;
1392 }
1393 }
1394 #endif
1395 ts << endl;
1396 // Go for next channel...
1397 ++iChannelID;
1398 }
1399 }
1400 // Try to keep it snappy :)
1401 QApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
1402 }
1403
1404 #ifdef CONFIG_VOLUME
1405 ts << "# " << tr("Global volume level") << endl;
1406 ts << "SET VOLUME " << ::lscp_get_volume(m_pClient) << endl;
1407 ts << endl;
1408 #endif
1409
1410 // Ok. we've wrote it.
1411 file.close();
1412
1413 // We're fornerly done.
1414 QApplication::restoreOverrideCursor();
1415
1416 // Have we any errors?
1417 if (iErrors > 0) {
1418 appendMessagesError(
1419 tr("Some settings could not be saved\n"
1420 "to \"%1\" session file.\n\nSorry.")
1421 .arg(sFilename));
1422 }
1423
1424 // Save as default session directory.
1425 if (m_pOptions)
1426 m_pOptions->sSessionDir = QFileInfo(sFilename).dir().absolutePath();
1427 // We're not dirty anymore.
1428 m_iDirtyCount = 0;
1429 // Stabilize form...
1430 m_sFilename = sFilename;
1431 updateRecentFiles(sFilename);
1432 appendMessages(tr("Save session: \"%1\".").arg(sessionName(m_sFilename)));
1433 stabilizeForm();
1434 return true;
1435 }
1436
1437
1438 // Session change receiver slot.
1439 void MainForm::sessionDirty (void)
1440 {
1441 // Just mark the dirty form.
1442 m_iDirtyCount++;
1443 // and update the form status...
1444 stabilizeForm();
1445 }
1446
1447
1448 //-------------------------------------------------------------------------
1449 // QSampler::MainForm -- File Action slots.
1450
1451 // Create a new sampler session.
1452 void MainForm::fileNew (void)
1453 {
1454 // Of course we'll start clean new.
1455 newSession();
1456 }
1457
1458
1459 // Open an existing sampler session.
1460 void MainForm::fileOpen (void)
1461 {
1462 // Open it right away.
1463 openSession();
1464 }
1465
1466
1467 // Open a recent file session.
1468 void MainForm::fileOpenRecent (void)
1469 {
1470 // Retrive filename index from action data...
1471 QAction *pAction = qobject_cast<QAction *> (sender());
1472 if (pAction && m_pOptions) {
1473 const int iIndex = pAction->data().toInt();
1474 if (iIndex >= 0 && iIndex < m_pOptions->recentFiles.count()) {
1475 QString sFilename = m_pOptions->recentFiles[iIndex];
1476 // Check if we can safely close the current session...
1477 if (!sFilename.isEmpty() && closeSession(true))
1478 loadSessionFile(sFilename);
1479 }
1480 }
1481 }
1482
1483
1484 // Save current sampler session.
1485 void MainForm::fileSave (void)
1486 {
1487 // Save it right away.
1488 saveSession(false);
1489 }
1490
1491
1492 // Save current sampler session with another name.
1493 void MainForm::fileSaveAs (void)
1494 {
1495 // Save it right away, maybe with another name.
1496 saveSession(true);
1497 }
1498
1499
1500 // Reset the sampler instance.
1501 void MainForm::fileReset (void)
1502 {
1503 if (m_pClient == nullptr)
1504 return;
1505
1506 // Ask user whether he/she want's an internal sampler reset...
1507 if (m_pOptions && m_pOptions->bConfirmReset) {
1508 const QString& sTitle = tr("Warning");
1509 const QString& sText = tr(
1510 "Resetting the sampler instance will close\n"
1511 "all device and channel configurations.\n\n"
1512 "Please note that this operation may cause\n"
1513 "temporary MIDI and Audio disruption.\n\n"
1514 "Do you want to reset the sampler engine now?");
1515 #if 0
1516 if (QMessageBox::warning(this, sTitle, sText,
1517 QMessageBox::Ok | QMessageBox::Cancel) == QMessageBox::Cancel)
1518 return;
1519 #else
1520 QMessageBox mbox(this);
1521 mbox.setIcon(QMessageBox::Warning);
1522 mbox.setWindowTitle(sTitle);
1523 mbox.setText(sText);
1524 mbox.setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel);
1525 QCheckBox cbox(tr("Don't ask this again"));
1526 cbox.setChecked(false);
1527 cbox.blockSignals(true);
1528 mbox.addButton(&cbox, QMessageBox::ActionRole);
1529 if (mbox.exec() == QMessageBox::Cancel)
1530 return;
1531 if (cbox.isChecked())
1532 m_pOptions->bConfirmReset = false;
1533 #endif
1534 }
1535
1536 // Trye closing the current session, first...
1537 if (!closeSession(true))
1538 return;
1539
1540 // Just do the reset, after closing down current session...
1541 // Do the actual sampler reset...
1542 if (::lscp_reset_sampler(m_pClient) != LSCP_OK) {
1543 appendMessagesClient("lscp_reset_sampler");
1544 appendMessagesError(tr("Could not reset sampler instance.\n\nSorry."));
1545 return;
1546 }
1547
1548 // Log this.
1549 appendMessages(tr("Sampler reset."));
1550
1551 // Make it a new session...
1552 newSession();
1553 }
1554
1555
1556 // Restart the client/server instance.
1557 void MainForm::fileRestart (void)
1558 {
1559 if (m_pOptions == nullptr)
1560 return;
1561
1562 bool bRestart = true;
1563
1564 // Ask user whether he/she want's a complete restart...
1565 // (if we're currently up and running)
1566 if (m_pOptions && m_pOptions->bConfirmRestart) {
1567 const QString& sTitle = tr("Warning");
1568 const QString& sText = tr(
1569 "New settings will be effective after\n"
1570 "restarting the client/server connection.\n\n"
1571 "Please note that this operation may cause\n"
1572 "temporary MIDI and Audio disruption.\n\n"
1573 "Do you want to restart the connection now?");
1574 #if 0
1575 if (QMessageBox::warning(this, sTitle, sText,
1576 QMessageBox::Ok | QMessageBox::Cancel) == QMessageBox::Cancel)
1577 bRestart = false;
1578 #else
1579 QMessageBox mbox(this);
1580 mbox.setIcon(QMessageBox::Warning);
1581 mbox.setWindowTitle(sTitle);
1582 mbox.setText(sText);
1583 mbox.setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel);
1584 QCheckBox cbox(tr("Don't ask this again"));
1585 cbox.setChecked(false);
1586 cbox.blockSignals(true);
1587 mbox.addButton(&cbox, QMessageBox::ActionRole);
1588 if (mbox.exec() == QMessageBox::Cancel)
1589 bRestart = false;
1590 else
1591 if (cbox.isChecked())
1592 m_pOptions->bConfirmRestart = false;
1593 #endif
1594 }
1595
1596 // Are we still for it?
1597 if (bRestart && closeSession(true)) {
1598 // Stop server, it will force the client too.
1599 stopServer();
1600 // Reschedule a restart...
1601 startSchedule(m_pOptions->iStartDelay);
1602 }
1603 }
1604
1605
1606 // Exit application program.
1607 void MainForm::fileExit (void)
1608 {
1609 // Go for close the whole thing.
1610 close();
1611 }
1612
1613
1614 //-------------------------------------------------------------------------
1615 // QSampler::MainForm -- Edit Action slots.
1616
1617 // Add a new sampler channel.
1618 void MainForm::editAddChannel (void)
1619 {
1620 ++m_iDirtySetup;
1621 addChannelStrip();
1622 --m_iDirtySetup;
1623 }
1624
1625 void MainForm::addChannelStrip (void)
1626 {
1627 if (m_pClient == nullptr)
1628 return;
1629
1630 // Just create the channel instance...
1631 Channel *pChannel = new Channel();
1632 if (pChannel == nullptr)
1633 return;
1634
1635 // Before we show it up, may be we'll
1636 // better ask for some initial values?
1637 if (!pChannel->channelSetup(this)) {
1638 delete pChannel;
1639 return;
1640 }
1641
1642 // And give it to the strip...
1643 // (will own the channel instance, if successful).
1644 if (!createChannelStrip(pChannel)) {
1645 delete pChannel;
1646 return;
1647 }
1648
1649 // Do we auto-arrange?
1650 channelsArrangeAuto();
1651
1652 // Make that an overall update.
1653 m_iDirtyCount++;
1654 stabilizeForm();
1655 }
1656
1657
1658 // Remove current sampler channel.
1659 void MainForm::editRemoveChannel (void)
1660 {
1661 ++m_iDirtySetup;
1662 removeChannelStrip();
1663 --m_iDirtySetup;
1664 }
1665
1666 void MainForm::removeChannelStrip (void)
1667 {
1668 if (m_pClient == nullptr)
1669 return;
1670
1671 ChannelStrip *pChannelStrip = activeChannelStrip();
1672 if (pChannelStrip == nullptr)
1673 return;
1674
1675 Channel *pChannel = pChannelStrip->channel();
1676 if (pChannel == nullptr)
1677 return;
1678
1679 // Prompt user if he/she's sure about this...
1680 if (m_pOptions && m_pOptions->bConfirmRemove) {
1681 const QString& sTitle = tr("Warning");
1682 const QString& sText = tr(
1683 "About to remove channel:\n\n"
1684 "%1\n\n"
1685 "Are you sure?")
1686 .arg(pChannelStrip->windowTitle());
1687 #if 0
1688 if (QMessageBox::warning(this, sTitle, sText,
1689 QMessageBox::Ok | QMessageBox::Cancel) == QMessageBox::Cancel)
1690 return;
1691 #else
1692 QMessageBox mbox(this);
1693 mbox.setIcon(QMessageBox::Warning);
1694 mbox.setWindowTitle(sTitle);
1695 mbox.setText(sText);
1696 mbox.setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel);
1697 QCheckBox cbox(tr("Don't ask this again"));
1698 cbox.setChecked(false);
1699 cbox.blockSignals(true);
1700 mbox.addButton(&cbox, QMessageBox::ActionRole);
1701 if (mbox.exec() == QMessageBox::Cancel)
1702 return;
1703 if (cbox.isChecked())
1704 m_pOptions->bConfirmRemove = false;
1705 #endif
1706 }
1707
1708 // Remove the existing sampler channel.
1709 if (!pChannel->removeChannel())
1710 return;
1711
1712 // Just delete the channel strip.
1713 destroyChannelStrip(pChannelStrip);
1714
1715 // We'll be dirty, for sure...
1716 m_iDirtyCount++;
1717 stabilizeForm();
1718 }
1719
1720
1721 // Setup current sampler channel.
1722 void MainForm::editSetupChannel (void)
1723 {
1724 if (m_pClient == nullptr)
1725 return;
1726
1727 ChannelStrip *pChannelStrip = activeChannelStrip();
1728 if (pChannelStrip == nullptr)
1729 return;
1730
1731 // Just invoque the channel strip procedure.
1732 pChannelStrip->channelSetup();
1733 }
1734
1735
1736 // Edit current sampler channel.
1737 void MainForm::editEditChannel (void)
1738 {
1739 if (m_pClient == nullptr)
1740 return;
1741
1742 ChannelStrip *pChannelStrip = activeChannelStrip();
1743 if (pChannelStrip == nullptr)
1744 return;
1745
1746 // Just invoque the channel strip procedure.
1747 pChannelStrip->channelEdit();
1748 }
1749
1750
1751 // Reset current sampler channel.
1752 void MainForm::editResetChannel (void)
1753 {
1754 if (m_pClient == nullptr)
1755 return;
1756
1757 ChannelStrip *pChannelStrip = activeChannelStrip();
1758 if (pChannelStrip == nullptr)
1759 return;
1760
1761 // Just invoque the channel strip procedure.
1762 pChannelStrip->channelReset();
1763 }
1764
1765
1766 // Reset all sampler channels.
1767 void MainForm::editResetAllChannels (void)
1768 {
1769 if (m_pClient == nullptr)
1770 return;
1771
1772 // Invoque the channel strip procedure,
1773 // for all channels out there...
1774 m_pWorkspace->setUpdatesEnabled(false);
1775 const QList<QMdiSubWindow *>& wlist
1776 = m_pWorkspace->subWindowList();
1777 foreach (QMdiSubWindow *pMdiSubWindow, wlist) {
1778 ChannelStrip *pChannelStrip
1779 = static_cast<ChannelStrip *> (pMdiSubWindow->widget());
1780 if (pChannelStrip)
1781 pChannelStrip->channelReset();
1782 }
1783 m_pWorkspace->setUpdatesEnabled(true);
1784 }
1785
1786
1787 //-------------------------------------------------------------------------
1788 // QSampler::MainForm -- View Action slots.
1789
1790 // Show/hide the main program window menubar.
1791 void MainForm::viewMenubar ( bool bOn )
1792 {
1793 if (bOn)
1794 m_ui.MenuBar->show();
1795 else
1796 m_ui.MenuBar->hide();
1797 }
1798
1799
1800 // Show/hide the main program window toolbar.
1801 void MainForm::viewToolbar ( bool bOn )
1802 {
1803 if (bOn) {
1804 m_ui.fileToolbar->show();
1805 m_ui.editToolbar->show();
1806 m_ui.channelsToolbar->show();
1807 } else {
1808 m_ui.fileToolbar->hide();
1809 m_ui.editToolbar->hide();
1810 m_ui.channelsToolbar->hide();
1811 }
1812 }
1813
1814
1815 // Show/hide the main program window statusbar.
1816 void MainForm::viewStatusbar ( bool bOn )
1817 {
1818 if (bOn)
1819 statusBar()->show();
1820 else
1821 statusBar()->hide();
1822 }
1823
1824
1825 // Show/hide the messages window logger.
1826 void MainForm::viewMessages ( bool bOn )
1827 {
1828 if (bOn)
1829 m_pMessages->show();
1830 else
1831 m_pMessages->hide();
1832 }
1833
1834
1835 // Show/hide the MIDI instrument list-view form.
1836 void MainForm::viewInstruments (void)
1837 {
1838 if (m_pOptions == nullptr)
1839 return;
1840
1841 if (m_pInstrumentListForm) {
1842 m_pOptions->saveWidgetGeometry(m_pInstrumentListForm);
1843 if (m_pInstrumentListForm->isVisible()) {
1844 m_pInstrumentListForm->hide();
1845 } else {
1846 m_pInstrumentListForm->show();
1847 m_pInstrumentListForm->raise();
1848 m_pInstrumentListForm->activateWindow();
1849 }
1850 }
1851 }
1852
1853
1854 // Show/hide the device configurator form.
1855 void MainForm::viewDevices (void)
1856 {
1857 if (m_pOptions == nullptr)
1858 return;
1859
1860 if (m_pDeviceForm) {
1861 m_pOptions->saveWidgetGeometry(m_pDeviceForm);
1862 if (m_pDeviceForm->isVisible()) {
1863 m_pDeviceForm->hide();
1864 } else {
1865 m_pDeviceForm->show();
1866 m_pDeviceForm->raise();
1867 m_pDeviceForm->activateWindow();
1868 }
1869 }
1870 }
1871
1872
1873 // Show options dialog.
1874 void MainForm::viewOptions (void)
1875 {
1876 if (m_pOptions == nullptr)
1877 return;
1878
1879 OptionsForm* pOptionsForm = new OptionsForm(this);
1880 if (pOptionsForm) {
1881 // Check out some initial nullities(tm)...
1882 ChannelStrip *pChannelStrip = activeChannelStrip();
1883 if (m_pOptions->sDisplayFont.isEmpty() && pChannelStrip)
1884 m_pOptions->sDisplayFont = pChannelStrip->displayFont().toString();
1885 if (m_pOptions->sMessagesFont.isEmpty() && m_pMessages)
1886 m_pOptions->sMessagesFont = m_pMessages->messagesFont().toString();
1887 // To track down deferred or immediate changes.
1888 const QString sOldServerHost = m_pOptions->sServerHost;
1889 const int iOldServerPort = m_pOptions->iServerPort;
1890 const int iOldServerTimeout = m_pOptions->iServerTimeout;
1891 const bool bOldServerStart = m_pOptions->bServerStart;
1892 const QString sOldServerCmdLine = m_pOptions->sServerCmdLine;
1893 const bool bOldMessagesLog = m_pOptions->bMessagesLog;
1894 const QString sOldMessagesLogPath = m_pOptions->sMessagesLogPath;
1895 const QString sOldDisplayFont = m_pOptions->sDisplayFont;
1896 const bool bOldDisplayEffect = m_pOptions->bDisplayEffect;
1897 const int iOldMaxVolume = m_pOptions->iMaxVolume;
1898 const QString sOldMessagesFont = m_pOptions->sMessagesFont;
1899 const bool bOldKeepOnTop = m_pOptions->bKeepOnTop;
1900 const bool bOldStdoutCapture = m_pOptions->bStdoutCapture;
1901 const int bOldMessagesLimit = m_pOptions->bMessagesLimit;
1902 const int iOldMessagesLimitLines = m_pOptions->iMessagesLimitLines;
1903 const bool bOldCompletePath = m_pOptions->bCompletePath;
1904 const bool bOldInstrumentNames = m_pOptions->bInstrumentNames;
1905 const int iOldMaxRecentFiles = m_pOptions->iMaxRecentFiles;
1906 const int iOldBaseFontSize = m_pOptions->iBaseFontSize;
1907 const QString sOldCustomStyleTheme = m_pOptions->sCustomStyleTheme;
1908 const QString sOldCustomColorTheme = m_pOptions->sCustomColorTheme;
1909 // Load the current setup settings.
1910 pOptionsForm->setup(m_pOptions);
1911 // Show the setup dialog...
1912 if (pOptionsForm->exec()) {
1913 // Warn if something will be only effective on next run.
1914 int iNeedRestart = 0;
1915 if (( bOldStdoutCapture && !m_pOptions->bStdoutCapture) ||
1916 (!bOldStdoutCapture && m_pOptions->bStdoutCapture)) {
1917 updateMessagesCapture();
1918 ++iNeedRestart;
1919 }
1920 if (( bOldKeepOnTop && !m_pOptions->bKeepOnTop) ||
1921 (!bOldKeepOnTop && m_pOptions->bKeepOnTop) ||
1922 (iOldBaseFontSize != m_pOptions->iBaseFontSize)) {
1923 ++iNeedRestart;
1924 }
1925 // Check whether restart is needed or whether
1926 // custom options maybe set up immediately...
1927 if (m_pOptions->sCustomStyleTheme != sOldCustomStyleTheme) {
1928 if (m_pOptions->sCustomStyleTheme.isEmpty()) {
1929 ++iNeedRestart;
1930 } else {
1931 QApplication::setStyle(
1932 QStyleFactory::create(m_pOptions->sCustomStyleTheme));
1933 }
1934 }
1935 if (m_pOptions->sCustomColorTheme != sOldCustomColorTheme) {
1936 if (m_pOptions->sCustomColorTheme.isEmpty()) {
1937 ++iNeedRestart;
1938 } else {
1939 QPalette pal;
1940 if (PaletteForm::namedPalette(
1941 &m_pOptions->settings(), m_pOptions->sCustomColorTheme, pal))
1942 QApplication::setPalette(pal);
1943 }
1944 }
1945 // Check wheather something immediate has changed.
1946 if (( bOldMessagesLog && !m_pOptions->bMessagesLog) ||
1947 (!bOldMessagesLog && m_pOptions->bMessagesLog) ||
1948 (sOldMessagesLogPath != m_pOptions->sMessagesLogPath))
1949 m_pMessages->setLogging(
1950 m_pOptions->bMessagesLog, m_pOptions->sMessagesLogPath);
1951 if (( bOldCompletePath && !m_pOptions->bCompletePath) ||
1952 (!bOldCompletePath && m_pOptions->bCompletePath) ||
1953 (iOldMaxRecentFiles != m_pOptions->iMaxRecentFiles))
1954 updateRecentFilesMenu();
1955 if (( bOldInstrumentNames && !m_pOptions->bInstrumentNames) ||
1956 (!bOldInstrumentNames && m_pOptions->bInstrumentNames))
1957 updateInstrumentNames();
1958 if (( bOldDisplayEffect && !m_pOptions->bDisplayEffect) ||
1959 (!bOldDisplayEffect && m_pOptions->bDisplayEffect))
1960 updateDisplayEffect();
1961 if (sOldDisplayFont != m_pOptions->sDisplayFont)
1962 updateDisplayFont();
1963 if (iOldMaxVolume != m_pOptions->iMaxVolume)
1964 updateMaxVolume();
1965 if (sOldMessagesFont != m_pOptions->sMessagesFont)
1966 updateMessagesFont();
1967 if (( bOldMessagesLimit && !m_pOptions->bMessagesLimit) ||
1968 (!bOldMessagesLimit && m_pOptions->bMessagesLimit) ||
1969 (iOldMessagesLimitLines != m_pOptions->iMessagesLimitLines))
1970 updateMessagesLimit();
1971 // Show restart needed message...
1972 if (iNeedRestart > 0) {
1973 QMessageBox::information(this,
1974 tr("Information"),
1975 tr("Some settings may be only effective\n"
1976 "next time you start this program."));
1977 }
1978 // And now the main thing, whether we'll do client/server recycling?
1979 if ((sOldServerHost != m_pOptions->sServerHost) ||
1980 (iOldServerPort != m_pOptions->iServerPort) ||
1981 (iOldServerTimeout != m_pOptions->iServerTimeout) ||
1982 ( bOldServerStart && !m_pOptions->bServerStart) ||
1983 (!bOldServerStart && m_pOptions->bServerStart) ||
1984 (sOldServerCmdLine != m_pOptions->sServerCmdLine
1985 && m_pOptions->bServerStart))
1986 fileRestart();
1987 }
1988 // Done.
1989 delete pOptionsForm;
1990 }
1991
1992 // This makes it.
1993 stabilizeForm();
1994 }
1995
1996
1997 //-------------------------------------------------------------------------
1998 // QSampler::MainForm -- Channels action slots.
1999
2000 // Arrange channel strips.
2001 void MainForm::channelsArrange (void)
2002 {
2003 // Full width vertical tiling
2004 const QList<QMdiSubWindow *>& wlist
2005 = m_pWorkspace->subWindowList();
2006 if (wlist.isEmpty())
2007 return;
2008
2009 m_pWorkspace->setUpdatesEnabled(false);
2010 int y = 0;
2011 foreach (QMdiSubWindow *pMdiSubWindow, wlist) {
2012 pMdiSubWindow->adjustSize();
2013 const QRect& frameRect
2014 = pMdiSubWindow->frameGeometry();
2015 int w = m_pWorkspace->width();
2016 if (w < frameRect.width())
2017 w = frameRect.width();
2018 const int h = frameRect.height();
2019 pMdiSubWindow->setGeometry(0, y, w, h);
2020 y += h;
2021 }
2022 m_pWorkspace->setUpdatesEnabled(true);
2023
2024 stabilizeForm();
2025 }
2026
2027
2028 // Auto-arrange channel strips.
2029 void MainForm::channelsAutoArrange ( bool bOn )
2030 {
2031 if (m_pOptions == nullptr)
2032 return;
2033
2034 // Toggle the auto-arrange flag.
2035 m_pOptions->bAutoArrange = bOn;
2036
2037 // If on, update whole workspace...
2038 channelsArrangeAuto();
2039 }
2040
2041
2042 void MainForm::channelsArrangeAuto (void)
2043 {
2044 if (m_pOptions && m_pOptions->bAutoArrange)
2045 channelsArrange();
2046 }
2047
2048
2049 //-------------------------------------------------------------------------
2050 // QSampler::MainForm -- Help Action slots.
2051
2052 // Show information about the Qt toolkit.
2053 void MainForm::helpAboutQt (void)
2054 {
2055 QMessageBox::aboutQt(this);
2056 }
2057
2058
2059 // Show information about application program.
2060 void MainForm::helpAbout (void)
2061 {
2062 QStringList list;
2063 #ifdef CONFIG_DEBUG
2064 list << tr("Debugging option enabled.");
2065 #endif
2066 #ifndef CONFIG_LIBGIG
2067 list << tr("GIG (libgig) file support disabled.");
2068 #endif
2069 #ifndef CONFIG_INSTRUMENT_NAME
2070 list << tr("LSCP (liblscp) instrument_name support disabled.");
2071 #endif
2072 #ifndef CONFIG_MUTE_SOLO
2073 list << tr("Sampler channel Mute/Solo support disabled.");
2074 #endif
2075 #ifndef CONFIG_AUDIO_ROUTING
2076 list << tr("LSCP (liblscp) audio_routing support disabled.");
2077 #endif
2078 #ifndef CONFIG_FXSEND
2079 list << tr("Sampler channel Effect Sends support disabled.");
2080 #endif
2081 #ifndef CONFIG_VOLUME
2082 list << tr("Global volume support disabled.");
2083 #endif
2084 #ifndef CONFIG_MIDI_INSTRUMENT
2085 list << tr("MIDI instrument mapping support disabled.");
2086 #endif
2087 #ifndef CONFIG_EDIT_INSTRUMENT
2088 list << tr("Instrument editing support disabled.");
2089 #endif
2090 #ifndef CONFIG_EVENT_CHANNEL_MIDI
2091 list << tr("Channel MIDI event support disabled.");
2092 #endif
2093 #ifndef CONFIG_EVENT_DEVICE_MIDI
2094 list << tr("Device MIDI event support disabled.");
2095 #endif
2096 #ifndef CONFIG_MAX_VOICES
2097 list << tr("Runtime max. voices / disk streams support disabled.");
2098 #endif
2099
2100 // Stuff the about box text...
2101 QString sText = "<h1>" QSAMPLER_TITLE "</h1>\n";
2102 sText += "<p>" + tr(QSAMPLER_SUBTITLE) + "<br />\n";
2103 sText += "<br />\n";
2104 sText += tr("Version") + ": <b>" PROJECT_VERSION "</b><br />\n";
2105 // sText += "<small>" + tr("Build") + ": " CONFIG_BUILD_DATE "</small><br />\n";
2106 if (!list.isEmpty()) {
2107 sText += "<small><font color=\"red\">";
2108 sText += list.join("<br />\n");
2109 sText += "</font></small>";
2110 }
2111 sText += "<br />\n";
2112 sText += tr("Using: Qt %1").arg(qVersion());
2113 #if defined(QT_STATIC)
2114 sText += "-static";
2115 #endif
2116 sText += ", ";
2117 sText += ::lscp_client_package();
2118 sText += " ";
2119 sText += ::lscp_client_version();
2120 #ifdef CONFIG_LIBGIG
2121 sText += ", ";
2122 sText += gig::libraryName().c_str();
2123 sText += " ";
2124 sText += gig::libraryVersion().c_str();
2125 #endif
2126 sText += "<br />\n";
2127 sText += "<br />\n";
2128 sText += tr("Website") + ": <a href=\"" QSAMPLER_WEBSITE "\">" QSAMPLER_WEBSITE "</a><br />\n";
2129 sText += "<br />\n";
2130 sText += "<small>";
2131 sText += QSAMPLER_COPYRIGHT "<br />\n";
2132 sText += QSAMPLER_COPYRIGHT2 "<br />\n";
2133 sText += "<br />\n";
2134 sText += tr("This program is free software; you can redistribute it and/or modify it") + "<br />\n";
2135 sText += tr("under the terms of the GNU General Public License version 2 or later.");
2136 sText += "</small>";
2137 sText += "<br />\n";
2138 sText += "</p>\n";
2139
2140 QMessageBox::about(this, tr("About"), sText);
2141 }
2142
2143
2144 //-------------------------------------------------------------------------
2145 // QSampler::MainForm -- Main window stabilization.
2146
2147 void MainForm::stabilizeForm (void)
2148 {
2149 // Update the main application caption...
2150 QString sSessionName = sessionName(m_sFilename);
2151 if (m_iDirtyCount > 0)
2152 sSessionName += " *";
2153 setWindowTitle(sSessionName);
2154
2155 // Update the main menu state...
2156 ChannelStrip *pChannelStrip = activeChannelStrip();
2157 const QList<QMdiSubWindow *>& wlist = m_pWorkspace->subWindowList();
2158 const bool bHasClient = (m_pOptions != nullptr && m_pClient != nullptr);
2159 const bool bHasChannel = (bHasClient && pChannelStrip != nullptr);
2160 const bool bHasChannels = (bHasClient && wlist.count() > 0);
2161 m_ui.fileNewAction->setEnabled(bHasClient);
2162 m_ui.fileOpenAction->setEnabled(bHasClient);
2163 m_ui.fileSaveAction->setEnabled(bHasClient && m_iDirtyCount > 0);
2164 m_ui.fileSaveAsAction->setEnabled(bHasClient);
2165 m_ui.fileResetAction->setEnabled(bHasClient);
2166 m_ui.fileRestartAction->setEnabled(bHasClient || m_pServer == nullptr);
2167 m_ui.editAddChannelAction->setEnabled(bHasClient);
2168 m_ui.editRemoveChannelAction->setEnabled(bHasChannel);
2169 m_ui.editSetupChannelAction->setEnabled(bHasChannel);
2170 #ifdef CONFIG_EDIT_INSTRUMENT
2171 m_ui.editEditChannelAction->setEnabled(bHasChannel);
2172 #else
2173 m_ui.editEditChannelAction->setEnabled(false);
2174 #endif
2175 m_ui.editResetChannelAction->setEnabled(bHasChannel);
2176 m_ui.editResetAllChannelsAction->setEnabled(bHasChannels);
2177 m_ui.viewMessagesAction->setChecked(m_pMessages && m_pMessages->isVisible());
2178 #ifdef CONFIG_MIDI_INSTRUMENT
2179 m_ui.viewInstrumentsAction->setChecked(m_pInstrumentListForm
2180 && m_pInstrumentListForm->isVisible());
2181 m_ui.viewInstrumentsAction->setEnabled(bHasClient);
2182 #else
2183 m_ui.viewInstrumentsAction->setEnabled(false);
2184 #endif
2185 m_ui.viewDevicesAction->setChecked(m_pDeviceForm
2186 && m_pDeviceForm->isVisible());
2187 m_ui.viewDevicesAction->setEnabled(bHasClient);
2188 m_ui.viewMidiDeviceStatusMenu->setEnabled(
2189 DeviceStatusForm::getInstances().size() > 0);
2190 m_ui.channelsArrangeAction->setEnabled(bHasChannels);
2191
2192 #ifdef CONFIG_VOLUME
2193 // Toolbar widgets are also affected...
2194 m_pVolumeSlider->setEnabled(bHasClient);
2195 m_pVolumeSpinBox->setEnabled(bHasClient);
2196 #endif
2197
2198 // Client/Server status...
2199 if (bHasClient) {
2200 m_statusItem[QSAMPLER_STATUS_CLIENT]->setText(tr("Connected"));
2201 m_statusItem[QSAMPLER_STATUS_SERVER]->setText(m_pOptions->sServerHost
2202 + ':' + QString::number(m_pOptions->iServerPort));
2203 } else {
2204 m_statusItem[QSAMPLER_STATUS_CLIENT]->clear();
2205 m_statusItem[QSAMPLER_STATUS_SERVER]->clear();
2206 }
2207 // Channel status...
2208 if (bHasChannel)
2209 m_statusItem[QSAMPLER_STATUS_CHANNEL]->setText(pChannelStrip->windowTitle());
2210 else
2211 m_statusItem[QSAMPLER_STATUS_CHANNEL]->clear();
2212 // Session status...
2213 if (m_iDirtyCount > 0)
2214 m_statusItem[QSAMPLER_STATUS_SESSION]->setText(tr("MOD"));
2215 else
2216 m_statusItem[QSAMPLER_STATUS_SESSION]->clear();
2217
2218 // Recent files menu.
2219 m_ui.fileOpenRecentMenu->setEnabled(m_pOptions->recentFiles.count() > 0);
2220 }
2221
2222
2223 // Global volume change receiver slot.
2224 void MainForm::volumeChanged ( int iVolume )
2225 {
2226 #ifdef CONFIG_VOLUME
2227
2228 if (m_iVolumeChanging > 0)
2229 return;
2230
2231 m_iVolumeChanging++;
2232
2233 // Update the toolbar widgets...
2234 if (m_pVolumeSlider->value() != iVolume)
2235 m_pVolumeSlider->setValue(iVolume);
2236 if (m_pVolumeSpinBox->value() != iVolume)
2237 m_pVolumeSpinBox->setValue(iVolume);
2238
2239 // Do it as commanded...
2240 const float fVolume = 0.01f * float(iVolume);
2241 if (::lscp_set_volume(m_pClient, fVolume) == LSCP_OK)
2242 appendMessages(QObject::tr("Volume: %1.").arg(fVolume));
2243 else
2244 appendMessagesClient("lscp_set_volume");
2245
2246 m_iVolumeChanging--;
2247
2248 m_iDirtyCount++;
2249 stabilizeForm();
2250
2251 #endif
2252 }
2253
2254
2255 // Channel change receiver slot.
2256 void MainForm::channelStripChanged ( ChannelStrip *pChannelStrip )
2257 {
2258 // Add this strip to the changed list...
2259 if (!m_changedStrips.contains(pChannelStrip)) {
2260 m_changedStrips.append(pChannelStrip);
2261 pChannelStrip->resetErrorCount();
2262 }
2263
2264 // Just mark the dirty form.
2265 m_iDirtyCount++;
2266 // and update the form status...
2267 stabilizeForm();
2268 }
2269
2270
2271 // Grab and restore current sampler channels session.
2272 void MainForm::updateSession (void)
2273 {
2274 #ifdef CONFIG_VOLUME
2275 const int iVolume = ::lroundf(100.0f * ::lscp_get_volume(m_pClient));
2276 m_iVolumeChanging++;
2277 m_pVolumeSlider->setValue(iVolume);
2278 m_pVolumeSpinBox->setValue(iVolume);
2279 m_iVolumeChanging--;
2280 #endif
2281 #ifdef CONFIG_MIDI_INSTRUMENT
2282 // FIXME: Make some room for default instrument maps...
2283 const int iMaps = ::lscp_get_midi_instrument_maps(m_pClient);
2284 if (iMaps < 0)
2285 appendMessagesClient("lscp_get_midi_instrument_maps");
2286 else if (iMaps < 1) {
2287 ::lscp_add_midi_instrument_map(m_pClient,
2288 tr("Chromatic").toUtf8().constData());
2289 ::lscp_add_midi_instrument_map(m_pClient,
2290 tr("Drum Kits").toUtf8().constData());
2291 }
2292 #endif
2293
2294 updateAllChannelStrips(false);
2295
2296 // Do we auto-arrange?
2297 channelsArrangeAuto();
2298
2299 // Remember to refresh devices and instruments...
2300 if (m_pInstrumentListForm)
2301 m_pInstrumentListForm->refreshInstruments();
2302 if (m_pDeviceForm)
2303 m_pDeviceForm->refreshDevices();
2304 }
2305
2306
2307 void MainForm::updateAllChannelStrips ( bool bRemoveDeadStrips )
2308 {
2309 // Skip if setting up a new channel strip...
2310 if (m_iDirtySetup > 0)
2311 return;
2312
2313 // Retrieve the current channel list.
2314 int *piChannelIDs = ::lscp_list_channels(m_pClient);
2315 if (piChannelIDs == nullptr) {
2316 if (::lscp_client_get_errno(m_pClient)) {
2317 appendMessagesClient("lscp_list_channels");
2318 appendMessagesError(
2319 tr("Could not get current list of channels.\n\nSorry."));
2320 }
2321 } else {
2322 // Try to (re)create each channel.
2323 m_pWorkspace->setUpdatesEnabled(false);
2324 for (int iChannel = 0; piChannelIDs[iChannel] >= 0; ++iChannel) {
2325 // Check if theres already a channel strip for this one...
2326 if (!channelStrip(piChannelIDs[iChannel]))
2327 createChannelStrip(new Channel(piChannelIDs[iChannel]));
2328 }
2329 // Do we auto-arrange?
2330 channelsArrangeAuto();
2331 // remove dead channel strips
2332 if (bRemoveDeadStrips) {
2333 const QList<QMdiSubWindow *>& wlist
2334 = m_pWorkspace->subWindowList();
2335 foreach (QMdiSubWindow *pMdiSubWindow, wlist) {
2336 ChannelStrip *pChannelStrip
2337 = static_cast<ChannelStrip *> (pMdiSubWindow->widget());
2338 if (pChannelStrip) {
2339 bool bExists = false;
2340 for (int iChannel = 0; piChannelIDs[iChannel] >= 0; ++iChannel) {
2341 Channel *pChannel = pChannelStrip->channel();
2342 if (pChannel == nullptr)
2343 break;
2344 if (piChannelIDs[iChannel] == pChannel->channelID()) {
2345 // strip exists, don't touch it
2346 bExists = true;
2347 break;
2348 }
2349 }
2350 if (!bExists)
2351 destroyChannelStrip(pChannelStrip);
2352 }
2353 }
2354 }
2355 m_pWorkspace->setUpdatesEnabled(true);
2356 }
2357
2358 stabilizeForm();
2359 }
2360
2361
2362 // Update the recent files list and menu.
2363 void MainForm::updateRecentFiles ( const QString& sFilename )
2364 {
2365 if (m_pOptions == nullptr)
2366 return;
2367
2368 // Remove from list if already there (avoid duplicates)
2369 const int iIndex = m_pOptions->recentFiles.indexOf(sFilename);
2370 if (iIndex >= 0)
2371 m_pOptions->recentFiles.removeAt(iIndex);
2372 // Put it to front...
2373 m_pOptions->recentFiles.push_front(sFilename);
2374 }
2375
2376
2377 // Update the recent files list and menu.
2378 void MainForm::updateRecentFilesMenu (void)
2379 {
2380 if (m_pOptions == nullptr)
2381 return;
2382
2383 // Time to keep the list under limits.
2384 int iRecentFiles = m_pOptions->recentFiles.count();
2385 while (iRecentFiles > m_pOptions->iMaxRecentFiles) {
2386 m_pOptions->recentFiles.pop_back();
2387 iRecentFiles--;
2388 }
2389
2390 // Rebuild the recent files menu...
2391 m_ui.fileOpenRecentMenu->clear();
2392 for (int i = 0; i < iRecentFiles; i++) {
2393 const QString& sFilename = m_pOptions->recentFiles[i];
2394 if (QFileInfo(sFilename).exists()) {
2395 QAction *pAction = m_ui.fileOpenRecentMenu->addAction(
2396 QString("&%1 %2").arg(i + 1).arg(sessionName(sFilename)),
2397 this, SLOT(fileOpenRecent()));
2398 pAction->setData(i);
2399 }
2400 }
2401 }
2402
2403
2404 // Force update of the channels instrument names mode.
2405 void MainForm::updateInstrumentNames (void)
2406 {
2407 // Full channel list update...
2408 const QList<QMdiSubWindow *>& wlist
2409 = m_pWorkspace->subWindowList();
2410 if (wlist.isEmpty())
2411 return;
2412
2413 m_pWorkspace->setUpdatesEnabled(false);
2414 foreach (QMdiSubWindow *pMdiSubWindow, wlist) {
2415 ChannelStrip *pChannelStrip
2416 = static_cast<ChannelStrip *> (pMdiSubWindow->widget());
2417 if (pChannelStrip)
2418 pChannelStrip->updateInstrumentName(true);
2419 }
2420 m_pWorkspace->setUpdatesEnabled(true);
2421 }
2422
2423
2424 // Force update of the channels display font.
2425 void MainForm::updateDisplayFont (void)
2426 {
2427 if (m_pOptions == nullptr)
2428 return;
2429
2430 // Check if display font is legal.
2431 if (m_pOptions->sDisplayFont.isEmpty())
2432 return;
2433
2434 // Realize it.
2435 QFont font;
2436 if (!font.fromString(m_pOptions->sDisplayFont))
2437 return;
2438
2439 // Full channel list update...
2440 const QList<QMdiSubWindow *>& wlist
2441 = m_pWorkspace->subWindowList();
2442 if (wlist.isEmpty())
2443 return;
2444
2445 m_pWorkspace->setUpdatesEnabled(false);
2446 foreach (QMdiSubWindow *pMdiSubWindow, wlist) {
2447 ChannelStrip *pChannelStrip
2448 = static_cast<ChannelStrip *> (pMdiSubWindow->widget());
2449 if (pChannelStrip)
2450 pChannelStrip->setDisplayFont(font);
2451 }
2452 m_pWorkspace->setUpdatesEnabled(true);
2453 }
2454
2455
2456 // Update channel strips background effect.
2457 void MainForm::updateDisplayEffect (void)
2458 {
2459 // Full channel list update...
2460 const QList<QMdiSubWindow *>& wlist
2461 = m_pWorkspace->subWindowList();
2462 if (wlist.isEmpty())
2463 return;
2464
2465 m_pWorkspace->setUpdatesEnabled(false);
2466 foreach (QMdiSubWindow *pMdiSubWindow, wlist) {
2467 ChannelStrip *pChannelStrip
2468 = static_cast<ChannelStrip *> (pMdiSubWindow->widget());
2469 if (pChannelStrip)
2470 pChannelStrip->setDisplayEffect(m_pOptions->bDisplayEffect);
2471 }
2472 m_pWorkspace->setUpdatesEnabled(true);
2473 }
2474
2475
2476 // Force update of the channels maximum volume setting.
2477 void MainForm::updateMaxVolume (void)
2478 {
2479 if (m_pOptions == nullptr)
2480 return;
2481
2482 #ifdef CONFIG_VOLUME
2483 m_iVolumeChanging++;
2484 m_pVolumeSlider->setMaximum(m_pOptions->iMaxVolume);
2485 m_pVolumeSpinBox->setMaximum(m_pOptions->iMaxVolume);
2486 m_iVolumeChanging--;
2487 #endif
2488
2489 // Full channel list update...
2490 const QList<QMdiSubWindow *>& wlist
2491 = m_pWorkspace->subWindowList();
2492 if (wlist.isEmpty())
2493 return;
2494
2495 m_pWorkspace->setUpdatesEnabled(false);
2496 foreach (QMdiSubWindow *pMdiSubWindow, wlist) {
2497 ChannelStrip *pChannelStrip
2498 = static_cast<ChannelStrip *> (pMdiSubWindow->widget());
2499 if (pChannelStrip)
2500 pChannelStrip->setMaxVolume(m_pOptions->iMaxVolume);
2501 }
2502 m_pWorkspace->setUpdatesEnabled(true);
2503 }
2504
2505
2506 //-------------------------------------------------------------------------
2507 // QSampler::MainForm -- Messages window form handlers.
2508
2509 // Messages output methods.
2510 void MainForm::appendMessages ( const QString& s )
2511 {
2512 if (m_pMessages)
2513 m_pMessages->appendMessages(s);
2514
2515 statusBar()->showMessage(s, 3000);
2516 }
2517
2518 void MainForm::appendMessagesColor ( const QString& s, const QColor& rgb )
2519 {
2520 if (m_pMessages)
2521 m_pMessages->appendMessagesColor(s, rgb);
2522
2523 statusBar()->showMessage(s, 3000);
2524 }
2525
2526 void MainForm::appendMessagesText ( const QString& s )
2527 {
2528 if (m_pMessages)
2529 m_pMessages->appendMessagesText(s);
2530 }
2531
2532 void MainForm::appendMessagesError ( const QString& s )
2533 {
2534 if (m_pMessages)
2535 m_pMessages->show();
2536
2537 appendMessagesColor(s.simplified(), Qt::red);
2538
2539 // Make it look responsive...:)
2540 QApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
2541
2542 if (m_pOptions && m_pOptions->bConfirmError) {
2543 const QString& sTitle = tr("Error");
2544 #if 0
2545 QMessageBox::critical(this, sTitle, sText, QMessageBox::Cancel);
2546 #else
2547 QMessageBox mbox(this);
2548 mbox.setIcon(QMessageBox::Critical);
2549 mbox.setWindowTitle(sTitle);
2550 mbox.setText(s);
2551 mbox.setStandardButtons(QMessageBox::Cancel);
2552 QCheckBox cbox(tr("Don't show this again"));
2553 cbox.setChecked(false);
2554 cbox.blockSignals(true);
2555 mbox.addButton(&cbox, QMessageBox::ActionRole);
2556 if (mbox.exec() && cbox.isChecked())
2557 m_pOptions->bConfirmError = false;
2558 #endif
2559 }
2560 }
2561
2562
2563 // This is a special message format, just for client results.
2564 void MainForm::appendMessagesClient( const QString& s )
2565 {
2566 if (m_pClient == nullptr)
2567 return;
2568
2569 appendMessagesColor(s + QString(": %1 (errno=%2)")
2570 .arg(::lscp_client_get_result(m_pClient))
2571 .arg(::lscp_client_get_errno(m_pClient)), "#996666");
2572
2573 // Make it look responsive...:)
2574 QApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
2575 }
2576
2577
2578 // Force update of the messages font.
2579 void MainForm::updateMessagesFont (void)
2580 {
2581 if (m_pOptions == nullptr)
2582 return;
2583
2584 if (m_pMessages && !m_pOptions->sMessagesFont.isEmpty()) {
2585 QFont font;
2586 if (font.fromString(m_pOptions->sMessagesFont))
2587 m_pMessages->setMessagesFont(font);
2588 }
2589 }
2590
2591
2592 // Update messages window line limit.
2593 void MainForm::updateMessagesLimit (void)
2594 {
2595 if (m_pOptions == nullptr)
2596 return;
2597
2598 if (m_pMessages) {
2599 if (m_pOptions->bMessagesLimit)
2600 m_pMessages->setMessagesLimit(m_pOptions->iMessagesLimitLines);
2601 else
2602 m_pMessages->setMessagesLimit(-1);
2603 }
2604 }
2605
2606
2607 // Enablement of the messages capture feature.
2608 void MainForm::updateMessagesCapture (void)
2609 {
2610 if (m_pOptions == nullptr)
2611 return;
2612
2613 if (m_pMessages)
2614 m_pMessages->setCaptureEnabled(m_pOptions->bStdoutCapture);
2615 }
2616
2617
2618 //-------------------------------------------------------------------------
2619 // QSampler::MainForm -- MDI channel strip management.
2620
2621 // The channel strip creation executive.
2622 ChannelStrip *MainForm::createChannelStrip ( Channel *pChannel )
2623 {
2624 if (m_pClient == nullptr || pChannel == nullptr)
2625 return nullptr;
2626
2627 // Add a new channel itema...
2628 ChannelStrip *pChannelStrip = new ChannelStrip();
2629 if (pChannelStrip == nullptr)
2630 return nullptr;
2631
2632 // Set some initial channel strip options...
2633 if (m_pOptions) {
2634 // Background display effect...
2635 pChannelStrip->setDisplayEffect(m_pOptions->bDisplayEffect);
2636 // We'll need a display font.
2637 QFont font;
2638 if (!m_pOptions->sDisplayFont.isEmpty() &&
2639 font.fromString(m_pOptions->sDisplayFont))
2640 pChannelStrip->setDisplayFont(font);
2641 // Maximum allowed volume setting.
2642 pChannelStrip->setMaxVolume(m_pOptions->iMaxVolume);
2643 }
2644
2645 // Add it to workspace...
2646 QMdiSubWindow *pMdiSubWindow
2647 = m_pWorkspace->addSubWindow(pChannelStrip,
2648 Qt::SubWindow | Qt::FramelessWindowHint);
2649 pMdiSubWindow->setAttribute(Qt::WA_DeleteOnClose);
2650
2651 // Actual channel strip setup...
2652 pChannelStrip->setup(pChannel);
2653
2654 QObject::connect(pChannelStrip,
2655 SIGNAL(channelChanged(ChannelStrip *)),
2656 SLOT(channelStripChanged(ChannelStrip *)));
2657
2658 // Now we show up us to the world.
2659 pChannelStrip->show();
2660
2661 // This is pretty new, so we'll watch for it closely.
2662 channelStripChanged(pChannelStrip);
2663
2664 // Return our successful reference...
2665 return pChannelStrip;
2666 }
2667
2668
2669 void MainForm::destroyChannelStrip ( ChannelStrip *pChannelStrip )
2670 {
2671 QMdiSubWindow *pMdiSubWindow
2672 = static_cast<QMdiSubWindow *> (pChannelStrip->parentWidget());
2673 if (pMdiSubWindow == nullptr)
2674 return;
2675
2676 // Just delete the channel strip.
2677 delete pChannelStrip;
2678 delete pMdiSubWindow;
2679
2680 // Do we auto-arrange?
2681 channelsArrangeAuto();
2682 }
2683
2684
2685 // Retrieve the active channel strip.
2686 ChannelStrip *MainForm::activeChannelStrip (void)
2687 {
2688 QMdiSubWindow *pMdiSubWindow = m_pWorkspace->activeSubWindow();
2689 if (pMdiSubWindow)
2690 return static_cast<ChannelStrip *> (pMdiSubWindow->widget());
2691 else
2692 return nullptr;
2693 }
2694
2695
2696 // Retrieve a channel strip by index.
2697 ChannelStrip *MainForm::channelStripAt ( int iStrip )
2698 {
2699 if (!m_pWorkspace) return nullptr;
2700
2701 const QList<QMdiSubWindow *>& wlist
2702 = m_pWorkspace->subWindowList();
2703 if (wlist.isEmpty())
2704 return nullptr;
2705
2706 if (iStrip < 0 || iStrip >= wlist.count())
2707 return nullptr;
2708
2709 QMdiSubWindow *pMdiSubWindow = wlist.at(iStrip);
2710 if (pMdiSubWindow)
2711 return static_cast<ChannelStrip *> (pMdiSubWindow->widget());
2712 else
2713 return nullptr;
2714 }
2715
2716
2717 // Retrieve a channel strip by sampler channel id.
2718 ChannelStrip *MainForm::channelStrip ( int iChannelID )
2719 {
2720 const QList<QMdiSubWindow *>& wlist
2721 = m_pWorkspace->subWindowList();
2722 if (wlist.isEmpty())
2723 return nullptr;
2724
2725 foreach (QMdiSubWindow *pMdiSubWindow, wlist) {
2726 ChannelStrip *pChannelStrip
2727 = static_cast<ChannelStrip *> (pMdiSubWindow->widget());
2728 if (pChannelStrip) {
2729 Channel *pChannel = pChannelStrip->channel();
2730 if (pChannel && pChannel->channelID() == iChannelID)
2731 return pChannelStrip;
2732 }
2733 }
2734
2735 // Not found.
2736 return nullptr;
2737 }
2738
2739
2740 // Construct the windows menu.
2741 void MainForm::channelsMenuAboutToShow (void)
2742 {
2743 m_ui.channelsMenu->clear();
2744 m_ui.channelsMenu->addAction(m_ui.channelsArrangeAction);
2745 m_ui.channelsMenu->addAction(m_ui.channelsAutoArrangeAction);
2746
2747 const QList<QMdiSubWindow *>& wlist
2748 = m_pWorkspace->subWindowList();
2749 if (!wlist.isEmpty()) {
2750 m_ui.channelsMenu->addSeparator();
2751 int iStrip = 0;
2752 foreach (QMdiSubWindow *pMdiSubWindow, wlist) {
2753 ChannelStrip *pChannelStrip
2754 = static_cast<ChannelStrip *> (pMdiSubWindow->widget());
2755 if (pChannelStrip) {
2756 QAction *pAction = m_ui.channelsMenu->addAction(
2757 pChannelStrip->windowTitle(),
2758 this, SLOT(channelsMenuActivated()));
2759 pAction->setCheckable(true);
2760 pAction->setChecked(activeChannelStrip() == pChannelStrip);
2761 pAction->setData(iStrip);
2762 }
2763 ++iStrip;
2764 }
2765 }
2766 }
2767
2768
2769 // Windows menu activation slot
2770 void MainForm::channelsMenuActivated (void)
2771 {
2772 // Retrive channel index from action data...
2773 QAction *pAction = qobject_cast<QAction *> (sender());
2774 if (pAction == nullptr)
2775 return;
2776
2777 ChannelStrip *pChannelStrip = channelStripAt(pAction->data().toInt());
2778 if (pChannelStrip) {
2779 pChannelStrip->showNormal();
2780 pChannelStrip->setFocus();
2781 }
2782 }
2783
2784
2785 //-------------------------------------------------------------------------
2786 // QSampler::MainForm -- Timer stuff.
2787
2788 // Set the pseudo-timer delay schedule.
2789 void MainForm::startSchedule ( int iStartDelay )
2790 {
2791 m_iStartDelay = 1 + (iStartDelay * 1000);
2792 m_iTimerDelay = 0;
2793 }
2794
2795 // Suspend the pseudo-timer delay schedule.
2796 void MainForm::stopSchedule (void)
2797 {
2798 m_iStartDelay = 0;
2799 m_iTimerDelay = 0;
2800 }
2801
2802 // Timer slot funtion.
2803 void MainForm::timerSlot (void)
2804 {
2805 if (m_pOptions == nullptr)
2806 return;
2807
2808 // Is it the first shot on server start after a few delay?
2809 if (m_iTimerDelay < m_iStartDelay) {
2810 m_iTimerDelay += QSAMPLER_TIMER_MSECS;
2811 if (m_iTimerDelay >= m_iStartDelay) {
2812 // If we cannot start it now, maybe a lil'mo'later ;)
2813 if (!startClient()) {
2814 m_iStartDelay += m_iTimerDelay;
2815 m_iTimerDelay = 0;
2816 }
2817 }
2818 }
2819
2820 if (m_pClient) {
2821 // Update the channel information for each pending strip...
2822 QListIterator<ChannelStrip *> iter(m_changedStrips);
2823 while (iter.hasNext()) {
2824 ChannelStrip *pChannelStrip = iter.next();
2825 // If successfull, remove from pending list...
2826 if (pChannelStrip->updateChannelInfo()) {
2827 const int iChannelStrip = m_changedStrips.indexOf(pChannelStrip);
2828 if (iChannelStrip >= 0)
2829 m_changedStrips.removeAt(iChannelStrip);
2830 }
2831 }
2832 // Refresh each channel usage, on each period...
2833 if (m_pOptions->bAutoRefresh) {
2834 m_iTimerSlot += QSAMPLER_TIMER_MSECS;
2835 if (m_iTimerSlot >= m_pOptions->iAutoRefreshTime) {
2836 m_iTimerSlot = 0;
2837 // Update the channel stream usage for each strip...
2838 const QList<QMdiSubWindow *>& wlist
2839 = m_pWorkspace->subWindowList();
2840 foreach (QMdiSubWindow *pMdiSubWindow, wlist) {
2841 ChannelStrip *pChannelStrip
2842 = static_cast<ChannelStrip *> (pMdiSubWindow->widget());
2843 if (pChannelStrip && pChannelStrip->isVisible())
2844 pChannelStrip->updateChannelUsage();
2845 }
2846 }
2847 }
2848
2849 #if CONFIG_LSCP_CLIENT_CONNECTION_LOST
2850 // If we lost connection to server: Try to automatically reconnect if we
2851 // did not start the server.
2852 //
2853 // TODO: If we started the server, then we might inform the user that
2854 // the server probably crashed and asking user ONCE whether we should
2855 // restart the server.
2856 if (lscp_client_connection_lost(m_pClient) && !m_pServer)
2857 startAutoReconnectClient();
2858 #endif // CONFIG_LSCP_CLIENT_CONNECTION_LOST
2859 }
2860
2861 // Register the next timer slot.
2862 QTimer::singleShot(QSAMPLER_TIMER_MSECS, this, SLOT(timerSlot()));
2863 }
2864
2865
2866 //-------------------------------------------------------------------------
2867 // QSampler::MainForm -- Server stuff.
2868
2869 // Start linuxsampler server...
2870 void MainForm::startServer (void)
2871 {
2872 if (m_pOptions == nullptr)
2873 return;
2874
2875 // Aren't already a client, are we?
2876 if (!m_pOptions->bServerStart || m_pClient)
2877 return;
2878
2879 // Is the server process instance still here?
2880 if (m_pServer) {
2881 if (QMessageBox::warning(this,
2882 tr("Warning"),
2883 tr("Could not start the LinuxSampler server.\n\n"
2884 "Maybe it is already started."),
2885 QMessageBox::Ok | QMessageBox::Cancel) == QMessageBox::Ok) {
2886 m_pServer->terminate();
2887 m_pServer->kill();
2888 }
2889 return;
2890 }
2891
2892 // Reset our timer counters...
2893 stopSchedule();
2894
2895 // Verify we have something to start with...
2896 if (m_pOptions->sServerCmdLine.isEmpty())
2897 return;
2898
2899 // OK. Let's build the startup process...
2900 m_pServer = new QProcess();
2901 m_bForceServerStop = true;
2902
2903 // Setup stdout/stderr capture...
2904 m_pServer->setProcessChannelMode(QProcess::ForwardedChannels);
2905 QObject::connect(m_pServer,
2906 SIGNAL(readyReadStandardOutput()),
2907 SLOT(readServerStdout()));
2908 QObject::connect(m_pServer,
2909 SIGNAL(readyReadStandardError()),
2910 SLOT(readServerStdout()));
2911
2912 // The unforgiveable signal communication...
2913 QObject::connect(m_pServer,
2914 SIGNAL(finished(int, QProcess::ExitStatus)),
2915 SLOT(processServerExit()));
2916
2917 // Build process arguments...
2918 QStringList args = m_pOptions->sServerCmdLine.split(' ');
2919 QString sCommand = args[0];
2920 args.removeAt(0);
2921
2922 appendMessages(tr("Server is starting..."));
2923 appendMessagesColor(m_pOptions->sServerCmdLine, "#990099");
2924
2925 // Go linuxsampler, go...
2926 m_pServer->start(sCommand, args);
2927 if (!m_pServer->waitForStarted()) {
2928 appendMessagesError(tr("Could not start server.\n\nSorry."));
2929 processServerExit();
2930 return;
2931 }
2932
2933 // Show startup results...
2934 appendMessages(
2935 tr("Server was started with PID=%1.")
2936 #if QT_VERSION < QT_VERSION_CHECK(5, 3, 0)
2937 .arg(quint64(m_pServer->pid())));
2938 #else
2939 .arg(quint64(m_pServer->processId())));
2940 #endif
2941
2942 // Reset (yet again) the timer counters,
2943 // but this time is deferred as the user opted.
2944 startSchedule(m_pOptions->iStartDelay);
2945 stabilizeForm();
2946 }
2947
2948
2949 // Stop linuxsampler server...
2950 void MainForm::stopServer ( bool bInteractive )
2951 {
2952 // Stop client code.
2953 stopClient();
2954
2955 if (m_pServer && bInteractive) {
2956 if (QMessageBox::question(this,
2957 tr("The backend's fate ..."),
2958 tr("You have the option to keep the sampler backend (LinuxSampler)\n"
2959 "running in the background. The sampler would continue to work\n"
2960 "according to your current sampler session and you could alter the\n"
2961 "sampler session at any time by relaunching QSampler.\n\n"
2962 "Do you want LinuxSampler to stop?"),
2963 QMessageBox::Yes | QMessageBox::No,
2964 QMessageBox::Yes) == QMessageBox::No) {
2965 m_bForceServerStop = false;
2966 }
2967 }
2968
2969 bool bGraceWait = true;
2970
2971 // And try to stop server.
2972 if (m_pServer && m_bForceServerStop) {
2973 appendMessages(tr("Server is stopping..."));
2974 if (m_pServer->state() == QProcess::Running) {
2975 #if defined(__WIN32__) || defined(_WIN32) || defined(WIN32)
2976 // Try harder...
2977 m_pServer->kill();
2978 #else
2979 // Try softly...
2980 m_pServer->terminate();
2981 bool bFinished = m_pServer->waitForFinished(QSAMPLER_TIMER_MSECS * 1000);
2982 if (bFinished) bGraceWait = false;
2983 #endif
2984 }
2985 } // Do final processing anyway.
2986 else processServerExit();
2987
2988 // Give it some time to terminate gracefully and stabilize...
2989 if (bGraceWait) {
2990 QElapsedTimer timer;
2991 timer.start();
2992 while (timer.elapsed() < QSAMPLER_TIMER_MSECS)
2993 QApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
2994 }
2995 }
2996
2997
2998 // Stdout handler...
2999 void MainForm::readServerStdout (void)
3000 {
3001 if (m_pMessages)
3002 m_pMessages->appendStdoutBuffer(m_pServer->readAllStandardOutput());
3003 }
3004
3005
3006 // Linuxsampler server cleanup.
3007 void MainForm::processServerExit (void)
3008 {
3009 // Force client code cleanup.
3010 stopClient();
3011
3012 // Flush anything that maybe pending...
3013 if (m_pMessages)
3014 m_pMessages->flushStdoutBuffer();
3015
3016 if (m_pServer && m_bForceServerStop) {
3017 if (m_pServer->state() != QProcess::NotRunning) {
3018 appendMessages(tr("Server is being forced..."));
3019 // Force final server shutdown...
3020 m_pServer->kill();
3021 // Give it some time to terminate gracefully and stabilize...
3022 QElapsedTimer timer;
3023 timer.start();
3024 while (timer.elapsed() < QSAMPLER_TIMER_MSECS)
3025 QApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
3026 }
3027 // Force final server shutdown...
3028 appendMessages(
3029 tr("Server was stopped with exit status %1.")
3030 .arg(m_pServer->exitStatus()));
3031 delete m_pServer;
3032 m_pServer = nullptr;
3033 }
3034
3035 // Again, make status visible stable.
3036 stabilizeForm();
3037 }
3038
3039
3040 //-------------------------------------------------------------------------
3041 // QSampler::MainForm -- Client stuff.
3042
3043 // The LSCP client callback procedure.
3044 lscp_status_t qsampler_client_callback ( lscp_client_t */*pClient*/,
3045 lscp_event_t event, const char *pchData, int cchData, void *pvData )
3046 {
3047 MainForm* pMainForm = (MainForm *) pvData;
3048 if (pMainForm == nullptr)
3049 return LSCP_FAILED;
3050
3051 // ATTN: DO NOT EVER call any GUI code here,
3052 // as this is run under some other thread context.
3053 // A custom event must be posted here...
3054 QApplication::postEvent(pMainForm,
3055 new LscpEvent(event, pchData, cchData));
3056
3057 return LSCP_OK;
3058 }
3059
3060
3061 // Start our almighty client...
3062 bool MainForm::startClient (bool bReconnectOnly)
3063 {
3064 // Have it a setup?
3065 if (m_pOptions == nullptr)
3066 return false;
3067
3068 // Aren't we already started, are we?
3069 if (m_pClient)
3070 return true;
3071
3072 // Log prepare here.
3073 appendMessages(tr("Client connecting..."));
3074
3075 // Create the client handle...
3076 m_pClient = ::lscp_client_create(
3077 m_pOptions->sServerHost.toUtf8().constData(),
3078 m_pOptions->iServerPort, qsampler_client_callback, this);
3079 if (m_pClient == nullptr) {
3080 // Is this the first try?
3081 // maybe we need to start a local server...
3082 if ((m_pServer && m_pServer->state() == QProcess::Running)
3083 || !m_pOptions->bServerStart || bReconnectOnly)
3084 {
3085 // if this method is called from autoReconnectClient()
3086 // then don't bother user with an error message...
3087 if (!bReconnectOnly) {
3088 appendMessagesError(
3089 tr("Could not connect to server as client.\n\nSorry.")
3090 );
3091 }
3092 } else {
3093 startServer();
3094 }
3095 // This is always a failure.
3096 stabilizeForm();
3097 return false;
3098 }
3099
3100 // Just set receive timeout value, blindly.
3101 ::lscp_client_set_timeout(m_pClient, m_pOptions->iServerTimeout);
3102 appendMessages(
3103 tr("Client receive timeout is set to %1 msec.")
3104 .arg(::lscp_client_get_timeout(m_pClient)));
3105
3106 // Subscribe to channel info change notifications...
3107 if (::lscp_client_subscribe(m_pClient, LSCP_EVENT_CHANNEL_COUNT) != LSCP_OK)
3108 appendMessagesClient("lscp_client_subscribe(CHANNEL_COUNT)");
3109 if (::lscp_client_subscribe(m_pClient, LSCP_EVENT_CHANNEL_INFO) != LSCP_OK)
3110 appendMessagesClient("lscp_client_subscribe(CHANNEL_INFO)");
3111
3112 DeviceStatusForm::onDevicesChanged(); // initialize
3113 updateViewMidiDeviceStatusMenu();
3114 if (::lscp_client_subscribe(m_pClient, LSCP_EVENT_MIDI_INPUT_DEVICE_COUNT) != LSCP_OK)
3115 appendMessagesClient("lscp_client_subscribe(MIDI_INPUT_DEVICE_COUNT)");
3116 if (::lscp_client_subscribe(m_pClient, LSCP_EVENT_MIDI_INPUT_DEVICE_INFO) != LSCP_OK)
3117 appendMessagesClient("lscp_client_subscribe(MIDI_INPUT_DEVICE_INFO)");
3118 if (::lscp_client_subscribe(m_pClient, LSCP_EVENT_AUDIO_OUTPUT_DEVICE_COUNT) != LSCP_OK)
3119 appendMessagesClient("lscp_client_subscribe(AUDIO_OUTPUT_DEVICE_COUNT)");
3120 if (::lscp_client_subscribe(m_pClient, LSCP_EVENT_AUDIO_OUTPUT_DEVICE_INFO) != LSCP_OK)
3121 appendMessagesClient("lscp_client_subscribe(AUDIO_OUTPUT_DEVICE_INFO)");
3122
3123 #if CONFIG_EVENT_CHANNEL_MIDI
3124 // Subscribe to channel MIDI data notifications...
3125 if (::lscp_client_subscribe(m_pClient, LSCP_EVENT_CHANNEL_MIDI) != LSCP_OK)
3126 appendMessagesClient("lscp_client_subscribe(CHANNEL_MIDI)");
3127 #endif
3128
3129 #if CONFIG_EVENT_DEVICE_MIDI
3130 // Subscribe to channel MIDI data notifications...
3131 if (::lscp_client_subscribe(m_pClient, LSCP_EVENT_DEVICE_MIDI) != LSCP_OK)
3132 appendMessagesClient("lscp_client_subscribe(DEVICE_MIDI)");
3133 #endif
3134
3135 // We may stop scheduling around.
3136 stopSchedule();
3137
3138 // We'll accept drops from now on...
3139 setAcceptDrops(true);
3140
3141 // Log success here.
3142 appendMessages(tr("Client connected."));
3143
3144 // Hard-notify instrumnet and device configuration forms,
3145 // if visible, that we're ready...
3146 if (m_pInstrumentListForm)
3147 m_pInstrumentListForm->refreshInstruments();
3148 if (m_pDeviceForm)
3149 m_pDeviceForm->refreshDevices();
3150
3151 // Is any session pending to be loaded?
3152 if (!m_pOptions->sessionFiles.isEmpty()) {
3153 // Just load the prabably startup session...
3154 if (loadSessionFile(m_pOptions->sessionFiles.first())) {
3155 m_pOptions->sessionFiles.clear();
3156 return true;
3157 }
3158 }
3159
3160 // send the current / loaded fine tuning settings to the sampler
3161 m_pOptions->sendFineTuningSettings();
3162
3163 // Make a new session
3164 return newSession();
3165 }
3166
3167
3168 // Stop client...
3169 void MainForm::stopClient (void)
3170 {
3171 if (m_pClient == nullptr)
3172 return;
3173
3174 // Log prepare here.
3175 appendMessages(tr("Client disconnecting..."));
3176
3177 // Clear timer counters...
3178 stopSchedule();
3179
3180 // We'll reject drops from now on...
3181 setAcceptDrops(false);
3182
3183 // Force any channel strips around, but
3184 // but avoid removing the corresponding
3185 // channels from the back-end server.
3186 m_iDirtyCount = 0;
3187 closeSession(false);
3188
3189 // Close us as a client...
3190 #if CONFIG_EVENT_DEVICE_MIDI
3191 ::lscp_client_unsubscribe(m_pClient, LSCP_EVENT_DEVICE_MIDI);
3192 #endif
3193 #if CONFIG_EVENT_CHANNEL_MIDI
3194 ::lscp_client_unsubscribe(m_pClient, LSCP_EVENT_CHANNEL_MIDI);
3195 #endif
3196 ::lscp_client_unsubscribe(m_pClient, LSCP_EVENT_AUDIO_OUTPUT_DEVICE_INFO);
3197 ::lscp_client_unsubscribe(m_pClient, LSCP_EVENT_AUDIO_OUTPUT_DEVICE_COUNT);
3198 ::lscp_client_unsubscribe(m_pClient, LSCP_EVENT_MIDI_INPUT_DEVICE_INFO);
3199 ::lscp_client_unsubscribe(m_pClient, LSCP_EVENT_MIDI_INPUT_DEVICE_COUNT);
3200 ::lscp_client_unsubscribe(m_pClient, LSCP_EVENT_CHANNEL_INFO);
3201 ::lscp_client_unsubscribe(m_pClient, LSCP_EVENT_CHANNEL_COUNT);
3202 ::lscp_client_destroy(m_pClient);
3203 m_pClient = nullptr;
3204
3205 // Hard-notify instrumnet and device configuration forms,
3206 // if visible, that we're running out...
3207 if (m_pInstrumentListForm)
3208 m_pInstrumentListForm->refreshInstruments();
3209 if (m_pDeviceForm)
3210 m_pDeviceForm->refreshDevices();
3211
3212 // Log final here.
3213 appendMessages(tr("Client disconnected."));
3214
3215 // Make visible status.
3216 stabilizeForm();
3217 }
3218
3219
3220 void MainForm::startAutoReconnectClient (void)
3221 {
3222 stopClient();
3223 appendMessages(tr("Trying to reconnect..."));
3224 QTimer::singleShot(QSAMPLER_TIMER_MSECS, this, SLOT(autoReconnectClient()));
3225 }
3226
3227
3228 void MainForm::autoReconnectClient (void)
3229 {
3230 const bool bSuccess = startClient(true);
3231 if (!bSuccess)
3232 QTimer::singleShot(QSAMPLER_TIMER_MSECS, this, SLOT(autoReconnectClient()));
3233 }
3234
3235
3236 // Channel strip activation/selection.
3237 void MainForm::activateStrip ( QMdiSubWindow *pMdiSubWindow )
3238 {
3239 ChannelStrip *pChannelStrip = nullptr;
3240 if (pMdiSubWindow)
3241 pChannelStrip = static_cast<ChannelStrip *> (pMdiSubWindow->widget());
3242 if (pChannelStrip)
3243 pChannelStrip->setSelected(true);
3244
3245 stabilizeForm();
3246 }
3247
3248
3249 // Channel toolbar orientation change.
3250 void MainForm::channelsToolbarOrientation ( Qt::Orientation orientation )
3251 {
3252 #ifdef CONFIG_VOLUME
3253 m_pVolumeSlider->setOrientation(orientation);
3254 if (orientation == Qt::Horizontal) {
3255 m_pVolumeSlider->setMinimumHeight(24);
3256 m_pVolumeSlider->setMaximumHeight(32);
3257 m_pVolumeSlider->setMinimumWidth(120);
3258 m_pVolumeSlider->setMaximumWidth(640);
3259 m_pVolumeSpinBox->setMaximumWidth(64);
3260 m_pVolumeSpinBox->setButtonSymbols(QSpinBox::UpDownArrows);
3261 } else {
3262 m_pVolumeSlider->setMinimumHeight(120);
3263 m_pVolumeSlider->setMaximumHeight(480);
3264 m_pVolumeSlider->setMinimumWidth(24);
3265 m_pVolumeSlider->setMaximumWidth(32);
3266 m_pVolumeSpinBox->setMaximumWidth(32);
3267 m_pVolumeSpinBox->setButtonSymbols(QSpinBox::NoButtons);
3268 }
3269 #endif
3270 }
3271
3272
3273 } // namespace QSampler
3274
3275
3276 // end of qsamplerMainForm.cpp

  ViewVC Help
Powered by ViewVC