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

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

Parent Directory Parent Directory | Revision Log Revision Log


Revision 3416 - (show annotations) (download)
Tue Feb 6 18:56:33 2018 UTC (6 years, 1 month ago) by schoenebeck
File size: 88953 byte(s)
* Fixed unnecessary latency when closing app.

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

  ViewVC Help
Powered by ViewVC