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

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

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1499 - (show annotations) (download)
Tue Nov 20 16:48:04 2007 UTC (16 years, 4 months ago) by capela
File size: 78757 byte(s)
* Qt4 migration: one first step forward to kiss Qt3Support goodbye.

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

  ViewVC Help
Powered by ViewVC