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

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

Parent Directory Parent Directory | Revision Log Revision Log


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

  ViewVC Help
Powered by ViewVC