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

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

Parent Directory Parent Directory | Revision Log Revision Log


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