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

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

Parent Directory Parent Directory | Revision Log Revision Log


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

  ViewVC Help
Powered by ViewVC