comparison main/MainWindow.cpp @ 456:4bccf2014f80

Merge from branch "templating"
author Chris Cannam
date Sun, 26 Jun 2011 19:56:01 +0100
parents 868f73825b37 e3644cd9cf99
children b3b9cc8f5466 a6cbec451508
comparison
equal deleted inserted replaced
452:fa0c3e79ea8e 456:4bccf2014f80
28 #include "data/osc/OSCQueue.h" 28 #include "data/osc/OSCQueue.h"
29 #include "framework/Document.h" 29 #include "framework/Document.h"
30 #include "framework/TransformUserConfigurator.h" 30 #include "framework/TransformUserConfigurator.h"
31 #include "view/ViewManager.h" 31 #include "view/ViewManager.h"
32 #include "base/Preferences.h" 32 #include "base/Preferences.h"
33 #include "base/ResourceFinder.h"
33 #include "layer/WaveformLayer.h" 34 #include "layer/WaveformLayer.h"
34 #include "layer/TimeRulerLayer.h" 35 #include "layer/TimeRulerLayer.h"
35 #include "layer/TimeInstantLayer.h" 36 #include "layer/TimeInstantLayer.h"
36 #include "layer/TimeValueLayer.h" 37 #include "layer/TimeValueLayer.h"
37 #include "layer/NoteLayer.h" 38 #include "layer/NoteLayer.h"
110 #include <QDateTime> 111 #include <QDateTime>
111 #include <QProcess> 112 #include <QProcess>
112 #include <QCheckBox> 113 #include <QCheckBox>
113 #include <QRegExp> 114 #include <QRegExp>
114 #include <QScrollArea> 115 #include <QScrollArea>
116 #include <QDesktopServices>
117 #include <QFileSystemWatcher>
115 118
116 #include <iostream> 119 #include <iostream>
117 #include <cstdio> 120 #include <cstdio>
118 #include <errno.h> 121 #include <errno.h>
119 122
135 m_playbackMenu(0), 138 m_playbackMenu(0),
136 m_existingLayersMenu(0), 139 m_existingLayersMenu(0),
137 m_sliceMenu(0), 140 m_sliceMenu(0),
138 m_recentFilesMenu(0), 141 m_recentFilesMenu(0),
139 m_recentTransformsMenu(0), 142 m_recentTransformsMenu(0),
143 m_templatesMenu(0),
140 m_rightButtonMenu(0), 144 m_rightButtonMenu(0),
141 m_rightButtonLayerMenu(0), 145 m_rightButtonLayerMenu(0),
142 m_rightButtonTransformsMenu(0), 146 m_rightButtonTransformsMenu(0),
143 m_rightButtonPlaybackMenu(0), 147 m_rightButtonPlaybackMenu(0),
144 m_soloAction(0), 148 m_soloAction(0),
156 m_playControlsSpacer(0), 160 m_playControlsSpacer(0),
157 m_playControlsWidth(0), 161 m_playControlsWidth(0),
158 m_preferencesDialog(0), 162 m_preferencesDialog(0),
159 m_layerTreeDialog(0), 163 m_layerTreeDialog(0),
160 m_activityLog(new ActivityLog()), 164 m_activityLog(new ActivityLog()),
161 m_keyReference(new KeyReference()) 165 m_keyReference(new KeyReference()),
166 m_templateWatcher(0)
162 { 167 {
163 Profiler profiler("MainWindow::MainWindow"); 168 Profiler profiler("MainWindow::MainWindow");
164 169
165 setWindowTitle(tr("Sonic Visualiser")); 170 setWindowTitle(tr("Sonic Visualiser"));
166 171
386 action->setStatusTip(tr("Abandon the current Sonic Visualiser session and start a new one")); 391 action->setStatusTip(tr("Abandon the current Sonic Visualiser session and start a new one"));
387 connect(action, SIGNAL(triggered()), this, SLOT(newSession())); 392 connect(action, SIGNAL(triggered()), this, SLOT(newSession()));
388 m_keyReference->registerShortcut(action); 393 m_keyReference->registerShortcut(action);
389 menu->addAction(action); 394 menu->addAction(action);
390 toolbar->addAction(action); 395 toolbar->addAction(action);
391 396 /*
392 icon = il.load("fileopensession"); 397 icon = il.load("fileopensession");
393 action = new QAction(icon, tr("&Open Session..."), this); 398 action = new QAction(icon, tr("&Open Session..."), this);
394 action->setShortcut(tr("Ctrl+O")); 399 action->setShortcut(tr("Ctrl+O"));
395 action->setStatusTip(tr("Open a previously saved Sonic Visualiser session file")); 400 action->setStatusTip(tr("Open a previously saved Sonic Visualiser session file"));
396 connect(action, SIGNAL(triggered()), this, SLOT(openSession())); 401 connect(action, SIGNAL(triggered()), this, SLOT(openSession()));
397 m_keyReference->registerShortcut(action); 402 m_keyReference->registerShortcut(action);
398 menu->addAction(action); 403 menu->addAction(action);
399 404 */
400 icon = il.load("fileopen"); 405 icon = il.load("fileopen");
401 icon.addPixmap(il.loadPixmap("fileopen-22")); 406 icon.addPixmap(il.loadPixmap("fileopen-22"));
402
403 action = new QAction(icon, tr("&Open..."), this); 407 action = new QAction(icon, tr("&Open..."), this);
408 action->setShortcut(tr("Ctrl+O"));
404 action->setStatusTip(tr("Open a session file, audio file, or layer")); 409 action->setStatusTip(tr("Open a session file, audio file, or layer"));
405 connect(action, SIGNAL(triggered()), this, SLOT(openSomething())); 410 connect(action, SIGNAL(triggered()), this, SLOT(openSomething()));
406 toolbar->addAction(action); 411 toolbar->addAction(action);
412 menu->addAction(action);
413
414 // We want this one to go on the toolbar now, if we add it at all,
415 // but on the menu later
416 QAction *iaction = new QAction(tr("&Import More Audio..."), this);
417 iaction->setShortcut(tr("Ctrl+I"));
418 iaction->setStatusTip(tr("Import an extra audio file into a new pane"));
419 connect(iaction, SIGNAL(triggered()), this, SLOT(importMoreAudio()));
420 connect(this, SIGNAL(canImportMoreAudio(bool)), iaction, SLOT(setEnabled(bool)));
421 m_keyReference->registerShortcut(iaction);
422
423 action = new QAction(tr("Open Lo&cation..."), this);
424 action->setShortcut(tr("Ctrl+Shift+O"));
425 action->setStatusTip(tr("Open or import a file from a remote URL"));
426 connect(action, SIGNAL(triggered()), this, SLOT(openLocation()));
427 m_keyReference->registerShortcut(action);
428 menu->addAction(action);
429
430 m_recentFilesMenu = menu->addMenu(tr("Open &Recent"));
431 m_recentFilesMenu->setTearOffEnabled(true);
432 setupRecentFilesMenu();
433 connect(&m_recentFiles, SIGNAL(recentChanged()),
434 this, SLOT(setupRecentFilesMenu()));
435
436 menu->addSeparator();
407 437
408 icon = il.load("filesave"); 438 icon = il.load("filesave");
409 icon.addPixmap(il.loadPixmap("filesave-22")); 439 icon.addPixmap(il.loadPixmap("filesave-22"));
410 action = new QAction(icon, tr("&Save Session"), this); 440 action = new QAction(icon, tr("&Save Session"), this);
411 action->setShortcut(tr("Ctrl+S")); 441 action->setShortcut(tr("Ctrl+S"));
425 menu->addAction(action); 455 menu->addAction(action);
426 toolbar->addAction(action); 456 toolbar->addAction(action);
427 457
428 menu->addSeparator(); 458 menu->addSeparator();
429 459
460 /*
430 icon = il.load("fileopenaudio"); 461 icon = il.load("fileopenaudio");
431 action = new QAction(icon, tr("&Import Audio File..."), this); 462 action = new QAction(icon, tr("&Import Audio File..."), this);
432 action->setShortcut(tr("Ctrl+I")); 463 action->setShortcut(tr("Ctrl+I"));
433 action->setStatusTip(tr("Import an existing audio file")); 464 action->setStatusTip(tr("Import an existing audio file"));
434 connect(action, SIGNAL(triggered()), this, SLOT(importAudio())); 465 connect(action, SIGNAL(triggered()), this, SLOT(importAudio()));
435 m_keyReference->registerShortcut(action); 466 m_keyReference->registerShortcut(action);
436 menu->addAction(action); 467 menu->addAction(action);
437 468 */
438 action = new QAction(tr("Import Secondary Audio File..."), this); 469
439 action->setShortcut(tr("Ctrl+Shift+I")); 470 // the Import action we made earlier
440 action->setStatusTip(tr("Import an extra audio file as a separate layer")); 471 menu->addAction(iaction);
441 connect(action, SIGNAL(triggered()), this, SLOT(importMoreAudio()));
442 connect(this, SIGNAL(canImportMoreAudio(bool)), action, SLOT(setEnabled(bool)));
443 m_keyReference->registerShortcut(action);
444 menu->addAction(action);
445 472
446 action = new QAction(tr("&Export Audio File..."), this); 473 action = new QAction(tr("&Export Audio File..."), this);
447 action->setStatusTip(tr("Export selection as an audio file")); 474 action->setStatusTip(tr("Export selection as an audio file"));
448 connect(action, SIGNAL(triggered()), this, SLOT(exportAudio())); 475 connect(action, SIGNAL(triggered()), this, SLOT(exportAudio()));
449 connect(this, SIGNAL(canExportAudio(bool)), action, SLOT(setEnabled(bool))); 476 connect(this, SIGNAL(canExportAudio(bool)), action, SLOT(setEnabled(bool)));
473 connect(this, SIGNAL(canExportImage(bool)), action, SLOT(setEnabled(bool))); 500 connect(this, SIGNAL(canExportImage(bool)), action, SLOT(setEnabled(bool)));
474 menu->addAction(action); 501 menu->addAction(action);
475 502
476 menu->addSeparator(); 503 menu->addSeparator();
477 504
478 action = new QAction(tr("Open Lo&cation..."), this); 505 QString templatesMenuLabel = tr("Apply Session Template");
479 action->setShortcut(tr("Ctrl+Shift+O")); 506 m_templatesMenu = menu->addMenu(templatesMenuLabel);
480 action->setStatusTip(tr("Open or import a file from a remote URL")); 507 m_templatesMenu->setTearOffEnabled(true);
481 connect(action, SIGNAL(triggered()), this, SLOT(openLocation())); 508 // We need to have a main model for this option to be useful:
482 m_keyReference->registerShortcut(action); 509 // canExportAudio captures that
483 menu->addAction(action); 510 connect(this, SIGNAL(canExportAudio(bool)), m_templatesMenu, SLOT(setEnabled(bool)));
484 511
485 menu->addSeparator(); 512 // Set up the menu in a moment, after m_manageTemplatesAction constructed
486 513
487 m_recentFilesMenu = menu->addMenu(tr("&Recent Files")); 514 action = new QAction(tr("Export Session as Template..."), this);
488 m_recentFilesMenu->setTearOffEnabled(true); 515 connect(action, SIGNAL(triggered()), this, SLOT(saveSessionAsTemplate()));
489 setupRecentFilesMenu(); 516 // We need to have something in the session for this to be useful:
490 connect(&m_recentFiles, SIGNAL(recentChanged()), 517 // canDeleteCurrentLayer captures that
491 this, SLOT(setupRecentFilesMenu())); 518 connect(this, SIGNAL(canExportAudio(bool)), action, SLOT(setEnabled(bool)));
492 519 menu->addAction(action);
493 menu->addSeparator(); 520
521 m_manageTemplatesAction = new QAction(tr("Manage Exported Templates"), this);
522 connect(m_manageTemplatesAction, SIGNAL(triggered()), this, SLOT(manageSavedTemplates()));
523 menu->addAction(m_manageTemplatesAction);
524
525 setupTemplatesMenu();
526
494 action = new QAction(tr("&Preferences..."), this); 527 action = new QAction(tr("&Preferences..."), this);
495 action->setStatusTip(tr("Adjust the application preferences")); 528 action->setStatusTip(tr("Adjust the application preferences"));
496 connect(action, SIGNAL(triggered()), this, SLOT(preferences())); 529 connect(action, SIGNAL(triggered()), this, SLOT(preferences()));
497 menu->addAction(action); 530 menu->addAction(action);
498 531
1615 m_recentFilesMenu->addAction(action); 1648 m_recentFilesMenu->addAction(action);
1616 } 1649 }
1617 } 1650 }
1618 1651
1619 void 1652 void
1653 MainWindow::setupTemplatesMenu()
1654 {
1655 m_templatesMenu->clear();
1656
1657 QAction *defaultAction = new QAction(tr("Standard Waveform"), this);
1658 defaultAction->setObjectName("default");
1659 connect(defaultAction, SIGNAL(triggered()), this, SLOT(applyTemplate()));
1660 m_templatesMenu->addAction(defaultAction);
1661
1662 m_templatesMenu->addSeparator();
1663
1664 QAction *action = 0;
1665
1666 QStringList templates = ResourceFinder().getResourceFiles("templates", "svt");
1667
1668 bool havePersonal = false;
1669
1670 // (ordered by name)
1671 std::set<QString> byName;
1672 foreach (QString t, templates) {
1673 if (!t.startsWith(":")) havePersonal = true;
1674 byName.insert(QFileInfo(t).baseName());
1675 }
1676
1677 foreach (QString t, byName) {
1678 if (t.toLower() == "default") continue;
1679 action = new QAction(t, this);
1680 connect(action, SIGNAL(triggered()), this, SLOT(applyTemplate()));
1681 m_templatesMenu->addAction(action);
1682 }
1683
1684 if (!templates.empty()) m_templatesMenu->addSeparator();
1685
1686 if (!m_templateWatcher) {
1687 m_templateWatcher = new QFileSystemWatcher(this);
1688 m_templateWatcher->addPath(ResourceFinder().getResourceSaveDir("templates"));
1689 connect(m_templateWatcher, SIGNAL(directoryChanged(const QString &)),
1690 this, SLOT(setupTemplatesMenu()));
1691 }
1692
1693 QAction *setDefaultAction = new QAction(tr("Choose Default Template..."), this);
1694 setDefaultAction->setObjectName("set_default_template");
1695 connect(setDefaultAction, SIGNAL(triggered()), this, SLOT(preferences()));
1696 m_templatesMenu->addSeparator();
1697 m_templatesMenu->addAction(setDefaultAction);
1698
1699 m_manageTemplatesAction->setEnabled(havePersonal);
1700 }
1701
1702
1703 void
1620 MainWindow::setupRecentTransformsMenu() 1704 MainWindow::setupRecentTransformsMenu()
1621 { 1705 {
1622 m_recentTransformsMenu->clear(); 1706 m_recentTransformsMenu->clear();
1623 vector<QString> transforms = m_recentTransforms.getRecent(); 1707 vector<QString> transforms = m_recentTransforms.getRecent();
1624 for (size_t i = 0; i < transforms.size(); ++i) { 1708 for (size_t i = 0; i < transforms.size(); ++i) {
2147 MainWindow::importAudio() 2231 MainWindow::importAudio()
2148 { 2232 {
2149 QString path = getOpenFileName(FileFinder::AudioFile); 2233 QString path = getOpenFileName(FileFinder::AudioFile);
2150 2234
2151 if (path != "") { 2235 if (path != "") {
2152 if (openAudio(path, ReplaceMainModel) == FileOpenFailed) { 2236 if (openAudio(path, ReplaceSession) == FileOpenFailed) {
2153 emit hideSplash(); 2237 emit hideSplash();
2154 QMessageBox::critical(this, tr("Failed to open file"), 2238 QMessageBox::critical(this, tr("Failed to open file"),
2155 tr("<b>File open failed</b><p>Audio file \"%1\" could not be opened").arg(path)); 2239 tr("<b>File open failed</b><p>Audio file \"%1\" could not be opened").arg(path));
2156 } 2240 }
2157 } 2241 }
2671 2755
2672 QString path = getOpenFileName(FileFinder::AnyFile); 2756 QString path = getOpenFileName(FileFinder::AnyFile);
2673 2757
2674 if (path.isEmpty()) return; 2758 if (path.isEmpty()) return;
2675 2759
2676 FileOpenStatus status = open(path, AskUser); 2760 FileOpenStatus status = open(path, ReplaceSession);
2677 2761
2678 if (status == FileOpenFailed) { 2762 if (status == FileOpenFailed) {
2679 emit hideSplash(); 2763 emit hideSplash();
2680 QMessageBox::critical(this, tr("Failed to open file"), 2764 QMessageBox::critical(this, tr("Failed to open file"),
2681 tr("<b>File open failed</b><p>File \"%1\" could not be opened").arg(path)); 2765 tr("<b>File open failed</b><p>File \"%1\" could not be opened").arg(path));
2703 2787
2704 settings.setValue("lastremote", text); 2788 settings.setValue("lastremote", text);
2705 2789
2706 if (text.isEmpty()) return; 2790 if (text.isEmpty()) return;
2707 2791
2708 FileOpenStatus status = open(text); 2792 FileOpenStatus status = open(text, AskUser);
2709 2793
2710 if (status == FileOpenFailed) { 2794 if (status == FileOpenFailed) {
2711 emit hideSplash(); 2795 emit hideSplash();
2712 QMessageBox::critical(this, tr("Failed to open location"), 2796 QMessageBox::critical(this, tr("Failed to open location"),
2713 tr("<b>Open failed</b><p>URL \"%1\" could not be opened").arg(text)); 2797 tr("<b>Open failed</b><p>URL \"%1\" could not be opened").arg(text));
2731 } 2815 }
2732 2816
2733 QString path = action->text(); 2817 QString path = action->text();
2734 if (path == "") return; 2818 if (path == "") return;
2735 2819
2736 FileOpenStatus status = open(path); 2820 FileOpenStatus status = open(path, ReplaceSession);
2737 2821
2738 if (status == FileOpenFailed) { 2822 if (status == FileOpenFailed) {
2739 emit hideSplash(); 2823 emit hideSplash();
2740 QMessageBox::critical(this, tr("Failed to open location"), 2824 QMessageBox::critical(this, tr("Failed to open location"),
2741 tr("<b>Open failed</b><p>File or URL \"%1\" could not be opened").arg(path)); 2825 tr("<b>Open failed</b><p>File or URL \"%1\" could not be opened").arg(path));
2742 } else if (status == FileOpenWrongMode) { 2826 } else if (status == FileOpenWrongMode) {
2743 emit hideSplash(); 2827 emit hideSplash();
2744 QMessageBox::critical(this, tr("Failed to open location"), 2828 QMessageBox::critical(this, tr("Failed to open location"),
2745 tr("<b>Audio required</b><p>Unable to load layer data from \"%1\" without an audio file.<br>Please load at least one audio file before importing annotations.").arg(path)); 2829 tr("<b>Audio required</b><p>Unable to load layer data from \"%1\" without an audio file.<br>Please load at least one audio file before importing annotations.").arg(path));
2746 } 2830 }
2831 }
2832
2833 void
2834 MainWindow::applyTemplate()
2835 {
2836 QObject *s = sender();
2837 QAction *action = qobject_cast<QAction *>(s);
2838
2839 if (!action) {
2840 std::cerr << "WARNING: MainWindow::applyTemplate: sender is not an action"
2841 << std::endl;
2842 return;
2843 }
2844
2845 QString n = action->objectName();
2846 if (n == "") n = action->text();
2847
2848 if (n == "") {
2849 std::cerr << "WARNING: MainWindow::applyTemplate: sender has no name"
2850 << std::endl;
2851 return;
2852 }
2853
2854 QString mainModelLocation;
2855 WaveFileModel *mm = getMainModel();
2856 if (mm) mainModelLocation = mm->getLocation();
2857 if (mainModelLocation != "") {
2858 openAudio(mainModelLocation, ReplaceSession, n);
2859 } else {
2860 openSessionTemplate(n);
2861 }
2862 }
2863
2864 void
2865 MainWindow::saveSessionAsTemplate()
2866 {
2867 QString name = QInputDialog::getText
2868 (this, tr("Enter template name"),
2869 tr("Please enter a name for the saved template:"));
2870 if (name == "") return;
2871
2872 name.replace(QRegExp("[^\\w\\s\\.\"'-]"), "_");
2873
2874 ResourceFinder rf;
2875 QString dir = rf.getResourceSaveDir("templates");
2876 QString filename = QString("%1/%2.svt").arg(dir).arg(name);
2877 if (QFile(filename).exists()) {
2878 if (QMessageBox::warning(this,
2879 tr("Template file exists"),
2880 tr("<b>Template file exists</b><p>The template \"%1\" already exists.<br>Overwrite it?").arg(name),
2881 QMessageBox::Ok | QMessageBox::Cancel,
2882 QMessageBox::Cancel) != QMessageBox::Ok) {
2883 return;
2884 }
2885 }
2886
2887 saveSessionTemplate(filename);
2888 }
2889
2890 void
2891 MainWindow::manageSavedTemplates()
2892 {
2893 ResourceFinder rf;
2894 QDesktopServices::openUrl("file:" + rf.getResourceSaveDir("templates"));
2747 } 2895 }
2748 2896
2749 void 2897 void
2750 MainWindow::paneAdded(Pane *pane) 2898 MainWindow::paneAdded(Pane *pane)
2751 { 2899 {
3912 } 4060 }
3913 4061
3914 void 4062 void
3915 MainWindow::preferences() 4063 MainWindow::preferences()
3916 { 4064 {
4065 bool goToTemplateTab =
4066 (sender() && sender()->objectName() == "set_default_template");
4067
3917 if (!m_preferencesDialog.isNull()) { 4068 if (!m_preferencesDialog.isNull()) {
3918 m_preferencesDialog->show(); 4069 m_preferencesDialog->show();
3919 m_preferencesDialog->raise(); 4070 m_preferencesDialog->raise();
4071 if (goToTemplateTab) {
4072 m_preferencesDialog->switchToTab(PreferencesDialog::TemplateTab);
4073 }
3920 return; 4074 return;
3921 } 4075 }
3922 4076
3923 m_preferencesDialog = new PreferencesDialog(this); 4077 m_preferencesDialog = new PreferencesDialog(this);
3924 4078
3929 // without resetting its own widgets, so its state will be 4083 // without resetting its own widgets, so its state will be
3930 // incorrect when next shown unless we construct it afresh 4084 // incorrect when next shown unless we construct it afresh
3931 m_preferencesDialog->setAttribute(Qt::WA_DeleteOnClose); 4085 m_preferencesDialog->setAttribute(Qt::WA_DeleteOnClose);
3932 4086
3933 m_preferencesDialog->show(); 4087 m_preferencesDialog->show();
4088 if (goToTemplateTab) {
4089 m_preferencesDialog->switchToTab(PreferencesDialog::TemplateTab);
4090 }
3934 } 4091 }
3935 4092
3936 void 4093 void
3937 MainWindow::mouseEnteredWidget() 4094 MainWindow::mouseEnteredWidget()
3938 { 4095 {