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 @ 175:741f88f51b06

History | View | Annotate | Download (83.7 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-2012 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 "PreferencesDialog.h"
21

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

    
71
//!!!
72
#include "data/model/AggregateWaveModel.h"
73

    
74
// For version information
75
#include "vamp/vamp.h"
76
#include "vamp-sdk/PluginBase.h"
77
#include "plugin/api/ladspa.h"
78
#include "plugin/api/dssi.h"
79

    
80
#include <bqaudioio/SystemPlaybackTarget.h>
81
#include <bqaudioio/SystemAudioIO.h>
82

    
83
#include <QApplication>
84
#include <QMessageBox>
85
#include <QGridLayout>
86
#include <QLabel>
87
#include <QAction>
88
#include <QMenuBar>
89
#include <QToolBar>
90
#include <QToolButton>
91
#include <QButtonGroup>
92
#include <QInputDialog>
93
#include <QStatusBar>
94
#include <QTreeView>
95
#include <QFile>
96
#include <QFileInfo>
97
#include <QDir>
98
#include <QTextStream>
99
#include <QProcess>
100
#include <QShortcut>
101
#include <QSettings>
102
#include <QDateTime>
103
#include <QProcess>
104
#include <QCheckBox>
105
#include <QRegExp>
106
#include <QScrollArea>
107
#include <QCloseEvent>
108

    
109
#include <iostream>
110
#include <cstdio>
111
#include <errno.h>
112

    
113
using std::cerr;
114
using std::endl;
115

    
116
using std::vector;
117
using std::map;
118
using std::set;
119
using std::pair;
120

    
121

    
122
MainWindow::MainWindow(bool withAudioOutput) :
123
    MainWindowBase(withAudioOutput ? WithAudioOutput : WithNothing),
124
    m_mainMenusCreated(false),
125
    m_playbackMenu(0),
126
    m_recentSessionsMenu(0),
127
    m_deleteSelectedAction(0),
128
    m_ffwdAction(0),
129
    m_rwdAction(0),
130
    m_recentSessions("RecentSessions", 20),
131
    m_exiting(false),
132
    m_preferencesDialog(0),
133
    m_layerTreeView(0),
134
    m_keyReference(new KeyReference()),
135
    m_displayMode(OutlineWaveformMode),
136
    m_salientCalculating(false),
137
    m_salientColour(0),
138
    m_sessionState(NoSession)
139
{
140
    setWindowTitle(QApplication::applicationName());
141

    
142
    UnitDatabase *udb = UnitDatabase::getInstance();
143
    udb->registerUnit("Hz");
144
    udb->registerUnit("dB");
145
    udb->registerUnit("s");
146

    
147
    ColourDatabase *cdb = ColourDatabase::getInstance();
148
    cdb->setUseDarkBackground(cdb->addColour(Qt::white, tr("White")), true);
149
    cdb->setUseDarkBackground(cdb->addColour(QColor(30, 150, 255), tr("Bright Blue")), true);
150
    cdb->setUseDarkBackground(cdb->addColour(Qt::red, tr("Bright Red")), true);
151
    cdb->setUseDarkBackground(cdb->addColour(Qt::green, tr("Bright Green")), true);
152
    cdb->setUseDarkBackground(cdb->addColour(QColor(255, 188, 80), tr("Bright Orange")), true);
153
    cdb->setUseDarkBackground(cdb->addColour(QColor(225, 74, 255), tr("Bright Purple")), true);
154
    cdb->setUseDarkBackground(cdb->addColour(Qt::yellow, tr("Bright Yellow")), true);
155

    
156
    Preferences::getInstance()->setResampleOnLoad(true);
157

    
158

    
159
    Preferences::getInstance()->setSpectrogramSmoothing
160
        (Preferences::SpectrogramInterpolated);
161

    
162
    Preferences::getInstance()->setSpectrogramXSmoothing
163
        (Preferences::SpectrogramXInterpolated);
164

    
165
    QSettings settings;
166

    
167
    settings.beginGroup("LayerDefaults");
168

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

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

    
175
    settings.endGroup();
176

    
177
    settings.beginGroup("MainWindow");
178
    settings.setValue("showstatusbar", false);
179
    settings.endGroup();
180

    
181
    settings.beginGroup("IconLoader");
182
    settings.setValue("invert-icons-on-dark-background", true);
183
    settings.endGroup();
184

    
185
    settings.beginGroup("View");
186
    settings.setValue("showcancelbuttons", false);
187
    settings.endGroup();
188

    
189
    m_viewManager->setAlignMode(true);
190
    m_viewManager->setPlaySoloMode(true);
191
    m_viewManager->setToolMode(ViewManager::NavigateMode);
192
    m_viewManager->setZoomWheelsEnabled(false);
193
    m_viewManager->setIlluminateLocalFeatures(false);
194
    m_viewManager->setShowWorkTitle(true);
195

    
196
    loadStyle();
197
    
198
    QFrame *mainFrame = new QFrame;
199
    QGridLayout *mainLayout = new QGridLayout;
200

    
201
    setCentralWidget(mainFrame);
202
    
203
    m_mainScroll = new QScrollArea(mainFrame);
204
    m_mainScroll->setWidgetResizable(true);
205
    m_mainScroll->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
206
    m_mainScroll->setFrameShape(QFrame::NoFrame);
207

    
208
    m_paneStack->setResizeMode(PaneStack::AutoResizeOnly);
209
    m_paneStack->setLayoutStyle(PaneStack::NoPropertyStacks);
210
    m_paneStack->setShowAlignmentViews(true);
211
    m_mainScroll->setWidget(m_paneStack);
212

    
213
    QFrame *bottomFrame = new QFrame(mainFrame);
214
    bottomFrame->setObjectName("BottomFrame");
215
    QGridLayout *bottomLayout = new QGridLayout;
216

    
217
    int bottomElementHeight = m_viewManager->scalePixelSize(45);
218
    if (bottomElementHeight < 40) bottomElementHeight = 40;
219
    int bottomButtonHeight = (bottomElementHeight * 3) / 4;
220
    
221
    QButtonGroup *bg = new QButtonGroup;
222
    IconLoader il;
223

    
224
    QFrame *buttonFrame = new QFrame(bottomFrame);
225
    QHBoxLayout *buttonLayout = new QHBoxLayout;
226
    buttonLayout->setSpacing(0);
227
    buttonLayout->setMargin(0);
228
    buttonFrame->setLayout(buttonLayout);
229

    
230
    QPushButton *button = new QPushButton;
231
    button->setIcon(il.load("waveform"));
232
    button->setText(tr("Outline waveform"));
233
    button->setCheckable(true);
234
    button->setChecked(true);
235
    button->setFixedHeight(bottomButtonHeight);
236
    bg->addButton(button);
237
    buttonLayout->addWidget(button);
238
    connect(button, SIGNAL(clicked()), this, SLOT(outlineWaveformModeSelected()));
239
    m_modeButtons[OutlineWaveformMode] = button;
240

    
241
    button = new QPushButton;
242
    button->setIcon(il.load("waveform"));
243
    button->setText(tr("Waveform"));
244
    button->setCheckable(true);
245
    button->setChecked(true);
246
    button->setFixedHeight(bottomButtonHeight);
247
    bg->addButton(button);
248
    buttonLayout->addWidget(button);
249
    connect(button, SIGNAL(clicked()), this, SLOT(standardWaveformModeSelected()));
250
    m_modeButtons[WaveformMode] = button;
251

    
252
    button = new QPushButton;
253
    button->setIcon(il.load("values"));
254
    button->setText(tr("Spectral flux"));
255
    button->setCheckable(true);
256
    button->setChecked(false);
257
    button->setFixedHeight(bottomButtonHeight);
258
    bg->addButton(button);
259
    buttonLayout->addWidget(button);
260
    connect(button, SIGNAL(clicked()), this, SLOT(curveModeSelected()));
261
    m_modeButtons[CurveMode] = button;
262

    
263
    button = new QPushButton;
264
    button->setIcon(il.load("values"));
265
    button->setText(tr("Sung pitch"));
266
    button->setCheckable(true);
267
    button->setChecked(false);
268
    button->setFixedHeight(bottomButtonHeight);
269
    bg->addButton(button);
270
    buttonLayout->addWidget(button);
271
    connect(button, SIGNAL(clicked()), this, SLOT(pitchModeSelected()));
272
    m_modeButtons[PitchMode] = button;
273

    
274
    button = new QPushButton;
275
    button->setIcon(il.load("colour3d"));
276
    button->setText(tr("Stereo azimuth"));
277
    button->setCheckable(true);
278
    button->setChecked(false);
279
    button->setFixedHeight(bottomButtonHeight);
280
    bg->addButton(button);
281
    buttonLayout->addWidget(button);
282
    connect(button, SIGNAL(clicked()), this, SLOT(azimuthModeSelected()));
283
    m_modeButtons[AzimuthMode] = button;
284

    
285
    button = new QPushButton;
286
    button->setIcon(il.load("colour3d"));
287
    button->setText(tr("Spectrogram"));
288
    button->setCheckable(true);
289
    button->setChecked(false);
290
    button->setFixedHeight(bottomButtonHeight);
291
    bg->addButton(button);
292
    buttonLayout->addWidget(button);
293
    connect(button, SIGNAL(clicked()), this, SLOT(spectrogramModeSelected()));
294
    m_modeButtons[SpectrogramMode] = button;
295

    
296
    button = new QPushButton;
297
    button->setIcon(il.load("colour3d"));
298
    button->setText(tr("Melodic spectrogram"));
299
    button->setCheckable(true);
300
    button->setChecked(false);
301
    button->setFixedHeight(bottomButtonHeight);
302
    bg->addButton(button);
303
    buttonLayout->addWidget(button);
304
    connect(button, SIGNAL(clicked()), this, SLOT(melodogramModeSelected()));
305
    m_modeButtons[MelodogramMode] = button;
306

    
307
    m_playSpeed = new AudioDial(bottomFrame);
308
    m_playSpeed->setMinimum(0);
309
    m_playSpeed->setMaximum(120);
310
    m_playSpeed->setValue(60);
311
    m_playSpeed->setFixedWidth(bottomElementHeight);
312
    m_playSpeed->setFixedHeight(bottomElementHeight);
313
    m_playSpeed->setNotchesVisible(true);
314
    m_playSpeed->setPageStep(10);
315
    m_playSpeed->setObjectName(tr("Playback Speed"));
316
    m_playSpeed->setRangeMapper(new PlaySpeedRangeMapper);
317
    m_playSpeed->setDefaultValue(60);
318
    m_playSpeed->setShowToolTip(true);
319
    connect(m_playSpeed, SIGNAL(valueChanged(int)),
320
            this, SLOT(playSpeedChanged(int)));
321
    connect(m_playSpeed, SIGNAL(mouseEntered()), this, SLOT(mouseEnteredWidget()));
322
    connect(m_playSpeed, SIGNAL(mouseLeft()), this, SLOT(mouseLeftWidget()));
323

    
324
    m_mainLevelPan = new LevelPanToolButton(bottomFrame);
325
    connect(m_mainLevelPan, SIGNAL(mouseEntered()), this, SLOT(mouseEnteredWidget()));
326
    connect(m_mainLevelPan, SIGNAL(mouseLeft()), this, SLOT(mouseLeftWidget()));
327
    m_mainLevelPan->setFixedHeight(bottomElementHeight);
328
    m_mainLevelPan->setFixedWidth(bottomElementHeight);
329
    m_mainLevelPan->setImageSize((bottomElementHeight * 3) / 4);
330
    m_mainLevelPan->setBigImageSize(bottomElementHeight * 3);
331

    
332
    int spacing = m_viewManager->scalePixelSize(4);
333

    
334
    bottomLayout->setSpacing(spacing);
335
    bottomLayout->setMargin(spacing);
336
    bottomLayout->addWidget(buttonFrame, 0, 0);
337
    bottomLayout->setColumnStretch(1, 10);
338
    bottomLayout->addWidget(m_playSpeed, 0, 2);
339
    bottomLayout->addWidget(m_mainLevelPan, 0, 3);
340
    bottomFrame->setLayout(bottomLayout);
341
    
342
    mainLayout->setSpacing(spacing);
343
    mainLayout->setMargin(spacing);
344
    mainLayout->addWidget(m_mainScroll, 0, 0);
345
    mainLayout->addWidget(bottomFrame, 1, 0);
346
    mainFrame->setLayout(mainLayout);
347

    
348
    setupMenus();
349
    setupToolbars();
350
    setupHelpMenu();
351

    
352
    statusBar()->hide();
353

    
354
    setIconsVisibleInMenus(false);
355
    finaliseMenus();
356

    
357
    openMostRecentSession();
358
}
359

    
360
MainWindow::~MainWindow()
361
{
362
    delete m_keyReference;
363
    delete m_preferencesDialog;
364
    delete m_layerTreeView;
365
    Profiles::getInstance()->dump();
366
}
367

    
368
void
369
MainWindow::setupMenus()
370
{
371
    if (!m_mainMenusCreated) {
372

    
373
#ifdef Q_OS_LINUX
374
        // In Ubuntu 14.04 the window's menu bar goes missing entirely
375
        // if the user is running any desktop environment other than Unity
376
        // (in which the faux single-menubar appears). The user has a
377
        // workaround, to remove the appmenu-qt5 package, but that is
378
        // awkward and the problem is so severe that it merits disabling
379
        // the system menubar integration altogether. Like this:
380
        menuBar()->setNativeMenuBar(false);
381
#endif
382
    }
383

    
384
    setupFileMenu();
385
    setupViewMenu();
386

    
387
    m_mainMenusCreated = true;
388
}
389

    
390
void
391
MainWindow::goFullScreen()
392
{
393
    if (!m_viewManager) return;
394

    
395
    if (m_viewManager->getZoomWheelsEnabled()) {
396
        // The wheels seem to end up in the wrong place in full-screen mode
397
        toggleZoomWheels();
398
    }
399

    
400
    QWidget *ps = m_mainScroll->takeWidget();
401
    ps->setParent(0);
402

    
403
    QShortcut *sc;
404

    
405
    sc = new QShortcut(QKeySequence("Esc"), ps);
406
    connect(sc, SIGNAL(activated()), this, SLOT(endFullScreen()));
407

    
408
    sc = new QShortcut(QKeySequence("F11"), ps);
409
    connect(sc, SIGNAL(activated()), this, SLOT(endFullScreen()));
410

    
411
    QAction *acts[] = {
412
        m_playAction, m_zoomInAction, m_zoomOutAction, m_zoomFitAction,
413
        m_scrollLeftAction, m_scrollRightAction, m_showPropertyBoxesAction
414
    };
415

    
416
    for (int i = 0; i < int(sizeof(acts)/sizeof(acts[0])); ++i) {
417
        sc = new QShortcut(acts[i]->shortcut(), ps);
418
        connect(sc, SIGNAL(activated()), acts[i], SLOT(trigger()));
419
    }
420

    
421
    ps->showFullScreen();
422
}
423

    
424
void
425
MainWindow::endFullScreen()
426
{
427
    // these were only created in goFullScreen:
428
    QObjectList cl = m_paneStack->children();
429
    foreach (QObject *o, cl) {
430
        QShortcut *sc = qobject_cast<QShortcut *>(o);
431
        if (sc) delete sc;
432
    }
433

    
434
    m_paneStack->showNormal();
435
    m_mainScroll->setWidget(m_paneStack);
436
}
437

    
438
void
439
MainWindow::setupFileMenu()
440
{
441
    if (m_mainMenusCreated) return;
442

    
443
    QMenu *menu = menuBar()->addMenu(tr("&File"));
444
    menu->setTearOffEnabled(false);
445
    QToolBar *toolbar = addToolBar(tr("File Toolbar"));
446

    
447
    m_keyReference->setCategory(tr("File and Session Management"));
448

    
449
    IconLoader il;
450

    
451
    QIcon icon = il.load("filenew");
452
    QAction *action = new QAction(icon, tr("&Clear Session"), this);
453
    action->setShortcut(tr("Ctrl+N"));
454
    action->setStatusTip(tr("Abandon the current session and start a new one"));
455
    connect(action, SIGNAL(triggered()), this, SLOT(newSession()));
456
    m_keyReference->registerShortcut(action);
457
    menu->addAction(action);
458
    toolbar->addAction(action);
459

    
460
    icon = il.load("fileopen");
461
    action = new QAction(icon, tr("&Add Files..."), this);
462
    action->setShortcut(tr("Ctrl+O"));
463
    action->setStatusTip(tr("Add one or more audio files"));
464
    connect(action, SIGNAL(triggered()), this, SLOT(openFiles()));
465
    m_keyReference->registerShortcut(action);
466
    menu->addAction(action);
467
    toolbar->addAction(action);
468

    
469
    action = new QAction(tr("Add Lo&cation..."), this);
470
    action->setShortcut(tr("Ctrl+Shift+O"));
471
    action->setStatusTip(tr("Add a file from a remote URL"));
472
    connect(action, SIGNAL(triggered()), this, SLOT(openLocation()));
473
    m_keyReference->registerShortcut(action);
474
    menu->addAction(action);
475

    
476
    menu->addSeparator();
477

    
478
    m_recentSessionsMenu = menu->addMenu(tr("&Recent Sessions"));
479
    m_recentSessionsMenu->setTearOffEnabled(false);
480
    setupRecentSessionsMenu();
481
    connect(&m_recentSessions, SIGNAL(recentChanged()),
482
            this, SLOT(setupRecentSessionsMenu()));
483

    
484
    menu->addSeparator();
485
    action = new QAction(tr("&Preferences..."), this);
486
    action->setStatusTip(tr("Adjust the application preferences"));
487
    connect(action, SIGNAL(triggered()), this, SLOT(preferences()));
488
    menu->addAction(action);
489

    
490
    menu->addSeparator();
491
    action = new QAction(il.load("exit"), tr("&Quit"), this);
492
    action->setShortcut(tr("Ctrl+Q"));
493
    action->setStatusTip(tr("Exit Sonic Vector"));
494
    connect(action, SIGNAL(triggered()), this, SLOT(close()));
495
    m_keyReference->registerShortcut(action);
496
    menu->addAction(action);
497
}
498

    
499
void
500
MainWindow::setupViewMenu()
501
{
502
    if (m_mainMenusCreated) return;
503

    
504
    IconLoader il;
505

    
506
    QAction *action = 0;
507

    
508
    m_keyReference->setCategory(tr("Panning and Navigation"));
509

    
510
    QMenu *menu = menuBar()->addMenu(tr("&View"));
511
    menu->setTearOffEnabled(false);
512
    m_scrollLeftAction = new QAction(tr("Scroll &Left"), this);
513
    m_scrollLeftAction->setShortcut(tr("Left"));
514
    m_scrollLeftAction->setStatusTip(tr("Scroll the current pane to the left"));
515
    connect(m_scrollLeftAction, SIGNAL(triggered()), this, SLOT(scrollLeft()));
516
    connect(this, SIGNAL(canScroll(bool)), m_scrollLeftAction, SLOT(setEnabled(bool)));
517
    m_keyReference->registerShortcut(m_scrollLeftAction);
518
    menu->addAction(m_scrollLeftAction);
519
        
520
    m_scrollRightAction = new QAction(tr("Scroll &Right"), this);
521
    m_scrollRightAction->setShortcut(tr("Right"));
522
    m_scrollRightAction->setStatusTip(tr("Scroll the current pane to the right"));
523
    connect(m_scrollRightAction, SIGNAL(triggered()), this, SLOT(scrollRight()));
524
    connect(this, SIGNAL(canScroll(bool)), m_scrollRightAction, SLOT(setEnabled(bool)));
525
    m_keyReference->registerShortcut(m_scrollRightAction);
526
    menu->addAction(m_scrollRightAction);
527
        
528
    action = new QAction(tr("&Jump Left"), this);
529
    action->setShortcut(tr("Ctrl+Left"));
530
    action->setStatusTip(tr("Scroll the current pane a big step to the left"));
531
    connect(action, SIGNAL(triggered()), this, SLOT(jumpLeft()));
532
    connect(this, SIGNAL(canScroll(bool)), action, SLOT(setEnabled(bool)));
533
    m_keyReference->registerShortcut(action);
534
    menu->addAction(action);
535
        
536
    action = new QAction(tr("J&ump Right"), this);
537
    action->setShortcut(tr("Ctrl+Right"));
538
    action->setStatusTip(tr("Scroll the current pane a big step to the right"));
539
    connect(action, SIGNAL(triggered()), this, SLOT(jumpRight()));
540
    connect(this, SIGNAL(canScroll(bool)), action, SLOT(setEnabled(bool)));
541
    m_keyReference->registerShortcut(action);
542
    menu->addAction(action);
543

    
544
    menu->addSeparator();
545

    
546
    m_keyReference->setCategory(tr("Zoom"));
547

    
548
    m_zoomInAction = new QAction(il.load("zoom-in"),
549
                                 tr("Zoom &In"), this);
550
    m_zoomInAction->setShortcut(tr("Up"));
551
    m_zoomInAction->setStatusTip(tr("Increase the zoom level"));
552
    connect(m_zoomInAction, SIGNAL(triggered()), this, SLOT(zoomIn()));
553
    connect(this, SIGNAL(canZoom(bool)), m_zoomInAction, SLOT(setEnabled(bool)));
554
    m_keyReference->registerShortcut(m_zoomInAction);
555
    menu->addAction(m_zoomInAction);
556
        
557
    m_zoomOutAction = new QAction(il.load("zoom-out"),
558
                                  tr("Zoom &Out"), this);
559
    m_zoomOutAction->setShortcut(tr("Down"));
560
    m_zoomOutAction->setStatusTip(tr("Decrease the zoom level"));
561
    connect(m_zoomOutAction, SIGNAL(triggered()), this, SLOT(zoomOut()));
562
    connect(this, SIGNAL(canZoom(bool)), m_zoomOutAction, SLOT(setEnabled(bool)));
563
    m_keyReference->registerShortcut(m_zoomOutAction);
564
    menu->addAction(m_zoomOutAction);
565
        
566
    action = new QAction(tr("Restore &Default Zoom"), this);
567
    action->setStatusTip(tr("Restore the zoom level to the default"));
568
    connect(action, SIGNAL(triggered()), this, SLOT(zoomDefault()));
569
    connect(this, SIGNAL(canZoom(bool)), action, SLOT(setEnabled(bool)));
570
    menu->addAction(action);
571

    
572
    m_zoomFitAction = new QAction(il.load("zoom-fit"),
573
                                  tr("Zoom to &Fit"), this);
574
    m_zoomFitAction->setShortcut(tr("F"));
575
    m_zoomFitAction->setStatusTip(tr("Zoom to show the whole file"));
576
    connect(m_zoomFitAction, SIGNAL(triggered()), this, SLOT(zoomToFit()));
577
    connect(this, SIGNAL(canZoom(bool)), m_zoomFitAction, SLOT(setEnabled(bool)));
578
    m_keyReference->registerShortcut(m_zoomFitAction);
579
    menu->addAction(m_zoomFitAction);
580

    
581
    menu->addSeparator();
582

    
583
    m_keyReference->setCategory(tr("Display Features"));
584

    
585
    action = new QAction(tr("Show &Centre Line"), this);
586
    action->setShortcut(tr("'"));
587
    action->setStatusTip(tr("Show or hide the centre line"));
588
    connect(action, SIGNAL(triggered()), this, SLOT(toggleCentreLine()));
589
    action->setCheckable(true);
590
    action->setChecked(true);
591
    m_keyReference->registerShortcut(action);
592
    menu->addAction(action);
593

    
594
    action = new QAction(tr("Show Salient Feature Layers"), this);
595
    action->setShortcut(tr("#"));
596
    action->setStatusTip(tr("Show or hide all salient-feature layers"));
597
    connect(action, SIGNAL(triggered()), this, SLOT(toggleSalientFeatures()));
598
    action->setCheckable(true);
599
    action->setChecked(true);
600
    m_keyReference->registerShortcut(action);
601
    menu->addAction(action);
602

    
603
    menu->addSeparator();
604

    
605
    QActionGroup *overlayGroup = new QActionGroup(this);
606
        
607
    action = new QAction(tr("Show &No Overlays"), this);
608
    action->setShortcut(tr("0"));
609
    action->setStatusTip(tr("Hide times, layer names, and scale"));
610
    connect(action, SIGNAL(triggered()), this, SLOT(showNoOverlays()));
611
    action->setCheckable(true);
612
    action->setChecked(false);
613
    overlayGroup->addAction(action);
614
    m_keyReference->registerShortcut(action);
615
    menu->addAction(action);
616
        
617
    action = new QAction(tr("Show &Minimal Overlays"), this);
618
    action->setShortcut(tr("9"));
619
    action->setStatusTip(tr("Show times and basic scale"));
620
    connect(action, SIGNAL(triggered()), this, SLOT(showMinimalOverlays()));
621
    action->setCheckable(true);
622
    action->setChecked(true);
623
    overlayGroup->addAction(action);
624
    m_keyReference->registerShortcut(action);
625
    menu->addAction(action);
626
        
627
    action = new QAction(tr("Show &All Overlays"), this);
628
    action->setShortcut(tr("8"));
629
    action->setStatusTip(tr("Show times, layer names, and scale"));
630
    connect(action, SIGNAL(triggered()), this, SLOT(showAllOverlays()));
631
    action->setCheckable(true);
632
    action->setChecked(false);
633
    overlayGroup->addAction(action);
634
    m_keyReference->registerShortcut(action);
635
    menu->addAction(action);
636
        
637
    menu->addSeparator();
638

    
639
#ifndef Q_OS_MAC
640
    // Only on non-Mac platforms -- on the Mac this interacts very
641
    // badly with the "native" full-screen mode
642
    action = new QAction(tr("Go Full-Screen"), this);
643
    action->setShortcut(tr("F11"));
644
    action->setStatusTip(tr("Expand the pane area to the whole screen"));
645
    connect(action, SIGNAL(triggered()), this, SLOT(goFullScreen()));
646
    m_keyReference->registerShortcut(action);
647
    menu->addAction(action);
648
#endif
649
}
650

    
651
void
652
MainWindow::setupHelpMenu()
653
{
654
    QMenu *menu = menuBar()->addMenu(tr("&Help"));
655
    menu->setTearOffEnabled(false);
656
    
657
    m_keyReference->setCategory(tr("Help"));
658

    
659
    IconLoader il;
660

    
661
    QAction *action = new QAction(il.load("help"),
662
                                  tr("&Help Reference"), this); 
663
    action->setShortcut(tr("F1"));
664
    action->setStatusTip(tr("Open the reference manual")); 
665
    connect(action, SIGNAL(triggered()), this, SLOT(help()));
666
    m_keyReference->registerShortcut(action);
667
    menu->addAction(action);
668

    
669
    action = new QAction(tr("&Key and Mouse Reference"), this);
670
    action->setShortcut(tr("F2"));
671
    action->setStatusTip(tr("Open a window showing the keystrokes you can use"));
672
    connect(action, SIGNAL(triggered()), this, SLOT(keyReference()));
673
    m_keyReference->registerShortcut(action);
674
    menu->addAction(action);
675
    
676
    action = new QAction(tr("Sonic Vector on the &Web"), this); 
677
    action->setStatusTip(tr("Open the Sonic Vector website")); 
678
    connect(action, SIGNAL(triggered()), this, SLOT(website()));
679
    menu->addAction(action);
680
    
681
    action = new QAction(tr("&About Sonic Vector"), this); 
682
    action->setStatusTip(tr("Show information about Sonic Vector")); 
683
    connect(action, SIGNAL(triggered()), this, SLOT(about()));
684
    menu->addAction(action);
685
}
686

    
687
void
688
MainWindow::setupRecentSessionsMenu()
689
{
690
    m_recentSessionsMenu->clear();
691
    vector<pair<QString, QString>> sessions = m_recentSessions.getRecentEntries();
692
    for (size_t i = 0; i < sessions.size(); ++i) {
693
        QString path = sessions[i].first;
694
        QString label = sessions[i].second;
695
        if (label == "") label = path;
696
        QAction *action = new QAction(label, this);
697
        action->setObjectName(path);
698
        connect(action, SIGNAL(triggered()), this, SLOT(openRecentSession()));
699
        if (i == 0) {
700
            action->setShortcut(tr("Ctrl+R"));
701
            m_keyReference->registerShortcut
702
                (tr("Re-open"),
703
                 action->shortcut().toString(),
704
                 tr("Re-open the current or most recently opened session"));
705
        }
706
        m_recentSessionsMenu->addAction(action);
707
    }
708
}
709

    
710
void
711
MainWindow::setupToolbars()
712
{
713
    m_keyReference->setCategory(tr("Playback and Transport Controls"));
714

    
715
    IconLoader il;
716

    
717
    QMenu *menu = m_playbackMenu = menuBar()->addMenu(tr("Play&back"));
718
    menu->setTearOffEnabled(false);
719

    
720
    QToolBar *toolbar = addToolBar(tr("Playback Toolbar"));
721

    
722
    QAction *rwdStartAction = toolbar->addAction(il.load("rewind-start"),
723
                                                 tr("Rewind to Start"));
724
    rwdStartAction->setShortcut(tr("Home"));
725
    rwdStartAction->setStatusTip(tr("Rewind to the start"));
726
    connect(rwdStartAction, SIGNAL(triggered()), this, SLOT(rewindStart()));
727
    connect(this, SIGNAL(canPlay(bool)), rwdStartAction, SLOT(setEnabled(bool)));
728

    
729
    QAction *m_rwdAction = toolbar->addAction(il.load("rewind"),
730
                                              tr("Rewind"));
731
    m_rwdAction->setShortcut(tr("PgUp"));
732
    m_rwdAction->setStatusTip(tr("Rewind to the previous time instant or time ruler notch"));
733
    connect(m_rwdAction, SIGNAL(triggered()), this, SLOT(rewind()));
734
    connect(this, SIGNAL(canRewind(bool)), m_rwdAction, SLOT(setEnabled(bool)));
735

    
736
    m_playAction = toolbar->addAction(il.load("playpause"),
737
                                      tr("Play / Pause"));
738
    m_playAction->setCheckable(true);
739
    m_playAction->setShortcut(tr("Space"));
740
    m_playAction->setStatusTip(tr("Start or stop playback from the current position"));
741
    connect(m_playAction, SIGNAL(triggered()), this, SLOT(play()));
742
    connect(m_playSource, SIGNAL(playStatusChanged(bool)),
743
            m_playAction, SLOT(setChecked(bool)));
744
    connect(this, SIGNAL(canPlay(bool)), m_playAction, SLOT(setEnabled(bool)));
745

    
746
    m_ffwdAction = toolbar->addAction(il.load("ffwd"),
747
                                              tr("Fast Forward"));
748
    m_ffwdAction->setShortcut(tr("PgDown"));
749
    m_ffwdAction->setStatusTip(tr("Fast-forward to the next time instant or time ruler notch"));
750
    connect(m_ffwdAction, SIGNAL(triggered()), this, SLOT(ffwd()));
751
    connect(this, SIGNAL(canFfwd(bool)), m_ffwdAction, SLOT(setEnabled(bool)));
752

    
753
    QAction *ffwdEndAction = toolbar->addAction(il.load("ffwd-end"),
754
                                                tr("Fast Forward to End"));
755
    ffwdEndAction->setShortcut(tr("End"));
756
    ffwdEndAction->setStatusTip(tr("Fast-forward to the end"));
757
    connect(ffwdEndAction, SIGNAL(triggered()), this, SLOT(ffwdEnd()));
758
    connect(this, SIGNAL(canPlay(bool)), ffwdEndAction, SLOT(setEnabled(bool)));
759

    
760
    QAction *alAction = 0;
761
    alAction = toolbar->addAction(il.load("align"),
762
                                  tr("Align File Timelines"));
763
    alAction->setCheckable(true);
764
    alAction->setChecked(m_viewManager->getAlignMode());
765
    alAction->setStatusTip(tr("Treat multiple audio files as versions of the same work, and align their timelines"));
766
    connect(m_viewManager, SIGNAL(alignModeChanged(bool)),
767
            alAction, SLOT(setChecked(bool)));
768
    connect(alAction, SIGNAL(triggered()), this, SLOT(alignToggled()));
769

    
770
    QSettings settings;
771

    
772
    QAction *tdAction = 0;
773
    tdAction = new QAction(tr("Adjust for Tuning Differences when Aligning"));
774
    tdAction->setCheckable(true);
775
    settings.beginGroup("Alignment");
776
    tdAction->setChecked(settings.value("align-pitch-aware", false).toBool());
777
    settings.endGroup();
778
    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"));
779
    connect(tdAction, SIGNAL(triggered()), this, SLOT(tuningDifferenceToggled()));
780

    
781
    m_keyReference->registerShortcut(m_playAction);
782
    m_keyReference->registerShortcut(m_rwdAction);
783
    m_keyReference->registerShortcut(m_ffwdAction);
784
    m_keyReference->registerShortcut(rwdStartAction);
785
    m_keyReference->registerShortcut(ffwdEndAction);
786

    
787
    menu->addAction(m_playAction);
788
    menu->addSeparator();
789
    menu->addAction(m_rwdAction);
790
    menu->addAction(m_ffwdAction);
791
    menu->addSeparator();
792
    menu->addAction(rwdStartAction);
793
    menu->addAction(ffwdEndAction);
794
    menu->addSeparator();
795
    menu->addAction(alAction);
796
    menu->addAction(tdAction);
797
    menu->addSeparator();
798

    
799
    QAction *fastAction = menu->addAction(tr("Speed Up"));
800
    fastAction->setShortcut(tr("Ctrl+PgUp"));
801
    fastAction->setStatusTip(tr("Time-stretch playback to speed it up without changing pitch"));
802
    connect(fastAction, SIGNAL(triggered()), this, SLOT(speedUpPlayback()));
803
    connect(this, SIGNAL(canSpeedUpPlayback(bool)), fastAction, SLOT(setEnabled(bool)));
804
    
805
    QAction *slowAction = menu->addAction(tr("Slow Down"));
806
    slowAction->setShortcut(tr("Ctrl+PgDown"));
807
    slowAction->setStatusTip(tr("Time-stretch playback to slow it down without changing pitch"));
808
    connect(slowAction, SIGNAL(triggered()), this, SLOT(slowDownPlayback()));
809
    connect(this, SIGNAL(canSlowDownPlayback(bool)), slowAction, SLOT(setEnabled(bool)));
810

    
811
    QAction *normalAction = menu->addAction(tr("Restore Normal Speed"));
812
    normalAction->setShortcut(tr("Ctrl+Home"));
813
    normalAction->setStatusTip(tr("Restore non-time-stretched playback"));
814
    connect(normalAction, SIGNAL(triggered()), this, SLOT(restoreNormalPlayback()));
815
    connect(this, SIGNAL(canChangePlaybackSpeed(bool)), normalAction, SLOT(setEnabled(bool)));
816

    
817
    m_keyReference->registerShortcut(fastAction);
818
    m_keyReference->registerShortcut(slowAction);
819
    m_keyReference->registerShortcut(normalAction);
820

    
821
    Pane::registerShortcuts(*m_keyReference);
822
}
823

    
824
void
825
MainWindow::updateMenuStates()
826
{
827
    MainWindowBase::updateMenuStates();
828

    
829
    Pane *currentPane = 0;
830
    Layer *currentLayer = 0;
831

    
832
    if (m_paneStack) currentPane = m_paneStack->getCurrentPane();
833
    if (currentPane) currentLayer = currentPane->getSelectedLayer();
834

    
835
    bool haveCurrentPane =
836
        (currentPane != 0);
837
    bool haveCurrentLayer =
838
        (haveCurrentPane &&
839
         (currentLayer != 0));
840
    bool haveCurrentTimeInstantsLayer = 
841
        (haveCurrentLayer &&
842
         dynamic_cast<TimeInstantLayer *>(currentLayer));
843
    bool haveCurrentTimeValueLayer = 
844
        (haveCurrentLayer &&
845
         dynamic_cast<TimeValueLayer *>(currentLayer));
846

    
847
    emit canChangePlaybackSpeed(true);
848
    int v = m_playSpeed->value();
849
    emit canSpeedUpPlayback(v < m_playSpeed->maximum());
850
    emit canSlowDownPlayback(v > m_playSpeed->minimum());
851

    
852
    if (m_ffwdAction && m_rwdAction) {
853
        if (haveCurrentTimeInstantsLayer) {
854
            m_ffwdAction->setText(tr("Fast Forward to Next Instant"));
855
            m_ffwdAction->setStatusTip(tr("Fast forward to the next time instant in the current layer"));
856
            m_rwdAction->setText(tr("Rewind to Previous Instant"));
857
            m_rwdAction->setStatusTip(tr("Rewind to the previous time instant in the current layer"));
858
        } else if (haveCurrentTimeValueLayer) {
859
            m_ffwdAction->setText(tr("Fast Forward to Next Point"));
860
            m_ffwdAction->setStatusTip(tr("Fast forward to the next point in the current layer"));
861
            m_rwdAction->setText(tr("Rewind to Previous Point"));
862
            m_rwdAction->setStatusTip(tr("Rewind to the previous point in the current layer"));
863
        } else {
864
            m_ffwdAction->setText(tr("Fast Forward"));
865
            m_ffwdAction->setStatusTip(tr("Fast forward"));
866
            m_rwdAction->setText(tr("Rewind"));
867
            m_rwdAction->setStatusTip(tr("Rewind"));
868
        }
869
    }
870
}
871

    
872
void
873
MainWindow::updateDescriptionLabel()
874
{
875
    // we don't actually have a description label
876
}
877

    
878
void
879
MainWindow::updateWindowTitle()
880
{
881
    QString title;
882

    
883
    QString sessionLabel = makeSessionLabel();
884
    
885
    if (sessionLabel != "") {
886
        title = tr("%1: %2")
887
            .arg(QApplication::applicationName())
888
            .arg(sessionLabel);
889
    } else {
890
        title = QApplication::applicationName();
891
    }
892
    
893
    setWindowTitle(title);
894
}
895

    
896
void
897
MainWindow::documentModified()
898
{
899
    MainWindowBase::documentModified();
900
}
901

    
902
void
903
MainWindow::documentRestored()
904
{
905
    MainWindowBase::documentRestored();
906
}
907

    
908
void
909
MainWindow::selectMainPane()
910
{
911
    if (m_paneStack && m_paneStack->getPaneCount() > 0) {
912
        m_paneStack->setCurrentPane(m_paneStack->getPane(0));
913
    }
914
}
915

    
916
void
917
MainWindow::newSession()
918
{
919
    cerr << "MainWindow::newSession" << endl;
920

    
921
    closeSession();
922
    createDocument();
923

    
924
    // Reset to a waveform mode; this is because it takes less time to
925
    // process & render than other modes, so we will be able to
926
    // checkpoint sooner - the result of starting out in e.g. pitch
927
    // mode can be quite strange because of the near-eternity before a
928
    // safe checkpoint can be made
929
    
930
    m_displayMode = OutlineWaveformMode;
931
    for (auto &bp : m_modeButtons) {
932
        bp.second->setChecked(false);
933
    }
934
    m_modeButtons[m_displayMode]->setChecked(true);
935
    
936
    // We need a pane, so that we have something to receive drop events
937
    
938
    Pane *pane = m_paneStack->addPane();
939

    
940
    connect(pane, SIGNAL(contextHelpChanged(const QString &)),
941
            this, SLOT(contextHelpChanged(const QString &)));
942

    
943
    m_document->setAutoAlignment(m_viewManager->getAlignMode());
944

    
945
    CommandHistory::getInstance()->clear();
946
    CommandHistory::getInstance()->documentSaved();
947
    documentRestored();
948
    updateMenuStates();
949
}
950

    
951
void
952
MainWindow::closeSession()
953
{
954
    checkpointSession();
955
    if (m_sessionState != SessionLoading) {
956
        m_sessionFile = "";
957
        m_sessionState = NoSession;
958
    }
959

    
960
    while (m_paneStack->getPaneCount() > 0) {
961

    
962
        Pane *pane = m_paneStack->getPane(m_paneStack->getPaneCount() - 1);
963

    
964
        while (pane->getLayerCount() > 0) {
965
            m_document->removeLayerFromView
966
                (pane, pane->getLayer(pane->getLayerCount() - 1));
967
        }
968

    
969
        m_paneStack->deletePane(pane);
970
    }
971

    
972
    while (m_paneStack->getHiddenPaneCount() > 0) {
973

    
974
        Pane *pane = m_paneStack->getHiddenPane
975
            (m_paneStack->getHiddenPaneCount() - 1);
976

    
977
        while (pane->getLayerCount() > 0) {
978
            m_document->removeLayerFromView
979
                (pane, pane->getLayer(pane->getLayerCount() - 1));
980
        }
981

    
982
        m_paneStack->deletePane(pane);
983
    }
984

    
985
    delete m_document;
986
    m_document = 0;
987
    m_viewManager->clearSelections();
988
    m_timeRulerLayer = 0; // document owned this
989

    
990
    setWindowTitle(tr("Sonic Vector"));
991

    
992
    CommandHistory::getInstance()->clear();
993
    CommandHistory::getInstance()->documentSaved();
994
    documentRestored();
995
}
996

    
997
void
998
MainWindow::openFiles()
999
{
1000
    QString orig = m_audioFile;
1001
    if (orig == "") orig = ".";
1002
    else orig = QFileInfo(orig).absoluteDir().canonicalPath();
1003

    
1004
    FileFinder *ff = FileFinder::getInstance();
1005

    
1006
    QStringList paths = ff->getOpenFileNames(FileFinder::AudioFile,
1007
                                             m_audioFile);
1008

    
1009
    m_sessionState = SessionActive;
1010
    
1011
    for (QString path: paths) {
1012
        
1013
        FileOpenStatus status = openPath(path, CreateAdditionalModel);
1014

    
1015
        if (status != FileOpenSucceeded) {
1016
            QMessageBox::critical(this, tr("Failed to open file"),
1017
                                  tr("<b>File open failed</b><p>File \"%1\" could not be opened").arg(path));
1018
        } else {
1019
            configureNewPane(m_paneStack->getCurrentPane());
1020
        }
1021
    }
1022
}
1023

    
1024
void
1025
MainWindow::openLocation()
1026
{
1027
    QSettings settings;
1028
    settings.beginGroup("MainWindow");
1029
    QString lastLocation = settings.value("lastremote", "").toString();
1030

    
1031
    bool ok = false;
1032
    QString text = QInputDialog::getText
1033
        (this, tr("Open Location"),
1034
         tr("Please enter the URL of the location to open:"),
1035
         QLineEdit::Normal, lastLocation, &ok);
1036

    
1037
    if (!ok) return;
1038

    
1039
    settings.setValue("lastremote", text);
1040

    
1041
    if (text.isEmpty()) return;
1042

    
1043
    m_sessionState = SessionActive;
1044
    
1045
    FileOpenStatus status = openPath(text, CreateAdditionalModel);
1046

    
1047
    if (status == FileOpenFailed) {
1048
        QMessageBox::critical(this, tr("Failed to open location"),
1049
                              tr("<b>Open failed</b><p>URL \"%1\" could not be opened").arg(text));
1050
    } else if (status == FileOpenWrongMode) {
1051
        QMessageBox::critical(this, tr("Failed to open location"),
1052
                              tr("<b>Audio required</b><p>Please load at least one audio file before importing annotation data"));
1053
    } else {
1054
        configureNewPane(m_paneStack->getCurrentPane());
1055
    }
1056
}
1057

    
1058
void
1059
MainWindow::openRecentSession()
1060
{
1061
    QObject *obj = sender();
1062
    QAction *action = dynamic_cast<QAction *>(obj);
1063
    
1064
    if (!action) {
1065
        cerr << "WARNING: MainWindow::openRecentSession: sender is not an action"
1066
                  << endl;
1067
        return;
1068
    }
1069

    
1070
    QString path = action->objectName();
1071
    
1072
    if (path == "") {
1073
        cerr << "WARNING: MainWindow::openRecentSession: action incorrectly named"
1074
             << endl;
1075
        return;
1076
    }
1077

    
1078
    m_sessionFile = path;
1079
    m_sessionState = SessionLoading;
1080

    
1081
    SVDEBUG << "MainWindow::openRecentSession: m_sessionFile is now "
1082
            << m_sessionFile << endl;
1083
    
1084
    FileOpenStatus status = openPath(path, ReplaceSession);
1085

    
1086
    if (status == FileOpenSucceeded) {
1087
        m_sessionState = SessionActive;
1088
        updateModeFromLayers(); // get the mode from session, then...
1089
        reselectMode();         // ...ensure there are no stragglers
1090
    } else {
1091
        m_sessionFile = "";
1092
        m_sessionState = NoSession;
1093

    
1094
        if (status == FileOpenFailed) {
1095
            QMessageBox::critical(this, tr("Failed to open location"),
1096
                                  tr("<b>Open failed</b><p>File or URL \"%1\" could not be opened").arg(path));
1097
        } else if (status == FileOpenWrongMode) {
1098
            QMessageBox::critical(this, tr("Failed to open location"),
1099
                                  tr("<b>Audio required</b><p>Please load at least one audio file before importing annotation data"));
1100
        }
1101
    }
1102
}
1103

    
1104
void
1105
MainWindow::openMostRecentSession()
1106
{
1107
    vector<QString> files = m_recentSessions.getRecentIdentifiers();
1108
    if (files.empty()) return;
1109

    
1110
    QString path = files[0];
1111
    if (path == "") return;
1112

    
1113
    m_sessionFile = path;
1114
    m_sessionState = SessionLoading;
1115

    
1116
    SVDEBUG << "MainWindow::openMostRecentSession: m_sessionFile is now "
1117
            << m_sessionFile << endl;
1118
    
1119
    FileOpenStatus status = openPath(path, ReplaceSession);
1120

    
1121
    if (status == FileOpenSucceeded) {
1122
        m_sessionState = SessionActive;
1123
        updateModeFromLayers(); // get the mode from session, then...
1124
        reselectMode();         // ...ensure there are no stragglers
1125
    } else {
1126
        m_sessionFile = "";
1127
        m_sessionState = NoSession;
1128

    
1129
        if (status == FileOpenFailed) {
1130
            QMessageBox::critical(this, tr("Failed to open location"),
1131
                                  tr("<b>Open failed</b><p>File or URL \"%1\" could not be opened").arg(path));
1132
        } else if (status == FileOpenWrongMode) {
1133
            QMessageBox::critical(this, tr("Failed to open location"),
1134
                                  tr("<b>Audio required</b><p>Please load at least one audio file before importing annotation data"));
1135
        }
1136
    }
1137
}
1138

    
1139
bool
1140
MainWindow::selectExistingLayerForMode(Pane *pane,
1141
                                       QString modeName,
1142
                                       Model **createFrom)
1143
{
1144
    // Search the given pane for any layer whose object name matches
1145
    // modeName, showing it if it exists, and hiding all other layers
1146
    // (except for time-instants layers, which are assumed to be used
1147
    // for persistent segment display and are left unmodified).
1148

    
1149
    // In the case where no such layer is found and false is returned,
1150
    // then if the return parameter createFrom is non-null, the value
1151
    // it points to will be set to a pointer to the model from which
1152
    // such a layer should be constructed.
1153

    
1154
    Model *model = 0;
1155

    
1156
    bool have = false;
1157

    
1158
    for (int i = 0; i < pane->getLayerCount(); ++i) {
1159
        
1160
        Layer *layer = pane->getLayer(i);
1161
        if (!layer || qobject_cast<TimeInstantLayer *>(layer)) {
1162
            continue;
1163
        }
1164
        
1165
        Model *lm = layer->getModel();
1166
        while (lm && lm->getSourceModel()) lm = lm->getSourceModel();
1167
        if (qobject_cast<WaveFileModel *>(lm)) model = lm;
1168
        
1169
        QString ln = layer->objectName();
1170

    
1171
        if (ln == modeName) {
1172
            layer->showLayer(pane, true);
1173
            have = true;
1174
        } else {
1175
            layer->showLayer(pane, false);
1176
        }
1177
    }
1178
    
1179
    if (have) return true;
1180

    
1181
    if (createFrom) {
1182
        *createFrom = model;
1183
    }
1184
    return false;
1185
}
1186

    
1187
void
1188
MainWindow::addSalientFeatureLayer(Pane *pane, WaveFileModel *model)
1189
{
1190
    //!!! what if there already is one? could have changed the main
1191
    //!!! model for example
1192

    
1193
    if (!model) {
1194
        cerr << "MainWindow::addSalientFeatureLayer: No model" << endl;
1195
        return;
1196
    }
1197
    
1198
    TransformFactory *tf = TransformFactory::getInstance();
1199
    if (!tf) {
1200
        cerr << "Failed to locate a transform factory!" << endl;
1201
        return;
1202
    }
1203
    
1204
    TransformId id = "vamp:nnls-chroma:chordino:simplechord";
1205
    if (!tf->haveTransform(id)) {
1206
        cerr << "No plugin available for salient feature layer; transform is: "
1207
             << id << endl;
1208
        return;
1209
    }
1210

    
1211
    if (!model) {
1212
        return;
1213
    }
1214

    
1215
    m_salientCalculating = true;
1216

    
1217
    Transform transform = tf->getDefaultTransformFor
1218
        (id, model->getSampleRate());
1219

    
1220
    ModelTransformer::Input input(model, -1);
1221

    
1222
    Layer *newLayer = m_document->createDerivedLayer(transform, model);
1223

    
1224
    if (newLayer) {
1225

    
1226
        TimeInstantLayer *til = qobject_cast<TimeInstantLayer *>(newLayer);
1227
        if (til) {
1228
            til->setPlotStyle(TimeInstantLayer::PlotInstants);
1229
            til->setBaseColour(m_salientColour);
1230
        }
1231

    
1232
        PlayParameters *params = newLayer->getPlayParameters();
1233
        if (params) {
1234
            params->setPlayAudible(false);
1235
        }
1236

    
1237
        connect(til, SIGNAL(modelCompletionChanged()),
1238
                this, SLOT(salientLayerCompletionChanged()));
1239
        
1240
        m_document->addLayerToView(pane, newLayer);
1241
        m_paneStack->setCurrentLayer(pane, newLayer);
1242
    }
1243
}
1244

    
1245
void
1246
MainWindow::salientLayerCompletionChanged()
1247
{
1248
    Layer *layer = qobject_cast<Layer *>(sender());
1249
    if (layer && layer->getCompletion(0) == 100) {
1250
        m_salientCalculating = false;
1251
        foreach (AlignmentModel *am, m_salientPending) {
1252
            mapSalientFeatureLayer(am);
1253
        }
1254
        m_salientPending.clear();
1255
    }
1256
}
1257

    
1258
TimeInstantLayer *
1259
MainWindow::findSalientFeatureLayer(Pane *pane)
1260
{
1261
    if (!getMainModel()) return nullptr;
1262
    
1263
    if (!pane) {
1264
        for (int i = 0; i < m_paneStack->getPaneCount(); ++i) {
1265

    
1266
            Pane *p = m_paneStack->getPane(i);
1267
            bool isAssociatedWithMainModel = false;
1268

    
1269
            for (int j = 0; j < p->getLayerCount(); ++j) {
1270
                Layer *l = p->getLayer(j);
1271
                if (l->getModel() == getMainModel()) {
1272
                    isAssociatedWithMainModel = true;
1273
                    break;
1274
                }
1275
            }
1276

    
1277
            if (isAssociatedWithMainModel) {
1278
                TimeInstantLayer *layerHere = findSalientFeatureLayer(p);
1279
                if (layerHere) return layerHere;
1280
            }
1281
        }
1282

    
1283
        return nullptr;
1284
    }
1285

    
1286
    for (int i = 0; i < pane->getLayerCount(); ++i) {
1287
        TimeInstantLayer *ll = qobject_cast<TimeInstantLayer *>
1288
            (pane->getLayer(i));
1289
        if (ll) return ll;
1290
    }
1291

    
1292
    return nullptr;
1293
}
1294

    
1295
void
1296
MainWindow::toggleSalientFeatures()
1297
{
1298
    bool targetDormantState = false;
1299

    
1300
    for (int i = 0; i < m_paneStack->getPaneCount(); ++i) {
1301
        Pane *p = m_paneStack->getPane(i);
1302
        TimeInstantLayer *layer = findSalientFeatureLayer(p);
1303
        if (layer) {
1304
            targetDormantState = !(layer->isLayerDormant(p));
1305
            break;
1306
        }
1307
    }
1308

    
1309
    for (int i = 0; i < m_paneStack->getPaneCount(); ++i) {
1310
        Pane *p = m_paneStack->getPane(i);
1311
        TimeInstantLayer *salient = findSalientFeatureLayer(p);
1312
        if (salient) {
1313
            salient->setLayerDormant(p, targetDormantState);
1314
        }
1315
        if (targetDormantState) {
1316
            for (int j = 0; j < p->getLayerCount(); ++j) {
1317
                Layer *l = p->getLayer(j);
1318
                if (l != salient) {
1319
                    p->propertyContainerSelected(p, l);
1320
                    break;
1321
                }
1322
            }
1323
        } else {
1324
            p->propertyContainerSelected(p, salient);
1325
        }
1326
    }
1327
}
1328

    
1329
void
1330
MainWindow::mapSalientFeatureLayer(AlignmentModel *am)
1331
{
1332
    if (m_salientCalculating) {
1333
        m_salientPending.insert(am);
1334
        return;
1335
    }
1336

    
1337
    TimeInstantLayer *salient = findSalientFeatureLayer();
1338
    if (!salient) {
1339
        SVCERR << "MainWindow::mapSalientFeatureLayer: No salient layer found"
1340
               << endl;
1341
        m_salientPending.insert(am);
1342
        return;
1343
    }
1344

    
1345
    if (!am) {
1346
        SVCERR << "MainWindow::mapSalientFeatureLayer: AlignmentModel is null!"
1347
               << endl;
1348
        return;
1349
    }
1350
    
1351
    const Model *model = am->getAlignedModel();
1352

    
1353
    Pane *pane = nullptr;
1354
    Layer *layer = nullptr;
1355
    Pane *firstPane = nullptr;
1356
    
1357
    for (int i = 0; i < m_paneStack->getPaneCount(); ++i) {
1358
        Pane *p = m_paneStack->getPane(i);
1359
        if (p && !firstPane) firstPane = p;
1360
        for (int j = 0; j < p->getLayerCount(); ++j) {
1361
            Layer *l = p->getLayer(j);
1362
            if (!l) continue;
1363
            if (l->getModel() == model) {
1364
                pane = p;
1365
                layer = l;
1366
                break;
1367
            }
1368
        }
1369
        if (layer) break;
1370
    }
1371

    
1372
    if (!pane || !layer) {
1373
        SVCERR << "MainWindow::mapSalientFeatureLayer: Failed to find model "
1374
               << model << " in any layer" << endl;
1375
        return;
1376
    }
1377

    
1378
    QString salientLayerName = tr("Mapped Salient Feature Layer");
1379

    
1380
    // Remove any existing mapped salient layer from this pane (in
1381
    // case we are re-aligning an existing model)
1382
    for (int j = 0; j < pane->getLayerCount(); ++j) {
1383
        Layer *l = pane->getLayer(j);
1384
        if (!l) continue;
1385
        if (l->objectName() == salientLayerName) {
1386
            SVDEBUG << "MainWindow::mapSalientFeatureLayer: "
1387
                    << "Removing existing mapped layer " << l << endl;
1388
            m_document->deleteLayer(l, true); // force flag: remove from views
1389
            break;
1390
        }
1391
    }
1392

    
1393
    pane->setCentreFrame(am->fromReference(firstPane->getCentreFrame()));
1394
    
1395
    const SparseOneDimensionalModel *from =
1396
        qobject_cast<const SparseOneDimensionalModel *>(salient->getModel());
1397
    if (!from) {
1398
        SVCERR << "MainWindow::mapSalientFeatureLayer: "
1399
               << "Salient layer lacks SparseOneDimensionalModel" << endl;
1400
        return;
1401
    }
1402
        
1403
    SparseOneDimensionalModel *to = new SparseOneDimensionalModel
1404
        (model->getSampleRate(), from->getResolution(), false);
1405

    
1406
    EventVector pp = from->getAllEvents();
1407
    for (const auto &p: pp) {
1408
        Event aligned = p
1409
            .withFrame(am->fromReference(p.getFrame()))
1410
            .withLabel(""); // remove label, as the analysis was not
1411
                            // conducted on the audio we're mapping to
1412
        to->add(aligned);
1413
    }
1414

    
1415
    Layer *newLayer = m_document->createImportedLayer(to);
1416

    
1417
    if (newLayer) {
1418

    
1419
        newLayer->setObjectName(salientLayerName);
1420
        
1421
        TimeInstantLayer *til = qobject_cast<TimeInstantLayer *>(newLayer);
1422
        if (til) {
1423
            til->setPlotStyle(TimeInstantLayer::PlotInstants);
1424
            til->setBaseColour(m_salientColour);
1425
        }
1426
        
1427
        PlayParameters *params = newLayer->getPlayParameters();
1428
        if (params) {
1429
            params->setPlayAudible(false);
1430
        }
1431

    
1432
        m_document->addLayerToView(pane, newLayer);
1433
        m_paneStack->setCurrentLayer(pane, newLayer);
1434
    }
1435
}
1436

    
1437
void
1438
MainWindow::outlineWaveformModeSelected()
1439
{
1440
    QString name = tr("Outline Waveform");
1441

    
1442
    for (int i = 0; i < m_paneStack->getPaneCount(); ++i) {
1443

    
1444
        Pane *pane = m_paneStack->getPane(i);
1445
        if (!pane) continue;
1446

    
1447
        Model *createFrom = nullptr;
1448
        if (!selectExistingLayerForMode(pane, name, &createFrom) &&
1449
            createFrom) {
1450

    
1451
            Layer *newLayer = m_document->createLayer(LayerFactory::Waveform);
1452
            newLayer->setObjectName(name);
1453

    
1454
            QString layerPropertyXml =
1455
                QString("<layer scale=\"%1\" channelMode=\"%2\" gain=\"0.95\"/>")
1456
                .arg(int(WaveformLayer::MeterScale))
1457
                .arg(int(WaveformLayer::MergeChannels));
1458
            LayerFactory::getInstance()->setLayerProperties
1459
                (newLayer, layerPropertyXml);
1460

    
1461
            SingleColourLayer *scl =
1462
                qobject_cast<SingleColourLayer *>(newLayer);
1463
            if (scl) {
1464
                scl->setBaseColour
1465
                    (i % ColourDatabase::getInstance()->getColourCount());
1466
            }
1467
            
1468
            m_document->setModel(newLayer, createFrom);
1469
            m_document->addLayerToView(pane, newLayer);
1470
            m_paneStack->setCurrentLayer(pane, newLayer);
1471
        }
1472

    
1473
        TimeInstantLayer *salient = findSalientFeatureLayer(pane);
1474
        if (salient) {
1475
            pane->propertyContainerSelected(pane, salient);
1476
        }
1477
    }
1478

    
1479
    m_displayMode = OutlineWaveformMode;
1480
    checkpointSession();
1481
}
1482

    
1483
void
1484
MainWindow::standardWaveformModeSelected()
1485
{
1486
    QString name = tr("Standard Waveform");
1487

    
1488
    for (int i = 0; i < m_paneStack->getPaneCount(); ++i) {
1489

    
1490
        Pane *pane = m_paneStack->getPane(i);
1491
        if (!pane) continue;
1492

    
1493
        Model *createFrom = nullptr;
1494
        if (!selectExistingLayerForMode(pane, name, &createFrom) &&
1495
            createFrom) {
1496

    
1497
            Layer *newLayer = m_document->createLayer(LayerFactory::Waveform);
1498
            newLayer->setObjectName(name);
1499

    
1500
            QString layerPropertyXml =
1501
                QString("<layer scale=\"%1\" channelMode=\"%2\"/>")
1502
                .arg(int(WaveformLayer::LinearScale))
1503
                .arg(int(WaveformLayer::SeparateChannels));
1504
            LayerFactory::getInstance()->setLayerProperties
1505
                (newLayer, layerPropertyXml);
1506

    
1507
            SingleColourLayer *scl =
1508
                qobject_cast<SingleColourLayer *>(newLayer);
1509
            if (scl) {
1510
                scl->setBaseColour
1511
                    (i % ColourDatabase::getInstance()->getColourCount());
1512
            }
1513
            
1514
            m_document->setModel(newLayer, createFrom);
1515
            m_document->addLayerToView(pane, newLayer);
1516
            m_paneStack->setCurrentLayer(pane, newLayer);
1517
        }
1518

    
1519
        TimeInstantLayer *salient = findSalientFeatureLayer(pane);
1520
        if (salient) {
1521
            pane->propertyContainerSelected(pane, salient);
1522
        }
1523
    }
1524

    
1525
    m_displayMode = WaveformMode;
1526
    checkpointSession();
1527
}
1528

    
1529
void
1530
MainWindow::spectrogramModeSelected()
1531
{
1532
    QString name = tr("Spectrogram");
1533

    
1534
    for (int i = 0; i < m_paneStack->getPaneCount(); ++i) {
1535

    
1536
        Pane *pane = m_paneStack->getPane(i);
1537
        if (!pane) continue;
1538

    
1539
        Model *createFrom = nullptr;
1540
        if (!selectExistingLayerForMode(pane, name, &createFrom) &&
1541
            createFrom) {
1542
            Layer *newLayer = m_document->createLayer(LayerFactory::Spectrogram);
1543
            newLayer->setObjectName(name);
1544
            m_document->setModel(newLayer, createFrom);
1545
            m_document->addLayerToView(pane, newLayer);
1546
            m_paneStack->setCurrentLayer(pane, newLayer);
1547
        }
1548

    
1549
        TimeInstantLayer *salient = findSalientFeatureLayer(pane);
1550
        if (salient) {
1551
            pane->propertyContainerSelected(pane, salient);
1552
        }
1553
    }
1554

    
1555
    m_displayMode = SpectrogramMode;
1556
    checkpointSession();
1557
}
1558

    
1559
void
1560
MainWindow::melodogramModeSelected()
1561
{
1562
    QString name = tr("Melodic Range Spectrogram");
1563

    
1564
    for (int i = 0; i < m_paneStack->getPaneCount(); ++i) {
1565

    
1566
        Pane *pane = m_paneStack->getPane(i);
1567
        if (!pane) continue;
1568

    
1569
        Model *createFrom = nullptr;
1570
        if (!selectExistingLayerForMode(pane, name, &createFrom) &&
1571
            createFrom) {
1572
            Layer *newLayer = m_document->createLayer
1573
                (LayerFactory::MelodicRangeSpectrogram);
1574
            newLayer->setObjectName(name);
1575
            m_document->setModel(newLayer, createFrom);
1576
            m_document->addLayerToView(pane, newLayer);
1577
            m_paneStack->setCurrentLayer(pane, newLayer);
1578
        }
1579

    
1580
        TimeInstantLayer *salient = findSalientFeatureLayer(pane);
1581
        if (salient) {
1582
            pane->propertyContainerSelected(pane, salient);
1583
        }
1584
    }
1585

    
1586
    m_displayMode = MelodogramMode;
1587
    checkpointSession();
1588
}
1589

    
1590
void
1591
MainWindow::selectTransformDrivenMode(QString name,
1592
                                      DisplayMode mode,
1593
                                      QString transformId,
1594
                                      QString layerPropertyXml)
1595
{
1596
    for (int i = 0; i < m_paneStack->getPaneCount(); ++i) {
1597

    
1598
        Pane *pane = m_paneStack->getPane(i);
1599
        if (!pane) continue;
1600

    
1601
        Model *createFrom = nullptr;
1602
        if (!selectExistingLayerForMode(pane, name, &createFrom) &&
1603
            createFrom) {
1604

    
1605
            TransformFactory *tf = TransformFactory::getInstance();
1606

    
1607
            if (tf->haveTransform(transformId)) {
1608

    
1609
                Transform transform = tf->getDefaultTransformFor
1610
                    (transformId, createFrom->getSampleRate());
1611

    
1612
                ModelTransformer::Input input(createFrom, -1);
1613
                
1614
                Layer *newLayer =
1615
                    m_document->createDerivedLayer(transform, createFrom);
1616

    
1617
                if (newLayer) {
1618
                    newLayer->setObjectName(name);
1619
                    LayerFactory::getInstance()->setLayerProperties
1620
                        (newLayer, layerPropertyXml);
1621

    
1622
                    SingleColourLayer *scl =
1623
                        qobject_cast<SingleColourLayer *>(newLayer);
1624
                    if (scl) {
1625
                        scl->setBaseColour
1626
                            (i % ColourDatabase::getInstance()->getColourCount());
1627
                    }
1628

    
1629
                    m_document->addLayerToView(pane, newLayer);
1630
                    m_paneStack->setCurrentLayer(pane, newLayer);
1631
                } else {
1632
                    SVCERR << "ERROR: Failed to create derived layer" << endl;
1633
                }
1634
            
1635
            } else {
1636
                SVCERR << "ERROR: No PYin plugin available" << endl;
1637
            }
1638
        }
1639

    
1640
        TimeInstantLayer *salient = findSalientFeatureLayer(pane);
1641
        if (salient) {
1642
            pane->propertyContainerSelected(pane, salient);
1643
        }
1644
    }
1645

    
1646
    m_displayMode = mode;
1647
    checkpointSession();
1648
}
1649

    
1650
void
1651
MainWindow::curveModeSelected()
1652
{
1653
    QString propertyXml =
1654
        QString("<layer plotStyle=\"%1\"/>")
1655
        .arg(int(TimeValueLayer::PlotStems));
1656

    
1657
    selectTransformDrivenMode
1658
        (tr("Curve"),
1659
         CurveMode,
1660
         "vamp:qm-vamp-plugins:qm-onsetdetector:detection_fn",
1661
         propertyXml);
1662
}
1663

    
1664
void
1665
MainWindow::pitchModeSelected()
1666
{
1667
    QString propertyXml =
1668
        QString("<layer plotStyle=\"%1\" verticalScale=\"%2\"/>")
1669
        .arg(int(TimeValueLayer::PlotDiscreteCurves))
1670
        .arg(int(TimeValueLayer::LogScale));
1671
    
1672
    selectTransformDrivenMode
1673
        (tr("Pitch"),
1674
         PitchMode,
1675
         "vamp:pyin:pyin:smoothedpitchtrack",
1676
         propertyXml);
1677
}
1678

    
1679
void
1680
MainWindow::azimuthModeSelected()
1681
{
1682
    QString propertyXml =
1683
        QString("<layer colourMap=\"Ice\" opaque=\"true\" smooth=\"true\" "
1684
                "binScale=\"%1\" columnNormalization=\"hybrid\"/>")
1685
        .arg(int(BinScale::Linear));
1686

    
1687
    selectTransformDrivenMode
1688
        (tr("Azimuth"),
1689
         AzimuthMode,
1690
         "vamp:azi:azi:plan",
1691
         propertyXml);
1692
}
1693

    
1694
void
1695
MainWindow::updateModeFromLayers()
1696
{
1697
    for (auto &bp : m_modeButtons) {
1698
        bp.second->setChecked(false);
1699
    }
1700

    
1701
    SVCERR << "MainWindow::updateModeFromLayers" << endl;
1702
    
1703
    for (int i = 0; i < m_paneStack->getPaneCount(); ++i) {
1704

    
1705
        Pane *pane = m_paneStack->getPane(i);
1706
        if (!pane) continue;
1707

    
1708
        SVCERR << "MainWindow::updateModeFromLayers: pane " << i << "..." << endl;
1709
        
1710
        bool found = false;
1711
        
1712
        for (int j = 0; j < pane->getLayerCount(); ++j) {
1713

    
1714
            Layer *layer = pane->getLayer(j);
1715
            if (!layer || qobject_cast<TimeInstantLayer *>(layer)) {
1716
                continue;
1717
            }
1718
            if (layer->isLayerDormant(pane)) {
1719
                continue;
1720
            }
1721

    
1722
            QString ln = layer->objectName();
1723

    
1724
            SVCERR << "MainWindow::updateModeFromLayers: layer " << j << " has name " << ln << endl;
1725
            
1726
            //!!! todo: store layer names in a map against layer types, so
1727
            //!!! as to ensure consistency
1728
        
1729
            if (ln == tr("Outline Waveform")) {
1730
                m_displayMode = OutlineWaveformMode;
1731
                found = true;
1732
                break;
1733
            } else if (ln == tr("Waveform")) {
1734
                m_displayMode = WaveformMode;
1735
                found = true;
1736
                break;
1737
            } else if (ln == tr("Melodic Range Spectrogram")) {
1738
                m_displayMode = MelodogramMode;
1739
                found = true;
1740
                break;
1741
            } else if (ln == tr("Spectrogram")) {
1742
                m_displayMode = SpectrogramMode;
1743
                found = true;
1744
                break;
1745
            } else if (ln == tr("Curve")) {
1746
                m_displayMode = CurveMode;
1747
                found = true;
1748
                break;
1749
            } else if (ln == tr("Pitch")) {
1750
                m_displayMode = PitchMode;
1751
                found = true;
1752
                break;
1753
            } else if (ln == tr("Azimuth")) {
1754
                m_displayMode = AzimuthMode;
1755
                found = true;
1756
                break;
1757
            }
1758
        }
1759

    
1760
        if (found) break;
1761
    }
1762

    
1763
    m_modeButtons[m_displayMode]->setChecked(true);
1764
}
1765

    
1766
void
1767
MainWindow::reselectMode()
1768
{
1769
    switch (m_displayMode) {
1770
    case CurveMode: curveModeSelected(); break;
1771
    case OutlineWaveformMode: outlineWaveformModeSelected(); break;
1772
    case WaveformMode: standardWaveformModeSelected(); break;
1773
    case SpectrogramMode: spectrogramModeSelected(); break;
1774
    case MelodogramMode: melodogramModeSelected(); break;
1775
    case AzimuthMode: azimuthModeSelected(); break;
1776
    case PitchMode: pitchModeSelected(); break;
1777
    }
1778
}
1779

    
1780
void
1781
MainWindow::paneAdded(Pane *pane)
1782
{
1783
    pane->setPlaybackFollow(PlaybackScrollContinuous);
1784
    m_paneStack->sizePanesEqually();
1785
    checkpointSession();
1786
}    
1787

    
1788
void
1789
MainWindow::paneHidden(Pane *)
1790
{
1791
    checkpointSession();
1792
}    
1793

    
1794
void
1795
MainWindow::paneAboutToBeDeleted(Pane *)
1796
{
1797
}    
1798

    
1799
void
1800
MainWindow::paneDropAccepted(Pane * /* pane */, QStringList uriList)
1801
{
1802
    if (uriList.empty()) return;
1803

    
1804
    QUrl first(uriList[0]);
1805

    
1806
    cerr << "uriList.size() == " << uriList.size() << endl;
1807
    cerr << "first.isLocalFile() == " << first.isLocalFile() << endl;
1808
    cerr << "QFileInfo(first.path()).isDir() == " << QFileInfo(first.path()).isDir() << endl;
1809
    
1810
    if (uriList.size() == 1 &&
1811
        first.isLocalFile() &&
1812
        QFileInfo(first.path()).isDir()) {
1813

    
1814
        FileOpenStatus status = openDirOfAudio(first.path());
1815

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

    
1821
        return;
1822
    }
1823
    
1824
    for (QStringList::iterator i = uriList.begin(); i != uriList.end(); ++i) {
1825

    
1826
        FileOpenStatus status = openPath(*i, CreateAdditionalModel);
1827

    
1828
        if (status == FileOpenFailed) {
1829
            QMessageBox::critical(this, tr("Failed to open dropped URL"),
1830
                                  tr("<b>Open failed</b><p>Dropped URL \"%1\" could not be opened").arg(*i));
1831
        } else if (status == FileOpenWrongMode) {
1832
            QMessageBox::critical(this, tr("Failed to open dropped URL"),
1833
                                  tr("<b>Audio required</b><p>Please load at least one audio file before importing annotation data"));
1834
        } else {
1835
            configureNewPane(m_paneStack->getCurrentPane());
1836
        }
1837
    }
1838
}
1839

    
1840
void
1841
MainWindow::paneDropAccepted(Pane *pane, QString text)
1842
{
1843
    if (pane) m_paneStack->setCurrentPane(pane);
1844

    
1845
    QUrl testUrl(text);
1846
    if (testUrl.scheme() == "file" || 
1847
        testUrl.scheme() == "http" || 
1848
        testUrl.scheme() == "ftp") {
1849
        QStringList list;
1850
        list.push_back(text);
1851
        paneDropAccepted(pane, list);
1852
        return;
1853
    }
1854

    
1855
    //!!! open as text -- but by importing as if a CSV, or just adding
1856
    //to a text layer?
1857
}
1858

    
1859
void
1860
MainWindow::configureNewPane(Pane *pane)
1861
{
1862
    SVCERR << "MainWindow::configureNewPane(" << pane << ")" << endl;
1863

    
1864
    if (!pane) return;
1865

    
1866
    zoomToFit();
1867
    reselectMode();
1868

    
1869
    Layer *waveformLayer = 0;
1870

    
1871
    for (int i = 0; i < pane->getLayerCount(); ++i) {
1872
        Layer *layer = pane->getLayer(i);
1873
        if (!layer) continue;
1874
        if (dynamic_cast<WaveformLayer *>(layer)) waveformLayer = layer;
1875
        if (dynamic_cast<TimeValueLayer *>(layer)) return;
1876
    }
1877
    if (!waveformLayer) return;
1878

    
1879
    waveformLayer->setObjectName(tr("Waveform"));
1880
}
1881

    
1882
void
1883
MainWindow::closeEvent(QCloseEvent *e)
1884
{
1885
    if (m_exiting) {
1886
        e->accept();
1887
        return;
1888
    }
1889

    
1890
//    cerr << "MainWindow::closeEvent" << endl;
1891

    
1892
    if (m_openingAudioFile) {
1893
//        cerr << "Busy - ignoring close event" << endl;
1894
        e->ignore();
1895
        return;
1896
    }
1897

    
1898
    closeSession();
1899

    
1900
    QSettings settings;
1901
    settings.beginGroup("MainWindow");
1902
    settings.setValue("maximised", isMaximized());
1903
    if (!isMaximized()) {
1904
        settings.setValue("size", size());
1905
        settings.setValue("position", pos());
1906
    }
1907
    settings.endGroup();
1908

    
1909
    delete m_keyReference;
1910
    m_keyReference = 0;
1911

    
1912
    if (m_preferencesDialog &&
1913
        m_preferencesDialog->isVisible()) {
1914
        closeSession(); // otherwise we'll have to wait for prefs changes
1915
        m_preferencesDialog->applicationClosing(false);
1916
    }
1917

    
1918
    if (m_layerTreeView &&
1919
        m_layerTreeView->isVisible()) {
1920
        delete m_layerTreeView;
1921
    }
1922

    
1923
    e->accept();
1924

    
1925
    m_exiting = true;
1926
    qApp->closeAllWindows();
1927
    
1928
    return;
1929
}
1930

    
1931
bool
1932
MainWindow::checkSaveModified()
1933
{
1934
    // It should always be OK to save, with our active-session paradigm
1935
    return true;
1936
}
1937

    
1938
bool
1939
MainWindow::commitData(bool /* mayAskUser */)
1940
{
1941
    if (m_preferencesDialog &&
1942
        m_preferencesDialog->isVisible()) {
1943
        m_preferencesDialog->applicationClosing(true);
1944
    }
1945
    checkpointSession();
1946
    return true;
1947
}
1948

    
1949
void
1950
MainWindow::preferenceChanged(PropertyContainer::PropertyName name)
1951
{
1952
    MainWindowBase::preferenceChanged(name);
1953
}
1954

    
1955
void
1956
MainWindow::renameCurrentLayer()
1957
{
1958
    Pane *pane = m_paneStack->getCurrentPane();
1959
    if (pane) {
1960
        Layer *layer = pane->getSelectedLayer();
1961
        if (layer) {
1962
            bool ok = false;
1963
            QString newName = QInputDialog::getText
1964
                (this, tr("Rename Layer"),
1965
                 tr("New name for this layer:"),
1966
                 QLineEdit::Normal, layer->objectName(), &ok);
1967
            if (ok) {
1968
                layer->setObjectName(newName);
1969
            }
1970
        }
1971
    }
1972
}
1973

    
1974
void
1975
MainWindow::alignToggled()
1976
{
1977
    QAction *action = dynamic_cast<QAction *>(sender());
1978
    
1979
    if (!m_viewManager) return;
1980

    
1981
    if (action) {
1982
        m_viewManager->setAlignMode(action->isChecked());
1983
    } else {
1984
        m_viewManager->setAlignMode(!m_viewManager->getAlignMode());
1985
    }
1986

    
1987
    if (m_viewManager->getAlignMode()) {
1988
        m_document->alignModels();
1989
        m_document->setAutoAlignment(true);
1990
    } else {
1991
        m_document->setAutoAlignment(false);
1992
    }
1993

    
1994
    for (int i = 0; i < m_paneStack->getPaneCount(); ++i) {
1995
        Pane *pane = m_paneStack->getPane(i);
1996
        if (!pane) continue;
1997
        pane->update();
1998
    }
1999
}
2000

    
2001
void
2002
MainWindow::tuningDifferenceToggled()
2003
{
2004
    QSettings settings;
2005
    settings.beginGroup("Alignment");
2006
    bool on = settings.value("align-pitch-aware", false).toBool();
2007
    settings.setValue("align-pitch-aware", !on);
2008
    settings.endGroup();
2009

    
2010
    if (m_viewManager->getAlignMode()) {
2011
        m_document->realignModels();
2012
    }
2013
}    
2014
    
2015
void
2016
MainWindow::playSpeedChanged(int position)
2017
{
2018
    PlaySpeedRangeMapper mapper;
2019

    
2020
    double percent = m_playSpeed->mappedValue();
2021
    double factor = mapper.getFactorForValue(percent);
2022

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

    
2025
    int centre = m_playSpeed->defaultValue();
2026

    
2027
    // Percentage is shown to 0dp if >100, to 1dp if <100; factor is
2028
    // shown to 3sf
2029

    
2030
    char pcbuf[30];
2031
    char facbuf[30];
2032
    
2033
    if (position == centre) {
2034
        contextHelpChanged(tr("Playback speed: Normal"));
2035
    } else if (position < centre) {
2036
        sprintf(pcbuf, "%.1f", percent);
2037
        sprintf(facbuf, "%.3g", 1.0 / factor);
2038
        contextHelpChanged(tr("Playback speed: %1% (%2x slower)")
2039
                           .arg(pcbuf)
2040
                           .arg(facbuf));
2041
    } else {
2042
        sprintf(pcbuf, "%.0f", percent);
2043
        sprintf(facbuf, "%.3g", factor);
2044
        contextHelpChanged(tr("Playback speed: %1% (%2x faster)")
2045
                           .arg(pcbuf)
2046
                           .arg(facbuf));
2047
    }
2048

    
2049
    m_playSource->setTimeStretch(1.0 / factor); // factor is a speedup
2050

    
2051
    updateMenuStates();
2052
}
2053

    
2054
void
2055
MainWindow::speedUpPlayback()
2056
{
2057
    int value = m_playSpeed->value();
2058
    value = value + m_playSpeed->pageStep();
2059
    if (value > m_playSpeed->maximum()) value = m_playSpeed->maximum();
2060
    m_playSpeed->setValue(value);
2061
}
2062

    
2063
void
2064
MainWindow::slowDownPlayback()
2065
{
2066
    int value = m_playSpeed->value();
2067
    value = value - m_playSpeed->pageStep();
2068
    if (value < m_playSpeed->minimum()) value = m_playSpeed->minimum();
2069
    m_playSpeed->setValue(value);
2070
}
2071

    
2072
void
2073
MainWindow::restoreNormalPlayback()
2074
{
2075
    m_playSpeed->setValue(m_playSpeed->defaultValue());
2076
}
2077

    
2078
void
2079
MainWindow::updateVisibleRangeDisplay(Pane *p) const
2080
{
2081
    if (!getMainModel() || !p) {
2082
        return;
2083
    }
2084

    
2085
    bool haveSelection = false;
2086
    sv_frame_t startFrame = 0, endFrame = 0;
2087

    
2088
    if (m_viewManager && m_viewManager->haveInProgressSelection()) {
2089

    
2090
        bool exclusive = false;
2091
        Selection s = m_viewManager->getInProgressSelection(exclusive);
2092

    
2093
        if (!s.isEmpty()) {
2094
            haveSelection = true;
2095
            startFrame = s.getStartFrame();
2096
            endFrame = s.getEndFrame();
2097
        }
2098
    }
2099

    
2100
    if (!haveSelection) {
2101
        startFrame = p->getFirstVisibleFrame();
2102
        endFrame = p->getLastVisibleFrame();
2103
    }
2104

    
2105
    RealTime start = RealTime::frame2RealTime
2106
        (startFrame, getMainModel()->getSampleRate());
2107

    
2108
    RealTime end = RealTime::frame2RealTime
2109
        (endFrame, getMainModel()->getSampleRate());
2110

    
2111
    RealTime duration = end - start;
2112

    
2113
    QString startStr, endStr, durationStr;
2114
    startStr = start.toText(true).c_str();
2115
    endStr = end.toText(true).c_str();
2116
    durationStr = duration.toText(true).c_str();
2117

    
2118
    if (haveSelection) {
2119
        m_myStatusMessage = tr("Selection: %1 to %2 (duration %3)")
2120
            .arg(startStr).arg(endStr).arg(durationStr);
2121
    } else {
2122
        m_myStatusMessage = tr("Visible: %1 to %2 (duration %3)")
2123
            .arg(startStr).arg(endStr).arg(durationStr);
2124
    }
2125

    
2126
    statusBar()->showMessage(m_myStatusMessage);
2127
}
2128

    
2129
void
2130
MainWindow::updatePositionStatusDisplays() const
2131
{
2132
    if (!statusBar()->isVisible()) return;
2133

    
2134
}
2135

    
2136
void
2137
MainWindow::monitoringLevelsChanged(float left, float right)
2138
{
2139
    m_mainLevelPan->setMonitoringLevels(left, right);
2140
}
2141

    
2142
void
2143
MainWindow::sampleRateMismatch(sv_samplerate_t requested,
2144
                               sv_samplerate_t actual,
2145
                               bool willResample)
2146
{
2147
    if (!willResample) {
2148
        //!!! more helpful message needed
2149
        QMessageBox::information
2150
            (this, tr("Sample rate mismatch"),
2151
             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.")
2152
             .arg(requested).arg(actual));
2153
    }        
2154

    
2155
    updateDescriptionLabel();
2156
}
2157

    
2158
void
2159
MainWindow::audioOverloadPluginDisabled()
2160
{
2161
    QMessageBox::information
2162
        (this, tr("Audio processing overload"),
2163
         tr("<b>Overloaded</b><p>Audio effects plugin auditioning has been disabled due to a processing overload."));
2164
}
2165

    
2166
void
2167
MainWindow::audioTimeStretchMultiChannelDisabled()
2168
{
2169
    static bool shownOnce = false;
2170
    if (shownOnce) return;
2171
    QMessageBox::information
2172
        (this, tr("Audio processing overload"),
2173
         tr("<b>Overloaded</b><p>Audio playback speed processing has been reduced to a single channel, due to a processing overload."));
2174
    shownOnce = true;
2175
}
2176

    
2177
void
2178
MainWindow::layerRemoved(Layer *layer)
2179
{
2180
    MainWindowBase::layerRemoved(layer);
2181
}
2182

    
2183
void
2184
MainWindow::layerInAView(Layer *layer, bool inAView)
2185
{
2186
    MainWindowBase::layerInAView(layer, inAView);
2187
}
2188

    
2189
void
2190
MainWindow::modelAdded(Model *model)
2191
{
2192
    MainWindowBase::modelAdded(model);
2193
}
2194

    
2195
void
2196
MainWindow::modelAboutToBeDeleted(Model *model)
2197
{
2198
    MainWindowBase::modelAboutToBeDeleted(model);
2199
}
2200

    
2201
QString
2202
MainWindow::makeSessionFilename()
2203
{
2204
    Model *mainModel = getMainModel();
2205
    if (!mainModel) {
2206
        SVDEBUG << "MainWindow::makeSessionFilename: No main model, returning empty filename" << endl;
2207
        return {};
2208
    }
2209
    
2210
    //!!! can refactor in common with RecordDirectory?
2211
    
2212
    QDir parentDir(TempDirectory::getInstance()->getContainingPath());
2213
    QString sessionDirName("session");
2214

    
2215
    if (!parentDir.mkpath(sessionDirName)) {
2216
        SVCERR << "ERROR: makeSessionFilename: Failed to create session dir in \"" << parentDir.canonicalPath() << "\"" << endl;
2217
        QMessageBox::critical(this, tr("Failed to create session directory"),
2218
                              tr("<p>Failed to create directory \"%1\" for session files</p>")
2219
                              .arg(parentDir.filePath(sessionDirName)));
2220
        return {};
2221
    }
2222

    
2223
    QDir sessionDir(parentDir.filePath(sessionDirName));
2224
    
2225
    QDateTime now = QDateTime::currentDateTime();
2226
    QString dateDirName = QString("%1").arg(now.toString("yyyyMMdd"));
2227

    
2228
    if (!sessionDir.mkpath(dateDirName)) {
2229
        SVCERR << "ERROR: makeSessionFilename: Failed to create datestamped session dir in \"" << sessionDir.canonicalPath() << "\"" << endl;
2230
        QMessageBox::critical(this, tr("Failed to create session directory"),
2231
                              tr("<p>Failed to create date directory \"%1\" for session files</p>")
2232
                              .arg(sessionDir.filePath(dateDirName)));
2233
        return {};
2234
    }
2235

    
2236
    QDir dateDir(sessionDir.filePath(dateDirName));
2237

    
2238
    QString sessionName = mainModel->getTitle();
2239
    if (sessionName == "") {
2240
        sessionName = mainModel->getLocation();
2241
    }
2242
    sessionName = QFileInfo(sessionName).baseName();
2243

    
2244
    QString sessionExt = 
2245
        InteractiveFileFinder::getInstance()->getApplicationSessionExtension();
2246

    
2247
    QString filePath = dateDir.filePath(QString("%1.%2")
2248
                                        .arg(sessionName)
2249
                                        .arg(sessionExt));
2250
    int suffix = 0;
2251
    while (QFile(filePath).exists()) {
2252
        if (++suffix == 100) {
2253
            SVCERR << "ERROR: makeSessionFilename: Failed to come up with unique session filename for " << sessionName << endl;
2254
            return {};
2255
        }
2256
        filePath = dateDir.filePath(QString("%1-%2.%3")
2257
                                    .arg(sessionName)
2258
                                    .arg(suffix)
2259
                                    .arg(sessionExt));
2260
    }
2261

    
2262
    SVDEBUG << "MainWindow::makeSessionFilename: returning "
2263
            << filePath << endl;
2264

    
2265
    return filePath;
2266
}
2267

    
2268
QString
2269
MainWindow::makeSessionLabel()
2270
{
2271
    Model *mainModel = getMainModel();
2272
    if (!mainModel) {
2273
        SVDEBUG << "MainWindow::makeSessionFilename: No main model, returning empty filename" << endl;
2274
        return {};
2275
    }
2276

    
2277
    QString sessionName = mainModel->getTitle();
2278
    if (sessionName == "") {
2279
        sessionName = mainModel->getLocation();
2280
        sessionName = QFileInfo(sessionName).baseName();
2281
    }
2282

    
2283
    int paneCount = 1;
2284
    if (m_paneStack) paneCount = m_paneStack->getPaneCount();
2285
    QString label = tr("%1: %n file(s)", "", paneCount).arg(sessionName);
2286
    
2287
    SVDEBUG << "MainWindow::makeSessionLabel: returning "
2288
            << label << endl;
2289

    
2290
    return label;
2291
}
2292

    
2293
void
2294
MainWindow::checkpointSession()
2295
{
2296
    if (m_sessionState == NoSession) {
2297
        SVCERR << "MainWindow::checkpointSession: no current session" << endl;
2298
        return;
2299
    }
2300

    
2301
    if (m_sessionState == SessionLoading) {
2302
        SVCERR << "MainWindow::checkpointSession: session is loading" << endl;
2303
        return;
2304
    }
2305

    
2306
    if (ModelTransformerFactory::getInstance()->haveRunningTransformers()) {
2307
        SVCERR << "MainWindow::checkpointSession: some transformers are still running" << endl;
2308
        return;
2309
    }
2310
    
2311
    // This test is necessary, so that we don't get into a nasty loop
2312
    // when checkpointing on closeSession called when opening a new
2313
    // session file
2314
    if (!m_documentModified) {
2315
        SVCERR << "MainWindow::checkpointSession: nothing to save" << endl;
2316
        return;
2317
    }
2318
    
2319
    if (m_sessionFile == "") {
2320
        SVCERR << "MainWindow::checkpointSession: no current session file" << endl;
2321
        return;
2322
    }
2323

    
2324
    QString sessionExt = 
2325
        InteractiveFileFinder::getInstance()->getApplicationSessionExtension();
2326

    
2327
    if (!m_sessionFile.endsWith("." + sessionExt)) {
2328
        // At one point in development we had a nasty situation where
2329
        // we loaded an audio file from the recent files list, then
2330
        // immediately saved the session over the top of it! This is
2331
        // just an additional guard against that kind of thing
2332
        SVCERR << "MainWindow::checkpointSession: suspicious session filename "
2333
               << m_sessionFile << ", not saving to it" << endl;
2334
        return;
2335

    
2336
        //!!! + we should also check that it is actually in our
2337
        //!!! auto-save session directory
2338
    }
2339
    
2340
    SVCERR << "MainWindow::checkpointSession: saving to session file: "
2341
           << m_sessionFile << endl;
2342

    
2343
    if (saveSessionFile(m_sessionFile)) {
2344
        m_recentSessions.addFile(m_sessionFile, makeSessionLabel());
2345
        CommandHistory::getInstance()->documentSaved();
2346
        documentRestored();
2347
        SVCERR << "MainWindow::checkpointSession complete" << endl;
2348
    } else {
2349
        SVCERR << "MainWindow::checkpointSession: save failed!" << endl;
2350
    }
2351
}
2352

    
2353
void
2354
MainWindow::mainModelChanged(WaveFileModel *model)
2355
{
2356
    SVDEBUG << "MainWindow::mainModelChanged(" << model << ")" << endl;
2357

    
2358
    if (m_sessionState == SessionLoading) {
2359
        SVDEBUG << "MainWindow::mainModelChanged: Session is loading, not (re)making session filename" << endl;
2360
    } else if (!model) {
2361
        SVDEBUG << "MainWindow::mainModelChanged: Null model, not (re)making session filename" << endl;
2362
    } else {
2363
        if (m_sessionState == NoSession) {
2364
            SVDEBUG << "MainWindow::mainModelChanged: Marking session as active" << endl;
2365
            m_sessionState = SessionActive;
2366
        } else {
2367
            SVDEBUG << "MainWindow::mainModelChanged: Session is active" << endl;
2368
        }
2369
        if (m_sessionFile == "") {
2370
            SVDEBUG << "MainWindow::mainModelChanged: No session file set, calling makeSessionFilename" << endl;
2371
            m_sessionFile = makeSessionFilename();
2372
        }
2373
    }
2374
    
2375
    m_salientPending.clear();
2376
    m_salientCalculating = false;
2377

    
2378
    MainWindowBase::mainModelChanged(model);
2379

    
2380
    if (m_playTarget || m_audioIO) {
2381
        connect(m_mainLevelPan, SIGNAL(levelChanged(float)),
2382
                this, SLOT(mainModelGainChanged(float)));
2383
        connect(m_mainLevelPan, SIGNAL(panChanged(float)),
2384
                this, SLOT(mainModelPanChanged(float)));
2385
    }
2386

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

    
2389
    if (m_sessionState != SessionLoading) {
2390

    
2391
        if (model &&
2392
            m_paneStack &&
2393
            (m_paneStack->getPaneCount() == 0)) {
2394
        
2395
            AddPaneCommand *command = new AddPaneCommand(this);
2396
            CommandHistory::getInstance()->addCommand(command);
2397
            Pane *pane = command->getPane();
2398
            Layer *newLayer =
2399
                m_document->createMainModelLayer(LayerFactory::Waveform);
2400
            newLayer->setObjectName(tr("Outline Waveform"));
2401

    
2402
            bool mono = (model->getChannelCount() == 1);
2403

    
2404
            QString layerPropertyXml =
2405
                QString("<layer scale=\"%1\" channelMode=\"%2\"/>")
2406
                .arg(int(WaveformLayer::MeterScale))
2407
                .arg(int(mono ?
2408
                         WaveformLayer::SeparateChannels :
2409
                         WaveformLayer::MergeChannels));
2410
            LayerFactory::getInstance()->setLayerProperties
2411
                (newLayer, layerPropertyXml);
2412
            
2413
            m_document->addLayerToView(pane, newLayer);
2414

    
2415
            addSalientFeatureLayer(pane, model);
2416
        
2417
        } else {
2418
            addSalientFeatureLayer(m_paneStack->getCurrentPane(), model);
2419
        }
2420
    }
2421

    
2422
    m_document->setAutoAlignment(m_viewManager->getAlignMode());
2423
}
2424

    
2425
void
2426
MainWindow::mainModelGainChanged(float gain)
2427
{
2428
    if (m_playTarget) {
2429
        m_playTarget->setOutputGain(gain);
2430
    } else if (m_audioIO) {
2431
        m_audioIO->setOutputGain(gain);
2432
    }
2433
}
2434

    
2435
void
2436
MainWindow::mainModelPanChanged(float balance)
2437
{
2438
    // this is indeed stereo balance rather than pan
2439
    if (m_playTarget) {
2440
        m_playTarget->setOutputBalance(balance);
2441
    } else if (m_audioIO) {
2442
        m_audioIO->setOutputBalance(balance);
2443
    }
2444
}
2445

    
2446
void
2447
MainWindow::modelGenerationFailed(QString transformName, QString message)
2448
{
2449
    if (message != "") {
2450

    
2451
        QMessageBox::warning
2452
            (this,
2453
             tr("Failed to generate layer"),
2454
             tr("<b>Layer generation failed</b><p>Failed to generate derived layer.<p>The layer transform \"%1\" failed:<p>%2")
2455
             .arg(transformName).arg(message),
2456
             QMessageBox::Ok);
2457
    } else {
2458
        QMessageBox::warning
2459
            (this,
2460
             tr("Failed to generate layer"),
2461
             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.")
2462
             .arg(transformName),
2463
             QMessageBox::Ok);
2464
    }
2465
}
2466

    
2467
void
2468
MainWindow::modelGenerationWarning(QString /* transformName */, QString message)
2469
{
2470
    QMessageBox::warning
2471
        (this, tr("Warning"), message, QMessageBox::Ok);
2472
}
2473

    
2474
void
2475
MainWindow::modelRegenerationFailed(QString layerName,
2476
                                    QString transformName, QString message)
2477
{
2478
    if (message != "") {
2479

    
2480
        QMessageBox::warning
2481
            (this,
2482
             tr("Failed to regenerate layer"),
2483
             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")
2484
             .arg(layerName).arg(transformName).arg(message),
2485
             QMessageBox::Ok);
2486
    } else {
2487
        QMessageBox::warning
2488
            (this,
2489
             tr("Failed to regenerate layer"),
2490
             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.")
2491
             .arg(layerName).arg(transformName),
2492
             QMessageBox::Ok);
2493
    }
2494
}
2495

    
2496
void
2497
MainWindow::modelRegenerationWarning(QString layerName,
2498
                                     QString /* transformName */,
2499
                                     QString message)
2500
{
2501
    QMessageBox::warning
2502
        (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);
2503
}
2504

    
2505
void
2506
MainWindow::alignmentComplete(AlignmentModel *model)
2507
{
2508
    cerr << "MainWindow::alignmentComplete(" << model << ")" << endl;
2509
    if (model) mapSalientFeatureLayer(model);
2510
    checkpointSession();
2511
}
2512

    
2513
void
2514
MainWindow::alignmentFailed(QString message)
2515
{
2516
    QMessageBox::warning
2517
        (this,
2518
         tr("Failed to calculate alignment"),
2519
         tr("<b>Alignment calculation failed</b><p>Failed to calculate an audio alignment:<p>%1")
2520
         .arg(message),
2521
         QMessageBox::Ok);
2522
}
2523

    
2524
void
2525
MainWindow::showLayerTree()
2526
{
2527
    if (!m_layerTreeView.isNull()) {
2528
        m_layerTreeView->show();
2529
        m_layerTreeView->raise();
2530
        return;
2531
    }
2532

    
2533
    //!!! should use an actual dialog class
2534
        
2535
    m_layerTreeView = new QTreeView();
2536
    LayerTreeModel *tree = new LayerTreeModel(m_paneStack);
2537
    m_layerTreeView->resize(500, 300); //!!!
2538
    m_layerTreeView->setModel(tree);
2539
    m_layerTreeView->expandAll();
2540
    m_layerTreeView->show();
2541
}
2542

    
2543
void
2544
MainWindow::handleOSCMessage(const OSCMessage & /* message */)
2545
{
2546
    cerr << "MainWindow::handleOSCMessage: Not implemented" << endl;
2547
}
2548

    
2549
void
2550
MainWindow::preferences()
2551
{
2552
    if (!m_preferencesDialog.isNull()) {
2553
        m_preferencesDialog->show();
2554
        m_preferencesDialog->raise();
2555
        return;
2556
    }
2557

    
2558
    m_preferencesDialog = new PreferencesDialog(this);
2559

    
2560
    // DeleteOnClose is safe here, because m_preferencesDialog is a
2561
    // QPointer that will be zeroed when the dialog is deleted.  We
2562
    // use it in preference to leaving the dialog lying around because
2563
    // if you Cancel the dialog, it resets the preferences state
2564
    // without resetting its own widgets, so its state will be
2565
    // incorrect when next shown unless we construct it afresh
2566
    m_preferencesDialog->setAttribute(Qt::WA_DeleteOnClose);
2567

    
2568
    m_preferencesDialog->show();
2569
}
2570

    
2571
void
2572
MainWindow::mouseEnteredWidget()
2573
{
2574
    QWidget *w = dynamic_cast<QWidget *>(sender());
2575
    if (!w) return;
2576

    
2577
    if (w == m_mainLevelPan) {
2578
        contextHelpChanged(tr("Adjust the master playback level"));
2579
    } else if (w == m_playSpeed) {
2580
        contextHelpChanged(tr("Adjust the master playback speed"));
2581
    }
2582
}
2583

    
2584
void
2585
MainWindow::mouseLeftWidget()
2586
{
2587
    contextHelpChanged("");
2588
}
2589

    
2590
void
2591
MainWindow::website()
2592
{
2593
    openHelpUrl(tr("http://www.sonicvisualiser.org/sonicvector/"));
2594
}
2595

    
2596
void
2597
MainWindow::help()
2598
{
2599
    openHelpUrl(tr("http://www.sonicvisualiser.org/sonicvector/doc/"));
2600
}
2601

    
2602
void
2603
MainWindow::about()
2604
{
2605
    bool debug = false;
2606
    QString version = "(unknown version)";
2607

    
2608
#ifdef BUILD_DEBUG
2609
    debug = true;
2610
#endif
2611
#ifdef VECT_VERSION
2612
#ifdef SVNREV
2613
    version = tr("Release %1 : Revision %2").arg(VECT_VERSION).arg(SVNREV);
2614
#else
2615
    version = tr("Release %1").arg(VECT_VERSION);
2616
#endif
2617
#else
2618
#ifdef SVNREV
2619
    version = tr("Unreleased : Revision %1").arg(SVNREV);
2620
#endif
2621
#endif
2622

    
2623
    QString aboutText;
2624

    
2625
    aboutText += tr("<h3>About Sonic Vector</h3>");
2626
    aboutText += tr("<p>Sonic Vector is a comparative viewer for sets of related audio recordings.</p>");
2627
    aboutText += tr("<p>%1 : %2 configuration</p>")
2628
        .arg(version)
2629
        .arg(debug ? tr("Debug") : tr("Release"));
2630

    
2631
    aboutText += 
2632
        "<p>Sonic Vector Copyright &copy; 2005 - 2019 Chris Cannam and<br>"
2633
        "Queen Mary, University of London.</p>"
2634
        "<p>This program uses library code from many other authors. Please<br>"
2635
        "refer to the accompanying documentation for more information.</p>"
2636
        "<p>This program is free software; you can redistribute it and/or<br>"
2637
        "modify it under the terms of the GNU General Public License as<br>"
2638
        "published by the Free Software Foundation; either version 2 of the<br>"
2639
        "License, or (at your option) any later version.<br>See the file "
2640
        "COPYING included with this distribution for more information.</p>";
2641
    
2642
    QMessageBox::about(this, tr("About Sonic Vector"), aboutText);
2643
}
2644

    
2645
void
2646
MainWindow::keyReference()
2647
{
2648
    m_keyReference->show();
2649
}
2650

    
2651
void
2652
MainWindow::loadStyle()
2653
{
2654
    m_viewManager->setGlobalDarkBackground(true);
2655

    
2656
#ifdef Q_OS_MAC    
2657
    QString stylepath = ":vect-mac.qss";
2658
#else
2659
    QString stylepath = ":vect.qss";
2660
#endif
2661

    
2662
    QFile file(stylepath);
2663
    if (!file.open(QFile::ReadOnly)) {
2664
        SVCERR << "WARNING: Failed to open style file " << stylepath << endl;
2665
    } else {
2666
        QString styleSheet = QLatin1String(file.readAll());
2667
        qApp->setStyleSheet(styleSheet);
2668
        QPalette pal(Qt::white, Qt::gray, Qt::white, Qt::black, Qt::gray, Qt::white, Qt::white, Qt::black, Qt::black);
2669
        qApp->setPalette(pal);
2670
    }
2671
}
2672

    
2673

    
2674