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 @ 136:5cf745be1767

History | View | Annotate | Download (83 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

    
120

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

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

    
148
    ColourDatabase *cdb = ColourDatabase::getInstance();
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(Qt::white, tr("White")), true);
153
    cdb->setUseDarkBackground(cdb->addColour(QColor(225, 74, 255), tr("Bright Purple")), true);
154
    cdb->setUseDarkBackground(cdb->addColour(QColor(255, 188, 80), tr("Bright Orange")), true);
155
    cdb->setUseDarkBackground(cdb->addColour(Qt::yellow, tr("Bright Yellow")), true);
156

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

    
159

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

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

    
166
    QSettings settings;
167

    
168
    settings.beginGroup("LayerDefaults");
169

    
170
    settings.setValue("waveform",
171
                      QString("<layer scale=\"%1\" channelMode=\"%2\"/>")
172
                      .arg(int(WaveformLayer::MeterScale))
173
                      .arg(int(WaveformLayer::MergeChannels)));
174

    
175
    settings.setValue("timevalues",
176
                      QString("<layer plotStyle=\"%1\"/>")
177
                      .arg(int(TimeValueLayer::PlotStems)));
178

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

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

    
185
    settings.setValue("colour3dplot",
186
                      QString("<layer channel=\"-1\" colourMap=\"Ice\" opaque=\"true\" smooth=\"true\" binScale=\"0\" columnNormalization=\"hybrid\"/>"));
187

    
188
    settings.endGroup();
189

    
190
    settings.beginGroup("MainWindow");
191
    settings.setValue("showstatusbar", false);
192
    settings.endGroup();
193

    
194
    settings.beginGroup("IconLoader");
195
    settings.setValue("invert-icons-on-dark-background", false);
196
    settings.endGroup();
197

    
198
    m_viewManager->setAlignMode(true);
199
    m_viewManager->setPlaySoloMode(true);
200
    m_viewManager->setToolMode(ViewManager::NavigateMode);
201
    m_viewManager->setZoomWheelsEnabled(false);
202
    m_viewManager->setIlluminateLocalFeatures(false);
203
    m_viewManager->setShowWorkTitle(true);
204

    
205
    loadStyle();
206
    
207
    QFrame *mainFrame = new QFrame;
208
    QGridLayout *mainLayout = new QGridLayout;
209

    
210
    setCentralWidget(mainFrame);
211
    
212
    m_mainScroll = new QScrollArea(mainFrame);
213
    m_mainScroll->setWidgetResizable(true);
214
    m_mainScroll->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
215
    m_mainScroll->setFrameShape(QFrame::NoFrame);
216

    
217
    m_paneStack->setResizeMode(PaneStack::AutoResizeOnly);
218
    m_paneStack->setLayoutStyle(PaneStack::NoPropertyStacks);
219
    m_paneStack->setShowAlignmentViews(true);
220
    m_mainScroll->setWidget(m_paneStack);
221

    
222
    QFrame *bottomFrame = new QFrame(mainFrame);
223
    bottomFrame->setObjectName("BottomFrame");
224
    QGridLayout *bottomLayout = new QGridLayout;
225

    
226
    int bottomElementHeight = m_viewManager->scalePixelSize(35);
227
    if (bottomElementHeight < 40) bottomElementHeight = 40;
228
    int bottomButtonHeight = (bottomElementHeight * 3) / 4;
229
    
230
    QButtonGroup *bg = new QButtonGroup;
231
    IconLoader il;
232

    
233
    QFrame *buttonFrame = new QFrame(bottomFrame);
234
    QHBoxLayout *buttonLayout = new QHBoxLayout;
235
    buttonLayout->setSpacing(0);
236
    buttonLayout->setMargin(0);
237
    buttonFrame->setLayout(buttonLayout);
238

    
239
    QToolButton *button = new QToolButton;
240
    button->setIcon(il.load("waveform"));
241
    button->setToolTip(tr("Waveform"));
242
    button->setCheckable(true);
243
    button->setChecked(true);
244
    button->setAutoRaise(true);
245
    button->setFixedWidth(bottomButtonHeight);
246
    button->setFixedHeight(bottomButtonHeight);
247
    bg->addButton(button);
248
    buttonLayout->addWidget(button);
249
    connect(button, SIGNAL(clicked()), this, SLOT(waveformModeSelected()));
250

    
251
    button = new QToolButton;
252
    button->setIcon(il.load("values"));
253
    button->setToolTip(tr("Novelty Curve"));
254
    button->setCheckable(true);
255
    button->setChecked(false);
256
    button->setAutoRaise(true);
257
    button->setFixedWidth(bottomButtonHeight);
258
    button->setFixedHeight(bottomButtonHeight);
259
    bg->addButton(button);
260
    buttonLayout->addWidget(button);
261
    connect(button, SIGNAL(clicked()), this, SLOT(curveModeSelected()));
262

    
263
    button = new QToolButton;
264
    button->setIcon(il.load("pitch"));
265
    button->setToolTip(tr("Pitch Plot"));
266
    button->setCheckable(true);
267
    button->setChecked(false);
268
    button->setAutoRaise(true);
269
    button->setFixedWidth(bottomButtonHeight);
270
    button->setFixedHeight(bottomButtonHeight);
271
    bg->addButton(button);
272
    buttonLayout->addWidget(button);
273
    connect(button, SIGNAL(clicked()), this, SLOT(pitchModeSelected()));
274

    
275
    button = new QToolButton;
276
    button->setIcon(il.load("azimuth"));
277
    button->setToolTip(tr("Stereo Azimuth Plot"));
278
    button->setCheckable(true);
279
    button->setChecked(false);
280
    button->setAutoRaise(true);
281
    button->setFixedWidth(bottomButtonHeight);
282
    button->setFixedHeight(bottomButtonHeight);
283
    bg->addButton(button);
284
    buttonLayout->addWidget(button);
285
    connect(button, SIGNAL(clicked()), this, SLOT(azimuthModeSelected()));
286

    
287
    button = new QToolButton;
288
    button->setIcon(il.load("spectrogram"));
289
    button->setToolTip(tr("Full-Range Spectrogram"));
290
    button->setCheckable(true);
291
    button->setChecked(false);
292
    button->setAutoRaise(true);
293
    button->setFixedWidth(bottomButtonHeight);
294
    button->setFixedHeight(bottomButtonHeight);
295
    bg->addButton(button);
296
    buttonLayout->addWidget(button);
297
    connect(button, SIGNAL(clicked()), this, SLOT(spectrogramModeSelected()));
298

    
299
    button = new QToolButton;
300
    button->setIcon(il.load("melodogram"));
301
    button->setToolTip(tr("Melodic-Range Spectrogram"));
302
    button->setCheckable(true);
303
    button->setChecked(false);
304
    button->setAutoRaise(true);
305
    button->setFixedWidth(bottomButtonHeight);
306
    button->setFixedHeight(bottomButtonHeight);
307
    bg->addButton(button);
308
    buttonLayout->addWidget(button);
309
    connect(button, SIGNAL(clicked()), this, SLOT(melodogramModeSelected()));
310

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

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

    
336
    int spacing = m_viewManager->scalePixelSize(4);
337

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

    
352
    setupMenus();
353
    setupToolbars();
354
    setupHelpMenu();
355

    
356
    statusBar();
357

    
358
    setIconsVisibleInMenus(false);
359
    finaliseMenus();
360

    
361
    openMostRecentSession();
362
}
363

    
364
MainWindow::~MainWindow()
365
{
366
    delete m_keyReference;
367
    delete m_preferencesDialog;
368
    delete m_layerTreeView;
369
    Profiles::getInstance()->dump();
370
}
371

    
372
void
373
MainWindow::setupMenus()
374
{
375
    if (!m_mainMenusCreated) {
376

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

    
387
        m_rightButtonMenu = new QMenu();
388

    
389
        // No -- we don't want tear-off enabled on the right-button
390
        // menu.  If it is enabled, then simply right-clicking and
391
        // releasing will pop up the menu, activate the tear-off, and
392
        // leave the torn-off menu window in front of the main window.
393
        // That isn't desirable.  I'm not sure it ever would be, in a
394
        // context menu -- perhaps technically a Qt bug?
395
//        m_rightButtonMenu->setTearOffEnabled(true);
396
    }
397

    
398
    if (!m_mainMenusCreated) {
399
        CommandHistory::getInstance()->registerMenu(m_rightButtonMenu);
400
        m_rightButtonMenu->addSeparator();
401
    }
402

    
403
    setupFileMenu();
404
//    setupEditMenu();
405
    setupViewMenu();
406

    
407
    m_mainMenusCreated = true;
408
}
409

    
410
void
411
MainWindow::goFullScreen()
412
{
413
    if (!m_viewManager) return;
414

    
415
    if (m_viewManager->getZoomWheelsEnabled()) {
416
        // The wheels seem to end up in the wrong place in full-screen mode
417
        toggleZoomWheels();
418
    }
419

    
420
    QWidget *ps = m_mainScroll->takeWidget();
421
    ps->setParent(0);
422

    
423
    QShortcut *sc;
424

    
425
    sc = new QShortcut(QKeySequence("Esc"), ps);
426
    connect(sc, SIGNAL(activated()), this, SLOT(endFullScreen()));
427

    
428
    sc = new QShortcut(QKeySequence("F11"), ps);
429
    connect(sc, SIGNAL(activated()), this, SLOT(endFullScreen()));
430

    
431
    QAction *acts[] = {
432
        m_playAction, m_zoomInAction, m_zoomOutAction, m_zoomFitAction,
433
        m_scrollLeftAction, m_scrollRightAction, m_showPropertyBoxesAction
434
    };
435

    
436
    for (int i = 0; i < int(sizeof(acts)/sizeof(acts[0])); ++i) {
437
        sc = new QShortcut(acts[i]->shortcut(), ps);
438
        connect(sc, SIGNAL(activated()), acts[i], SLOT(trigger()));
439
    }
440

    
441
    ps->showFullScreen();
442
}
443

    
444
void
445
MainWindow::endFullScreen()
446
{
447
    // these were only created in goFullScreen:
448
    QObjectList cl = m_paneStack->children();
449
    foreach (QObject *o, cl) {
450
        QShortcut *sc = qobject_cast<QShortcut *>(o);
451
        if (sc) delete sc;
452
    }
453

    
454
    m_paneStack->showNormal();
455
    m_mainScroll->setWidget(m_paneStack);
456
}
457

    
458
void
459
MainWindow::setupFileMenu()
460
{
461
    if (m_mainMenusCreated) return;
462

    
463
    QMenu *menu = menuBar()->addMenu(tr("&File"));
464
    menu->setTearOffEnabled(false);
465
    QToolBar *toolbar = addToolBar(tr("File Toolbar"));
466

    
467
    m_keyReference->setCategory(tr("File and Session Management"));
468

    
469
    IconLoader il;
470

    
471
    QIcon icon = il.load("filenew");
472
    QAction *action = new QAction(icon, tr("&Clear Session"), this);
473
    action->setShortcut(tr("Ctrl+N"));
474
    action->setStatusTip(tr("Abandon the current session and start a new one"));
475
    connect(action, SIGNAL(triggered()), this, SLOT(newSession()));
476
    m_keyReference->registerShortcut(action);
477
    menu->addAction(action);
478
    toolbar->addAction(action);
479

    
480
    icon = il.load("fileopen");
481
    action = new QAction(icon, tr("&Add Files..."), this);
482
    action->setShortcut(tr("Ctrl+O"));
483
    action->setStatusTip(tr("Add one or more audio files"));
484
    connect(action, SIGNAL(triggered()), this, SLOT(openFiles()));
485
    m_keyReference->registerShortcut(action);
486
    menu->addAction(action);
487
    toolbar->addAction(action);
488

    
489
    action = new QAction(tr("Add Lo&cation..."), this);
490
    action->setShortcut(tr("Ctrl+Shift+O"));
491
    action->setStatusTip(tr("Add a file from a remote URL"));
492
    connect(action, SIGNAL(triggered()), this, SLOT(openLocation()));
493
    m_keyReference->registerShortcut(action);
494
    menu->addAction(action);
495

    
496
    menu->addSeparator();
497

    
498
    m_recentSessionsMenu = menu->addMenu(tr("&Recent Sessions"));
499
    m_recentSessionsMenu->setTearOffEnabled(false);
500
    setupRecentSessionsMenu();
501
    connect(&m_recentSessions, SIGNAL(recentChanged()),
502
            this, SLOT(setupRecentSessionsMenu()));
503

    
504
    menu->addSeparator();
505
    action = new QAction(tr("&Preferences..."), this);
506
    action->setStatusTip(tr("Adjust the application preferences"));
507
    connect(action, SIGNAL(triggered()), this, SLOT(preferences()));
508
    menu->addAction(action);
509

    
510
    menu->addSeparator();
511
    action = new QAction(il.load("exit"), tr("&Quit"), this);
512
    action->setShortcut(tr("Ctrl+Q"));
513
    action->setStatusTip(tr("Exit Sonic Vector"));
514
    connect(action, SIGNAL(triggered()), this, SLOT(close()));
515
    m_keyReference->registerShortcut(action);
516
    menu->addAction(action);
517
}
518

    
519
void
520
MainWindow::setupEditMenu()
521
{
522
    if (m_mainMenusCreated) return;
523

    
524
    QMenu *menu = menuBar()->addMenu(tr("&Edit"));
525
    menu->setTearOffEnabled(false);
526
    CommandHistory::getInstance()->registerMenu(menu);
527
}
528

    
529
void
530
MainWindow::setupViewMenu()
531
{
532
    if (m_mainMenusCreated) return;
533

    
534
    IconLoader il;
535

    
536
    QAction *action = 0;
537

    
538
    m_keyReference->setCategory(tr("Panning and Navigation"));
539

    
540
    QMenu *menu = menuBar()->addMenu(tr("&View"));
541
    menu->setTearOffEnabled(false);
542
    m_scrollLeftAction = new QAction(tr("Scroll &Left"), this);
543
    m_scrollLeftAction->setShortcut(tr("Left"));
544
    m_scrollLeftAction->setStatusTip(tr("Scroll the current pane to the left"));
545
    connect(m_scrollLeftAction, SIGNAL(triggered()), this, SLOT(scrollLeft()));
546
    connect(this, SIGNAL(canScroll(bool)), m_scrollLeftAction, SLOT(setEnabled(bool)));
547
    m_keyReference->registerShortcut(m_scrollLeftAction);
548
    menu->addAction(m_scrollLeftAction);
549
        
550
    m_scrollRightAction = new QAction(tr("Scroll &Right"), this);
551
    m_scrollRightAction->setShortcut(tr("Right"));
552
    m_scrollRightAction->setStatusTip(tr("Scroll the current pane to the right"));
553
    connect(m_scrollRightAction, SIGNAL(triggered()), this, SLOT(scrollRight()));
554
    connect(this, SIGNAL(canScroll(bool)), m_scrollRightAction, SLOT(setEnabled(bool)));
555
    m_keyReference->registerShortcut(m_scrollRightAction);
556
    menu->addAction(m_scrollRightAction);
557
        
558
    action = new QAction(tr("&Jump Left"), this);
559
    action->setShortcut(tr("Ctrl+Left"));
560
    action->setStatusTip(tr("Scroll the current pane a big step to the left"));
561
    connect(action, SIGNAL(triggered()), this, SLOT(jumpLeft()));
562
    connect(this, SIGNAL(canScroll(bool)), action, SLOT(setEnabled(bool)));
563
    m_keyReference->registerShortcut(action);
564
    menu->addAction(action);
565
        
566
    action = new QAction(tr("J&ump Right"), this);
567
    action->setShortcut(tr("Ctrl+Right"));
568
    action->setStatusTip(tr("Scroll the current pane a big step to the right"));
569
    connect(action, SIGNAL(triggered()), this, SLOT(jumpRight()));
570
    connect(this, SIGNAL(canScroll(bool)), action, SLOT(setEnabled(bool)));
571
    m_keyReference->registerShortcut(action);
572
    menu->addAction(action);
573

    
574
    menu->addSeparator();
575

    
576
    m_keyReference->setCategory(tr("Zoom"));
577

    
578
    m_zoomInAction = new QAction(il.load("zoom-in"),
579
                                 tr("Zoom &In"), this);
580
    m_zoomInAction->setShortcut(tr("Up"));
581
    m_zoomInAction->setStatusTip(tr("Increase the zoom level"));
582
    connect(m_zoomInAction, SIGNAL(triggered()), this, SLOT(zoomIn()));
583
    connect(this, SIGNAL(canZoom(bool)), m_zoomInAction, SLOT(setEnabled(bool)));
584
    m_keyReference->registerShortcut(m_zoomInAction);
585
    menu->addAction(m_zoomInAction);
586
        
587
    m_zoomOutAction = new QAction(il.load("zoom-out"),
588
                                  tr("Zoom &Out"), this);
589
    m_zoomOutAction->setShortcut(tr("Down"));
590
    m_zoomOutAction->setStatusTip(tr("Decrease the zoom level"));
591
    connect(m_zoomOutAction, SIGNAL(triggered()), this, SLOT(zoomOut()));
592
    connect(this, SIGNAL(canZoom(bool)), m_zoomOutAction, SLOT(setEnabled(bool)));
593
    m_keyReference->registerShortcut(m_zoomOutAction);
594
    menu->addAction(m_zoomOutAction);
595
        
596
    action = new QAction(tr("Restore &Default Zoom"), this);
597
    action->setStatusTip(tr("Restore the zoom level to the default"));
598
    connect(action, SIGNAL(triggered()), this, SLOT(zoomDefault()));
599
    connect(this, SIGNAL(canZoom(bool)), action, SLOT(setEnabled(bool)));
600
    menu->addAction(action);
601

    
602
    m_zoomFitAction = new QAction(il.load("zoom-fit"),
603
                                  tr("Zoom to &Fit"), this);
604
    m_zoomFitAction->setShortcut(tr("F"));
605
    m_zoomFitAction->setStatusTip(tr("Zoom to show the whole file"));
606
    connect(m_zoomFitAction, SIGNAL(triggered()), this, SLOT(zoomToFit()));
607
    connect(this, SIGNAL(canZoom(bool)), m_zoomFitAction, SLOT(setEnabled(bool)));
608
    m_keyReference->registerShortcut(m_zoomFitAction);
609
    menu->addAction(m_zoomFitAction);
610

    
611
    menu->addSeparator();
612

    
613
    m_keyReference->setCategory(tr("Display Features"));
614

    
615
    action = new QAction(tr("Show &Centre Line"), this);
616
    action->setShortcut(tr("'"));
617
    action->setStatusTip(tr("Show or hide the centre line"));
618
    connect(action, SIGNAL(triggered()), this, SLOT(toggleCentreLine()));
619
    action->setCheckable(true);
620
    action->setChecked(true);
621
    m_keyReference->registerShortcut(action);
622
    menu->addAction(action);
623

    
624
    action = new QAction(tr("Show Salient Feature Layers"), this);
625
    action->setShortcut(tr("#"));
626
    action->setStatusTip(tr("Show or hide all salient-feature layers"));
627
    connect(action, SIGNAL(triggered()), this, SLOT(toggleSalientFeatures()));
628
    action->setCheckable(true);
629
    action->setChecked(true);
630
    m_keyReference->registerShortcut(action);
631
    menu->addAction(action);
632

    
633
    menu->addSeparator();
634

    
635
    QActionGroup *overlayGroup = new QActionGroup(this);
636
        
637
    action = new QAction(tr("Show &No Overlays"), this);
638
    action->setShortcut(tr("0"));
639
    action->setStatusTip(tr("Hide times, layer names, and scale"));
640
    connect(action, SIGNAL(triggered()), this, SLOT(showNoOverlays()));
641
    action->setCheckable(true);
642
    action->setChecked(false);
643
    overlayGroup->addAction(action);
644
    m_keyReference->registerShortcut(action);
645
    menu->addAction(action);
646
        
647
    action = new QAction(tr("Show &Minimal Overlays"), this);
648
    action->setShortcut(tr("9"));
649
    action->setStatusTip(tr("Show times and basic scale"));
650
    connect(action, SIGNAL(triggered()), this, SLOT(showMinimalOverlays()));
651
    action->setCheckable(true);
652
    action->setChecked(true);
653
    overlayGroup->addAction(action);
654
    m_keyReference->registerShortcut(action);
655
    menu->addAction(action);
656
        
657
    action = new QAction(tr("Show &All Overlays"), this);
658
    action->setShortcut(tr("8"));
659
    action->setStatusTip(tr("Show times, layer names, and scale"));
660
    connect(action, SIGNAL(triggered()), this, SLOT(showAllOverlays()));
661
    action->setCheckable(true);
662
    action->setChecked(false);
663
    overlayGroup->addAction(action);
664
    m_keyReference->registerShortcut(action);
665
    menu->addAction(action);
666
        
667
    menu->addSeparator();
668

    
669
    action = new QAction(tr("Show &Zoom Wheels"), this);
670
    action->setShortcut(tr("Z"));
671
    action->setStatusTip(tr("Show thumbwheels for zooming horizontally and vertically"));
672
    connect(action, SIGNAL(triggered()), this, SLOT(toggleZoomWheels()));
673
    action->setCheckable(true);
674
    action->setChecked(m_viewManager->getZoomWheelsEnabled());
675
    m_keyReference->registerShortcut(action);
676
    menu->addAction(action);
677
        
678
    m_showPropertyBoxesAction = new QAction(tr("Show Property Bo&xes"), this);
679
    m_showPropertyBoxesAction->setShortcut(tr("X"));
680
    m_showPropertyBoxesAction->setStatusTip(tr("Show the layer property boxes at the side of the main window"));
681
    connect(m_showPropertyBoxesAction, SIGNAL(triggered()), this, SLOT(togglePropertyBoxes()));
682
    m_showPropertyBoxesAction->setCheckable(true);
683
    m_showPropertyBoxesAction->setChecked(false);
684
    m_keyReference->registerShortcut(m_showPropertyBoxesAction);
685
    menu->addAction(m_showPropertyBoxesAction);
686

    
687
    action = new QAction(tr("Show Status &Bar"), this);
688
    action->setStatusTip(tr("Show context help information in the status bar at the bottom of the window"));
689
    connect(action, SIGNAL(triggered()), this, SLOT(toggleStatusBar()));
690
    action->setCheckable(true);
691
    action->setChecked(true);
692
    menu->addAction(action);
693

    
694
    QSettings settings;
695
    settings.beginGroup("MainWindow");
696
    bool sb = settings.value("showstatusbar", true).toBool();
697
    if (!sb) {
698
        action->setChecked(false);
699
        statusBar()->hide();
700
    }
701
    settings.endGroup();
702

    
703
    menu->addSeparator();
704

    
705
    action = new QAction(tr("Go Full-Screen"), this);
706
    action->setShortcut(tr("F11"));
707
    action->setStatusTip(tr("Expand the pane area to the whole screen"));
708
    connect(action, SIGNAL(triggered()), this, SLOT(goFullScreen()));
709
    m_keyReference->registerShortcut(action);
710
    menu->addAction(action);
711
}
712

    
713
void
714
MainWindow::setupHelpMenu()
715
{
716
    QMenu *menu = menuBar()->addMenu(tr("&Help"));
717
    menu->setTearOffEnabled(false);
718
    
719
    m_keyReference->setCategory(tr("Help"));
720

    
721
    IconLoader il;
722

    
723
    QAction *action = new QAction(il.load("help"),
724
                                  tr("&Help Reference"), this); 
725
    action->setShortcut(tr("F1"));
726
    action->setStatusTip(tr("Open the reference manual")); 
727
    connect(action, SIGNAL(triggered()), this, SLOT(help()));
728
    m_keyReference->registerShortcut(action);
729
    menu->addAction(action);
730

    
731
    action = new QAction(tr("&Key and Mouse Reference"), this);
732
    action->setShortcut(tr("F2"));
733
    action->setStatusTip(tr("Open a window showing the keystrokes you can use"));
734
    connect(action, SIGNAL(triggered()), this, SLOT(keyReference()));
735
    m_keyReference->registerShortcut(action);
736
    menu->addAction(action);
737
    
738
    action = new QAction(tr("Sonic Vector on the &Web"), this); 
739
    action->setStatusTip(tr("Open the Sonic Vector website")); 
740
    connect(action, SIGNAL(triggered()), this, SLOT(website()));
741
    menu->addAction(action);
742
    
743
    action = new QAction(tr("&About Sonic Vector"), this); 
744
    action->setStatusTip(tr("Show information about Sonic Vector")); 
745
    connect(action, SIGNAL(triggered()), this, SLOT(about()));
746
    menu->addAction(action);
747
}
748

    
749
void
750
MainWindow::setupRecentSessionsMenu()
751
{
752
    m_recentSessionsMenu->clear();
753
    vector<QString> files = m_recentSessions.getRecent();
754
    for (size_t i = 0; i < files.size(); ++i) {
755
        QString path = files[i];
756
        QAction *action = new QAction(path, this);
757
        action->setObjectName(path);
758
        connect(action, SIGNAL(triggered()), this, SLOT(openRecentSession()));
759
        if (i == 0) {
760
            action->setShortcut(tr("Ctrl+R"));
761
            m_keyReference->registerShortcut
762
                (tr("Re-open"),
763
                 action->shortcut().toString(),
764
                 tr("Re-open the current or most recently opened session"));
765
        }
766
        m_recentSessionsMenu->addAction(action);
767
    }
768
}
769

    
770
void
771
MainWindow::setupToolbars()
772
{
773
    m_keyReference->setCategory(tr("Playback and Transport Controls"));
774

    
775
    IconLoader il;
776

    
777
    QMenu *menu = m_playbackMenu = menuBar()->addMenu(tr("Play&back"));
778
    menu->setTearOffEnabled(false);
779
    m_rightButtonMenu->addSeparator();
780
    m_rightButtonPlaybackMenu = m_rightButtonMenu->addMenu(tr("Playback"));
781

    
782
    QToolBar *toolbar = addToolBar(tr("Playback Toolbar"));
783

    
784
    QAction *rwdStartAction = toolbar->addAction(il.load("rewind-start"),
785
                                                 tr("Rewind to Start"));
786
    rwdStartAction->setShortcut(tr("Home"));
787
    rwdStartAction->setStatusTip(tr("Rewind to the start"));
788
    connect(rwdStartAction, SIGNAL(triggered()), this, SLOT(rewindStart()));
789
    connect(this, SIGNAL(canPlay(bool)), rwdStartAction, SLOT(setEnabled(bool)));
790

    
791
    QAction *m_rwdAction = toolbar->addAction(il.load("rewind"),
792
                                              tr("Rewind"));
793
    m_rwdAction->setShortcut(tr("PgUp"));
794
    m_rwdAction->setStatusTip(tr("Rewind to the previous time instant or time ruler notch"));
795
    connect(m_rwdAction, SIGNAL(triggered()), this, SLOT(rewind()));
796
    connect(this, SIGNAL(canRewind(bool)), m_rwdAction, SLOT(setEnabled(bool)));
797

    
798
    m_playAction = toolbar->addAction(il.load("playpause"),
799
                                      tr("Play / Pause"));
800
    m_playAction->setCheckable(true);
801
    m_playAction->setShortcut(tr("Space"));
802
    m_playAction->setStatusTip(tr("Start or stop playback from the current position"));
803
    connect(m_playAction, SIGNAL(triggered()), this, SLOT(play()));
804
    connect(m_playSource, SIGNAL(playStatusChanged(bool)),
805
            m_playAction, SLOT(setChecked(bool)));
806
    connect(this, SIGNAL(canPlay(bool)), m_playAction, SLOT(setEnabled(bool)));
807

    
808
    m_ffwdAction = toolbar->addAction(il.load("ffwd"),
809
                                              tr("Fast Forward"));
810
    m_ffwdAction->setShortcut(tr("PgDown"));
811
    m_ffwdAction->setStatusTip(tr("Fast-forward to the next time instant or time ruler notch"));
812
    connect(m_ffwdAction, SIGNAL(triggered()), this, SLOT(ffwd()));
813
    connect(this, SIGNAL(canFfwd(bool)), m_ffwdAction, SLOT(setEnabled(bool)));
814

    
815
    QAction *ffwdEndAction = toolbar->addAction(il.load("ffwd-end"),
816
                                                tr("Fast Forward to End"));
817
    ffwdEndAction->setShortcut(tr("End"));
818
    ffwdEndAction->setStatusTip(tr("Fast-forward to the end"));
819
    connect(ffwdEndAction, SIGNAL(triggered()), this, SLOT(ffwdEnd()));
820
    connect(this, SIGNAL(canPlay(bool)), ffwdEndAction, SLOT(setEnabled(bool)));
821
/*
822
    toolbar = addToolBar(tr("Play Mode Toolbar"));
823

824
    QAction *psAction = toolbar->addAction(il.load("playselection"),
825
                                           tr("Constrain Playback to Selection"));
826
    psAction->setCheckable(true);
827
    psAction->setChecked(m_viewManager->getPlaySelectionMode());
828
    psAction->setShortcut(tr("s"));
829
    psAction->setStatusTip(tr("Constrain playback to the selected regions"));
830
    connect(m_viewManager, SIGNAL(playSelectionModeChanged(bool)),
831
            psAction, SLOT(setChecked(bool)));
832
    connect(psAction, SIGNAL(triggered()), this, SLOT(playSelectionToggled()));
833
    connect(this, SIGNAL(canPlaySelection(bool)), psAction, SLOT(setEnabled(bool)));
834

835
    QAction *plAction = toolbar->addAction(il.load("playloop"),
836
                                           tr("Loop Playback"));
837
    plAction->setCheckable(true);
838
    plAction->setChecked(m_viewManager->getPlayLoopMode());
839
    plAction->setShortcut(tr("l"));
840
    plAction->setStatusTip(tr("Loop playback"));
841
    connect(m_viewManager, SIGNAL(playLoopModeChanged(bool)),
842
            plAction, SLOT(setChecked(bool)));
843
    connect(plAction, SIGNAL(triggered()), this, SLOT(playLoopToggled()));
844
    connect(this, SIGNAL(canPlay(bool)), plAction, SLOT(setEnabled(bool)));
845

846
    QAction *soAction = toolbar->addAction(il.load("solo"),
847
                                           tr("Solo Current Pane"));
848
    soAction->setCheckable(true);
849
    soAction->setChecked(m_viewManager->getPlaySoloMode());
850
    soAction->setShortcut(tr("o"));
851
    soAction->setStatusTip(tr("Solo the current pane during playback"));
852
    connect(m_viewManager, SIGNAL(playSoloModeChanged(bool)),
853
            soAction, SLOT(setChecked(bool)));
854
    connect(soAction, SIGNAL(triggered()), this, SLOT(playSoloToggled()));
855
    connect(this, SIGNAL(canPlay(bool)), soAction, SLOT(setEnabled(bool)));
856

857
    m_keyReference->registerShortcut(psAction);
858
    m_keyReference->registerShortcut(plAction);
859
    m_keyReference->registerShortcut(soAction);
860
*/
861

    
862

    
863
    QAction *alAction = 0;
864
    alAction = toolbar->addAction(il.load("align"),
865
                                  tr("Align File Timelines"));
866
    alAction->setCheckable(true);
867
    alAction->setChecked(m_viewManager->getAlignMode());
868
    alAction->setStatusTip(tr("Treat multiple audio files as versions of the same work, and align their timelines"));
869
    connect(m_viewManager, SIGNAL(alignModeChanged(bool)),
870
            alAction, SLOT(setChecked(bool)));
871
    connect(alAction, SIGNAL(triggered()), this, SLOT(alignToggled()));
872

    
873
    m_keyReference->registerShortcut(m_playAction);
874
    m_keyReference->registerShortcut(m_rwdAction);
875
    m_keyReference->registerShortcut(m_ffwdAction);
876
    m_keyReference->registerShortcut(rwdStartAction);
877
    m_keyReference->registerShortcut(ffwdEndAction);
878

    
879
/*
880
    menu->addAction(psAction);
881
    menu->addAction(plAction);
882
    menu->addAction(soAction);
883
*/
884
    menu->addAction(m_playAction);
885
    menu->addSeparator();
886
    menu->addAction(m_rwdAction);
887
    menu->addAction(m_ffwdAction);
888
    menu->addSeparator();
889
    menu->addAction(rwdStartAction);
890
    menu->addAction(ffwdEndAction);
891
    menu->addSeparator();
892
    menu->addAction(alAction);
893
    menu->addSeparator();
894

    
895
    m_rightButtonPlaybackMenu->addAction(m_playAction);
896
/*
897
    m_rightButtonPlaybackMenu->addAction(psAction);
898
    m_rightButtonPlaybackMenu->addAction(plAction);
899
    m_rightButtonPlaybackMenu->addAction(soAction);
900
*/
901
    m_rightButtonPlaybackMenu->addSeparator();
902
    m_rightButtonPlaybackMenu->addAction(m_rwdAction);
903
    m_rightButtonPlaybackMenu->addAction(m_ffwdAction);
904
    m_rightButtonPlaybackMenu->addSeparator();
905
    m_rightButtonPlaybackMenu->addAction(rwdStartAction);
906
    m_rightButtonPlaybackMenu->addAction(ffwdEndAction);
907
    m_rightButtonPlaybackMenu->addSeparator();
908
    m_rightButtonPlaybackMenu->addAction(alAction);
909
    m_rightButtonPlaybackMenu->addSeparator();
910

    
911
    QAction *fastAction = menu->addAction(tr("Speed Up"));
912
    fastAction->setShortcut(tr("Ctrl+PgUp"));
913
    fastAction->setStatusTip(tr("Time-stretch playback to speed it up without changing pitch"));
914
    connect(fastAction, SIGNAL(triggered()), this, SLOT(speedUpPlayback()));
915
    connect(this, SIGNAL(canSpeedUpPlayback(bool)), fastAction, SLOT(setEnabled(bool)));
916
    
917
    QAction *slowAction = menu->addAction(tr("Slow Down"));
918
    slowAction->setShortcut(tr("Ctrl+PgDown"));
919
    slowAction->setStatusTip(tr("Time-stretch playback to slow it down without changing pitch"));
920
    connect(slowAction, SIGNAL(triggered()), this, SLOT(slowDownPlayback()));
921
    connect(this, SIGNAL(canSlowDownPlayback(bool)), slowAction, SLOT(setEnabled(bool)));
922

    
923
    QAction *normalAction = menu->addAction(tr("Restore Normal Speed"));
924
    normalAction->setShortcut(tr("Ctrl+Home"));
925
    normalAction->setStatusTip(tr("Restore non-time-stretched playback"));
926
    connect(normalAction, SIGNAL(triggered()), this, SLOT(restoreNormalPlayback()));
927
    connect(this, SIGNAL(canChangePlaybackSpeed(bool)), normalAction, SLOT(setEnabled(bool)));
928

    
929
    m_keyReference->registerShortcut(fastAction);
930
    m_keyReference->registerShortcut(slowAction);
931
    m_keyReference->registerShortcut(normalAction);
932

    
933
    m_rightButtonPlaybackMenu->addAction(fastAction);
934
    m_rightButtonPlaybackMenu->addAction(slowAction);
935
    m_rightButtonPlaybackMenu->addAction(normalAction);
936
/*
937
    toolbar = addToolBar(tr("Edit Toolbar"));
938
    CommandHistory::getInstance()->registerToolbar(toolbar);
939
*/
940

    
941
    Pane::registerShortcuts(*m_keyReference);
942
}
943

    
944
void
945
MainWindow::updateMenuStates()
946
{
947
    MainWindowBase::updateMenuStates();
948

    
949
    Pane *currentPane = 0;
950
    Layer *currentLayer = 0;
951

    
952
    if (m_paneStack) currentPane = m_paneStack->getCurrentPane();
953
    if (currentPane) currentLayer = currentPane->getSelectedLayer();
954

    
955
    bool haveCurrentPane =
956
        (currentPane != 0);
957
    bool haveCurrentLayer =
958
        (haveCurrentPane &&
959
         (currentLayer != 0));
960
    bool haveCurrentTimeInstantsLayer = 
961
        (haveCurrentLayer &&
962
         dynamic_cast<TimeInstantLayer *>(currentLayer));
963
    bool haveCurrentTimeValueLayer = 
964
        (haveCurrentLayer &&
965
         dynamic_cast<TimeValueLayer *>(currentLayer));
966

    
967
    emit canChangePlaybackSpeed(true);
968
    int v = m_playSpeed->value();
969
    emit canSpeedUpPlayback(v < m_playSpeed->maximum());
970
    emit canSlowDownPlayback(v > m_playSpeed->minimum());
971

    
972
    if (m_ffwdAction && m_rwdAction) {
973
        if (haveCurrentTimeInstantsLayer) {
974
            m_ffwdAction->setText(tr("Fast Forward to Next Instant"));
975
            m_ffwdAction->setStatusTip(tr("Fast forward to the next time instant in the current layer"));
976
            m_rwdAction->setText(tr("Rewind to Previous Instant"));
977
            m_rwdAction->setStatusTip(tr("Rewind to the previous time instant in the current layer"));
978
        } else if (haveCurrentTimeValueLayer) {
979
            m_ffwdAction->setText(tr("Fast Forward to Next Point"));
980
            m_ffwdAction->setStatusTip(tr("Fast forward to the next point in the current layer"));
981
            m_rwdAction->setText(tr("Rewind to Previous Point"));
982
            m_rwdAction->setStatusTip(tr("Rewind to the previous point in the current layer"));
983
        } else {
984
            m_ffwdAction->setText(tr("Fast Forward"));
985
            m_ffwdAction->setStatusTip(tr("Fast forward"));
986
            m_rwdAction->setText(tr("Rewind"));
987
            m_rwdAction->setStatusTip(tr("Rewind"));
988
        }
989
    }
990
}
991

    
992
void
993
MainWindow::updateDescriptionLabel()
994
{
995
    if (!getMainModel()) {
996
        return;
997
    }
998

    
999
    QString description;
1000

    
1001
    sv_samplerate_t ssr = getMainModel()->getSampleRate();
1002
    sv_samplerate_t tsr = ssr;
1003
    if (m_playSource) tsr = m_playSource->getDeviceSampleRate();
1004

    
1005
    if (ssr != tsr) {
1006
        description = tr("%1Hz (resampling to %2Hz)").arg(ssr).arg(tsr);
1007
    } else {
1008
        description = QString("%1Hz").arg(ssr);
1009
    }
1010

    
1011
    description = QString("%1 - %2")
1012
        .arg(RealTime::frame2RealTime(getMainModel()->getEndFrame(), ssr)
1013
             .toText(false).c_str())
1014
        .arg(description);
1015

    
1016
    //!!! but we don't actually have a description label
1017
}
1018

    
1019
void
1020
MainWindow::documentModified()
1021
{
1022
    MainWindowBase::documentModified();
1023
    checkpointSession();
1024
}
1025

    
1026
void
1027
MainWindow::documentRestored()
1028
{
1029
    MainWindowBase::documentRestored();
1030
}
1031

    
1032
void
1033
MainWindow::selectMainPane()
1034
{
1035
    if (m_paneStack && m_paneStack->getPaneCount() > 0) {
1036
        m_paneStack->setCurrentPane(m_paneStack->getPane(0));
1037
    }
1038
}
1039

    
1040
void
1041
MainWindow::newSession()
1042
{
1043
    cerr << "MainWindow::newSession" << endl;
1044

    
1045
    closeSession();
1046
    createDocument();
1047

    
1048
    // We need a pane, so that we have something to receive drop events
1049
    
1050
    Pane *pane = m_paneStack->addPane();
1051

    
1052
    connect(pane, SIGNAL(contextHelpChanged(const QString &)),
1053
            this, SLOT(contextHelpChanged(const QString &)));
1054

    
1055
    m_document->setAutoAlignment(m_viewManager->getAlignMode());
1056

    
1057
    CommandHistory::getInstance()->clear();
1058
    CommandHistory::getInstance()->documentSaved();
1059
    documentRestored();
1060
    updateMenuStates();
1061
}
1062

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

    
1072
    while (m_paneStack->getPaneCount() > 0) {
1073

    
1074
        Pane *pane = m_paneStack->getPane(m_paneStack->getPaneCount() - 1);
1075

    
1076
        while (pane->getLayerCount() > 0) {
1077
            m_document->removeLayerFromView
1078
                (pane, pane->getLayer(pane->getLayerCount() - 1));
1079
        }
1080

    
1081
        m_paneStack->deletePane(pane);
1082
    }
1083

    
1084
    while (m_paneStack->getHiddenPaneCount() > 0) {
1085

    
1086
        Pane *pane = m_paneStack->getHiddenPane
1087
            (m_paneStack->getHiddenPaneCount() - 1);
1088

    
1089
        while (pane->getLayerCount() > 0) {
1090
            m_document->removeLayerFromView
1091
                (pane, pane->getLayer(pane->getLayerCount() - 1));
1092
        }
1093

    
1094
        m_paneStack->deletePane(pane);
1095
    }
1096

    
1097
    delete m_document;
1098
    m_document = 0;
1099
    m_viewManager->clearSelections();
1100
    m_timeRulerLayer = 0; // document owned this
1101

    
1102
    setWindowTitle(tr("Sonic Vector"));
1103

    
1104
    CommandHistory::getInstance()->clear();
1105
    CommandHistory::getInstance()->documentSaved();
1106
    documentRestored();
1107
}
1108

    
1109
void
1110
MainWindow::openFiles()
1111
{
1112
    QString orig = m_audioFile;
1113
    if (orig == "") orig = ".";
1114
    else orig = QFileInfo(orig).absoluteDir().canonicalPath();
1115

    
1116
    FileFinder *ff = FileFinder::getInstance();
1117

    
1118
    QStringList paths = ff->getOpenFileNames(FileFinder::AudioFile,
1119
                                             m_audioFile);
1120

    
1121
    m_sessionState = SessionActive;
1122
    
1123
    for (QString path: paths) {
1124
        
1125
        FileOpenStatus status = openPath(path, CreateAdditionalModel);
1126

    
1127
        if (status != FileOpenSucceeded) {
1128
            QMessageBox::critical(this, tr("Failed to open file"),
1129
                                  tr("<b>File open failed</b><p>File \"%1\" could not be opened").arg(path));
1130
        } else {
1131
            configureNewPane(m_paneStack->getCurrentPane());
1132
        }
1133
    }
1134
}
1135

    
1136
void
1137
MainWindow::openLocation()
1138
{
1139
    QSettings settings;
1140
    settings.beginGroup("MainWindow");
1141
    QString lastLocation = settings.value("lastremote", "").toString();
1142

    
1143
    bool ok = false;
1144
    QString text = QInputDialog::getText
1145
        (this, tr("Open Location"),
1146
         tr("Please enter the URL of the location to open:"),
1147
         QLineEdit::Normal, lastLocation, &ok);
1148

    
1149
    if (!ok) return;
1150

    
1151
    settings.setValue("lastremote", text);
1152

    
1153
    if (text.isEmpty()) return;
1154

    
1155
    m_sessionState = SessionActive;
1156
    
1157
    FileOpenStatus status = openPath(text, CreateAdditionalModel);
1158

    
1159
    if (status == FileOpenFailed) {
1160
        QMessageBox::critical(this, tr("Failed to open location"),
1161
                              tr("<b>Open failed</b><p>URL \"%1\" could not be opened").arg(text));
1162
    } else if (status == FileOpenWrongMode) {
1163
        QMessageBox::critical(this, tr("Failed to open location"),
1164
                              tr("<b>Audio required</b><p>Please load at least one audio file before importing annotation data"));
1165
    } else {
1166
        configureNewPane(m_paneStack->getCurrentPane());
1167
    }
1168
}
1169

    
1170
void
1171
MainWindow::openRecentSession()
1172
{
1173
    QObject *obj = sender();
1174
    QAction *action = dynamic_cast<QAction *>(obj);
1175
    
1176
    if (!action) {
1177
        cerr << "WARNING: MainWindow::openRecentSession: sender is not an action"
1178
                  << endl;
1179
        return;
1180
    }
1181

    
1182
    QString path = action->objectName();
1183
    
1184
    if (path == "") {
1185
        cerr << "WARNING: MainWindow::openRecentSession: action incorrectly named"
1186
             << endl;
1187
        return;
1188
    }
1189

    
1190
    m_sessionFile = path;
1191
    m_sessionState = SessionLoading;
1192

    
1193
    SVDEBUG << "MainWindow::openRecentSession: m_sessionFile is now "
1194
            << m_sessionFile << endl;
1195
    
1196
    FileOpenStatus status = openPath(path, ReplaceSession);
1197

    
1198
    if (status == FileOpenSucceeded) {
1199
        m_sessionState = SessionActive;
1200
    } else {
1201
        m_sessionFile = "";
1202
        m_sessionState = NoSession;
1203

    
1204
        if (status == FileOpenFailed) {
1205
            QMessageBox::critical(this, tr("Failed to open location"),
1206
                                  tr("<b>Open failed</b><p>File or URL \"%1\" could not be opened").arg(path));
1207
        } else if (status == FileOpenWrongMode) {
1208
            QMessageBox::critical(this, tr("Failed to open location"),
1209
                                  tr("<b>Audio required</b><p>Please load at least one audio file before importing annotation data"));
1210
        }
1211
    }
1212
}
1213

    
1214
void
1215
MainWindow::openMostRecentSession()
1216
{
1217
    vector<QString> files = m_recentSessions.getRecent();
1218
    if (files.empty()) return;
1219

    
1220
    QString path = files[0];
1221
    if (path == "") return;
1222

    
1223
    m_sessionFile = path;
1224
    m_sessionState = SessionLoading;
1225

    
1226
    SVDEBUG << "MainWindow::openMostRecentSession: m_sessionFile is now "
1227
            << m_sessionFile << endl;
1228
    
1229
    FileOpenStatus status = openPath(path, ReplaceSession);
1230

    
1231
    if (status == FileOpenSucceeded) {
1232
        m_sessionState = SessionActive;
1233
    } else {
1234
        m_sessionFile = "";
1235
        m_sessionState = NoSession;
1236

    
1237
        if (status == FileOpenFailed) {
1238
            QMessageBox::critical(this, tr("Failed to open location"),
1239
                                  tr("<b>Open failed</b><p>File or URL \"%1\" could not be opened").arg(path));
1240
        } else if (status == FileOpenWrongMode) {
1241
            QMessageBox::critical(this, tr("Failed to open location"),
1242
                                  tr("<b>Audio required</b><p>Please load at least one audio file before importing annotation data"));
1243
        }
1244
    }
1245
}
1246

    
1247
bool
1248
MainWindow::selectExistingLayerForMode(Pane *pane,
1249
                                       QString modeName,
1250
                                       Model **createFrom)
1251
{
1252
    // Search the given pane for any layer whose object name matches
1253
    // modeName, showing it if it exists, and hiding all other layers
1254
    // (except for time-instants layers, which are assumed to be used
1255
    // for persistent segment display and are left unmodified).
1256

    
1257
    // In the case where no such layer is found and false is returned,
1258
    // then if the return parameter createFrom is non-null, the value
1259
    // it points to will be set to a pointer to the model from which
1260
    // such a layer should be constructed.
1261

    
1262
    Model *model = 0;
1263

    
1264
    bool have = false;
1265

    
1266
    for (int i = 0; i < pane->getLayerCount(); ++i) {
1267
        
1268
        Layer *layer = pane->getLayer(i);
1269
        if (!layer || qobject_cast<TimeInstantLayer *>(layer)) continue;
1270
        
1271
        Model *lm = layer->getModel();
1272
        while (lm && lm->getSourceModel()) lm = lm->getSourceModel();
1273
        if (qobject_cast<WaveFileModel *>(lm)) model = lm;
1274
        
1275
        QString ln = layer->objectName();
1276
        if (ln != modeName) {
1277
            m_hiddenLayers[pane].insert(layer);
1278
            m_document->removeLayerFromView(pane, layer);
1279
            continue;
1280
        }
1281
        
1282
        have = true;
1283
    }
1284
    
1285
    if (have) return true;
1286

    
1287
    LayerSet &ls = m_hiddenLayers[pane];
1288
    bool found = false;
1289
    for (LayerSet::iterator i = ls.begin(); i != ls.end(); ++i) {
1290
        if ((*i)->objectName() == modeName) {
1291
            m_document->addLayerToView(pane, *i);
1292
            ls.erase(i);
1293
            found = true;
1294
            break;
1295
        }
1296
    }
1297

    
1298
    if (found) return true;
1299

    
1300
    if (createFrom) {
1301
        *createFrom = model;
1302
    }
1303
    return false;
1304
}
1305

    
1306
void
1307
MainWindow::addSalientFeatureLayer(Pane *pane, WaveFileModel *model)
1308
{
1309
    //!!! what if there already is one? could have changed the main
1310
    //!!! model for example
1311

    
1312
    if (!model) {
1313
        cerr << "MainWindow::addSalientFeatureLayer: No model" << endl;
1314
        return;
1315
    }
1316
    
1317
    TransformFactory *tf = TransformFactory::getInstance();
1318
    if (!tf) {
1319
        cerr << "Failed to locate a transform factory!" << endl;
1320
        return;
1321
    }
1322
    
1323
//    TransformId id = "vamp:qm-vamp-plugins:qm-keydetector:key";
1324
    TransformId id = "vamp:nnls-chroma:chordino:simplechord";
1325
    if (!tf->haveTransform(id)) {
1326
        cerr << "No plugin available for salient feature layer; transform is: "
1327
             << id << endl;
1328
        return;
1329
    }
1330

    
1331
    if (!model) {
1332
        return;
1333
    }
1334

    
1335
    m_salientCalculating = true;
1336

    
1337
    Transform transform = tf->getDefaultTransformFor
1338
        (id, model->getSampleRate());
1339

    
1340
    transform.setStepSize(1024);
1341
    transform.setBlockSize(2048);
1342

    
1343
    ModelTransformer::Input input(model, -1);
1344

    
1345
    Layer *newLayer = m_document->createDerivedLayer(transform, model);
1346

    
1347
    if (newLayer) {
1348

    
1349
        TimeInstantLayer *til = qobject_cast<TimeInstantLayer *>(newLayer);
1350
        if (til) {
1351
            til->setPlotStyle(TimeInstantLayer::PlotInstants);
1352
            til->setBaseColour(m_salientColour);
1353
        }
1354

    
1355
        connect(til, SIGNAL(modelCompletionChanged()),
1356
                this, SLOT(salientLayerCompletionChanged()));
1357
        
1358
        m_document->addLayerToView(pane, newLayer);
1359
        m_paneStack->setCurrentLayer(pane, newLayer);
1360
    }
1361
}
1362

    
1363
void
1364
MainWindow::salientLayerCompletionChanged()
1365
{
1366
    Layer *layer = qobject_cast<Layer *>(sender());
1367
    if (layer && layer->getCompletion(0) == 100) {
1368
        m_salientCalculating = false;
1369
        foreach (AlignmentModel *am, m_salientPending) {
1370
            mapSalientFeatureLayer(am);
1371
        }
1372
        m_salientPending.clear();
1373
    }
1374
}
1375

    
1376
TimeInstantLayer *
1377
MainWindow::findSalientFeatureLayer(Pane *pane)
1378
{
1379
    if (!getMainModel()) return nullptr;
1380
    
1381
    if (!pane) {
1382
        for (int i = 0; i < m_paneStack->getPaneCount(); ++i) {
1383

    
1384
            Pane *p = m_paneStack->getPane(i);
1385
            bool isAssociatedWithMainModel = false;
1386

    
1387
            for (int j = 0; j < p->getLayerCount(); ++j) {
1388
                Layer *l = p->getLayer(j);
1389
                if (l->getModel() == getMainModel()) {
1390
                    isAssociatedWithMainModel = true;
1391
                    break;
1392
                }
1393
            }
1394

    
1395
            if (isAssociatedWithMainModel) {
1396
                TimeInstantLayer *layerHere = findSalientFeatureLayer(p);
1397
                if (layerHere) return layerHere;
1398
            }
1399
        }
1400

    
1401
        return nullptr;
1402
    }
1403

    
1404
    for (int i = 0; i < pane->getLayerCount(); ++i) {
1405
        TimeInstantLayer *ll = qobject_cast<TimeInstantLayer *>
1406
            (pane->getLayer(i));
1407
        if (ll) return ll;
1408
    }
1409

    
1410
    return nullptr;
1411
}
1412

    
1413
void
1414
MainWindow::toggleSalientFeatures()
1415
{
1416
    bool targetDormantState = false;
1417

    
1418
    for (int i = 0; i < m_paneStack->getPaneCount(); ++i) {
1419
        Pane *p = m_paneStack->getPane(i);
1420
        TimeInstantLayer *layer = findSalientFeatureLayer(p);
1421
        if (layer) {
1422
            targetDormantState = !(layer->isLayerDormant(p));
1423
            break;
1424
        }
1425
    }
1426

    
1427
    for (int i = 0; i < m_paneStack->getPaneCount(); ++i) {
1428
        Pane *p = m_paneStack->getPane(i);
1429
        TimeInstantLayer *salient = findSalientFeatureLayer(p);
1430
        if (salient) {
1431
            salient->setLayerDormant(p, targetDormantState);
1432
        }
1433
        if (targetDormantState) {
1434
            for (int j = 0; j < p->getLayerCount(); ++j) {
1435
                Layer *l = p->getLayer(j);
1436
                if (l != salient) {
1437
                    p->propertyContainerSelected(p, l);
1438
                    break;
1439
                }
1440
            }
1441
        } else {
1442
            p->propertyContainerSelected(p, salient);
1443
        }
1444
    }
1445
}
1446

    
1447
void
1448
MainWindow::mapSalientFeatureLayer(AlignmentModel *am)
1449
{
1450
    if (m_salientCalculating) {
1451
        m_salientPending.insert(am);
1452
        return;
1453
    }
1454

    
1455
    TimeInstantLayer *salient = findSalientFeatureLayer();
1456
    if (!salient) {
1457
        cerr << "MainWindow::mapSalientFeatureLayer: No salient layer found"
1458
             << endl;
1459
        m_salientPending.insert(am);
1460
        return;
1461
    }
1462

    
1463
    if (!am) {
1464
        cerr << "MainWindow::mapSalientFeatureLayer: AlignmentModel is null!" << endl;
1465
        return;
1466
    }
1467
    
1468
    const Model *model = am->getAlignedModel();
1469

    
1470
    Pane *pane = nullptr;
1471
    Layer *layer = nullptr;
1472
    Pane *firstPane = nullptr;
1473
    
1474
    for (int i = 0; i < m_paneStack->getPaneCount(); ++i) {
1475
        Pane *p = m_paneStack->getPane(i);
1476
        if (p && !firstPane) firstPane = p;
1477
        for (int j = 0; j < p->getLayerCount(); ++j) {
1478
            Layer *l = p->getLayer(j);
1479
            if (!l) continue;
1480
            if (l->getModel() == model) {
1481
                pane = p;
1482
                layer = l;
1483
                break;
1484
            }
1485
        }
1486
        if (layer) break;
1487
    }
1488

    
1489
    if (!pane || !layer) {
1490
        cerr << "MainWindow::mapSalientFeatureLayer: Failed to find model "
1491
             << model << " in any layer" << endl;
1492
        return;
1493
    }
1494

    
1495
    pane->setCentreFrame(am->fromReference(firstPane->getCentreFrame()));
1496
    
1497
    const SparseOneDimensionalModel *from =
1498
        qobject_cast<const SparseOneDimensionalModel *>(salient->getModel());
1499
    if (!from) {
1500
        cerr << "MainWindow::mapSalientFeatureLayer: Salient layer lacks SparseOneDimensionalModel" << endl;
1501
        return;
1502
    }
1503
        
1504
    SparseOneDimensionalModel *to = new SparseOneDimensionalModel
1505
        (model->getSampleRate(), from->getResolution(), false);
1506

    
1507
    EventVector pp = from->getAllEvents();
1508
    for (const auto &p: pp) {
1509
        Event aligned = p
1510
            .withFrame(am->fromReference(p.getFrame()))
1511
            .withLabel(""); // remove label, as the analysis was not
1512
                            // conducted on the audio we're mapping to
1513
        to->add(aligned);
1514
    }
1515

    
1516
    Layer *newLayer = m_document->createImportedLayer(to);
1517

    
1518
    if (newLayer) {
1519

    
1520
        TimeInstantLayer *til = qobject_cast<TimeInstantLayer *>(newLayer);
1521
        if (til) {
1522
            til->setPlotStyle(TimeInstantLayer::PlotInstants);
1523
            til->setBaseColour(m_salientColour);
1524
        }
1525
        
1526
        m_document->addLayerToView(pane, newLayer);
1527
        m_paneStack->setCurrentLayer(pane, newLayer);
1528
    }
1529
}
1530

    
1531
void
1532
MainWindow::curveModeSelected()
1533
{
1534
    QString name = tr("Curve");
1535

    
1536
    for (int i = 0; i < m_paneStack->getPaneCount(); ++i) {
1537

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

    
1541
        Model *createFrom = nullptr;
1542
        if (!selectExistingLayerForMode(pane, name, &createFrom) &&
1543
            createFrom) {
1544

    
1545
            TransformId id = "vamp:qm-vamp-plugins:qm-onsetdetector:detection_fn";
1546
            TransformFactory *tf = TransformFactory::getInstance();
1547

    
1548
            if (tf->haveTransform(id)) {
1549

    
1550
                Transform transform = tf->getDefaultTransformFor
1551
                    (id, createFrom->getSampleRate());
1552

    
1553
                transform.setStepSize(1024);
1554
                transform.setBlockSize(2048);
1555

    
1556
                ModelTransformer::Input input(createFrom, -1);
1557
                
1558
                Layer *newLayer =
1559
                    m_document->createDerivedLayer(transform, createFrom);
1560

    
1561
                if (newLayer) {
1562
                    newLayer->setObjectName(name);
1563
                    m_document->addLayerToView(pane, newLayer);
1564
                    m_paneStack->setCurrentLayer(pane, newLayer);
1565
                }
1566
            
1567
            } else {
1568
                SVCERR << "ERROR: No onset detector plugin available" << endl;
1569
            }
1570
        }
1571

    
1572
        TimeInstantLayer *salient = findSalientFeatureLayer(pane);
1573
        if (salient) {
1574
            pane->propertyContainerSelected(pane, salient);
1575
        }
1576
    }
1577

    
1578
    m_displayMode = CurveMode;
1579
}
1580

    
1581
void
1582
MainWindow::waveformModeSelected()
1583
{
1584
    QString name = tr("Waveform");
1585

    
1586
    for (int i = 0; i < m_paneStack->getPaneCount(); ++i) {
1587

    
1588
        Pane *pane = m_paneStack->getPane(i);
1589
        if (!pane) continue;
1590

    
1591
        Model *createFrom = nullptr;
1592
        if (!selectExistingLayerForMode(pane, name, &createFrom) &&
1593
            createFrom) {
1594
            Layer *newLayer = m_document->createLayer(LayerFactory::Waveform);
1595
            newLayer->setObjectName(name);
1596
            m_document->setModel(newLayer, createFrom);
1597
            m_document->addLayerToView(pane, newLayer);
1598
            m_paneStack->setCurrentLayer(pane, newLayer);
1599
        }
1600

    
1601
        TimeInstantLayer *salient = findSalientFeatureLayer(pane);
1602
        if (salient) {
1603
            pane->propertyContainerSelected(pane, salient);
1604
        }
1605
    }
1606

    
1607
    m_displayMode = WaveformMode;
1608
}
1609

    
1610
void
1611
MainWindow::spectrogramModeSelected()
1612
{
1613
    QString name = tr("Spectrogram");
1614

    
1615
    for (int i = 0; i < m_paneStack->getPaneCount(); ++i) {
1616

    
1617
        Pane *pane = m_paneStack->getPane(i);
1618
        if (!pane) continue;
1619

    
1620
        Model *createFrom = nullptr;
1621
        if (!selectExistingLayerForMode(pane, name, &createFrom) &&
1622
            createFrom) {
1623
            Layer *newLayer = m_document->createLayer(LayerFactory::Spectrogram);
1624
            newLayer->setObjectName(name);
1625
            m_document->setModel(newLayer, createFrom);
1626
            m_document->addLayerToView(pane, newLayer);
1627
            m_paneStack->setCurrentLayer(pane, newLayer);
1628
        }
1629

    
1630
        TimeInstantLayer *salient = findSalientFeatureLayer(pane);
1631
        if (salient) {
1632
            pane->propertyContainerSelected(pane, salient);
1633
        }
1634
    }
1635

    
1636
    m_displayMode = SpectrogramMode;
1637
}
1638

    
1639
void
1640
MainWindow::melodogramModeSelected()
1641
{
1642
    QString name = tr("Melodic Range Spectrogram");
1643

    
1644
    for (int i = 0; i < m_paneStack->getPaneCount(); ++i) {
1645

    
1646
        Pane *pane = m_paneStack->getPane(i);
1647
        if (!pane) continue;
1648

    
1649
        Model *createFrom = nullptr;
1650
        if (!selectExistingLayerForMode(pane, name, &createFrom) &&
1651
            createFrom) {
1652
            Layer *newLayer = m_document->createLayer
1653
                (LayerFactory::MelodicRangeSpectrogram);
1654
            newLayer->setObjectName(name);
1655
            m_document->setModel(newLayer, createFrom);
1656
            m_document->addLayerToView(pane, newLayer);
1657
            m_paneStack->setCurrentLayer(pane, newLayer);
1658
        }
1659

    
1660
        TimeInstantLayer *salient = findSalientFeatureLayer(pane);
1661
        if (salient) {
1662
            pane->propertyContainerSelected(pane, salient);
1663
        }
1664
    }
1665

    
1666
    m_displayMode = MelodogramMode;
1667
}
1668

    
1669
void
1670
MainWindow::pitchModeSelected()
1671
{
1672
    QString name = tr("Pitch");
1673

    
1674
    for (int i = 0; i < m_paneStack->getPaneCount(); ++i) {
1675

    
1676
        Pane *pane = m_paneStack->getPane(i);
1677
        if (!pane) continue;
1678

    
1679
        Model *createFrom = nullptr;
1680
        if (!selectExistingLayerForMode(pane, name, &createFrom) &&
1681
            createFrom) {
1682

    
1683
            TransformId id = "vamp:pyin:pyin:smoothedpitchtrack";
1684
            TransformFactory *tf = TransformFactory::getInstance();
1685

    
1686
            if (tf->haveTransform(id)) {
1687

    
1688
                Transform transform = tf->getDefaultTransformFor
1689
                    (id, createFrom->getSampleRate());
1690

    
1691
                ModelTransformer::Input input(createFrom, -1);
1692
                
1693
                Layer *newLayer =
1694
                    m_document->createDerivedLayer(transform, createFrom);
1695

    
1696
                TimeValueLayer *values =
1697
                    qobject_cast<TimeValueLayer *>(newLayer);
1698

    
1699
                if (values) {
1700
                    values->setPlotStyle(TimeValueLayer::PlotDiscreteCurves);
1701
                }
1702
                
1703
                if (newLayer) {
1704
                    newLayer->setObjectName(name);
1705
                    m_document->addLayerToView(pane, newLayer);
1706
                    m_paneStack->setCurrentLayer(pane, newLayer);
1707
                }
1708
            
1709
            } else {
1710
                SVCERR << "ERROR: No PYin plugin available" << endl;
1711
            }
1712
        }
1713

    
1714
        TimeInstantLayer *salient = findSalientFeatureLayer(pane);
1715
        if (salient) {
1716
            pane->propertyContainerSelected(pane, salient);
1717
        }
1718
    }
1719

    
1720
    m_displayMode = PitchMode;
1721
}
1722

    
1723
void
1724
MainWindow::azimuthModeSelected()
1725
{
1726
    QString name = tr("Azimuth");
1727

    
1728
    for (int i = 0; i < m_paneStack->getPaneCount(); ++i) {
1729

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

    
1733
        Model *createFrom = nullptr;
1734
        if (!selectExistingLayerForMode(pane, name, &createFrom) &&
1735
            createFrom) {
1736

    
1737
            TransformId id = "vamp:azi:azi:plan";
1738
            TransformFactory *tf = TransformFactory::getInstance();
1739

    
1740
            if (tf->haveTransform(id)) {
1741

    
1742
                Transform transform = tf->getDefaultTransformFor
1743
                    (id, createFrom->getSampleRate());
1744

    
1745
                ModelTransformer::Input input(createFrom, -1);
1746
                
1747
                Layer *newLayer =
1748
                    m_document->createDerivedLayer(transform, createFrom);
1749

    
1750
                if (newLayer) {
1751
                    newLayer->setObjectName(name);
1752
                    m_document->addLayerToView(pane, newLayer);
1753
                    m_paneStack->setCurrentLayer(pane, newLayer);
1754
                }
1755
            
1756
            } else {
1757
                SVCERR << "ERROR: No Azimuth plugin available" << endl;
1758
            }
1759
        }
1760

    
1761
        TimeInstantLayer *salient = findSalientFeatureLayer(pane);
1762
        if (salient) {
1763
            pane->propertyContainerSelected(pane, salient);
1764
        }
1765
    }
1766

    
1767
    m_displayMode = AzimuthMode;
1768
}
1769

    
1770
void
1771
MainWindow::reselectMode()
1772
{
1773
    switch (m_displayMode) {
1774
    case CurveMode: curveModeSelected(); break;
1775
    case WaveformMode: waveformModeSelected(); break;
1776
    case SpectrogramMode: spectrogramModeSelected(); break;
1777
    case MelodogramMode: melodogramModeSelected(); break;
1778
    case AzimuthMode: azimuthModeSelected(); break;
1779
    }
1780
}
1781

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

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

    
1796
void
1797
MainWindow::paneAboutToBeDeleted(Pane *)
1798
{
1799
}    
1800

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

    
1806
    QUrl first(uriList[0]);
1807

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

    
1816
        FileOpenStatus status = openDirOfAudio(first.path());
1817

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

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

    
1828
        FileOpenStatus status = openPath(*i, CreateAdditionalModel);
1829

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

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

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

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

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

    
1866
    if (!pane) return;
1867

    
1868
    zoomToFit();
1869
    reselectMode();
1870

    
1871
    Layer *waveformLayer = 0;
1872

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

    
1881
    waveformLayer->setObjectName(tr("Waveform"));
1882
}
1883

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

    
1892
//    cerr << "MainWindow::closeEvent" << endl;
1893

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

    
1900
    closeSession();
1901

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

    
1911
    delete m_keyReference;
1912
    m_keyReference = 0;
1913

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

    
1920
    if (m_layerTreeView &&
1921
        m_layerTreeView->isVisible()) {
1922
        delete m_layerTreeView;
1923
    }
1924

    
1925
    e->accept();
1926

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

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

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

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

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

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

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

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

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

    
2003
void
2004
MainWindow::playSpeedChanged(int position)
2005
{
2006
    PlaySpeedRangeMapper mapper;
2007

    
2008
    double percent = m_playSpeed->mappedValue();
2009
    double factor = mapper.getFactorForValue(percent);
2010

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

    
2013
    int centre = m_playSpeed->defaultValue();
2014

    
2015
    // Percentage is shown to 0dp if >100, to 1dp if <100; factor is
2016
    // shown to 3sf
2017

    
2018
    char pcbuf[30];
2019
    char facbuf[30];
2020
    
2021
    if (position == centre) {
2022
        contextHelpChanged(tr("Playback speed: Normal"));
2023
    } else if (position < centre) {
2024
        sprintf(pcbuf, "%.1f", percent);
2025
        sprintf(facbuf, "%.3g", 1.0 / factor);
2026
        contextHelpChanged(tr("Playback speed: %1% (%2x slower)")
2027
                           .arg(pcbuf)
2028
                           .arg(facbuf));
2029
    } else {
2030
        sprintf(pcbuf, "%.0f", percent);
2031
        sprintf(facbuf, "%.3g", factor);
2032
        contextHelpChanged(tr("Playback speed: %1% (%2x faster)")
2033
                           .arg(pcbuf)
2034
                           .arg(facbuf));
2035
    }
2036

    
2037
    m_playSource->setTimeStretch(1.0 / factor); // factor is a speedup
2038

    
2039
    updateMenuStates();
2040
}
2041

    
2042
void
2043
MainWindow::speedUpPlayback()
2044
{
2045
    int value = m_playSpeed->value();
2046
    value = value + m_playSpeed->pageStep();
2047
    if (value > m_playSpeed->maximum()) value = m_playSpeed->maximum();
2048
    m_playSpeed->setValue(value);
2049
}
2050

    
2051
void
2052
MainWindow::slowDownPlayback()
2053
{
2054
    int value = m_playSpeed->value();
2055
    value = value - m_playSpeed->pageStep();
2056
    if (value < m_playSpeed->minimum()) value = m_playSpeed->minimum();
2057
    m_playSpeed->setValue(value);
2058
}
2059

    
2060
void
2061
MainWindow::restoreNormalPlayback()
2062
{
2063
    m_playSpeed->setValue(m_playSpeed->defaultValue());
2064
}
2065

    
2066
void
2067
MainWindow::updateVisibleRangeDisplay(Pane *p) const
2068
{
2069
    if (!getMainModel() || !p) {
2070
        return;
2071
    }
2072

    
2073
    bool haveSelection = false;
2074
    sv_frame_t startFrame = 0, endFrame = 0;
2075

    
2076
    if (m_viewManager && m_viewManager->haveInProgressSelection()) {
2077

    
2078
        bool exclusive = false;
2079
        Selection s = m_viewManager->getInProgressSelection(exclusive);
2080

    
2081
        if (!s.isEmpty()) {
2082
            haveSelection = true;
2083
            startFrame = s.getStartFrame();
2084
            endFrame = s.getEndFrame();
2085
        }
2086
    }
2087

    
2088
    if (!haveSelection) {
2089
        startFrame = p->getFirstVisibleFrame();
2090
        endFrame = p->getLastVisibleFrame();
2091
    }
2092

    
2093
    RealTime start = RealTime::frame2RealTime
2094
        (startFrame, getMainModel()->getSampleRate());
2095

    
2096
    RealTime end = RealTime::frame2RealTime
2097
        (endFrame, getMainModel()->getSampleRate());
2098

    
2099
    RealTime duration = end - start;
2100

    
2101
    QString startStr, endStr, durationStr;
2102
    startStr = start.toText(true).c_str();
2103
    endStr = end.toText(true).c_str();
2104
    durationStr = duration.toText(true).c_str();
2105

    
2106
    if (haveSelection) {
2107
        m_myStatusMessage = tr("Selection: %1 to %2 (duration %3)")
2108
            .arg(startStr).arg(endStr).arg(durationStr);
2109
    } else {
2110
        m_myStatusMessage = tr("Visible: %1 to %2 (duration %3)")
2111
            .arg(startStr).arg(endStr).arg(durationStr);
2112
    }
2113

    
2114
    statusBar()->showMessage(m_myStatusMessage);
2115
}
2116

    
2117
void
2118
MainWindow::updatePositionStatusDisplays() const
2119
{
2120
    if (!statusBar()->isVisible()) return;
2121

    
2122
}
2123

    
2124
void
2125
MainWindow::monitoringLevelsChanged(float left, float right)
2126
{
2127
    m_mainLevelPan->setMonitoringLevels(left, right);
2128
}
2129

    
2130
void
2131
MainWindow::sampleRateMismatch(sv_samplerate_t requested,
2132
                               sv_samplerate_t actual,
2133
                               bool willResample)
2134
{
2135
    if (!willResample) {
2136
        //!!! more helpful message needed
2137
        QMessageBox::information
2138
            (this, tr("Sample rate mismatch"),
2139
             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.")
2140
             .arg(requested).arg(actual));
2141
    }        
2142

    
2143
    updateDescriptionLabel();
2144
}
2145

    
2146
void
2147
MainWindow::audioOverloadPluginDisabled()
2148
{
2149
    QMessageBox::information
2150
        (this, tr("Audio processing overload"),
2151
         tr("<b>Overloaded</b><p>Audio effects plugin auditioning has been disabled due to a processing overload."));
2152
}
2153

    
2154
void
2155
MainWindow::audioTimeStretchMultiChannelDisabled()
2156
{
2157
    static bool shownOnce = false;
2158
    if (shownOnce) return;
2159
    QMessageBox::information
2160
        (this, tr("Audio processing overload"),
2161
         tr("<b>Overloaded</b><p>Audio playback speed processing has been reduced to a single channel, due to a processing overload."));
2162
    shownOnce = true;
2163
}
2164

    
2165
void
2166
MainWindow::layerRemoved(Layer *layer)
2167
{
2168
    MainWindowBase::layerRemoved(layer);
2169
}
2170

    
2171
void
2172
MainWindow::layerInAView(Layer *layer, bool inAView)
2173
{
2174
    MainWindowBase::layerInAView(layer, inAView);
2175
}
2176

    
2177
void
2178
MainWindow::modelAdded(Model *model)
2179
{
2180
    MainWindowBase::modelAdded(model);
2181
}
2182

    
2183
void
2184
MainWindow::modelAboutToBeDeleted(Model *model)
2185
{
2186
    MainWindowBase::modelAboutToBeDeleted(model);
2187
}
2188

    
2189
QString
2190
MainWindow::makeSessionFilename()
2191
{
2192
    Model *mainModel = getMainModel();
2193
    if (!mainModel) {
2194
        SVDEBUG << "MainWindow::makeSessionFilename: No main model, returning empty filename" << endl;
2195
        return "";
2196
    }
2197
    
2198
    //!!! can refactor in common with RecordDirectory
2199
    
2200
    QDir parentDir(TempDirectory::getInstance()->getContainingPath());
2201
    QString sessionDirName("session");
2202

    
2203
    if (!parentDir.mkpath(sessionDirName)) {
2204
        SVCERR << "ERROR: makeSessionFilename: Failed to create session dir in \"" << parentDir.canonicalPath() << "\"" << endl;
2205
        QMessageBox::critical(this, tr("Failed to create session directory"),
2206
                              tr("<p>Failed to create directory \"%1\" for session files</p>")
2207
                              .arg(parentDir.filePath(sessionDirName)));
2208
        return QString();
2209
    }
2210

    
2211
    QDir sessionDir(parentDir.filePath(sessionDirName));
2212
    
2213
    QDateTime now = QDateTime::currentDateTime();
2214
    QString dateDirName = QString("%1").arg(now.toString("yyyyMMdd"));
2215

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

    
2224
    QDir dateDir(sessionDir.filePath(dateDirName));
2225

    
2226
    QString sessionName = mainModel->getTitle();
2227
    if (sessionName == "") {
2228
        sessionName = mainModel->getLocation();
2229
    }
2230
    sessionName = QFileInfo(sessionName).baseName();
2231

    
2232
    QString sessionExt = 
2233
        InteractiveFileFinder::getInstance()->getApplicationSessionExtension();
2234

    
2235
    QString filePath = dateDir.filePath(QString("%1.%2")
2236
                                        .arg(sessionName)
2237
                                        .arg(sessionExt));
2238
    int suffix = 0;
2239
    while (QFile(filePath).exists()) {
2240
        if (++suffix == 100) {
2241
            SVCERR << "ERROR: makeSessionFilename: Failed to come up with unique session filename for " << sessionName << endl;
2242
            return "";
2243
        }
2244
        filePath = dateDir.filePath(QString("%1-%2.%3")
2245
                                    .arg(sessionName)
2246
                                    .arg(suffix)
2247
                                    .arg(sessionExt));
2248
    }
2249

    
2250
    SVDEBUG << "MainWindow::makeSessionFilename: returning "
2251
            << filePath << endl;
2252

    
2253
    return filePath;
2254
}
2255

    
2256
void
2257
MainWindow::checkpointSession()
2258
{
2259
    if (m_sessionState == NoSession) {
2260
        SVCERR << "MainWindow::checkpointSession: no current session" << endl;
2261
        return;
2262
    }
2263

    
2264
    if (m_sessionState == SessionLoading) {
2265
        SVCERR << "MainWindow::checkpointSession: session is loading" << endl;
2266
        return;
2267
    }
2268

    
2269
    // This check is necessary, so that we don't get into a nasty loop
2270
    // when checkpointing on closeSession called when opening a new
2271
    // session file
2272
    if (!m_documentModified) {
2273
        SVCERR << "MainWindow::checkpointSession: nothing to save" << endl;
2274
        return;
2275
    }
2276
    
2277
    if (m_sessionFile == "") {
2278
        SVCERR << "MainWindow::checkpointSession: no current session file" << endl;
2279
        return;
2280
    }
2281

    
2282
    QString sessionExt = 
2283
        InteractiveFileFinder::getInstance()->getApplicationSessionExtension();
2284

    
2285
    if (!m_sessionFile.endsWith("." + sessionExt)) {
2286
        // At one point in development we had a nasty situation where
2287
        // we loaded an audio file from the recent files list, then
2288
        // immediately saved the session over the top of it! This is
2289
        // just an additional guard against that kind of thing
2290
        SVCERR << "MainWindow::checkpointSession: suspicious session filename "
2291
               << m_sessionFile << ", not saving to it" << endl;
2292
        return;
2293
    }
2294
    
2295
    SVCERR << "MainWindow::checkpointSession: saving to session file: "
2296
           << m_sessionFile << endl;
2297

    
2298
    if (saveSessionFile(m_sessionFile)) {
2299
        CommandHistory::getInstance()->documentSaved();
2300
        documentRestored();
2301
        SVCERR << "MainWindow::checkpointSession complete" << endl;
2302
    } else {
2303
        SVCERR << "MainWindow::checkpointSession: save failed!" << endl;
2304
    }
2305
}
2306

    
2307
void
2308
MainWindow::mainModelChanged(WaveFileModel *model)
2309
{
2310
    SVDEBUG << "MainWindow::mainModelChanged(" << model << ")" << endl;
2311

    
2312
    if (m_sessionState == SessionLoading) {
2313
        SVDEBUG << "MainWindow::mainModelChanged: Session is loading, not (re)making session filename" << endl;
2314
    } else if (!model) {
2315
        SVDEBUG << "MainWindow::mainModelChanged: Null model, not (re)making session filename" << endl;
2316
    } else {
2317
        if (m_sessionState == NoSession) {
2318
            SVDEBUG << "MainWindow::mainModelChanged: Marking session as active" << endl;
2319
            m_sessionState = SessionActive;
2320
        } else {
2321
            SVDEBUG << "MainWindow::mainModelChanged: Session is active" << endl;
2322
        }
2323
        if (m_sessionFile == "") {
2324
            SVDEBUG << "MainWindow::mainModelChanged: No session file set, calling makeSessionFilename" << endl;
2325
            m_sessionFile = makeSessionFilename();
2326
            m_recentSessions.addFile(m_sessionFile);
2327
        }
2328
    }
2329
    
2330
    m_salientPending.clear();
2331
    m_salientCalculating = false;
2332

    
2333
    MainWindowBase::mainModelChanged(model);
2334

    
2335
    if (m_playTarget || m_audioIO) {
2336
        connect(m_mainLevelPan, SIGNAL(levelChanged(float)),
2337
                this, SLOT(mainModelGainChanged(float)));
2338
        connect(m_mainLevelPan, SIGNAL(panChanged(float)),
2339
                this, SLOT(mainModelPanChanged(float)));
2340
    }
2341

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

    
2344
    if (m_sessionState != SessionLoading) {
2345

    
2346
        if (model &&
2347
            m_paneStack &&
2348
            (m_paneStack->getPaneCount() == 0)) {
2349
        
2350
            AddPaneCommand *command = new AddPaneCommand(this);
2351
            CommandHistory::getInstance()->addCommand(command);
2352
            Pane *pane = command->getPane();
2353
            Layer *newLayer = m_document->createMainModelLayer
2354
                (LayerFactory::Waveform);
2355
            if (newLayer) {
2356
                m_document->addLayerToView(pane, newLayer);
2357
            }
2358
            addSalientFeatureLayer(pane, model);
2359
        
2360
        } else {
2361
            addSalientFeatureLayer(m_paneStack->getCurrentPane(), model);
2362
        }
2363
    }
2364

    
2365
    m_document->setAutoAlignment(m_viewManager->getAlignMode());
2366
}
2367

    
2368
void
2369
MainWindow::mainModelGainChanged(float gain)
2370
{
2371
    if (m_playTarget) {
2372
        m_playTarget->setOutputGain(gain);
2373
    } else if (m_audioIO) {
2374
        m_audioIO->setOutputGain(gain);
2375
    }
2376
}
2377

    
2378
void
2379
MainWindow::mainModelPanChanged(float balance)
2380
{
2381
    // this is indeed stereo balance rather than pan
2382
    if (m_playTarget) {
2383
        m_playTarget->setOutputBalance(balance);
2384
    } else if (m_audioIO) {
2385
        m_audioIO->setOutputBalance(balance);
2386
    }
2387
}
2388

    
2389
void
2390
MainWindow::modelGenerationFailed(QString transformName, QString message)
2391
{
2392
    if (message != "") {
2393

    
2394
        QMessageBox::warning
2395
            (this,
2396
             tr("Failed to generate layer"),
2397
             tr("<b>Layer generation failed</b><p>Failed to generate derived layer.<p>The layer transform \"%1\" failed:<p>%2")
2398
             .arg(transformName).arg(message),
2399
             QMessageBox::Ok);
2400
    } else {
2401
        QMessageBox::warning
2402
            (this,
2403
             tr("Failed to generate layer"),
2404
             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.")
2405
             .arg(transformName),
2406
             QMessageBox::Ok);
2407
    }
2408
}
2409

    
2410
void
2411
MainWindow::modelGenerationWarning(QString /* transformName */, QString message)
2412
{
2413
    QMessageBox::warning
2414
        (this, tr("Warning"), message, QMessageBox::Ok);
2415
}
2416

    
2417
void
2418
MainWindow::modelRegenerationFailed(QString layerName,
2419
                                    QString transformName, QString message)
2420
{
2421
    if (message != "") {
2422

    
2423
        QMessageBox::warning
2424
            (this,
2425
             tr("Failed to regenerate layer"),
2426
             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")
2427
             .arg(layerName).arg(transformName).arg(message),
2428
             QMessageBox::Ok);
2429
    } else {
2430
        QMessageBox::warning
2431
            (this,
2432
             tr("Failed to regenerate layer"),
2433
             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.")
2434
             .arg(layerName).arg(transformName),
2435
             QMessageBox::Ok);
2436
    }
2437
}
2438

    
2439
void
2440
MainWindow::modelRegenerationWarning(QString layerName,
2441
                                     QString /* transformName */,
2442
                                     QString message)
2443
{
2444
    QMessageBox::warning
2445
        (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);
2446
}
2447

    
2448
void
2449
MainWindow::alignmentComplete(AlignmentModel *model)
2450
{
2451
    cerr << "MainWindow::alignmentComplete(" << model << ")" << endl;
2452
    if (model) mapSalientFeatureLayer(model);
2453
    checkpointSession();
2454
}
2455

    
2456
void
2457
MainWindow::alignmentFailed(QString message)
2458
{
2459
    QMessageBox::warning
2460
        (this,
2461
         tr("Failed to calculate alignment"),
2462
         tr("<b>Alignment calculation failed</b><p>Failed to calculate an audio alignment:<p>%1")
2463
         .arg(message),
2464
         QMessageBox::Ok);
2465
}
2466

    
2467
void
2468
MainWindow::rightButtonMenuRequested(Pane *pane, QPoint position)
2469
{
2470
//    cerr << "MainWindow::rightButtonMenuRequested(" << pane << ", " << position.x() << ", " << position.y() << ")" << endl;
2471
    m_paneStack->setCurrentPane(pane);
2472
    m_rightButtonMenu->popup(position);
2473
}
2474

    
2475
void
2476
MainWindow::showLayerTree()
2477
{
2478
    if (!m_layerTreeView.isNull()) {
2479
        m_layerTreeView->show();
2480
        m_layerTreeView->raise();
2481
        return;
2482
    }
2483

    
2484
    //!!! should use an actual dialog class
2485
        
2486
    m_layerTreeView = new QTreeView();
2487
    LayerTreeModel *tree = new LayerTreeModel(m_paneStack);
2488
    m_layerTreeView->resize(500, 300); //!!!
2489
    m_layerTreeView->setModel(tree);
2490
    m_layerTreeView->expandAll();
2491
    m_layerTreeView->show();
2492
}
2493

    
2494
void
2495
MainWindow::handleOSCMessage(const OSCMessage & /* message */)
2496
{
2497
    cerr << "MainWindow::handleOSCMessage: Not implemented" << endl;
2498
}
2499

    
2500
void
2501
MainWindow::preferences()
2502
{
2503
    if (!m_preferencesDialog.isNull()) {
2504
        m_preferencesDialog->show();
2505
        m_preferencesDialog->raise();
2506
        return;
2507
    }
2508

    
2509
    m_preferencesDialog = new PreferencesDialog(this);
2510

    
2511
    // DeleteOnClose is safe here, because m_preferencesDialog is a
2512
    // QPointer that will be zeroed when the dialog is deleted.  We
2513
    // use it in preference to leaving the dialog lying around because
2514
    // if you Cancel the dialog, it resets the preferences state
2515
    // without resetting its own widgets, so its state will be
2516
    // incorrect when next shown unless we construct it afresh
2517
    m_preferencesDialog->setAttribute(Qt::WA_DeleteOnClose);
2518

    
2519
    m_preferencesDialog->show();
2520
}
2521

    
2522
void
2523
MainWindow::mouseEnteredWidget()
2524
{
2525
    QWidget *w = dynamic_cast<QWidget *>(sender());
2526
    if (!w) return;
2527

    
2528
    if (w == m_mainLevelPan) {
2529
        contextHelpChanged(tr("Adjust the master playback level"));
2530
    } else if (w == m_playSpeed) {
2531
        contextHelpChanged(tr("Adjust the master playback speed"));
2532
    }
2533
}
2534

    
2535
void
2536
MainWindow::mouseLeftWidget()
2537
{
2538
    contextHelpChanged("");
2539
}
2540

    
2541
void
2542
MainWindow::website()
2543
{
2544
    openHelpUrl(tr("http://www.sonicvisualiser.org/sonicvector/"));
2545
}
2546

    
2547
void
2548
MainWindow::help()
2549
{
2550
    openHelpUrl(tr("http://www.sonicvisualiser.org/sonicvector/doc/"));
2551
}
2552

    
2553
void
2554
MainWindow::about()
2555
{
2556
    bool debug = false;
2557
    QString version = "(unknown version)";
2558

    
2559
#ifdef BUILD_DEBUG
2560
    debug = true;
2561
#endif
2562
#ifdef VECT_VERSION
2563
#ifdef SVNREV
2564
    version = tr("Release %1 : Revision %2").arg(VECT_VERSION).arg(SVNREV);
2565
#else
2566
    version = tr("Release %1").arg(VECT_VERSION);
2567
#endif
2568
#else
2569
#ifdef SVNREV
2570
    version = tr("Unreleased : Revision %1").arg(SVNREV);
2571
#endif
2572
#endif
2573

    
2574
    QString aboutText;
2575

    
2576
    aboutText += tr("<h3>About Sonic Vector</h3>");
2577
    aboutText += tr("<p>Sonic Vector is a comparative viewer for sets of related audio recordings.</p>");
2578
    aboutText += tr("<p>%1 : %2 configuration</p>")
2579
        .arg(version)
2580
        .arg(debug ? tr("Debug") : tr("Release"));
2581

    
2582
    aboutText += 
2583
        "<p>Sonic Vector Copyright &copy; 2005 - 2019 Chris Cannam and<br>"
2584
        "Queen Mary, University of London.</p>"
2585
        "<p>This program uses library code from many other authors. Please<br>"
2586
        "refer to the accompanying documentation for more information.</p>"
2587
        "<p>This program is free software; you can redistribute it and/or<br>"
2588
        "modify it under the terms of the GNU General Public License as<br>"
2589
        "published by the Free Software Foundation; either version 2 of the<br>"
2590
        "License, or (at your option) any later version.<br>See the file "
2591
        "COPYING included with this distribution for more information.</p>";
2592
    
2593
    QMessageBox::about(this, tr("About Sonic Vector"), aboutText);
2594
}
2595

    
2596
void
2597
MainWindow::keyReference()
2598
{
2599
    m_keyReference->show();
2600
}
2601

    
2602
void
2603
MainWindow::loadStyle()
2604
{
2605
    m_viewManager->setGlobalDarkBackground(true);
2606
    
2607
    QString stylepath = ":vect.qss";
2608
    QFile file(stylepath);
2609
    if (!file.open(QFile::ReadOnly)) {
2610
        SVCERR << "WARNING: Failed to open style file " << stylepath << endl;
2611
    } else {
2612
        QString styleSheet = QLatin1String(file.readAll());
2613
        qApp->setStyleSheet(styleSheet);
2614
        QPalette pal(Qt::white, Qt::gray, Qt::white, Qt::black, Qt::gray, Qt::white, Qt::white, Qt::black, Qt::black);
2615
        qApp->setPalette(pal);
2616
    }
2617
}
2618

    
2619

    
2620