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

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

Parent Directory Parent Directory | Revision Log Revision Log


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

  ViewVC Help
Powered by ViewVC