Mercurial > hg > easaier-soundaccess
comparison sv/main/MainWindow.cpp @ 0:fc9323a41f5a
start base : Sonic Visualiser sv1-1.0rc1
author | lbajardsilogic |
---|---|
date | Fri, 11 May 2007 09:08:14 +0000 |
parents | |
children | ba54bc09cd62 |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:fc9323a41f5a |
---|---|
1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ | |
2 | |
3 /* | |
4 Sonic Visualiser | |
5 An audio file viewer and annotation editor. | |
6 Centre for Digital Music, Queen Mary, University of London. | |
7 This file copyright 2006 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 "document/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 "view/ViewManager.h" | |
27 #include "base/Preferences.h" | |
28 #include "layer/WaveformLayer.h" | |
29 #include "layer/TimeRulerLayer.h" | |
30 #include "layer/TimeInstantLayer.h" | |
31 #include "layer/TimeValueLayer.h" | |
32 #include "layer/Colour3DPlotLayer.h" | |
33 #include "layer/SliceLayer.h" | |
34 #include "layer/SliceableLayer.h" | |
35 #include "widgets/Fader.h" | |
36 #include "view/Overview.h" | |
37 #include "widgets/PropertyBox.h" | |
38 #include "widgets/PropertyStack.h" | |
39 #include "widgets/AudioDial.h" | |
40 #include "widgets/LayerTree.h" | |
41 #include "widgets/ListInputDialog.h" | |
42 #include "widgets/SubdividingMenu.h" | |
43 #include "widgets/NotifyingPushButton.h" | |
44 #include "audioio/AudioCallbackPlaySource.h" | |
45 #include "audioio/AudioCallbackPlayTarget.h" | |
46 #include "audioio/AudioTargetFactory.h" | |
47 #include "audioio/PlaySpeedRangeMapper.h" | |
48 #include "data/fileio/AudioFileReaderFactory.h" | |
49 #include "data/fileio/DataFileReaderFactory.h" | |
50 #include "data/fileio/WavFileWriter.h" | |
51 #include "data/fileio/CSVFileWriter.h" | |
52 #include "data/fileio/BZipFileDevice.h" | |
53 #include "data/fileio/RemoteFile.h" | |
54 #include "data/fft/FFTDataServer.h" | |
55 #include "base/RecentFiles.h" | |
56 #include "transform/TransformFactory.h" | |
57 #include "base/PlayParameterRepository.h" | |
58 #include "base/XmlExportable.h" | |
59 #include "base/CommandHistory.h" | |
60 #include "base/Profiler.h" | |
61 #include "base/Clipboard.h" | |
62 #include "osc/OSCQueue.h" | |
63 | |
64 // For version information | |
65 #include "vamp/vamp.h" | |
66 #include "vamp-sdk/PluginBase.h" | |
67 #include "plugin/api/ladspa.h" | |
68 #include "plugin/api/dssi.h" | |
69 | |
70 #include <QApplication> | |
71 #include <QMessageBox> | |
72 #include <QGridLayout> | |
73 #include <QLabel> | |
74 #include <QAction> | |
75 #include <QMenuBar> | |
76 #include <QToolBar> | |
77 #include <QInputDialog> | |
78 #include <QStatusBar> | |
79 #include <QTreeView> | |
80 #include <QFile> | |
81 #include <QFileInfo> | |
82 #include <QDir> | |
83 #include <QTextStream> | |
84 #include <QProcess> | |
85 #include <QShortcut> | |
86 #include <QSettings> | |
87 #include <QDateTime> | |
88 #include <QProcess> | |
89 #include <QCheckBox> | |
90 #include <QRegExp> | |
91 | |
92 #include <iostream> | |
93 #include <cstdio> | |
94 #include <errno.h> | |
95 | |
96 using std::cerr; | |
97 using std::endl; | |
98 | |
99 using std::vector; | |
100 using std::map; | |
101 using std::set; | |
102 | |
103 | |
104 MainWindow::MainWindow(bool withAudioOutput, bool withOSCSupport) : | |
105 m_document(0), | |
106 m_paneStack(0), | |
107 m_viewManager(0), | |
108 m_overview(0), | |
109 m_timeRulerLayer(0), | |
110 m_audioOutput(withAudioOutput), | |
111 m_playSource(0), | |
112 m_playTarget(0), | |
113 m_oscQueue(withOSCSupport ? new OSCQueue() : 0), | |
114 m_recentFiles("RecentFiles", 20), | |
115 m_recentTransforms("RecentTransforms", 20), | |
116 m_mainMenusCreated(false), | |
117 m_paneMenu(0), | |
118 m_layerMenu(0), | |
119 m_transformsMenu(0), | |
120 m_existingLayersMenu(0), | |
121 m_sliceMenu(0), | |
122 m_recentFilesMenu(0), | |
123 m_recentTransformsMenu(0), | |
124 m_rightButtonMenu(0), | |
125 m_rightButtonLayerMenu(0), | |
126 m_rightButtonTransformsMenu(0), | |
127 m_documentModified(false), | |
128 m_openingAudioFile(false), | |
129 m_abandoning(false), | |
130 m_preferencesDialog(0) | |
131 { | |
132 setWindowTitle(tr("Sonic Visualiser")); | |
133 | |
134 UnitDatabase::getInstance()->registerUnit("Hz"); | |
135 UnitDatabase::getInstance()->registerUnit("dB"); | |
136 | |
137 connect(CommandHistory::getInstance(), SIGNAL(commandExecuted()), | |
138 this, SLOT(documentModified())); | |
139 connect(CommandHistory::getInstance(), SIGNAL(documentRestored()), | |
140 this, SLOT(documentRestored())); | |
141 | |
142 QFrame *frame = new QFrame; | |
143 setCentralWidget(frame); | |
144 | |
145 QGridLayout *layout = new QGridLayout; | |
146 | |
147 m_viewManager = new ViewManager(); | |
148 connect(m_viewManager, SIGNAL(selectionChanged()), | |
149 this, SLOT(updateMenuStates())); | |
150 connect(m_viewManager, SIGNAL(inProgressSelectionChanged()), | |
151 this, SLOT(inProgressSelectionChanged())); | |
152 | |
153 m_descriptionLabel = new QLabel; | |
154 | |
155 m_paneStack = new PaneStack(frame, m_viewManager); | |
156 connect(m_paneStack, SIGNAL(currentPaneChanged(Pane *)), | |
157 this, SLOT(currentPaneChanged(Pane *))); | |
158 connect(m_paneStack, SIGNAL(currentLayerChanged(Pane *, Layer *)), | |
159 this, SLOT(currentLayerChanged(Pane *, Layer *))); | |
160 connect(m_paneStack, SIGNAL(rightButtonMenuRequested(Pane *, QPoint)), | |
161 this, SLOT(rightButtonMenuRequested(Pane *, QPoint))); | |
162 connect(m_paneStack, SIGNAL(propertyStacksResized()), | |
163 this, SLOT(propertyStacksResized())); | |
164 connect(m_paneStack, SIGNAL(contextHelpChanged(const QString &)), | |
165 this, SLOT(contextHelpChanged(const QString &))); | |
166 | |
167 m_overview = new Overview(frame); | |
168 m_overview->setViewManager(m_viewManager); | |
169 m_overview->setFixedHeight(40); | |
170 m_overview->setFrameStyle(QFrame::StyledPanel | QFrame::Sunken); | |
171 connect(m_overview, SIGNAL(contextHelpChanged(const QString &)), | |
172 this, SLOT(contextHelpChanged(const QString &))); | |
173 | |
174 m_panLayer = new WaveformLayer; | |
175 m_panLayer->setChannelMode(WaveformLayer::MergeChannels); | |
176 // m_panLayer->setScale(WaveformLayer::MeterScale); | |
177 // m_panLayer->setAutoNormalize(true); | |
178 m_panLayer->setBaseColour(Qt::darkGreen); | |
179 m_panLayer->setAggressiveCacheing(true); | |
180 m_overview->addLayer(m_panLayer); | |
181 | |
182 m_playSource = new AudioCallbackPlaySource(m_viewManager); | |
183 | |
184 connect(m_playSource, SIGNAL(sampleRateMismatch(size_t, size_t, bool)), | |
185 this, SLOT(sampleRateMismatch(size_t, size_t, bool))); | |
186 connect(m_playSource, SIGNAL(audioOverloadPluginDisabled()), | |
187 this, SLOT(audioOverloadPluginDisabled())); | |
188 | |
189 m_fader = new Fader(frame, false); | |
190 connect(m_fader, SIGNAL(mouseEntered()), this, SLOT(mouseEnteredWidget())); | |
191 connect(m_fader, SIGNAL(mouseLeft()), this, SLOT(mouseLeftWidget())); | |
192 | |
193 m_playSpeed = new AudioDial(frame); | |
194 m_playSpeed->setMinimum(0); | |
195 m_playSpeed->setMaximum(200); | |
196 m_playSpeed->setValue(100); | |
197 m_playSpeed->setFixedWidth(24); | |
198 m_playSpeed->setFixedHeight(24); | |
199 m_playSpeed->setNotchesVisible(true); | |
200 m_playSpeed->setPageStep(10); | |
201 m_playSpeed->setObjectName(tr("Playback Speedup")); | |
202 m_playSpeed->setDefaultValue(100); | |
203 m_playSpeed->setRangeMapper(new PlaySpeedRangeMapper(0, 200)); | |
204 m_playSpeed->setShowToolTip(true); | |
205 connect(m_playSpeed, SIGNAL(valueChanged(int)), | |
206 this, SLOT(playSpeedChanged(int))); | |
207 connect(m_playSpeed, SIGNAL(mouseEntered()), this, SLOT(mouseEnteredWidget())); | |
208 connect(m_playSpeed, SIGNAL(mouseLeft()), this, SLOT(mouseLeftWidget())); | |
209 | |
210 m_playSharpen = new NotifyingPushButton(frame); | |
211 m_playSharpen->setToolTip(tr("Sharpen percussive transients")); | |
212 m_playSharpen->setFixedSize(20, 20); | |
213 // m_playSharpen->setFlat(true); | |
214 m_playSharpen->setEnabled(false); | |
215 m_playSharpen->setCheckable(true); | |
216 m_playSharpen->setChecked(false); | |
217 m_playSharpen->setIcon(QIcon(":icons/sharpen.png")); | |
218 connect(m_playSharpen, SIGNAL(clicked()), this, SLOT(playSharpenToggled())); | |
219 connect(m_playSharpen, SIGNAL(mouseEntered()), this, SLOT(mouseEnteredWidget())); | |
220 connect(m_playSharpen, SIGNAL(mouseLeft()), this, SLOT(mouseLeftWidget())); | |
221 | |
222 m_playMono = new NotifyingPushButton(frame); | |
223 m_playMono->setToolTip(tr("Run time stretcher in mono only")); | |
224 m_playMono->setFixedSize(20, 20); | |
225 // m_playMono->setFlat(true); | |
226 m_playMono->setEnabled(false); | |
227 m_playMono->setCheckable(true); | |
228 m_playMono->setChecked(false); | |
229 m_playMono->setIcon(QIcon(":icons/mono.png")); | |
230 connect(m_playMono, SIGNAL(clicked()), this, SLOT(playMonoToggled())); | |
231 connect(m_playMono, SIGNAL(mouseEntered()), this, SLOT(mouseEnteredWidget())); | |
232 connect(m_playMono, SIGNAL(mouseLeft()), this, SLOT(mouseLeftWidget())); | |
233 | |
234 QSettings settings; | |
235 settings.beginGroup("MainWindow"); | |
236 m_playSharpen->setChecked(settings.value("playsharpen", true).toBool()); | |
237 m_playMono->setChecked(settings.value("playmono", false).toBool()); | |
238 settings.endGroup(); | |
239 | |
240 layout->setSpacing(4); | |
241 layout->addWidget(m_paneStack, 0, 0, 1, 5); | |
242 layout->addWidget(m_overview, 1, 0); | |
243 layout->addWidget(m_fader, 1, 1); | |
244 layout->addWidget(m_playSpeed, 1, 2); | |
245 layout->addWidget(m_playSharpen, 1, 3); | |
246 layout->addWidget(m_playMono, 1, 4); | |
247 | |
248 m_paneStack->setPropertyStackMinWidth | |
249 (m_fader->width() + m_playSpeed->width() + m_playSharpen->width() + | |
250 m_playMono->width() + layout->spacing() * 4); | |
251 | |
252 layout->setColumnStretch(0, 10); | |
253 | |
254 frame->setLayout(layout); | |
255 | |
256 connect(m_viewManager, SIGNAL(outputLevelsChanged(float, float)), | |
257 this, SLOT(outputLevelsChanged(float, float))); | |
258 | |
259 connect(m_viewManager, SIGNAL(playbackFrameChanged(unsigned long)), | |
260 this, SLOT(playbackFrameChanged(unsigned long))); | |
261 | |
262 connect(m_viewManager, SIGNAL(globalCentreFrameChanged(unsigned long)), | |
263 this, SLOT(globalCentreFrameChanged(unsigned long))); | |
264 | |
265 connect(m_viewManager, SIGNAL(viewCentreFrameChanged(View *, unsigned long)), | |
266 this, SLOT(viewCentreFrameChanged(View *, unsigned long))); | |
267 | |
268 connect(m_viewManager, SIGNAL(viewZoomLevelChanged(View *, unsigned long, bool)), | |
269 this, SLOT(viewZoomLevelChanged(View *, unsigned long, bool))); | |
270 | |
271 connect(Preferences::getInstance(), | |
272 SIGNAL(propertyChanged(PropertyContainer::PropertyName)), | |
273 this, | |
274 SLOT(preferenceChanged(PropertyContainer::PropertyName))); | |
275 | |
276 // preferenceChanged("Property Box Layout"); | |
277 | |
278 if (m_oscQueue && m_oscQueue->isOK()) { | |
279 connect(m_oscQueue, SIGNAL(messagesAvailable()), this, SLOT(pollOSC())); | |
280 QTimer *oscTimer = new QTimer(this); | |
281 connect(oscTimer, SIGNAL(timeout()), this, SLOT(pollOSC())); | |
282 oscTimer->start(1000); | |
283 } | |
284 | |
285 setupMenus(); | |
286 setupToolbars(); | |
287 | |
288 statusBar(); | |
289 | |
290 newSession(); | |
291 } | |
292 | |
293 MainWindow::~MainWindow() | |
294 { | |
295 // std::cerr << "MainWindow::~MainWindow()" << std::endl; | |
296 | |
297 if (!m_abandoning) { | |
298 closeSession(); | |
299 } | |
300 delete m_playTarget; | |
301 delete m_playSource; | |
302 delete m_viewManager; | |
303 delete m_oscQueue; | |
304 Profiles::getInstance()->dump(); | |
305 } | |
306 | |
307 QString | |
308 MainWindow::getOpenFileName(FileFinder::FileType type) | |
309 { | |
310 FileFinder *ff = FileFinder::getInstance(); | |
311 switch (type) { | |
312 case FileFinder::SessionFile: | |
313 return ff->getOpenFileName(type, m_sessionFile); | |
314 case FileFinder::AudioFile: | |
315 return ff->getOpenFileName(type, m_audioFile); | |
316 case FileFinder::LayerFile: | |
317 return ff->getOpenFileName(type, m_sessionFile); | |
318 case FileFinder::SessionOrAudioFile: | |
319 return ff->getOpenFileName(type, m_sessionFile); | |
320 case FileFinder::ImageFile: | |
321 return ff->getOpenFileName(type, m_sessionFile); | |
322 case FileFinder::AnyFile: | |
323 if (getMainModel() != 0 && | |
324 m_paneStack != 0 && | |
325 m_paneStack->getCurrentPane() != 0) { // can import a layer | |
326 return ff->getOpenFileName(FileFinder::AnyFile, m_sessionFile); | |
327 } else { | |
328 return ff->getOpenFileName(FileFinder::SessionOrAudioFile, | |
329 m_sessionFile); | |
330 } | |
331 } | |
332 return ""; | |
333 } | |
334 | |
335 QString | |
336 MainWindow::getSaveFileName(FileFinder::FileType type) | |
337 { | |
338 FileFinder *ff = FileFinder::getInstance(); | |
339 switch (type) { | |
340 case FileFinder::SessionFile: | |
341 return ff->getSaveFileName(type, m_sessionFile); | |
342 case FileFinder::AudioFile: | |
343 return ff->getSaveFileName(type, m_audioFile); | |
344 case FileFinder::LayerFile: | |
345 return ff->getSaveFileName(type, m_sessionFile); | |
346 case FileFinder::SessionOrAudioFile: | |
347 return ff->getSaveFileName(type, m_sessionFile); | |
348 case FileFinder::ImageFile: | |
349 return ff->getSaveFileName(type, m_sessionFile); | |
350 case FileFinder::AnyFile: | |
351 return ff->getSaveFileName(type, m_sessionFile); | |
352 } | |
353 return ""; | |
354 } | |
355 | |
356 void | |
357 MainWindow::registerLastOpenedFilePath(FileFinder::FileType type, QString path) | |
358 { | |
359 FileFinder *ff = FileFinder::getInstance(); | |
360 ff->registerLastOpenedFilePath(type, path); | |
361 } | |
362 | |
363 void | |
364 MainWindow::setupMenus() | |
365 { | |
366 if (!m_mainMenusCreated) { | |
367 m_rightButtonMenu = new QMenu(); | |
368 | |
369 // No -- we don't want tear-off enabled on the right-button | |
370 // menu. If it is enabled, then simply right-clicking and | |
371 // releasing will pop up the menu, activate the tear-off, and | |
372 // leave the torn-off menu window in front of the main window. | |
373 // That isn't desirable. I'm not sure it ever would be, in a | |
374 // context menu -- perhaps technically a Qt bug? | |
375 // m_rightButtonMenu->setTearOffEnabled(true); | |
376 } | |
377 | |
378 if (m_rightButtonLayerMenu) { | |
379 m_rightButtonLayerMenu->clear(); | |
380 } else { | |
381 m_rightButtonLayerMenu = m_rightButtonMenu->addMenu(tr("&Layer")); | |
382 m_rightButtonLayerMenu->setTearOffEnabled(true); | |
383 m_rightButtonMenu->addSeparator(); | |
384 } | |
385 | |
386 if (m_rightButtonTransformsMenu) { | |
387 m_rightButtonTransformsMenu->clear(); | |
388 } else { | |
389 m_rightButtonTransformsMenu = m_rightButtonMenu->addMenu(tr("&Transform")); | |
390 m_rightButtonTransformsMenu->setTearOffEnabled(true); | |
391 m_rightButtonMenu->addSeparator(); | |
392 } | |
393 | |
394 if (!m_mainMenusCreated) { | |
395 CommandHistory::getInstance()->registerMenu(m_rightButtonMenu); | |
396 m_rightButtonMenu->addSeparator(); | |
397 } | |
398 | |
399 setupFileMenu(); | |
400 setupEditMenu(); | |
401 setupViewMenu(); | |
402 setupPaneAndLayerMenus(); | |
403 setupTransformsMenu(); | |
404 setupHelpMenu(); | |
405 | |
406 m_mainMenusCreated = true; | |
407 } | |
408 | |
409 void | |
410 MainWindow::setupFileMenu() | |
411 { | |
412 if (m_mainMenusCreated) return; | |
413 | |
414 QMenu *menu = menuBar()->addMenu(tr("&File")); | |
415 menu->setTearOffEnabled(true); | |
416 QToolBar *toolbar = addToolBar(tr("File Toolbar")); | |
417 | |
418 QIcon icon(":icons/filenew.png"); | |
419 icon.addFile(":icons/filenew-22.png"); | |
420 QAction *action = new QAction(icon, tr("&New Session"), this); | |
421 action->setShortcut(tr("Ctrl+N")); | |
422 action->setStatusTip(tr("Abandon the current Sonic Visualiser session and start a new one")); | |
423 connect(action, SIGNAL(triggered()), this, SLOT(newSession())); | |
424 menu->addAction(action); | |
425 toolbar->addAction(action); | |
426 | |
427 icon = QIcon(":icons/fileopensession.png"); | |
428 action = new QAction(icon, tr("&Open Session..."), this); | |
429 action->setShortcut(tr("Ctrl+O")); | |
430 action->setStatusTip(tr("Open a previously saved Sonic Visualiser session file")); | |
431 connect(action, SIGNAL(triggered()), this, SLOT(openSession())); | |
432 menu->addAction(action); | |
433 | |
434 icon = QIcon(":icons/fileopen.png"); | |
435 icon.addFile(":icons/fileopen-22.png"); | |
436 | |
437 action = new QAction(icon, tr("&Open..."), this); | |
438 action->setStatusTip(tr("Open a session file, audio file, or layer")); | |
439 connect(action, SIGNAL(triggered()), this, SLOT(openSomething())); | |
440 toolbar->addAction(action); | |
441 | |
442 icon = QIcon(":icons/filesave.png"); | |
443 icon.addFile(":icons/filesave-22.png"); | |
444 action = new QAction(icon, tr("&Save Session"), this); | |
445 action->setShortcut(tr("Ctrl+S")); | |
446 action->setStatusTip(tr("Save the current session into a Sonic Visualiser session file")); | |
447 connect(action, SIGNAL(triggered()), this, SLOT(saveSession())); | |
448 connect(this, SIGNAL(canSave(bool)), action, SLOT(setEnabled(bool))); | |
449 menu->addAction(action); | |
450 toolbar->addAction(action); | |
451 | |
452 icon = QIcon(":icons/filesaveas.png"); | |
453 icon.addFile(":icons/filesaveas-22.png"); | |
454 action = new QAction(icon, tr("Save Session &As..."), this); | |
455 action->setStatusTip(tr("Save the current session into a new Sonic Visualiser session file")); | |
456 connect(action, SIGNAL(triggered()), this, SLOT(saveSessionAs())); | |
457 menu->addAction(action); | |
458 toolbar->addAction(action); | |
459 | |
460 menu->addSeparator(); | |
461 | |
462 icon = QIcon(":icons/fileopenaudio.png"); | |
463 action = new QAction(icon, tr("&Import Audio File..."), this); | |
464 action->setShortcut(tr("Ctrl+I")); | |
465 action->setStatusTip(tr("Import an existing audio file")); | |
466 connect(action, SIGNAL(triggered()), this, SLOT(importAudio())); | |
467 menu->addAction(action); | |
468 | |
469 action = new QAction(tr("Import Secondary Audio File..."), this); | |
470 action->setShortcut(tr("Ctrl+Shift+I")); | |
471 action->setStatusTip(tr("Import an extra audio file as a separate layer")); | |
472 connect(action, SIGNAL(triggered()), this, SLOT(importMoreAudio())); | |
473 connect(this, SIGNAL(canImportMoreAudio(bool)), action, SLOT(setEnabled(bool))); | |
474 menu->addAction(action); | |
475 | |
476 action = new QAction(tr("&Export Audio File..."), this); | |
477 action->setStatusTip(tr("Export selection as an audio file")); | |
478 connect(action, SIGNAL(triggered()), this, SLOT(exportAudio())); | |
479 connect(this, SIGNAL(canExportAudio(bool)), action, SLOT(setEnabled(bool))); | |
480 menu->addAction(action); | |
481 | |
482 menu->addSeparator(); | |
483 | |
484 action = new QAction(tr("Import Annotation &Layer..."), this); | |
485 action->setShortcut(tr("Ctrl+L")); | |
486 action->setStatusTip(tr("Import layer data from an existing file")); | |
487 connect(action, SIGNAL(triggered()), this, SLOT(importLayer())); | |
488 connect(this, SIGNAL(canImportLayer(bool)), action, SLOT(setEnabled(bool))); | |
489 menu->addAction(action); | |
490 | |
491 action = new QAction(tr("Export Annotation Layer..."), this); | |
492 action->setStatusTip(tr("Export layer data to a file")); | |
493 connect(action, SIGNAL(triggered()), this, SLOT(exportLayer())); | |
494 connect(this, SIGNAL(canExportLayer(bool)), action, SLOT(setEnabled(bool))); | |
495 menu->addAction(action); | |
496 | |
497 menu->addSeparator(); | |
498 | |
499 action = new QAction(tr("Export Image File..."), this); | |
500 action->setStatusTip(tr("Export a single pane to an image file")); | |
501 connect(action, SIGNAL(triggered()), this, SLOT(exportImage())); | |
502 connect(this, SIGNAL(canExportImage(bool)), action, SLOT(setEnabled(bool))); | |
503 menu->addAction(action); | |
504 | |
505 menu->addSeparator(); | |
506 | |
507 action = new QAction(tr("Open Lo&cation..."), this); | |
508 action->setShortcut(tr("Ctrl+Shift+O")); | |
509 action->setStatusTip(tr("Open or import a file from a remote URL")); | |
510 connect(action, SIGNAL(triggered()), this, SLOT(openLocation())); | |
511 menu->addAction(action); | |
512 | |
513 menu->addSeparator(); | |
514 | |
515 m_recentFilesMenu = menu->addMenu(tr("&Recent Files")); | |
516 m_recentFilesMenu->setTearOffEnabled(true); | |
517 setupRecentFilesMenu(); | |
518 connect(&m_recentFiles, SIGNAL(recentChanged()), | |
519 this, SLOT(setupRecentFilesMenu())); | |
520 | |
521 menu->addSeparator(); | |
522 action = new QAction(tr("&Preferences..."), this); | |
523 action->setStatusTip(tr("Adjust the application preferences")); | |
524 connect(action, SIGNAL(triggered()), this, SLOT(preferences())); | |
525 menu->addAction(action); | |
526 | |
527 /*!!! | |
528 menu->addSeparator(); | |
529 | |
530 action = new QAction(tr("Play / Pause"), this); | |
531 action->setShortcut(tr("Space")); | |
532 action->setStatusTip(tr("Start or stop playback from the current position")); | |
533 connect(action, SIGNAL(triggered()), this, SLOT(play())); | |
534 menu->addAction(action); | |
535 */ | |
536 | |
537 menu->addSeparator(); | |
538 action = new QAction(QIcon(":/icons/exit.png"), | |
539 tr("&Quit"), this); | |
540 action->setShortcut(tr("Ctrl+Q")); | |
541 action->setStatusTip(tr("Exit Sonic Visualiser")); | |
542 connect(action, SIGNAL(triggered()), this, SLOT(close())); | |
543 menu->addAction(action); | |
544 } | |
545 | |
546 void | |
547 MainWindow::setupEditMenu() | |
548 { | |
549 if (m_mainMenusCreated) return; | |
550 | |
551 QMenu *menu = menuBar()->addMenu(tr("&Edit")); | |
552 menu->setTearOffEnabled(true); | |
553 CommandHistory::getInstance()->registerMenu(menu); | |
554 | |
555 menu->addSeparator(); | |
556 | |
557 QAction *action = new QAction(QIcon(":/icons/editcut.png"), | |
558 tr("Cu&t"), this); | |
559 action->setShortcut(tr("Ctrl+X")); | |
560 action->setStatusTip(tr("Cut the selection from the current layer to the clipboard")); | |
561 connect(action, SIGNAL(triggered()), this, SLOT(cut())); | |
562 connect(this, SIGNAL(canEditSelection(bool)), action, SLOT(setEnabled(bool))); | |
563 menu->addAction(action); | |
564 m_rightButtonMenu->addAction(action); | |
565 | |
566 action = new QAction(QIcon(":/icons/editcopy.png"), | |
567 tr("&Copy"), this); | |
568 action->setShortcut(tr("Ctrl+C")); | |
569 action->setStatusTip(tr("Copy the selection from the current layer to the clipboard")); | |
570 connect(action, SIGNAL(triggered()), this, SLOT(copy())); | |
571 connect(this, SIGNAL(canEditSelection(bool)), action, SLOT(setEnabled(bool))); | |
572 menu->addAction(action); | |
573 m_rightButtonMenu->addAction(action); | |
574 | |
575 action = new QAction(QIcon(":/icons/editpaste.png"), | |
576 tr("&Paste"), this); | |
577 action->setShortcut(tr("Ctrl+V")); | |
578 action->setStatusTip(tr("Paste from the clipboard to the current layer")); | |
579 connect(action, SIGNAL(triggered()), this, SLOT(paste())); | |
580 connect(this, SIGNAL(canPaste(bool)), action, SLOT(setEnabled(bool))); | |
581 menu->addAction(action); | |
582 m_rightButtonMenu->addAction(action); | |
583 | |
584 action = new QAction(tr("&Delete Selected Items"), this); | |
585 action->setShortcut(tr("Del")); | |
586 action->setStatusTip(tr("Delete the selection from the current layer")); | |
587 connect(action, SIGNAL(triggered()), this, SLOT(deleteSelected())); | |
588 connect(this, SIGNAL(canEditSelection(bool)), action, SLOT(setEnabled(bool))); | |
589 menu->addAction(action); | |
590 m_rightButtonMenu->addAction(action); | |
591 | |
592 menu->addSeparator(); | |
593 m_rightButtonMenu->addSeparator(); | |
594 | |
595 action = new QAction(tr("Select &All"), this); | |
596 action->setShortcut(tr("Ctrl+A")); | |
597 action->setStatusTip(tr("Select the whole duration of the current session")); | |
598 connect(action, SIGNAL(triggered()), this, SLOT(selectAll())); | |
599 connect(this, SIGNAL(canSelect(bool)), action, SLOT(setEnabled(bool))); | |
600 menu->addAction(action); | |
601 m_rightButtonMenu->addAction(action); | |
602 | |
603 action = new QAction(tr("Select &Visible Range"), this); | |
604 action->setShortcut(tr("Ctrl+Shift+A")); | |
605 action->setStatusTip(tr("Select the time range corresponding to the current window width")); | |
606 connect(action, SIGNAL(triggered()), this, SLOT(selectVisible())); | |
607 connect(this, SIGNAL(canSelect(bool)), action, SLOT(setEnabled(bool))); | |
608 menu->addAction(action); | |
609 | |
610 action = new QAction(tr("Select to &Start"), this); | |
611 action->setShortcut(tr("Shift+Left")); | |
612 action->setStatusTip(tr("Select from the start of the session to the current playback position")); | |
613 connect(action, SIGNAL(triggered()), this, SLOT(selectToStart())); | |
614 connect(this, SIGNAL(canSelect(bool)), action, SLOT(setEnabled(bool))); | |
615 menu->addAction(action); | |
616 | |
617 action = new QAction(tr("Select to &End"), this); | |
618 action->setShortcut(tr("Shift+Right")); | |
619 action->setStatusTip(tr("Select from the current playback position to the end of the session")); | |
620 connect(action, SIGNAL(triggered()), this, SLOT(selectToEnd())); | |
621 connect(this, SIGNAL(canSelect(bool)), action, SLOT(setEnabled(bool))); | |
622 menu->addAction(action); | |
623 | |
624 action = new QAction(tr("C&lear Selection"), this); | |
625 action->setShortcut(tr("Esc")); | |
626 action->setStatusTip(tr("Clear the selection")); | |
627 connect(action, SIGNAL(triggered()), this, SLOT(clearSelection())); | |
628 connect(this, SIGNAL(canClearSelection(bool)), action, SLOT(setEnabled(bool))); | |
629 menu->addAction(action); | |
630 m_rightButtonMenu->addAction(action); | |
631 | |
632 menu->addSeparator(); | |
633 | |
634 action = new QAction(tr("&Insert Instant at Playback Position"), this); | |
635 action->setShortcut(tr("Enter")); | |
636 action->setStatusTip(tr("Insert a new time instant at the current playback position, in a new layer if necessary")); | |
637 connect(action, SIGNAL(triggered()), this, SLOT(insertInstant())); | |
638 connect(this, SIGNAL(canInsertInstant(bool)), action, SLOT(setEnabled(bool))); | |
639 menu->addAction(action); | |
640 | |
641 action = new QAction(tr("Insert Instants at Selection &Boundaries"), this); | |
642 action->setShortcut(tr("Shift+Enter")); | |
643 action->setStatusTip(tr("Insert new time instants at the start and end of the current selection, in a new layer if necessary")); | |
644 connect(action, SIGNAL(triggered()), this, SLOT(insertInstantsAtBoundaries())); | |
645 connect(this, SIGNAL(canInsertInstantsAtBoundaries(bool)), action, SLOT(setEnabled(bool))); | |
646 menu->addAction(action); | |
647 | |
648 // Laptop shortcut (no keypad Enter key) | |
649 connect(new QShortcut(tr(";"), this), SIGNAL(activated()), | |
650 this, SLOT(insertInstant())); | |
651 } | |
652 | |
653 void | |
654 MainWindow::setupViewMenu() | |
655 { | |
656 if (m_mainMenusCreated) return; | |
657 | |
658 QAction *action = 0; | |
659 | |
660 QMenu *menu = menuBar()->addMenu(tr("&View")); | |
661 menu->setTearOffEnabled(true); | |
662 action = new QAction(tr("Scroll &Left"), this); | |
663 action->setShortcut(tr("Left")); | |
664 action->setStatusTip(tr("Scroll the current pane to the left")); | |
665 connect(action, SIGNAL(triggered()), this, SLOT(scrollLeft())); | |
666 connect(this, SIGNAL(canScroll(bool)), action, SLOT(setEnabled(bool))); | |
667 menu->addAction(action); | |
668 | |
669 action = new QAction(tr("Scroll &Right"), this); | |
670 action->setShortcut(tr("Right")); | |
671 action->setStatusTip(tr("Scroll the current pane to the right")); | |
672 connect(action, SIGNAL(triggered()), this, SLOT(scrollRight())); | |
673 connect(this, SIGNAL(canScroll(bool)), action, SLOT(setEnabled(bool))); | |
674 menu->addAction(action); | |
675 | |
676 action = new QAction(tr("&Jump Left"), this); | |
677 action->setShortcut(tr("Ctrl+Left")); | |
678 action->setStatusTip(tr("Scroll the current pane a big step to the left")); | |
679 connect(action, SIGNAL(triggered()), this, SLOT(jumpLeft())); | |
680 connect(this, SIGNAL(canScroll(bool)), action, SLOT(setEnabled(bool))); | |
681 menu->addAction(action); | |
682 | |
683 action = new QAction(tr("J&ump Right"), this); | |
684 action->setShortcut(tr("Ctrl+Right")); | |
685 action->setStatusTip(tr("Scroll the current pane a big step to the right")); | |
686 connect(action, SIGNAL(triggered()), this, SLOT(jumpRight())); | |
687 connect(this, SIGNAL(canScroll(bool)), action, SLOT(setEnabled(bool))); | |
688 menu->addAction(action); | |
689 | |
690 menu->addSeparator(); | |
691 | |
692 action = new QAction(QIcon(":/icons/zoom-in.png"), | |
693 tr("Zoom &In"), this); | |
694 action->setShortcut(tr("Up")); | |
695 action->setStatusTip(tr("Increase the zoom level")); | |
696 connect(action, SIGNAL(triggered()), this, SLOT(zoomIn())); | |
697 connect(this, SIGNAL(canZoom(bool)), action, SLOT(setEnabled(bool))); | |
698 menu->addAction(action); | |
699 | |
700 action = new QAction(QIcon(":/icons/zoom-out.png"), | |
701 tr("Zoom &Out"), this); | |
702 action->setShortcut(tr("Down")); | |
703 action->setStatusTip(tr("Decrease the zoom level")); | |
704 connect(action, SIGNAL(triggered()), this, SLOT(zoomOut())); | |
705 connect(this, SIGNAL(canZoom(bool)), action, SLOT(setEnabled(bool))); | |
706 menu->addAction(action); | |
707 | |
708 action = new QAction(tr("Restore &Default Zoom"), this); | |
709 action->setStatusTip(tr("Restore the zoom level to the default")); | |
710 connect(action, SIGNAL(triggered()), this, SLOT(zoomDefault())); | |
711 connect(this, SIGNAL(canZoom(bool)), action, SLOT(setEnabled(bool))); | |
712 menu->addAction(action); | |
713 | |
714 action = new QAction(QIcon(":/icons/zoom-fit.png"), | |
715 tr("Zoom to &Fit"), this); | |
716 action->setStatusTip(tr("Zoom to show the whole file")); | |
717 connect(action, SIGNAL(triggered()), this, SLOT(zoomToFit())); | |
718 connect(this, SIGNAL(canZoom(bool)), action, SLOT(setEnabled(bool))); | |
719 menu->addAction(action); | |
720 | |
721 menu->addSeparator(); | |
722 | |
723 QActionGroup *overlayGroup = new QActionGroup(this); | |
724 | |
725 action = new QAction(tr("Show &No Overlays"), this); | |
726 action->setShortcut(tr("0")); | |
727 action->setStatusTip(tr("Hide centre indicator, frame times, layer names and scale")); | |
728 connect(action, SIGNAL(triggered()), this, SLOT(showNoOverlays())); | |
729 action->setCheckable(true); | |
730 action->setChecked(false); | |
731 overlayGroup->addAction(action); | |
732 menu->addAction(action); | |
733 | |
734 action = new QAction(tr("Show &Minimal Overlays"), this); | |
735 action->setShortcut(tr("9")); | |
736 action->setStatusTip(tr("Show centre indicator only")); | |
737 connect(action, SIGNAL(triggered()), this, SLOT(showMinimalOverlays())); | |
738 action->setCheckable(true); | |
739 action->setChecked(false); | |
740 overlayGroup->addAction(action); | |
741 menu->addAction(action); | |
742 | |
743 action = new QAction(tr("Show &Standard Overlays"), this); | |
744 action->setShortcut(tr("8")); | |
745 action->setStatusTip(tr("Show centre indicator, frame times and scale")); | |
746 connect(action, SIGNAL(triggered()), this, SLOT(showStandardOverlays())); | |
747 action->setCheckable(true); | |
748 action->setChecked(true); | |
749 overlayGroup->addAction(action); | |
750 menu->addAction(action); | |
751 | |
752 action = new QAction(tr("Show &All Overlays"), this); | |
753 action->setShortcut(tr("7")); | |
754 action->setStatusTip(tr("Show all texts and scale")); | |
755 connect(action, SIGNAL(triggered()), this, SLOT(showAllOverlays())); | |
756 action->setCheckable(true); | |
757 action->setChecked(false); | |
758 overlayGroup->addAction(action); | |
759 menu->addAction(action); | |
760 | |
761 menu->addSeparator(); | |
762 | |
763 action = new QAction(tr("Show &Zoom Wheels"), this); | |
764 action->setShortcut(tr("Z")); | |
765 action->setStatusTip(tr("Show thumbwheels for zooming horizontally and vertically")); | |
766 connect(action, SIGNAL(triggered()), this, SLOT(toggleZoomWheels())); | |
767 action->setCheckable(true); | |
768 action->setChecked(m_viewManager->getZoomWheelsEnabled()); | |
769 menu->addAction(action); | |
770 | |
771 action = new QAction(tr("Show Property Bo&xes"), this); | |
772 action->setShortcut(tr("X")); | |
773 action->setStatusTip(tr("Show the layer property boxes at the side of the main window")); | |
774 connect(action, SIGNAL(triggered()), this, SLOT(togglePropertyBoxes())); | |
775 action->setCheckable(true); | |
776 action->setChecked(true); | |
777 menu->addAction(action); | |
778 | |
779 action = new QAction(tr("Show Status &Bar"), this); | |
780 action->setStatusTip(tr("Show context help information in the status bar at the bottom of the window")); | |
781 connect(action, SIGNAL(triggered()), this, SLOT(toggleStatusBar())); | |
782 action->setCheckable(true); | |
783 action->setChecked(true); | |
784 menu->addAction(action); | |
785 | |
786 QSettings settings; | |
787 settings.beginGroup("MainWindow"); | |
788 bool sb = settings.value("showstatusbar", true).toBool(); | |
789 if (!sb) { | |
790 action->setChecked(false); | |
791 statusBar()->hide(); | |
792 } | |
793 settings.endGroup(); | |
794 | |
795 /*!!! This one doesn't work properly yet | |
796 | |
797 menu->addSeparator(); | |
798 | |
799 action = new QAction(tr("Show La&yer Hierarchy"), this); | |
800 action->setShortcut(tr("Alt+L")); | |
801 action->setStatusTip(tr("Open a window displaying the hierarchy of panes and layers in this session")); | |
802 connect(action, SIGNAL(triggered()), this, SLOT(showLayerTree())); | |
803 menu->addAction(action); | |
804 */ | |
805 } | |
806 | |
807 void | |
808 MainWindow::setupPaneAndLayerMenus() | |
809 { | |
810 if (m_paneMenu) { | |
811 m_paneActions.clear(); | |
812 m_paneMenu->clear(); | |
813 } else { | |
814 m_paneMenu = menuBar()->addMenu(tr("&Pane")); | |
815 m_paneMenu->setTearOffEnabled(true); | |
816 } | |
817 | |
818 if (m_layerMenu) { | |
819 m_layerActions.clear(); | |
820 m_layerMenu->clear(); | |
821 } else { | |
822 m_layerMenu = menuBar()->addMenu(tr("&Layer")); | |
823 m_layerMenu->setTearOffEnabled(true); | |
824 } | |
825 | |
826 QMenu *menu = m_paneMenu; | |
827 | |
828 QAction *action = new QAction(QIcon(":/icons/pane.png"), tr("Add &New Pane"), this); | |
829 action->setShortcut(tr("Alt+N")); | |
830 action->setStatusTip(tr("Add a new pane containing only a time ruler")); | |
831 connect(action, SIGNAL(triggered()), this, SLOT(addPane())); | |
832 connect(this, SIGNAL(canAddPane(bool)), action, SLOT(setEnabled(bool))); | |
833 m_paneActions[action] = PaneConfiguration(LayerFactory::TimeRuler); | |
834 menu->addAction(action); | |
835 | |
836 menu->addSeparator(); | |
837 | |
838 menu = m_layerMenu; | |
839 | |
840 // menu->addSeparator(); | |
841 | |
842 LayerFactory::LayerTypeSet emptyLayerTypes = | |
843 LayerFactory::getInstance()->getValidEmptyLayerTypes(); | |
844 | |
845 for (LayerFactory::LayerTypeSet::iterator i = emptyLayerTypes.begin(); | |
846 i != emptyLayerTypes.end(); ++i) { | |
847 | |
848 QIcon icon; | |
849 QString mainText, tipText, channelText; | |
850 LayerFactory::LayerType type = *i; | |
851 QString name = LayerFactory::getInstance()->getLayerPresentationName(type); | |
852 | |
853 icon = QIcon(QString(":/icons/%1.png") | |
854 .arg(LayerFactory::getInstance()->getLayerIconName(type))); | |
855 | |
856 mainText = tr("Add New %1 Layer").arg(name); | |
857 tipText = tr("Add a new empty layer of type %1").arg(name); | |
858 | |
859 action = new QAction(icon, mainText, this); | |
860 action->setStatusTip(tipText); | |
861 | |
862 if (type == LayerFactory::Text) { | |
863 action->setShortcut(tr("Alt+T")); | |
864 } | |
865 | |
866 connect(action, SIGNAL(triggered()), this, SLOT(addLayer())); | |
867 connect(this, SIGNAL(canAddLayer(bool)), action, SLOT(setEnabled(bool))); | |
868 m_layerActions[action] = type; | |
869 menu->addAction(action); | |
870 m_rightButtonLayerMenu->addAction(action); | |
871 } | |
872 | |
873 m_rightButtonLayerMenu->addSeparator(); | |
874 menu->addSeparator(); | |
875 | |
876 LayerFactory::LayerType backgroundTypes[] = { | |
877 LayerFactory::Waveform, | |
878 LayerFactory::Spectrogram, | |
879 LayerFactory::MelodicRangeSpectrogram, | |
880 LayerFactory::PeakFrequencySpectrogram, | |
881 LayerFactory::Spectrum | |
882 }; | |
883 | |
884 std::vector<Model *> models; | |
885 if (m_document) models = m_document->getTransformInputModels(); //!!! not well named for this! | |
886 bool plural = (models.size() > 1); | |
887 if (models.empty()) { | |
888 models.push_back(getMainModel()); // probably 0 | |
889 } | |
890 | |
891 for (unsigned int i = 0; | |
892 i < sizeof(backgroundTypes)/sizeof(backgroundTypes[0]); ++i) { | |
893 | |
894 for (int menuType = 0; menuType <= 1; ++menuType) { // pane, layer | |
895 | |
896 if (menuType == 0) menu = m_paneMenu; | |
897 else menu = m_layerMenu; | |
898 | |
899 QMenu *submenu = 0; | |
900 | |
901 QIcon icon; | |
902 QString mainText, shortcutText, tipText, channelText; | |
903 LayerFactory::LayerType type = backgroundTypes[i]; | |
904 bool mono = true; | |
905 | |
906 switch (type) { | |
907 | |
908 case LayerFactory::Waveform: | |
909 icon = QIcon(":/icons/waveform.png"); | |
910 mainText = tr("Add &Waveform"); | |
911 if (menuType == 0) { | |
912 shortcutText = tr("Alt+W"); | |
913 tipText = tr("Add a new pane showing a waveform view"); | |
914 } else { | |
915 tipText = tr("Add a new layer showing a waveform view"); | |
916 } | |
917 mono = false; | |
918 break; | |
919 | |
920 case LayerFactory::Spectrogram: | |
921 icon = QIcon(":/icons/spectrogram.png"); | |
922 mainText = tr("Add &Spectrogram"); | |
923 if (menuType == 0) { | |
924 shortcutText = tr("Alt+S"); | |
925 tipText = tr("Add a new pane showing a spectrogram"); | |
926 } else { | |
927 tipText = tr("Add a new layer showing a spectrogram"); | |
928 } | |
929 break; | |
930 | |
931 case LayerFactory::MelodicRangeSpectrogram: | |
932 icon = QIcon(":/icons/spectrogram.png"); | |
933 mainText = tr("Add &Melodic Range Spectrogram"); | |
934 if (menuType == 0) { | |
935 shortcutText = tr("Alt+M"); | |
936 tipText = tr("Add a new pane showing a spectrogram set up for an overview of note pitches"); | |
937 } else { | |
938 tipText = tr("Add a new layer showing a spectrogram set up for an overview of note pitches"); | |
939 } | |
940 break; | |
941 | |
942 case LayerFactory::PeakFrequencySpectrogram: | |
943 icon = QIcon(":/icons/spectrogram.png"); | |
944 mainText = tr("Add &Peak Frequency Spectrogram"); | |
945 if (menuType == 0) { | |
946 shortcutText = tr("Alt+P"); | |
947 tipText = tr("Add a new pane showing a spectrogram set up for tracking frequencies"); | |
948 } else { | |
949 tipText = tr("Add a new layer showing a spectrogram set up for tracking frequencies"); | |
950 } | |
951 break; | |
952 | |
953 case LayerFactory::Spectrum: | |
954 icon = QIcon(":/icons/spectrum.png"); | |
955 mainText = tr("Add Spectr&um"); | |
956 if (menuType == 0) { | |
957 shortcutText = tr("Alt+U"); | |
958 tipText = tr("Add a new pane showing a frequency spectrum"); | |
959 } else { | |
960 tipText = tr("Add a new layer showing a frequency spectrum"); | |
961 } | |
962 break; | |
963 | |
964 default: break; | |
965 } | |
966 | |
967 std::vector<Model *> candidateModels; | |
968 if (menuType == 0) { | |
969 candidateModels = models; | |
970 } else { | |
971 candidateModels.push_back(0); | |
972 } | |
973 | |
974 for (std::vector<Model *>::iterator mi = | |
975 candidateModels.begin(); | |
976 mi != candidateModels.end(); ++mi) { | |
977 | |
978 Model *model = *mi; | |
979 | |
980 int channels = 0; | |
981 if (model) { | |
982 DenseTimeValueModel *dtvm = | |
983 dynamic_cast<DenseTimeValueModel *>(model); | |
984 if (dtvm) channels = dtvm->getChannelCount(); | |
985 } | |
986 if (channels < 1 && getMainModel()) { | |
987 channels = getMainModel()->getChannelCount(); | |
988 } | |
989 if (channels < 1) channels = 1; | |
990 | |
991 for (int c = 0; c <= channels; ++c) { | |
992 | |
993 if (c == 1 && channels == 1) continue; | |
994 bool isDefault = (c == 0); | |
995 bool isOnly = (isDefault && (channels == 1)); | |
996 | |
997 if (menuType == 1) { | |
998 if (isDefault) isOnly = true; | |
999 else continue; | |
1000 } | |
1001 | |
1002 if (isOnly && (!plural || menuType == 1)) { | |
1003 | |
1004 if (menuType == 1 && type != LayerFactory::Waveform) { | |
1005 action = new QAction(mainText, this); | |
1006 } else { | |
1007 action = new QAction(icon, mainText, this); | |
1008 } | |
1009 | |
1010 action->setShortcut(shortcutText); | |
1011 action->setStatusTip(tipText); | |
1012 if (menuType == 0) { | |
1013 connect(action, SIGNAL(triggered()), | |
1014 this, SLOT(addPane())); | |
1015 connect(this, SIGNAL(canAddPane(bool)), | |
1016 action, SLOT(setEnabled(bool))); | |
1017 m_paneActions[action] = PaneConfiguration(type); | |
1018 } else { | |
1019 connect(action, SIGNAL(triggered()), | |
1020 this, SLOT(addLayer())); | |
1021 connect(this, SIGNAL(canAddLayer(bool)), | |
1022 action, SLOT(setEnabled(bool))); | |
1023 m_layerActions[action] = type; | |
1024 } | |
1025 menu->addAction(action); | |
1026 | |
1027 } else { | |
1028 | |
1029 if (!submenu) { | |
1030 submenu = menu->addMenu(mainText); | |
1031 submenu->setTearOffEnabled(true); | |
1032 } else if (isDefault) { | |
1033 submenu->addSeparator(); | |
1034 } | |
1035 | |
1036 QString actionText; | |
1037 if (c == 0) { | |
1038 if (mono) { | |
1039 actionText = tr("&All Channels Mixed"); | |
1040 } else { | |
1041 actionText = tr("&All Channels"); | |
1042 } | |
1043 } else { | |
1044 actionText = tr("Channel &%1").arg(c); | |
1045 } | |
1046 | |
1047 if (model) { | |
1048 actionText = tr("%1: %2") | |
1049 .arg(model->objectName()) | |
1050 .arg(actionText); | |
1051 } | |
1052 | |
1053 if (isDefault) { | |
1054 action = new QAction(icon, actionText, this); | |
1055 if (!model || model == getMainModel()) { | |
1056 action->setShortcut(shortcutText); | |
1057 } | |
1058 } else { | |
1059 action = new QAction(actionText, this); | |
1060 } | |
1061 | |
1062 action->setStatusTip(tipText); | |
1063 | |
1064 if (menuType == 0) { | |
1065 connect(action, SIGNAL(triggered()), | |
1066 this, SLOT(addPane())); | |
1067 connect(this, SIGNAL(canAddPane(bool)), | |
1068 action, SLOT(setEnabled(bool))); | |
1069 m_paneActions[action] = | |
1070 PaneConfiguration(type, model, c - 1); | |
1071 } else { | |
1072 connect(action, SIGNAL(triggered()), | |
1073 this, SLOT(addLayer())); | |
1074 connect(this, SIGNAL(canAddLayer(bool)), | |
1075 action, SLOT(setEnabled(bool))); | |
1076 m_layerActions[action] = type; | |
1077 } | |
1078 | |
1079 submenu->addAction(action); | |
1080 } | |
1081 } | |
1082 } | |
1083 } | |
1084 } | |
1085 | |
1086 menu = m_paneMenu; | |
1087 | |
1088 menu->addSeparator(); | |
1089 | |
1090 action = new QAction(QIcon(":/icons/editdelete.png"), tr("&Delete Pane"), this); | |
1091 action->setShortcut(tr("Alt+D")); | |
1092 action->setStatusTip(tr("Delete the currently active pane")); | |
1093 connect(action, SIGNAL(triggered()), this, SLOT(deleteCurrentPane())); | |
1094 connect(this, SIGNAL(canDeleteCurrentPane(bool)), action, SLOT(setEnabled(bool))); | |
1095 menu->addAction(action); | |
1096 | |
1097 menu = m_layerMenu; | |
1098 | |
1099 action = new QAction(QIcon(":/icons/timeruler.png"), tr("Add &Time Ruler"), this); | |
1100 action->setStatusTip(tr("Add a new layer showing a time ruler")); | |
1101 connect(action, SIGNAL(triggered()), this, SLOT(addLayer())); | |
1102 connect(this, SIGNAL(canAddLayer(bool)), action, SLOT(setEnabled(bool))); | |
1103 m_layerActions[action] = LayerFactory::TimeRuler; | |
1104 menu->addAction(action); | |
1105 | |
1106 menu->addSeparator(); | |
1107 | |
1108 m_existingLayersMenu = menu->addMenu(tr("Add &Existing Layer")); | |
1109 m_existingLayersMenu->setTearOffEnabled(true); | |
1110 m_rightButtonLayerMenu->addMenu(m_existingLayersMenu); | |
1111 | |
1112 m_sliceMenu = menu->addMenu(tr("Add S&lice of Layer")); | |
1113 m_sliceMenu->setTearOffEnabled(true); | |
1114 m_rightButtonLayerMenu->addMenu(m_sliceMenu); | |
1115 | |
1116 setupExistingLayersMenus(); | |
1117 | |
1118 m_rightButtonLayerMenu->addSeparator(); | |
1119 menu->addSeparator(); | |
1120 | |
1121 action = new QAction(tr("&Rename Layer..."), this); | |
1122 action->setShortcut(tr("Alt+R")); | |
1123 action->setStatusTip(tr("Rename the currently active layer")); | |
1124 connect(action, SIGNAL(triggered()), this, SLOT(renameCurrentLayer())); | |
1125 connect(this, SIGNAL(canRenameLayer(bool)), action, SLOT(setEnabled(bool))); | |
1126 menu->addAction(action); | |
1127 m_rightButtonLayerMenu->addAction(action); | |
1128 | |
1129 action = new QAction(QIcon(":/icons/editdelete.png"), tr("&Delete Layer"), this); | |
1130 action->setShortcut(tr("Alt+Shift+D")); | |
1131 action->setStatusTip(tr("Delete the currently active layer")); | |
1132 connect(action, SIGNAL(triggered()), this, SLOT(deleteCurrentLayer())); | |
1133 connect(this, SIGNAL(canDeleteCurrentLayer(bool)), action, SLOT(setEnabled(bool))); | |
1134 menu->addAction(action); | |
1135 m_rightButtonLayerMenu->addAction(action); | |
1136 } | |
1137 | |
1138 void | |
1139 MainWindow::setupTransformsMenu() | |
1140 { | |
1141 if (m_transformsMenu) { | |
1142 m_transformActions.clear(); | |
1143 m_transformActionsReverse.clear(); | |
1144 m_transformsMenu->clear(); | |
1145 } else { | |
1146 m_transformsMenu = menuBar()->addMenu(tr("&Transform")); | |
1147 m_transformsMenu->setTearOffEnabled(true); | |
1148 } | |
1149 | |
1150 TransformFactory::TransformList transforms = | |
1151 TransformFactory::getInstance()->getAllTransforms(); | |
1152 | |
1153 vector<QString> types = | |
1154 TransformFactory::getInstance()->getAllTransformTypes(); | |
1155 | |
1156 map<QString, map<QString, SubdividingMenu *> > categoryMenus; | |
1157 map<QString, map<QString, SubdividingMenu *> > makerMenus; | |
1158 | |
1159 map<QString, SubdividingMenu *> byPluginNameMenus; | |
1160 map<QString, map<QString, QMenu *> > pluginNameMenus; | |
1161 | |
1162 set<SubdividingMenu *> pendingMenus; | |
1163 | |
1164 m_recentTransformsMenu = m_transformsMenu->addMenu(tr("&Recent Transforms")); | |
1165 m_recentTransformsMenu->setTearOffEnabled(true); | |
1166 m_rightButtonTransformsMenu->addMenu(m_recentTransformsMenu); | |
1167 connect(&m_recentTransforms, SIGNAL(recentChanged()), | |
1168 this, SLOT(setupRecentTransformsMenu())); | |
1169 | |
1170 m_transformsMenu->addSeparator(); | |
1171 m_rightButtonTransformsMenu->addSeparator(); | |
1172 | |
1173 for (vector<QString>::iterator i = types.begin(); i != types.end(); ++i) { | |
1174 | |
1175 if (i != types.begin()) { | |
1176 m_transformsMenu->addSeparator(); | |
1177 m_rightButtonTransformsMenu->addSeparator(); | |
1178 } | |
1179 | |
1180 QString byCategoryLabel = tr("%1 by Category").arg(*i); | |
1181 SubdividingMenu *byCategoryMenu = new SubdividingMenu(byCategoryLabel, | |
1182 20, 40); | |
1183 byCategoryMenu->setTearOffEnabled(true); | |
1184 m_transformsMenu->addMenu(byCategoryMenu); | |
1185 m_rightButtonTransformsMenu->addMenu(byCategoryMenu); | |
1186 pendingMenus.insert(byCategoryMenu); | |
1187 | |
1188 vector<QString> categories = | |
1189 TransformFactory::getInstance()->getTransformCategories(*i); | |
1190 | |
1191 for (vector<QString>::iterator j = categories.begin(); | |
1192 j != categories.end(); ++j) { | |
1193 | |
1194 QString category = *j; | |
1195 if (category == "") category = tr("Unclassified"); | |
1196 | |
1197 if (categories.size() < 2) { | |
1198 categoryMenus[*i][category] = byCategoryMenu; | |
1199 continue; | |
1200 } | |
1201 | |
1202 QStringList components = category.split(" > "); | |
1203 QString key; | |
1204 | |
1205 for (QStringList::iterator k = components.begin(); | |
1206 k != components.end(); ++k) { | |
1207 | |
1208 QString parentKey = key; | |
1209 if (key != "") key += " > "; | |
1210 key += *k; | |
1211 | |
1212 if (categoryMenus[*i].find(key) == categoryMenus[*i].end()) { | |
1213 SubdividingMenu *m = new SubdividingMenu(*k, 20, 40); | |
1214 m->setTearOffEnabled(true); | |
1215 pendingMenus.insert(m); | |
1216 categoryMenus[*i][key] = m; | |
1217 if (parentKey == "") { | |
1218 byCategoryMenu->addMenu(m); | |
1219 } else { | |
1220 categoryMenus[*i][parentKey]->addMenu(m); | |
1221 } | |
1222 } | |
1223 } | |
1224 } | |
1225 | |
1226 QString byPluginNameLabel = tr("%1 by Plugin Name").arg(*i); | |
1227 byPluginNameMenus[*i] = new SubdividingMenu(byPluginNameLabel); | |
1228 byPluginNameMenus[*i]->setTearOffEnabled(true); | |
1229 m_transformsMenu->addMenu(byPluginNameMenus[*i]); | |
1230 m_rightButtonTransformsMenu->addMenu(byPluginNameMenus[*i]); | |
1231 pendingMenus.insert(byPluginNameMenus[*i]); | |
1232 | |
1233 QString byMakerLabel = tr("%1 by Maker").arg(*i); | |
1234 SubdividingMenu *byMakerMenu = new SubdividingMenu(byMakerLabel, 20, 40); | |
1235 byMakerMenu->setTearOffEnabled(true); | |
1236 m_transformsMenu->addMenu(byMakerMenu); | |
1237 m_rightButtonTransformsMenu->addMenu(byMakerMenu); | |
1238 pendingMenus.insert(byMakerMenu); | |
1239 | |
1240 vector<QString> makers = | |
1241 TransformFactory::getInstance()->getTransformMakers(*i); | |
1242 | |
1243 for (vector<QString>::iterator j = makers.begin(); | |
1244 j != makers.end(); ++j) { | |
1245 | |
1246 QString maker = *j; | |
1247 if (maker == "") maker = tr("Unknown"); | |
1248 maker.replace(QRegExp(tr(" [\\(<].*$")), ""); | |
1249 | |
1250 makerMenus[*i][maker] = new SubdividingMenu(maker, 30, 40); | |
1251 makerMenus[*i][maker]->setTearOffEnabled(true); | |
1252 byMakerMenu->addMenu(makerMenus[*i][maker]); | |
1253 pendingMenus.insert(makerMenus[*i][maker]); | |
1254 } | |
1255 } | |
1256 | |
1257 for (unsigned int i = 0; i < transforms.size(); ++i) { | |
1258 | |
1259 QString name = transforms[i].name; | |
1260 if (name == "") name = transforms[i].identifier; | |
1261 | |
1262 // std::cerr << "Plugin Name: " << name.toStdString() << std::endl; | |
1263 | |
1264 QString type = transforms[i].type; | |
1265 | |
1266 QString category = transforms[i].category; | |
1267 if (category == "") category = tr("Unclassified"); | |
1268 | |
1269 QString maker = transforms[i].maker; | |
1270 if (maker == "") maker = tr("Unknown"); | |
1271 maker.replace(QRegExp(tr(" [\\(<].*$")), ""); | |
1272 | |
1273 QString pluginName = name.section(": ", 0, 0); | |
1274 QString output = name.section(": ", 1); | |
1275 | |
1276 QAction *action = new QAction(tr("%1...").arg(name), this); | |
1277 connect(action, SIGNAL(triggered()), this, SLOT(addLayer())); | |
1278 m_transformActions[action] = transforms[i].identifier; | |
1279 m_transformActionsReverse[transforms[i].identifier] = action; | |
1280 connect(this, SIGNAL(canAddLayer(bool)), action, SLOT(setEnabled(bool))); | |
1281 | |
1282 action->setStatusTip(transforms[i].description); | |
1283 | |
1284 if (categoryMenus[type].find(category) == categoryMenus[type].end()) { | |
1285 std::cerr << "WARNING: MainWindow::setupMenus: Internal error: " | |
1286 << "No category menu for transform \"" | |
1287 << name.toStdString() << "\" (category = \"" | |
1288 << category.toStdString() << "\")" << std::endl; | |
1289 } else { | |
1290 categoryMenus[type][category]->addAction(action); | |
1291 } | |
1292 | |
1293 if (makerMenus[type].find(maker) == makerMenus[type].end()) { | |
1294 std::cerr << "WARNING: MainWindow::setupMenus: Internal error: " | |
1295 << "No maker menu for transform \"" | |
1296 << name.toStdString() << "\" (maker = \"" | |
1297 << maker.toStdString() << "\")" << std::endl; | |
1298 } else { | |
1299 makerMenus[type][maker]->addAction(action); | |
1300 } | |
1301 | |
1302 action = new QAction(tr("%1...").arg(output == "" ? pluginName : output), this); | |
1303 connect(action, SIGNAL(triggered()), this, SLOT(addLayer())); | |
1304 m_transformActions[action] = transforms[i].identifier; | |
1305 connect(this, SIGNAL(canAddLayer(bool)), action, SLOT(setEnabled(bool))); | |
1306 action->setStatusTip(transforms[i].description); | |
1307 | |
1308 // cerr << "Transform: \"" << name.toStdString() << "\": plugin name \"" << pluginName.toStdString() << "\"" << endl; | |
1309 | |
1310 if (pluginNameMenus[type].find(pluginName) == | |
1311 pluginNameMenus[type].end()) { | |
1312 | |
1313 SubdividingMenu *parentMenu = byPluginNameMenus[type]; | |
1314 parentMenu->setTearOffEnabled(true); | |
1315 | |
1316 if (output == "") { | |
1317 parentMenu->addAction(pluginName, action); | |
1318 } else { | |
1319 pluginNameMenus[type][pluginName] = | |
1320 parentMenu->addMenu(pluginName); | |
1321 connect(this, SIGNAL(canAddLayer(bool)), | |
1322 pluginNameMenus[type][pluginName], | |
1323 SLOT(setEnabled(bool))); | |
1324 } | |
1325 } | |
1326 | |
1327 if (pluginNameMenus[type].find(pluginName) != | |
1328 pluginNameMenus[type].end()) { | |
1329 pluginNameMenus[type][pluginName]->addAction(action); | |
1330 } | |
1331 } | |
1332 | |
1333 for (set<SubdividingMenu *>::iterator i = pendingMenus.begin(); | |
1334 i != pendingMenus.end(); ++i) { | |
1335 (*i)->entriesAdded(); | |
1336 } | |
1337 | |
1338 setupRecentTransformsMenu(); | |
1339 } | |
1340 | |
1341 void | |
1342 MainWindow::setupHelpMenu() | |
1343 { | |
1344 if (m_mainMenusCreated) return; | |
1345 | |
1346 QMenu *menu = menuBar()->addMenu(tr("&Help")); | |
1347 menu->setTearOffEnabled(true); | |
1348 | |
1349 QAction *action = new QAction(QIcon(":icons/help.png"), | |
1350 tr("&Help Reference"), this); | |
1351 action->setStatusTip(tr("Open the Sonic Visualiser reference manual")); | |
1352 connect(action, SIGNAL(triggered()), this, SLOT(help())); | |
1353 menu->addAction(action); | |
1354 | |
1355 action = new QAction(tr("Sonic Visualiser on the &Web"), this); | |
1356 action->setStatusTip(tr("Open the Sonic Visualiser website")); | |
1357 connect(action, SIGNAL(triggered()), this, SLOT(website())); | |
1358 menu->addAction(action); | |
1359 | |
1360 action = new QAction(tr("&About Sonic Visualiser"), this); | |
1361 action->setStatusTip(tr("Show information about Sonic Visualiser")); | |
1362 connect(action, SIGNAL(triggered()), this, SLOT(about())); | |
1363 menu->addAction(action); | |
1364 } | |
1365 | |
1366 void | |
1367 MainWindow::setupRecentFilesMenu() | |
1368 { | |
1369 m_recentFilesMenu->clear(); | |
1370 vector<QString> files = m_recentFiles.getRecent(); | |
1371 for (size_t i = 0; i < files.size(); ++i) { | |
1372 QAction *action = new QAction(files[i], this); | |
1373 connect(action, SIGNAL(triggered()), this, SLOT(openRecentFile())); | |
1374 m_recentFilesMenu->addAction(action); | |
1375 } | |
1376 } | |
1377 | |
1378 void | |
1379 MainWindow::setupRecentTransformsMenu() | |
1380 { | |
1381 m_recentTransformsMenu->clear(); | |
1382 vector<QString> transforms = m_recentTransforms.getRecent(); | |
1383 for (size_t i = 0; i < transforms.size(); ++i) { | |
1384 TransformActionReverseMap::iterator ti = | |
1385 m_transformActionsReverse.find(transforms[i]); | |
1386 if (ti == m_transformActionsReverse.end()) { | |
1387 std::cerr << "WARNING: MainWindow::setupRecentTransformsMenu: " | |
1388 << "Unknown transform \"" << transforms[i].toStdString() | |
1389 << "\" in recent transforms list" << std::endl; | |
1390 continue; | |
1391 } | |
1392 m_recentTransformsMenu->addAction(ti->second); | |
1393 } | |
1394 } | |
1395 | |
1396 void | |
1397 MainWindow::setupExistingLayersMenus() | |
1398 { | |
1399 if (!m_existingLayersMenu) return; // should have been created by setupMenus | |
1400 | |
1401 // std::cerr << "MainWindow::setupExistingLayersMenu" << std::endl; | |
1402 | |
1403 m_existingLayersMenu->clear(); | |
1404 m_existingLayerActions.clear(); | |
1405 | |
1406 m_sliceMenu->clear(); | |
1407 m_sliceActions.clear(); | |
1408 | |
1409 vector<Layer *> orderedLayers; | |
1410 set<Layer *> observedLayers; | |
1411 set<Layer *> sliceableLayers; | |
1412 | |
1413 LayerFactory *factory = LayerFactory::getInstance(); | |
1414 | |
1415 for (int i = 0; i < m_paneStack->getPaneCount(); ++i) { | |
1416 | |
1417 Pane *pane = m_paneStack->getPane(i); | |
1418 if (!pane) continue; | |
1419 | |
1420 for (int j = 0; j < pane->getLayerCount(); ++j) { | |
1421 | |
1422 Layer *layer = pane->getLayer(j); | |
1423 if (!layer) continue; | |
1424 if (observedLayers.find(layer) != observedLayers.end()) { | |
1425 // std::cerr << "found duplicate layer " << layer << std::endl; | |
1426 continue; | |
1427 } | |
1428 | |
1429 // std::cerr << "found new layer " << layer << " (name = " | |
1430 // << layer->getLayerPresentationName().toStdString() << ")" << std::endl; | |
1431 | |
1432 orderedLayers.push_back(layer); | |
1433 observedLayers.insert(layer); | |
1434 | |
1435 if (factory->isLayerSliceable(layer)) { | |
1436 sliceableLayers.insert(layer); | |
1437 } | |
1438 } | |
1439 } | |
1440 | |
1441 map<QString, int> observedNames; | |
1442 | |
1443 for (size_t i = 0; i < orderedLayers.size(); ++i) { | |
1444 | |
1445 Layer *layer = orderedLayers[i]; | |
1446 | |
1447 QString name = layer->getLayerPresentationName(); | |
1448 int n = ++observedNames[name]; | |
1449 if (n > 1) name = QString("%1 <%2>").arg(name).arg(n); | |
1450 | |
1451 QIcon icon = QIcon(QString(":/icons/%1.png") | |
1452 .arg(factory->getLayerIconName | |
1453 (factory->getLayerType(layer)))); | |
1454 | |
1455 QAction *action = new QAction(icon, name, this); | |
1456 connect(action, SIGNAL(triggered()), this, SLOT(addLayer())); | |
1457 connect(this, SIGNAL(canAddLayer(bool)), action, SLOT(setEnabled(bool))); | |
1458 m_existingLayerActions[action] = layer; | |
1459 | |
1460 m_existingLayersMenu->addAction(action); | |
1461 | |
1462 if (sliceableLayers.find(layer) != sliceableLayers.end()) { | |
1463 action = new QAction(icon, name, this); | |
1464 connect(action, SIGNAL(triggered()), this, SLOT(addLayer())); | |
1465 connect(this, SIGNAL(canAddLayer(bool)), action, SLOT(setEnabled(bool))); | |
1466 m_sliceActions[action] = layer; | |
1467 m_sliceMenu->addAction(action); | |
1468 } | |
1469 } | |
1470 | |
1471 m_sliceMenu->setEnabled(!m_sliceActions.empty()); | |
1472 } | |
1473 | |
1474 void | |
1475 MainWindow::setupToolbars() | |
1476 { | |
1477 QToolBar *toolbar = addToolBar(tr("Transport Toolbar")); | |
1478 | |
1479 QAction *action = toolbar->addAction(QIcon(":/icons/rewind-start.png"), | |
1480 tr("Rewind to Start")); | |
1481 action->setShortcut(tr("Home")); | |
1482 action->setStatusTip(tr("Rewind to the start")); | |
1483 connect(action, SIGNAL(triggered()), this, SLOT(rewindStart())); | |
1484 connect(this, SIGNAL(canPlay(bool)), action, SLOT(setEnabled(bool))); | |
1485 | |
1486 action = toolbar->addAction(QIcon(":/icons/rewind.png"), | |
1487 tr("Rewind")); | |
1488 action->setShortcut(tr("PageUp")); | |
1489 action->setStatusTip(tr("Rewind to the previous time instant in the current layer")); | |
1490 connect(action, SIGNAL(triggered()), this, SLOT(rewind())); | |
1491 connect(this, SIGNAL(canRewind(bool)), action, SLOT(setEnabled(bool))); | |
1492 | |
1493 action = toolbar->addAction(QIcon(":/icons/playpause.png"), | |
1494 tr("Play / Pause")); | |
1495 action->setCheckable(true); | |
1496 action->setShortcut(tr("Space")); | |
1497 action->setStatusTip(tr("Start or stop playback from the current position")); | |
1498 connect(action, SIGNAL(triggered()), this, SLOT(play())); | |
1499 connect(m_playSource, SIGNAL(playStatusChanged(bool)), | |
1500 action, SLOT(setChecked(bool))); | |
1501 connect(this, SIGNAL(canPlay(bool)), action, SLOT(setEnabled(bool))); | |
1502 | |
1503 action = toolbar->addAction(QIcon(":/icons/ffwd.png"), | |
1504 tr("Fast Forward")); | |
1505 action->setShortcut(tr("PageDown")); | |
1506 action->setStatusTip(tr("Fast forward to the next time instant in the current layer")); | |
1507 connect(action, SIGNAL(triggered()), this, SLOT(ffwd())); | |
1508 connect(this, SIGNAL(canFfwd(bool)), action, SLOT(setEnabled(bool))); | |
1509 | |
1510 action = toolbar->addAction(QIcon(":/icons/ffwd-end.png"), | |
1511 tr("Fast Forward to End")); | |
1512 action->setShortcut(tr("End")); | |
1513 action->setStatusTip(tr("Fast-forward to the end")); | |
1514 connect(action, SIGNAL(triggered()), this, SLOT(ffwdEnd())); | |
1515 connect(this, SIGNAL(canPlay(bool)), action, SLOT(setEnabled(bool))); | |
1516 | |
1517 toolbar = addToolBar(tr("Play Mode Toolbar")); | |
1518 | |
1519 action = toolbar->addAction(QIcon(":/icons/playselection.png"), | |
1520 tr("Constrain Playback to Selection")); | |
1521 action->setCheckable(true); | |
1522 action->setChecked(m_viewManager->getPlaySelectionMode()); | |
1523 action->setShortcut(tr("s")); | |
1524 action->setStatusTip(tr("Constrain playback to the selected area")); | |
1525 connect(m_viewManager, SIGNAL(playSelectionModeChanged(bool)), | |
1526 action, SLOT(setChecked(bool))); | |
1527 connect(action, SIGNAL(triggered()), this, SLOT(playSelectionToggled())); | |
1528 connect(this, SIGNAL(canPlaySelection(bool)), action, SLOT(setEnabled(bool))); | |
1529 | |
1530 action = toolbar->addAction(QIcon(":/icons/playloop.png"), | |
1531 tr("Loop Playback")); | |
1532 action->setCheckable(true); | |
1533 action->setChecked(m_viewManager->getPlayLoopMode()); | |
1534 action->setShortcut(tr("l")); | |
1535 action->setStatusTip(tr("Loop playback")); | |
1536 connect(m_viewManager, SIGNAL(playLoopModeChanged(bool)), | |
1537 action, SLOT(setChecked(bool))); | |
1538 connect(action, SIGNAL(triggered()), this, SLOT(playLoopToggled())); | |
1539 connect(this, SIGNAL(canPlay(bool)), action, SLOT(setEnabled(bool))); | |
1540 | |
1541 toolbar = addToolBar(tr("Edit Toolbar")); | |
1542 CommandHistory::getInstance()->registerToolbar(toolbar); | |
1543 | |
1544 toolbar = addToolBar(tr("Tools Toolbar")); | |
1545 QActionGroup *group = new QActionGroup(this); | |
1546 | |
1547 action = toolbar->addAction(QIcon(":/icons/navigate.png"), | |
1548 tr("Navigate")); | |
1549 action->setCheckable(true); | |
1550 action->setChecked(true); | |
1551 action->setShortcut(tr("1")); | |
1552 action->setStatusTip(tr("Navigate")); | |
1553 connect(action, SIGNAL(triggered()), this, SLOT(toolNavigateSelected())); | |
1554 group->addAction(action); | |
1555 m_toolActions[ViewManager::NavigateMode] = action; | |
1556 | |
1557 action = toolbar->addAction(QIcon(":/icons/select.png"), | |
1558 tr("Select")); | |
1559 action->setCheckable(true); | |
1560 action->setShortcut(tr("2")); | |
1561 action->setStatusTip(tr("Select ranges")); | |
1562 connect(action, SIGNAL(triggered()), this, SLOT(toolSelectSelected())); | |
1563 group->addAction(action); | |
1564 m_toolActions[ViewManager::SelectMode] = action; | |
1565 | |
1566 action = toolbar->addAction(QIcon(":/icons/move.png"), | |
1567 tr("Edit")); | |
1568 action->setCheckable(true); | |
1569 action->setShortcut(tr("3")); | |
1570 action->setStatusTip(tr("Edit items in layer")); | |
1571 connect(action, SIGNAL(triggered()), this, SLOT(toolEditSelected())); | |
1572 connect(this, SIGNAL(canEditLayer(bool)), action, SLOT(setEnabled(bool))); | |
1573 group->addAction(action); | |
1574 m_toolActions[ViewManager::EditMode] = action; | |
1575 | |
1576 action = toolbar->addAction(QIcon(":/icons/draw.png"), | |
1577 tr("Draw")); | |
1578 action->setCheckable(true); | |
1579 action->setShortcut(tr("4")); | |
1580 action->setStatusTip(tr("Draw new items in layer")); | |
1581 connect(action, SIGNAL(triggered()), this, SLOT(toolDrawSelected())); | |
1582 connect(this, SIGNAL(canEditLayer(bool)), action, SLOT(setEnabled(bool))); | |
1583 group->addAction(action); | |
1584 m_toolActions[ViewManager::DrawMode] = action; | |
1585 | |
1586 // action = toolbar->addAction(QIcon(":/icons/text.png"), | |
1587 // tr("Text")); | |
1588 // action->setCheckable(true); | |
1589 // action->setShortcut(tr("5")); | |
1590 // connect(action, SIGNAL(triggered()), this, SLOT(toolTextSelected())); | |
1591 // group->addAction(action); | |
1592 // m_toolActions[ViewManager::TextMode] = action; | |
1593 | |
1594 toolNavigateSelected(); | |
1595 } | |
1596 | |
1597 void | |
1598 MainWindow::updateMenuStates() | |
1599 { | |
1600 Pane *currentPane = 0; | |
1601 Layer *currentLayer = 0; | |
1602 | |
1603 if (m_paneStack) currentPane = m_paneStack->getCurrentPane(); | |
1604 if (currentPane) currentLayer = currentPane->getSelectedLayer(); | |
1605 | |
1606 bool haveCurrentPane = | |
1607 (currentPane != 0); | |
1608 bool haveCurrentLayer = | |
1609 (haveCurrentPane && | |
1610 (currentLayer != 0)); | |
1611 bool haveMainModel = | |
1612 (getMainModel() != 0); | |
1613 bool havePlayTarget = | |
1614 (m_playTarget != 0); | |
1615 bool haveSelection = | |
1616 (m_viewManager && | |
1617 !m_viewManager->getSelections().empty()); | |
1618 bool haveCurrentEditableLayer = | |
1619 (haveCurrentLayer && | |
1620 currentLayer->isLayerEditable()); | |
1621 bool haveCurrentTimeInstantsLayer = | |
1622 (haveCurrentLayer && | |
1623 dynamic_cast<TimeInstantLayer *>(currentLayer)); | |
1624 bool haveCurrentTimeValueLayer = | |
1625 (haveCurrentLayer && | |
1626 dynamic_cast<TimeValueLayer *>(currentLayer)); | |
1627 bool haveCurrentColour3DPlot = | |
1628 (haveCurrentLayer && | |
1629 dynamic_cast<Colour3DPlotLayer *>(currentLayer)); | |
1630 bool haveClipboardContents = | |
1631 (m_viewManager && | |
1632 !m_viewManager->getClipboard().empty()); | |
1633 | |
1634 emit canAddPane(haveMainModel); | |
1635 emit canDeleteCurrentPane(haveCurrentPane); | |
1636 emit canZoom(haveMainModel && haveCurrentPane); | |
1637 emit canScroll(haveMainModel && haveCurrentPane); | |
1638 emit canAddLayer(haveMainModel && haveCurrentPane); | |
1639 emit canImportMoreAudio(haveMainModel); | |
1640 emit canImportLayer(haveMainModel && haveCurrentPane); | |
1641 emit canExportAudio(haveMainModel); | |
1642 emit canExportLayer(haveMainModel && | |
1643 (haveCurrentEditableLayer || haveCurrentColour3DPlot)); | |
1644 emit canExportImage(haveMainModel && haveCurrentPane); | |
1645 emit canDeleteCurrentLayer(haveCurrentLayer); | |
1646 emit canRenameLayer(haveCurrentLayer); | |
1647 emit canEditLayer(haveCurrentEditableLayer); | |
1648 emit canSelect(haveMainModel && haveCurrentPane); | |
1649 emit canPlay(/*!!! haveMainModel && */ havePlayTarget); | |
1650 emit canFfwd(haveCurrentTimeInstantsLayer || haveCurrentTimeValueLayer); | |
1651 emit canRewind(haveCurrentTimeInstantsLayer || haveCurrentTimeValueLayer); | |
1652 emit canPaste(haveCurrentEditableLayer && haveClipboardContents); | |
1653 emit canInsertInstant(haveCurrentPane); | |
1654 emit canInsertInstantsAtBoundaries(haveCurrentPane && haveSelection); | |
1655 emit canPlaySelection(haveMainModel && havePlayTarget && haveSelection); | |
1656 emit canClearSelection(haveSelection); | |
1657 emit canEditSelection(haveSelection && haveCurrentEditableLayer); | |
1658 emit canSave(m_sessionFile != "" && m_documentModified); | |
1659 } | |
1660 | |
1661 void | |
1662 MainWindow::updateDescriptionLabel() | |
1663 { | |
1664 if (!getMainModel()) { | |
1665 m_descriptionLabel->setText(tr("No audio file loaded.")); | |
1666 return; | |
1667 } | |
1668 | |
1669 QString description; | |
1670 | |
1671 size_t ssr = getMainModel()->getSampleRate(); | |
1672 size_t tsr = ssr; | |
1673 if (m_playSource) tsr = m_playSource->getTargetSampleRate(); | |
1674 | |
1675 if (ssr != tsr) { | |
1676 description = tr("%1Hz (resampling to %2Hz)").arg(ssr).arg(tsr); | |
1677 } else { | |
1678 description = QString("%1Hz").arg(ssr); | |
1679 } | |
1680 | |
1681 description = QString("%1 - %2") | |
1682 .arg(RealTime::frame2RealTime(getMainModel()->getEndFrame(), ssr) | |
1683 .toText(false).c_str()) | |
1684 .arg(description); | |
1685 | |
1686 m_descriptionLabel->setText(description); | |
1687 } | |
1688 | |
1689 void | |
1690 MainWindow::documentModified() | |
1691 { | |
1692 // std::cerr << "MainWindow::documentModified" << std::endl; | |
1693 | |
1694 if (!m_documentModified) { | |
1695 setWindowTitle(tr("%1 (modified)").arg(windowTitle())); | |
1696 } | |
1697 | |
1698 m_documentModified = true; | |
1699 updateMenuStates(); | |
1700 } | |
1701 | |
1702 void | |
1703 MainWindow::documentRestored() | |
1704 { | |
1705 // std::cerr << "MainWindow::documentRestored" << std::endl; | |
1706 | |
1707 if (m_documentModified) { | |
1708 QString wt(windowTitle()); | |
1709 wt.replace(tr(" (modified)"), ""); | |
1710 setWindowTitle(wt); | |
1711 } | |
1712 | |
1713 m_documentModified = false; | |
1714 updateMenuStates(); | |
1715 } | |
1716 | |
1717 void | |
1718 MainWindow::playLoopToggled() | |
1719 { | |
1720 QAction *action = dynamic_cast<QAction *>(sender()); | |
1721 | |
1722 if (action) { | |
1723 m_viewManager->setPlayLoopMode(action->isChecked()); | |
1724 } else { | |
1725 m_viewManager->setPlayLoopMode(!m_viewManager->getPlayLoopMode()); | |
1726 } | |
1727 } | |
1728 | |
1729 void | |
1730 MainWindow::playSelectionToggled() | |
1731 { | |
1732 QAction *action = dynamic_cast<QAction *>(sender()); | |
1733 | |
1734 if (action) { | |
1735 m_viewManager->setPlaySelectionMode(action->isChecked()); | |
1736 } else { | |
1737 m_viewManager->setPlaySelectionMode(!m_viewManager->getPlaySelectionMode()); | |
1738 } | |
1739 } | |
1740 | |
1741 void | |
1742 MainWindow::currentPaneChanged(Pane *p) | |
1743 { | |
1744 updateMenuStates(); | |
1745 updateVisibleRangeDisplay(p); | |
1746 } | |
1747 | |
1748 void | |
1749 MainWindow::currentLayerChanged(Pane *p, Layer *) | |
1750 { | |
1751 updateMenuStates(); | |
1752 updateVisibleRangeDisplay(p); | |
1753 } | |
1754 | |
1755 void | |
1756 MainWindow::toolNavigateSelected() | |
1757 { | |
1758 m_viewManager->setToolMode(ViewManager::NavigateMode); | |
1759 } | |
1760 | |
1761 void | |
1762 MainWindow::toolSelectSelected() | |
1763 { | |
1764 m_viewManager->setToolMode(ViewManager::SelectMode); | |
1765 } | |
1766 | |
1767 void | |
1768 MainWindow::toolEditSelected() | |
1769 { | |
1770 m_viewManager->setToolMode(ViewManager::EditMode); | |
1771 } | |
1772 | |
1773 void | |
1774 MainWindow::toolDrawSelected() | |
1775 { | |
1776 m_viewManager->setToolMode(ViewManager::DrawMode); | |
1777 } | |
1778 | |
1779 //void | |
1780 //MainWindow::toolTextSelected() | |
1781 //{ | |
1782 // m_viewManager->setToolMode(ViewManager::TextMode); | |
1783 //} | |
1784 | |
1785 void | |
1786 MainWindow::selectAll() | |
1787 { | |
1788 if (!getMainModel()) return; | |
1789 m_viewManager->setSelection(Selection(getMainModel()->getStartFrame(), | |
1790 getMainModel()->getEndFrame())); | |
1791 } | |
1792 | |
1793 void | |
1794 MainWindow::selectToStart() | |
1795 { | |
1796 if (!getMainModel()) return; | |
1797 m_viewManager->setSelection(Selection(getMainModel()->getStartFrame(), | |
1798 m_viewManager->getGlobalCentreFrame())); | |
1799 } | |
1800 | |
1801 void | |
1802 MainWindow::selectToEnd() | |
1803 { | |
1804 if (!getMainModel()) return; | |
1805 m_viewManager->setSelection(Selection(m_viewManager->getGlobalCentreFrame(), | |
1806 getMainModel()->getEndFrame())); | |
1807 } | |
1808 | |
1809 void | |
1810 MainWindow::selectVisible() | |
1811 { | |
1812 Model *model = getMainModel(); | |
1813 if (!model) return; | |
1814 | |
1815 Pane *currentPane = m_paneStack->getCurrentPane(); | |
1816 if (!currentPane) return; | |
1817 | |
1818 size_t startFrame, endFrame; | |
1819 | |
1820 if (currentPane->getStartFrame() < 0) startFrame = 0; | |
1821 else startFrame = currentPane->getStartFrame(); | |
1822 | |
1823 if (currentPane->getEndFrame() > model->getEndFrame()) endFrame = model->getEndFrame(); | |
1824 else endFrame = currentPane->getEndFrame(); | |
1825 | |
1826 m_viewManager->setSelection(Selection(startFrame, endFrame)); | |
1827 } | |
1828 | |
1829 void | |
1830 MainWindow::clearSelection() | |
1831 { | |
1832 m_viewManager->clearSelections(); | |
1833 } | |
1834 | |
1835 void | |
1836 MainWindow::cut() | |
1837 { | |
1838 Pane *currentPane = m_paneStack->getCurrentPane(); | |
1839 if (!currentPane) return; | |
1840 | |
1841 Layer *layer = currentPane->getSelectedLayer(); | |
1842 if (!layer) return; | |
1843 | |
1844 Clipboard &clipboard = m_viewManager->getClipboard(); | |
1845 clipboard.clear(); | |
1846 | |
1847 MultiSelection::SelectionList selections = m_viewManager->getSelections(); | |
1848 | |
1849 CommandHistory::getInstance()->startCompoundOperation(tr("Cut"), true); | |
1850 | |
1851 for (MultiSelection::SelectionList::iterator i = selections.begin(); | |
1852 i != selections.end(); ++i) { | |
1853 layer->copy(*i, clipboard); | |
1854 layer->deleteSelection(*i); | |
1855 } | |
1856 | |
1857 CommandHistory::getInstance()->endCompoundOperation(); | |
1858 } | |
1859 | |
1860 void | |
1861 MainWindow::copy() | |
1862 { | |
1863 Pane *currentPane = m_paneStack->getCurrentPane(); | |
1864 if (!currentPane) return; | |
1865 | |
1866 Layer *layer = currentPane->getSelectedLayer(); | |
1867 if (!layer) return; | |
1868 | |
1869 Clipboard &clipboard = m_viewManager->getClipboard(); | |
1870 clipboard.clear(); | |
1871 | |
1872 MultiSelection::SelectionList selections = m_viewManager->getSelections(); | |
1873 | |
1874 for (MultiSelection::SelectionList::iterator i = selections.begin(); | |
1875 i != selections.end(); ++i) { | |
1876 layer->copy(*i, clipboard); | |
1877 } | |
1878 } | |
1879 | |
1880 void | |
1881 MainWindow::paste() | |
1882 { | |
1883 Pane *currentPane = m_paneStack->getCurrentPane(); | |
1884 if (!currentPane) return; | |
1885 | |
1886 //!!! if we have no current layer, we should create one of the most | |
1887 // appropriate type | |
1888 | |
1889 Layer *layer = currentPane->getSelectedLayer(); | |
1890 if (!layer) return; | |
1891 | |
1892 Clipboard &clipboard = m_viewManager->getClipboard(); | |
1893 Clipboard::PointList contents = clipboard.getPoints(); | |
1894 /* | |
1895 long minFrame = 0; | |
1896 bool have = false; | |
1897 for (int i = 0; i < contents.size(); ++i) { | |
1898 if (!contents[i].haveFrame()) continue; | |
1899 if (!have || contents[i].getFrame() < minFrame) { | |
1900 minFrame = contents[i].getFrame(); | |
1901 have = true; | |
1902 } | |
1903 } | |
1904 | |
1905 long frameOffset = long(m_viewManager->getGlobalCentreFrame()) - minFrame; | |
1906 | |
1907 layer->paste(clipboard, frameOffset); | |
1908 */ | |
1909 layer->paste(clipboard, 0, true); | |
1910 } | |
1911 | |
1912 void | |
1913 MainWindow::deleteSelected() | |
1914 { | |
1915 if (m_paneStack->getCurrentPane() && | |
1916 m_paneStack->getCurrentPane()->getSelectedLayer()) { | |
1917 | |
1918 MultiSelection::SelectionList selections = | |
1919 m_viewManager->getSelections(); | |
1920 | |
1921 for (MultiSelection::SelectionList::iterator i = selections.begin(); | |
1922 i != selections.end(); ++i) { | |
1923 | |
1924 m_paneStack->getCurrentPane()->getSelectedLayer()->deleteSelection(*i); | |
1925 } | |
1926 } | |
1927 } | |
1928 | |
1929 void | |
1930 MainWindow::insertInstant() | |
1931 { | |
1932 int frame = m_viewManager->getPlaybackFrame(); | |
1933 insertInstantAt(frame); | |
1934 } | |
1935 | |
1936 void | |
1937 MainWindow::insertInstantsAtBoundaries() | |
1938 { | |
1939 MultiSelection::SelectionList selections = m_viewManager->getSelections(); | |
1940 for (MultiSelection::SelectionList::iterator i = selections.begin(); | |
1941 i != selections.end(); ++i) { | |
1942 size_t start = i->getStartFrame(); | |
1943 size_t end = i->getEndFrame(); | |
1944 if (start != end) { | |
1945 insertInstantAt(i->getStartFrame()); | |
1946 insertInstantAt(i->getEndFrame()); | |
1947 } | |
1948 } | |
1949 } | |
1950 | |
1951 void | |
1952 MainWindow::insertInstantAt(size_t frame) | |
1953 { | |
1954 Pane *pane = m_paneStack->getCurrentPane(); | |
1955 if (!pane) { | |
1956 return; | |
1957 } | |
1958 | |
1959 Layer *layer = dynamic_cast<TimeInstantLayer *> | |
1960 (pane->getSelectedLayer()); | |
1961 | |
1962 if (!layer) { | |
1963 for (int i = pane->getLayerCount(); i > 0; --i) { | |
1964 layer = dynamic_cast<TimeInstantLayer *>(pane->getLayer(i - 1)); | |
1965 if (layer) break; | |
1966 } | |
1967 | |
1968 if (!layer) { | |
1969 CommandHistory::getInstance()->startCompoundOperation | |
1970 (tr("Add Point"), true); | |
1971 layer = m_document->createEmptyLayer(LayerFactory::TimeInstants); | |
1972 if (layer) { | |
1973 m_document->addLayerToView(pane, layer); | |
1974 m_paneStack->setCurrentLayer(pane, layer); | |
1975 } | |
1976 CommandHistory::getInstance()->endCompoundOperation(); | |
1977 } | |
1978 } | |
1979 | |
1980 if (layer) { | |
1981 | |
1982 Model *model = layer->getModel(); | |
1983 SparseOneDimensionalModel *sodm = dynamic_cast<SparseOneDimensionalModel *> | |
1984 (model); | |
1985 | |
1986 if (sodm) { | |
1987 SparseOneDimensionalModel::Point point | |
1988 (frame, QString("%1").arg(sodm->getPointCount() + 1)); | |
1989 CommandHistory::getInstance()->addCommand | |
1990 (new SparseOneDimensionalModel::AddPointCommand(sodm, point, | |
1991 tr("Add Points")), | |
1992 true, true); // bundled | |
1993 } | |
1994 } | |
1995 } | |
1996 | |
1997 void | |
1998 MainWindow::importAudio() | |
1999 { | |
2000 QString path = getOpenFileName(FileFinder::AudioFile); | |
2001 | |
2002 if (path != "") { | |
2003 if (openAudioFile(path, ReplaceMainModel) == FileOpenFailed) { | |
2004 QMessageBox::critical(this, tr("Failed to open file"), | |
2005 tr("Audio file \"%1\" could not be opened").arg(path)); | |
2006 } | |
2007 } | |
2008 } | |
2009 | |
2010 void | |
2011 MainWindow::importMoreAudio() | |
2012 { | |
2013 QString path = getOpenFileName(FileFinder::AudioFile); | |
2014 | |
2015 if (path != "") { | |
2016 if (openAudioFile(path, CreateAdditionalModel) == FileOpenFailed) { | |
2017 QMessageBox::critical(this, tr("Failed to open file"), | |
2018 tr("Audio file \"%1\" could not be opened").arg(path)); | |
2019 } | |
2020 } | |
2021 } | |
2022 | |
2023 void | |
2024 MainWindow::exportAudio() | |
2025 { | |
2026 if (!getMainModel()) return; | |
2027 | |
2028 QString path = getSaveFileName(FileFinder::AudioFile); | |
2029 | |
2030 if (path == "") return; | |
2031 | |
2032 bool ok = false; | |
2033 QString error; | |
2034 | |
2035 MultiSelection ms = m_viewManager->getSelection(); | |
2036 MultiSelection::SelectionList selections = m_viewManager->getSelections(); | |
2037 | |
2038 bool multiple = false; | |
2039 | |
2040 MultiSelection *selectionToWrite = 0; | |
2041 | |
2042 if (selections.size() == 1) { | |
2043 | |
2044 QStringList items; | |
2045 items << tr("Export the selected region only") | |
2046 << tr("Export the whole audio file"); | |
2047 | |
2048 bool ok = false; | |
2049 QString item = ListInputDialog::getItem | |
2050 (this, tr("Select region to export"), | |
2051 tr("Which region from the original audio file do you want to export?"), | |
2052 items, 0, &ok); | |
2053 | |
2054 if (!ok || item.isEmpty()) return; | |
2055 | |
2056 if (item == items[0]) selectionToWrite = &ms; | |
2057 | |
2058 } else if (selections.size() > 1) { | |
2059 | |
2060 QStringList items; | |
2061 items << tr("Export the selected regions into a single audio file") | |
2062 << tr("Export the selected regions into separate files") | |
2063 << tr("Export the whole audio file"); | |
2064 | |
2065 QString item = ListInputDialog::getItem | |
2066 (this, tr("Select region to export"), | |
2067 tr("Multiple regions of the original audio file are selected.\nWhat do you want to export?"), | |
2068 items, 0, &ok); | |
2069 | |
2070 if (!ok || item.isEmpty()) return; | |
2071 | |
2072 if (item == items[0]) { | |
2073 | |
2074 selectionToWrite = &ms; | |
2075 | |
2076 } else if (item == items[1]) { | |
2077 | |
2078 multiple = true; | |
2079 | |
2080 int n = 1; | |
2081 QString base = path; | |
2082 base.replace(".wav", ""); | |
2083 | |
2084 for (MultiSelection::SelectionList::iterator i = selections.begin(); | |
2085 i != selections.end(); ++i) { | |
2086 | |
2087 MultiSelection subms; | |
2088 subms.setSelection(*i); | |
2089 | |
2090 QString subpath = QString("%1.%2.wav").arg(base).arg(n); | |
2091 ++n; | |
2092 | |
2093 if (QFileInfo(subpath).exists()) { | |
2094 error = tr("Fragment file %1 already exists, aborting").arg(subpath); | |
2095 break; | |
2096 } | |
2097 | |
2098 WavFileWriter subwriter(subpath, | |
2099 getMainModel()->getSampleRate(), | |
2100 getMainModel()->getChannelCount()); | |
2101 subwriter.writeModel(getMainModel(), &subms); | |
2102 ok = subwriter.isOK(); | |
2103 | |
2104 if (!ok) { | |
2105 error = subwriter.getError(); | |
2106 break; | |
2107 } | |
2108 } | |
2109 } | |
2110 } | |
2111 | |
2112 if (!multiple) { | |
2113 WavFileWriter writer(path, | |
2114 getMainModel()->getSampleRate(), | |
2115 getMainModel()->getChannelCount()); | |
2116 writer.writeModel(getMainModel(), selectionToWrite); | |
2117 ok = writer.isOK(); | |
2118 error = writer.getError(); | |
2119 } | |
2120 | |
2121 if (ok) { | |
2122 if (!multiple) { | |
2123 m_recentFiles.addFile(path); | |
2124 } | |
2125 } else { | |
2126 QMessageBox::critical(this, tr("Failed to write file"), error); | |
2127 } | |
2128 } | |
2129 | |
2130 void | |
2131 MainWindow::importLayer() | |
2132 { | |
2133 Pane *pane = m_paneStack->getCurrentPane(); | |
2134 | |
2135 if (!pane) { | |
2136 // shouldn't happen, as the menu action should have been disabled | |
2137 std::cerr << "WARNING: MainWindow::importLayer: no current pane" << std::endl; | |
2138 return; | |
2139 } | |
2140 | |
2141 if (!getMainModel()) { | |
2142 // shouldn't happen, as the menu action should have been disabled | |
2143 std::cerr << "WARNING: MainWindow::importLayer: No main model -- hence no default sample rate available" << std::endl; | |
2144 return; | |
2145 } | |
2146 | |
2147 QString path = getOpenFileName(FileFinder::LayerFile); | |
2148 | |
2149 if (path != "") { | |
2150 | |
2151 if (openLayerFile(path) == FileOpenFailed) { | |
2152 QMessageBox::critical(this, tr("Failed to open file"), | |
2153 tr("File %1 could not be opened.").arg(path)); | |
2154 return; | |
2155 } | |
2156 } | |
2157 } | |
2158 | |
2159 MainWindow::FileOpenStatus | |
2160 MainWindow::openLayerFile(QString path) | |
2161 { | |
2162 return openLayerFile(path, path); | |
2163 } | |
2164 | |
2165 MainWindow::FileOpenStatus | |
2166 MainWindow::openLayerFile(QString path, QString location) | |
2167 { | |
2168 Pane *pane = m_paneStack->getCurrentPane(); | |
2169 | |
2170 if (!pane) { | |
2171 // shouldn't happen, as the menu action should have been disabled | |
2172 std::cerr << "WARNING: MainWindow::openLayerFile: no current pane" << std::endl; | |
2173 return FileOpenFailed; | |
2174 } | |
2175 | |
2176 if (!getMainModel()) { | |
2177 // shouldn't happen, as the menu action should have been disabled | |
2178 std::cerr << "WARNING: MainWindow::openLayerFile: No main model -- hence no default sample rate available" << std::endl; | |
2179 return FileOpenFailed; | |
2180 } | |
2181 | |
2182 bool realFile = (location == path); | |
2183 | |
2184 if (path.endsWith(".svl") || path.endsWith(".xml")) { | |
2185 | |
2186 PaneCallback callback(this); | |
2187 QFile file(path); | |
2188 | |
2189 if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { | |
2190 std::cerr << "ERROR: MainWindow::openLayerFile(" | |
2191 << location.toStdString() | |
2192 << "): Failed to open file for reading" << std::endl; | |
2193 return FileOpenFailed; | |
2194 } | |
2195 | |
2196 SVFileReader reader(m_document, callback, location); | |
2197 reader.setCurrentPane(pane); | |
2198 | |
2199 QXmlInputSource inputSource(&file); | |
2200 reader.parse(inputSource); | |
2201 | |
2202 if (!reader.isOK()) { | |
2203 std::cerr << "ERROR: MainWindow::openLayerFile(" | |
2204 << location.toStdString() | |
2205 << "): Failed to read XML file: " | |
2206 << reader.getErrorString().toStdString() << std::endl; | |
2207 return FileOpenFailed; | |
2208 } | |
2209 | |
2210 m_recentFiles.addFile(location); | |
2211 | |
2212 if (realFile) { | |
2213 registerLastOpenedFilePath(FileFinder::LayerFile, path); // for file dialog | |
2214 } | |
2215 | |
2216 return FileOpenSucceeded; | |
2217 | |
2218 } else { | |
2219 | |
2220 Model *model = DataFileReaderFactory::load(path, getMainModel()->getSampleRate()); | |
2221 | |
2222 if (model) { | |
2223 | |
2224 Layer *newLayer = m_document->createImportedLayer(model); | |
2225 | |
2226 if (newLayer) { | |
2227 | |
2228 m_document->addLayerToView(pane, newLayer); | |
2229 m_recentFiles.addFile(location); | |
2230 | |
2231 if (realFile) { | |
2232 registerLastOpenedFilePath(FileFinder::LayerFile, path); // for file dialog | |
2233 } | |
2234 | |
2235 return FileOpenSucceeded; | |
2236 } | |
2237 } | |
2238 } | |
2239 | |
2240 return FileOpenFailed; | |
2241 } | |
2242 | |
2243 void | |
2244 MainWindow::exportLayer() | |
2245 { | |
2246 Pane *pane = m_paneStack->getCurrentPane(); | |
2247 if (!pane) return; | |
2248 | |
2249 Layer *layer = pane->getSelectedLayer(); | |
2250 if (!layer) return; | |
2251 | |
2252 Model *model = layer->getModel(); | |
2253 if (!model) return; | |
2254 | |
2255 QString path = getSaveFileName(FileFinder::LayerFile); | |
2256 | |
2257 if (path == "") return; | |
2258 | |
2259 if (QFileInfo(path).suffix() == "") path += ".svl"; | |
2260 | |
2261 QString error; | |
2262 | |
2263 if (path.endsWith(".xml") || path.endsWith(".svl")) { | |
2264 | |
2265 QFile file(path); | |
2266 if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { | |
2267 error = tr("Failed to open file %1 for writing").arg(path); | |
2268 } else { | |
2269 QTextStream out(&file); | |
2270 out << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" | |
2271 << "<!DOCTYPE sonic-visualiser>\n" | |
2272 << "<sv>\n" | |
2273 << " <data>\n"; | |
2274 | |
2275 model->toXml(out, " "); | |
2276 | |
2277 out << " </data>\n" | |
2278 << " <display>\n"; | |
2279 | |
2280 layer->toXml(out, " "); | |
2281 | |
2282 out << " </display>\n" | |
2283 << "</sv>\n"; | |
2284 } | |
2285 | |
2286 } else { | |
2287 | |
2288 CSVFileWriter writer(path, model, | |
2289 (path.endsWith(".csv") ? "," : "\t")); | |
2290 writer.write(); | |
2291 | |
2292 if (!writer.isOK()) { | |
2293 error = writer.getError(); | |
2294 } | |
2295 } | |
2296 | |
2297 if (error != "") { | |
2298 QMessageBox::critical(this, tr("Failed to write file"), error); | |
2299 } else { | |
2300 m_recentFiles.addFile(path); | |
2301 } | |
2302 } | |
2303 | |
2304 void | |
2305 MainWindow::exportImage() | |
2306 { | |
2307 Pane *pane = m_paneStack->getCurrentPane(); | |
2308 if (!pane) return; | |
2309 | |
2310 QString path = getSaveFileName(FileFinder::ImageFile); | |
2311 | |
2312 if (path == "") return; | |
2313 | |
2314 if (QFileInfo(path).suffix() == "") path += ".png"; | |
2315 | |
2316 bool haveSelection = m_viewManager && !m_viewManager->getSelections().empty(); | |
2317 | |
2318 QSize total, visible, selected; | |
2319 total = pane->getImageSize(); | |
2320 visible = pane->getImageSize(pane->getFirstVisibleFrame(), | |
2321 pane->getLastVisibleFrame()); | |
2322 | |
2323 size_t sf0 = 0, sf1 = 0; | |
2324 | |
2325 if (haveSelection) { | |
2326 MultiSelection::SelectionList selections = m_viewManager->getSelections(); | |
2327 sf0 = selections.begin()->getStartFrame(); | |
2328 MultiSelection::SelectionList::iterator e = selections.end(); | |
2329 --e; | |
2330 sf1 = e->getEndFrame(); | |
2331 selected = pane->getImageSize(sf0, sf1); | |
2332 } | |
2333 | |
2334 QStringList items; | |
2335 items << tr("Export the whole pane (%1x%2 pixels)") | |
2336 .arg(total.width()).arg(total.height()); | |
2337 items << tr("Export the visible area only (%1x%2 pixels)") | |
2338 .arg(visible.width()).arg(visible.height()); | |
2339 if (haveSelection) { | |
2340 items << tr("Export the selection extent (%1x%2 pixels)") | |
2341 .arg(selected.width()).arg(selected.height()); | |
2342 } else { | |
2343 items << tr("Export the selection extent"); | |
2344 } | |
2345 | |
2346 QSettings settings; | |
2347 settings.beginGroup("MainWindow"); | |
2348 int deflt = settings.value("lastimageexportregion", 0).toInt(); | |
2349 if (deflt == 2 && !haveSelection) deflt = 1; | |
2350 if (deflt == 0 && total.width() > 32767) deflt = 1; | |
2351 | |
2352 ListInputDialog *lid = new ListInputDialog | |
2353 (this, tr("Select region to export"), | |
2354 tr("Which region of the current pane do you want to export as an image?"), | |
2355 items, deflt); | |
2356 | |
2357 if (!haveSelection) { | |
2358 lid->setItemAvailability(2, false); | |
2359 } | |
2360 if (total.width() > 32767) { // appears to be the limit of a QImage | |
2361 lid->setItemAvailability(0, false); | |
2362 lid->setFootnote(tr("Note: the whole pane is too wide to be exported as a single image.")); | |
2363 } | |
2364 | |
2365 bool ok = lid->exec(); | |
2366 QString item = lid->getCurrentString(); | |
2367 delete lid; | |
2368 | |
2369 if (!ok || item.isEmpty()) return; | |
2370 | |
2371 settings.setValue("lastimageexportregion", deflt); | |
2372 | |
2373 QImage *image = 0; | |
2374 | |
2375 if (item == items[0]) { | |
2376 image = pane->toNewImage(); | |
2377 } else if (item == items[1]) { | |
2378 image = pane->toNewImage(pane->getFirstVisibleFrame(), | |
2379 pane->getLastVisibleFrame()); | |
2380 } else if (haveSelection) { | |
2381 image = pane->toNewImage(sf0, sf1); | |
2382 } | |
2383 | |
2384 if (!image) return; | |
2385 | |
2386 if (!image->save(path, "PNG")) { | |
2387 QMessageBox::critical(this, tr("Failed to save image file"), | |
2388 tr("Failed to save image file %1").arg(path)); | |
2389 } | |
2390 | |
2391 delete image; | |
2392 } | |
2393 | |
2394 MainWindow::FileOpenStatus | |
2395 MainWindow::openAudioFile(QString path, AudioFileOpenMode mode) | |
2396 { | |
2397 return openAudioFile(path, path, mode); | |
2398 } | |
2399 | |
2400 MainWindow::FileOpenStatus | |
2401 MainWindow::openAudioFile(QString path, QString location, AudioFileOpenMode mode) | |
2402 { | |
2403 if (!(QFileInfo(path).exists() && | |
2404 QFileInfo(path).isFile() && | |
2405 QFileInfo(path).isReadable())) { | |
2406 return FileOpenFailed; | |
2407 } | |
2408 | |
2409 m_openingAudioFile = true; | |
2410 | |
2411 WaveFileModel *newModel = new WaveFileModel(path, location); | |
2412 | |
2413 if (!newModel->isOK()) { | |
2414 delete newModel; | |
2415 m_openingAudioFile = false; | |
2416 return FileOpenFailed; | |
2417 } | |
2418 | |
2419 bool setAsMain = true; | |
2420 static bool prevSetAsMain = true; | |
2421 | |
2422 bool realFile = (location == path); | |
2423 | |
2424 if (mode == CreateAdditionalModel) setAsMain = false; | |
2425 else if (mode == AskUser) { | |
2426 if (m_document->getMainModel()) { | |
2427 | |
2428 QStringList items; | |
2429 items << tr("Replace the existing main waveform") | |
2430 << tr("Load this file into a new waveform pane"); | |
2431 | |
2432 bool ok = false; | |
2433 QString item = ListInputDialog::getItem | |
2434 (this, tr("Select target for import"), | |
2435 tr("You already have an audio waveform loaded.\nWhat would you like to do with the new audio file?"), | |
2436 items, prevSetAsMain ? 0 : 1, &ok); | |
2437 | |
2438 if (!ok || item.isEmpty()) { | |
2439 delete newModel; | |
2440 m_openingAudioFile = false; | |
2441 return FileOpenCancelled; | |
2442 } | |
2443 | |
2444 setAsMain = (item == items[0]); | |
2445 prevSetAsMain = setAsMain; | |
2446 } | |
2447 } | |
2448 | |
2449 if (setAsMain) { | |
2450 | |
2451 Model *prevMain = getMainModel(); | |
2452 if (prevMain) { | |
2453 m_playSource->removeModel(prevMain); | |
2454 PlayParameterRepository::getInstance()->removeModel(prevMain); | |
2455 } | |
2456 | |
2457 PlayParameterRepository::getInstance()->addModel(newModel); | |
2458 | |
2459 m_document->setMainModel(newModel); | |
2460 setupMenus(); | |
2461 | |
2462 if (m_sessionFile == "") { | |
2463 setWindowTitle(tr("Sonic Visualiser: %1") | |
2464 .arg(QFileInfo(location).fileName())); | |
2465 CommandHistory::getInstance()->clear(); | |
2466 CommandHistory::getInstance()->documentSaved(); | |
2467 m_documentModified = false; | |
2468 } else { | |
2469 setWindowTitle(tr("Sonic Visualiser: %1 [%2]") | |
2470 .arg(QFileInfo(m_sessionFile).fileName()) | |
2471 .arg(QFileInfo(location).fileName())); | |
2472 if (m_documentModified) { | |
2473 m_documentModified = false; | |
2474 documentModified(); // so as to restore "(modified)" window title | |
2475 } | |
2476 } | |
2477 | |
2478 if (realFile) m_audioFile = path; | |
2479 | |
2480 } else { // !setAsMain | |
2481 | |
2482 CommandHistory::getInstance()->startCompoundOperation | |
2483 (tr("Import \"%1\"").arg(QFileInfo(location).fileName()), true); | |
2484 | |
2485 m_document->addImportedModel(newModel); | |
2486 | |
2487 AddPaneCommand *command = new AddPaneCommand(this); | |
2488 CommandHistory::getInstance()->addCommand(command); | |
2489 | |
2490 Pane *pane = command->getPane(); | |
2491 | |
2492 if (!m_timeRulerLayer) { | |
2493 m_timeRulerLayer = m_document->createMainModelLayer | |
2494 (LayerFactory::TimeRuler); | |
2495 } | |
2496 | |
2497 m_document->addLayerToView(pane, m_timeRulerLayer); | |
2498 | |
2499 Layer *newLayer = m_document->createImportedLayer(newModel); | |
2500 | |
2501 if (newLayer) { | |
2502 m_document->addLayerToView(pane, newLayer); | |
2503 } | |
2504 | |
2505 CommandHistory::getInstance()->endCompoundOperation(); | |
2506 } | |
2507 | |
2508 updateMenuStates(); | |
2509 m_recentFiles.addFile(location); | |
2510 if (realFile) { | |
2511 registerLastOpenedFilePath(FileFinder::AudioFile, path); // for file dialog | |
2512 } | |
2513 m_openingAudioFile = false; | |
2514 | |
2515 return FileOpenSucceeded; | |
2516 } | |
2517 | |
2518 void | |
2519 MainWindow::createPlayTarget() | |
2520 { | |
2521 if (m_playTarget) return; | |
2522 | |
2523 m_playTarget = AudioTargetFactory::createCallbackTarget(m_playSource); | |
2524 if (!m_playTarget) { | |
2525 QMessageBox::warning | |
2526 (this, tr("Couldn't open audio device"), | |
2527 tr("Could not open an audio device for playback.\nAudio playback will not be available during this session.\n"), | |
2528 QMessageBox::Ok, 0); | |
2529 } | |
2530 connect(m_fader, SIGNAL(valueChanged(float)), | |
2531 m_playTarget, SLOT(setOutputGain(float))); | |
2532 } | |
2533 | |
2534 WaveFileModel * | |
2535 MainWindow::getMainModel() | |
2536 { | |
2537 if (!m_document) return 0; | |
2538 return m_document->getMainModel(); | |
2539 } | |
2540 | |
2541 const WaveFileModel * | |
2542 MainWindow::getMainModel() const | |
2543 { | |
2544 if (!m_document) return 0; | |
2545 return m_document->getMainModel(); | |
2546 } | |
2547 | |
2548 void | |
2549 MainWindow::newSession() | |
2550 { | |
2551 if (!checkSaveModified()) return; | |
2552 | |
2553 closeSession(); | |
2554 createDocument(); | |
2555 | |
2556 Pane *pane = m_paneStack->addPane(); | |
2557 | |
2558 connect(pane, SIGNAL(contextHelpChanged(const QString &)), | |
2559 this, SLOT(contextHelpChanged(const QString &))); | |
2560 | |
2561 if (!m_timeRulerLayer) { | |
2562 m_timeRulerLayer = m_document->createMainModelLayer | |
2563 (LayerFactory::TimeRuler); | |
2564 } | |
2565 | |
2566 m_document->addLayerToView(pane, m_timeRulerLayer); | |
2567 | |
2568 Layer *waveform = m_document->createMainModelLayer(LayerFactory::Waveform); | |
2569 m_document->addLayerToView(pane, waveform); | |
2570 | |
2571 m_overview->registerView(pane); | |
2572 | |
2573 CommandHistory::getInstance()->clear(); | |
2574 CommandHistory::getInstance()->documentSaved(); | |
2575 documentRestored(); | |
2576 updateMenuStates(); | |
2577 } | |
2578 | |
2579 void | |
2580 MainWindow::createDocument() | |
2581 { | |
2582 m_document = new Document; | |
2583 | |
2584 connect(m_document, SIGNAL(layerAdded(Layer *)), | |
2585 this, SLOT(layerAdded(Layer *))); | |
2586 connect(m_document, SIGNAL(layerRemoved(Layer *)), | |
2587 this, SLOT(layerRemoved(Layer *))); | |
2588 connect(m_document, SIGNAL(layerAboutToBeDeleted(Layer *)), | |
2589 this, SLOT(layerAboutToBeDeleted(Layer *))); | |
2590 connect(m_document, SIGNAL(layerInAView(Layer *, bool)), | |
2591 this, SLOT(layerInAView(Layer *, bool))); | |
2592 | |
2593 connect(m_document, SIGNAL(modelAdded(Model *)), | |
2594 this, SLOT(modelAdded(Model *))); | |
2595 connect(m_document, SIGNAL(mainModelChanged(WaveFileModel *)), | |
2596 this, SLOT(mainModelChanged(WaveFileModel *))); | |
2597 connect(m_document, SIGNAL(modelAboutToBeDeleted(Model *)), | |
2598 this, SLOT(modelAboutToBeDeleted(Model *))); | |
2599 | |
2600 connect(m_document, SIGNAL(modelGenerationFailed(QString)), | |
2601 this, SLOT(modelGenerationFailed(QString))); | |
2602 connect(m_document, SIGNAL(modelRegenerationFailed(QString, QString)), | |
2603 this, SLOT(modelRegenerationFailed(QString, QString))); | |
2604 } | |
2605 | |
2606 void | |
2607 MainWindow::closeSession() | |
2608 { | |
2609 if (!checkSaveModified()) return; | |
2610 | |
2611 while (m_paneStack->getPaneCount() > 0) { | |
2612 | |
2613 Pane *pane = m_paneStack->getPane(m_paneStack->getPaneCount() - 1); | |
2614 | |
2615 while (pane->getLayerCount() > 0) { | |
2616 m_document->removeLayerFromView | |
2617 (pane, pane->getLayer(pane->getLayerCount() - 1)); | |
2618 } | |
2619 | |
2620 m_overview->unregisterView(pane); | |
2621 m_paneStack->deletePane(pane); | |
2622 } | |
2623 | |
2624 while (m_paneStack->getHiddenPaneCount() > 0) { | |
2625 | |
2626 Pane *pane = m_paneStack->getHiddenPane | |
2627 (m_paneStack->getHiddenPaneCount() - 1); | |
2628 | |
2629 while (pane->getLayerCount() > 0) { | |
2630 m_document->removeLayerFromView | |
2631 (pane, pane->getLayer(pane->getLayerCount() - 1)); | |
2632 } | |
2633 | |
2634 m_overview->unregisterView(pane); | |
2635 m_paneStack->deletePane(pane); | |
2636 } | |
2637 | |
2638 delete m_document; | |
2639 m_document = 0; | |
2640 m_viewManager->clearSelections(); | |
2641 m_timeRulerLayer = 0; // document owned this | |
2642 | |
2643 m_sessionFile = ""; | |
2644 setWindowTitle(tr("Sonic Visualiser")); | |
2645 | |
2646 CommandHistory::getInstance()->clear(); | |
2647 CommandHistory::getInstance()->documentSaved(); | |
2648 documentRestored(); | |
2649 } | |
2650 | |
2651 void | |
2652 MainWindow::openSession() | |
2653 { | |
2654 if (!checkSaveModified()) return; | |
2655 | |
2656 QString orig = m_audioFile; | |
2657 if (orig == "") orig = "."; | |
2658 else orig = QFileInfo(orig).absoluteDir().canonicalPath(); | |
2659 | |
2660 QString path = getOpenFileName(FileFinder::SessionFile); | |
2661 | |
2662 if (path.isEmpty()) return; | |
2663 | |
2664 if (openSessionFile(path) == FileOpenFailed) { | |
2665 QMessageBox::critical(this, tr("Failed to open file"), | |
2666 tr("Session file \"%1\" could not be opened").arg(path)); | |
2667 } | |
2668 } | |
2669 | |
2670 void | |
2671 MainWindow::openSomething() | |
2672 { | |
2673 QString orig = m_audioFile; | |
2674 if (orig == "") orig = "."; | |
2675 else orig = QFileInfo(orig).absoluteDir().canonicalPath(); | |
2676 | |
2677 bool canImportLayer = (getMainModel() != 0 && | |
2678 m_paneStack != 0 && | |
2679 m_paneStack->getCurrentPane() != 0); | |
2680 | |
2681 QString path = getOpenFileName(FileFinder::AnyFile); | |
2682 | |
2683 if (path.isEmpty()) return; | |
2684 | |
2685 if (path.endsWith(".sv")) { | |
2686 | |
2687 if (!checkSaveModified()) return; | |
2688 | |
2689 if (openSessionFile(path) == FileOpenFailed) { | |
2690 QMessageBox::critical(this, tr("Failed to open file"), | |
2691 tr("Session file \"%1\" could not be opened").arg(path)); | |
2692 } | |
2693 | |
2694 } else { | |
2695 | |
2696 if (openAudioFile(path, AskUser) == FileOpenFailed) { | |
2697 | |
2698 if (!canImportLayer || (openLayerFile(path) == FileOpenFailed)) { | |
2699 | |
2700 QMessageBox::critical(this, tr("Failed to open file"), | |
2701 tr("File \"%1\" could not be opened").arg(path)); | |
2702 } | |
2703 } | |
2704 } | |
2705 } | |
2706 | |
2707 void | |
2708 MainWindow::openLocation() | |
2709 { | |
2710 QSettings settings; | |
2711 settings.beginGroup("MainWindow"); | |
2712 QString lastLocation = settings.value("lastremote", "").toString(); | |
2713 | |
2714 bool ok = false; | |
2715 QString text = QInputDialog::getText | |
2716 (this, tr("Open Location"), | |
2717 tr("Please enter the URL of the location to open:"), | |
2718 QLineEdit::Normal, lastLocation, &ok); | |
2719 | |
2720 if (!ok) return; | |
2721 | |
2722 settings.setValue("lastremote", text); | |
2723 | |
2724 if (text.isEmpty()) return; | |
2725 | |
2726 if (openURL(QUrl(text)) == FileOpenFailed) { | |
2727 QMessageBox::critical(this, tr("Failed to open location"), | |
2728 tr("URL \"%1\" could not be opened").arg(text)); | |
2729 } | |
2730 } | |
2731 | |
2732 void | |
2733 MainWindow::openRecentFile() | |
2734 { | |
2735 QObject *obj = sender(); | |
2736 QAction *action = dynamic_cast<QAction *>(obj); | |
2737 | |
2738 if (!action) { | |
2739 std::cerr << "WARNING: MainWindow::openRecentFile: sender is not an action" | |
2740 << std::endl; | |
2741 return; | |
2742 } | |
2743 | |
2744 QString path = action->text(); | |
2745 if (path == "") return; | |
2746 | |
2747 QUrl url(path); | |
2748 if (RemoteFile::canHandleScheme(url)) { | |
2749 openURL(url); | |
2750 return; | |
2751 } | |
2752 | |
2753 if (path.endsWith("sv")) { | |
2754 | |
2755 if (!checkSaveModified()) return; | |
2756 | |
2757 if (openSessionFile(path) == FileOpenFailed) { | |
2758 QMessageBox::critical(this, tr("Failed to open file"), | |
2759 tr("Session file \"%1\" could not be opened").arg(path)); | |
2760 } | |
2761 | |
2762 } else { | |
2763 | |
2764 if (openAudioFile(path, AskUser) == FileOpenFailed) { | |
2765 | |
2766 bool canImportLayer = (getMainModel() != 0 && | |
2767 m_paneStack != 0 && | |
2768 m_paneStack->getCurrentPane() != 0); | |
2769 | |
2770 if (!canImportLayer || (openLayerFile(path) == FileOpenFailed)) { | |
2771 | |
2772 QMessageBox::critical(this, tr("Failed to open file"), | |
2773 tr("File \"%1\" could not be opened").arg(path)); | |
2774 } | |
2775 } | |
2776 } | |
2777 } | |
2778 | |
2779 MainWindow::FileOpenStatus | |
2780 MainWindow::openURL(QUrl url) | |
2781 { | |
2782 if (url.scheme().toLower() == "file") { | |
2783 return openSomeFile(url.toLocalFile()); | |
2784 } else if (!RemoteFile::canHandleScheme(url)) { | |
2785 QMessageBox::critical(this, tr("Unsupported scheme in URL"), | |
2786 tr("The URL scheme \"%1\" is not supported") | |
2787 .arg(url.scheme())); | |
2788 return FileOpenFailed; | |
2789 } else { | |
2790 RemoteFile rf(url); | |
2791 rf.wait(); | |
2792 if (!rf.isOK()) { | |
2793 QMessageBox::critical(this, tr("File download failed"), | |
2794 tr("Failed to download URL \"%1\": %2") | |
2795 .arg(url.toString()).arg(rf.getErrorString())); | |
2796 return FileOpenFailed; | |
2797 } | |
2798 FileOpenStatus status; | |
2799 if ((status = openSomeFile(rf.getLocalFilename(), url.toString())) != | |
2800 FileOpenSucceeded) { | |
2801 rf.deleteLocalFile(); | |
2802 } | |
2803 return status; | |
2804 } | |
2805 } | |
2806 | |
2807 MainWindow::FileOpenStatus | |
2808 MainWindow::openSomeFile(QString path, AudioFileOpenMode mode) | |
2809 { | |
2810 return openSomeFile(path, path, mode); | |
2811 } | |
2812 | |
2813 MainWindow::FileOpenStatus | |
2814 MainWindow::openSomeFile(QString path, QString location, | |
2815 AudioFileOpenMode mode) | |
2816 { | |
2817 FileOpenStatus status; | |
2818 | |
2819 bool canImportLayer = (getMainModel() != 0 && | |
2820 m_paneStack != 0 && | |
2821 m_paneStack->getCurrentPane() != 0); | |
2822 | |
2823 if ((status = openAudioFile(path, location, mode)) != FileOpenFailed) { | |
2824 return status; | |
2825 } else if ((status = openSessionFile(path, location)) != FileOpenFailed) { | |
2826 return status; | |
2827 } else if (!canImportLayer) { | |
2828 return FileOpenFailed; | |
2829 } else if ((status = openLayerFile(path, location)) != FileOpenFailed) { | |
2830 return status; | |
2831 } else { | |
2832 return FileOpenFailed; | |
2833 } | |
2834 } | |
2835 | |
2836 MainWindow::FileOpenStatus | |
2837 MainWindow::openSessionFile(QString path) | |
2838 { | |
2839 return openSessionFile(path, path); | |
2840 } | |
2841 | |
2842 MainWindow::FileOpenStatus | |
2843 MainWindow::openSessionFile(QString path, QString location) | |
2844 { | |
2845 BZipFileDevice bzFile(path); | |
2846 if (!bzFile.open(QIODevice::ReadOnly)) { | |
2847 std::cerr << "Failed to open session file \"" << location.toStdString() | |
2848 << "\": " << bzFile.errorString().toStdString() << std::endl; | |
2849 return FileOpenFailed; | |
2850 } | |
2851 | |
2852 if (!checkSaveModified()) return FileOpenCancelled; | |
2853 | |
2854 QString error; | |
2855 closeSession(); | |
2856 createDocument(); | |
2857 | |
2858 PaneCallback callback(this); | |
2859 m_viewManager->clearSelections(); | |
2860 | |
2861 SVFileReader reader(m_document, callback, location); | |
2862 QXmlInputSource inputSource(&bzFile); | |
2863 reader.parse(inputSource); | |
2864 | |
2865 if (!reader.isOK()) { | |
2866 error = tr("SV XML file read error:\n%1").arg(reader.getErrorString()); | |
2867 } | |
2868 | |
2869 bzFile.close(); | |
2870 | |
2871 bool ok = (error == ""); | |
2872 | |
2873 bool realFile = (location == path); | |
2874 | |
2875 if (ok) { | |
2876 | |
2877 setWindowTitle(tr("Sonic Visualiser: %1") | |
2878 .arg(QFileInfo(location).fileName())); | |
2879 | |
2880 if (realFile) m_sessionFile = path; | |
2881 | |
2882 setupMenus(); | |
2883 CommandHistory::getInstance()->clear(); | |
2884 CommandHistory::getInstance()->documentSaved(); | |
2885 m_documentModified = false; | |
2886 updateMenuStates(); | |
2887 | |
2888 m_recentFiles.addFile(location); | |
2889 | |
2890 if (realFile) { | |
2891 registerLastOpenedFilePath(FileFinder::SessionFile, path); // for file dialog | |
2892 } | |
2893 | |
2894 } else { | |
2895 setWindowTitle(tr("Sonic Visualiser")); | |
2896 } | |
2897 | |
2898 return ok ? FileOpenSucceeded : FileOpenFailed; | |
2899 } | |
2900 | |
2901 void | |
2902 MainWindow::closeEvent(QCloseEvent *e) | |
2903 { | |
2904 // std::cerr << "MainWindow::closeEvent" << std::endl; | |
2905 | |
2906 if (m_openingAudioFile) { | |
2907 // std::cerr << "Busy - ignoring close event" << std::endl; | |
2908 e->ignore(); | |
2909 return; | |
2910 } | |
2911 | |
2912 if (!m_abandoning && !checkSaveModified()) { | |
2913 // std::cerr << "Ignoring close event" << std::endl; | |
2914 e->ignore(); | |
2915 return; | |
2916 } | |
2917 | |
2918 QSettings settings; | |
2919 settings.beginGroup("MainWindow"); | |
2920 settings.setValue("size", size()); | |
2921 settings.setValue("position", pos()); | |
2922 settings.endGroup(); | |
2923 | |
2924 e->accept(); | |
2925 return; | |
2926 } | |
2927 | |
2928 bool | |
2929 MainWindow::commitData(bool mayAskUser) | |
2930 { | |
2931 if (mayAskUser) { | |
2932 return checkSaveModified(); | |
2933 } else { | |
2934 if (!m_documentModified) return true; | |
2935 | |
2936 // If we can't check with the user first, then we can't save | |
2937 // to the original session file (even if we have it) -- have | |
2938 // to use a temporary file | |
2939 | |
2940 QString svDirBase = ".sv1"; | |
2941 QString svDir = QDir::home().filePath(svDirBase); | |
2942 | |
2943 if (!QFileInfo(svDir).exists()) { | |
2944 if (!QDir::home().mkdir(svDirBase)) return false; | |
2945 } else { | |
2946 if (!QFileInfo(svDir).isDir()) return false; | |
2947 } | |
2948 | |
2949 // This name doesn't have to be unguessable | |
2950 #ifndef _WIN32 | |
2951 QString fname = QString("tmp-%1-%2.sv") | |
2952 .arg(QDateTime::currentDateTime().toString("yyyyMMddhhmmsszzz")) | |
2953 .arg(QProcess().pid()); | |
2954 #else | |
2955 QString fname = QString("tmp-%1.sv") | |
2956 .arg(QDateTime::currentDateTime().toString("yyyyMMddhhmmsszzz")); | |
2957 #endif | |
2958 QString fpath = QDir(svDir).filePath(fname); | |
2959 if (saveSessionFile(fpath)) { | |
2960 m_recentFiles.addFile(fpath); | |
2961 return true; | |
2962 } else { | |
2963 return false; | |
2964 } | |
2965 } | |
2966 } | |
2967 | |
2968 bool | |
2969 MainWindow::checkSaveModified() | |
2970 { | |
2971 // Called before some destructive operation (e.g. new session, | |
2972 // exit program). Return true if we can safely proceed, false to | |
2973 // cancel. | |
2974 | |
2975 if (!m_documentModified) return true; | |
2976 | |
2977 int button = | |
2978 QMessageBox::warning(this, | |
2979 tr("Session modified"), | |
2980 tr("The current session has been modified.\nDo you want to save it?"), | |
2981 QMessageBox::Yes, | |
2982 QMessageBox::No, | |
2983 QMessageBox::Cancel); | |
2984 | |
2985 if (button == QMessageBox::Yes) { | |
2986 saveSession(); | |
2987 if (m_documentModified) { // save failed -- don't proceed! | |
2988 return false; | |
2989 } else { | |
2990 return true; // saved, so it's safe to continue now | |
2991 } | |
2992 } else if (button == QMessageBox::No) { | |
2993 m_documentModified = false; // so we know to abandon it | |
2994 return true; | |
2995 } | |
2996 | |
2997 // else cancel | |
2998 return false; | |
2999 } | |
3000 | |
3001 void | |
3002 MainWindow::saveSession() | |
3003 { | |
3004 if (m_sessionFile != "") { | |
3005 if (!saveSessionFile(m_sessionFile)) { | |
3006 QMessageBox::critical(this, tr("Failed to save file"), | |
3007 tr("Session file \"%1\" could not be saved.").arg(m_sessionFile)); | |
3008 } else { | |
3009 CommandHistory::getInstance()->documentSaved(); | |
3010 documentRestored(); | |
3011 } | |
3012 } else { | |
3013 saveSessionAs(); | |
3014 } | |
3015 } | |
3016 | |
3017 void | |
3018 MainWindow::saveSessionAs() | |
3019 { | |
3020 QString orig = m_audioFile; | |
3021 if (orig == "") orig = "."; | |
3022 else orig = QFileInfo(orig).absoluteDir().canonicalPath(); | |
3023 | |
3024 QString path = getSaveFileName(FileFinder::SessionFile); | |
3025 | |
3026 if (path == "") return; | |
3027 | |
3028 if (!saveSessionFile(path)) { | |
3029 QMessageBox::critical(this, tr("Failed to save file"), | |
3030 tr("Session file \"%1\" could not be saved.").arg(path)); | |
3031 } else { | |
3032 setWindowTitle(tr("Sonic Visualiser: %1") | |
3033 .arg(QFileInfo(path).fileName())); | |
3034 m_sessionFile = path; | |
3035 CommandHistory::getInstance()->documentSaved(); | |
3036 documentRestored(); | |
3037 m_recentFiles.addFile(path); | |
3038 } | |
3039 } | |
3040 | |
3041 bool | |
3042 MainWindow::saveSessionFile(QString path) | |
3043 { | |
3044 BZipFileDevice bzFile(path); | |
3045 if (!bzFile.open(QIODevice::WriteOnly)) { | |
3046 std::cerr << "Failed to open session file \"" << path.toStdString() | |
3047 << "\" for writing: " | |
3048 << bzFile.errorString().toStdString() << std::endl; | |
3049 return false; | |
3050 } | |
3051 | |
3052 QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); | |
3053 | |
3054 QTextStream out(&bzFile); | |
3055 toXml(out); | |
3056 out.flush(); | |
3057 | |
3058 QApplication::restoreOverrideCursor(); | |
3059 | |
3060 if (!bzFile.isOK()) { | |
3061 QMessageBox::critical(this, tr("Failed to write file"), | |
3062 tr("Failed to write to file \"%1\": %2") | |
3063 .arg(path).arg(bzFile.errorString())); | |
3064 bzFile.close(); | |
3065 return false; | |
3066 } | |
3067 | |
3068 bzFile.close(); | |
3069 return true; | |
3070 } | |
3071 | |
3072 void | |
3073 MainWindow::toXml(QTextStream &out) | |
3074 { | |
3075 QString indent(" "); | |
3076 | |
3077 out << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"; | |
3078 out << "<!DOCTYPE sonic-visualiser>\n"; | |
3079 out << "<sv>\n"; | |
3080 | |
3081 m_document->toXml(out, "", ""); | |
3082 | |
3083 out << "<display>\n"; | |
3084 | |
3085 out << QString(" <window width=\"%1\" height=\"%2\"/>\n") | |
3086 .arg(width()).arg(height()); | |
3087 | |
3088 for (int i = 0; i < m_paneStack->getPaneCount(); ++i) { | |
3089 | |
3090 Pane *pane = m_paneStack->getPane(i); | |
3091 | |
3092 if (pane) { | |
3093 pane->toXml(out, indent); | |
3094 } | |
3095 } | |
3096 | |
3097 out << "</display>\n"; | |
3098 | |
3099 m_viewManager->getSelection().toXml(out); | |
3100 | |
3101 out << "</sv>\n"; | |
3102 } | |
3103 | |
3104 Pane * | |
3105 MainWindow::addPaneToStack() | |
3106 { | |
3107 AddPaneCommand *command = new AddPaneCommand(this); | |
3108 CommandHistory::getInstance()->addCommand(command); | |
3109 return command->getPane(); | |
3110 } | |
3111 | |
3112 void | |
3113 MainWindow::zoomIn() | |
3114 { | |
3115 Pane *currentPane = m_paneStack->getCurrentPane(); | |
3116 if (currentPane) currentPane->zoom(true); | |
3117 } | |
3118 | |
3119 void | |
3120 MainWindow::zoomOut() | |
3121 { | |
3122 Pane *currentPane = m_paneStack->getCurrentPane(); | |
3123 if (currentPane) currentPane->zoom(false); | |
3124 } | |
3125 | |
3126 void | |
3127 MainWindow::zoomToFit() | |
3128 { | |
3129 Pane *currentPane = m_paneStack->getCurrentPane(); | |
3130 if (!currentPane) return; | |
3131 | |
3132 Model *model = getMainModel(); | |
3133 if (!model) return; | |
3134 | |
3135 size_t start = model->getStartFrame(); | |
3136 size_t end = model->getEndFrame(); | |
3137 size_t pixels = currentPane->width(); | |
3138 size_t zoomLevel = (end - start) / pixels; | |
3139 | |
3140 currentPane->setZoomLevel(zoomLevel); | |
3141 currentPane->setStartFrame(start); | |
3142 } | |
3143 | |
3144 void | |
3145 MainWindow::zoomDefault() | |
3146 { | |
3147 Pane *currentPane = m_paneStack->getCurrentPane(); | |
3148 if (currentPane) currentPane->setZoomLevel(1024); | |
3149 } | |
3150 | |
3151 void | |
3152 MainWindow::scrollLeft() | |
3153 { | |
3154 Pane *currentPane = m_paneStack->getCurrentPane(); | |
3155 if (currentPane) currentPane->scroll(false, false); | |
3156 } | |
3157 | |
3158 void | |
3159 MainWindow::jumpLeft() | |
3160 { | |
3161 Pane *currentPane = m_paneStack->getCurrentPane(); | |
3162 if (currentPane) currentPane->scroll(false, true); | |
3163 } | |
3164 | |
3165 void | |
3166 MainWindow::scrollRight() | |
3167 { | |
3168 Pane *currentPane = m_paneStack->getCurrentPane(); | |
3169 if (currentPane) currentPane->scroll(true, false); | |
3170 } | |
3171 | |
3172 void | |
3173 MainWindow::jumpRight() | |
3174 { | |
3175 Pane *currentPane = m_paneStack->getCurrentPane(); | |
3176 if (currentPane) currentPane->scroll(true, true); | |
3177 } | |
3178 | |
3179 void | |
3180 MainWindow::showNoOverlays() | |
3181 { | |
3182 m_viewManager->setOverlayMode(ViewManager::NoOverlays); | |
3183 } | |
3184 | |
3185 void | |
3186 MainWindow::showMinimalOverlays() | |
3187 { | |
3188 m_viewManager->setOverlayMode(ViewManager::MinimalOverlays); | |
3189 } | |
3190 | |
3191 void | |
3192 MainWindow::showStandardOverlays() | |
3193 { | |
3194 m_viewManager->setOverlayMode(ViewManager::StandardOverlays); | |
3195 } | |
3196 | |
3197 void | |
3198 MainWindow::showAllOverlays() | |
3199 { | |
3200 m_viewManager->setOverlayMode(ViewManager::AllOverlays); | |
3201 } | |
3202 | |
3203 void | |
3204 MainWindow::toggleZoomWheels() | |
3205 { | |
3206 if (m_viewManager->getZoomWheelsEnabled()) { | |
3207 m_viewManager->setZoomWheelsEnabled(false); | |
3208 } else { | |
3209 m_viewManager->setZoomWheelsEnabled(true); | |
3210 } | |
3211 } | |
3212 | |
3213 void | |
3214 MainWindow::togglePropertyBoxes() | |
3215 { | |
3216 if (m_paneStack->getLayoutStyle() == PaneStack::NoPropertyStacks) { | |
3217 if (Preferences::getInstance()->getPropertyBoxLayout() == | |
3218 Preferences::VerticallyStacked) { | |
3219 m_paneStack->setLayoutStyle(PaneStack::PropertyStackPerPaneLayout); | |
3220 } else { | |
3221 m_paneStack->setLayoutStyle(PaneStack::SinglePropertyStackLayout); | |
3222 } | |
3223 } else { | |
3224 m_paneStack->setLayoutStyle(PaneStack::NoPropertyStacks); | |
3225 } | |
3226 } | |
3227 | |
3228 void | |
3229 MainWindow::toggleStatusBar() | |
3230 { | |
3231 QSettings settings; | |
3232 settings.beginGroup("MainWindow"); | |
3233 bool sb = settings.value("showstatusbar", true).toBool(); | |
3234 | |
3235 if (sb) { | |
3236 statusBar()->hide(); | |
3237 } else { | |
3238 statusBar()->show(); | |
3239 } | |
3240 | |
3241 settings.setValue("showstatusbar", !sb); | |
3242 | |
3243 settings.endGroup(); | |
3244 } | |
3245 | |
3246 void | |
3247 MainWindow::preferenceChanged(PropertyContainer::PropertyName name) | |
3248 { | |
3249 if (name == "Property Box Layout") { | |
3250 if (m_paneStack->getLayoutStyle() != PaneStack::NoPropertyStacks) { | |
3251 if (Preferences::getInstance()->getPropertyBoxLayout() == | |
3252 Preferences::VerticallyStacked) { | |
3253 m_paneStack->setLayoutStyle(PaneStack::PropertyStackPerPaneLayout); | |
3254 } else { | |
3255 m_paneStack->setLayoutStyle(PaneStack::SinglePropertyStackLayout); | |
3256 } | |
3257 } | |
3258 } | |
3259 } | |
3260 | |
3261 void | |
3262 MainWindow::play() | |
3263 { | |
3264 if (m_playSource->isPlaying()) { | |
3265 stop(); | |
3266 } else { | |
3267 playbackFrameChanged(m_viewManager->getPlaybackFrame()); | |
3268 m_playSource->play(m_viewManager->getPlaybackFrame()); | |
3269 } | |
3270 } | |
3271 | |
3272 void | |
3273 MainWindow::ffwd() | |
3274 { | |
3275 if (!getMainModel()) return; | |
3276 | |
3277 int frame = m_viewManager->getPlaybackFrame(); | |
3278 ++frame; | |
3279 | |
3280 Pane *pane = m_paneStack->getCurrentPane(); | |
3281 if (!pane) return; | |
3282 | |
3283 Layer *layer = pane->getSelectedLayer(); | |
3284 | |
3285 if (!dynamic_cast<TimeInstantLayer *>(layer) && | |
3286 !dynamic_cast<TimeValueLayer *>(layer)) return; | |
3287 | |
3288 size_t resolution = 0; | |
3289 if (!layer->snapToFeatureFrame(pane, frame, resolution, Layer::SnapRight)) { | |
3290 frame = getMainModel()->getEndFrame(); | |
3291 } | |
3292 | |
3293 m_viewManager->setPlaybackFrame(frame); | |
3294 } | |
3295 | |
3296 void | |
3297 MainWindow::ffwdEnd() | |
3298 { | |
3299 if (!getMainModel()) return; | |
3300 m_viewManager->setPlaybackFrame(getMainModel()->getEndFrame()); | |
3301 } | |
3302 | |
3303 void | |
3304 MainWindow::rewind() | |
3305 { | |
3306 if (!getMainModel()) return; | |
3307 | |
3308 int frame = m_viewManager->getPlaybackFrame(); | |
3309 if (frame > 0) --frame; | |
3310 | |
3311 Pane *pane = m_paneStack->getCurrentPane(); | |
3312 if (!pane) return; | |
3313 | |
3314 Layer *layer = pane->getSelectedLayer(); | |
3315 | |
3316 if (!dynamic_cast<TimeInstantLayer *>(layer) && | |
3317 !dynamic_cast<TimeValueLayer *>(layer)) return; | |
3318 | |
3319 size_t resolution = 0; | |
3320 if (!layer->snapToFeatureFrame(pane, frame, resolution, Layer::SnapLeft)) { | |
3321 frame = getMainModel()->getEndFrame(); | |
3322 } | |
3323 | |
3324 m_viewManager->setPlaybackFrame(frame); | |
3325 } | |
3326 | |
3327 void | |
3328 MainWindow::rewindStart() | |
3329 { | |
3330 if (!getMainModel()) return; | |
3331 m_viewManager->setPlaybackFrame(getMainModel()->getStartFrame()); | |
3332 } | |
3333 | |
3334 void | |
3335 MainWindow::stop() | |
3336 { | |
3337 m_playSource->stop(); | |
3338 | |
3339 if (m_paneStack && m_paneStack->getCurrentPane()) { | |
3340 updateVisibleRangeDisplay(m_paneStack->getCurrentPane()); | |
3341 } else { | |
3342 m_myStatusMessage = ""; | |
3343 statusBar()->showMessage(""); | |
3344 } | |
3345 } | |
3346 | |
3347 void | |
3348 MainWindow::addPane() | |
3349 { | |
3350 QObject *s = sender(); | |
3351 QAction *action = dynamic_cast<QAction *>(s); | |
3352 | |
3353 if (!action) { | |
3354 std::cerr << "WARNING: MainWindow::addPane: sender is not an action" | |
3355 << std::endl; | |
3356 return; | |
3357 } | |
3358 | |
3359 PaneActionMap::iterator i = m_paneActions.find(action); | |
3360 | |
3361 if (i == m_paneActions.end()) { | |
3362 std::cerr << "WARNING: MainWindow::addPane: unknown action " | |
3363 << action->objectName().toStdString() << std::endl; | |
3364 return; | |
3365 } | |
3366 | |
3367 addPane(i->second, action->text()); | |
3368 } | |
3369 | |
3370 void | |
3371 MainWindow::addPane(const PaneConfiguration &configuration, QString text) | |
3372 { | |
3373 CommandHistory::getInstance()->startCompoundOperation(text, true); | |
3374 | |
3375 AddPaneCommand *command = new AddPaneCommand(this); | |
3376 CommandHistory::getInstance()->addCommand(command); | |
3377 | |
3378 Pane *pane = command->getPane(); | |
3379 | |
3380 if (configuration.layer == LayerFactory::Spectrum) { | |
3381 pane->setPlaybackFollow(PlaybackScrollContinuous); | |
3382 pane->setFollowGlobalZoom(false); | |
3383 pane->setZoomLevel(512); | |
3384 } | |
3385 | |
3386 if (configuration.layer != LayerFactory::TimeRuler && | |
3387 configuration.layer != LayerFactory::Spectrum) { | |
3388 | |
3389 if (!m_timeRulerLayer) { | |
3390 // std::cerr << "no time ruler layer, creating one" << std::endl; | |
3391 m_timeRulerLayer = m_document->createMainModelLayer | |
3392 (LayerFactory::TimeRuler); | |
3393 } | |
3394 | |
3395 // std::cerr << "adding time ruler layer " << m_timeRulerLayer << std::endl; | |
3396 | |
3397 m_document->addLayerToView(pane, m_timeRulerLayer); | |
3398 } | |
3399 | |
3400 Layer *newLayer = m_document->createLayer(configuration.layer); | |
3401 | |
3402 Model *suggestedModel = configuration.sourceModel; | |
3403 Model *model = 0; | |
3404 | |
3405 if (suggestedModel) { | |
3406 | |
3407 // check its validity | |
3408 std::vector<Model *> inputModels = m_document->getTransformInputModels(); | |
3409 for (size_t j = 0; j < inputModels.size(); ++j) { | |
3410 if (inputModels[j] == suggestedModel) { | |
3411 model = suggestedModel; | |
3412 break; | |
3413 } | |
3414 } | |
3415 | |
3416 if (!model) { | |
3417 std::cerr << "WARNING: Model " << (void *)suggestedModel | |
3418 << " appears in pane action map, but is not reported " | |
3419 << "by document as a valid transform source" << std::endl; | |
3420 } | |
3421 } | |
3422 | |
3423 if (!model) model = m_document->getMainModel(); | |
3424 | |
3425 m_document->setModel(newLayer, model); | |
3426 | |
3427 m_document->setChannel(newLayer, configuration.channel); | |
3428 m_document->addLayerToView(pane, newLayer); | |
3429 | |
3430 m_paneStack->setCurrentPane(pane); | |
3431 m_paneStack->setCurrentLayer(pane, newLayer); | |
3432 | |
3433 // std::cerr << "MainWindow::addPane: global centre frame is " | |
3434 // << m_viewManager->getGlobalCentreFrame() << std::endl; | |
3435 // pane->setCentreFrame(m_viewManager->getGlobalCentreFrame()); | |
3436 | |
3437 CommandHistory::getInstance()->endCompoundOperation(); | |
3438 | |
3439 updateMenuStates(); | |
3440 } | |
3441 | |
3442 MainWindow::AddPaneCommand::AddPaneCommand(MainWindow *mw) : | |
3443 m_mw(mw), | |
3444 m_pane(0), | |
3445 m_prevCurrentPane(0), | |
3446 m_added(false) | |
3447 { | |
3448 } | |
3449 | |
3450 MainWindow::AddPaneCommand::~AddPaneCommand() | |
3451 { | |
3452 if (m_pane && !m_added) { | |
3453 m_mw->m_paneStack->deletePane(m_pane); | |
3454 } | |
3455 } | |
3456 | |
3457 QString | |
3458 MainWindow::AddPaneCommand::getName() const | |
3459 { | |
3460 return tr("Add Pane"); | |
3461 } | |
3462 | |
3463 void | |
3464 MainWindow::AddPaneCommand::execute() | |
3465 { | |
3466 if (!m_pane) { | |
3467 m_prevCurrentPane = m_mw->m_paneStack->getCurrentPane(); | |
3468 m_pane = m_mw->m_paneStack->addPane(); | |
3469 | |
3470 connect(m_pane, SIGNAL(contextHelpChanged(const QString &)), | |
3471 m_mw, SLOT(contextHelpChanged(const QString &))); | |
3472 } else { | |
3473 m_mw->m_paneStack->showPane(m_pane); | |
3474 } | |
3475 | |
3476 m_mw->m_paneStack->setCurrentPane(m_pane); | |
3477 m_mw->m_overview->registerView(m_pane); | |
3478 m_added = true; | |
3479 } | |
3480 | |
3481 void | |
3482 MainWindow::AddPaneCommand::unexecute() | |
3483 { | |
3484 m_mw->m_paneStack->hidePane(m_pane); | |
3485 m_mw->m_paneStack->setCurrentPane(m_prevCurrentPane); | |
3486 m_mw->m_overview->unregisterView(m_pane); | |
3487 m_added = false; | |
3488 } | |
3489 | |
3490 MainWindow::RemovePaneCommand::RemovePaneCommand(MainWindow *mw, Pane *pane) : | |
3491 m_mw(mw), | |
3492 m_pane(pane), | |
3493 m_added(true) | |
3494 { | |
3495 } | |
3496 | |
3497 MainWindow::RemovePaneCommand::~RemovePaneCommand() | |
3498 { | |
3499 if (m_pane && !m_added) { | |
3500 m_mw->m_paneStack->deletePane(m_pane); | |
3501 } | |
3502 } | |
3503 | |
3504 QString | |
3505 MainWindow::RemovePaneCommand::getName() const | |
3506 { | |
3507 return tr("Remove Pane"); | |
3508 } | |
3509 | |
3510 void | |
3511 MainWindow::RemovePaneCommand::execute() | |
3512 { | |
3513 m_prevCurrentPane = m_mw->m_paneStack->getCurrentPane(); | |
3514 m_mw->m_paneStack->hidePane(m_pane); | |
3515 m_mw->m_overview->unregisterView(m_pane); | |
3516 m_added = false; | |
3517 } | |
3518 | |
3519 void | |
3520 MainWindow::RemovePaneCommand::unexecute() | |
3521 { | |
3522 m_mw->m_paneStack->showPane(m_pane); | |
3523 m_mw->m_paneStack->setCurrentPane(m_prevCurrentPane); | |
3524 m_mw->m_overview->registerView(m_pane); | |
3525 m_added = true; | |
3526 } | |
3527 | |
3528 void | |
3529 MainWindow::addLayer() | |
3530 { | |
3531 QObject *s = sender(); | |
3532 QAction *action = dynamic_cast<QAction *>(s); | |
3533 | |
3534 if (!action) { | |
3535 std::cerr << "WARNING: MainWindow::addLayer: sender is not an action" | |
3536 << std::endl; | |
3537 return; | |
3538 } | |
3539 | |
3540 Pane *pane = m_paneStack->getCurrentPane(); | |
3541 | |
3542 if (!pane) { | |
3543 std::cerr << "WARNING: MainWindow::addLayer: no current pane" << std::endl; | |
3544 return; | |
3545 } | |
3546 | |
3547 ExistingLayerActionMap::iterator ei = m_existingLayerActions.find(action); | |
3548 | |
3549 if (ei != m_existingLayerActions.end()) { | |
3550 Layer *newLayer = ei->second; | |
3551 m_document->addLayerToView(pane, newLayer); | |
3552 m_paneStack->setCurrentLayer(pane, newLayer); | |
3553 return; | |
3554 } | |
3555 | |
3556 ei = m_sliceActions.find(action); | |
3557 | |
3558 if (ei != m_sliceActions.end()) { | |
3559 Layer *newLayer = m_document->createLayer(LayerFactory::Slice); | |
3560 // document->setModel(newLayer, ei->second->getModel()); | |
3561 SliceableLayer *source = dynamic_cast<SliceableLayer *>(ei->second); | |
3562 SliceLayer *dest = dynamic_cast<SliceLayer *>(newLayer); | |
3563 if (source && dest) { | |
3564 dest->setSliceableModel(source->getSliceableModel()); | |
3565 connect(source, SIGNAL(sliceableModelReplaced(const Model *, const Model *)), | |
3566 dest, SLOT(sliceableModelReplaced(const Model *, const Model *))); | |
3567 connect(m_document, SIGNAL(modelAboutToBeDeleted(Model *)), | |
3568 dest, SLOT(modelAboutToBeDeleted(Model *))); | |
3569 } | |
3570 m_document->addLayerToView(pane, newLayer); | |
3571 m_paneStack->setCurrentLayer(pane, newLayer); | |
3572 return; | |
3573 } | |
3574 | |
3575 TransformActionMap::iterator i = m_transformActions.find(action); | |
3576 | |
3577 if (i == m_transformActions.end()) { | |
3578 | |
3579 LayerActionMap::iterator i = m_layerActions.find(action); | |
3580 | |
3581 if (i == m_layerActions.end()) { | |
3582 std::cerr << "WARNING: MainWindow::addLayer: unknown action " | |
3583 << action->objectName().toStdString() << std::endl; | |
3584 return; | |
3585 } | |
3586 | |
3587 LayerFactory::LayerType type = i->second; | |
3588 | |
3589 LayerFactory::LayerTypeSet emptyTypes = | |
3590 LayerFactory::getInstance()->getValidEmptyLayerTypes(); | |
3591 | |
3592 Layer *newLayer; | |
3593 | |
3594 if (emptyTypes.find(type) != emptyTypes.end()) { | |
3595 | |
3596 newLayer = m_document->createEmptyLayer(type); | |
3597 m_toolActions[ViewManager::DrawMode]->trigger(); | |
3598 | |
3599 } else { | |
3600 | |
3601 newLayer = m_document->createMainModelLayer(type); | |
3602 } | |
3603 | |
3604 m_document->addLayerToView(pane, newLayer); | |
3605 m_paneStack->setCurrentLayer(pane, newLayer); | |
3606 | |
3607 return; | |
3608 } | |
3609 | |
3610 TransformId transform = i->second; | |
3611 TransformFactory *factory = TransformFactory::getInstance(); | |
3612 | |
3613 QString configurationXml; | |
3614 | |
3615 int channel = -1; | |
3616 // pick up the default channel from any existing layers on the same pane | |
3617 for (int j = 0; j < pane->getLayerCount(); ++j) { | |
3618 int c = LayerFactory::getInstance()->getChannel(pane->getLayer(j)); | |
3619 if (c != -1) { | |
3620 channel = c; | |
3621 break; | |
3622 } | |
3623 } | |
3624 | |
3625 // We always ask for configuration, even if the plugin isn't | |
3626 // supposed to be configurable, because we need to let the user | |
3627 // change the execution context (block size etc). | |
3628 | |
3629 PluginTransform::ExecutionContext context(channel); | |
3630 | |
3631 std::vector<Model *> candidateInputModels = | |
3632 m_document->getTransformInputModels(); | |
3633 | |
3634 Model *inputModel = factory->getConfigurationForTransform(transform, | |
3635 candidateInputModels, | |
3636 context, | |
3637 configurationXml, | |
3638 m_playSource); | |
3639 if (!inputModel) return; | |
3640 | |
3641 // std::cerr << "MainWindow::addLayer: Input model is " << inputModel << " \"" << inputModel->objectName().toStdString() << "\"" << std::endl; | |
3642 | |
3643 Layer *newLayer = m_document->createDerivedLayer(transform, | |
3644 inputModel, | |
3645 context, | |
3646 configurationXml); | |
3647 | |
3648 if (newLayer) { | |
3649 m_document->addLayerToView(pane, newLayer); | |
3650 m_document->setChannel(newLayer, context.channel); | |
3651 m_recentTransforms.add(transform); | |
3652 m_paneStack->setCurrentLayer(pane, newLayer); | |
3653 } | |
3654 | |
3655 updateMenuStates(); | |
3656 } | |
3657 | |
3658 void | |
3659 MainWindow::deleteCurrentPane() | |
3660 { | |
3661 CommandHistory::getInstance()->startCompoundOperation | |
3662 (tr("Delete Pane"), true); | |
3663 | |
3664 Pane *pane = m_paneStack->getCurrentPane(); | |
3665 if (pane) { | |
3666 while (pane->getLayerCount() > 0) { | |
3667 Layer *layer = pane->getLayer(0); | |
3668 if (layer) { | |
3669 m_document->removeLayerFromView(pane, layer); | |
3670 } else { | |
3671 break; | |
3672 } | |
3673 } | |
3674 | |
3675 RemovePaneCommand *command = new RemovePaneCommand(this, pane); | |
3676 CommandHistory::getInstance()->addCommand(command); | |
3677 } | |
3678 | |
3679 CommandHistory::getInstance()->endCompoundOperation(); | |
3680 | |
3681 updateMenuStates(); | |
3682 } | |
3683 | |
3684 void | |
3685 MainWindow::deleteCurrentLayer() | |
3686 { | |
3687 Pane *pane = m_paneStack->getCurrentPane(); | |
3688 if (pane) { | |
3689 Layer *layer = pane->getSelectedLayer(); | |
3690 if (layer) { | |
3691 m_document->removeLayerFromView(pane, layer); | |
3692 } | |
3693 } | |
3694 updateMenuStates(); | |
3695 } | |
3696 | |
3697 void | |
3698 MainWindow::renameCurrentLayer() | |
3699 { | |
3700 Pane *pane = m_paneStack->getCurrentPane(); | |
3701 if (pane) { | |
3702 Layer *layer = pane->getSelectedLayer(); | |
3703 if (layer) { | |
3704 bool ok = false; | |
3705 QString newName = QInputDialog::getText | |
3706 (this, tr("Rename Layer"), | |
3707 tr("New name for this layer:"), | |
3708 QLineEdit::Normal, layer->objectName(), &ok); | |
3709 if (ok) { | |
3710 layer->setObjectName(newName); | |
3711 setupExistingLayersMenus(); | |
3712 } | |
3713 } | |
3714 } | |
3715 } | |
3716 | |
3717 void | |
3718 MainWindow::playSpeedChanged(int position) | |
3719 { | |
3720 PlaySpeedRangeMapper mapper(0, 200); | |
3721 | |
3722 float percent = m_playSpeed->mappedValue(); | |
3723 | |
3724 float factor = mapper.getFactorForValue(percent); | |
3725 | |
3726 // float factor = mapper.getFactorForPosition(position); | |
3727 // float percent = mapper.getValueForPosition(position); | |
3728 | |
3729 std::cerr << "speed = " << position << " percent = " << percent << " factor = " << factor << std::endl; | |
3730 | |
3731 //!!! bool slow = (position < 100); | |
3732 bool something = (position != 100); | |
3733 /*!!! | |
3734 int pc = lrintf(percent); | |
3735 | |
3736 m_playSpeed->setToolTip(tr("Playback speed: %1%2%") | |
3737 .arg(!slow ? "+" : "") | |
3738 .arg(pc)); | |
3739 */ | |
3740 m_playSharpen->setEnabled(something); | |
3741 m_playMono->setEnabled(something); | |
3742 bool sharpen = (something && m_playSharpen->isChecked()); | |
3743 bool mono = (something && m_playMono->isChecked()); | |
3744 m_playSource->setTimeStretch(factor, sharpen, mono); | |
3745 } | |
3746 | |
3747 void | |
3748 MainWindow::playSharpenToggled() | |
3749 { | |
3750 QSettings settings; | |
3751 settings.beginGroup("MainWindow"); | |
3752 settings.setValue("playsharpen", m_playSharpen->isChecked()); | |
3753 settings.endGroup(); | |
3754 | |
3755 playSpeedChanged(m_playSpeed->value()); | |
3756 } | |
3757 | |
3758 void | |
3759 MainWindow::playMonoToggled() | |
3760 { | |
3761 QSettings settings; | |
3762 settings.beginGroup("MainWindow"); | |
3763 settings.setValue("playmono", m_playMono->isChecked()); | |
3764 settings.endGroup(); | |
3765 | |
3766 playSpeedChanged(m_playSpeed->value()); | |
3767 } | |
3768 | |
3769 void | |
3770 MainWindow::playbackFrameChanged(unsigned long frame) | |
3771 { | |
3772 if (!(m_playSource && m_playSource->isPlaying()) || !getMainModel()) return; | |
3773 | |
3774 RealTime now = RealTime::frame2RealTime | |
3775 (frame, getMainModel()->getSampleRate()); | |
3776 | |
3777 if (now.sec == m_lastPlayStatusSec) return; | |
3778 | |
3779 RealTime then = RealTime::frame2RealTime | |
3780 (m_playSource->getPlayEndFrame(), getMainModel()->getSampleRate()); | |
3781 | |
3782 QString nowStr; | |
3783 QString thenStr; | |
3784 QString remainingStr; | |
3785 | |
3786 if (then.sec > 10) { | |
3787 nowStr = now.toSecText().c_str(); | |
3788 thenStr = then.toSecText().c_str(); | |
3789 remainingStr = (then - now).toSecText().c_str(); | |
3790 m_lastPlayStatusSec = now.sec; | |
3791 } else { | |
3792 nowStr = now.toText(true).c_str(); | |
3793 thenStr = then.toText(true).c_str(); | |
3794 remainingStr = (then - now).toText(true).c_str(); | |
3795 } | |
3796 | |
3797 m_myStatusMessage = tr("Playing: %1 of %2 (%3 remaining)") | |
3798 .arg(nowStr).arg(thenStr).arg(remainingStr); | |
3799 | |
3800 statusBar()->showMessage(m_myStatusMessage); | |
3801 } | |
3802 | |
3803 void | |
3804 MainWindow::globalCentreFrameChanged(unsigned long ) | |
3805 { | |
3806 if ((m_playSource && m_playSource->isPlaying()) || !getMainModel()) return; | |
3807 Pane *p = 0; | |
3808 if (!m_paneStack || !(p = m_paneStack->getCurrentPane())) return; | |
3809 if (!p->getFollowGlobalPan()) return; | |
3810 updateVisibleRangeDisplay(p); | |
3811 } | |
3812 | |
3813 void | |
3814 MainWindow::viewCentreFrameChanged(View *v, unsigned long ) | |
3815 { | |
3816 if ((m_playSource && m_playSource->isPlaying()) || !getMainModel()) return; | |
3817 Pane *p = 0; | |
3818 if (!m_paneStack || !(p = m_paneStack->getCurrentPane())) return; | |
3819 if (v == p) updateVisibleRangeDisplay(p); | |
3820 } | |
3821 | |
3822 void | |
3823 MainWindow::viewZoomLevelChanged(View *v, unsigned long , bool ) | |
3824 { | |
3825 if ((m_playSource && m_playSource->isPlaying()) || !getMainModel()) return; | |
3826 Pane *p = 0; | |
3827 if (!m_paneStack || !(p = m_paneStack->getCurrentPane())) return; | |
3828 if (v == p) updateVisibleRangeDisplay(p); | |
3829 } | |
3830 | |
3831 void | |
3832 MainWindow::updateVisibleRangeDisplay(Pane *p) const | |
3833 { | |
3834 if (!getMainModel() || !p) { | |
3835 return; | |
3836 } | |
3837 | |
3838 bool haveSelection = false; | |
3839 size_t startFrame = 0, endFrame = 0; | |
3840 | |
3841 if (m_viewManager && m_viewManager->haveInProgressSelection()) { | |
3842 | |
3843 bool exclusive = false; | |
3844 Selection s = m_viewManager->getInProgressSelection(exclusive); | |
3845 | |
3846 if (!s.isEmpty()) { | |
3847 haveSelection = true; | |
3848 startFrame = s.getStartFrame(); | |
3849 endFrame = s.getEndFrame(); | |
3850 } | |
3851 } | |
3852 | |
3853 if (!haveSelection) { | |
3854 startFrame = p->getFirstVisibleFrame(); | |
3855 endFrame = p->getLastVisibleFrame(); | |
3856 } | |
3857 | |
3858 RealTime start = RealTime::frame2RealTime | |
3859 (startFrame, getMainModel()->getSampleRate()); | |
3860 | |
3861 RealTime end = RealTime::frame2RealTime | |
3862 (endFrame, getMainModel()->getSampleRate()); | |
3863 | |
3864 RealTime duration = end - start; | |
3865 | |
3866 QString startStr, endStr, durationStr; | |
3867 startStr = start.toText(true).c_str(); | |
3868 endStr = end.toText(true).c_str(); | |
3869 durationStr = duration.toText(true).c_str(); | |
3870 | |
3871 if (haveSelection) { | |
3872 m_myStatusMessage = tr("Selection: %1 to %2 (duration %3)") | |
3873 .arg(startStr).arg(endStr).arg(durationStr); | |
3874 } else { | |
3875 m_myStatusMessage = tr("Visible: %1 to %2 (duration %3)") | |
3876 .arg(startStr).arg(endStr).arg(durationStr); | |
3877 } | |
3878 | |
3879 statusBar()->showMessage(m_myStatusMessage); | |
3880 } | |
3881 | |
3882 void | |
3883 MainWindow::outputLevelsChanged(float left, float right) | |
3884 { | |
3885 m_fader->setPeakLeft(left); | |
3886 m_fader->setPeakRight(right); | |
3887 } | |
3888 | |
3889 void | |
3890 MainWindow::sampleRateMismatch(size_t requested, size_t actual, | |
3891 bool willResample) | |
3892 { | |
3893 if (!willResample) { | |
3894 //!!! more helpful message needed | |
3895 QMessageBox::information | |
3896 (this, tr("Sample rate mismatch"), | |
3897 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.") | |
3898 .arg(requested).arg(actual)); | |
3899 } | |
3900 | |
3901 updateDescriptionLabel(); | |
3902 } | |
3903 | |
3904 void | |
3905 MainWindow::audioOverloadPluginDisabled() | |
3906 { | |
3907 QMessageBox::information | |
3908 (this, tr("Audio processing overload"), | |
3909 tr("Audio effects plugin auditioning has been disabled\ndue to a processing overload.")); | |
3910 } | |
3911 | |
3912 void | |
3913 MainWindow::layerAdded(Layer *) | |
3914 { | |
3915 // std::cerr << "MainWindow::layerAdded(" << layer << ")" << std::endl; | |
3916 // setupExistingLayersMenu(); | |
3917 updateMenuStates(); | |
3918 } | |
3919 | |
3920 void | |
3921 MainWindow::layerRemoved(Layer *) | |
3922 { | |
3923 // std::cerr << "MainWindow::layerRemoved(" << layer << ")" << std::endl; | |
3924 setupExistingLayersMenus(); | |
3925 updateMenuStates(); | |
3926 } | |
3927 | |
3928 void | |
3929 MainWindow::layerAboutToBeDeleted(Layer *layer) | |
3930 { | |
3931 // std::cerr << "MainWindow::layerAboutToBeDeleted(" << layer << ")" << std::endl; | |
3932 if (layer == m_timeRulerLayer) { | |
3933 // std::cerr << "(this is the time ruler layer)" << std::endl; | |
3934 m_timeRulerLayer = 0; | |
3935 } | |
3936 } | |
3937 | |
3938 void | |
3939 MainWindow::layerInAView(Layer *layer, bool inAView) | |
3940 { | |
3941 // std::cerr << "MainWindow::layerInAView(" << layer << "," << inAView << ")" << std::endl; | |
3942 | |
3943 // Check whether we need to add or remove model from play source | |
3944 Model *model = layer->getModel(); | |
3945 if (model) { | |
3946 if (inAView) { | |
3947 m_playSource->addModel(model); | |
3948 } else { | |
3949 bool found = false; | |
3950 for (int i = 0; i < m_paneStack->getPaneCount(); ++i) { | |
3951 Pane *pane = m_paneStack->getPane(i); | |
3952 if (!pane) continue; | |
3953 for (int j = 0; j < pane->getLayerCount(); ++j) { | |
3954 Layer *pl = pane->getLayer(j); | |
3955 if (pl && pl->getModel() == model) { | |
3956 found = true; | |
3957 break; | |
3958 } | |
3959 } | |
3960 if (found) break; | |
3961 } | |
3962 if (!found) m_playSource->removeModel(model); | |
3963 } | |
3964 } | |
3965 | |
3966 setupExistingLayersMenus(); | |
3967 updateMenuStates(); | |
3968 } | |
3969 | |
3970 void | |
3971 MainWindow::modelAdded(Model *model) | |
3972 { | |
3973 // std::cerr << "MainWindow::modelAdded(" << model << ")" << std::endl; | |
3974 m_playSource->addModel(model); | |
3975 if (dynamic_cast<DenseTimeValueModel *>(model)) { | |
3976 setupPaneAndLayerMenus(); | |
3977 } | |
3978 } | |
3979 | |
3980 void | |
3981 MainWindow::mainModelChanged(WaveFileModel *model) | |
3982 { | |
3983 // std::cerr << "MainWindow::mainModelChanged(" << model << ")" << std::endl; | |
3984 updateDescriptionLabel(); | |
3985 m_panLayer->setModel(model); | |
3986 if (model) m_viewManager->setMainModelSampleRate(model->getSampleRate()); | |
3987 if (model && !m_playTarget && m_audioOutput) createPlayTarget(); | |
3988 } | |
3989 | |
3990 void | |
3991 MainWindow::modelAboutToBeDeleted(Model *model) | |
3992 { | |
3993 // std::cerr << "MainWindow::modelAboutToBeDeleted(" << model << ")" << std::endl; | |
3994 m_playSource->removeModel(model); | |
3995 FFTDataServer::modelAboutToBeDeleted(model); | |
3996 } | |
3997 | |
3998 void | |
3999 MainWindow::modelGenerationFailed(QString transformName) | |
4000 { | |
4001 QMessageBox::warning | |
4002 (this, | |
4003 tr("Failed to generate layer"), | |
4004 tr("Failed to generate a derived layer.\n\nThe layer transform \"%1\" failed.\n\nThis probably means that a plugin failed to initialise, perhaps because it\nrejected the processing block size that was requested.") | |
4005 .arg(transformName), | |
4006 QMessageBox::Ok, 0); | |
4007 } | |
4008 | |
4009 void | |
4010 MainWindow::modelRegenerationFailed(QString layerName, QString transformName) | |
4011 { | |
4012 QMessageBox::warning | |
4013 (this, | |
4014 tr("Failed to regenerate layer"), | |
4015 tr("Failed to regenerate derived layer \"%1\".\n\nThe layer transform \"%2\" failed to run.\n\nThis probably means the layer used a plugin that is not currently available.") | |
4016 .arg(layerName).arg(transformName), | |
4017 QMessageBox::Ok, 0); | |
4018 } | |
4019 | |
4020 void | |
4021 MainWindow::rightButtonMenuRequested(Pane *pane, QPoint position) | |
4022 { | |
4023 // std::cerr << "MainWindow::rightButtonMenuRequested(" << pane << ", " << position.x() << ", " << position.y() << ")" << std::endl; | |
4024 m_paneStack->setCurrentPane(pane); | |
4025 m_rightButtonMenu->popup(position); | |
4026 } | |
4027 | |
4028 void | |
4029 MainWindow::propertyStacksResized() | |
4030 { | |
4031 /* | |
4032 std::cerr << "MainWindow::propertyStacksResized" << std::endl; | |
4033 Pane *pane = m_paneStack->getCurrentPane(); | |
4034 if (pane && m_overview) { | |
4035 std::cerr << "Fixed width: " << pane->width() << std::endl; | |
4036 m_overview->setFixedWidth(pane->width()); | |
4037 } | |
4038 */ | |
4039 } | |
4040 | |
4041 void | |
4042 MainWindow::showLayerTree() | |
4043 { | |
4044 QTreeView *view = new QTreeView(); | |
4045 LayerTreeModel *tree = new LayerTreeModel(m_paneStack); | |
4046 view->expand(tree->index(0, 0, QModelIndex())); | |
4047 view->setModel(tree); | |
4048 view->show(); | |
4049 } | |
4050 | |
4051 void | |
4052 MainWindow::pollOSC() | |
4053 { | |
4054 if (!m_oscQueue || m_oscQueue->isEmpty()) return; | |
4055 std::cerr << "MainWindow::pollOSC: have " << m_oscQueue->getMessagesAvailable() << " messages" << std::endl; | |
4056 | |
4057 if (m_openingAudioFile) return; | |
4058 | |
4059 OSCMessage message = m_oscQueue->readMessage(); | |
4060 | |
4061 if (message.getTarget() != 0) { | |
4062 return; //!!! for now -- this class is target 0, others not handled yet | |
4063 } | |
4064 | |
4065 handleOSCMessage(message); | |
4066 } | |
4067 | |
4068 void | |
4069 MainWindow::handleOSCMessage(const OSCMessage &message) | |
4070 { | |
4071 std::cerr << "MainWindow::handleOSCMessage: thread id = " | |
4072 << QThread::currentThreadId() << std::endl; | |
4073 | |
4074 // This large function should really be abstracted out. | |
4075 | |
4076 if (message.getMethod() == "open") { | |
4077 | |
4078 if (message.getArgCount() == 1 && | |
4079 message.getArg(0).canConvert(QVariant::String)) { | |
4080 QString path = message.getArg(0).toString(); | |
4081 if (openSomeFile(path, ReplaceMainModel) != FileOpenSucceeded) { | |
4082 std::cerr << "MainWindow::handleOSCMessage: File open failed for path \"" | |
4083 << path.toStdString() << "\"" << std::endl; | |
4084 } | |
4085 //!!! we really need to spin here and not return until the | |
4086 // file has been completely decoded... | |
4087 } | |
4088 | |
4089 } else if (message.getMethod() == "openadditional") { | |
4090 | |
4091 if (message.getArgCount() == 1 && | |
4092 message.getArg(0).canConvert(QVariant::String)) { | |
4093 QString path = message.getArg(0).toString(); | |
4094 if (openSomeFile(path, CreateAdditionalModel) != FileOpenSucceeded) { | |
4095 std::cerr << "MainWindow::handleOSCMessage: File open failed for path \"" | |
4096 << path.toStdString() << "\"" << std::endl; | |
4097 } | |
4098 } | |
4099 | |
4100 } else if (message.getMethod() == "recent" || | |
4101 message.getMethod() == "last") { | |
4102 | |
4103 int n = 0; | |
4104 if (message.getMethod() == "recent" && | |
4105 message.getArgCount() == 1 && | |
4106 message.getArg(0).canConvert(QVariant::Int)) { | |
4107 n = message.getArg(0).toInt() - 1; | |
4108 } | |
4109 std::vector<QString> recent = m_recentFiles.getRecent(); | |
4110 if (n >= 0 && n < int(recent.size())) { | |
4111 if (openSomeFile(recent[n], ReplaceMainModel) != FileOpenSucceeded) { | |
4112 std::cerr << "MainWindow::handleOSCMessage: File open failed for path \"" | |
4113 << recent[n].toStdString() << "\"" << std::endl; | |
4114 } | |
4115 } | |
4116 | |
4117 } else if (message.getMethod() == "save") { | |
4118 | |
4119 QString path; | |
4120 if (message.getArgCount() == 1 && | |
4121 message.getArg(0).canConvert(QVariant::String)) { | |
4122 path = message.getArg(0).toString(); | |
4123 if (QFileInfo(path).exists()) { | |
4124 std::cerr << "MainWindow::handleOSCMessage: Refusing to overwrite existing file in save" << std::endl; | |
4125 } else { | |
4126 saveSessionFile(path); | |
4127 } | |
4128 } | |
4129 | |
4130 } else if (message.getMethod() == "export") { | |
4131 | |
4132 QString path; | |
4133 if (getMainModel()) { | |
4134 if (message.getArgCount() == 1 && | |
4135 message.getArg(0).canConvert(QVariant::String)) { | |
4136 path = message.getArg(0).toString(); | |
4137 if (QFileInfo(path).exists()) { | |
4138 std::cerr << "MainWindow::handleOSCMessage: Refusing to overwrite existing file in export" << std::endl; | |
4139 } else { | |
4140 WavFileWriter writer(path, | |
4141 getMainModel()->getSampleRate(), | |
4142 getMainModel()->getChannelCount()); | |
4143 MultiSelection ms = m_viewManager->getSelection(); | |
4144 if (!ms.getSelections().empty()) { | |
4145 writer.writeModel(getMainModel(), &ms); | |
4146 } else { | |
4147 writer.writeModel(getMainModel()); | |
4148 } | |
4149 } | |
4150 } | |
4151 } | |
4152 | |
4153 } else if (message.getMethod() == "jump" || | |
4154 message.getMethod() == "play") { | |
4155 | |
4156 if (getMainModel()) { | |
4157 | |
4158 unsigned long frame = m_viewManager->getPlaybackFrame(); | |
4159 bool selection = false; | |
4160 bool play = (message.getMethod() == "play"); | |
4161 | |
4162 if (message.getArgCount() == 1) { | |
4163 | |
4164 if (message.getArg(0).canConvert(QVariant::String) && | |
4165 message.getArg(0).toString() == "selection") { | |
4166 | |
4167 selection = true; | |
4168 | |
4169 } else if (message.getArg(0).canConvert(QVariant::String) && | |
4170 message.getArg(0).toString() == "end") { | |
4171 | |
4172 frame = getMainModel()->getEndFrame(); | |
4173 | |
4174 } else if (message.getArg(0).canConvert(QVariant::Double)) { | |
4175 | |
4176 double time = message.getArg(0).toDouble(); | |
4177 if (time < 0.0) time = 0.0; | |
4178 | |
4179 frame = lrint(time * getMainModel()->getSampleRate()); | |
4180 } | |
4181 } | |
4182 | |
4183 if (frame > getMainModel()->getEndFrame()) { | |
4184 frame = getMainModel()->getEndFrame(); | |
4185 } | |
4186 | |
4187 if (play) { | |
4188 m_viewManager->setPlaySelectionMode(selection); | |
4189 } | |
4190 | |
4191 if (selection) { | |
4192 MultiSelection::SelectionList sl = m_viewManager->getSelections(); | |
4193 if (!sl.empty()) { | |
4194 frame = sl.begin()->getStartFrame(); | |
4195 } | |
4196 } | |
4197 | |
4198 m_viewManager->setPlaybackFrame(frame); | |
4199 | |
4200 if (play && !m_playSource->isPlaying()) { | |
4201 m_playSource->play(frame); | |
4202 } | |
4203 } | |
4204 | |
4205 } else if (message.getMethod() == "stop") { | |
4206 | |
4207 if (m_playSource->isPlaying()) m_playSource->stop(); | |
4208 | |
4209 } else if (message.getMethod() == "loop") { | |
4210 | |
4211 if (message.getArgCount() == 1 && | |
4212 message.getArg(0).canConvert(QVariant::String)) { | |
4213 | |
4214 QString str = message.getArg(0).toString(); | |
4215 if (str == "on") { | |
4216 m_viewManager->setPlayLoopMode(true); | |
4217 } else if (str == "off") { | |
4218 m_viewManager->setPlayLoopMode(false); | |
4219 } | |
4220 } | |
4221 | |
4222 } else if (message.getMethod() == "select" || | |
4223 message.getMethod() == "addselect") { | |
4224 | |
4225 if (getMainModel()) { | |
4226 | |
4227 int f0 = getMainModel()->getStartFrame(); | |
4228 int f1 = getMainModel()->getEndFrame(); | |
4229 | |
4230 bool done = false; | |
4231 | |
4232 if (message.getArgCount() == 2 && | |
4233 message.getArg(0).canConvert(QVariant::Double) && | |
4234 message.getArg(1).canConvert(QVariant::Double)) { | |
4235 | |
4236 double t0 = message.getArg(0).toDouble(); | |
4237 double t1 = message.getArg(1).toDouble(); | |
4238 if (t1 < t0) { double temp = t0; t0 = t1; t1 = temp; } | |
4239 if (t0 < 0.0) t0 = 0.0; | |
4240 if (t1 < 0.0) t1 = 0.0; | |
4241 | |
4242 f0 = lrint(t0 * getMainModel()->getSampleRate()); | |
4243 f1 = lrint(t1 * getMainModel()->getSampleRate()); | |
4244 | |
4245 Pane *pane = m_paneStack->getCurrentPane(); | |
4246 Layer *layer = 0; | |
4247 if (pane) layer = pane->getSelectedLayer(); | |
4248 if (layer) { | |
4249 size_t resolution; | |
4250 layer->snapToFeatureFrame(pane, f0, resolution, | |
4251 Layer::SnapLeft); | |
4252 layer->snapToFeatureFrame(pane, f1, resolution, | |
4253 Layer::SnapRight); | |
4254 } | |
4255 | |
4256 } else if (message.getArgCount() == 1 && | |
4257 message.getArg(0).canConvert(QVariant::String)) { | |
4258 | |
4259 QString str = message.getArg(0).toString(); | |
4260 if (str == "none") { | |
4261 m_viewManager->clearSelections(); | |
4262 done = true; | |
4263 } | |
4264 } | |
4265 | |
4266 if (!done) { | |
4267 if (message.getMethod() == "select") { | |
4268 m_viewManager->setSelection(Selection(f0, f1)); | |
4269 } else { | |
4270 m_viewManager->addSelection(Selection(f0, f1)); | |
4271 } | |
4272 } | |
4273 } | |
4274 | |
4275 } else if (message.getMethod() == "add") { | |
4276 | |
4277 if (getMainModel()) { | |
4278 | |
4279 if (message.getArgCount() >= 1 && | |
4280 message.getArg(0).canConvert(QVariant::String)) { | |
4281 | |
4282 int channel = -1; | |
4283 if (message.getArgCount() == 2 && | |
4284 message.getArg(0).canConvert(QVariant::Int)) { | |
4285 channel = message.getArg(0).toInt(); | |
4286 if (channel < -1 || | |
4287 channel > int(getMainModel()->getChannelCount())) { | |
4288 std::cerr << "WARNING: MainWindow::handleOSCMessage: channel " | |
4289 << channel << " out of range" << std::endl; | |
4290 channel = -1; | |
4291 } | |
4292 } | |
4293 | |
4294 QString str = message.getArg(0).toString(); | |
4295 | |
4296 LayerFactory::LayerType type = | |
4297 LayerFactory::getInstance()->getLayerTypeForName(str); | |
4298 | |
4299 if (type == LayerFactory::UnknownLayer) { | |
4300 std::cerr << "WARNING: MainWindow::handleOSCMessage: unknown layer " | |
4301 << "type " << str.toStdString() << std::endl; | |
4302 } else { | |
4303 | |
4304 PaneConfiguration configuration(type, | |
4305 getMainModel(), | |
4306 channel); | |
4307 | |
4308 addPane(configuration, | |
4309 tr("Add %1 Pane") | |
4310 .arg(LayerFactory::getInstance()-> | |
4311 getLayerPresentationName(type))); | |
4312 } | |
4313 } | |
4314 } | |
4315 | |
4316 } else if (message.getMethod() == "undo") { | |
4317 | |
4318 CommandHistory::getInstance()->undo(); | |
4319 | |
4320 } else if (message.getMethod() == "redo") { | |
4321 | |
4322 CommandHistory::getInstance()->redo(); | |
4323 | |
4324 } else if (message.getMethod() == "set") { | |
4325 | |
4326 if (message.getArgCount() == 2 && | |
4327 message.getArg(0).canConvert(QVariant::String) && | |
4328 message.getArg(1).canConvert(QVariant::Double)) { | |
4329 | |
4330 QString property = message.getArg(0).toString(); | |
4331 float value = (float)message.getArg(1).toDouble(); | |
4332 | |
4333 if (property == "gain") { | |
4334 if (value < 0.0) value = 0.0; | |
4335 m_fader->setValue(value); | |
4336 if (m_playTarget) m_playTarget->setOutputGain(value); | |
4337 } else if (property == "speedup") { | |
4338 m_playSpeed->setMappedValue(value); | |
4339 } else if (property == "overlays") { | |
4340 if (value < 0.5) { | |
4341 m_viewManager->setOverlayMode(ViewManager::NoOverlays); | |
4342 } else if (value < 1.5) { | |
4343 m_viewManager->setOverlayMode(ViewManager::MinimalOverlays); | |
4344 } else if (value < 2.5) { | |
4345 m_viewManager->setOverlayMode(ViewManager::StandardOverlays); | |
4346 } else { | |
4347 m_viewManager->setOverlayMode(ViewManager::AllOverlays); | |
4348 } | |
4349 } else if (property == "zoomwheels") { | |
4350 m_viewManager->setZoomWheelsEnabled(value > 0.5); | |
4351 } else if (property == "propertyboxes") { | |
4352 bool toggle = ((value < 0.5) != | |
4353 (m_paneStack->getLayoutStyle() == PaneStack::NoPropertyStacks)); | |
4354 if (toggle) togglePropertyBoxes(); | |
4355 } | |
4356 | |
4357 } else { | |
4358 PropertyContainer *container = 0; | |
4359 Pane *pane = m_paneStack->getCurrentPane(); | |
4360 if (pane && | |
4361 message.getArgCount() == 3 && | |
4362 message.getArg(0).canConvert(QVariant::String) && | |
4363 message.getArg(1).canConvert(QVariant::String) && | |
4364 message.getArg(2).canConvert(QVariant::String)) { | |
4365 if (message.getArg(0).toString() == "pane") { | |
4366 container = pane->getPropertyContainer(0); | |
4367 } else if (message.getArg(0).toString() == "layer") { | |
4368 container = pane->getSelectedLayer(); | |
4369 } | |
4370 } | |
4371 if (container) { | |
4372 QString nameString = message.getArg(1).toString(); | |
4373 QString valueString = message.getArg(2).toString(); | |
4374 container->setPropertyWithCommand(nameString, valueString); | |
4375 } | |
4376 } | |
4377 | |
4378 } else if (message.getMethod() == "setcurrent") { | |
4379 | |
4380 int paneIndex = -1, layerIndex = -1; | |
4381 bool wantLayer = false; | |
4382 | |
4383 if (message.getArgCount() >= 1 && | |
4384 message.getArg(0).canConvert(QVariant::Int)) { | |
4385 | |
4386 paneIndex = message.getArg(0).toInt() - 1; | |
4387 | |
4388 if (message.getArgCount() >= 2 && | |
4389 message.getArg(1).canConvert(QVariant::Int)) { | |
4390 wantLayer = true; | |
4391 layerIndex = message.getArg(1).toInt() - 1; | |
4392 } | |
4393 } | |
4394 | |
4395 if (paneIndex >= 0 && paneIndex < m_paneStack->getPaneCount()) { | |
4396 Pane *pane = m_paneStack->getPane(paneIndex); | |
4397 m_paneStack->setCurrentPane(pane); | |
4398 if (layerIndex >= 0 && layerIndex < pane->getLayerCount()) { | |
4399 Layer *layer = pane->getLayer(layerIndex); | |
4400 m_paneStack->setCurrentLayer(pane, layer); | |
4401 } else if (wantLayer && layerIndex == -1) { | |
4402 m_paneStack->setCurrentLayer(pane, 0); | |
4403 } | |
4404 } | |
4405 | |
4406 } else if (message.getMethod() == "delete") { | |
4407 | |
4408 if (message.getArgCount() == 1 && | |
4409 message.getArg(0).canConvert(QVariant::String)) { | |
4410 | |
4411 QString target = message.getArg(0).toString(); | |
4412 | |
4413 if (target == "pane") { | |
4414 | |
4415 deleteCurrentPane(); | |
4416 | |
4417 } else if (target == "layer") { | |
4418 | |
4419 deleteCurrentLayer(); | |
4420 | |
4421 } else { | |
4422 | |
4423 std::cerr << "WARNING: MainWindow::handleOSCMessage: Unknown delete target " << target.toStdString() << std::endl; | |
4424 } | |
4425 } | |
4426 | |
4427 } else if (message.getMethod() == "zoom") { | |
4428 | |
4429 if (message.getArgCount() == 1) { | |
4430 if (message.getArg(0).canConvert(QVariant::String) && | |
4431 message.getArg(0).toString() == "in") { | |
4432 zoomIn(); | |
4433 } else if (message.getArg(0).canConvert(QVariant::String) && | |
4434 message.getArg(0).toString() == "out") { | |
4435 zoomOut(); | |
4436 } else if (message.getArg(0).canConvert(QVariant::String) && | |
4437 message.getArg(0).toString() == "default") { | |
4438 zoomDefault(); | |
4439 } else if (message.getArg(0).canConvert(QVariant::Double)) { | |
4440 double level = message.getArg(0).toDouble(); | |
4441 Pane *currentPane = m_paneStack->getCurrentPane(); | |
4442 if (level < 1.0) level = 1.0; | |
4443 if (currentPane) currentPane->setZoomLevel(lrint(level)); | |
4444 } | |
4445 } | |
4446 | |
4447 } else if (message.getMethod() == "zoomvertical") { | |
4448 | |
4449 Pane *pane = m_paneStack->getCurrentPane(); | |
4450 Layer *layer = 0; | |
4451 if (pane && pane->getLayerCount() > 0) { | |
4452 layer = pane->getLayer(pane->getLayerCount() - 1); | |
4453 } | |
4454 int defaultStep = 0; | |
4455 int steps = 0; | |
4456 if (layer) { | |
4457 steps = layer->getVerticalZoomSteps(defaultStep); | |
4458 if (message.getArgCount() == 1 && steps > 0) { | |
4459 if (message.getArg(0).canConvert(QVariant::String) && | |
4460 message.getArg(0).toString() == "in") { | |
4461 int step = layer->getCurrentVerticalZoomStep() + 1; | |
4462 if (step < steps) layer->setVerticalZoomStep(step); | |
4463 } else if (message.getArg(0).canConvert(QVariant::String) && | |
4464 message.getArg(0).toString() == "out") { | |
4465 int step = layer->getCurrentVerticalZoomStep() - 1; | |
4466 if (step >= 0) layer->setVerticalZoomStep(step); | |
4467 } else if (message.getArg(0).canConvert(QVariant::String) && | |
4468 message.getArg(0).toString() == "default") { | |
4469 layer->setVerticalZoomStep(defaultStep); | |
4470 } | |
4471 } else if (message.getArgCount() == 2) { | |
4472 if (message.getArg(0).canConvert(QVariant::Double) && | |
4473 message.getArg(1).canConvert(QVariant::Double)) { | |
4474 double min = message.getArg(0).toDouble(); | |
4475 double max = message.getArg(1).toDouble(); | |
4476 layer->setDisplayExtents(min, max); | |
4477 } | |
4478 } | |
4479 } | |
4480 | |
4481 } else if (message.getMethod() == "quit") { | |
4482 | |
4483 m_abandoning = true; | |
4484 close(); | |
4485 | |
4486 } else if (message.getMethod() == "resize") { | |
4487 | |
4488 if (message.getArgCount() == 2) { | |
4489 | |
4490 int width = 0, height = 0; | |
4491 | |
4492 if (message.getArg(1).canConvert(QVariant::Int)) { | |
4493 | |
4494 height = message.getArg(1).toInt(); | |
4495 | |
4496 if (message.getArg(0).canConvert(QVariant::String) && | |
4497 message.getArg(0).toString() == "pane") { | |
4498 | |
4499 Pane *pane = m_paneStack->getCurrentPane(); | |
4500 if (pane) pane->resize(pane->width(), height); | |
4501 | |
4502 } else if (message.getArg(0).canConvert(QVariant::Int)) { | |
4503 | |
4504 width = message.getArg(0).toInt(); | |
4505 resize(width, height); | |
4506 } | |
4507 } | |
4508 } | |
4509 | |
4510 } else if (message.getMethod() == "transform") { | |
4511 | |
4512 Pane *pane = m_paneStack->getCurrentPane(); | |
4513 | |
4514 if (getMainModel() && | |
4515 pane && | |
4516 message.getArgCount() == 1 && | |
4517 message.getArg(0).canConvert(QVariant::String)) { | |
4518 | |
4519 TransformId transform = message.getArg(0).toString(); | |
4520 | |
4521 Layer *newLayer = m_document->createDerivedLayer | |
4522 (transform, | |
4523 getMainModel(), | |
4524 TransformFactory::getInstance()->getDefaultContextForTransform | |
4525 (transform, getMainModel()), | |
4526 ""); | |
4527 | |
4528 if (newLayer) { | |
4529 m_document->addLayerToView(pane, newLayer); | |
4530 m_recentTransforms.add(transform); | |
4531 m_paneStack->setCurrentLayer(pane, newLayer); | |
4532 } | |
4533 } | |
4534 | |
4535 } else { | |
4536 std::cerr << "WARNING: MainWindow::handleOSCMessage: Unknown or unsupported " | |
4537 << "method \"" << message.getMethod().toStdString() | |
4538 << "\"" << std::endl; | |
4539 } | |
4540 | |
4541 } | |
4542 | |
4543 void | |
4544 MainWindow::preferences() | |
4545 { | |
4546 if (!m_preferencesDialog.isNull()) { | |
4547 m_preferencesDialog->show(); | |
4548 m_preferencesDialog->raise(); | |
4549 return; | |
4550 } | |
4551 | |
4552 m_preferencesDialog = new PreferencesDialog(this); | |
4553 | |
4554 // DeleteOnClose is safe here, because m_preferencesDialog is a | |
4555 // QPointer that will be zeroed when the dialog is deleted. We | |
4556 // use it in preference to leaving the dialog lying around because | |
4557 // if you Cancel the dialog, it resets the preferences state | |
4558 // without resetting its own widgets, so its state will be | |
4559 // incorrect when next shown unless we construct it afresh | |
4560 m_preferencesDialog->setAttribute(Qt::WA_DeleteOnClose); | |
4561 | |
4562 m_preferencesDialog->show(); | |
4563 } | |
4564 | |
4565 void | |
4566 MainWindow::mouseEnteredWidget() | |
4567 { | |
4568 QWidget *w = dynamic_cast<QWidget *>(sender()); | |
4569 if (!w) return; | |
4570 | |
4571 if (w == m_fader) { | |
4572 contextHelpChanged(tr("Adjust the master playback level")); | |
4573 } else if (w == m_playSpeed) { | |
4574 contextHelpChanged(tr("Adjust the master playback speed")); | |
4575 } else if (w == m_playSharpen && w->isEnabled()) { | |
4576 contextHelpChanged(tr("Toggle transient sharpening for playback time scaling")); | |
4577 } else if (w == m_playMono && w->isEnabled()) { | |
4578 contextHelpChanged(tr("Toggle mono mode for playback time scaling")); | |
4579 } | |
4580 } | |
4581 | |
4582 void | |
4583 MainWindow::mouseLeftWidget() | |
4584 { | |
4585 contextHelpChanged(""); | |
4586 } | |
4587 | |
4588 void | |
4589 MainWindow::inProgressSelectionChanged() | |
4590 { | |
4591 Pane *currentPane = 0; | |
4592 if (m_paneStack) currentPane = m_paneStack->getCurrentPane(); | |
4593 if (currentPane) updateVisibleRangeDisplay(currentPane); | |
4594 } | |
4595 | |
4596 void | |
4597 MainWindow::contextHelpChanged(const QString &s) | |
4598 { | |
4599 if (s == "" && m_myStatusMessage != "") { | |
4600 statusBar()->showMessage(m_myStatusMessage); | |
4601 return; | |
4602 } | |
4603 statusBar()->showMessage(s); | |
4604 } | |
4605 | |
4606 void | |
4607 MainWindow::website() | |
4608 { | |
4609 openHelpUrl(tr("http://www.sonicvisualiser.org/")); | |
4610 } | |
4611 | |
4612 void | |
4613 MainWindow::help() | |
4614 { | |
4615 openHelpUrl(tr("http://www.sonicvisualiser.org/doc/reference/1.0/en/")); | |
4616 } | |
4617 | |
4618 void | |
4619 MainWindow::openHelpUrl(QString url) | |
4620 { | |
4621 // This method mostly lifted from Qt Assistant source code | |
4622 | |
4623 QProcess *process = new QProcess(this); | |
4624 connect(process, SIGNAL(finished(int)), process, SLOT(deleteLater())); | |
4625 | |
4626 QStringList args; | |
4627 | |
4628 #ifdef Q_OS_MAC | |
4629 args.append(url); | |
4630 process->start("open", args); | |
4631 #else | |
4632 #ifdef Q_OS_WIN32 | |
4633 | |
4634 QString pf(getenv("ProgramFiles")); | |
4635 QString command = pf + QString("\\Internet Explorer\\IEXPLORE.EXE"); | |
4636 | |
4637 args.append(url); | |
4638 process->start(command, args); | |
4639 | |
4640 #else | |
4641 #ifdef Q_WS_X11 | |
4642 if (!qgetenv("KDE_FULL_SESSION").isEmpty()) { | |
4643 args.append("exec"); | |
4644 args.append(url); | |
4645 process->start("kfmclient", args); | |
4646 } else if (!qgetenv("BROWSER").isEmpty()) { | |
4647 args.append(url); | |
4648 process->start(qgetenv("BROWSER"), args); | |
4649 } else { | |
4650 args.append(url); | |
4651 process->start("firefox", args); | |
4652 } | |
4653 #endif | |
4654 #endif | |
4655 #endif | |
4656 } | |
4657 | |
4658 void | |
4659 MainWindow::about() | |
4660 { | |
4661 bool debug = false; | |
4662 QString version = "(unknown version)"; | |
4663 | |
4664 #ifdef BUILD_DEBUG | |
4665 debug = true; | |
4666 #endif | |
4667 #ifdef SV_VERSION | |
4668 #ifdef SVNREV | |
4669 version = tr("Release %1 : Revision %2").arg(SV_VERSION).arg(SVNREV); | |
4670 #else | |
4671 version = tr("Release %1").arg(SV_VERSION); | |
4672 #endif | |
4673 #else | |
4674 #ifdef SVNREV | |
4675 version = tr("Unreleased : Revision %1").arg(SVNREV); | |
4676 #endif | |
4677 #endif | |
4678 | |
4679 QString aboutText; | |
4680 | |
4681 aboutText += tr("<h3>About Sonic Visualiser</h3>"); | |
4682 aboutText += tr("<p>Sonic Visualiser is a program for viewing and exploring audio data for<br>semantic music analysis and annotation.</p>"); | |
4683 aboutText += tr("<p>%1 : %2 configuration</p>") | |
4684 .arg(version) | |
4685 .arg(debug ? tr("Debug") : tr("Release")); | |
4686 | |
4687 #ifndef BUILD_STATIC | |
4688 aboutText += tr("<br>Using Qt v%1 © Trolltech AS").arg(QT_VERSION_STR); | |
4689 #else | |
4690 #ifdef QT_SHARED | |
4691 aboutText += tr("<br>Using Qt v%1 © Trolltech AS").arg(QT_VERSION_STR); | |
4692 #endif | |
4693 #endif | |
4694 | |
4695 #ifdef BUILD_STATIC | |
4696 aboutText += tr("<p>Statically linked"); | |
4697 #ifndef QT_SHARED | |
4698 aboutText += tr("<br>With Qt (v%1) © Trolltech AS").arg(QT_VERSION_STR); | |
4699 #endif | |
4700 #ifdef HAVE_JACK | |
4701 #ifdef JACK_VERSION | |
4702 aboutText += tr("<br>With JACK audio output (v%1) © Paul Davis and Jack O'Quin").arg(JACK_VERSION); | |
4703 #else | |
4704 aboutText += tr("<br>With JACK audio output © Paul Davis and Jack O'Quin"); | |
4705 #endif | |
4706 #endif | |
4707 #ifdef HAVE_PORTAUDIO | |
4708 aboutText += tr("<br>With PortAudio audio output © Ross Bencina and Phil Burk"); | |
4709 #endif | |
4710 #ifdef HAVE_OGGZ | |
4711 #ifdef OGGZ_VERSION | |
4712 aboutText += tr("<br>With Ogg file decoder (oggz v%1, fishsound v%2) © CSIRO Australia").arg(OGGZ_VERSION).arg(FISHSOUND_VERSION); | |
4713 #else | |
4714 aboutText += tr("<br>With Ogg file decoder © CSIRO Australia"); | |
4715 #endif | |
4716 #endif | |
4717 #ifdef HAVE_MAD | |
4718 #ifdef MAD_VERSION | |
4719 aboutText += tr("<br>With MAD mp3 decoder (v%1) © Underbit Technologies Inc").arg(MAD_VERSION); | |
4720 #else | |
4721 aboutText += tr("<br>With MAD mp3 decoder © Underbit Technologies Inc"); | |
4722 #endif | |
4723 #endif | |
4724 #ifdef HAVE_SAMPLERATE | |
4725 #ifdef SAMPLERATE_VERSION | |
4726 aboutText += tr("<br>With libsamplerate (v%1) © Erik de Castro Lopo").arg(SAMPLERATE_VERSION); | |
4727 #else | |
4728 aboutText += tr("<br>With libsamplerate © Erik de Castro Lopo"); | |
4729 #endif | |
4730 #endif | |
4731 #ifdef HAVE_SNDFILE | |
4732 #ifdef SNDFILE_VERSION | |
4733 aboutText += tr("<br>With libsndfile (v%1) © Erik de Castro Lopo").arg(SNDFILE_VERSION); | |
4734 #else | |
4735 aboutText += tr("<br>With libsndfile © Erik de Castro Lopo"); | |
4736 #endif | |
4737 #endif | |
4738 #ifdef HAVE_FFTW3F | |
4739 #ifdef FFTW3_VERSION | |
4740 aboutText += tr("<br>With FFTW3 (v%1) © Matteo Frigo and MIT").arg(FFTW3_VERSION); | |
4741 #else | |
4742 aboutText += tr("<br>With FFTW3 © Matteo Frigo and MIT"); | |
4743 #endif | |
4744 #endif | |
4745 #ifdef HAVE_VAMP | |
4746 aboutText += tr("<br>With Vamp plugin support (API v%1, host SDK v%2) © Chris Cannam").arg(VAMP_API_VERSION).arg(VAMP_SDK_VERSION); | |
4747 #endif | |
4748 aboutText += tr("<br>With LADSPA plugin support (API v%1) © Richard Furse, Paul Davis, Stefan Westerfeld").arg(LADSPA_VERSION); | |
4749 aboutText += tr("<br>With DSSI plugin support (API v%1) © Chris Cannam, Steve Harris, Sean Bolton").arg(DSSI_VERSION); | |
4750 #ifdef HAVE_LIBLO | |
4751 #ifdef LIBLO_VERSION | |
4752 aboutText += tr("<br>With liblo Lite OSC library (v%1) © Steve Harris").arg(LIBLO_VERSION); | |
4753 #else | |
4754 aboutText += tr("<br>With liblo Lite OSC library © Steve Harris").arg(LIBLO_VERSION); | |
4755 #endif | |
4756 if (m_oscQueue && m_oscQueue->isOK()) { | |
4757 aboutText += tr("<p>The OSC URL for this instance is: \"%1\"").arg(m_oscQueue->getOSCURL()); | |
4758 } | |
4759 #endif | |
4760 aboutText += "</p>"; | |
4761 #endif | |
4762 | |
4763 aboutText += | |
4764 "<p>Sonic Visualiser Copyright © 2005 - 2007 Chris Cannam and<br>" | |
4765 "Queen Mary, University of London.</p>" | |
4766 "<p>This program is free software; you can redistribute it and/or<br>" | |
4767 "modify it under the terms of the GNU General Public License as<br>" | |
4768 "published by the Free Software Foundation; either version 2 of the<br>" | |
4769 "License, or (at your option) any later version.<br>See the file " | |
4770 "COPYING included with this distribution for more information.</p>"; | |
4771 | |
4772 QMessageBox::about(this, tr("About Sonic Visualiser"), aboutText); | |
4773 } | |
4774 |