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

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

Parent Directory Parent Directory | Revision Log Revision Log


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

  ViewVC Help
Powered by ViewVC