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 @ 110:125520c3dc05

History | View | Annotate | Download (73.8 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 "view/ViewManager.h"
30
#include "base/Preferences.h"
31
#include "layer/WaveformLayer.h"
32
#include "layer/TimeRulerLayer.h"
33
#include "layer/TimeInstantLayer.h"
34
#include "layer/TimeValueLayer.h"
35
#include "layer/Colour3DPlotLayer.h"
36
#include "layer/SliceLayer.h"
37
#include "layer/SliceableLayer.h"
38
#include "view/Overview.h"
39
#include "widgets/PropertyBox.h"
40
#include "widgets/PropertyStack.h"
41
#include "widgets/AudioDial.h"
42
#include "widgets/LevelPanWidget.h"
43
#include "widgets/LevelPanToolButton.h"
44
#include "widgets/IconLoader.h"
45
#include "widgets/LayerTree.h"
46
#include "widgets/ListInputDialog.h"
47
#include "widgets/SubdividingMenu.h"
48
#include "widgets/NotifyingPushButton.h"
49
#include "widgets/KeyReference.h"
50
#include "audio/AudioCallbackPlaySource.h"
51
#include "audio/PlaySpeedRangeMapper.h"
52
#include "data/fileio/DataFileReaderFactory.h"
53
#include "data/fileio/PlaylistFileReader.h"
54
#include "data/fileio/WavFileWriter.h"
55
#include "data/fileio/CSVFileWriter.h"
56
#include "data/fileio/BZipFileDevice.h"
57
#include "data/fileio/FileSource.h"
58
#include "base/RecentFiles.h"
59
#include "transform/TransformFactory.h"
60
#include "transform/ModelTransformerFactory.h"
61
#include "base/PlayParameterRepository.h"
62
#include "base/XmlExportable.h"
63
#include "widgets/CommandHistory.h"
64
#include "base/Profiler.h"
65
#include "base/Clipboard.h"
66
#include "base/UnitDatabase.h"
67
#include "layer/ColourDatabase.h"
68
#include "data/osc/OSCQueue.h"
69

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

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

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

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

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

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

    
115
using std::vector;
116
using std::map;
117
using std::set;
118

    
119

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

    
140
    StorageAdviser::setFixedRecommendation
141
        (StorageAdviser::Recommendation(StorageAdviser::UseDisc |
142
                                        StorageAdviser::ConserveSpace));
143

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

    
149
    ColourDatabase *cdb = ColourDatabase::getInstance();
150
    cdb->addColour(Qt::black, tr("Black"));
151
    cdb->addColour(Qt::darkRed, tr("Red"));
152
    cdb->addColour(Qt::darkBlue, tr("Blue"));
153
    cdb->addColour(Qt::darkGreen, tr("Green"));
154
    cdb->addColour(QColor(200, 50, 255), tr("Purple"));
155
    cdb->addColour(QColor(255, 150, 50), tr("Orange"));
156
    cdb->setUseDarkBackground(cdb->addColour(Qt::white, tr("White")), true);
157
    cdb->setUseDarkBackground(cdb->addColour(Qt::red, tr("Bright Red")), true);
158
    cdb->setUseDarkBackground(cdb->addColour(QColor(30, 150, 255), tr("Bright Blue")), true);
159
    cdb->setUseDarkBackground(cdb->addColour(Qt::green, tr("Bright Green")), true);
160
    cdb->setUseDarkBackground(cdb->addColour(QColor(225, 74, 255), tr("Bright Purple")), true);
161
    cdb->setUseDarkBackground(cdb->addColour(QColor(255, 188, 80), tr("Bright Orange")), true);
162

    
163
    Preferences::getInstance()->setResampleOnLoad(true);
164

    
165

    
166
    Preferences::getInstance()->setSpectrogramSmoothing
167
        (Preferences::SpectrogramInterpolated);
168

    
169
    Preferences::getInstance()->setSpectrogramXSmoothing
170
        (Preferences::SpectrogramXInterpolated);
171

    
172
    QSettings settings;
173

    
174
    settings.beginGroup("LayerDefaults");
175

    
176
    settings.setValue("waveform",
177
                      QString("<layer scale=\"%1\" channelMode=\"%2\"/>")
178
                      .arg(int(WaveformLayer::MeterScale))
179
                      .arg(int(WaveformLayer::MergeChannels)));
180

    
181
    settings.setValue("timevalues",
182
                      QString("<layer plotStyle=\"%1\"/>")
183
                      .arg(int(TimeValueLayer::PlotStems)));
184

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

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

    
191
    settings.endGroup();
192

    
193
    settings.beginGroup("MainWindow");
194
    settings.setValue("showstatusbar", false);
195
    settings.endGroup();
196

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

    
204
    loadStyle();
205
    
206
    QFrame *frame = new QFrame;
207
    setCentralWidget(frame);
208

    
209
    QGridLayout *layout = new QGridLayout;
210
    
211
    m_mainScroll = new QScrollArea(frame);
212
    m_mainScroll->setWidgetResizable(true);
213
    m_mainScroll->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
214
    m_mainScroll->setFrameShape(QFrame::NoFrame);
215

    
216
    m_paneStack->setLayoutStyle(PaneStack::NoPropertyStacks);
217
    m_paneStack->setShowAlignmentViews(true);
218
    m_mainScroll->setWidget(m_paneStack);
219
    
220
    QButtonGroup *bg = new QButtonGroup;
221
    IconLoader il;
222

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

    
229
    QToolButton *button = new QToolButton;
230
    button->setIcon(il.load("waveform"));
231
    button->setToolTip(tr("Waveform"));
232
    button->setCheckable(true);
233
    button->setChecked(true);
234
    button->setAutoRaise(true);
235
    bg->addButton(button);
236
    buttonLayout->addWidget(button);
237
    connect(button, SIGNAL(clicked()), this, SLOT(waveformModeSelected()));
238

    
239
    button = new QToolButton;
240
    button->setIcon(il.load("values"));
241
    button->setToolTip(tr("Novelty Curve"));
242
    button->setCheckable(true);
243
    button->setChecked(false);
244
    button->setAutoRaise(true);
245
    bg->addButton(button);
246
    buttonLayout->addWidget(button);
247
    connect(button, SIGNAL(clicked()), this, SLOT(curveModeSelected()));
248

    
249
    button = new QToolButton;
250
    button->setIcon(il.load("spectrogram"));
251
    button->setToolTip(tr("Full-Range Spectrogram"));
252
    button->setCheckable(true);
253
    button->setChecked(false);
254
    button->setAutoRaise(true);
255
    bg->addButton(button);
256
    buttonLayout->addWidget(button);
257
    connect(button, SIGNAL(clicked()), this, SLOT(spectrogramModeSelected()));
258

    
259
    button = new QToolButton;
260
    button->setIcon(il.load("melodogram"));
261
    button->setToolTip(tr("Melodic-Range Spectrogram"));
262
    button->setCheckable(true);
263
    button->setChecked(false);
264
    button->setAutoRaise(true);
265
    bg->addButton(button);
266
    buttonLayout->addWidget(button);
267
    connect(button, SIGNAL(clicked()), this, SLOT(melodogramModeSelected()));
268

    
269
    layout->addWidget(buttonFrame, 1, 0);
270

    
271
    m_overview = new Overview(frame);
272
    m_overview->setViewManager(m_viewManager);
273
    int overviewHeight = m_viewManager->scalePixelSize(35);
274
    if (overviewHeight < 40) overviewHeight = 40;
275
    m_overview->setFixedHeight(overviewHeight);
276
#ifndef _WIN32
277
    // For some reason, the contents of the overview never appear if we
278
    // make this setting on Windows.  I have no inclination at the moment
279
    // to track down the reason why.
280
    m_overview->setFrameStyle(QFrame::StyledPanel | QFrame::Sunken);
281
#endif
282
    connect(m_overview, SIGNAL(contextHelpChanged(const QString &)),
283
            this, SLOT(contextHelpChanged(const QString &)));
284
    m_overview->hide();
285

    
286
    m_panLayer = new WaveformLayer;
287
    m_panLayer->setChannelMode(WaveformLayer::MergeChannels);
288
    m_panLayer->setAggressiveCacheing(true);
289
    m_overview->addLayer(m_panLayer);
290

    
291
    if (m_viewManager->getGlobalDarkBackground()) {
292
        m_panLayer->setBaseColour
293
            (ColourDatabase::getInstance()->getColourIndex(tr("Bright Green")));
294
    } else {
295
        m_panLayer->setBaseColour
296
            (ColourDatabase::getInstance()->getColourIndex(tr("Green")));
297
    }        
298

    
299
    m_playSpeed = new AudioDial(frame);
300
    m_playSpeed->setMinimum(0);
301
    m_playSpeed->setMaximum(120);
302
    m_playSpeed->setValue(60);
303
    m_playSpeed->setFixedWidth(overviewHeight);
304
    m_playSpeed->setFixedHeight(overviewHeight);
305
    m_playSpeed->setNotchesVisible(true);
306
    m_playSpeed->setPageStep(10);
307
    m_playSpeed->setObjectName(tr("Playback Speed"));
308
    m_playSpeed->setRangeMapper(new PlaySpeedRangeMapper);
309
    m_playSpeed->setDefaultValue(60);
310
    m_playSpeed->setShowToolTip(true);
311
    connect(m_playSpeed, SIGNAL(valueChanged(int)),
312
            this, SLOT(playSpeedChanged(int)));
313
    connect(m_playSpeed, SIGNAL(mouseEntered()), this, SLOT(mouseEnteredWidget()));
314
    connect(m_playSpeed, SIGNAL(mouseLeft()), this, SLOT(mouseLeftWidget()));
315

    
316
    m_mainLevelPan = new LevelPanToolButton(frame);
317
    connect(m_mainLevelPan, SIGNAL(mouseEntered()), this, SLOT(mouseEnteredWidget()));
318
    connect(m_mainLevelPan, SIGNAL(mouseLeft()), this, SLOT(mouseLeftWidget()));
319
    m_mainLevelPan->setFixedHeight(overviewHeight);
320
    m_mainLevelPan->setFixedWidth(overviewHeight);
321
    m_mainLevelPan->setImageSize((overviewHeight * 3) / 4);
322
    m_mainLevelPan->setBigImageSize(overviewHeight * 3);
323

    
324
    m_playControlsSpacer = new QFrame;
325

    
326
    layout->setSpacing(m_viewManager->scalePixelSize(4));
327
    layout->addWidget(m_mainScroll, 0, 0, 1, 6);
328
    layout->addWidget(m_overview, 1, 1);
329
    layout->addWidget(m_playSpeed, 1, 2);
330
    layout->addWidget(m_playControlsSpacer, 1, 3);
331
    layout->addWidget(m_mainLevelPan, 1, 4);
332

    
333
    m_playControlsSpacer->setFixedSize(QSize(2, 2));
334
    layout->setColumnStretch(1, 10);
335
    
336
    frame->setLayout(layout);
337

    
338
    setupMenus();
339
    setupToolbars();
340
    setupHelpMenu();
341

    
342
    statusBar();
343

    
344
    setIconsVisibleInMenus(false);
345
    finaliseMenus();
346

    
347
    newSession();
348
}
349

    
350
MainWindow::~MainWindow()
351
{
352
    delete m_keyReference;
353
    delete m_preferencesDialog;
354
    delete m_layerTreeView;
355
    Profiles::getInstance()->dump();
356
}
357

    
358
void
359
MainWindow::setupMenus()
360
{
361
    if (!m_mainMenusCreated) {
362

    
363
#ifdef Q_OS_LINUX
364
        // In Ubuntu 14.04 the window's menu bar goes missing entirely
365
        // if the user is running any desktop environment other than Unity
366
        // (in which the faux single-menubar appears). The user has a
367
        // workaround, to remove the appmenu-qt5 package, but that is
368
        // awkward and the problem is so severe that it merits disabling
369
        // the system menubar integration altogether. Like this:
370
        menuBar()->setNativeMenuBar(false);
371
#endif
372

    
373
        m_rightButtonMenu = new QMenu();
374

    
375
        // No -- we don't want tear-off enabled on the right-button
376
        // menu.  If it is enabled, then simply right-clicking and
377
        // releasing will pop up the menu, activate the tear-off, and
378
        // leave the torn-off menu window in front of the main window.
379
        // That isn't desirable.  I'm not sure it ever would be, in a
380
        // context menu -- perhaps technically a Qt bug?
381
//        m_rightButtonMenu->setTearOffEnabled(true);
382
    }
383

    
384
    if (!m_mainMenusCreated) {
385
        CommandHistory::getInstance()->registerMenu(m_rightButtonMenu);
386
        m_rightButtonMenu->addSeparator();
387
    }
388

    
389
    setupFileMenu();
390
//    setupEditMenu();
391
    setupViewMenu();
392

    
393
    m_mainMenusCreated = true;
394
}
395

    
396
void
397
MainWindow::goFullScreen()
398
{
399
    if (!m_viewManager) return;
400

    
401
    if (m_viewManager->getZoomWheelsEnabled()) {
402
        // The wheels seem to end up in the wrong place in full-screen mode
403
        toggleZoomWheels();
404
    }
405

    
406
    QWidget *ps = m_mainScroll->takeWidget();
407
    ps->setParent(0);
408

    
409
    QShortcut *sc;
410

    
411
    sc = new QShortcut(QKeySequence("Esc"), ps);
412
    connect(sc, SIGNAL(activated()), this, SLOT(endFullScreen()));
413

    
414
    sc = new QShortcut(QKeySequence("F11"), ps);
415
    connect(sc, SIGNAL(activated()), this, SLOT(endFullScreen()));
416

    
417
    QAction *acts[] = {
418
        m_playAction, m_zoomInAction, m_zoomOutAction, m_zoomFitAction,
419
        m_scrollLeftAction, m_scrollRightAction, m_showPropertyBoxesAction
420
    };
421

    
422
    for (int i = 0; i < int(sizeof(acts)/sizeof(acts[0])); ++i) {
423
        sc = new QShortcut(acts[i]->shortcut(), ps);
424
        connect(sc, SIGNAL(activated()), acts[i], SLOT(trigger()));
425
    }
426

    
427
    ps->showFullScreen();
428
}
429

    
430
void
431
MainWindow::endFullScreen()
432
{
433
    // these were only created in goFullScreen:
434
    QObjectList cl = m_paneStack->children();
435
    foreach (QObject *o, cl) {
436
        QShortcut *sc = qobject_cast<QShortcut *>(o);
437
        if (sc) delete sc;
438
    }
439

    
440
    m_paneStack->showNormal();
441
    m_mainScroll->setWidget(m_paneStack);
442
}
443

    
444
void
445
MainWindow::setupFileMenu()
446
{
447
    if (m_mainMenusCreated) return;
448

    
449
    QMenu *menu = menuBar()->addMenu(tr("&File"));
450
    menu->setTearOffEnabled(false);
451
    QToolBar *toolbar = addToolBar(tr("File Toolbar"));
452

    
453
    m_keyReference->setCategory(tr("File and Session Management"));
454

    
455
    IconLoader il;
456

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

    
466
    icon = il.load("fileopen");
467
    action = new QAction(icon, tr("&Add File..."), this);
468
    action->setShortcut(tr("Ctrl+O"));
469
    action->setStatusTip(tr("Add a file"));
470
    connect(action, SIGNAL(triggered()), this, SLOT(openFile()));
471
    m_keyReference->registerShortcut(action);
472
    menu->addAction(action);
473
    toolbar->addAction(action);
474

    
475
    action = new QAction(tr("Add Lo&cation..."), this);
476
    action->setShortcut(tr("Ctrl+Shift+O"));
477
    action->setStatusTip(tr("Add a file from a remote URL"));
478
    connect(action, SIGNAL(triggered()), this, SLOT(openLocation()));
479
    m_keyReference->registerShortcut(action);
480
    menu->addAction(action);
481

    
482
    menu->addSeparator();
483

    
484
    m_recentFilesMenu = menu->addMenu(tr("&Recent Locations"));
485
    m_recentFilesMenu->setTearOffEnabled(false);
486
    setupRecentFilesMenu();
487
    connect(&m_recentFiles, SIGNAL(recentChanged()),
488
            this, SLOT(setupRecentFilesMenu()));
489

    
490
    menu->addSeparator();
491
    action = new QAction(tr("&Preferences..."), this);
492
    action->setStatusTip(tr("Adjust the application preferences"));
493
    connect(action, SIGNAL(triggered()), this, SLOT(preferences()));
494
    menu->addAction(action);
495

    
496
    menu->addSeparator();
497
    action = new QAction(il.load("exit"), tr("&Quit"), this);
498
    action->setShortcut(tr("Ctrl+Q"));
499
    action->setStatusTip(tr("Exit Sonic Vector"));
500
    connect(action, SIGNAL(triggered()), this, SLOT(close()));
501
    m_keyReference->registerShortcut(action);
502
    menu->addAction(action);
503
}
504

    
505
void
506
MainWindow::setupEditMenu()
507
{
508
    if (m_mainMenusCreated) return;
509

    
510
    QMenu *menu = menuBar()->addMenu(tr("&Edit"));
511
    menu->setTearOffEnabled(false);
512
    CommandHistory::getInstance()->registerMenu(menu);
513
}
514

    
515
void
516
MainWindow::setupViewMenu()
517
{
518
    if (m_mainMenusCreated) return;
519

    
520
    IconLoader il;
521

    
522
    QAction *action = 0;
523

    
524
    m_keyReference->setCategory(tr("Panning and Navigation"));
525

    
526
    QMenu *menu = menuBar()->addMenu(tr("&View"));
527
    menu->setTearOffEnabled(false);
528
    m_scrollLeftAction = new QAction(tr("Scroll &Left"), this);
529
    m_scrollLeftAction->setShortcut(tr("Left"));
530
    m_scrollLeftAction->setStatusTip(tr("Scroll the current pane to the left"));
531
    connect(m_scrollLeftAction, SIGNAL(triggered()), this, SLOT(scrollLeft()));
532
    connect(this, SIGNAL(canScroll(bool)), m_scrollLeftAction, SLOT(setEnabled(bool)));
533
    m_keyReference->registerShortcut(m_scrollLeftAction);
534
    menu->addAction(m_scrollLeftAction);
535
        
536
    m_scrollRightAction = new QAction(tr("Scroll &Right"), this);
537
    m_scrollRightAction->setShortcut(tr("Right"));
538
    m_scrollRightAction->setStatusTip(tr("Scroll the current pane to the right"));
539
    connect(m_scrollRightAction, SIGNAL(triggered()), this, SLOT(scrollRight()));
540
    connect(this, SIGNAL(canScroll(bool)), m_scrollRightAction, SLOT(setEnabled(bool)));
541
    m_keyReference->registerShortcut(m_scrollRightAction);
542
    menu->addAction(m_scrollRightAction);
543
        
544
    action = new QAction(tr("&Jump Left"), this);
545
    action->setShortcut(tr("Ctrl+Left"));
546
    action->setStatusTip(tr("Scroll the current pane a big step to the left"));
547
    connect(action, SIGNAL(triggered()), this, SLOT(jumpLeft()));
548
    connect(this, SIGNAL(canScroll(bool)), action, SLOT(setEnabled(bool)));
549
    m_keyReference->registerShortcut(action);
550
    menu->addAction(action);
551
        
552
    action = new QAction(tr("J&ump Right"), this);
553
    action->setShortcut(tr("Ctrl+Right"));
554
    action->setStatusTip(tr("Scroll the current pane a big step to the right"));
555
    connect(action, SIGNAL(triggered()), this, SLOT(jumpRight()));
556
    connect(this, SIGNAL(canScroll(bool)), action, SLOT(setEnabled(bool)));
557
    m_keyReference->registerShortcut(action);
558
    menu->addAction(action);
559

    
560
    menu->addSeparator();
561

    
562
    m_keyReference->setCategory(tr("Zoom"));
563

    
564
    m_zoomInAction = new QAction(il.load("zoom-in"),
565
                                 tr("Zoom &In"), this);
566
    m_zoomInAction->setShortcut(tr("Up"));
567
    m_zoomInAction->setStatusTip(tr("Increase the zoom level"));
568
    connect(m_zoomInAction, SIGNAL(triggered()), this, SLOT(zoomIn()));
569
    connect(this, SIGNAL(canZoom(bool)), m_zoomInAction, SLOT(setEnabled(bool)));
570
    m_keyReference->registerShortcut(m_zoomInAction);
571
    menu->addAction(m_zoomInAction);
572
        
573
    m_zoomOutAction = new QAction(il.load("zoom-out"),
574
                                  tr("Zoom &Out"), this);
575
    m_zoomOutAction->setShortcut(tr("Down"));
576
    m_zoomOutAction->setStatusTip(tr("Decrease the zoom level"));
577
    connect(m_zoomOutAction, SIGNAL(triggered()), this, SLOT(zoomOut()));
578
    connect(this, SIGNAL(canZoom(bool)), m_zoomOutAction, SLOT(setEnabled(bool)));
579
    m_keyReference->registerShortcut(m_zoomOutAction);
580
    menu->addAction(m_zoomOutAction);
581
        
582
    action = new QAction(tr("Restore &Default Zoom"), this);
583
    action->setStatusTip(tr("Restore the zoom level to the default"));
584
    connect(action, SIGNAL(triggered()), this, SLOT(zoomDefault()));
585
    connect(this, SIGNAL(canZoom(bool)), action, SLOT(setEnabled(bool)));
586
    menu->addAction(action);
587

    
588
    m_zoomFitAction = new QAction(il.load("zoom-fit"),
589
                                  tr("Zoom to &Fit"), this);
590
    m_zoomFitAction->setShortcut(tr("F"));
591
    m_zoomFitAction->setStatusTip(tr("Zoom to show the whole file"));
592
    connect(m_zoomFitAction, SIGNAL(triggered()), this, SLOT(zoomToFit()));
593
    connect(this, SIGNAL(canZoom(bool)), m_zoomFitAction, SLOT(setEnabled(bool)));
594
    m_keyReference->registerShortcut(m_zoomFitAction);
595
    menu->addAction(m_zoomFitAction);
596

    
597
    menu->addSeparator();
598

    
599
    m_keyReference->setCategory(tr("Display Features"));
600

    
601
    action = new QAction(tr("Show &Centre Line"), this);
602
    action->setShortcut(tr("'"));
603
    action->setStatusTip(tr("Show or hide the centre line"));
604
    connect(action, SIGNAL(triggered()), this, SLOT(toggleCentreLine()));
605
    action->setCheckable(true);
606
    action->setChecked(true);
607
    m_keyReference->registerShortcut(action);
608
    menu->addAction(action);
609

    
610
    action = new QAction(tr("Toggle All Time Rulers"), this);
611
    action->setShortcut(tr("#"));
612
    action->setStatusTip(tr("Show or hide all time rulers"));
613
    connect(action, SIGNAL(triggered()), this, SLOT(toggleTimeRulers()));
614
    m_keyReference->registerShortcut(action);
615
    menu->addAction(action);
616

    
617
    menu->addSeparator();
618

    
619
    QActionGroup *overlayGroup = new QActionGroup(this);
620
        
621
    action = new QAction(tr("Show &No Overlays"), this);
622
    action->setShortcut(tr("0"));
623
    action->setStatusTip(tr("Hide times, layer names, and scale"));
624
    connect(action, SIGNAL(triggered()), this, SLOT(showNoOverlays()));
625
    action->setCheckable(true);
626
    action->setChecked(false);
627
    overlayGroup->addAction(action);
628
    m_keyReference->registerShortcut(action);
629
    menu->addAction(action);
630
        
631
    action = new QAction(tr("Show &Minimal Overlays"), this);
632
    action->setShortcut(tr("9"));
633
    action->setStatusTip(tr("Show times and basic scale"));
634
    connect(action, SIGNAL(triggered()), this, SLOT(showMinimalOverlays()));
635
    action->setCheckable(true);
636
    action->setChecked(true);
637
    overlayGroup->addAction(action);
638
    m_keyReference->registerShortcut(action);
639
    menu->addAction(action);
640
        
641
    action = new QAction(tr("Show &All Overlays"), this);
642
    action->setShortcut(tr("8"));
643
    action->setStatusTip(tr("Show times, layer names, and scale"));
644
    connect(action, SIGNAL(triggered()), this, SLOT(showAllOverlays()));
645
    action->setCheckable(true);
646
    action->setChecked(false);
647
    overlayGroup->addAction(action);
648
    m_keyReference->registerShortcut(action);
649
    menu->addAction(action);
650
        
651
    menu->addSeparator();
652

    
653
    action = new QAction(tr("Show &Zoom Wheels"), this);
654
    action->setShortcut(tr("Z"));
655
    action->setStatusTip(tr("Show thumbwheels for zooming horizontally and vertically"));
656
    connect(action, SIGNAL(triggered()), this, SLOT(toggleZoomWheels()));
657
    action->setCheckable(true);
658
    action->setChecked(m_viewManager->getZoomWheelsEnabled());
659
    m_keyReference->registerShortcut(action);
660
    menu->addAction(action);
661
        
662
    m_showPropertyBoxesAction = new QAction(tr("Show Property Bo&xes"), this);
663
    m_showPropertyBoxesAction->setShortcut(tr("X"));
664
    m_showPropertyBoxesAction->setStatusTip(tr("Show the layer property boxes at the side of the main window"));
665
    connect(m_showPropertyBoxesAction, SIGNAL(triggered()), this, SLOT(togglePropertyBoxes()));
666
    m_showPropertyBoxesAction->setCheckable(true);
667
    m_showPropertyBoxesAction->setChecked(false);
668
    m_keyReference->registerShortcut(m_showPropertyBoxesAction);
669
    menu->addAction(m_showPropertyBoxesAction);
670

    
671
    action = new QAction(tr("Show Status &Bar"), this);
672
    action->setStatusTip(tr("Show context help information in the status bar at the bottom of the window"));
673
    connect(action, SIGNAL(triggered()), this, SLOT(toggleStatusBar()));
674
    action->setCheckable(true);
675
    action->setChecked(true);
676
    menu->addAction(action);
677

    
678
    QSettings settings;
679
    settings.beginGroup("MainWindow");
680
    bool sb = settings.value("showstatusbar", true).toBool();
681
    if (!sb) {
682
        action->setChecked(false);
683
        statusBar()->hide();
684
    }
685
    settings.endGroup();
686

    
687
    menu->addSeparator();
688

    
689
    action = new QAction(tr("Show La&yer Hierarchy"), this);
690
    action->setShortcut(tr("H"));
691
    action->setStatusTip(tr("Open a window displaying the hierarchy of panes and layers in this session"));
692
    connect(action, SIGNAL(triggered()), this, SLOT(showLayerTree()));
693
    m_keyReference->registerShortcut(action);
694
    menu->addAction(action);
695

    
696
    menu->addSeparator();
697

    
698
    action = new QAction(tr("Go Full-Screen"), this);
699
    action->setShortcut(tr("F11"));
700
    action->setStatusTip(tr("Expand the pane area to the whole screen"));
701
    connect(action, SIGNAL(triggered()), this, SLOT(goFullScreen()));
702
    m_keyReference->registerShortcut(action);
703
    menu->addAction(action);
704
}
705

    
706
void
707
MainWindow::setupHelpMenu()
708
{
709
    QMenu *menu = menuBar()->addMenu(tr("&Help"));
710
    menu->setTearOffEnabled(false);
711
    
712
    m_keyReference->setCategory(tr("Help"));
713

    
714
    IconLoader il;
715

    
716
    QAction *action = new QAction(il.load("help"),
717
                                  tr("&Help Reference"), this); 
718
    action->setShortcut(tr("F1"));
719
    action->setStatusTip(tr("Open the reference manual")); 
720
    connect(action, SIGNAL(triggered()), this, SLOT(help()));
721
    m_keyReference->registerShortcut(action);
722
    menu->addAction(action);
723

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

    
742
void
743
MainWindow::setupRecentFilesMenu()
744
{
745
    m_recentFilesMenu->clear();
746
    vector<QString> files = m_recentFiles.getRecent();
747
    for (size_t i = 0; i < files.size(); ++i) {
748
        QAction *action = new QAction(files[i], this);
749
        connect(action, SIGNAL(triggered()), this, SLOT(openRecentFile()));
750
        if (i == 0) {
751
            action->setShortcut(tr("Ctrl+R"));
752
            m_keyReference->registerShortcut
753
                (tr("Re-open"),
754
                 action->shortcut().toString(),
755
                 tr("Re-open the current or most recently opened file"));
756
        }
757
        m_recentFilesMenu->addAction(action);
758
    }
759
}
760

    
761
void
762
MainWindow::setupToolbars()
763
{
764
    m_keyReference->setCategory(tr("Playback and Transport Controls"));
765

    
766
    IconLoader il;
767

    
768
    QMenu *menu = m_playbackMenu = menuBar()->addMenu(tr("Play&back"));
769
    menu->setTearOffEnabled(false);
770
    m_rightButtonMenu->addSeparator();
771
    m_rightButtonPlaybackMenu = m_rightButtonMenu->addMenu(tr("Playback"));
772

    
773
    QToolBar *toolbar = addToolBar(tr("Playback Toolbar"));
774

    
775
    QAction *rwdStartAction = toolbar->addAction(il.load("rewind-start"),
776
                                                 tr("Rewind to Start"));
777
    rwdStartAction->setShortcut(tr("Home"));
778
    rwdStartAction->setStatusTip(tr("Rewind to the start"));
779
    connect(rwdStartAction, SIGNAL(triggered()), this, SLOT(rewindStart()));
780
    connect(this, SIGNAL(canPlay(bool)), rwdStartAction, SLOT(setEnabled(bool)));
781

    
782
    QAction *m_rwdAction = toolbar->addAction(il.load("rewind"),
783
                                              tr("Rewind"));
784
    m_rwdAction->setShortcut(tr("PgUp"));
785
    m_rwdAction->setStatusTip(tr("Rewind to the previous time instant or time ruler notch"));
786
    connect(m_rwdAction, SIGNAL(triggered()), this, SLOT(rewind()));
787
    connect(this, SIGNAL(canRewind(bool)), m_rwdAction, SLOT(setEnabled(bool)));
788

    
789
    m_playAction = toolbar->addAction(il.load("playpause"),
790
                                      tr("Play / Pause"));
791
    m_playAction->setCheckable(true);
792
    m_playAction->setShortcut(tr("Space"));
793
    m_playAction->setStatusTip(tr("Start or stop playback from the current position"));
794
    connect(m_playAction, SIGNAL(triggered()), this, SLOT(play()));
795
    connect(m_playSource, SIGNAL(playStatusChanged(bool)),
796
            m_playAction, SLOT(setChecked(bool)));
797
    connect(this, SIGNAL(canPlay(bool)), m_playAction, SLOT(setEnabled(bool)));
798

    
799
    m_ffwdAction = toolbar->addAction(il.load("ffwd"),
800
                                              tr("Fast Forward"));
801
    m_ffwdAction->setShortcut(tr("PgDown"));
802
    m_ffwdAction->setStatusTip(tr("Fast-forward to the next time instant or time ruler notch"));
803
    connect(m_ffwdAction, SIGNAL(triggered()), this, SLOT(ffwd()));
804
    connect(this, SIGNAL(canFfwd(bool)), m_ffwdAction, SLOT(setEnabled(bool)));
805

    
806
    QAction *ffwdEndAction = toolbar->addAction(il.load("ffwd-end"),
807
                                                tr("Fast Forward to End"));
808
    ffwdEndAction->setShortcut(tr("End"));
809
    ffwdEndAction->setStatusTip(tr("Fast-forward to the end"));
810
    connect(ffwdEndAction, SIGNAL(triggered()), this, SLOT(ffwdEnd()));
811
    connect(this, SIGNAL(canPlay(bool)), ffwdEndAction, SLOT(setEnabled(bool)));
812
/*
813
    toolbar = addToolBar(tr("Play Mode Toolbar"));
814

815
    QAction *psAction = toolbar->addAction(il.load("playselection"),
816
                                           tr("Constrain Playback to Selection"));
817
    psAction->setCheckable(true);
818
    psAction->setChecked(m_viewManager->getPlaySelectionMode());
819
    psAction->setShortcut(tr("s"));
820
    psAction->setStatusTip(tr("Constrain playback to the selected regions"));
821
    connect(m_viewManager, SIGNAL(playSelectionModeChanged(bool)),
822
            psAction, SLOT(setChecked(bool)));
823
    connect(psAction, SIGNAL(triggered()), this, SLOT(playSelectionToggled()));
824
    connect(this, SIGNAL(canPlaySelection(bool)), psAction, SLOT(setEnabled(bool)));
825

826
    QAction *plAction = toolbar->addAction(il.load("playloop"),
827
                                           tr("Loop Playback"));
828
    plAction->setCheckable(true);
829
    plAction->setChecked(m_viewManager->getPlayLoopMode());
830
    plAction->setShortcut(tr("l"));
831
    plAction->setStatusTip(tr("Loop playback"));
832
    connect(m_viewManager, SIGNAL(playLoopModeChanged(bool)),
833
            plAction, SLOT(setChecked(bool)));
834
    connect(plAction, SIGNAL(triggered()), this, SLOT(playLoopToggled()));
835
    connect(this, SIGNAL(canPlay(bool)), plAction, SLOT(setEnabled(bool)));
836

837
    QAction *soAction = toolbar->addAction(il.load("solo"),
838
                                           tr("Solo Current Pane"));
839
    soAction->setCheckable(true);
840
    soAction->setChecked(m_viewManager->getPlaySoloMode());
841
    soAction->setShortcut(tr("o"));
842
    soAction->setStatusTip(tr("Solo the current pane during playback"));
843
    connect(m_viewManager, SIGNAL(playSoloModeChanged(bool)),
844
            soAction, SLOT(setChecked(bool)));
845
    connect(soAction, SIGNAL(triggered()), this, SLOT(playSoloToggled()));
846
    connect(this, SIGNAL(canPlay(bool)), soAction, SLOT(setEnabled(bool)));
847

848
    m_keyReference->registerShortcut(psAction);
849
    m_keyReference->registerShortcut(plAction);
850
    m_keyReference->registerShortcut(soAction);
851
*/
852

    
853

    
854
    QAction *alAction = 0;
855
    alAction = toolbar->addAction(il.load("align"),
856
                                  tr("Align File Timelines"));
857
    alAction->setCheckable(true);
858
    alAction->setChecked(m_viewManager->getAlignMode());
859
    alAction->setStatusTip(tr("Treat multiple audio files as versions of the same work, and align their timelines"));
860
    connect(m_viewManager, SIGNAL(alignModeChanged(bool)),
861
            alAction, SLOT(setChecked(bool)));
862
    connect(alAction, SIGNAL(triggered()), this, SLOT(alignToggled()));
863

    
864
    m_keyReference->registerShortcut(m_playAction);
865
    m_keyReference->registerShortcut(m_rwdAction);
866
    m_keyReference->registerShortcut(m_ffwdAction);
867
    m_keyReference->registerShortcut(rwdStartAction);
868
    m_keyReference->registerShortcut(ffwdEndAction);
869

    
870
/*
871
    menu->addAction(psAction);
872
    menu->addAction(plAction);
873
    menu->addAction(soAction);
874
*/
875
    menu->addAction(m_playAction);
876
    menu->addSeparator();
877
    menu->addAction(m_rwdAction);
878
    menu->addAction(m_ffwdAction);
879
    menu->addSeparator();
880
    menu->addAction(rwdStartAction);
881
    menu->addAction(ffwdEndAction);
882
    menu->addSeparator();
883
    menu->addAction(alAction);
884
    menu->addSeparator();
885

    
886
    m_rightButtonPlaybackMenu->addAction(m_playAction);
887
/*
888
    m_rightButtonPlaybackMenu->addAction(psAction);
889
    m_rightButtonPlaybackMenu->addAction(plAction);
890
    m_rightButtonPlaybackMenu->addAction(soAction);
891
*/
892
    m_rightButtonPlaybackMenu->addSeparator();
893
    m_rightButtonPlaybackMenu->addAction(m_rwdAction);
894
    m_rightButtonPlaybackMenu->addAction(m_ffwdAction);
895
    m_rightButtonPlaybackMenu->addSeparator();
896
    m_rightButtonPlaybackMenu->addAction(rwdStartAction);
897
    m_rightButtonPlaybackMenu->addAction(ffwdEndAction);
898
    m_rightButtonPlaybackMenu->addSeparator();
899
    m_rightButtonPlaybackMenu->addAction(alAction);
900
    m_rightButtonPlaybackMenu->addSeparator();
901

    
902
    QAction *fastAction = menu->addAction(tr("Speed Up"));
903
    fastAction->setShortcut(tr("Ctrl+PgUp"));
904
    fastAction->setStatusTip(tr("Time-stretch playback to speed it up without changing pitch"));
905
    connect(fastAction, SIGNAL(triggered()), this, SLOT(speedUpPlayback()));
906
    connect(this, SIGNAL(canSpeedUpPlayback(bool)), fastAction, SLOT(setEnabled(bool)));
907
    
908
    QAction *slowAction = menu->addAction(tr("Slow Down"));
909
    slowAction->setShortcut(tr("Ctrl+PgDown"));
910
    slowAction->setStatusTip(tr("Time-stretch playback to slow it down without changing pitch"));
911
    connect(slowAction, SIGNAL(triggered()), this, SLOT(slowDownPlayback()));
912
    connect(this, SIGNAL(canSlowDownPlayback(bool)), slowAction, SLOT(setEnabled(bool)));
913

    
914
    QAction *normalAction = menu->addAction(tr("Restore Normal Speed"));
915
    normalAction->setShortcut(tr("Ctrl+Home"));
916
    normalAction->setStatusTip(tr("Restore non-time-stretched playback"));
917
    connect(normalAction, SIGNAL(triggered()), this, SLOT(restoreNormalPlayback()));
918
    connect(this, SIGNAL(canChangePlaybackSpeed(bool)), normalAction, SLOT(setEnabled(bool)));
919

    
920
    m_keyReference->registerShortcut(fastAction);
921
    m_keyReference->registerShortcut(slowAction);
922
    m_keyReference->registerShortcut(normalAction);
923

    
924
    m_rightButtonPlaybackMenu->addAction(fastAction);
925
    m_rightButtonPlaybackMenu->addAction(slowAction);
926
    m_rightButtonPlaybackMenu->addAction(normalAction);
927
/*
928
    toolbar = addToolBar(tr("Edit Toolbar"));
929
    CommandHistory::getInstance()->registerToolbar(toolbar);
930
*/
931

    
932
    Pane::registerShortcuts(*m_keyReference);
933
}
934

    
935
void
936
MainWindow::updateMenuStates()
937
{
938
    MainWindowBase::updateMenuStates();
939

    
940
    Pane *currentPane = 0;
941
    Layer *currentLayer = 0;
942

    
943
    if (m_paneStack) currentPane = m_paneStack->getCurrentPane();
944
    if (currentPane) currentLayer = currentPane->getSelectedLayer();
945

    
946
    bool haveCurrentPane =
947
        (currentPane != 0);
948
    bool haveCurrentLayer =
949
        (haveCurrentPane &&
950
         (currentLayer != 0));
951
    bool haveCurrentTimeInstantsLayer = 
952
        (haveCurrentLayer &&
953
         dynamic_cast<TimeInstantLayer *>(currentLayer));
954
    bool haveCurrentTimeValueLayer = 
955
        (haveCurrentLayer &&
956
         dynamic_cast<TimeValueLayer *>(currentLayer));
957

    
958
    emit canChangePlaybackSpeed(true);
959
    int v = m_playSpeed->value();
960
    emit canSpeedUpPlayback(v < m_playSpeed->maximum());
961
    emit canSlowDownPlayback(v > m_playSpeed->minimum());
962

    
963
    if (m_ffwdAction && m_rwdAction) {
964
        if (haveCurrentTimeInstantsLayer) {
965
            m_ffwdAction->setText(tr("Fast Forward to Next Instant"));
966
            m_ffwdAction->setStatusTip(tr("Fast forward to the next time instant in the current layer"));
967
            m_rwdAction->setText(tr("Rewind to Previous Instant"));
968
            m_rwdAction->setStatusTip(tr("Rewind to the previous time instant in the current layer"));
969
        } else if (haveCurrentTimeValueLayer) {
970
            m_ffwdAction->setText(tr("Fast Forward to Next Point"));
971
            m_ffwdAction->setStatusTip(tr("Fast forward to the next point in the current layer"));
972
            m_rwdAction->setText(tr("Rewind to Previous Point"));
973
            m_rwdAction->setStatusTip(tr("Rewind to the previous point in the current layer"));
974
        } else {
975
            m_ffwdAction->setText(tr("Fast Forward"));
976
            m_ffwdAction->setStatusTip(tr("Fast forward"));
977
            m_rwdAction->setText(tr("Rewind"));
978
            m_rwdAction->setStatusTip(tr("Rewind"));
979
        }
980
    }
981
}
982

    
983
void
984
MainWindow::updateDescriptionLabel()
985
{
986
    if (!getMainModel()) {
987
        return;
988
    }
989

    
990
    QString description;
991

    
992
    sv_samplerate_t ssr = getMainModel()->getSampleRate();
993
    sv_samplerate_t tsr = ssr;
994
    if (m_playSource) tsr = m_playSource->getDeviceSampleRate();
995

    
996
    if (ssr != tsr) {
997
        description = tr("%1Hz (resampling to %2Hz)").arg(ssr).arg(tsr);
998
    } else {
999
        description = QString("%1Hz").arg(ssr);
1000
    }
1001

    
1002
    description = QString("%1 - %2")
1003
        .arg(RealTime::frame2RealTime(getMainModel()->getEndFrame(), ssr)
1004
             .toText(false).c_str())
1005
        .arg(description);
1006

    
1007
    //!!! but we don't actually have a description label
1008
}
1009

    
1010
void
1011
MainWindow::documentModified()
1012
{
1013
    //!!!
1014
    MainWindowBase::documentModified();
1015
}
1016

    
1017
void
1018
MainWindow::documentRestored()
1019
{
1020
    //!!!
1021
    MainWindowBase::documentRestored();
1022
}
1023

    
1024
void
1025
MainWindow::selectMainPane()
1026
{
1027
    if (m_paneStack && m_paneStack->getPaneCount() > 0) {
1028
        m_paneStack->setCurrentPane(m_paneStack->getPane(0));
1029
    }
1030
}
1031

    
1032
void
1033
MainWindow::newSession()
1034
{
1035
    if (!checkSaveModified()) return;
1036

    
1037
    cerr << "MainWindow::newSession" << endl;
1038

    
1039
    closeSession();
1040
    createDocument();
1041

    
1042
    // We need a pane, so that we have something to receive drop events
1043
    
1044
    Pane *pane = m_paneStack->addPane();
1045

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

    
1049
    m_overview->registerView(pane);
1050

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

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

    
1059
void
1060
MainWindow::closeSession()
1061
{
1062
    if (!checkSaveModified()) return;
1063

    
1064
    while (m_paneStack->getPaneCount() > 0) {
1065

    
1066
        Pane *pane = m_paneStack->getPane(m_paneStack->getPaneCount() - 1);
1067

    
1068
        while (pane->getLayerCount() > 0) {
1069
            m_document->removeLayerFromView
1070
                (pane, pane->getLayer(pane->getLayerCount() - 1));
1071
        }
1072

    
1073
        m_overview->unregisterView(pane);
1074
        m_paneStack->deletePane(pane);
1075
    }
1076

    
1077
    while (m_paneStack->getHiddenPaneCount() > 0) {
1078

    
1079
        Pane *pane = m_paneStack->getHiddenPane
1080
            (m_paneStack->getHiddenPaneCount() - 1);
1081

    
1082
        while (pane->getLayerCount() > 0) {
1083
            m_document->removeLayerFromView
1084
                (pane, pane->getLayer(pane->getLayerCount() - 1));
1085
        }
1086

    
1087
        m_overview->unregisterView(pane);
1088
        m_paneStack->deletePane(pane);
1089
    }
1090

    
1091
    delete m_document;
1092
    m_document = 0;
1093
    m_viewManager->clearSelections();
1094
    m_timeRulerLayer = 0; // document owned this
1095

    
1096
    m_sessionFile = "";
1097
    setWindowTitle(tr("Sonic Vector"));
1098

    
1099
    CommandHistory::getInstance()->clear();
1100
    CommandHistory::getInstance()->documentSaved();
1101
    documentRestored();
1102
}
1103

    
1104
void
1105
MainWindow::openFile()
1106
{
1107
    QString orig = m_audioFile;
1108
    if (orig == "") orig = ".";
1109
    else orig = QFileInfo(orig).absoluteDir().canonicalPath();
1110

    
1111
    QString path = getOpenFileName(FileFinder::AnyFile);
1112

    
1113
    if (path.isEmpty()) return;
1114

    
1115
    FileOpenStatus status = openPath(path, CreateAdditionalModel);
1116

    
1117
    if (status == FileOpenFailed) {
1118
        QMessageBox::critical(this, tr("Failed to open file"),
1119
                              tr("<b>File open failed</b><p>File \"%1\" could not be opened").arg(path));
1120
    } else if (status == FileOpenWrongMode) {
1121
        QMessageBox::critical(this, tr("Failed to open file"),
1122
                              tr("<b>Audio required</b><p>Please load at least one audio file before importing annotation data"));
1123
    } else {
1124
        configureNewPane(m_paneStack->getCurrentPane());
1125
    }
1126
}
1127

    
1128
void
1129
MainWindow::openLocation()
1130
{
1131
    QSettings settings;
1132
    settings.beginGroup("MainWindow");
1133
    QString lastLocation = settings.value("lastremote", "").toString();
1134

    
1135
    bool ok = false;
1136
    QString text = QInputDialog::getText
1137
        (this, tr("Open Location"),
1138
         tr("Please enter the URL of the location to open:"),
1139
         QLineEdit::Normal, lastLocation, &ok);
1140

    
1141
    if (!ok) return;
1142

    
1143
    settings.setValue("lastremote", text);
1144

    
1145
    if (text.isEmpty()) return;
1146

    
1147
    FileOpenStatus status = openPath(text, CreateAdditionalModel);
1148

    
1149
    if (status == FileOpenFailed) {
1150
        QMessageBox::critical(this, tr("Failed to open location"),
1151
                              tr("<b>Open failed</b><p>URL \"%1\" could not be opened").arg(text));
1152
    } else if (status == FileOpenWrongMode) {
1153
        QMessageBox::critical(this, tr("Failed to open location"),
1154
                              tr("<b>Audio required</b><p>Please load at least one audio file before importing annotation data"));
1155
    } else {
1156
        configureNewPane(m_paneStack->getCurrentPane());
1157
    }
1158
}
1159

    
1160
void
1161
MainWindow::openRecentFile()
1162
{
1163
    QObject *obj = sender();
1164
    QAction *action = dynamic_cast<QAction *>(obj);
1165
    
1166
    if (!action) {
1167
        cerr << "WARNING: MainWindow::openRecentFile: sender is not an action"
1168
                  << endl;
1169
        return;
1170
    }
1171

    
1172
    QString path = action->text();
1173
    if (path == "") return;
1174

    
1175

    
1176
    cerr << "about to call open(), have " << m_paneStack->getPaneCount() << " panes" << endl;
1177

    
1178
    FileOpenStatus status = openPath(path, CreateAdditionalModel);
1179

    
1180
    cerr << "called open(), have " << m_paneStack->getPaneCount() << " panes" << endl;
1181

    
1182
    if (status == FileOpenFailed) {
1183
        QMessageBox::critical(this, tr("Failed to open location"),
1184
                              tr("<b>Open failed</b><p>File or URL \"%1\" could not be opened").arg(path));
1185
    } else if (status == FileOpenWrongMode) {
1186
        QMessageBox::critical(this, tr("Failed to open location"),
1187
                              tr("<b>Audio required</b><p>Please load at least one audio file before importing annotation data"));
1188
    } else {
1189
        configureNewPane(m_paneStack->getCurrentPane());
1190
    }
1191
}
1192

    
1193
Model *
1194
MainWindow::selectExistingLayerForMode(Pane *pane, QString name)
1195
{   
1196
    // Hides all layers in the given pane that have names differing
1197
    // from the given name, except for time instants layers (which are
1198
    // assumed to be used for segment display). If a layer is found
1199
    // that has the given name, shows that layer and returns 0. If no
1200
    // layer is found with the given name, returns a pointer to the
1201
    // model from which such a layer should be constructed.
1202

    
1203
    Model *model = 0;
1204

    
1205
    bool have = false;
1206

    
1207
    for (int i = 0; i < pane->getLayerCount(); ++i) {
1208
        
1209
        Layer *layer = pane->getLayer(i);
1210
        if (!layer || qobject_cast<TimeInstantLayer *>(layer)) continue;
1211
        
1212
        Model *lm = layer->getModel();
1213
        while (lm && lm->getSourceModel()) lm = lm->getSourceModel();
1214
        if (qobject_cast<WaveFileModel *>(lm)) model = lm;
1215
        
1216
        QString ln = layer->objectName();
1217
        if (ln != name) {
1218
            m_hiddenLayers[pane].insert(layer);
1219
            m_document->removeLayerFromView(pane, layer);
1220
            continue;
1221
        }
1222
        
1223
        have = true;
1224
    }
1225
    
1226
    if (have) return 0;
1227

    
1228
    LayerSet &ls = m_hiddenLayers[pane];
1229
    bool found = false;
1230
    for (LayerSet::iterator i = ls.begin(); i != ls.end(); ++i) {
1231
        if ((*i)->objectName() == name) {
1232
            m_document->addLayerToView(pane, *i);
1233
            ls.erase(i);
1234
            found = true;
1235
            break;
1236
        }
1237
    }
1238

    
1239
    if (found) return 0;
1240

    
1241
    return model;
1242
}
1243

    
1244
void
1245
MainWindow::addSalientFeatureLayer(Pane *pane, WaveFileModel *model)
1246
{
1247
    //!!! what if there already is one? could have changed the main
1248
    //!!! model for example
1249

    
1250
    if (!model) {
1251
        cerr << "MainWindow::addSalientFeatureLayer: No model" << endl;
1252
        return;
1253
    }
1254
    
1255
    TransformFactory *tf = TransformFactory::getInstance();
1256
    if (!tf) {
1257
        cerr << "Failed to locate a transform factory!" << endl;
1258
        return;
1259
    }
1260
    
1261
//    TransformId id = "vamp:qm-vamp-plugins:qm-keydetector:key";
1262
    TransformId id = "vamp:nnls-chroma:chordino:simplechord";
1263
    if (!tf->haveTransform(id)) {
1264
        cerr << "No plugin available for salient feature layer; transform is: "
1265
             << id << endl;
1266
        return;
1267
    }
1268

    
1269
    if (!model) {
1270
        return;
1271
    }
1272

    
1273
    m_salientCalculating = true;
1274

    
1275
    Transform transform = tf->getDefaultTransformFor
1276
        (id, model->getSampleRate());
1277

    
1278
    transform.setStepSize(1024);
1279
    transform.setBlockSize(2048);
1280

    
1281
    ModelTransformer::Input input(model, -1);
1282

    
1283
    Layer *newLayer = m_document->createDerivedLayer(transform, model);
1284

    
1285
    if (newLayer) {
1286

    
1287
        TimeInstantLayer *til = qobject_cast<TimeInstantLayer *>(newLayer);
1288
        if (til) til->setPlotStyle(TimeInstantLayer::PlotInstants);
1289

    
1290
        connect(til, SIGNAL(modelCompletionChanged()),
1291
                this, SLOT(salientLayerCompletionChanged()));
1292
        
1293
        m_document->addLayerToView(pane, newLayer);
1294
        m_paneStack->setCurrentLayer(pane, newLayer);
1295
    }
1296
}
1297

    
1298
void
1299
MainWindow::salientLayerCompletionChanged()
1300
{
1301
    Layer *layer = qobject_cast<Layer *>(sender());
1302
    cerr << "MainWindow::salientLayerCompletionChanged: layer = " << layer << endl;
1303
    if (layer && layer->getCompletion(0) == 100) {
1304
        m_salientCalculating = false;
1305
        cerr << "mapping " << m_salientPending.size() << " salient layer(s)" << endl;
1306
        foreach (AlignmentModel *am, m_salientPending) {
1307
            mapSalientFeatureLayer(am);
1308
        }
1309
        m_salientPending.clear();
1310
    }
1311
}
1312

    
1313
TimeInstantLayer *
1314
MainWindow::findSalientFeatureLayer()
1315
{
1316
    if (!getMainModel()) return 0;
1317

    
1318
    for (int i = 0; i < m_paneStack->getPaneCount(); ++i) {
1319
        Pane *p = m_paneStack->getPane(i);
1320

    
1321
        for (int j = 0; j < p->getLayerCount(); ++j) {
1322
            Layer *l = p->getLayer(j);
1323

    
1324
            if (l->getModel() == getMainModel()) {
1325

    
1326
                for (int k = 0; k < p->getLayerCount(); ++k) {
1327
                    TimeInstantLayer *ll = qobject_cast<TimeInstantLayer *>
1328
                        (p->getLayer(k));
1329
                    if (ll) return ll;
1330
                }
1331
            }
1332
        }
1333
    }
1334

    
1335
    return 0;
1336
}
1337

    
1338
void
1339
MainWindow::mapSalientFeatureLayer(AlignmentModel *am)
1340
{
1341
    if (m_salientCalculating) {
1342
        m_salientPending.insert(am);
1343
        return;
1344
    }
1345

    
1346
    TimeInstantLayer *salient = findSalientFeatureLayer();
1347
    if (!salient) {
1348
        cerr << "MainWindow::mapSalientFeatureLayer: No salient layer found"
1349
             << endl;
1350
        m_salientPending.insert(am);
1351
        return;
1352
    }
1353

    
1354
    if (!am) {
1355
        cerr << "MainWindow::mapSalientFeatureLayer: AlignmentModel is null!" << endl;
1356
        return;
1357
    }
1358
    
1359
    const Model *model = am->getAlignedModel();
1360

    
1361
    Pane *pane = 0;
1362
    Layer *layer = 0;
1363
    
1364
    for (int i = 0; i < m_paneStack->getPaneCount(); ++i) {
1365
        Pane *p = m_paneStack->getPane(i);
1366
        for (int j = 0; j < p->getLayerCount(); ++j) {
1367
            Layer *l = p->getLayer(j);
1368
            if (l && (l->getModel() == model)) {
1369
                pane = p;
1370
                layer = l;
1371
                break;
1372
            }
1373
        }
1374
    }
1375

    
1376
    if (!pane || !layer) {
1377
        cerr << "MainWindow::mapSalientFeatureLayer: Failed to find model "
1378
             << model << " in any layer" << endl;
1379
        return;
1380
    }
1381

    
1382
    //!!! command?
1383

    
1384
    const SparseOneDimensionalModel *from =
1385
        qobject_cast<const SparseOneDimensionalModel *>(salient->getModel());
1386
    if (!from) {
1387
        cerr << "MainWindow::mapSalientFeatureLayer: Salient layer lacks SparseOneDimensionalModel" << endl;
1388
        return;
1389
    }
1390
        
1391
    SparseOneDimensionalModel *to = new SparseOneDimensionalModel
1392
        (model->getSampleRate(), from->getResolution(), false);
1393
    
1394
    SparseOneDimensionalModel::PointList pp = from->getPoints();
1395
    foreach (SparseOneDimensionalModel::Point p, pp) {
1396
        p.frame = am->fromReference(p.frame);
1397
        to->addPoint(p);
1398
    }
1399

    
1400
    Layer *newLayer = m_document->createImportedLayer(to);
1401

    
1402
    if (newLayer) {
1403

    
1404
        TimeInstantLayer *til = qobject_cast<TimeInstantLayer *>(newLayer);
1405
        if (til) til->setPlotStyle(TimeInstantLayer::PlotInstants);
1406
        
1407
        m_document->addLayerToView(pane, newLayer);
1408
        m_paneStack->setCurrentLayer(pane, newLayer);
1409
    }
1410
}
1411

    
1412
void
1413
MainWindow::curveModeSelected()
1414
{
1415
    QString name = tr("Curve");
1416

    
1417
    for (int i = 0; i < m_paneStack->getPaneCount(); ++i) {
1418

    
1419
        Pane *pane = m_paneStack->getPane(i);
1420
        if (!pane) continue;
1421

    
1422
        Model *model = selectExistingLayerForMode(pane, name);
1423
        if (!model) continue;
1424

    
1425
        TransformId id = "vamp:qm-vamp-plugins:qm-onsetdetector:detection_fn";
1426
        TransformFactory *tf = TransformFactory::getInstance();
1427

    
1428
        if (tf->haveTransform(id)) {
1429

    
1430
            Transform transform = tf->getDefaultTransformFor
1431
                (id, model->getSampleRate());
1432

    
1433
            transform.setStepSize(1024);
1434
            transform.setBlockSize(2048);
1435

    
1436
            ModelTransformer::Input input(model, -1);
1437

    
1438
//!!! no equivalent for this yet            context.updates = false;
1439

    
1440
            Layer *newLayer = m_document->createDerivedLayer(transform, model);
1441

    
1442
            if (newLayer) {
1443
                newLayer->setObjectName(name);
1444
                m_document->addLayerToView(pane, newLayer);
1445
                m_paneStack->setCurrentLayer(pane, newLayer);
1446
            }
1447
            
1448
        } else {
1449
            cerr << "No onset detector plugin available" << endl;
1450
        }
1451
    }
1452

    
1453
    m_displayMode = CurveMode;
1454
}
1455

    
1456
void
1457
MainWindow::waveformModeSelected()
1458
{
1459
    QString name = tr("Waveform");
1460

    
1461
    for (int i = 0; i < m_paneStack->getPaneCount(); ++i) {
1462

    
1463
        Pane *pane = m_paneStack->getPane(i);
1464
        if (!pane) continue;
1465

    
1466
        Model *model = selectExistingLayerForMode(pane, name);
1467
        if (!model) continue;
1468

    
1469
        Layer *newLayer = m_document->createLayer(LayerFactory::Waveform);
1470
        newLayer->setObjectName(name);
1471
        m_document->setModel(newLayer, model);
1472
        m_document->addLayerToView(pane, newLayer);
1473
        m_paneStack->setCurrentLayer(pane, newLayer);
1474
    }
1475

    
1476
    m_displayMode = WaveformMode;
1477
}
1478

    
1479
void
1480
MainWindow::spectrogramModeSelected()
1481
{
1482
    QString name = tr("Spectrogram");
1483

    
1484
    for (int i = 0; i < m_paneStack->getPaneCount(); ++i) {
1485

    
1486
        Pane *pane = m_paneStack->getPane(i);
1487
        if (!pane) continue;
1488

    
1489
        Model *model = selectExistingLayerForMode(pane, name);
1490
        if (!model) continue;
1491
        
1492
        Layer *newLayer = m_document->createLayer(LayerFactory::Spectrogram);
1493
        newLayer->setObjectName(name);
1494
        m_document->setModel(newLayer, model);
1495
        m_document->addLayerToView(pane, newLayer);
1496
        m_paneStack->setCurrentLayer(pane, newLayer);
1497
    }
1498

    
1499
    m_displayMode = SpectrogramMode;
1500
}
1501

    
1502
void
1503
MainWindow::melodogramModeSelected()
1504
{
1505
    QString name = tr("Melodic Range Spectrogram");
1506

    
1507
    for (int i = 0; i < m_paneStack->getPaneCount(); ++i) {
1508

    
1509
        Pane *pane = m_paneStack->getPane(i);
1510
        if (!pane) continue;
1511

    
1512
        Model *model = selectExistingLayerForMode(pane, name);
1513
        if (!model) continue;
1514

    
1515
        Layer *newLayer = m_document->createLayer
1516
            (LayerFactory::MelodicRangeSpectrogram);
1517
        newLayer->setObjectName(name);
1518
        m_document->setModel(newLayer, model);
1519
        m_document->addLayerToView(pane, newLayer);
1520
        m_paneStack->setCurrentLayer(pane, newLayer);
1521
    }
1522

    
1523
    m_displayMode = MelodogramMode;
1524
}
1525

    
1526
void
1527
MainWindow::reselectMode()
1528
{
1529
    switch (m_displayMode) {
1530
    case CurveMode: curveModeSelected(); break;
1531
    case WaveformMode: waveformModeSelected(); break;
1532
    case SpectrogramMode: spectrogramModeSelected(); break;
1533
    case MelodogramMode: melodogramModeSelected(); break;
1534
    }
1535
}
1536

    
1537
void
1538
MainWindow::paneAdded(Pane *pane)
1539
{
1540
    pane->setPlaybackFollow(PlaybackScrollContinuous);
1541
    m_paneStack->sizePanesEqually();
1542
    if (m_overview) m_overview->registerView(pane);
1543
}    
1544

    
1545
void
1546
MainWindow::paneHidden(Pane *pane)
1547
{
1548
    if (m_overview) m_overview->unregisterView(pane); 
1549
}    
1550

    
1551
void
1552
MainWindow::paneAboutToBeDeleted(Pane *pane)
1553
{
1554
    if (m_overview) m_overview->unregisterView(pane); 
1555
}    
1556

    
1557
void
1558
MainWindow::paneDropAccepted(Pane * /* pane */, QStringList uriList)
1559
{
1560
    if (uriList.empty()) return;
1561

    
1562
    QUrl first(uriList[0]);
1563

    
1564
    cerr << "uriList.size() == " << uriList.size() << endl;
1565
    cerr << "first.isLocalFile() == " << first.isLocalFile() << endl;
1566
    cerr << "QFileInfo(first.path()).isDir() == " << QFileInfo(first.path()).isDir() << endl;
1567
    
1568
    if (uriList.size() == 1 &&
1569
        first.isLocalFile() &&
1570
        QFileInfo(first.path()).isDir()) {
1571

    
1572
        FileOpenStatus status = openDirOfAudio(first.path());
1573

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

    
1579
        return;
1580
    }
1581
    
1582
    for (QStringList::iterator i = uriList.begin(); i != uriList.end(); ++i) {
1583

    
1584
        FileOpenStatus status = openPath(*i, CreateAdditionalModel);
1585

    
1586
        if (status == FileOpenFailed) {
1587
            QMessageBox::critical(this, tr("Failed to open dropped URL"),
1588
                                  tr("<b>Open failed</b><p>Dropped URL \"%1\" could not be opened").arg(*i));
1589
        } else if (status == FileOpenWrongMode) {
1590
            QMessageBox::critical(this, tr("Failed to open dropped URL"),
1591
                                  tr("<b>Audio required</b><p>Please load at least one audio file before importing annotation data"));
1592
        } else {
1593
            configureNewPane(m_paneStack->getCurrentPane());
1594
        }
1595
    }
1596
}
1597

    
1598
void
1599
MainWindow::paneDropAccepted(Pane *pane, QString text)
1600
{
1601
    if (pane) m_paneStack->setCurrentPane(pane);
1602

    
1603
    QUrl testUrl(text);
1604
    if (testUrl.scheme() == "file" || 
1605
        testUrl.scheme() == "http" || 
1606
        testUrl.scheme() == "ftp") {
1607
        QStringList list;
1608
        list.push_back(text);
1609
        paneDropAccepted(pane, list);
1610
        return;
1611
    }
1612

    
1613
    //!!! open as text -- but by importing as if a CSV, or just adding
1614
    //to a text layer?
1615
}
1616

    
1617
void
1618
MainWindow::configureNewPane(Pane *pane)
1619
{
1620
    cerr << "MainWindow::configureNewPane(" << pane << ")" << endl;
1621

    
1622
    if (!pane) return;
1623

    
1624
    Layer *waveformLayer = 0;
1625

    
1626
    for (int i = 0; i < pane->getLayerCount(); ++i) {
1627
        Layer *layer = pane->getLayer(i);
1628
        if (!layer) continue;
1629
        if (dynamic_cast<WaveformLayer *>(layer)) waveformLayer = layer;
1630
        if (dynamic_cast<TimeValueLayer *>(layer)) return;
1631
    }
1632
    if (!waveformLayer) return;
1633

    
1634
    waveformLayer->setObjectName(tr("Waveform"));
1635

    
1636
    zoomToFit();
1637
    reselectMode();
1638
}
1639

    
1640
void
1641
MainWindow::closeEvent(QCloseEvent *e)
1642
{
1643
    if (m_exiting) {
1644
        e->accept();
1645
        return;
1646
    }
1647

    
1648
//    cerr << "MainWindow::closeEvent" << endl;
1649

    
1650
    if (m_openingAudioFile) {
1651
//        cerr << "Busy - ignoring close event" << endl;
1652
        e->ignore();
1653
        return;
1654
    }
1655

    
1656
    if (!m_abandoning && !checkSaveModified()) {
1657
//        cerr << "Ignoring close event" << endl;
1658
        e->ignore();
1659
        return;
1660
    }
1661

    
1662
    QSettings settings;
1663
    settings.beginGroup("MainWindow");
1664
    settings.setValue("maximised", isMaximized());
1665
    if (!isMaximized()) {
1666
        settings.setValue("size", size());
1667
        settings.setValue("position", pos());
1668
    }
1669
    settings.endGroup();
1670

    
1671
    delete m_keyReference;
1672
    m_keyReference = 0;
1673

    
1674
    if (m_preferencesDialog &&
1675
        m_preferencesDialog->isVisible()) {
1676
        closeSession(); // otherwise we'll have to wait for prefs changes
1677
        m_preferencesDialog->applicationClosing(false);
1678
    }
1679

    
1680
    if (m_layerTreeView &&
1681
        m_layerTreeView->isVisible()) {
1682
        delete m_layerTreeView;
1683
    }
1684

    
1685
    closeSession();
1686

    
1687
    e->accept();
1688

    
1689
    m_exiting = true;
1690
    qApp->closeAllWindows();
1691
    
1692
    return;
1693
}
1694

    
1695
bool
1696
MainWindow::commitData(bool mayAskUser)
1697
{
1698
    if (mayAskUser) {
1699
        bool rv = checkSaveModified();
1700
        if (rv) {
1701
            if (m_preferencesDialog &&
1702
                m_preferencesDialog->isVisible()) {
1703
                m_preferencesDialog->applicationClosing(false);
1704
            }
1705
        }
1706
        return rv;
1707
    } else {
1708
        if (m_preferencesDialog &&
1709
            m_preferencesDialog->isVisible()) {
1710
            m_preferencesDialog->applicationClosing(true);
1711
        }
1712
        if (!m_documentModified) return true;
1713

    
1714
        // If we can't check with the user first, then we can't save
1715
        // to the original session file (even if we have it) -- have
1716
        // to use a temporary file
1717

    
1718
        QString svDirBase = ".sv1";
1719
        QString svDir = QDir::home().filePath(svDirBase);
1720

    
1721
        if (!QFileInfo(svDir).exists()) {
1722
            if (!QDir::home().mkdir(svDirBase)) return false;
1723
        } else {
1724
            if (!QFileInfo(svDir).isDir()) return false;
1725
        }
1726
        
1727
        // This name doesn't have to be unguessable
1728
#ifndef _WIN32
1729
        QString fname = QString("tmp-%1-%2.sv")
1730
            .arg(QDateTime::currentDateTime().toString("yyyyMMddhhmmsszzz"))
1731
            .arg(QProcess().pid());
1732
#else
1733
        QString fname = QString("tmp-%1.sv")
1734
            .arg(QDateTime::currentDateTime().toString("yyyyMMddhhmmsszzz"));
1735
#endif
1736
        QString fpath = QDir(svDir).filePath(fname);
1737
        if (saveSessionFile(fpath)) {
1738
            m_recentFiles.addFile(fpath);
1739
            return true;
1740
        } else {
1741
            return false;
1742
        }
1743
    }
1744
}
1745

    
1746
bool
1747
MainWindow::checkSaveModified()
1748
{
1749
    // Called before some destructive operation (e.g. new session,
1750
    // exit program).  Return true if we can safely proceed, false to
1751
    // cancel.
1752

    
1753
    if (!m_documentModified) return true;
1754

    
1755
    int button = 
1756
        QMessageBox::warning(this,
1757
                             tr("Session modified"),
1758
                             tr("The current session has been modified.\nDo you want to save it?"),
1759
                             QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel,
1760
                             QMessageBox::Yes);
1761

    
1762
    if (button == QMessageBox::Yes) {
1763
        saveSession();
1764
        if (m_documentModified) { // save failed -- don't proceed!
1765
            return false;
1766
        } else {
1767
            return true; // saved, so it's safe to continue now
1768
        }
1769
    } else if (button == QMessageBox::No) {
1770
        m_documentModified = false; // so we know to abandon it
1771
        return true;
1772
    }
1773

    
1774
    // else cancel
1775
    return false;
1776
}
1777

    
1778
void
1779
MainWindow::saveSession()
1780
{
1781
    if (m_sessionFile != "") {
1782
        if (!saveSessionFile(m_sessionFile)) {
1783
            QMessageBox::critical(this, tr("Failed to save file"),
1784
                                  tr("Session file \"%1\" could not be saved.").arg(m_sessionFile));
1785
        } else {
1786
            CommandHistory::getInstance()->documentSaved();
1787
            documentRestored();
1788
        }
1789
    } else {
1790
        saveSessionAs();
1791
    }
1792
}
1793

    
1794
void
1795
MainWindow::saveSessionAs()
1796
{
1797
    QString orig = m_audioFile;
1798
    if (orig == "") orig = ".";
1799
    else orig = QFileInfo(orig).absoluteDir().canonicalPath();
1800

    
1801
    QString path = getSaveFileName(FileFinder::SessionFile);
1802

    
1803
    if (path == "") return;
1804

    
1805
    if (!saveSessionFile(path)) {
1806
        QMessageBox::critical(this, tr("Failed to save file"),
1807
                              tr("Session file \"%1\" could not be saved.").arg(path));
1808
    } else {
1809
        setWindowTitle(tr("%1: %2")
1810
                       .arg(QApplication::applicationName())
1811
                       .arg(QFileInfo(path).fileName()));
1812
        m_sessionFile = path;
1813
        CommandHistory::getInstance()->documentSaved();
1814
        documentRestored();
1815
        m_recentFiles.addFile(path);
1816
    }
1817
}
1818

    
1819
void
1820
MainWindow::preferenceChanged(PropertyContainer::PropertyName name)
1821
{
1822
    MainWindowBase::preferenceChanged(name);
1823

    
1824
    if (name == "Background Mode" && m_viewManager) {
1825
        if (m_viewManager->getGlobalDarkBackground()) {
1826
            m_panLayer->setBaseColour
1827
                (ColourDatabase::getInstance()->getColourIndex(tr("Bright Green")));
1828
        } else {
1829
            m_panLayer->setBaseColour
1830
                (ColourDatabase::getInstance()->getColourIndex(tr("Green")));
1831
        }      
1832
    }    
1833
}
1834

    
1835
void
1836
MainWindow::renameCurrentLayer()
1837
{
1838
    Pane *pane = m_paneStack->getCurrentPane();
1839
    if (pane) {
1840
        Layer *layer = pane->getSelectedLayer();
1841
        if (layer) {
1842
            bool ok = false;
1843
            QString newName = QInputDialog::getText
1844
                (this, tr("Rename Layer"),
1845
                 tr("New name for this layer:"),
1846
                 QLineEdit::Normal, layer->objectName(), &ok);
1847
            if (ok) {
1848
                layer->setObjectName(newName);
1849
            }
1850
        }
1851
    }
1852
}
1853

    
1854
void
1855
MainWindow::alignToggled()
1856
{
1857
    QAction *action = dynamic_cast<QAction *>(sender());
1858
    
1859
    if (!m_viewManager) return;
1860

    
1861
    if (action) {
1862
        m_viewManager->setAlignMode(action->isChecked());
1863
    } else {
1864
        m_viewManager->setAlignMode(!m_viewManager->getAlignMode());
1865
    }
1866

    
1867
    if (m_viewManager->getAlignMode()) {
1868
        m_document->alignModels();
1869
        m_document->setAutoAlignment(true);
1870
    } else {
1871
        m_document->setAutoAlignment(false);
1872
    }
1873

    
1874
    for (int i = 0; i < m_paneStack->getPaneCount(); ++i) {
1875
        Pane *pane = m_paneStack->getPane(i);
1876
        if (!pane) continue;
1877
        pane->update();
1878
    }
1879
}
1880

    
1881
void
1882
MainWindow::playSpeedChanged(int position)
1883
{
1884
    PlaySpeedRangeMapper mapper;
1885

    
1886
    double percent = m_playSpeed->mappedValue();
1887
    double factor = mapper.getFactorForValue(percent);
1888

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

    
1891
    int centre = m_playSpeed->defaultValue();
1892

    
1893
    // Percentage is shown to 0dp if >100, to 1dp if <100; factor is
1894
    // shown to 3sf
1895

    
1896
    char pcbuf[30];
1897
    char facbuf[30];
1898
    
1899
    if (position == centre) {
1900
        contextHelpChanged(tr("Playback speed: Normal"));
1901
    } else if (position < centre) {
1902
        sprintf(pcbuf, "%.1f", percent);
1903
        sprintf(facbuf, "%.3g", 1.0 / factor);
1904
        contextHelpChanged(tr("Playback speed: %1% (%2x slower)")
1905
                           .arg(pcbuf)
1906
                           .arg(facbuf));
1907
    } else {
1908
        sprintf(pcbuf, "%.0f", percent);
1909
        sprintf(facbuf, "%.3g", factor);
1910
        contextHelpChanged(tr("Playback speed: %1% (%2x faster)")
1911
                           .arg(pcbuf)
1912
                           .arg(facbuf));
1913
    }
1914

    
1915
    m_playSource->setTimeStretch(1.0 / factor); // factor is a speedup
1916

    
1917
    updateMenuStates();
1918
}
1919

    
1920
void
1921
MainWindow::speedUpPlayback()
1922
{
1923
    int value = m_playSpeed->value();
1924
    value = value + m_playSpeed->pageStep();
1925
    if (value > m_playSpeed->maximum()) value = m_playSpeed->maximum();
1926
    m_playSpeed->setValue(value);
1927
}
1928

    
1929
void
1930
MainWindow::slowDownPlayback()
1931
{
1932
    int value = m_playSpeed->value();
1933
    value = value - m_playSpeed->pageStep();
1934
    if (value < m_playSpeed->minimum()) value = m_playSpeed->minimum();
1935
    m_playSpeed->setValue(value);
1936
}
1937

    
1938
void
1939
MainWindow::restoreNormalPlayback()
1940
{
1941
    m_playSpeed->setValue(m_playSpeed->defaultValue());
1942
}
1943

    
1944
void
1945
MainWindow::updateVisibleRangeDisplay(Pane *p) const
1946
{
1947
    if (!getMainModel() || !p) {
1948
        return;
1949
    }
1950

    
1951
    bool haveSelection = false;
1952
    sv_frame_t startFrame = 0, endFrame = 0;
1953

    
1954
    if (m_viewManager && m_viewManager->haveInProgressSelection()) {
1955

    
1956
        bool exclusive = false;
1957
        Selection s = m_viewManager->getInProgressSelection(exclusive);
1958

    
1959
        if (!s.isEmpty()) {
1960
            haveSelection = true;
1961
            startFrame = s.getStartFrame();
1962
            endFrame = s.getEndFrame();
1963
        }
1964
    }
1965

    
1966
    if (!haveSelection) {
1967
        startFrame = p->getFirstVisibleFrame();
1968
        endFrame = p->getLastVisibleFrame();
1969
    }
1970

    
1971
    RealTime start = RealTime::frame2RealTime
1972
        (startFrame, getMainModel()->getSampleRate());
1973

    
1974
    RealTime end = RealTime::frame2RealTime
1975
        (endFrame, getMainModel()->getSampleRate());
1976

    
1977
    RealTime duration = end - start;
1978

    
1979
    QString startStr, endStr, durationStr;
1980
    startStr = start.toText(true).c_str();
1981
    endStr = end.toText(true).c_str();
1982
    durationStr = duration.toText(true).c_str();
1983

    
1984
    if (haveSelection) {
1985
        m_myStatusMessage = tr("Selection: %1 to %2 (duration %3)")
1986
            .arg(startStr).arg(endStr).arg(durationStr);
1987
    } else {
1988
        m_myStatusMessage = tr("Visible: %1 to %2 (duration %3)")
1989
            .arg(startStr).arg(endStr).arg(durationStr);
1990
    }
1991

    
1992
    statusBar()->showMessage(m_myStatusMessage);
1993
}
1994

    
1995
void
1996
MainWindow::updatePositionStatusDisplays() const
1997
{
1998
    if (!statusBar()->isVisible()) return;
1999

    
2000
}
2001

    
2002
void
2003
MainWindow::monitoringLevelsChanged(float left, float right)
2004
{
2005
    m_mainLevelPan->setMonitoringLevels(left, right);
2006
}
2007

    
2008
void
2009
MainWindow::sampleRateMismatch(sv_samplerate_t requested,
2010
                               sv_samplerate_t actual,
2011
                               bool willResample)
2012
{
2013
    if (!willResample) {
2014
        //!!! more helpful message needed
2015
        QMessageBox::information
2016
            (this, tr("Sample rate mismatch"),
2017
             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.")
2018
             .arg(requested).arg(actual));
2019
    }        
2020

    
2021
    updateDescriptionLabel();
2022
}
2023

    
2024
void
2025
MainWindow::audioOverloadPluginDisabled()
2026
{
2027
    QMessageBox::information
2028
        (this, tr("Audio processing overload"),
2029
         tr("<b>Overloaded</b><p>Audio effects plugin auditioning has been disabled due to a processing overload."));
2030
}
2031

    
2032
void
2033
MainWindow::audioTimeStretchMultiChannelDisabled()
2034
{
2035
    static bool shownOnce = false;
2036
    if (shownOnce) return;
2037
    QMessageBox::information
2038
        (this, tr("Audio processing overload"),
2039
         tr("<b>Overloaded</b><p>Audio playback speed processing has been reduced to a single channel, due to a processing overload."));
2040
    shownOnce = true;
2041
}
2042

    
2043
void
2044
MainWindow::layerRemoved(Layer *layer)
2045
{
2046
    MainWindowBase::layerRemoved(layer);
2047
}
2048

    
2049
void
2050
MainWindow::layerInAView(Layer *layer, bool inAView)
2051
{
2052
    MainWindowBase::layerInAView(layer, inAView);
2053
}
2054

    
2055
void
2056
MainWindow::modelAdded(Model *model)
2057
{
2058
    MainWindowBase::modelAdded(model);
2059
}
2060

    
2061
void
2062
MainWindow::modelAboutToBeDeleted(Model *model)
2063
{
2064
    MainWindowBase::modelAboutToBeDeleted(model);
2065
}
2066

    
2067
void
2068
MainWindow::mainModelChanged(WaveFileModel *model)
2069
{
2070
    SVDEBUG << "MainWindow::mainModelChanged(" << model << ")" << endl;
2071

    
2072
    m_salientPending.clear();
2073
    m_salientCalculating = false;
2074
    
2075
    m_panLayer->setModel(model);
2076

    
2077
    MainWindowBase::mainModelChanged(model);
2078

    
2079
    if (m_playTarget || m_audioIO) {
2080
        connect(m_mainLevelPan, SIGNAL(levelChanged(float)),
2081
                this, SLOT(mainModelGainChanged(float)));
2082
        connect(m_mainLevelPan, SIGNAL(panChanged(float)),
2083
                this, SLOT(mainModelPanChanged(float)));
2084
    }
2085

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

    
2088
    if (model && m_paneStack && (m_paneStack->getPaneCount() == 0)) {
2089
        AddPaneCommand *command = new AddPaneCommand(this);
2090
        CommandHistory::getInstance()->addCommand(command);
2091
        Pane *pane = command->getPane();
2092
        Layer *newLayer = m_document->createImportedLayer(model);
2093
        if (newLayer) {
2094
            m_document->addLayerToView(pane, newLayer);
2095
        }
2096
        addSalientFeatureLayer(pane, model);
2097
    } else {
2098
        addSalientFeatureLayer(m_paneStack->getCurrentPane(), model);
2099
    }
2100

    
2101
    m_document->setAutoAlignment(m_viewManager->getAlignMode());
2102
}
2103

    
2104
void
2105
MainWindow::mainModelGainChanged(float gain)
2106
{
2107
    if (m_playTarget) {
2108
        m_playTarget->setOutputGain(gain);
2109
    } else if (m_audioIO) {
2110
        m_audioIO->setOutputGain(gain);
2111
    }
2112
}
2113

    
2114
void
2115
MainWindow::mainModelPanChanged(float balance)
2116
{
2117
    // this is indeed stereo balance rather than pan
2118
    if (m_playTarget) {
2119
        m_playTarget->setOutputBalance(balance);
2120
    } else if (m_audioIO) {
2121
        m_audioIO->setOutputBalance(balance);
2122
    }
2123
}
2124

    
2125
void
2126
MainWindow::modelGenerationFailed(QString transformName, QString message)
2127
{
2128
    if (message != "") {
2129

    
2130
        QMessageBox::warning
2131
            (this,
2132
             tr("Failed to generate layer"),
2133
             tr("<b>Layer generation failed</b><p>Failed to generate derived layer.<p>The layer transform \"%1\" failed:<p>%2")
2134
             .arg(transformName).arg(message),
2135
             QMessageBox::Ok);
2136
    } else {
2137
        QMessageBox::warning
2138
            (this,
2139
             tr("Failed to generate layer"),
2140
             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.")
2141
             .arg(transformName),
2142
             QMessageBox::Ok);
2143
    }
2144
}
2145

    
2146
void
2147
MainWindow::modelGenerationWarning(QString /* transformName */, QString message)
2148
{
2149
    QMessageBox::warning
2150
        (this, tr("Warning"), message, QMessageBox::Ok);
2151
}
2152

    
2153
void
2154
MainWindow::modelRegenerationFailed(QString layerName,
2155
                                    QString transformName, QString message)
2156
{
2157
    if (message != "") {
2158

    
2159
        QMessageBox::warning
2160
            (this,
2161
             tr("Failed to regenerate layer"),
2162
             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")
2163
             .arg(layerName).arg(transformName).arg(message),
2164
             QMessageBox::Ok);
2165
    } else {
2166
        QMessageBox::warning
2167
            (this,
2168
             tr("Failed to regenerate layer"),
2169
             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.")
2170
             .arg(layerName).arg(transformName),
2171
             QMessageBox::Ok);
2172
    }
2173
}
2174

    
2175
void
2176
MainWindow::modelRegenerationWarning(QString layerName,
2177
                                     QString /* transformName */,
2178
                                     QString message)
2179
{
2180
    QMessageBox::warning
2181
        (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);
2182
}
2183

    
2184
void
2185
MainWindow::alignmentComplete(AlignmentModel *model)
2186
{
2187
    cerr << "MainWindow::alignmentComplete(" << model << ")" << endl;
2188
    if (model) mapSalientFeatureLayer(model);
2189
}
2190

    
2191
void
2192
MainWindow::alignmentFailed(QString message)
2193
{
2194
    QMessageBox::warning
2195
        (this,
2196
         tr("Failed to calculate alignment"),
2197
         tr("<b>Alignment calculation failed</b><p>Failed to calculate an audio alignment:<p>%1")
2198
         .arg(message),
2199
         QMessageBox::Ok);
2200
}
2201

    
2202
void
2203
MainWindow::rightButtonMenuRequested(Pane *pane, QPoint position)
2204
{
2205
//    cerr << "MainWindow::rightButtonMenuRequested(" << pane << ", " << position.x() << ", " << position.y() << ")" << endl;
2206
    m_paneStack->setCurrentPane(pane);
2207
    m_rightButtonMenu->popup(position);
2208
}
2209

    
2210
void
2211
MainWindow::showLayerTree()
2212
{
2213
    if (!m_layerTreeView.isNull()) {
2214
        m_layerTreeView->show();
2215
        m_layerTreeView->raise();
2216
        return;
2217
    }
2218

    
2219
    //!!! should use an actual dialog class
2220
        
2221
    m_layerTreeView = new QTreeView();
2222
    LayerTreeModel *tree = new LayerTreeModel(m_paneStack);
2223
    m_layerTreeView->resize(500, 300); //!!!
2224
    m_layerTreeView->setModel(tree);
2225
    m_layerTreeView->expandAll();
2226
    m_layerTreeView->show();
2227
}
2228

    
2229
void
2230
MainWindow::handleOSCMessage(const OSCMessage & /* message */)
2231
{
2232
    cerr << "MainWindow::handleOSCMessage: Not implemented" << endl;
2233
}
2234

    
2235
void
2236
MainWindow::preferences()
2237
{
2238
    if (!m_preferencesDialog.isNull()) {
2239
        m_preferencesDialog->show();
2240
        m_preferencesDialog->raise();
2241
        return;
2242
    }
2243

    
2244
    m_preferencesDialog = new PreferencesDialog(this);
2245

    
2246
    // DeleteOnClose is safe here, because m_preferencesDialog is a
2247
    // QPointer that will be zeroed when the dialog is deleted.  We
2248
    // use it in preference to leaving the dialog lying around because
2249
    // if you Cancel the dialog, it resets the preferences state
2250
    // without resetting its own widgets, so its state will be
2251
    // incorrect when next shown unless we construct it afresh
2252
    m_preferencesDialog->setAttribute(Qt::WA_DeleteOnClose);
2253

    
2254
    m_preferencesDialog->show();
2255
}
2256

    
2257
void
2258
MainWindow::mouseEnteredWidget()
2259
{
2260
    QWidget *w = dynamic_cast<QWidget *>(sender());
2261
    if (!w) return;
2262

    
2263
    if (w == m_mainLevelPan) {
2264
        contextHelpChanged(tr("Adjust the master playback level"));
2265
    } else if (w == m_playSpeed) {
2266
        contextHelpChanged(tr("Adjust the master playback speed"));
2267
    }
2268
}
2269

    
2270
void
2271
MainWindow::mouseLeftWidget()
2272
{
2273
    contextHelpChanged("");
2274
}
2275

    
2276
void
2277
MainWindow::website()
2278
{
2279
    openHelpUrl(tr("http://www.sonicvisualiser.org/sonicvector/"));
2280
}
2281

    
2282
void
2283
MainWindow::help()
2284
{
2285
    openHelpUrl(tr("http://www.sonicvisualiser.org/sonicvector/doc/"));
2286
}
2287

    
2288
void
2289
MainWindow::about()
2290
{
2291
    bool debug = false;
2292
    QString version = "(unknown version)";
2293

    
2294
#ifdef BUILD_DEBUG
2295
    debug = true;
2296
#endif
2297
#ifdef VECT_VERSION
2298
#ifdef SVNREV
2299
    version = tr("Release %1 : Revision %2").arg(VECT_VERSION).arg(SVNREV);
2300
#else
2301
    version = tr("Release %1").arg(VECT_VERSION);
2302
#endif
2303
#else
2304
#ifdef SVNREV
2305
    version = tr("Unreleased : Revision %1").arg(SVNREV);
2306
#endif
2307
#endif
2308

    
2309
    QString aboutText;
2310

    
2311
    aboutText += tr("<h3>About Sonic Vector</h3>");
2312
    aboutText += tr("<p>Sonic Vector is a comparative viewer for sets of related audio recordings.</p>");
2313
    aboutText += tr("<p>%1 : %2 configuration</p>")
2314
        .arg(version)
2315
        .arg(debug ? tr("Debug") : tr("Release"));
2316

    
2317
    aboutText += 
2318
        "<p>Sonic Vector Copyright &copy; 2005 - 2014 Chris Cannam and<br>"
2319
        "Queen Mary, University of London.</p>"
2320
        "<p>This program uses library code from many other authors. Please<br>"
2321
        "refer to the accompanying documentation for more information.</p>"
2322
        "<p>This program is free software; you can redistribute it and/or<br>"
2323
        "modify it under the terms of the GNU General Public License as<br>"
2324
        "published by the Free Software Foundation; either version 2 of the<br>"
2325
        "License, or (at your option) any later version.<br>See the file "
2326
        "COPYING included with this distribution for more information.</p>";
2327
    
2328
    QMessageBox::about(this, tr("About Sonic Vector"), aboutText);
2329
}
2330

    
2331
void
2332
MainWindow::keyReference()
2333
{
2334
    m_keyReference->show();
2335
}
2336

    
2337
void
2338
MainWindow::loadStyle()
2339
{
2340
    m_viewManager->setGlobalDarkBackground(true);
2341
    
2342
    QString stylepath = ":vect.qss";
2343
    QFile file(stylepath);
2344
    if (!file.open(QFile::ReadOnly)) {
2345
        std::cerr << "WARNING: Failed to open style file " << stylepath
2346
                  << std::endl;
2347
    } else {
2348
        QString styleSheet = QLatin1String(file.readAll());
2349
        qApp->setStyleSheet(styleSheet);
2350
        QPalette pal(Qt::white, Qt::gray, Qt::white, Qt::black, Qt::gray, Qt::white, Qt::white, Qt::black, Qt::black);
2351
        qApp->setPalette(pal);
2352
    }
2353
}
2354

    
2355

    
2356