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

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

Parent Directory Parent Directory | Revision Log Revision Log


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

  ViewVC Help
Powered by ViewVC