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

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

Parent Directory Parent Directory | Revision Log Revision Log


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

  ViewVC Help
Powered by ViewVC