To check out this repository please hg clone the following URL, or open the URL using EasyMercurial or your preferred Mercurial client.

Statistics Download as Zip
| Branch: | Tag: | Revision:

root / main / MainWindow.cpp @ 312:6394462e0c12

History | View | Annotate | Download (104 KB)

1
/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
2

    
3
/*
4
    Vect
5
    An experimental audio player for plural recordings of a work
6
    Centre for Digital Music, Queen Mary, University of London.
7
    This file copyright 2006-2019 Chris Cannam and QMUL.
8
    
9
    This program is free software; you can redistribute it and/or
10
    modify it under the terms of the GNU General Public License as
11
    published by the Free Software Foundation; either version 2 of the
12
    License, or (at your option) any later version.  See the file
13
    COPYING included with this distribution for more information.
14
*/
15

    
16
#include "../version.h"
17

    
18
#include "MainWindow.h"
19
#include "framework/Document.h"
20
#include "framework/VersionTester.h"
21

    
22
#include "PreferencesDialog.h"
23
#include "NetworkPermissionTester.h"
24
#include "IntroDialog.h"
25

    
26
#include "view/Pane.h"
27
#include "view/PaneStack.h"
28
#include "data/model/WaveFileModel.h"
29
#include "data/model/SparseOneDimensionalModel.h"
30
#include "data/model/AlignmentModel.h"
31
#include "data/model/SparseOneDimensionalModel.h"
32
#include "base/StorageAdviser.h"
33
#include "base/Exceptions.h"
34
#include "base/TempDirectory.h"
35
#include "base/RecordDirectory.h"
36
#include "view/ViewManager.h"
37
#include "base/Preferences.h"
38
#include "layer/WaveformLayer.h"
39
#include "layer/TimeRulerLayer.h"
40
#include "layer/TimeInstantLayer.h"
41
#include "layer/TimeValueLayer.h"
42
#include "layer/Colour3DPlotLayer.h"
43
#include "layer/SliceLayer.h"
44
#include "layer/SliceableLayer.h"
45
#include "layer/SpectrogramLayer.h"
46
#include "view/Overview.h"
47
#include "widgets/PropertyBox.h"
48
#include "widgets/PropertyStack.h"
49
#include "widgets/AudioDial.h"
50
#include "widgets/LevelPanWidget.h"
51
#include "widgets/LevelPanToolButton.h"
52
#include "widgets/IconLoader.h"
53
#include "widgets/LayerTree.h"
54
#include "widgets/ListInputDialog.h"
55
#include "widgets/SubdividingMenu.h"
56
#include "widgets/NotifyingPushButton.h"
57
#include "widgets/KeyReference.h"
58
#include "audio/AudioCallbackPlaySource.h"
59
#include "audio/AudioCallbackRecordTarget.h"
60
#include "audio/PlaySpeedRangeMapper.h"
61
#include "data/fileio/DataFileReaderFactory.h"
62
#include "data/fileio/PlaylistFileReader.h"
63
#include "data/fileio/WavFileWriter.h"
64
#include "data/fileio/CSVFileWriter.h"
65
#include "data/fileio/BZipFileDevice.h"
66
#include "data/fileio/FileSource.h"
67
#include "base/RecentFiles.h"
68
#include "transform/TransformFactory.h"
69
#include "transform/ModelTransformerFactory.h"
70
#include "base/PlayParameterRepository.h"
71
#include "base/XmlExportable.h"
72
#include "widgets/CommandHistory.h"
73
#include "base/Profiler.h"
74
#include "base/Clipboard.h"
75
#include "base/UnitDatabase.h"
76
#include "layer/ColourDatabase.h"
77
#include "data/osc/OSCQueue.h"
78

    
79
//!!!
80
#include "data/model/AggregateWaveModel.h"
81

    
82
// For version information
83
#include "vamp/vamp.h"
84
#include "vamp-sdk/PluginBase.h"
85
#include "plugin/api/ladspa.h"
86
#include "plugin/api/dssi.h"
87

    
88
#include <bqaudioio/SystemPlaybackTarget.h>
89
#include <bqaudioio/SystemAudioIO.h>
90

    
91
#include <QApplication>
92
#include <QMessageBox>
93
#include <QGridLayout>
94
#include <QLabel>
95
#include <QAction>
96
#include <QMenuBar>
97
#include <QToolBar>
98
#include <QToolButton>
99
#include <QButtonGroup>
100
#include <QInputDialog>
101
#include <QStatusBar>
102
#include <QTreeView>
103
#include <QFile>
104
#include <QFileInfo>
105
#include <QDir>
106
#include <QTextStream>
107
#include <QProcess>
108
#include <QShortcut>
109
#include <QSettings>
110
#include <QDateTime>
111
#include <QProcess>
112
#include <QCheckBox>
113
#include <QRegExp>
114
#include <QScrollArea>
115
#include <QCloseEvent>
116
#include <QDialogButtonBox>
117
#include <QTextEdit>
118

    
119
#include <iostream>
120
#include <cstdio>
121
#include <errno.h>
122

    
123
using std::cerr;
124
using std::endl;
125

    
126
using std::vector;
127
using std::map;
128
using std::set;
129
using std::pair;
130

    
131

    
132
MainWindow::MainWindow(SoundOptions options) :
133
    MainWindowBase(options),
134
    m_mainMenusCreated(false),
135
    m_playbackMenu(0),
136
    m_recentSessionsMenu(0),
137
    m_deleteSelectedAction(0),
138
    m_ffwdAction(0),
139
    m_rwdAction(0),
140
    m_recentSessions("RecentSessions", 20),
141
    m_exiting(false),
142
    m_preferencesDialog(0),
143
    m_layerTreeView(0),
144
    m_keyReference(new KeyReference()),
145
    m_displayMode(OutlineWaveformMode),
146
    m_salientCalculating(false),
147
    m_salientColour(0),
148
    m_sessionState(NoSession)
149
{
150
    setWindowTitle(QApplication::applicationName());
151

    
152
    setUnifiedTitleAndToolBarOnMac(true);
153

    
154
    UnitDatabase *udb = UnitDatabase::getInstance();
155
    udb->registerUnit("Hz");
156
    udb->registerUnit("dB");
157
    udb->registerUnit("s");
158

    
159
    ColourDatabase *cdb = ColourDatabase::getInstance();
160
    cdb->setUseDarkBackground(cdb->addColour(Qt::white, tr("White")), true);
161
    cdb->setUseDarkBackground(cdb->addColour(QColor(30, 150, 255), tr("Bright Blue")), true);
162
    cdb->setUseDarkBackground(cdb->addColour(Qt::red, tr("Bright Red")), true);
163
    cdb->setUseDarkBackground(cdb->addColour(Qt::green, tr("Bright Green")), true);
164
    cdb->setUseDarkBackground(cdb->addColour(QColor(255, 188, 80), tr("Bright Orange")), true);
165
    cdb->setUseDarkBackground(cdb->addColour(QColor(225, 74, 255), tr("Bright Purple")), true);
166
    cdb->setUseDarkBackground(cdb->addColour(Qt::yellow, tr("Bright Yellow")), true);
167

    
168
    Preferences::getInstance()->setResampleOnLoad(true);
169

    
170
    Preferences::getInstance()->setSpectrogramSmoothing
171
        (Preferences::SpectrogramInterpolated);
172

    
173
    Preferences::getInstance()->setSpectrogramXSmoothing
174
        (Preferences::SpectrogramXInterpolated);
175

    
176
    QSettings settings;
177

    
178
    settings.beginGroup("LayerDefaults");
179

    
180
    settings.setValue("spectrogram",
181
                      QString("<layer channel=\"-1\" windowSize=\"1024\" colourMap=\"White on Black\" windowHopLevel=\"2\"/>"));
182

    
183
    settings.setValue("melodicrange",
184
                      QString("<layer channel=\"-1\" gain=\"1\" normalizeVisibleArea=\"false\" columnNormalization=\"hybrid\" colourMap=\"Ice\" minFrequency=\"80\" maxFrequency=\"1500\" windowSize=\"8192\" windowOverlap=\"75\" binDisplay=\"0\" />"));
185

    
186
    settings.endGroup();
187

    
188
    settings.beginGroup("MainWindow");
189
    settings.setValue("showstatusbar", false);
190
    settings.endGroup();
191

    
192
    settings.beginGroup("IconLoader");
193
    settings.setValue("invert-icons-on-dark-background", true);
194
    settings.endGroup();
195

    
196
    settings.beginGroup("View");
197
    settings.setValue("showcancelbuttons", false);
198
    settings.endGroup();
199

    
200
    settings.beginGroup("Alignment");
201
    if (!settings.contains("align-pitch-aware")) {
202
        settings.setValue("align-pitch-aware", true);
203
    }
204
    settings.endGroup();
205

    
206
    m_viewManager->setAlignMode(true);
207
    m_viewManager->setPlaySoloMode(true);
208
    m_viewManager->setToolMode(ViewManager::NavigateMode);
209
    m_viewManager->setZoomWheelsEnabled(false);
210
    m_viewManager->setIlluminateLocalFeatures(false);
211
    m_viewManager->setShowWorkTitle(true);
212
    m_viewManager->setOpportunisticEditingEnabled(false);
213

    
214
    loadStyle();
215
    
216
    QFrame *mainFrame = new QFrame;
217
    QGridLayout *mainLayout = new QGridLayout;
218

    
219
    setCentralWidget(mainFrame);
220
    
221
    m_mainScroll = new QScrollArea(mainFrame);
222
    m_mainScroll->setWidgetResizable(true);
223
    m_mainScroll->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
224
    m_mainScroll->setFrameShape(QFrame::NoFrame);
225

    
226
    m_paneStack->setResizeMode(PaneStack::AutoResizeOnly);
227
    m_paneStack->setLayoutStyle(PaneStack::NoPropertyStacks);
228
    m_paneStack->setShowCloseButtonOnFirstPane(false);
229
    m_paneStack->setShowAlignmentViews(true);
230
    m_mainScroll->setWidget(m_paneStack);
231

    
232
    QFrame *bottomFrame = new QFrame(mainFrame);
233
    bottomFrame->setObjectName("BottomFrame");
234
    QGridLayout *bottomLayout = new QGridLayout;
235

    
236
    int bottomElementHeight = m_viewManager->scalePixelSize(45);
237
    if (bottomElementHeight < 40) bottomElementHeight = 40;
238
    int bottomButtonHeight = (bottomElementHeight * 3) / 4;
239
    
240
    QButtonGroup *bg = new QButtonGroup;
241
    IconLoader il;
242

    
243
    QFrame *buttonFrame = new QFrame(bottomFrame);
244
    QHBoxLayout *buttonLayout = new QHBoxLayout;
245
    buttonLayout->setSpacing(0);
246
    buttonLayout->setMargin(0);
247
    buttonFrame->setLayout(buttonLayout);
248

    
249
    QPushButton *button = new QPushButton;
250
    button->setIcon(il.load("waveform"));
251
    button->setText(tr("Outline waveform"));
252
    button->setCheckable(true);
253
    button->setChecked(true);
254
    button->setFixedHeight(bottomButtonHeight);
255
    bg->addButton(button);
256
    buttonLayout->addWidget(button);
257
    connect(button, SIGNAL(clicked()), this, SLOT(outlineWaveformModeSelected()));
258
    m_modeButtons[OutlineWaveformMode] = button;
259
    m_modeLayerNames[OutlineWaveformMode] = "Outline Waveform"; // not to be translated
260
    m_modeDisplayOrder.push_back(OutlineWaveformMode);
261

    
262
    button = new QPushButton;
263
    button->setIcon(il.load("waveform"));
264
    button->setText(tr("Waveform"));
265
    button->setCheckable(true);
266
    button->setChecked(false);
267
    button->setFixedHeight(bottomButtonHeight);
268
    bg->addButton(button);
269
    buttonLayout->addWidget(button);
270
    connect(button, SIGNAL(clicked()), this, SLOT(standardWaveformModeSelected()));
271
    m_modeButtons[WaveformMode] = button;
272
    m_modeLayerNames[WaveformMode] = "Waveform"; // not to be translated
273
    m_modeDisplayOrder.push_back(WaveformMode);
274

    
275
    button = new QPushButton;
276
    button->setIcon(il.load("colour3d"));
277
    button->setText(tr("Melodic spectrogram"));
278
    button->setCheckable(true);
279
    button->setChecked(false);
280
    button->setFixedHeight(bottomButtonHeight);
281
    bg->addButton(button);
282
    buttonLayout->addWidget(button);
283
    connect(button, SIGNAL(clicked()), this, SLOT(melodogramModeSelected()));
284
    m_modeButtons[MelodogramMode] = button;
285
    m_modeLayerNames[MelodogramMode] = "Melodogram"; // not to be translated
286
    m_modeDisplayOrder.push_back(MelodogramMode);
287

    
288
    button = new QPushButton;
289
    button->setIcon(il.load("colour3d"));
290
    button->setText(tr("Spectrogram"));
291
    button->setCheckable(true);
292
    button->setChecked(false);
293
    button->setFixedHeight(bottomButtonHeight);
294
    bg->addButton(button);
295
    buttonLayout->addWidget(button);
296
    connect(button, SIGNAL(clicked()), this, SLOT(spectrogramModeSelected()));
297
    m_modeButtons[SpectrogramMode] = button;
298
    m_modeLayerNames[SpectrogramMode] = "Spectrogram"; // not to be translated
299
    m_modeDisplayOrder.push_back(SpectrogramMode);
300

    
301
    button = new QPushButton;
302
    button->setIcon(il.load("values"));
303
    button->setText(tr("Sung pitch"));
304
    button->setCheckable(true);
305
    button->setChecked(false);
306
    button->setFixedHeight(bottomButtonHeight);
307
    bg->addButton(button);
308
    buttonLayout->addWidget(button);
309
    connect(button, SIGNAL(clicked()), this, SLOT(pitchModeSelected()));
310
    m_modeButtons[PitchMode] = button;
311
    m_modeLayerNames[PitchMode] = "Pitch"; // not to be translated
312
    m_modeDisplayOrder.push_back(PitchMode);
313

    
314
    button = new QPushButton;
315
    button->setIcon(il.load("colour3d"));
316
    button->setText(tr("Key"));
317
    button->setCheckable(true);
318
    button->setChecked(false);
319
    button->setFixedHeight(bottomButtonHeight);
320
    bg->addButton(button);
321
    buttonLayout->addWidget(button);
322
    connect(button, SIGNAL(clicked()), this, SLOT(keyModeSelected()));
323
    m_modeButtons[KeyMode] = button;
324
    m_modeLayerNames[KeyMode] = "Key"; // not to be translated
325
    m_modeDisplayOrder.push_back(KeyMode);
326

    
327
    button = new QPushButton;
328
    button->setIcon(il.load("colour3d"));
329
    button->setText(tr("Stereo azimuth"));
330
    button->setCheckable(true);
331
    button->setChecked(false);
332
    button->setFixedHeight(bottomButtonHeight);
333
    bg->addButton(button);
334
    buttonLayout->addWidget(button);
335
    connect(button, SIGNAL(clicked()), this, SLOT(azimuthModeSelected()));
336
    m_modeButtons[AzimuthMode] = button;
337
    m_modeLayerNames[AzimuthMode] = "Azimuth"; // not to be translated
338
    m_modeDisplayOrder.push_back(AzimuthMode);
339

    
340
    m_playSpeed = new AudioDial(bottomFrame);
341
    m_playSpeed->setMinimum(0);
342
    m_playSpeed->setMaximum(120);
343
    m_playSpeed->setValue(60);
344
    m_playSpeed->setFixedWidth(int(bottomElementHeight * 0.9));
345
    m_playSpeed->setFixedHeight(int(bottomElementHeight * 0.9));
346
    m_playSpeed->setNotchesVisible(true);
347
    m_playSpeed->setPageStep(10);
348
    m_playSpeed->setObjectName(tr("Playback Speed"));
349
    m_playSpeed->setRangeMapper(new PlaySpeedRangeMapper);
350
    m_playSpeed->setDefaultValue(60);
351
    m_playSpeed->setShowToolTip(true);
352
    connect(m_playSpeed, SIGNAL(valueChanged(int)),
353
            this, SLOT(playSpeedChanged(int)));
354
    connect(m_playSpeed, SIGNAL(mouseEntered()), this, SLOT(mouseEnteredWidget()));
355
    connect(m_playSpeed, SIGNAL(mouseLeft()), this, SLOT(mouseLeftWidget()));
356

    
357
    m_mainLevelPan = new LevelPanToolButton(bottomFrame);
358
    connect(m_mainLevelPan, SIGNAL(mouseEntered()), this, SLOT(mouseEnteredWidget()));
359
    connect(m_mainLevelPan, SIGNAL(mouseLeft()), this, SLOT(mouseLeftWidget()));
360
    m_mainLevelPan->setFixedHeight(bottomElementHeight);
361
    m_mainLevelPan->setFixedWidth(bottomElementHeight);
362
    m_mainLevelPan->setImageSize((bottomElementHeight * 3) / 4);
363
    m_mainLevelPan->setBigImageSize(bottomElementHeight * 3);
364

    
365
    int spacing = m_viewManager->scalePixelSize(4);
366

    
367
    bottomLayout->setSpacing(spacing);
368
    bottomLayout->setMargin(spacing);
369
    bottomLayout->addWidget(buttonFrame, 0, 0);
370
    bottomLayout->setColumnStretch(1, 10);
371
    bottomLayout->addWidget(m_playSpeed, 0, 2);
372
    bottomLayout->addWidget(m_mainLevelPan, 0, 3);
373
    bottomFrame->setLayout(bottomLayout);
374
    
375
    mainLayout->setSpacing(spacing);
376
    mainLayout->setMargin(spacing);
377
    mainLayout->addWidget(m_mainScroll, 0, 0);
378
    mainLayout->addWidget(bottomFrame, 1, 0);
379
    mainFrame->setLayout(mainLayout);
380

    
381
    setupMenus();
382
    setupToolbars();
383
    setupHelpMenu();
384

    
385
    statusBar()->hide();
386

    
387
    setIconsVisibleInMenus(false);
388
    finaliseMenus();
389

    
390
    NetworkPermissionTester tester;
391
    bool networkPermission = tester.havePermission();
392
    if (networkPermission) {
393
        m_versionTester = new VersionTester
394
            ("sonicvisualiser.org", "latest-vect-version.txt", VECT_VERSION);
395
        connect(m_versionTester, SIGNAL(newerVersionAvailable(QString)),
396
                this, SLOT(newerVersionAvailable(QString)));
397
    } else {
398
        m_versionTester = 0;
399
    }
400

    
401
    reopenLastSession();
402

    
403
    QTimer::singleShot(400, this, SLOT(introDialog()));
404
                       
405
//    QTimer::singleShot(500, this, SLOT(betaReleaseWarning()));
406
}
407

    
408
MainWindow::~MainWindow()
409
{
410
    delete m_keyReference;
411
    delete m_preferencesDialog;
412
    delete m_layerTreeView;
413
    Profiles::getInstance()->dump();
414
}
415

    
416
void
417
MainWindow::setupMenus()
418
{
419
    if (!m_mainMenusCreated) {
420

    
421
#ifdef Q_OS_LINUX
422
        // In Ubuntu 14.04 the window's menu bar goes missing entirely
423
        // if the user is running any desktop environment other than Unity
424
        // (in which the faux single-menubar appears). The user has a
425
        // workaround, to remove the appmenu-qt5 package, but that is
426
        // awkward and the problem is so severe that it merits disabling
427
        // the system menubar integration altogether. Like this:
428
        menuBar()->setNativeMenuBar(false);
429
#endif
430
    }
431

    
432
    setupFileMenu();
433
    setupViewMenu();
434

    
435
    m_mainMenusCreated = true;
436
}
437

    
438
void
439
MainWindow::goFullScreen()
440
{
441
    if (!m_viewManager) return;
442

    
443
    if (m_viewManager->getZoomWheelsEnabled()) {
444
        // The wheels seem to end up in the wrong place in full-screen mode
445
        toggleZoomWheels();
446
    }
447

    
448
    QWidget *ps = m_mainScroll->takeWidget();
449
    ps->setParent(0);
450

    
451
    QShortcut *sc;
452

    
453
    sc = new QShortcut(QKeySequence("Esc"), ps);
454
    connect(sc, SIGNAL(activated()), this, SLOT(endFullScreen()));
455

    
456
    sc = new QShortcut(QKeySequence("F11"), ps);
457
    connect(sc, SIGNAL(activated()), this, SLOT(endFullScreen()));
458

    
459
    QAction *acts[] = {
460
        m_playAction, m_zoomInAction, m_zoomOutAction, m_zoomFitAction,
461
        m_scrollLeftAction, m_scrollRightAction,
462
        m_selectPreviousPaneAction, m_selectNextPaneAction,
463
        m_selectPreviousDisplayModeAction, m_selectNextDisplayModeAction
464
    };
465

    
466
    for (int i = 0; i < int(sizeof(acts)/sizeof(acts[0])); ++i) {
467
        sc = new QShortcut(acts[i]->shortcut(), ps);
468
        connect(sc, SIGNAL(activated()), acts[i], SLOT(trigger()));
469
    }
470

    
471
    ps->showFullScreen();
472
}
473

    
474
void
475
MainWindow::endFullScreen()
476
{
477
    // these were only created in goFullScreen:
478
    QObjectList cl = m_paneStack->children();
479
    foreach (QObject *o, cl) {
480
        QShortcut *sc = qobject_cast<QShortcut *>(o);
481
        if (sc) delete sc;
482
    }
483

    
484
    m_paneStack->showNormal();
485
    m_mainScroll->setWidget(m_paneStack);
486
}
487

    
488
void
489
MainWindow::setupFileMenu()
490
{
491
    if (m_mainMenusCreated) return;
492

    
493
    QMenu *menu = menuBar()->addMenu(tr("&File"));
494
    menu->setTearOffEnabled(false);
495
    QToolBar *toolbar = addToolBar(tr("File Toolbar"));
496

    
497
    m_keyReference->setCategory(tr("File and Session Management"));
498

    
499
    IconLoader il;
500

    
501
    QIcon icon = il.load("filenew");
502
    QAction *action = new QAction(icon, tr("&Clear Session"), this);
503
    action->setShortcut(tr("Ctrl+N"));
504
    action->setStatusTip(tr("Abandon the current session and start a new one"));
505
    connect(action, SIGNAL(triggered()), this, SLOT(newSession()));
506
    m_keyReference->registerShortcut(action);
507
    menu->addAction(action);
508
    toolbar->addAction(action);
509

    
510
    icon = il.load("fileopen");
511
    action = new QAction(icon, tr("&Add Files..."), this);
512
    action->setShortcut(tr("Ctrl+O"));
513
    action->setStatusTip(tr("Add one or more audio files"));
514
    connect(action, SIGNAL(triggered()), this, SLOT(openFiles()));
515
    m_keyReference->registerShortcut(action);
516
    menu->addAction(action);
517
    toolbar->addAction(action);
518

    
519
    action = new QAction(tr("Add Lo&cation..."), this);
520
    action->setShortcut(tr("Ctrl+Shift+O"));
521
    action->setStatusTip(tr("Add a file from a remote URL"));
522
    connect(action, SIGNAL(triggered()), this, SLOT(openLocation()));
523
    m_keyReference->registerShortcut(action);
524
    menu->addAction(action);
525

    
526
    menu->addSeparator();
527
    
528
    action = new QAction(tr("Browse Recorded Audio"), this);
529
    action->setStatusTip(tr("Open the Recorded Audio folder in the system file browser"));
530
    connect(action, SIGNAL(triggered()), this, SLOT(browseRecordedAudio()));
531
    menu->addAction(action);
532

    
533
    menu->addSeparator();
534

    
535
    m_recentSessionsMenu = menu->addMenu(tr("&Recent Sessions"));
536
    m_recentSessionsMenu->setTearOffEnabled(false);
537
    setupRecentSessionsMenu();
538
    connect(&m_recentSessions, SIGNAL(recentChanged()),
539
            this, SLOT(setupRecentSessionsMenu()));
540
    
541
    /*
542
    menu->addSeparator();
543
    action = new QAction(tr("&Preferences..."), this);
544
    action->setStatusTip(tr("Adjust the application preferences"));
545
    connect(action, SIGNAL(triggered()), this, SLOT(preferences()));
546
    menu->addAction(action);
547
    */
548
    
549
    menu->addSeparator();
550
    action = new QAction(il.load("exit"), tr("&Quit"), this);
551
    action->setShortcut(tr("Ctrl+Q"));
552
    action->setStatusTip(tr("Exit Sonic Lineup"));
553
    connect(action, SIGNAL(triggered()), this, SLOT(close()));
554
    m_keyReference->registerShortcut(action);
555
    menu->addAction(action);
556
}
557

    
558
void
559
MainWindow::setupViewMenu()
560
{
561
    if (m_mainMenusCreated) return;
562

    
563
    IconLoader il;
564

    
565
    QAction *action = 0;
566

    
567
    m_keyReference->setCategory(tr("Panning and Navigation"));
568

    
569
    QMenu *menu = menuBar()->addMenu(tr("&View"));
570
    menu->setTearOffEnabled(false);
571
    m_scrollLeftAction = new QAction(tr("Scroll &Left"), this);
572
    m_scrollLeftAction->setShortcut(tr("Left"));
573
    m_scrollLeftAction->setStatusTip(tr("Scroll the current pane to the left"));
574
    connect(m_scrollLeftAction, SIGNAL(triggered()), this, SLOT(scrollLeft()));
575
    connect(this, SIGNAL(canScroll(bool)), m_scrollLeftAction, SLOT(setEnabled(bool)));
576
    m_keyReference->registerShortcut(m_scrollLeftAction);
577
    menu->addAction(m_scrollLeftAction);
578
        
579
    m_scrollRightAction = new QAction(tr("Scroll &Right"), this);
580
    m_scrollRightAction->setShortcut(tr("Right"));
581
    m_scrollRightAction->setStatusTip(tr("Scroll the current pane to the right"));
582
    connect(m_scrollRightAction, SIGNAL(triggered()), this, SLOT(scrollRight()));
583
    connect(this, SIGNAL(canScroll(bool)), m_scrollRightAction, SLOT(setEnabled(bool)));
584
    m_keyReference->registerShortcut(m_scrollRightAction);
585
    menu->addAction(m_scrollRightAction);
586
        
587
    action = new QAction(tr("&Jump Left"), this);
588
    action->setShortcut(tr("Ctrl+Left"));
589
    action->setStatusTip(tr("Scroll the current pane a big step to the left"));
590
    connect(action, SIGNAL(triggered()), this, SLOT(jumpLeft()));
591
    connect(this, SIGNAL(canScroll(bool)), action, SLOT(setEnabled(bool)));
592
    m_keyReference->registerShortcut(action);
593
    menu->addAction(action);
594
        
595
    action = new QAction(tr("J&ump Right"), this);
596
    action->setShortcut(tr("Ctrl+Right"));
597
    action->setStatusTip(tr("Scroll the current pane a big step to the right"));
598
    connect(action, SIGNAL(triggered()), this, SLOT(jumpRight()));
599
    connect(this, SIGNAL(canScroll(bool)), action, SLOT(setEnabled(bool)));
600
    m_keyReference->registerShortcut(action);
601
    menu->addAction(action);
602

    
603
    menu->addSeparator();
604

    
605
    action = new QAction(tr("Switch to Previous Pane"), this);
606
    action->setShortcut(tr("["));
607
    action->setStatusTip(tr("Make the next pane up in the pane stack current"));
608
    connect(action, SIGNAL(triggered()), this, SLOT(previousPane()));
609
    connect(this, SIGNAL(canSelectPreviousPane(bool)), action, SLOT(setEnabled(bool)));
610
    m_keyReference->registerShortcut(action);
611
    menu->addAction(action);
612
    m_selectPreviousPaneAction = action;
613

    
614
    action = new QAction(tr("Switch to Next Pane"), this);
615
    action->setShortcut(tr("]"));
616
    action->setStatusTip(tr("Make the next pane down in the pane stack current"));
617
    connect(action, SIGNAL(triggered()), this, SLOT(nextPane()));
618
    connect(this, SIGNAL(canSelectNextPane(bool)), action, SLOT(setEnabled(bool)));
619
    m_keyReference->registerShortcut(action);
620
    menu->addAction(action);
621
    m_selectNextPaneAction = action;
622

    
623
    menu->addSeparator();
624

    
625
    action = new QAction(tr("Switch to Previous View Mode"), this);
626
    action->setShortcut(tr("{"));
627
    action->setStatusTip(tr("Make the next view mode current"));
628
    connect(action, SIGNAL(triggered()), this, SLOT(previousDisplayMode()));
629
    connect(this, SIGNAL(canSelectPreviousDisplayMode(bool)), action, SLOT(setEnabled(bool)));
630
    m_keyReference->registerShortcut(action);
631
    menu->addAction(action);
632
    m_selectPreviousDisplayModeAction = action;
633

    
634
    action = new QAction(tr("Switch to Next View Mode"), this);
635
    action->setShortcut(tr("}"));
636
    action->setStatusTip(tr("Make the next view mode current"));
637
    connect(action, SIGNAL(triggered()), this, SLOT(nextDisplayMode()));
638
    connect(this, SIGNAL(canSelectNextDisplayMode(bool)), action, SLOT(setEnabled(bool)));
639
    m_keyReference->registerShortcut(action);
640
    menu->addAction(action);
641
    m_selectNextDisplayModeAction = action;
642

    
643
    menu->addSeparator();
644

    
645
    m_keyReference->setCategory(tr("Zoom"));
646

    
647
    m_zoomInAction = new QAction(il.load("zoom-in"),
648
                                 tr("Zoom &In"), this);
649
    m_zoomInAction->setShortcut(tr("Up"));
650
    m_zoomInAction->setStatusTip(tr("Increase the zoom level"));
651
    connect(m_zoomInAction, SIGNAL(triggered()), this, SLOT(zoomIn()));
652
    connect(this, SIGNAL(canZoom(bool)), m_zoomInAction, SLOT(setEnabled(bool)));
653
    m_keyReference->registerShortcut(m_zoomInAction);
654
    menu->addAction(m_zoomInAction);
655
        
656
    m_zoomOutAction = new QAction(il.load("zoom-out"),
657
                                  tr("Zoom &Out"), this);
658
    m_zoomOutAction->setShortcut(tr("Down"));
659
    m_zoomOutAction->setStatusTip(tr("Decrease the zoom level"));
660
    connect(m_zoomOutAction, SIGNAL(triggered()), this, SLOT(zoomOut()));
661
    connect(this, SIGNAL(canZoom(bool)), m_zoomOutAction, SLOT(setEnabled(bool)));
662
    m_keyReference->registerShortcut(m_zoomOutAction);
663
    menu->addAction(m_zoomOutAction);
664
        
665
    action = new QAction(tr("Restore &Default Zoom"), this);
666
    action->setStatusTip(tr("Restore the zoom level to the default"));
667
    connect(action, SIGNAL(triggered()), this, SLOT(zoomDefault()));
668
    connect(this, SIGNAL(canZoom(bool)), action, SLOT(setEnabled(bool)));
669
    menu->addAction(action);
670

    
671
    m_zoomFitAction = new QAction(il.load("zoom-fit"),
672
                                  tr("Zoom to &Fit"), this);
673
    m_zoomFitAction->setShortcut(tr("F"));
674
    m_zoomFitAction->setStatusTip(tr("Zoom to show the whole file"));
675
    connect(m_zoomFitAction, SIGNAL(triggered()), this, SLOT(zoomToFit()));
676
    connect(this, SIGNAL(canZoom(bool)), m_zoomFitAction, SLOT(setEnabled(bool)));
677
    m_keyReference->registerShortcut(m_zoomFitAction);
678
    menu->addAction(m_zoomFitAction);
679

    
680
    menu->addSeparator();
681

    
682
    m_keyReference->setCategory(tr("Display Features"));
683

    
684
    action = new QAction(tr("Show &Centre Line"), this);
685
    action->setShortcut(tr("'"));
686
    action->setStatusTip(tr("Show or hide the centre line"));
687
    connect(action, SIGNAL(triggered()), this, SLOT(toggleCentreLine()));
688
    action->setCheckable(true);
689
    action->setChecked(true);
690
    m_keyReference->registerShortcut(action);
691
    menu->addAction(action);
692

    
693
    action = new QAction(tr("Show Salient Feature Layers"), this);
694
    action->setShortcut(tr("#"));
695
    action->setStatusTip(tr("Show or hide all salient-feature layers"));
696
    connect(action, SIGNAL(triggered()), this, SLOT(toggleSalientFeatures()));
697
    action->setCheckable(true);
698
    action->setChecked(true);
699
    m_keyReference->registerShortcut(action);
700
    menu->addAction(action);
701

    
702
    action = new QAction(tr("Show Vertical Scales"), this);
703
    action->setShortcut(tr("S"));
704
    action->setStatusTip(tr("Show or hide all vertical scales"));
705
    connect(action, SIGNAL(triggered()), this, SLOT(toggleVerticalScales()));
706
    action->setCheckable(true);
707
    action->setChecked(false);
708
    m_viewManager->setOverlayMode(ViewManager::NoOverlays);
709
    m_keyReference->registerShortcut(action);
710
    menu->addAction(action);
711

    
712
    // We need this separator even if not adding the full-screen
713
    // option ourselves, as the Mac automatic full-screen entry
714
    // doesn't include a separator first
715
    menu->addSeparator();
716

    
717
#ifndef Q_OS_MAC
718
    // Only on non-Mac platforms -- on the Mac this interacts very
719
    // badly with the "native" full-screen mode
720
    action = new QAction(tr("Go Full-Screen"), this);
721
    action->setShortcut(tr("F11"));
722
    action->setStatusTip(tr("Expand the pane area to the whole screen"));
723
    connect(action, SIGNAL(triggered()), this, SLOT(goFullScreen()));
724
    m_keyReference->registerShortcut(action);
725
    menu->addAction(action);
726
#endif
727
}
728

    
729
void
730
MainWindow::setupHelpMenu()
731
{
732
    QMenu *menu = menuBar()->addMenu(tr("&Help"));
733
    menu->setTearOffEnabled(false);
734
    
735
    m_keyReference->setCategory(tr("Help"));
736

    
737
    IconLoader il;
738

    
739
    QAction *action = new QAction(il.load("help"),
740
                                  tr("&Help Reference"), this); 
741
    action->setShortcut(tr("F1"));
742
    action->setStatusTip(tr("Open the reference manual")); 
743
    connect(action, SIGNAL(triggered()), this, SLOT(help()));
744
    m_keyReference->registerShortcut(action);
745
    menu->addAction(action);
746

    
747
    action = new QAction(tr("&Key and Mouse Reference"), this);
748
    action->setShortcut(tr("F2"));
749
    action->setStatusTip(tr("Open a window showing the keystrokes you can use"));
750
    connect(action, SIGNAL(triggered()), this, SLOT(keyReference()));
751
    m_keyReference->registerShortcut(action);
752
    menu->addAction(action);
753

    
754
    QString name = QApplication::applicationName();
755
    
756
    action = new QAction(tr("What's &New In This Release?"), this); 
757
    action->setStatusTip(tr("List the changes in this release (and every previous release) of %1").arg(name)); 
758
    connect(action, SIGNAL(triggered()), this, SLOT(whatsNew()));
759
    menu->addAction(action);
760
    
761
    action = new QAction(tr("&About %1").arg(name), this); 
762
    action->setStatusTip(tr("Show information about %1").arg(name)); 
763
    connect(action, SIGNAL(triggered()), this, SLOT(about()));
764
    menu->addAction(action);
765
}
766

    
767
void
768
MainWindow::setupRecentSessionsMenu()
769
{
770
    m_recentSessionsMenu->clear();
771
    vector<pair<QString, QString>> sessions = m_recentSessions.getRecentEntries();
772
    for (size_t i = 0; i < sessions.size(); ++i) {
773
        QString path = sessions[i].first;
774
        QString label = sessions[i].second;
775
        if (label == "") label = path;
776
        QAction *action = new QAction(label, this);
777
        action->setObjectName(path);
778
        connect(action, SIGNAL(triggered()), this, SLOT(openRecentSession()));
779
        if (i == 0) {
780
            action->setShortcut(tr("Ctrl+R"));
781
            m_keyReference->registerShortcut
782
                (tr("Re-open"),
783
                 action->shortcut().toString(),
784
                 tr("Re-open the current or most recently opened session"));
785
        }
786
        m_recentSessionsMenu->addAction(action);
787
    }
788
}
789

    
790
void
791
MainWindow::setupToolbars()
792
{
793
    m_keyReference->setCategory(tr("Playback and Transport Controls"));
794

    
795
    IconLoader il;
796

    
797
    QMenu *menu = m_playbackMenu = menuBar()->addMenu(tr("Play&back"));
798
    menu->setTearOffEnabled(false);
799

    
800
    QToolBar *toolbar = addToolBar(tr("Playback Toolbar"));
801

    
802
    QAction *rwdStartAction = toolbar->addAction(il.load("rewind-start"),
803
                                                 tr("Rewind to Start"));
804
    rwdStartAction->setShortcut(tr("Home"));
805
    rwdStartAction->setStatusTip(tr("Rewind to the start"));
806
    connect(rwdStartAction, SIGNAL(triggered()), this, SLOT(rewindStart()));
807
    connect(this, SIGNAL(canPlay(bool)), rwdStartAction, SLOT(setEnabled(bool)));
808

    
809
    QAction *m_rwdAction = toolbar->addAction(il.load("rewind"),
810
                                              tr("Rewind"));
811
    m_rwdAction->setShortcut(tr("PgUp"));
812
    m_rwdAction->setStatusTip(tr("Rewind to the previous time instant or time ruler notch"));
813
    connect(m_rwdAction, SIGNAL(triggered()), this, SLOT(rewind()));
814
    connect(this, SIGNAL(canRewind(bool)), m_rwdAction, SLOT(setEnabled(bool)));
815

    
816
    m_playAction = toolbar->addAction(il.load("playpause"),
817
                                      tr("Play / Pause"));
818
    m_playAction->setCheckable(true);
819
    m_playAction->setShortcut(tr("Space"));
820
    m_playAction->setStatusTip(tr("Start or stop playback from the current position"));
821
    connect(m_playAction, SIGNAL(triggered()), this, SLOT(play()));
822
    connect(m_playSource, SIGNAL(playStatusChanged(bool)),
823
            m_playAction, SLOT(setChecked(bool)));
824
    connect(this, SIGNAL(canPlay(bool)), m_playAction, SLOT(setEnabled(bool)));
825

    
826
    m_ffwdAction = toolbar->addAction(il.load("ffwd"),
827
                                      tr("Fast Forward"));
828
    m_ffwdAction->setShortcut(tr("PgDown"));
829
    m_ffwdAction->setStatusTip(tr("Fast-forward to the next time instant or time ruler notch"));
830
    connect(m_ffwdAction, SIGNAL(triggered()), this, SLOT(ffwd()));
831
    connect(this, SIGNAL(canFfwd(bool)), m_ffwdAction, SLOT(setEnabled(bool)));
832

    
833
    QAction *ffwdEndAction = toolbar->addAction(il.load("ffwd-end"),
834
                                                tr("Fast Forward to End"));
835
    ffwdEndAction->setShortcut(tr("End"));
836
    ffwdEndAction->setStatusTip(tr("Fast-forward to the end"));
837
    connect(ffwdEndAction, SIGNAL(triggered()), this, SLOT(ffwdEnd()));
838
    connect(this, SIGNAL(canPlay(bool)), ffwdEndAction, SLOT(setEnabled(bool)));
839

    
840
    QAction *recordAction = toolbar->addAction(il.load("record"),
841
                                               tr("Record"));
842
    recordAction->setCheckable(true);
843
    recordAction->setShortcut(tr("Ctrl+Space"));
844
    recordAction->setStatusTip(tr("Record a new audio file"));
845
    connect(recordAction, SIGNAL(triggered()), this, SLOT(record()));
846
    connect(m_recordTarget, SIGNAL(recordStatusChanged(bool)),
847
            recordAction, SLOT(setChecked(bool)));
848
    connect(this, SIGNAL(canRecord(bool)),
849
            recordAction, SLOT(setEnabled(bool)));
850

    
851
    m_keyReference->registerShortcut(m_playAction);
852
    m_keyReference->registerShortcut(m_rwdAction);
853
    m_keyReference->registerShortcut(m_ffwdAction);
854
    m_keyReference->registerShortcut(rwdStartAction);
855
    m_keyReference->registerShortcut(ffwdEndAction);
856
    m_keyReference->registerShortcut(recordAction);
857

    
858
    menu->addAction(m_playAction);
859
    menu->addSeparator();
860
    menu->addAction(m_rwdAction);
861
    menu->addAction(m_ffwdAction);
862
    menu->addSeparator();
863
    menu->addAction(rwdStartAction);
864
    menu->addAction(ffwdEndAction);
865
    menu->addSeparator();
866
    menu->addAction(recordAction);
867
    menu->addSeparator();
868

    
869
    QAction *fastAction = menu->addAction(tr("Speed Up"));
870
    fastAction->setShortcut(tr("Ctrl+PgUp"));
871
    fastAction->setStatusTip(tr("Time-stretch playback to speed it up without changing pitch"));
872
    connect(fastAction, SIGNAL(triggered()), this, SLOT(speedUpPlayback()));
873
    connect(this, SIGNAL(canSpeedUpPlayback(bool)), fastAction, SLOT(setEnabled(bool)));
874
    
875
    QAction *slowAction = menu->addAction(tr("Slow Down"));
876
    slowAction->setShortcut(tr("Ctrl+PgDown"));
877
    slowAction->setStatusTip(tr("Time-stretch playback to slow it down without changing pitch"));
878
    connect(slowAction, SIGNAL(triggered()), this, SLOT(slowDownPlayback()));
879
    connect(this, SIGNAL(canSlowDownPlayback(bool)), slowAction, SLOT(setEnabled(bool)));
880

    
881
    QAction *normalAction = menu->addAction(tr("Restore Normal Speed"));
882
    normalAction->setShortcut(tr("Ctrl+Home"));
883
    normalAction->setStatusTip(tr("Restore non-time-stretched playback"));
884
    connect(normalAction, SIGNAL(triggered()), this, SLOT(restoreNormalPlayback()));
885
    connect(this, SIGNAL(canChangePlaybackSpeed(bool)), normalAction, SLOT(setEnabled(bool)));
886

    
887
    m_keyReference->registerShortcut(fastAction);
888
    m_keyReference->registerShortcut(slowAction);
889
    m_keyReference->registerShortcut(normalAction);
890

    
891
    QAction *alAction = 0;
892
    alAction = toolbar->addAction(il.load("align"),
893
                                  tr("Align File Timelines"));
894
    alAction->setCheckable(true);
895
    alAction->setChecked(m_viewManager->getAlignMode());
896
    alAction->setStatusTip(tr("Treat multiple audio files as versions of the same work, and align their timelines"));
897
    connect(m_viewManager, SIGNAL(alignModeChanged(bool)),
898
            alAction, SLOT(setChecked(bool)));
899
    connect(alAction, SIGNAL(triggered()), this, SLOT(alignToggled()));
900

    
901
    QSettings settings;
902

    
903
    QAction *tdAction = 0;
904
    tdAction = new QAction(tr("Allow for Pitch Difference when Aligning"), this);
905
    tdAction->setCheckable(true);
906
    settings.beginGroup("Alignment");
907
    tdAction->setChecked(settings.value("align-pitch-aware", false).toBool());
908
    settings.endGroup();
909
    tdAction->setStatusTip(tr("Compare relative pitch content of audio files before aligning, in order to correctly align recordings of the same material at different tuning pitches"));
910
    connect(tdAction, SIGNAL(triggered()), this, SLOT(tuningDifferenceToggled()));
911

    
912
    menu->addSeparator();
913
    menu->addAction(alAction);
914
    menu->addAction(tdAction);
915

    
916
    Pane::registerShortcuts(*m_keyReference);
917
}
918

    
919
void
920
MainWindow::updateMenuStates()
921
{
922
    MainWindowBase::updateMenuStates();
923

    
924
    Pane *currentPane = 0;
925
    Layer *currentLayer = 0;
926

    
927
    if (m_paneStack) currentPane = m_paneStack->getCurrentPane();
928
    if (currentPane) currentLayer = currentPane->getSelectedLayer();
929

    
930
    bool haveCurrentPane =
931
        (currentPane != 0);
932
    bool haveCurrentLayer =
933
        (haveCurrentPane &&
934
         (currentLayer != 0));
935
    bool haveCurrentTimeInstantsLayer = 
936
        (haveCurrentLayer &&
937
         dynamic_cast<TimeInstantLayer *>(currentLayer));
938
    bool haveCurrentTimeValueLayer = 
939
        (haveCurrentLayer &&
940
         dynamic_cast<TimeValueLayer *>(currentLayer));
941

    
942
    emit canChangePlaybackSpeed(true);
943
    int v = m_playSpeed->value();
944
    emit canSpeedUpPlayback(v < m_playSpeed->maximum());
945
    emit canSlowDownPlayback(v > m_playSpeed->minimum());
946

    
947
    emit canSelectPreviousDisplayMode
948
        (!m_modeDisplayOrder.empty() &&
949
         (m_displayMode != m_modeDisplayOrder[0]));
950

    
951
    emit canSelectNextDisplayMode
952
        (!m_modeDisplayOrder.empty() &&
953
         (m_displayMode != m_modeDisplayOrder[m_modeDisplayOrder.size()-1]));
954

    
955
    if (m_ffwdAction && m_rwdAction) {
956
        if (haveCurrentTimeInstantsLayer) {
957
            m_ffwdAction->setText(tr("Fast Forward to Next Instant"));
958
            m_ffwdAction->setStatusTip(tr("Fast forward to the next time instant in the current layer"));
959
            m_rwdAction->setText(tr("Rewind to Previous Instant"));
960
            m_rwdAction->setStatusTip(tr("Rewind to the previous time instant in the current layer"));
961
        } else if (haveCurrentTimeValueLayer) {
962
            m_ffwdAction->setText(tr("Fast Forward to Next Point"));
963
            m_ffwdAction->setStatusTip(tr("Fast forward to the next point in the current layer"));
964
            m_rwdAction->setText(tr("Rewind to Previous Point"));
965
            m_rwdAction->setStatusTip(tr("Rewind to the previous point in the current layer"));
966
        } else {
967
            m_ffwdAction->setText(tr("Fast Forward"));
968
            m_ffwdAction->setStatusTip(tr("Fast forward"));
969
            m_rwdAction->setText(tr("Rewind"));
970
            m_rwdAction->setStatusTip(tr("Rewind"));
971
        }
972
    }
973
}
974

    
975
void
976
MainWindow::updateDescriptionLabel()
977
{
978
    // we don't actually have a description label
979
}
980

    
981
void
982
MainWindow::updateWindowTitle()
983
{
984
    QString title;
985

    
986
    QString sessionLabel = makeSessionLabel();
987
    
988
    if (sessionLabel != "") {
989
        title = tr("%1: %2")
990
            .arg(QApplication::applicationName())
991
            .arg(sessionLabel);
992
    } else {
993
        title = QApplication::applicationName();
994
    }
995
    
996
    setWindowTitle(title);
997
}
998

    
999
void
1000
MainWindow::documentModified()
1001
{
1002
    MainWindowBase::documentModified();
1003
}
1004

    
1005
void
1006
MainWindow::documentRestored()
1007
{
1008
    MainWindowBase::documentRestored();
1009
}
1010

    
1011
void
1012
MainWindow::selectMainPane()
1013
{
1014
    if (m_paneStack && m_paneStack->getPaneCount() > 0) {
1015
        m_paneStack->setCurrentPane(m_paneStack->getPane(0));
1016
    }
1017
}
1018

    
1019
void
1020
MainWindow::browseRecordedAudio()
1021
{
1022
    QString path = RecordDirectory::getRecordContainerDirectory();
1023
    if (path == "") path = RecordDirectory::getRecordDirectory();
1024
    if (path == "") return;
1025

    
1026
    openLocalFolder(path);
1027
}
1028

    
1029
void
1030
MainWindow::newSession()
1031
{
1032
    cerr << "MainWindow::newSession" << endl;
1033

    
1034
    closeSession();
1035
    createDocument();
1036
    
1037
    m_displayMode = OutlineWaveformMode;
1038
    for (auto &bp : m_modeButtons) {
1039
        bp.second->setChecked(false);
1040
    }
1041
    m_modeButtons[m_displayMode]->setChecked(true);
1042
    
1043
    // We need a pane, so that we have something to receive drop events
1044
    
1045
    Pane *pane = m_paneStack->addPane();
1046

    
1047
    connect(pane, SIGNAL(contextHelpChanged(const QString &)),
1048
            this, SLOT(contextHelpChanged(const QString &)));
1049

    
1050
    m_document->setAutoAlignment(m_viewManager->getAlignMode());
1051

    
1052
    CommandHistory::getInstance()->clear();
1053
    CommandHistory::getInstance()->documentSaved();
1054
    documentRestored();
1055
    updateMenuStates();
1056

    
1057
    // Record that the last (i.e. current, as of now) session is
1058
    // empty, so that if we exit now and re-start, we get an empty
1059
    // session as is proper instead of loading the last non-empty one
1060
    QSettings settings;
1061
    settings.beginGroup("MainWindow");
1062
    settings.setValue("lastsession", "");
1063
    settings.endGroup();
1064
}
1065

    
1066
void
1067
MainWindow::closeSession()
1068
{
1069
    checkpointSession();
1070
    if (m_sessionState != SessionLoading) {
1071
        m_sessionFile = "";
1072
        m_sessionState = NoSession;
1073
    }
1074

    
1075
    while (m_paneStack->getPaneCount() > 0) {
1076

    
1077
        Pane *pane = m_paneStack->getPane(m_paneStack->getPaneCount() - 1);
1078

    
1079
        while (pane->getLayerCount() > 0) {
1080
            m_document->removeLayerFromView
1081
                (pane, pane->getLayer(pane->getLayerCount() - 1));
1082
        }
1083

    
1084
        m_paneStack->deletePane(pane);
1085
    }
1086

    
1087
    while (m_paneStack->getHiddenPaneCount() > 0) {
1088

    
1089
        Pane *pane = m_paneStack->getHiddenPane
1090
            (m_paneStack->getHiddenPaneCount() - 1);
1091

    
1092
        while (pane->getLayerCount() > 0) {
1093
            m_document->removeLayerFromView
1094
                (pane, pane->getLayer(pane->getLayerCount() - 1));
1095
        }
1096

    
1097
        m_paneStack->deletePane(pane);
1098
    }
1099

    
1100
    delete m_document;
1101
    m_document = 0;
1102
    m_viewManager->clearSelections();
1103
    m_timeRulerLayer = 0; // document owned this
1104

    
1105
    setWindowTitle(tr("Sonic Lineup"));
1106

    
1107
    CommandHistory::getInstance()->clear();
1108
    CommandHistory::getInstance()->documentSaved();
1109
    documentRestored();
1110
}
1111

    
1112
void
1113
MainWindow::openFiles()
1114
{
1115
    QString orig = m_audioFile;
1116
    if (orig == "") orig = ".";
1117
    else orig = QFileInfo(orig).absoluteDir().canonicalPath();
1118

    
1119
    FileFinder *ff = FileFinder::getInstance();
1120

    
1121
    QStringList paths = ff->getOpenFileNames(FileFinder::AudioFile,
1122
                                             m_audioFile);
1123

    
1124
    if (paths.empty()) return;
1125
    
1126
    m_sessionState = SessionActive;
1127
    
1128
    for (QString path: paths) {
1129

    
1130
        FileOpenStatus status = FileOpenFailed;
1131
        
1132
        FileSource source(path);
1133
        if (source.isAvailable()) {
1134
            source.waitForData();
1135

    
1136
            try {
1137
                status = openAudio(source, CreateAdditionalModel);
1138
            } catch (const InsufficientDiscSpace &e) {
1139
                SVCERR << "MainWindowBase: Caught InsufficientDiscSpace in file open" << endl;
1140
                QMessageBox::critical
1141
                    (this, tr("Not enough disc space"),
1142
                     tr("<b>Not enough disc space</b><p>There doesn't appear to be enough spare disc space to accommodate any necessary temporary files.</p><p>Please clear some space and try again.</p>").arg(e.what()));
1143
                return;
1144
            }
1145
        }
1146
        
1147
        if (status != FileOpenSucceeded) {
1148
            QMessageBox::critical(this, tr("Failed to open file"),
1149
                                  tr("<b>File open failed</b><p>File \"%1\" could not be opened").arg(path));
1150
        } else {
1151
            configureNewPane(m_paneStack->getCurrentPane());
1152
        }
1153
    }
1154
}
1155

    
1156
void
1157
MainWindow::openLocation()
1158
{
1159
    QSettings settings;
1160
    settings.beginGroup("MainWindow");
1161
    QString lastLocation = settings.value("lastremote", "").toString();
1162

    
1163
    bool ok = false;
1164
    QString text = QInputDialog::getText
1165
        (this, tr("Open Location"),
1166
         tr("Please enter the URL of the location to open:"),
1167
         QLineEdit::Normal, lastLocation, &ok);
1168

    
1169
    if (!ok) return;
1170
    if (text.isEmpty()) return;
1171

    
1172
    m_sessionState = SessionActive;
1173

    
1174
    FileOpenStatus status = FileOpenFailed;
1175
        
1176
    FileSource source(text);
1177
    if (source.isAvailable()) {
1178
        source.waitForData();
1179

    
1180
        try {
1181
            status = openAudio(source, CreateAdditionalModel);
1182
        } catch (const InsufficientDiscSpace &e) {
1183
            SVCERR << "MainWindowBase: Caught InsufficientDiscSpace in file open" << endl;
1184
            QMessageBox::critical
1185
                (this, tr("Not enough disc space"),
1186
                 tr("<b>Not enough disc space</b><p>There doesn't appear to be enough spare disc space to accommodate any necessary temporary files.</p><p>Please clear some space and try again.</p>").arg(e.what()));
1187
            return;
1188
        }
1189
    }
1190
        
1191
    if (status != FileOpenSucceeded) {
1192
        QMessageBox::critical(this, tr("Failed to open location"),
1193
                              tr("<b>Open failed</b><p>URL \"%1\" could not be opened").arg(text));
1194
    } else {
1195
        configureNewPane(m_paneStack->getCurrentPane());
1196
        settings.setValue("lastremote", text);
1197
    }
1198
}
1199

    
1200
void
1201
MainWindow::openRecentSession()
1202
{
1203
    QObject *obj = sender();
1204
    QAction *action = dynamic_cast<QAction *>(obj);
1205
    
1206
    if (!action) {
1207
        cerr << "WARNING: MainWindow::openRecentSession: sender is not an action"
1208
                  << endl;
1209
        return;
1210
    }
1211

    
1212
    QString path = action->objectName();
1213
    
1214
    if (path == "") {
1215
        cerr << "WARNING: MainWindow::openRecentSession: action incorrectly named"
1216
             << endl;
1217
        return;
1218
    }
1219

    
1220
    openSmallSessionFile(path);
1221
}
1222

    
1223
void
1224
MainWindow::reopenLastSession()
1225
{
1226
    QSettings settings;
1227
    settings.beginGroup("MainWindow");
1228
    QString lastSession = settings.value("lastsession", "").toString();
1229
    settings.endGroup();
1230

    
1231
    if (lastSession != "") {
1232
        openSmallSessionFile(lastSession);
1233
    }
1234

    
1235
    if (m_sessionState == NoSession) {
1236
        newSession(); // to ensure we have a drop target
1237
    }
1238
}
1239

    
1240
void
1241
MainWindow::openSmallSessionFile(QString path)
1242
{
1243
    m_sessionFile = path;
1244
    m_sessionState = SessionLoading;
1245

    
1246
    SVDEBUG << "MainWindow::openSmallSessionFile: m_sessionFile is now "
1247
            << m_sessionFile << endl;
1248

    
1249
    try {
1250
        SmallSession session = SmallSession::load(path);
1251
        openSmallSession(session);
1252
    } catch (const std::runtime_error &e) {
1253
        QMessageBox::critical
1254
            (this, tr("Failed to reload session"),
1255
             tr("<b>Open failed</b>"
1256
                "<p>Session file \"%1\" could not be opened: %2</p>")
1257
             .arg(path).arg(e.what()));
1258
        m_sessionFile = "";
1259
        m_sessionState = NoSession;
1260
    }
1261
}
1262

    
1263
void
1264
MainWindow::openSmallSession(const SmallSession &session)
1265
{
1266
    QString errorText;
1267
    FileOpenStatus status;
1268
    
1269
    closeSession();
1270
    createDocument();
1271

    
1272
    status = openPath(session.mainFile, ReplaceMainModel);
1273

    
1274
    if (status != FileOpenSucceeded) {
1275
        errorText = tr("Unable to open main audio file %1")
1276
            .arg(session.mainFile);
1277
        goto failed;
1278
    }
1279
    
1280
    configureNewPane(m_paneStack->getCurrentPane());
1281
        
1282
    for (QString path: session.additionalFiles) {
1283

    
1284
        QApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
1285
        
1286
        status = openPath(path, CreateAdditionalModel);
1287

    
1288
        if (status != FileOpenSucceeded) {
1289
            errorText = tr("Unable to open audio file %1").arg(path);
1290
            goto failed;
1291
        }
1292

    
1293
        configureNewPane(m_paneStack->getCurrentPane());
1294
    }
1295

    
1296
    m_documentModified = false;
1297
    m_sessionState = SessionActive;
1298
    return;
1299

    
1300
failed:
1301
    QMessageBox::critical(this, tr("Failed to load session"),
1302
                          tr("<b>Open failed</b><p>Session could not be opened: %2</p>").arg(errorText));
1303
    m_sessionFile = "";
1304
    m_sessionState = NoSession;
1305
}
1306

    
1307
bool
1308
MainWindow::selectExistingLayerForMode(Pane *pane,
1309
                                       QString modeName,
1310
                                       ModelId *createFrom)
1311
{
1312
    // Search the given pane for any layer whose object name matches
1313
    // modeName, showing it if it exists, and hiding all other layers
1314
    // (except for time-instants layers, which are assumed to be used
1315
    // for persistent segment display and are left unmodified).
1316

    
1317
    // In the case where no such layer is found and false is returned,
1318
    // then if the return parameter createFrom is non-null, the value
1319
    // it points to will be set to a pointer to the model from which
1320
    // such a layer should be constructed.
1321

    
1322
    ModelId modelId;
1323

    
1324
    bool have = false;
1325

    
1326
    for (int i = 0; i < pane->getLayerCount(); ++i) {
1327
        
1328
        Layer *layer = pane->getLayer(i);
1329
        if (!layer || qobject_cast<TimeInstantLayer *>(layer)) {
1330
            continue;
1331
        }
1332

    
1333
        modelId = layer->getModel();
1334
        auto sourceId = layer->getSourceModel();
1335
        if (!sourceId.isNone()) modelId = sourceId;
1336
        
1337
        QString ln = layer->objectName();
1338
        if (ln == modeName) {
1339
            layer->showLayer(pane, true);
1340
            have = true;
1341
        } else {
1342
            layer->showLayer(pane, false);
1343
        }
1344
    }
1345
    
1346
    if (have) return true;
1347

    
1348
    if (createFrom) {
1349
        *createFrom = modelId;
1350
    }
1351
    return false;
1352
}
1353

    
1354
void
1355
MainWindow::addSalientFeatureLayer(Pane *pane, ModelId modelId)
1356
{
1357
    //!!! what if there already is one? could have changed the main
1358
    //!!! model for example
1359

    
1360
    auto model = ModelById::getAs<WaveFileModel>(modelId);
1361
    if (!model) {
1362
        cerr << "MainWindow::addSalientFeatureLayer: No model" << endl;
1363
        return;
1364
    }
1365
    
1366
    TransformFactory *tf = TransformFactory::getInstance();
1367
    if (!tf) {
1368
        cerr << "Failed to locate a transform factory!" << endl;
1369
        return;
1370
    }
1371
    
1372
    TransformId id = "vamp:nnls-chroma:chordino:simplechord";
1373
    if (!tf->haveTransform(id)) {
1374
        cerr << "No plugin available for salient feature layer; transform is: "
1375
             << id << endl;
1376
        return;
1377
    }
1378

    
1379
    m_salientCalculating = true;
1380

    
1381
    Transform transform = tf->getDefaultTransformFor
1382
        (id, model->getSampleRate());
1383

    
1384
    ModelTransformer::Input input(modelId, -1);
1385

    
1386
    Layer *newLayer = m_document->createDerivedLayer(transform, modelId);
1387

    
1388
    if (newLayer) {
1389

    
1390
        TimeInstantLayer *til = qobject_cast<TimeInstantLayer *>(newLayer);
1391
        if (til) {
1392
            til->setPlotStyle(TimeInstantLayer::PlotInstants);
1393
            til->setBaseColour(m_salientColour);
1394
        }
1395

    
1396
        auto params = newLayer->getPlayParameters();
1397
        if (params) {
1398
            params->setPlayAudible(false);
1399
        }
1400

    
1401
        connect(til, SIGNAL(modelCompletionChanged(ModelId)),
1402
                this, SLOT(salientLayerCompletionChanged(ModelId)));
1403
        
1404
        m_document->addLayerToView(pane, newLayer);
1405
        m_paneStack->setCurrentLayer(pane, newLayer);
1406
    }
1407
}
1408

    
1409
void
1410
MainWindow::salientLayerCompletionChanged(ModelId)
1411
{
1412
    Layer *layer = qobject_cast<Layer *>(sender());
1413
    if (layer && layer->getCompletion(0) == 100) {
1414
        m_salientCalculating = false;
1415
        for (ModelId am: m_salientPending) {
1416
            mapSalientFeatureLayer(am);
1417
        }
1418
        m_salientPending.clear();
1419
    }
1420
}
1421

    
1422
TimeInstantLayer *
1423
MainWindow::findSalientFeatureLayer(Pane *pane)
1424
{
1425
    if (!getMainModel()) return nullptr;
1426
    
1427
    if (!pane) {
1428
        for (int i = 0; i < m_paneStack->getPaneCount(); ++i) {
1429

    
1430
            Pane *p = m_paneStack->getPane(i);
1431
            bool isAssociatedWithMainModel = false;
1432

    
1433
            for (int j = 0; j < p->getLayerCount(); ++j) {
1434
                Layer *l = p->getLayer(j);
1435
                if (l->getModel() == getMainModelId()) {
1436
                    isAssociatedWithMainModel = true;
1437
                    break;
1438
                }
1439
            }
1440

    
1441
            if (isAssociatedWithMainModel) {
1442
                TimeInstantLayer *layerHere = findSalientFeatureLayer(p);
1443
                if (layerHere) return layerHere;
1444
            }
1445
        }
1446

    
1447
        return nullptr;
1448
    }
1449

    
1450
    for (int i = 0; i < pane->getLayerCount(); ++i) {
1451
        TimeInstantLayer *ll = qobject_cast<TimeInstantLayer *>
1452
            (pane->getLayer(i));
1453
        if (ll) return ll;
1454
    }
1455

    
1456
    return nullptr;
1457
}
1458

    
1459
void
1460
MainWindow::toggleVerticalScales()
1461
{
1462
    if (m_viewManager->getOverlayMode() == ViewManager::NoOverlays) {
1463
        m_viewManager->setOverlayMode(ViewManager::StandardOverlays);
1464
    } else {
1465
        m_viewManager->setOverlayMode(ViewManager::NoOverlays);
1466
    }
1467
}
1468

    
1469
void
1470
MainWindow::toggleSalientFeatures()
1471
{
1472
    bool targetDormantState = false;
1473

    
1474
    for (int i = 0; i < m_paneStack->getPaneCount(); ++i) {
1475
        Pane *p = m_paneStack->getPane(i);
1476
        TimeInstantLayer *layer = findSalientFeatureLayer(p);
1477
        if (layer) {
1478
            targetDormantState = !(layer->isLayerDormant(p));
1479
            break;
1480
        }
1481
    }
1482

    
1483
    for (int i = 0; i < m_paneStack->getPaneCount(); ++i) {
1484
        Pane *p = m_paneStack->getPane(i);
1485
        TimeInstantLayer *salient = findSalientFeatureLayer(p);
1486
        if (salient) {
1487
            salient->setLayerDormant(p, targetDormantState);
1488
        }
1489
        if (targetDormantState) {
1490
            for (int j = 0; j < p->getLayerCount(); ++j) {
1491
                Layer *l = p->getLayer(j);
1492
                if (l != salient) {
1493
                    p->propertyContainerSelected(p, l);
1494
                    break;
1495
                }
1496
            }
1497
        } else {
1498
            p->propertyContainerSelected(p, salient);
1499
        }
1500
    }
1501
}
1502

    
1503
void
1504
MainWindow::mapSalientFeatureLayer(ModelId amId)
1505
{
1506
    auto am = ModelById::getAs<AlignmentModel>(amId);
1507
    if (!am) {
1508
        SVCERR << "MainWindow::mapSalientFeatureLayer: AlignmentModel is absent!"
1509
               << endl;
1510
        return;
1511
    }
1512
    
1513
    if (m_salientCalculating) {
1514
        m_salientPending.insert(amId);
1515
        return;
1516
    }
1517

    
1518
    TimeInstantLayer *salient = findSalientFeatureLayer();
1519
    if (!salient) {
1520
        SVCERR << "MainWindow::mapSalientFeatureLayer: No salient layer found"
1521
               << endl;
1522
        m_salientPending.insert(amId);
1523
        return;
1524
    }
1525
    
1526
    ModelId modelId = am->getAlignedModel();
1527
    auto model = ModelById::get(modelId);
1528
    if (!model) {
1529
        SVCERR << "MainWindow::mapSalientFeatureLayer: No aligned model in AlignmentModel" << endl;
1530
        return;
1531
    }
1532

    
1533
    Pane *pane = nullptr;
1534
    Layer *layer = nullptr;
1535
    Pane *firstPane = nullptr;
1536
    
1537
    for (int i = 0; i < m_paneStack->getPaneCount(); ++i) {
1538
        Pane *p = m_paneStack->getPane(i);
1539
        if (p && !firstPane) firstPane = p;
1540
        for (int j = 0; j < p->getLayerCount(); ++j) {
1541
            Layer *l = p->getLayer(j);
1542
            if (!l) continue;
1543
            if (l->getModel() == modelId) {
1544
                pane = p;
1545
                layer = l;
1546
                break;
1547
            }
1548
        }
1549
        if (layer) break;
1550
    }
1551

    
1552
    if (!pane || !layer) {
1553
        SVCERR << "MainWindow::mapSalientFeatureLayer: Failed to find model "
1554
               << modelId << " in any layer" << endl;
1555
        return;
1556
    }
1557

    
1558
    QString salientLayerName = tr("Mapped Salient Feature Layer");
1559

    
1560
    // Remove any existing mapped salient layer from this pane (in
1561
    // case we are re-aligning an existing model)
1562
    for (int j = 0; j < pane->getLayerCount(); ++j) {
1563
        Layer *l = pane->getLayer(j);
1564
        if (!l) continue;
1565
        if (l->objectName() == salientLayerName) {
1566
            SVDEBUG << "MainWindow::mapSalientFeatureLayer: "
1567
                    << "Removing existing mapped layer " << l << endl;
1568
            m_document->deleteLayer(l, true); // force flag: remove from views
1569
            break;
1570
        }
1571
    }
1572

    
1573
    pane->setCentreFrame(model->alignFromReference(firstPane->getCentreFrame()));
1574

    
1575
    auto fromId = salient->getModel();
1576
    auto from = ModelById::getAs<SparseOneDimensionalModel>(fromId);
1577
    if (!from) {
1578
        SVCERR << "MainWindow::mapSalientFeatureLayer: "
1579
               << "Salient layer lacks SparseOneDimensionalModel" << endl;
1580
        return;
1581
    }
1582

    
1583
    auto to = std::make_shared<SparseOneDimensionalModel>
1584
        (model->getSampleRate(), from->getResolution(), false);
1585
    auto toId = ModelById::add(to);
1586

    
1587
    EventVector pp = from->getAllEvents();
1588
    for (const auto &p: pp) {
1589
        Event aligned = p
1590
            .withFrame(model->alignFromReference(p.getFrame()))
1591
            .withLabel(""); // remove label, as the analysis was not
1592
                            // conducted on the audio we're mapping to
1593
        to->add(aligned);
1594
    }
1595

    
1596
    Layer *newLayer = m_document->createImportedLayer(toId);
1597

    
1598
    if (newLayer) {
1599

    
1600
        newLayer->setObjectName(salientLayerName);
1601
        
1602
        TimeInstantLayer *til = qobject_cast<TimeInstantLayer *>(newLayer);
1603
        if (til) {
1604
            til->setPlotStyle(TimeInstantLayer::PlotInstants);
1605
            til->setBaseColour(m_salientColour);
1606
        }
1607
        
1608
        auto params = newLayer->getPlayParameters();
1609
        if (params) {
1610
            params->setPlayAudible(false);
1611
        }
1612

    
1613
        m_document->addLayerToView(pane, newLayer);
1614
        m_paneStack->setCurrentLayer(pane, newLayer);
1615
    }
1616
}
1617

    
1618
void
1619
MainWindow::outlineWaveformModeSelected()
1620
{
1621
    QString name = m_modeLayerNames[OutlineWaveformMode];
1622

    
1623
    Pane *currentPane = m_paneStack->getCurrentPane();
1624
    
1625
    for (int i = 0; i < m_paneStack->getPaneCount(); ++i) {
1626

    
1627
        Pane *pane = m_paneStack->getPane(i);
1628
        if (!pane) continue;
1629

    
1630
        ModelId createFrom;
1631
        if (!selectExistingLayerForMode(pane, name, &createFrom) &&
1632
            !createFrom.isNone()) {
1633

    
1634
            Layer *newLayer = m_document->createLayer(LayerFactory::Waveform);
1635
            newLayer->setObjectName(name);
1636

    
1637
            QString layerPropertyXml =
1638
                QString("<layer scale=\"%1\" channelMode=\"%2\" gain=\"0.95\"/>")
1639
                .arg(int(WaveformLayer::MeterScale))
1640
                .arg(int(WaveformLayer::MergeChannels));
1641
            LayerFactory::getInstance()->setLayerProperties
1642
                (newLayer, layerPropertyXml);
1643

    
1644
            SingleColourLayer *scl =
1645
                qobject_cast<SingleColourLayer *>(newLayer);
1646
            if (scl) {
1647
                scl->setBaseColour
1648
                    (i % ColourDatabase::getInstance()->getColourCount());
1649
            }
1650
            
1651
            m_document->setModel(newLayer, createFrom);
1652
            m_document->addLayerToView(pane, newLayer);
1653
            m_paneStack->setCurrentLayer(pane, newLayer);
1654
        }
1655

    
1656
        TimeInstantLayer *salient = findSalientFeatureLayer(pane);
1657
        if (salient) {
1658
            pane->propertyContainerSelected(pane, salient);
1659
        }
1660
    }
1661

    
1662
    if (currentPane) {
1663
        m_paneStack->setCurrentPane(currentPane);
1664
    }
1665
    
1666
    m_displayMode = OutlineWaveformMode;
1667
    checkpointSession();
1668
}
1669

    
1670
void
1671
MainWindow::standardWaveformModeSelected()
1672
{
1673
    QString name = m_modeLayerNames[WaveformMode];
1674

    
1675
    Pane *currentPane = m_paneStack->getCurrentPane();
1676
    
1677
    for (int i = 0; i < m_paneStack->getPaneCount(); ++i) {
1678

    
1679
        Pane *pane = m_paneStack->getPane(i);
1680
        if (!pane) continue;
1681

    
1682
        ModelId createFrom;
1683
        if (!selectExistingLayerForMode(pane, name, &createFrom) &&
1684
            !createFrom.isNone()) {
1685

    
1686
            Layer *newLayer = m_document->createLayer(LayerFactory::Waveform);
1687
            newLayer->setObjectName(name);
1688

    
1689
            QString layerPropertyXml =
1690
                QString("<layer scale=\"%1\" channelMode=\"%2\"/>")
1691
                .arg(int(WaveformLayer::LinearScale))
1692
                .arg(int(WaveformLayer::SeparateChannels));
1693
            LayerFactory::getInstance()->setLayerProperties
1694
                (newLayer, layerPropertyXml);
1695

    
1696
            SingleColourLayer *scl =
1697
                qobject_cast<SingleColourLayer *>(newLayer);
1698
            if (scl) {
1699
                scl->setBaseColour
1700
                    (i % ColourDatabase::getInstance()->getColourCount());
1701
            }
1702
            
1703
            m_document->setModel(newLayer, createFrom);
1704
            m_document->addLayerToView(pane, newLayer);
1705
            m_paneStack->setCurrentLayer(pane, newLayer);
1706
        }
1707

    
1708
        TimeInstantLayer *salient = findSalientFeatureLayer(pane);
1709
        if (salient) {
1710
            pane->propertyContainerSelected(pane, salient);
1711
        }
1712
    }
1713

    
1714
    if (currentPane) {
1715
        m_paneStack->setCurrentPane(currentPane);
1716
    }
1717

    
1718
    m_displayMode = WaveformMode;
1719
    checkpointSession();
1720
}
1721

    
1722
void
1723
MainWindow::spectrogramModeSelected()
1724
{
1725
    QString name = m_modeLayerNames[SpectrogramMode];
1726

    
1727
    Pane *currentPane = m_paneStack->getCurrentPane();
1728
    
1729
    for (int i = 0; i < m_paneStack->getPaneCount(); ++i) {
1730

    
1731
        Pane *pane = m_paneStack->getPane(i);
1732
        if (!pane) continue;
1733

    
1734
        ModelId createFrom;
1735
        if (!selectExistingLayerForMode(pane, name, &createFrom) &&
1736
            !createFrom.isNone()) {
1737
            Layer *newLayer = m_document->createLayer(LayerFactory::Spectrogram);
1738
            newLayer->setObjectName(name);
1739
            m_document->setModel(newLayer, createFrom);
1740
            m_document->addLayerToView(pane, newLayer);
1741
            m_paneStack->setCurrentLayer(pane, newLayer);
1742
        }
1743

    
1744
        TimeInstantLayer *salient = findSalientFeatureLayer(pane);
1745
        if (salient) {
1746
            pane->propertyContainerSelected(pane, salient);
1747
        }
1748
    }
1749

    
1750
    if (currentPane) {
1751
        m_paneStack->setCurrentPane(currentPane);
1752
    }
1753

    
1754
    m_displayMode = SpectrogramMode;
1755
    checkpointSession();
1756
}
1757

    
1758
void
1759
MainWindow::melodogramModeSelected()
1760
{
1761
    QString name = m_modeLayerNames[MelodogramMode];
1762

    
1763
    Pane *currentPane = m_paneStack->getCurrentPane();
1764

    
1765
    for (int i = 0; i < m_paneStack->getPaneCount(); ++i) {
1766

    
1767
        Pane *pane = m_paneStack->getPane(i);
1768
        if (!pane) continue;
1769

    
1770
        ModelId createFrom;
1771
        if (!selectExistingLayerForMode(pane, name, &createFrom) &&
1772
            !createFrom.isNone()) {
1773
            Layer *newLayer = m_document->createLayer
1774
                (LayerFactory::MelodicRangeSpectrogram);
1775
            SpectrogramLayer *spectrogram = qobject_cast<SpectrogramLayer *>
1776
                (newLayer);
1777
            spectrogram->setVerticallyFixed();
1778
            newLayer->setObjectName(name);
1779
            m_document->setModel(newLayer, createFrom);
1780
            m_document->addLayerToView(pane, newLayer);
1781
            m_paneStack->setCurrentLayer(pane, newLayer);
1782
        }
1783

    
1784
        TimeInstantLayer *salient = findSalientFeatureLayer(pane);
1785
        if (salient) {
1786
            pane->propertyContainerSelected(pane, salient);
1787
        }
1788
    }
1789

    
1790
    if (currentPane) {
1791
        m_paneStack->setCurrentPane(currentPane);
1792
    }
1793

    
1794
    m_displayMode = MelodogramMode;
1795
    checkpointSession();
1796
}
1797

    
1798
void
1799
MainWindow::selectTransformDrivenMode(DisplayMode mode,
1800
                                      QString transformId,
1801
                                      Transform::ParameterMap parameters,
1802
                                      QString layerPropertyXml,
1803
                                      bool includeGhostReference)
1804
{
1805
    QString name = m_modeLayerNames[mode];
1806

    
1807
    // Bring forth any existing layers of the appropriate name; for
1808
    // each pane that lacks one, make a note of the model from which
1809
    // we should create it
1810

    
1811
    map<Pane *, ModelId> sourceModels;
1812
    
1813
    for (int i = 0; i < m_paneStack->getPaneCount(); ++i) {
1814
        Pane *pane = m_paneStack->getPane(i);
1815
        ModelId createFrom;
1816
        if (!selectExistingLayerForMode(pane, name, &createFrom)) {
1817
            if (!createFrom.isNone()) {
1818
                sourceModels[pane] = createFrom;
1819
            }
1820
        }
1821
    }
1822

    
1823
    Layer *ghostReference = nullptr;
1824

    
1825
    if (includeGhostReference && !sourceModels.empty()) {
1826

    
1827
        // Look up the layer of this type in the first pane -- this is
1828
        // the reference that we must include as a ghost in the pane
1829
        // that we're adding the new layer to.
1830

    
1831
        // NB it won't exist if this is the first time into this mode
1832
        // and we haven't created the layer for the reference pane yet
1833
        // - we have to handle that in the creation loop below.
1834
        
1835
        Pane *pane = m_paneStack->getPane(0);
1836
        
1837
        for (int i = 0; i < pane->getLayerCount(); ++i) {
1838
            Layer *layer = pane->getLayer(i);
1839
            if (!layer || qobject_cast<TimeInstantLayer *>(layer)) {
1840
                continue;
1841
            }
1842
            if (layer->objectName() == name) {
1843
                ghostReference = layer;
1844
                break;
1845
            }
1846
        }
1847
    }
1848

    
1849
    Pane *currentPane = m_paneStack->getCurrentPane();
1850

    
1851
    TransformFactory *tf = TransformFactory::getInstance();
1852

    
1853
    if (tf->haveTransform(transformId)) {
1854

    
1855
        for (int i = 0; i < m_paneStack->getPaneCount(); ++i) {
1856

    
1857
            Pane *pane = m_paneStack->getPane(i);
1858

    
1859
            if (sourceModels.find(pane) == sourceModels.end()) {
1860
                // no need to create, this one exists already
1861
                continue;
1862
            }
1863

    
1864
            ModelId source = sourceModels[pane];
1865

    
1866
            if (ghostReference) {
1867
                m_document->addLayerToView(pane, ghostReference);
1868
                pane->setUseAligningProxy(true);
1869
            }
1870
            
1871
            Transform transform = tf->getDefaultTransformFor(transformId);
1872

    
1873
            if (!parameters.empty()) {
1874
                transform.setParameters(parameters);
1875
            }
1876

    
1877
            ModelTransformer::Input input(source, -1);
1878

    
1879
            Layer *layer = m_document->createDerivedLayer(transform, source);
1880

    
1881
            if (layer) {
1882

    
1883
                layer->setObjectName(name);
1884
                LayerFactory::getInstance()->setLayerProperties
1885
                    (layer, layerPropertyXml);
1886

    
1887
                SingleColourLayer *scl =
1888
                    qobject_cast<SingleColourLayer *>(layer);
1889
                if (scl) {
1890
                    int colourIndex = 
1891
                        (i % ColourDatabase::getInstance()->getColourCount());
1892
                    scl->setBaseColour(colourIndex);
1893
                }
1894

    
1895
                m_document->addLayerToView(pane, layer);
1896
                m_paneStack->setCurrentLayer(pane, layer);
1897

    
1898
                if (!ghostReference && includeGhostReference && i == 0) {
1899
                    ghostReference = layer;
1900
                }
1901
                
1902
            } else {
1903
                SVCERR << "ERROR: Failed to create derived layer" << endl;
1904
            }
1905

    
1906
            TimeInstantLayer *salient = findSalientFeatureLayer(pane);
1907
            if (salient) {
1908
                pane->propertyContainerSelected(pane, salient);
1909
            }
1910
        }
1911
    } else {
1912
        SVCERR << "ERROR: No plugin available for mode: " << name << endl;
1913
    }
1914

    
1915
    if (currentPane) {
1916
        m_paneStack->setCurrentPane(currentPane);
1917
    }
1918

    
1919
    m_displayMode = mode;
1920
    checkpointSession();
1921
}
1922

    
1923
void
1924
MainWindow::pitchModeSelected()
1925
{
1926
    QString propertyXml =
1927
        QString("<layer plotStyle=\"%1\" verticalScale=\"%2\" scaleMinimum=\"%3\" scaleMaximum=\"%4\"/>")
1928
        .arg(int(TimeValueLayer::PlotDiscreteCurves))
1929
        .arg(int(TimeValueLayer::LogScale))
1930
        .arg(40)
1931
        .arg(510);
1932
    
1933
    selectTransformDrivenMode
1934
        (PitchMode,
1935
         "vamp:pyin:pyin:smoothedpitchtrack",
1936
         {},
1937
         propertyXml,
1938
         true); // ghost reference
1939
}
1940

    
1941
void
1942
MainWindow::keyModeSelected()
1943
{
1944
    QString propertyXml =
1945
        QString("<layer colourMap=\"Sunset\" opaque=\"true\" smooth=\"false\" "
1946
                "binScale=\"%1\" columnNormalization=\"none\"/>")
1947
        .arg(int(BinScale::Linear));
1948
    
1949
    selectTransformDrivenMode
1950
        (KeyMode,
1951
         "vamp:qm-vamp-plugins:qm-keydetector:tonicstrength",
1952
         {},
1953
         propertyXml,
1954
         false);
1955
}
1956

    
1957
void
1958
MainWindow::azimuthModeSelected()
1959
{
1960
    QString propertyXml =
1961
        QString("<layer colourMap=\"Ice\" opaque=\"true\" smooth=\"true\" "
1962
                "binScale=\"%1\" columnNormalization=\"hybrid\"/>")
1963
        .arg(int(BinScale::Linear));
1964

    
1965
    selectTransformDrivenMode
1966
        (AzimuthMode,
1967
         "vamp:azi:azi:plan",
1968
         {},
1969
         propertyXml,
1970
         false);
1971
}
1972

    
1973
void
1974
MainWindow::previousDisplayMode()
1975
{
1976
    for (int i = 0; in_range_for(m_modeDisplayOrder, i); ++i) {
1977
        if (m_displayMode == m_modeDisplayOrder[i]) {
1978
            if (i > 0) {
1979
                m_modeButtons[m_modeDisplayOrder[i-1]]->click();
1980
            }
1981
            break;
1982
        }
1983
    }
1984
}
1985

    
1986
void
1987
MainWindow::nextDisplayMode()
1988
{
1989
    for (int i = 0; in_range_for(m_modeDisplayOrder, i); ++i) {
1990
        if (m_displayMode == m_modeDisplayOrder[i]) {
1991
            if (in_range_for(m_modeDisplayOrder, i+1)) {
1992
                m_modeButtons[m_modeDisplayOrder[i+1]]->click();
1993
            }
1994
            break;
1995
        }
1996
    }
1997
}
1998

    
1999
void
2000
MainWindow::updateModeFromLayers()
2001
{
2002
    for (auto &bp : m_modeButtons) {
2003
        bp.second->setChecked(false);
2004
    }
2005

    
2006
    SVCERR << "MainWindow::updateModeFromLayers" << endl;
2007
    
2008
    for (int i = 0; i < m_paneStack->getPaneCount(); ++i) {
2009

    
2010
        Pane *pane = m_paneStack->getPane(i);
2011
        if (!pane) continue;
2012

    
2013
        SVCERR << "MainWindow::updateModeFromLayers: pane " << i << "..." << endl;
2014
        
2015
        bool found = false;
2016
        
2017
        for (int j = 0; j < pane->getLayerCount(); ++j) {
2018

    
2019
            Layer *layer = pane->getLayer(j);
2020
            if (!layer || qobject_cast<TimeInstantLayer *>(layer)) {
2021
                continue;
2022
            }
2023
            if (layer->isLayerDormant(pane)) {
2024
                continue;
2025
            }
2026

    
2027
            QString ln = layer->objectName();
2028

    
2029
            SVCERR << "MainWindow::updateModeFromLayers: layer " << j << " has name " << ln << endl;
2030

    
2031
            for (const auto &mp: m_modeLayerNames) {
2032
                if (ln == mp.second) {
2033
                    m_displayMode = mp.first;
2034
                    found = true;
2035
                    break;
2036
                }
2037
            }
2038
        }
2039

    
2040
        if (found) break;
2041
    }
2042

    
2043
    m_modeButtons[m_displayMode]->setChecked(true);
2044
}
2045

    
2046
void
2047
MainWindow::reselectMode()
2048
{
2049
    switch (m_displayMode) {
2050
    case OutlineWaveformMode: outlineWaveformModeSelected(); break;
2051
    case WaveformMode: standardWaveformModeSelected(); break;
2052
    case SpectrogramMode: spectrogramModeSelected(); break;
2053
    case MelodogramMode: melodogramModeSelected(); break;
2054
    case AzimuthMode: azimuthModeSelected(); break;
2055
    case PitchMode: pitchModeSelected(); break;
2056
    case KeyMode: keyModeSelected(); break;
2057
    }
2058
}
2059

    
2060
void
2061
MainWindow::paneAdded(Pane *pane)
2062
{
2063
    pane->setPlaybackFollow(PlaybackScrollContinuous);
2064
    m_paneStack->sizePanesEqually();
2065
    checkpointSession();
2066
}    
2067

    
2068
void
2069
MainWindow::paneHidden(Pane *)
2070
{
2071
    checkpointSession();
2072
}    
2073

    
2074
void
2075
MainWindow::paneAboutToBeDeleted(Pane *)
2076
{
2077
}    
2078

    
2079
void
2080
MainWindow::paneDropAccepted(Pane * /* pane */, QStringList uriList)
2081
{
2082
    if (uriList.empty()) return;
2083

    
2084
    QUrl first(uriList[0]);
2085

    
2086
    cerr << "uriList.size() == " << uriList.size() << endl;
2087
    cerr << "first.isLocalFile() == " << first.isLocalFile() << endl;
2088
    cerr << "QFileInfo(first.path()).isDir() == " << QFileInfo(first.path()).isDir() << endl;
2089

    
2090
    m_sessionState = SessionActive;
2091
    
2092
    if (uriList.size() == 1 &&
2093
        first.isLocalFile() &&
2094
        QFileInfo(first.path()).isDir()) {
2095

    
2096
        FileOpenStatus status = openDirOfAudio(first.path());
2097

    
2098
        if (status != FileOpenSucceeded) {
2099
            QMessageBox::critical(this, tr("Failed to open dropped URL"),
2100
                                  tr("<b>Open failed</b><p>Dropped URL \"%1\" could not be opened").arg(uriList[0]));
2101
        }
2102

    
2103
        return;
2104
    }
2105
    
2106
    for (QString uri: uriList) {
2107

    
2108
        FileOpenStatus status = FileOpenFailed;
2109
        
2110
        FileSource source(uri);
2111
        if (source.isAvailable()) {
2112
            source.waitForData();
2113

    
2114
            try {
2115
                status = openAudio(source, CreateAdditionalModel);
2116
            } catch (const InsufficientDiscSpace &e) {
2117
                SVCERR << "MainWindowBase: Caught InsufficientDiscSpace in file open" << endl;
2118
                QMessageBox::critical
2119
                    (this, tr("Not enough disc space"),
2120
                     tr("<b>Not enough disc space</b><p>There doesn't appear to be enough spare disc space to accommodate any necessary temporary files.</p><p>Please clear some space and try again.</p>").arg(e.what()));
2121
                return;
2122
            }
2123
        }
2124
            
2125
        if (status != FileOpenSucceeded) {
2126
            QMessageBox::critical(this, tr("Failed to open dropped URL"),
2127
                                  tr("<b>Open failed</b><p>Dropped audio file location \"%1\" could not be opened").arg(uri));
2128
        } else {
2129
            configureNewPane(m_paneStack->getCurrentPane());
2130
        }
2131
    }
2132
}
2133

    
2134
void
2135
MainWindow::paneDropAccepted(Pane *pane, QString text)
2136
{
2137
    if (pane) m_paneStack->setCurrentPane(pane);
2138

    
2139
    QUrl testUrl(text);
2140
    if (testUrl.scheme() == "file" || 
2141
        testUrl.scheme() == "http" || 
2142
        testUrl.scheme() == "ftp") {
2143
        QStringList list;
2144
        list.push_back(text);
2145
        paneDropAccepted(pane, list);
2146
        return;
2147
    }
2148

    
2149
    //!!! open as text -- but by importing as if a CSV, or just adding
2150
    //to a text layer?
2151
}
2152

    
2153
void
2154
MainWindow::configureNewPane(Pane *pane)
2155
{
2156
    SVCERR << "MainWindow::configureNewPane(" << pane << ")" << endl;
2157

    
2158
    if (!pane) return;
2159

    
2160
    // MainWindowBase::addOpenedAudioModel adds a waveform layer for
2161
    // each additional model besides the main one (assuming that the
2162
    // main one gets it, if needed, from the session template). We
2163
    // don't actually want to use those - we'll be adding our own with
2164
    // specific parameters - but they don't cost much, so rather than
2165
    // remove them, just rename them to something that won't cause
2166
    // confusion with the name-based mode layer handling. NB we have
2167
    // to do this before calling reselectMode(), as that will add
2168
    // another competing waveform layer
2169
    
2170
    Layer *waveformLayer = 0;
2171

    
2172
    for (int i = 0; i < pane->getLayerCount(); ++i) {
2173
        Layer *layer = pane->getLayer(i);
2174
        if (!layer) {
2175
            continue;
2176
        }
2177
        if (dynamic_cast<WaveformLayer *>(layer)) {
2178
            waveformLayer = layer;
2179
        }
2180
        if (dynamic_cast<TimeValueLayer *>(layer)) {
2181
            break;
2182
        }
2183
    }
2184

    
2185
    if (waveformLayer) {
2186
        waveformLayer->setObjectName("Automatically Created - Unused"); // not to be translated
2187
    }
2188

    
2189
    zoomToFit();
2190
    reselectMode();
2191
}
2192

    
2193
void
2194
MainWindow::record()
2195
{
2196
    MainWindowBase::record();
2197
    configureNewPane(m_paneStack->getCurrentPane());
2198
}
2199

    
2200
void
2201
MainWindow::closeEvent(QCloseEvent *e)
2202
{
2203
    if (m_exiting) {
2204
        e->accept();
2205
        return;
2206
    }
2207

    
2208
//    cerr << "MainWindow::closeEvent" << endl;
2209

    
2210
    if (m_openingAudioFile) {
2211
//        cerr << "Busy - ignoring close event" << endl;
2212
        e->ignore();
2213
        return;
2214
    }
2215

    
2216
    closeSession();
2217

    
2218
    QSettings settings;
2219
    settings.beginGroup("MainWindow");
2220
    settings.setValue("maximised", isMaximized());
2221
    if (!isMaximized()) {
2222
        settings.setValue("size", size());
2223
        settings.setValue("position", pos());
2224
    }
2225
    settings.endGroup();
2226

    
2227
    delete m_keyReference;
2228
    m_keyReference = 0;
2229

    
2230
    if (m_preferencesDialog &&
2231
        m_preferencesDialog->isVisible()) {
2232
        closeSession(); // otherwise we'll have to wait for prefs changes
2233
        m_preferencesDialog->applicationClosing(false);
2234
    }
2235

    
2236
    if (m_layerTreeView &&
2237
        m_layerTreeView->isVisible()) {
2238
        delete m_layerTreeView;
2239
    }
2240

    
2241
    e->accept();
2242

    
2243
    m_exiting = true;
2244
    qApp->closeAllWindows();
2245
    
2246
    return;
2247
}
2248

    
2249
bool
2250
MainWindow::checkSaveModified()
2251
{
2252
    // It should always be OK to save, with our active-session paradigm
2253
    return true;
2254
}
2255

    
2256
bool
2257
MainWindow::commitData(bool /* mayAskUser */)
2258
{
2259
    if (m_preferencesDialog &&
2260
        m_preferencesDialog->isVisible()) {
2261
        m_preferencesDialog->applicationClosing(true);
2262
    }
2263
    checkpointSession();
2264
    return true;
2265
}
2266

    
2267
void
2268
MainWindow::preferenceChanged(PropertyContainer::PropertyName name)
2269
{
2270
    MainWindowBase::preferenceChanged(name);
2271
}
2272

    
2273
void
2274
MainWindow::renameCurrentLayer()
2275
{
2276
    Pane *pane = m_paneStack->getCurrentPane();
2277
    if (pane) {
2278
        Layer *layer = pane->getSelectedLayer();
2279
        if (layer) {
2280
            bool ok = false;
2281
            QString newName = QInputDialog::getText
2282
                (this, tr("Rename Layer"),
2283
                 tr("New name for this layer:"),
2284
                 QLineEdit::Normal, layer->objectName(), &ok);
2285
            if (ok) {
2286
                layer->setObjectName(newName);
2287
            }
2288
        }
2289
    }
2290
}
2291

    
2292
void
2293
MainWindow::alignToggled()
2294
{
2295
    QAction *action = dynamic_cast<QAction *>(sender());
2296
    
2297
    if (!m_viewManager) return;
2298

    
2299
    if (action) {
2300
        m_viewManager->setAlignMode(action->isChecked());
2301
    } else {
2302
        m_viewManager->setAlignMode(!m_viewManager->getAlignMode());
2303
    }
2304

    
2305
    if (m_viewManager->getAlignMode()) {
2306
        m_document->alignModels();
2307
        m_document->setAutoAlignment(true);
2308
    } else {
2309
        m_document->setAutoAlignment(false);
2310
    }
2311

    
2312
    for (int i = 0; i < m_paneStack->getPaneCount(); ++i) {
2313
        Pane *pane = m_paneStack->getPane(i);
2314
        if (!pane) continue;
2315
        pane->update();
2316
    }
2317
}
2318

    
2319
void
2320
MainWindow::tuningDifferenceToggled()
2321
{
2322
    QSettings settings;
2323
    settings.beginGroup("Alignment");
2324
    bool on = settings.value("align-pitch-aware", false).toBool();
2325
    settings.setValue("align-pitch-aware", !on);
2326
    settings.endGroup();
2327

    
2328
    if (m_viewManager->getAlignMode()) {
2329
        m_document->realignModels();
2330
    }
2331
}    
2332
    
2333
void
2334
MainWindow::playSpeedChanged(int position)
2335
{
2336
    PlaySpeedRangeMapper mapper;
2337

    
2338
    double percent = m_playSpeed->mappedValue();
2339
    double factor = mapper.getFactorForValue(percent);
2340

    
2341
//    cerr << "play speed position = " << position << " (range 0-120) percent = " << percent << " factor = " << factor << endl;
2342

    
2343
    int centre = m_playSpeed->defaultValue();
2344

    
2345
    // Percentage is shown to 0dp if >100, to 1dp if <100; factor is
2346
    // shown to 3sf
2347

    
2348
    char pcbuf[30];
2349
    char facbuf[30];
2350
    
2351
    if (position == centre) {
2352
        contextHelpChanged(tr("Playback speed: Normal"));
2353
    } else if (position < centre) {
2354
        sprintf(pcbuf, "%.1f", percent);
2355
        sprintf(facbuf, "%.3g", 1.0 / factor);
2356
        contextHelpChanged(tr("Playback speed: %1% (%2x slower)")
2357
                           .arg(pcbuf)
2358
                           .arg(facbuf));
2359
    } else {
2360
        sprintf(pcbuf, "%.0f", percent);
2361
        sprintf(facbuf, "%.3g", factor);
2362
        contextHelpChanged(tr("Playback speed: %1% (%2x faster)")
2363
                           .arg(pcbuf)
2364
                           .arg(facbuf));
2365
    }
2366

    
2367
    m_playSource->setTimeStretch(1.0 / factor); // factor is a speedup
2368

    
2369
    updateMenuStates();
2370
}
2371

    
2372
void
2373
MainWindow::speedUpPlayback()
2374
{
2375
    int value = m_playSpeed->value();
2376
    value = value + m_playSpeed->pageStep();
2377
    if (value > m_playSpeed->maximum()) value = m_playSpeed->maximum();
2378
    m_playSpeed->setValue(value);
2379
}
2380

    
2381
void
2382
MainWindow::slowDownPlayback()
2383
{
2384
    int value = m_playSpeed->value();
2385
    value = value - m_playSpeed->pageStep();
2386
    if (value < m_playSpeed->minimum()) value = m_playSpeed->minimum();
2387
    m_playSpeed->setValue(value);
2388
}
2389

    
2390
void
2391
MainWindow::restoreNormalPlayback()
2392
{
2393
    m_playSpeed->setValue(m_playSpeed->defaultValue());
2394
}
2395

    
2396
void
2397
MainWindow::updateVisibleRangeDisplay(Pane *p) const
2398
{
2399
    if (!getMainModel() || !p) {
2400
        return;
2401
    }
2402

    
2403
    bool haveSelection = false;
2404
    sv_frame_t startFrame = 0, endFrame = 0;
2405

    
2406
    if (m_viewManager && m_viewManager->haveInProgressSelection()) {
2407

    
2408
        bool exclusive = false;
2409
        Selection s = m_viewManager->getInProgressSelection(exclusive);
2410

    
2411
        if (!s.isEmpty()) {
2412
            haveSelection = true;
2413
            startFrame = s.getStartFrame();
2414
            endFrame = s.getEndFrame();
2415
        }
2416
    }
2417

    
2418
    if (!haveSelection) {
2419
        startFrame = p->getFirstVisibleFrame();
2420
        endFrame = p->getLastVisibleFrame();
2421
    }
2422

    
2423
    RealTime start = RealTime::frame2RealTime
2424
        (startFrame, getMainModel()->getSampleRate());
2425

    
2426
    RealTime end = RealTime::frame2RealTime
2427
        (endFrame, getMainModel()->getSampleRate());
2428

    
2429
    RealTime duration = end - start;
2430

    
2431
    QString startStr, endStr, durationStr;
2432
    startStr = start.toText(true).c_str();
2433
    endStr = end.toText(true).c_str();
2434
    durationStr = duration.toText(true).c_str();
2435

    
2436
    if (haveSelection) {
2437
        m_myStatusMessage = tr("Selection: %1 to %2 (duration %3)")
2438
            .arg(startStr).arg(endStr).arg(durationStr);
2439
    } else {
2440
        m_myStatusMessage = tr("Visible: %1 to %2 (duration %3)")
2441
            .arg(startStr).arg(endStr).arg(durationStr);
2442
    }
2443

    
2444
    statusBar()->showMessage(m_myStatusMessage);
2445
}
2446

    
2447
void
2448
MainWindow::updatePositionStatusDisplays() const
2449
{
2450
    if (!statusBar()->isVisible()) return;
2451

    
2452
}
2453

    
2454
void
2455
MainWindow::monitoringLevelsChanged(float left, float right)
2456
{
2457
    m_mainLevelPan->setMonitoringLevels(left, right);
2458
}
2459

    
2460
void
2461
MainWindow::sampleRateMismatch(sv_samplerate_t requested,
2462
                               sv_samplerate_t actual,
2463
                               bool willResample)
2464
{
2465
    if (!willResample) {
2466
        //!!! more helpful message needed
2467
        QMessageBox::information
2468
            (this, tr("Sample rate mismatch"),
2469
             tr("The sample rate of this audio file (%1 Hz) does not match\nthe current playback rate (%2 Hz).\n\nThe file will play at the wrong speed and pitch.")
2470
             .arg(requested).arg(actual));
2471
    }        
2472

    
2473
    updateDescriptionLabel();
2474
}
2475

    
2476
void
2477
MainWindow::audioOverloadPluginDisabled()
2478
{
2479
    QMessageBox::information
2480
        (this, tr("Audio processing overload"),
2481
         tr("<b>Overloaded</b><p>Audio effects plugin auditioning has been disabled due to a processing overload."));
2482
}
2483

    
2484
void
2485
MainWindow::audioTimeStretchMultiChannelDisabled()
2486
{
2487
    static bool shownOnce = false;
2488
    if (shownOnce) return;
2489
    QMessageBox::information
2490
        (this, tr("Audio processing overload"),
2491
         tr("<b>Overloaded</b><p>Audio playback speed processing has been reduced to a single channel, due to a processing overload."));
2492
    shownOnce = true;
2493
}
2494

    
2495
void
2496
MainWindow::introDialog()
2497
{
2498
    IntroDialog d(this);
2499
}
2500

    
2501
void
2502
MainWindow::betaReleaseWarning()
2503
{
2504
    QMessageBox::information
2505
        (this, tr("Test release"),
2506
         tr("<b>This is a test release of %1</b><p>This release is made for test purposes only - please send feedback to the developers.</p>").arg(QApplication::applicationName()));
2507
}
2508

    
2509
void
2510
MainWindow::layerRemoved(Layer *layer)
2511
{
2512
    MainWindowBase::layerRemoved(layer);
2513
}
2514

    
2515
void
2516
MainWindow::layerInAView(Layer *layer, bool inAView)
2517
{
2518
    MainWindowBase::layerInAView(layer, inAView);
2519
}
2520

    
2521
void
2522
MainWindow::modelAdded(ModelId model)
2523
{
2524
    MainWindowBase::modelAdded(model);
2525
}
2526

    
2527
QString
2528
MainWindow::makeSessionFilename()
2529
{
2530
    auto mainModel = getMainModel();
2531
    if (!mainModel) {
2532
        SVDEBUG << "MainWindow::makeSessionFilename: No main model, returning empty filename" << endl;
2533
        return {};
2534
    }
2535
    
2536
    QDir parentDir(TempDirectory::getInstance()->getContainingPath());
2537
    QString sessionDirName("session");
2538

    
2539
    if (!parentDir.mkpath(sessionDirName)) {
2540
        SVCERR << "ERROR: makeSessionFilename: Failed to create session dir in \"" << parentDir.canonicalPath() << "\"" << endl;
2541
        QMessageBox::critical(this, tr("Failed to create session directory"),
2542
                              tr("<p>Failed to create directory \"%1\" for session files</p>")
2543
                              .arg(parentDir.filePath(sessionDirName)));
2544
        return {};
2545
    }
2546

    
2547
    QDir sessionDir(parentDir.filePath(sessionDirName));
2548
    
2549
    QDateTime now = QDateTime::currentDateTime();
2550
    QString dateDirName = QString("%1").arg(now.toString("yyyyMMdd"));
2551

    
2552
    if (!sessionDir.mkpath(dateDirName)) {
2553
        SVCERR << "ERROR: makeSessionFilename: Failed to create datestamped session dir in \"" << sessionDir.canonicalPath() << "\"" << endl;
2554
        QMessageBox::critical(this, tr("Failed to create session directory"),
2555
                              tr("<p>Failed to create date directory \"%1\" for session files</p>")
2556
                              .arg(sessionDir.filePath(dateDirName)));
2557
        return {};
2558
    }
2559

    
2560
    QDir dateDir(sessionDir.filePath(dateDirName));
2561

    
2562
    QString sessionName = mainModel->getTitle();
2563
    if (sessionName == "") {
2564
        sessionName = mainModel->getLocation();
2565
    }
2566
    sessionName = QFileInfo(sessionName).baseName();
2567
    sessionName.replace(QRegExp("[<>:\"/\\\\|?*\\0000-\\0039()\\[\\]$]"), "_");
2568

    
2569
    QString sessionExt = 
2570
        InteractiveFileFinder::getInstance()->getApplicationSessionExtension();
2571

    
2572
    QString filePath = dateDir.filePath(QString("%1.%2")
2573
                                        .arg(sessionName)
2574
                                        .arg(sessionExt));
2575
    int suffix = 0;
2576
    while (QFile(filePath).exists()) {
2577
        if (++suffix == 100) {
2578
            SVCERR << "ERROR: makeSessionFilename: Failed to come up with unique session filename for " << sessionName << endl;
2579
            QMessageBox::critical(this, tr("Failed to obtain unique filename"),
2580
                                  tr("<p>Failed to obtain a unique filename for session file</p>"));
2581
            return {};
2582
        }
2583
        filePath = dateDir.filePath(QString("%1-%2.%3")
2584
                                    .arg(sessionName)
2585
                                    .arg(suffix)
2586
                                    .arg(sessionExt));
2587
    }
2588

    
2589
    SVDEBUG << "MainWindow::makeSessionFilename: returning "
2590
            << filePath << endl;
2591

    
2592
    return filePath;
2593
}
2594

    
2595
QString
2596
MainWindow::makeSessionLabel()
2597
{
2598
    auto mainModel = getMainModel();
2599
    if (!mainModel) {
2600
        SVDEBUG << "MainWindow::makeSessionFilename: No main model, returning empty filename" << endl;
2601
        return {};
2602
    }
2603

    
2604
    QString sessionName = mainModel->getTitle();
2605
    if (sessionName == "") {
2606
        sessionName = mainModel->getLocation();
2607
        sessionName = QFileInfo(sessionName).baseName();
2608
    }
2609

    
2610
    int paneCount = 1;
2611
    if (m_paneStack) paneCount = m_paneStack->getPaneCount();
2612
    QString label = tr("%1: %n file(s)", "", paneCount).arg(sessionName);
2613
    
2614
    SVDEBUG << "MainWindow::makeSessionLabel: returning "
2615
            << label << endl;
2616

    
2617
    return label;
2618
}
2619

    
2620
void
2621
MainWindow::checkpointSession()
2622
{
2623
    if (m_sessionState == NoSession) {
2624
        SVCERR << "MainWindow::checkpointSession: no current session" << endl;
2625
        return;
2626
    }
2627

    
2628
    if (m_sessionState == SessionLoading) {
2629
        SVCERR << "MainWindow::checkpointSession: session is loading" << endl;
2630
        return;
2631
    }
2632
    
2633
    // This test is necessary, so that we don't get into a nasty loop
2634
    // when checkpointing on closeSession called when opening a new
2635
    // session file
2636
    if (!m_documentModified) {
2637
        SVCERR << "MainWindow::checkpointSession: nothing to save" << endl;
2638
        return;
2639
    }
2640
    
2641
    if (m_sessionFile == "") {
2642
        SVCERR << "MainWindow::checkpointSession: no current session file" << endl;
2643
        return;
2644
    }
2645

    
2646
    QString sessionExt = 
2647
        InteractiveFileFinder::getInstance()->getApplicationSessionExtension();
2648

    
2649
    if (!m_sessionFile.endsWith("." + sessionExt)) {
2650
        // At one point in development we had a nasty situation where
2651
        // we loaded an audio file from the recent files list, then
2652
        // immediately saved the session over the top of it! This is
2653
        // just an additional guard against that kind of thing
2654
        SVCERR << "MainWindow::checkpointSession: suspicious session filename "
2655
               << m_sessionFile << ", not saving to it" << endl;
2656
        return;
2657

    
2658
        //!!! + we should also check that it is actually in our
2659
        //!!! auto-save session directory
2660
    }
2661
    
2662
    SVCERR << "MainWindow::checkpointSession: saving to session file: "
2663
           << m_sessionFile << endl;
2664

    
2665
    SmallSession session(makeSmallSession());
2666

    
2667
    try {
2668
        SmallSession::save(session, m_sessionFile);
2669
        m_recentSessions.addFile(m_sessionFile, makeSessionLabel());
2670
        CommandHistory::getInstance()->documentSaved();
2671
        documentRestored();
2672

    
2673
        QSettings settings;
2674
        settings.beginGroup("MainWindow");
2675
        settings.setValue("lastsession", m_sessionFile);
2676
        settings.endGroup();
2677

    
2678
        SVCERR << "MainWindow::checkpointSession complete" << endl;
2679
        
2680
    } catch (const std::runtime_error &e) {
2681
        SVCERR << "MainWindow::checkpointSession: save failed: "
2682
               << e.what() << endl;
2683
        QMessageBox::critical
2684
            (this, tr("Failed to checkpoint session"),
2685
             tr("<b>Checkpoint failed</b>"
2686
                "<p>Session checkpoint file could not be saved: %1</p>")
2687
             .arg(e.what()));
2688
    }
2689
}
2690

    
2691
SmallSession
2692
MainWindow::makeSmallSession()
2693
{
2694
    SmallSession session;
2695
    if (!m_paneStack) return session;
2696

    
2697
    auto mainModel = getMainModel();
2698
    if (!mainModel) return session;
2699

    
2700
    session.mainFile = mainModel->getLocation();
2701

    
2702
    std::set<QString> alreadyRecorded;
2703
    alreadyRecorded.insert(session.mainFile);
2704
    
2705
    for (int i = 0; i < m_paneStack->getPaneCount(); ++i) {
2706
        Pane *p = m_paneStack->getPane(i);
2707
        for (int j = 0; j < p->getLayerCount(); ++j) {
2708
            Layer *l = p->getLayer(j);
2709
            auto modelId = l->getModel();
2710
            auto sourceId = l->getSourceModel();
2711
            if (!sourceId.isNone()) {
2712
                modelId = sourceId;
2713
            }
2714
            if (auto wfm = ModelById::getAs<WaveFileModel>(modelId)) {
2715
                QString location = wfm->getLocation();
2716
                if (alreadyRecorded.find(location) == alreadyRecorded.end()) {
2717
                    session.additionalFiles.push_back(location);
2718
                    alreadyRecorded.insert(location);
2719
                }
2720
            }
2721
        }
2722
    }
2723

    
2724
    SVCERR << "MainWindow::makeSmallSession: have " << session.additionalFiles.size() << " non-main model(s)" << endl;
2725
    
2726
    return session;
2727
}
2728

    
2729
void
2730
MainWindow::mainModelChanged(ModelId modelId)
2731
{
2732
    SVDEBUG << "MainWindow::mainModelChanged(" << modelId << ")" << endl;
2733

    
2734
    if (m_sessionState == SessionLoading) {
2735
        SVDEBUG << "MainWindow::mainModelChanged: Session is loading, not (re)making session filename" << endl;
2736
    } else if (modelId.isNone()) {
2737
        SVDEBUG << "MainWindow::mainModelChanged: Null model, not (re)making session filename" << endl;
2738
    } else {
2739
        if (m_sessionState == NoSession) {
2740
            SVDEBUG << "MainWindow::mainModelChanged: Marking session as active" << endl;
2741
            m_sessionState = SessionActive;
2742
        } else {
2743
            SVDEBUG << "MainWindow::mainModelChanged: Session is active" << endl;
2744
        }
2745
        if (m_sessionFile == "") {
2746
            SVDEBUG << "MainWindow::mainModelChanged: No session file set, calling makeSessionFilename" << endl;
2747
            m_sessionFile = makeSessionFilename();
2748
        }
2749
    }
2750
    
2751
    m_salientPending.clear();
2752
    m_salientCalculating = false;
2753

    
2754
    MainWindowBase::mainModelChanged(modelId);
2755

    
2756
    if (m_playTarget || m_audioIO) {
2757
        connect(m_mainLevelPan, SIGNAL(levelChanged(float)),
2758
                this, SLOT(mainModelGainChanged(float)));
2759
        connect(m_mainLevelPan, SIGNAL(panChanged(float)),
2760
                this, SLOT(mainModelPanChanged(float)));
2761
    }
2762

    
2763
    SVDEBUG << "Pane stack pane count = " << m_paneStack->getPaneCount() << endl;
2764

    
2765
    auto model = ModelById::getAs<WaveFileModel>(modelId);
2766
    if (model &&
2767
        m_paneStack &&
2768
        (m_paneStack->getPaneCount() == 0)) {
2769
        
2770
        AddPaneCommand *command = new AddPaneCommand(this);
2771
        CommandHistory::getInstance()->addCommand(command);
2772
        Pane *pane = command->getPane();
2773
        Layer *newLayer =
2774
            m_document->createMainModelLayer(LayerFactory::Waveform);
2775
        newLayer->setObjectName(tr("Outline Waveform"));
2776
        
2777
        bool mono = (model->getChannelCount() == 1);
2778
        
2779
        QString layerPropertyXml =
2780
            QString("<layer scale=\"%1\" channelMode=\"%2\"/>")
2781
            .arg(int(WaveformLayer::MeterScale))
2782
            .arg(int(mono ?
2783
                     WaveformLayer::SeparateChannels :
2784
                     WaveformLayer::MergeChannels));
2785
        LayerFactory::getInstance()->setLayerProperties
2786
            (newLayer, layerPropertyXml);
2787
            
2788
        m_document->addLayerToView(pane, newLayer);
2789

    
2790
        addSalientFeatureLayer(pane, modelId);
2791
    }
2792

    
2793
    m_document->setAutoAlignment(m_viewManager->getAlignMode());
2794
}
2795

    
2796
void
2797
MainWindow::mainModelGainChanged(float gain)
2798
{
2799
    if (m_playTarget) {
2800
        m_playTarget->setOutputGain(gain);
2801
    } else if (m_audioIO) {
2802
        m_audioIO->setOutputGain(gain);
2803
    }
2804
}
2805

    
2806
void
2807
MainWindow::mainModelPanChanged(float balance)
2808
{
2809
    // this is indeed stereo balance rather than pan
2810
    if (m_playTarget) {
2811
        m_playTarget->setOutputBalance(balance);
2812
    } else if (m_audioIO) {
2813
        m_audioIO->setOutputBalance(balance);
2814
    }
2815
}
2816

    
2817
void
2818
MainWindow::modelGenerationFailed(QString transformName, QString message)
2819
{
2820
    if (message != "") {
2821

    
2822
        QMessageBox::warning
2823
            (this,
2824
             tr("Failed to generate layer"),
2825
             tr("<b>Layer generation failed</b><p>Failed to generate derived layer.<p>The layer transform \"%1\" failed:<p>%2")
2826
             .arg(transformName).arg(message),
2827
             QMessageBox::Ok);
2828
    } else {
2829
        QMessageBox::warning
2830
            (this,
2831
             tr("Failed to generate layer"),
2832
             tr("<b>Layer generation failed</b><p>Failed to generate a derived layer.<p>The layer transform \"%1\" failed.<p>No error information is available.")
2833
             .arg(transformName),
2834
             QMessageBox::Ok);
2835
    }
2836
}
2837

    
2838
void
2839
MainWindow::modelGenerationWarning(QString /* transformName */, QString message)
2840
{
2841
    QMessageBox::warning
2842
        (this, tr("Warning"), message, QMessageBox::Ok);
2843
}
2844

    
2845
void
2846
MainWindow::modelRegenerationFailed(QString layerName,
2847
                                    QString transformName, QString message)
2848
{
2849
    if (message != "") {
2850

    
2851
        QMessageBox::warning
2852
            (this,
2853
             tr("Failed to regenerate layer"),
2854
             tr("<b>Layer generation failed</b><p>Failed to regenerate derived layer \"%1\" using new data model as input.<p>The layer transform \"%2\" failed:<p>%3")
2855
             .arg(layerName).arg(transformName).arg(message),
2856
             QMessageBox::Ok);
2857
    } else {
2858
        QMessageBox::warning
2859
            (this,
2860
             tr("Failed to regenerate layer"),
2861
             tr("<b>Layer generation failed</b><p>Failed to regenerate derived layer \"%1\" using new data model as input.<p>The layer transform \"%2\" failed.<p>No error information is available.")
2862
             .arg(layerName).arg(transformName),
2863
             QMessageBox::Ok);
2864
    }
2865
}
2866

    
2867
void
2868
MainWindow::modelRegenerationWarning(QString layerName,
2869
                                     QString /* transformName */,
2870
                                     QString message)
2871
{
2872
    QMessageBox::warning
2873
        (this, tr("Warning"), tr("<b>Warning when regenerating layer</b><p>When regenerating the derived layer \"%1\" using new data model as input:<p>%2").arg(layerName).arg(message), QMessageBox::Ok);
2874
}
2875

    
2876
void
2877
MainWindow::alignmentComplete(ModelId modelId)
2878
{
2879
    cerr << "MainWindow::alignmentComplete(" << modelId << ")" << endl;
2880
    mapSalientFeatureLayer(modelId);
2881
    checkpointSession();
2882
}
2883

    
2884
void
2885
MainWindow::alignmentFailed(QString message)
2886
{
2887
    QMessageBox::warning
2888
        (this,
2889
         tr("Failed to calculate alignment"),
2890
         tr("<b>Alignment calculation failed</b><p>Failed to calculate an audio alignment:<p>%1")
2891
         .arg(message),
2892
         QMessageBox::Ok);
2893
}
2894

    
2895
void
2896
MainWindow::showLayerTree()
2897
{
2898
    if (!m_layerTreeView.isNull()) {
2899
        m_layerTreeView->show();
2900
        m_layerTreeView->raise();
2901
        return;
2902
    }
2903

    
2904
    //!!! should use an actual dialog class
2905
        
2906
    m_layerTreeView = new QTreeView();
2907
    LayerTreeModel *tree = new LayerTreeModel(m_paneStack);
2908
    m_layerTreeView->resize(500, 300); //!!!
2909
    m_layerTreeView->setModel(tree);
2910
    m_layerTreeView->expandAll();
2911
    m_layerTreeView->show();
2912
}
2913

    
2914
void
2915
MainWindow::handleOSCMessage(const OSCMessage & /* message */)
2916
{
2917
    cerr << "MainWindow::handleOSCMessage: Not implemented" << endl;
2918
}
2919

    
2920
void
2921
MainWindow::preferences()
2922
{
2923
    if (!m_preferencesDialog.isNull()) {
2924
        m_preferencesDialog->show();
2925
        m_preferencesDialog->raise();
2926
        return;
2927
    }
2928

    
2929
    m_preferencesDialog = new PreferencesDialog(this);
2930

    
2931
    // DeleteOnClose is safe here, because m_preferencesDialog is a
2932
    // QPointer that will be zeroed when the dialog is deleted.  We
2933
    // use it in preference to leaving the dialog lying around because
2934
    // if you Cancel the dialog, it resets the preferences state
2935
    // without resetting its own widgets, so its state will be
2936
    // incorrect when next shown unless we construct it afresh
2937
    m_preferencesDialog->setAttribute(Qt::WA_DeleteOnClose);
2938

    
2939
    m_preferencesDialog->show();
2940
}
2941

    
2942
void
2943
MainWindow::mouseEnteredWidget()
2944
{
2945
    QWidget *w = dynamic_cast<QWidget *>(sender());
2946
    if (!w) return;
2947

    
2948
    if (w == m_mainLevelPan) {
2949
        contextHelpChanged(tr("Adjust the master playback level"));
2950
    } else if (w == m_playSpeed) {
2951
        contextHelpChanged(tr("Adjust the master playback speed"));
2952
    }
2953
}
2954

    
2955
void
2956
MainWindow::mouseLeftWidget()
2957
{
2958
    contextHelpChanged("");
2959
}
2960

    
2961
void
2962
MainWindow::website()
2963
{
2964
    openHelpUrl(tr("http://www.sonicvisualiser.org/sonic-lineup/"));
2965
}
2966

    
2967
void
2968
MainWindow::help()
2969
{
2970
    openHelpUrl(tr("http://www.sonicvisualiser.org/sonic-lineup/doc/"));
2971
}
2972

    
2973
void
2974
MainWindow::whatsNew()
2975
{
2976
    QFile changelog(":CHANGELOG");
2977
    changelog.open(QFile::ReadOnly);
2978
    QByteArray content = changelog.readAll();
2979
    QString text = QString::fromUtf8(content);
2980

    
2981
    QDialog *d = new QDialog(this);
2982
    d->setWindowTitle(tr("What's New"));
2983
        
2984
    QGridLayout *layout = new QGridLayout;
2985
    d->setLayout(layout);
2986

    
2987
    int row = 0;
2988
    
2989
    QLabel *iconLabel = new QLabel;
2990
    iconLabel->setPixmap(QApplication::windowIcon().pixmap(64, 64));
2991
    layout->addWidget(iconLabel, row, 0);
2992
    
2993
    layout->addWidget
2994
        (new QLabel(tr("<h3>What's New in %1</h3>")
2995
                    .arg(QApplication::applicationName())),
2996
         row++, 1);
2997
    layout->setColumnStretch(2, 10);
2998

    
2999
    QTextEdit *textEdit = new QTextEdit;
3000
    layout->addWidget(textEdit, row++, 1, 1, 2);
3001

    
3002
    if (m_newerVersionIs != "") {
3003
        layout->addWidget(new QLabel(tr("<b>Note:</b> A newer version of %1 is available.<br>(Version %2 is available; you are using version %3)").arg(QApplication::applicationName()).arg(m_newerVersionIs).arg(VECT_VERSION)), row++, 1, 1, 2);
3004
    }
3005
    
3006
    QDialogButtonBox *bb = new QDialogButtonBox(QDialogButtonBox::Ok);
3007
    layout->addWidget(bb, row++, 0, 1, 3);
3008
    connect(bb, SIGNAL(accepted()), d, SLOT(accept()));
3009

    
3010
    text.replace('\r', "");
3011
    text.replace(QRegExp("(.)\n +(.)"), "\\1 \\2");
3012
    text.replace(QRegExp("\n - ([^\n]+)"), "\n<li>\\1</li>");
3013
    text.replace(QRegExp(": *\n"), ":\n<ul>\n");
3014
    text.replace(QRegExp("</li>\n\\s*\n"), "</li>\n</ul>\n\n");
3015
    text.replace(QRegExp("\n(\\w[^:\n]+:)"), "\n<p><b>\\1</b></p>");
3016
//    text.replace(QRegExp("<li>([^,.\n]+)([,.] +\\w)"), "<li><b>\\1</b>\\2");
3017
    
3018
    textEdit->setHtml(text);
3019
    textEdit->setReadOnly(true);
3020

    
3021
    d->setMinimumSize(m_viewManager->scalePixelSize(520),
3022
                      m_viewManager->scalePixelSize(450));
3023
    
3024
    d->exec();
3025

    
3026
    delete d;
3027
}
3028

    
3029
QString
3030
MainWindow::getReleaseText() const
3031
{
3032
    bool debug = false;
3033
    QString version = "(unknown version)";
3034

    
3035
#ifdef BUILD_DEBUG
3036
    debug = true;
3037
#endif // BUILD_DEBUG
3038
#ifdef VECT_VERSION
3039
#ifdef SVNREV
3040
    version = tr("Release %1 : Revision %2").arg(VECT_VERSION).arg(SVNREV);
3041
#else // !SVNREV
3042
    version = tr("Release %1").arg(VECT_VERSION);
3043
#endif // SVNREV
3044
#else // !VECT_VERSION
3045
#ifdef SVNREV
3046
    version = tr("Unreleased : Revision %1").arg(SVNREV);
3047
#endif // SVNREV
3048
#endif // VECT_VERSION
3049

    
3050
    return tr("%1 : %2 configuration, %3-bit build")
3051
        .arg(version)
3052
        .arg(debug ? tr("Debug") : tr("Release"))
3053
        .arg(sizeof(void *) * 8);
3054
}
3055

    
3056
void
3057
MainWindow::about()
3058
{
3059
    QString aboutText;
3060

    
3061
    aboutText += tr("<h3>About Sonic Lineup</h3>");
3062
    aboutText += tr("<p>Sonic Lineup is an application for comparative visualisation and alignment of related audio recordings.</p>");
3063
    aboutText += QString("<p><small>%1</small></p>").arg(getReleaseText());
3064

    
3065
    aboutText += 
3066
        tr("<p><small>Sonic Lineup and Sonic Visualiser application code<br>Copyright &copy; 2005&ndash;2019 Chris Cannam"
3067
           " and Queen Mary, University of London.</small></p>");
3068

    
3069
    aboutText += 
3070
        tr("<p><small>MATCH Audio Alignment plugin<br>Copyright &copy; "
3071
           "2007&ndash;2019 Simon Dixon, Chris Cannam, and Queen Mary "
3072
           "University of London;<br>Copyright &copy; 2014&ndash;2015 Tido "
3073
           "GmbH.</small></p>");
3074

    
3075
    aboutText += 
3076
        tr("<p><small>NNLS Chroma and Chordino plugin<br>Copyright &copy; "
3077
           "2008&ndash;2019 Matthias Mauch and Queen Mary "
3078
           "University of London.</small></p>");
3079

    
3080
    aboutText += 
3081
        tr("<p><small>pYIN plugin<br>Copyright &copy; "
3082
           "2012&ndash;2019 Matthias Mauch and Queen Mary "
3083
           "University of London.</small></p>");
3084

    
3085
    aboutText += 
3086
        tr("<p><small>QM Key Detector plugin<br>Copyright &copy; "
3087
           "2006&ndash;2019 Katy Noland, Christian Landone, and Queen Mary "
3088
           "University of London.</small></p>");
3089

    
3090
    aboutText += "<p><small>";
3091
    
3092
    aboutText += tr("With Qt v%1 &copy; The Qt Company").arg(QT_VERSION_STR);
3093

    
3094
    aboutText += "</small><small>";
3095

    
3096
#ifdef HAVE_JACK
3097
#ifdef JACK_VERSION
3098
    aboutText += tr("<br>With JACK audio output library v%1 &copy; Paul Davis and Jack O'Quin").arg(JACK_VERSION);
3099
#else // !JACK_VERSION
3100
    aboutText += tr("<br>With JACK audio output library &copy; Paul Davis and Jack O'Quin");
3101
#endif // JACK_VERSION
3102
#endif // HAVE_JACK
3103
#ifdef HAVE_PORTAUDIO
3104
    aboutText += tr("<br>With PortAudio audio output library &copy; Ross Bencina and Phil Burk");
3105
#endif // HAVE_PORTAUDIO
3106
#ifdef HAVE_LIBPULSE
3107
#ifdef LIBPULSE_VERSION
3108
    aboutText += tr("<br>With PulseAudio audio output library v%1 &copy; Lennart Poettering and Pierre Ossman").arg(LIBPULSE_VERSION);
3109
#else // !LIBPULSE_VERSION
3110
    aboutText += tr("<br>With PulseAudio audio output library &copy; Lennart Poettering and Pierre Ossman");
3111
#endif // LIBPULSE_VERSION
3112
#endif // HAVE_LIBPULSE
3113
#ifdef HAVE_OGGZ
3114
#ifdef OGGZ_VERSION
3115
    aboutText += tr("<br>With Ogg file decoder (oggz v%1, fishsound v%2) &copy; CSIRO Australia").arg(OGGZ_VERSION).arg(FISHSOUND_VERSION);
3116
#else // !OGGZ_VERSION
3117
    aboutText += tr("<br>With Ogg file decoder &copy; CSIRO Australia");
3118
#endif // OGGZ_VERSION
3119
#endif // HAVE_OGGZ
3120
#ifdef HAVE_OPUS
3121
    aboutText += tr("<br>With Opus decoder &copy; Xiph.Org Foundation");
3122
#endif // HAVE_OPUS
3123
#ifdef HAVE_MAD
3124
#ifdef MAD_VERSION
3125
    aboutText += tr("<br>With MAD mp3 decoder v%1 &copy; Underbit Technologies Inc").arg(MAD_VERSION);
3126
#else // !MAD_VERSION
3127
    aboutText += tr("<br>With MAD mp3 decoder &copy; Underbit Technologies Inc");
3128
#endif // MAD_VERSION
3129
#endif // HAVE_MAD
3130
#ifdef HAVE_SAMPLERATE
3131
#ifdef SAMPLERATE_VERSION
3132
    aboutText += tr("<br>With libsamplerate v%1 &copy; Erik de Castro Lopo").arg(SAMPLERATE_VERSION);
3133
#else // !SAMPLERATE_VERSION
3134
    aboutText += tr("<br>With libsamplerate &copy; Erik de Castro Lopo");
3135
#endif // SAMPLERATE_VERSION
3136
#endif // HAVE_SAMPLERATE
3137
#ifdef HAVE_SNDFILE
3138
#ifdef SNDFILE_VERSION
3139
    aboutText += tr("<br>With libsndfile v%1 &copy; Erik de Castro Lopo").arg(SNDFILE_VERSION);
3140
#else // !SNDFILE_VERSION
3141
    aboutText += tr("<br>With libsndfile &copy; Erik de Castro Lopo");
3142
#endif // SNDFILE_VERSION
3143
#endif // HAVE_SNDFILE
3144
#ifdef HAVE_FFTW3F
3145
#ifdef FFTW3_VERSION
3146
    aboutText += tr("<br>With FFTW3 v%1 &copy; Matteo Frigo and MIT").arg(FFTW3_VERSION);
3147
#else // !FFTW3_VERSION
3148
    aboutText += tr("<br>With FFTW3 &copy; Matteo Frigo and MIT");
3149
#endif // FFTW3_VERSION
3150
#endif // HAVE_FFTW3F
3151
#ifdef HAVE_RUBBERBAND
3152
#ifdef RUBBERBAND_VERSION
3153
    aboutText += tr("<br>With Rubber Band Library v%1 &copy; Particular Programs Ltd").arg(RUBBERBAND_VERSION);
3154
#else // !RUBBERBAND_VERSION
3155
    aboutText += tr("<br>With Rubber Band Library &copy; Particular Programs Ltd");
3156
#endif // RUBBERBAND_VERSION
3157
#endif // HAVE_RUBBERBAND
3158
    aboutText += tr("<br>With Vamp plugin support (API v%1, host SDK v%2) &copy; Chris Cannam and QMUL").arg(VAMP_API_VERSION).arg(VAMP_SDK_VERSION);
3159
#ifdef REDLAND_VERSION
3160
    aboutText += tr("<br>With Redland RDF datastore v%1 &copy; Dave Beckett and the University of Bristol").arg(REDLAND_VERSION);
3161
#else // !REDLAND_VERSION
3162
    aboutText += tr("<br>With Redland RDF datastore &copy; Dave Beckett and the University of Bristol");
3163
#endif // REDLAND_VERSION
3164
    aboutText += tr("<br>With Serd and Sord RDF parser and store &copy; David Robillard");
3165
    aboutText += "</small></p>";
3166

    
3167
    aboutText += "<p><small>";
3168
    aboutText += tr("Russian UI translation contributed by Alexandre Prokoudine.");
3169
    aboutText += "<br>";
3170
    aboutText += tr("Czech UI translation contributed by Pavel Fric.");
3171
    aboutText += "</small></p>";
3172
    
3173
    aboutText +=
3174
        "<p><small>This program is free software; you can redistribute it and/or "
3175
        "modify it under the terms of the GNU General Public License as "
3176
        "published by the Free Software Foundation; either version 2 of the "
3177
        "License, or (at your option) any later version.<br>See the file "
3178
        "COPYING included with this distribution for more information.</small></p>";
3179

    
3180
    // use our own dialog so we can influence the size
3181

    
3182
    QDialog *d = new QDialog(this);
3183

    
3184
    d->setWindowTitle(tr("About %1").arg(QApplication::applicationName()));
3185
        
3186
    QGridLayout *layout = new QGridLayout;
3187
    d->setLayout(layout);
3188

    
3189
    int row = 0;
3190
    
3191
    QLabel *iconLabel = new QLabel;
3192
    iconLabel->setPixmap(QApplication::windowIcon().pixmap(64, 64));
3193
    layout->addWidget(iconLabel, row, 0, Qt::AlignTop);
3194

    
3195
    QLabel *mainText = new QLabel();
3196
    layout->addWidget(mainText, row, 1, 1, 2);
3197

    
3198
    layout->setRowStretch(row, 10);
3199
    layout->setColumnStretch(1, 10);
3200

    
3201
    ++row;
3202

    
3203
    QDialogButtonBox *bb = new QDialogButtonBox(QDialogButtonBox::Ok);
3204
    layout->addWidget(bb, row++, 0, 1, 3);
3205
    connect(bb, SIGNAL(accepted()), d, SLOT(accept()));
3206

    
3207
    mainText->setWordWrap(true);
3208
    mainText->setOpenExternalLinks(true);
3209
    mainText->setText(aboutText);
3210

    
3211
    d->setMinimumSize(m_viewManager->scalePixelSize(420),
3212
                      m_viewManager->scalePixelSize(200));
3213
    
3214
    d->exec();
3215

    
3216
    delete d;
3217
}
3218

    
3219
void
3220
MainWindow::keyReference()
3221
{
3222
    m_keyReference->show();
3223
}
3224

    
3225
void
3226
MainWindow::newerVersionAvailable(QString version)
3227
{
3228
    m_newerVersionIs = version;
3229
    
3230
    //!!! nicer URL would be nicer
3231
    QSettings settings;
3232
    settings.beginGroup("NewerVersionWarning");
3233
    QString tag = QString("version-%1-available-show").arg(version);
3234
    if (settings.value(tag, true).toBool()) {
3235
        QString title(tr("Newer version available"));
3236
        QString text(tr("<h3>Newer version available</h3><p>You are using version %1 of %2, but version %3 is now available.</p><p>Please see the <a href=\"http://www.sonicvisualiser.org/sonic-lineup/\">%4 website</a> for more information.</p>").arg(VECT_VERSION).arg(QApplication::applicationName()).arg(version).arg(QApplication::applicationName()));
3237
        QMessageBox::information(this, title, text);
3238
        settings.setValue(tag, false);
3239
    }
3240
    settings.endGroup();
3241
}
3242

    
3243
void
3244
MainWindow::loadStyle()
3245
{
3246
    m_viewManager->setGlobalDarkBackground(true);
3247

    
3248
#ifdef Q_OS_MAC    
3249
    QString stylepath = ":vect-mac.qss";
3250
#else
3251
    QString stylepath = ":vect.qss";
3252
#endif
3253

    
3254
    QFile file(stylepath);
3255
    if (!file.open(QFile::ReadOnly)) {
3256
        SVCERR << "WARNING: Failed to open style file " << stylepath << endl;
3257
    } else {
3258
        QString styleSheet = QLatin1String(file.readAll());
3259
        qApp->setStyleSheet(styleSheet);
3260
        QPalette pal(Qt::white, Qt::gray, Qt::white, Qt::black, Qt::gray, Qt::white, Qt::white, Qt::black, Qt::black);
3261
        qApp->setPalette(pal);
3262
    }
3263
}
3264

    
3265

    
3266