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

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

Parent Directory Parent Directory | Revision Log Revision Log


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

  ViewVC Help
Powered by ViewVC