changeset 155:7310316bf74b

* Change a number of keyboard shortcuts -- get rid of all the Alt+ shortcuts and introduce a few more Ctrl+ ones instead, as well as a number of plain single keypresses * Add Playback menu * Add time-ruler support for snap to feature, use it in ffwd/rewind (rewind still needs fixing) * restore layer hierarchy window prior to making it work correctly
author Chris Cannam
date Thu, 28 Jun 2007 14:50:58 +0000
parents f6ce5febc07f
children 63a9c88fbba9
files audioio/PhaseVocoderTimeStretcher.cpp main/MainWindow.cpp main/MainWindow.h
diffstat 3 files changed, 271 insertions(+), 148 deletions(-) [+]
line wrap: on
line diff
--- a/audioio/PhaseVocoderTimeStretcher.cpp	Tue Jun 26 12:27:47 2007 +0000
+++ b/audioio/PhaseVocoderTimeStretcher.cpp	Thu Jun 28 14:50:58 2007 +0000
@@ -545,7 +545,7 @@
         isTransient = true;
 
 
-        std::cerr << "isTransient (count = " << count << ", prev = " << m_prevTransientScore << ", diff = " << count - m_prevTransientScore << ", ratio = " << (m_totalCount > 0 ? (float (m_n2sum) / float(m_totalCount * m_n1)) : 1.f) << ", ideal = " << m_ratio << ")" << std::endl;
+//        std::cerr << "isTransient (count = " << count << ", prev = " << m_prevTransientScore << ", diff = " << count - m_prevTransientScore << ", ratio = " << (m_totalCount > 0 ? (float (m_n2sum) / float(m_totalCount * m_n1)) : 1.f) << ", ideal = " << m_ratio << ")" << std::endl;
 //    } else {
 //        std::cerr << " !transient (count = " << count << ", prev = " << m_prevTransientScore << ", diff = " << count - m_prevTransientScore << ")" << std::endl;
     }
--- a/main/MainWindow.cpp	Tue Jun 26 12:27:47 2007 +0000
+++ b/main/MainWindow.cpp	Thu Jun 28 14:50:58 2007 +0000
@@ -117,6 +117,7 @@
     m_paneMenu(0),
     m_layerMenu(0),
     m_transformsMenu(0),
+    m_playbackMenu(0),
     m_existingLayersMenu(0),
     m_sliceMenu(0),
     m_recentFilesMenu(0),
@@ -124,6 +125,9 @@
     m_rightButtonMenu(0),
     m_rightButtonLayerMenu(0),
     m_rightButtonTransformsMenu(0),
+    m_rightButtonPlaybackMenu(0),
+    m_ffwdAction(0),
+    m_rwdAction(0),
     m_documentModified(false),
     m_openingAudioFile(false),
     m_abandoning(false),
@@ -290,6 +294,7 @@
 
     setupMenus();
     setupToolbars();
+    setupHelpMenu();
 
     statusBar();
 
@@ -407,7 +412,6 @@
     setupViewMenu();
     setupPaneAndLayerMenus();
     setupTransformsMenu();
-    setupHelpMenu();
 
     m_mainMenusCreated = true;
 }
@@ -530,16 +534,6 @@
     connect(action, SIGNAL(triggered()), this, SLOT(preferences()));
     menu->addAction(action);
 	
-    /*!!!
-      menu->addSeparator();
-	
-      action = new QAction(tr("Play / Pause"), this);
-      action->setShortcut(tr("Space"));
-      action->setStatusTip(tr("Start or stop playback from the current position"));
-      connect(action, SIGNAL(triggered()), this, SLOT(play()));
-      menu->addAction(action);
-    */
-
     menu->addSeparator();
     action = new QAction(QIcon(":/icons/exit.png"),
                          tr("&Quit"), this);
@@ -719,6 +713,7 @@
 
     action = new QAction(QIcon(":/icons/zoom-fit.png"),
                          tr("Zoom to &Fit"), this);
+    action->setShortcut(tr("F"));
     action->setStatusTip(tr("Zoom to show the whole file"));
     connect(action, SIGNAL(triggered()), this, SLOT(zoomToFit()));
     connect(this, SIGNAL(canZoom(bool)), action, SLOT(setEnabled(bool)));
@@ -798,16 +793,13 @@
     }
     settings.endGroup();
 
-/*!!! This one doesn't work properly yet
-
     menu->addSeparator();
 
     action = new QAction(tr("Show La&yer Hierarchy"), this);
-    action->setShortcut(tr("Alt+L"));
+    action->setShortcut(tr("H"));
     action->setStatusTip(tr("Open a window displaying the hierarchy of panes and layers in this session"));
     connect(action, SIGNAL(triggered()), this, SLOT(showLayerTree()));
     menu->addAction(action);
- */
 }
 
 void
@@ -832,7 +824,7 @@
     QMenu *menu = m_paneMenu;
 
     QAction *action = new QAction(QIcon(":/icons/pane.png"), tr("Add &New Pane"), this);
-    action->setShortcut(tr("Alt+N"));
+    action->setShortcut(tr("N"));
     action->setStatusTip(tr("Add a new pane containing only a time ruler"));
     connect(action, SIGNAL(triggered()), this, SLOT(addPane()));
     connect(this, SIGNAL(canAddPane(bool)), action, SLOT(setEnabled(bool)));
@@ -866,7 +858,7 @@
 	action->setStatusTip(tipText);
 
 	if (type == LayerFactory::Text) {
-	    action->setShortcut(tr("Alt+T"));
+	    action->setShortcut(tr("T"));
 	}
 
 	connect(action, SIGNAL(triggered()), this, SLOT(addLayer()));
@@ -915,7 +907,7 @@
                 icon = QIcon(":/icons/waveform.png");
                 mainText = tr("Add &Waveform");
                 if (menuType == 0) {
-                    shortcutText = tr("Alt+W");
+                    shortcutText = tr("W");
                     tipText = tr("Add a new pane showing a waveform view");
                 } else {
                     tipText = tr("Add a new layer showing a waveform view");
@@ -927,7 +919,7 @@
                 icon = QIcon(":/icons/spectrogram.png");
                 mainText = tr("Add &Spectrogram");
                 if (menuType == 0) {
-                    shortcutText = tr("Alt+S");
+                    shortcutText = tr("G");
                     tipText = tr("Add a new pane showing a spectrogram");
                 } else {
                     tipText = tr("Add a new layer showing a spectrogram");
@@ -938,7 +930,7 @@
                 icon = QIcon(":/icons/spectrogram.png");
                 mainText = tr("Add &Melodic Range Spectrogram");
                 if (menuType == 0) {
-                    shortcutText = tr("Alt+M");
+                    shortcutText = tr("M");
                     tipText = tr("Add a new pane showing a spectrogram set up for an overview of note pitches");
                 } else {
                     tipText = tr("Add a new layer showing a spectrogram set up for an overview of note pitches");
@@ -947,9 +939,9 @@
 		
             case LayerFactory::PeakFrequencySpectrogram:
                 icon = QIcon(":/icons/spectrogram.png");
-                mainText = tr("Add &Peak Frequency Spectrogram");
+                mainText = tr("Add Pea&k Frequency Spectrogram");
                 if (menuType == 0) {
-                    shortcutText = tr("Alt+P");
+                    shortcutText = tr("K");
                     tipText = tr("Add a new pane showing a spectrogram set up for tracking frequencies");
                 } else {
                     tipText = tr("Add a new layer showing a spectrogram set up for tracking frequencies");
@@ -960,7 +952,7 @@
                 icon = QIcon(":/icons/spectrum.png");
                 mainText = tr("Add Spectr&um");
                 if (menuType == 0) {
-                    shortcutText = tr("Alt+U");
+                    shortcutText = tr("U");
                     tipText = tr("Add a new pane showing a frequency spectrum");
                 } else {
                     tipText = tr("Add a new layer showing a frequency spectrum");
@@ -1094,7 +1086,7 @@
     menu->addSeparator();
 
     action = new QAction(QIcon(":/icons/editdelete.png"), tr("&Delete Pane"), this);
-    action->setShortcut(tr("Alt+D"));
+    action->setShortcut(tr("Ctrl+Shift+D"));
     action->setStatusTip(tr("Delete the currently active pane"));
     connect(action, SIGNAL(triggered()), this, SLOT(deleteCurrentPane()));
     connect(this, SIGNAL(canDeleteCurrentPane(bool)), action, SLOT(setEnabled(bool)));
@@ -1125,7 +1117,7 @@
     menu->addSeparator();
 
     action = new QAction(tr("&Rename Layer..."), this);
-    action->setShortcut(tr("Alt+R"));
+    action->setShortcut(tr("R"));
     action->setStatusTip(tr("Rename the currently active layer"));
     connect(action, SIGNAL(triggered()), this, SLOT(renameCurrentLayer()));
     connect(this, SIGNAL(canRenameLayer(bool)), action, SLOT(setEnabled(bool)));
@@ -1133,7 +1125,7 @@
     m_rightButtonLayerMenu->addAction(action);
 
     action = new QAction(QIcon(":/icons/editdelete.png"), tr("&Delete Layer"), this);
-    action->setShortcut(tr("Alt+Shift+D"));
+    action->setShortcut(tr("Ctrl+D"));
     action->setStatusTip(tr("Delete the currently active layer"));
     connect(action, SIGNAL(triggered()), this, SLOT(deleteCurrentLayer()));
     connect(this, SIGNAL(canDeleteCurrentLayer(bool)), action, SLOT(setEnabled(bool)));
@@ -1347,8 +1339,6 @@
 void
 MainWindow::setupHelpMenu()
 {
-    if (m_mainMenusCreated) return;
-
     QMenu *menu = menuBar()->addMenu(tr("&Help"));
     menu->setTearOffEnabled(true);
     
@@ -1377,6 +1367,7 @@
     for (size_t i = 0; i < files.size(); ++i) {
 	QAction *action = new QAction(files[i], this);
 	connect(action, SIGNAL(triggered()), this, SLOT(openRecentFile()));
+        if (i == 0) action->setShortcut(tr("Ctrl+R"));
 	m_recentFilesMenu->addAction(action);
     }
 }
@@ -1395,6 +1386,7 @@
                       << "\" in recent transforms list" << std::endl;
             continue;
         }
+        if (i == 0) ti->second->setShortcut(tr("Ctrl+T"));
 	m_recentTransformsMenu->addAction(ti->second);
     }
 }
@@ -1480,69 +1472,113 @@
 void
 MainWindow::setupToolbars()
 {
-    QToolBar *toolbar = addToolBar(tr("Transport Toolbar"));
-
-    QAction *action = toolbar->addAction(QIcon(":/icons/rewind-start.png"),
-					 tr("Rewind to Start"));
-    action->setShortcut(tr("Home"));
-    action->setStatusTip(tr("Rewind to the start"));
-    connect(action, SIGNAL(triggered()), this, SLOT(rewindStart()));
-    connect(this, SIGNAL(canPlay(bool)), action, SLOT(setEnabled(bool)));
-
-    action = toolbar->addAction(QIcon(":/icons/rewind.png"),
-				tr("Rewind"));
-    action->setShortcut(tr("PageUp"));
-    action->setStatusTip(tr("Rewind to the previous time instant in the current layer"));
-    connect(action, SIGNAL(triggered()), this, SLOT(rewind()));
-    connect(this, SIGNAL(canRewind(bool)), action, SLOT(setEnabled(bool)));
-
-    action = toolbar->addAction(QIcon(":/icons/playpause.png"),
-				tr("Play / Pause"));
-    action->setCheckable(true);
-    action->setShortcut(tr("Space"));
-    action->setStatusTip(tr("Start or stop playback from the current position"));
-    connect(action, SIGNAL(triggered()), this, SLOT(play()));
+    QMenu *menu = m_playbackMenu = menuBar()->addMenu(tr("Play&back"));
+    menu->setTearOffEnabled(true);
+    m_rightButtonMenu->addSeparator();
+    m_rightButtonPlaybackMenu = m_rightButtonMenu->addMenu(tr("Playback"));
+
+    QToolBar *toolbar = addToolBar(tr("Playback Toolbar"));
+
+    QAction *rwdStartAction = toolbar->addAction(QIcon(":/icons/rewind-start.png"),
+                                                 tr("Rewind to Start"));
+    rwdStartAction->setShortcut(tr("Home"));
+    rwdStartAction->setStatusTip(tr("Rewind to the start"));
+    connect(rwdStartAction, SIGNAL(triggered()), this, SLOT(rewindStart()));
+    connect(this, SIGNAL(canPlay(bool)), rwdStartAction, SLOT(setEnabled(bool)));
+
+    QAction *m_rwdAction = toolbar->addAction(QIcon(":/icons/rewind.png"),
+                                              tr("Rewind"));
+    m_rwdAction->setShortcut(tr("PgUp"));
+    connect(m_rwdAction, SIGNAL(triggered()), this, SLOT(rewind()));
+    connect(this, SIGNAL(canRewind(bool)), m_rwdAction, SLOT(setEnabled(bool)));
+
+    QAction *playAction = toolbar->addAction(QIcon(":/icons/playpause.png"),
+                                             tr("Play / Pause"));
+    playAction->setCheckable(true);
+    playAction->setShortcut(tr("Space"));
+    playAction->setStatusTip(tr("Start or stop playback from the current position"));
+    connect(playAction, SIGNAL(triggered()), this, SLOT(play()));
     connect(m_playSource, SIGNAL(playStatusChanged(bool)),
-	    action, SLOT(setChecked(bool)));
-    connect(this, SIGNAL(canPlay(bool)), action, SLOT(setEnabled(bool)));
-
-    action = toolbar->addAction(QIcon(":/icons/ffwd.png"),
-				tr("Fast Forward"));
-    action->setShortcut(tr("PageDown"));
-    action->setStatusTip(tr("Fast forward to the next time instant in the current layer"));
-    connect(action, SIGNAL(triggered()), this, SLOT(ffwd()));
-    connect(this, SIGNAL(canFfwd(bool)), action, SLOT(setEnabled(bool)));
-
-    action = toolbar->addAction(QIcon(":/icons/ffwd-end.png"),
-				tr("Fast Forward to End"));
-    action->setShortcut(tr("End"));
-    action->setStatusTip(tr("Fast-forward to the end"));
-    connect(action, SIGNAL(triggered()), this, SLOT(ffwdEnd()));
-    connect(this, SIGNAL(canPlay(bool)), action, SLOT(setEnabled(bool)));
+	    playAction, SLOT(setChecked(bool)));
+    connect(this, SIGNAL(canPlay(bool)), playAction, SLOT(setEnabled(bool)));
+
+    m_ffwdAction = toolbar->addAction(QIcon(":/icons/ffwd.png"),
+                                              tr("Fast Forward"));
+    m_ffwdAction->setShortcut(tr("PgDown"));
+    connect(m_ffwdAction, SIGNAL(triggered()), this, SLOT(ffwd()));
+    connect(this, SIGNAL(canFfwd(bool)), m_ffwdAction, SLOT(setEnabled(bool)));
+
+    QAction *ffwdEndAction = toolbar->addAction(QIcon(":/icons/ffwd-end.png"),
+                                                tr("Fast Forward to End"));
+    ffwdEndAction->setShortcut(tr("End"));
+    ffwdEndAction->setStatusTip(tr("Fast-forward to the end"));
+    connect(ffwdEndAction, SIGNAL(triggered()), this, SLOT(ffwdEnd()));
+    connect(this, SIGNAL(canPlay(bool)), ffwdEndAction, SLOT(setEnabled(bool)));
 
     toolbar = addToolBar(tr("Play Mode Toolbar"));
 
-    action = toolbar->addAction(QIcon(":/icons/playselection.png"),
-				tr("Constrain Playback to Selection"));
-    action->setCheckable(true);
-    action->setChecked(m_viewManager->getPlaySelectionMode());
-    action->setShortcut(tr("s"));
-    action->setStatusTip(tr("Constrain playback to the selected area"));
+    QAction *psAction = toolbar->addAction(QIcon(":/icons/playselection.png"),
+                                           tr("Constrain Playback to Selection"));
+    psAction->setCheckable(true);
+    psAction->setChecked(m_viewManager->getPlaySelectionMode());
+    psAction->setShortcut(tr("s"));
+    psAction->setStatusTip(tr("Constrain playback to the selected area"));
     connect(m_viewManager, SIGNAL(playSelectionModeChanged(bool)),
-            action, SLOT(setChecked(bool)));
-    connect(action, SIGNAL(triggered()), this, SLOT(playSelectionToggled()));
-    connect(this, SIGNAL(canPlaySelection(bool)), action, SLOT(setEnabled(bool)));
-
-    action = toolbar->addAction(QIcon(":/icons/playloop.png"),
-				tr("Loop Playback"));
-    action->setCheckable(true);
-    action->setChecked(m_viewManager->getPlayLoopMode());
-    action->setShortcut(tr("l"));
-    action->setStatusTip(tr("Loop playback"));
+            psAction, SLOT(setChecked(bool)));
+    connect(psAction, SIGNAL(triggered()), this, SLOT(playSelectionToggled()));
+    connect(this, SIGNAL(canPlaySelection(bool)), psAction, SLOT(setEnabled(bool)));
+
+    QAction *plAction = toolbar->addAction(QIcon(":/icons/playloop.png"),
+                                           tr("Loop Playback"));
+    plAction->setCheckable(true);
+    plAction->setChecked(m_viewManager->getPlayLoopMode());
+    plAction->setShortcut(tr("l"));
+    plAction->setStatusTip(tr("Loop playback"));
     connect(m_viewManager, SIGNAL(playLoopModeChanged(bool)),
-            action, SLOT(setChecked(bool)));
-    connect(action, SIGNAL(triggered()), this, SLOT(playLoopToggled()));
-    connect(this, SIGNAL(canPlay(bool)), action, SLOT(setEnabled(bool)));
+            plAction, SLOT(setChecked(bool)));
+    connect(plAction, SIGNAL(triggered()), this, SLOT(playLoopToggled()));
+    connect(this, SIGNAL(canPlay(bool)), plAction, SLOT(setEnabled(bool)));
+
+    menu->addAction(playAction);
+    menu->addAction(psAction);
+    menu->addAction(plAction);
+    menu->addSeparator();
+    menu->addAction(m_rwdAction);
+    menu->addAction(m_ffwdAction);
+    menu->addSeparator();
+    menu->addAction(rwdStartAction);
+    menu->addAction(ffwdEndAction);
+    menu->addSeparator();
+
+    m_rightButtonPlaybackMenu->addAction(playAction);
+    m_rightButtonPlaybackMenu->addAction(psAction);
+    m_rightButtonPlaybackMenu->addAction(plAction);
+    m_rightButtonPlaybackMenu->addSeparator();
+    m_rightButtonPlaybackMenu->addAction(m_rwdAction);
+    m_rightButtonPlaybackMenu->addAction(m_ffwdAction);
+    m_rightButtonPlaybackMenu->addSeparator();
+    m_rightButtonPlaybackMenu->addAction(rwdStartAction);
+    m_rightButtonPlaybackMenu->addAction(ffwdEndAction);
+    m_rightButtonPlaybackMenu->addSeparator();
+
+    QAction *fastAction = menu->addAction(tr("Speed Up"));
+    fastAction->setShortcut(tr("Ctrl+PgUp"));
+    connect(fastAction, SIGNAL(triggered()), this, SLOT(speedUpPlayback()));
+    connect(this, SIGNAL(canSpeedUpPlayback(bool)), fastAction, SLOT(setEnabled(bool)));
+    
+    QAction *slowAction = menu->addAction(tr("Slow Down"));
+    slowAction->setShortcut(tr("Ctrl+PgDown"));
+    connect(slowAction, SIGNAL(triggered()), this, SLOT(slowDownPlayback()));
+    connect(this, SIGNAL(canSlowDownPlayback(bool)), slowAction, SLOT(setEnabled(bool)));
+
+    QAction *normalAction = menu->addAction(tr("Restore Normal Speed"));
+    normalAction->setShortcut(tr("Ctrl+Home"));
+    connect(normalAction, SIGNAL(triggered()), this, SLOT(restoreNormalPlayback()));
+    connect(this, SIGNAL(canChangePlaybackSpeed(bool)), normalAction, SLOT(setEnabled(bool)));
+
+    m_rightButtonPlaybackMenu->addAction(fastAction);
+    m_rightButtonPlaybackMenu->addAction(slowAction);
+    m_rightButtonPlaybackMenu->addAction(normalAction);
 
     toolbar = addToolBar(tr("Edit Toolbar"));
     CommandHistory::getInstance()->registerToolbar(toolbar);
@@ -1550,8 +1586,8 @@
     toolbar = addToolBar(tr("Tools Toolbar"));
     QActionGroup *group = new QActionGroup(this);
 
-    action = toolbar->addAction(QIcon(":/icons/navigate.png"),
-				tr("Navigate"));
+    QAction *action = toolbar->addAction(QIcon(":/icons/navigate.png"),
+                                         tr("Navigate"));
     action->setCheckable(true);
     action->setChecked(true);
     action->setShortcut(tr("1"));
@@ -1661,9 +1697,9 @@
     emit canRenameLayer(haveCurrentLayer);
     emit canEditLayer(haveCurrentEditableLayer);
     emit canSelect(haveMainModel && haveCurrentPane);
-    emit canPlay(/*!!! haveMainModel && */ havePlayTarget);
-    emit canFfwd(haveCurrentTimeInstantsLayer || haveCurrentTimeValueLayer);
-    emit canRewind(haveCurrentTimeInstantsLayer || haveCurrentTimeValueLayer);
+    emit canPlay(havePlayTarget);
+    emit canFfwd(true);
+    emit canRewind(true);
     emit canPaste(haveCurrentEditableLayer && haveClipboardContents);
     emit canInsertInstant(haveCurrentPane);
     emit canInsertInstantsAtBoundaries(haveCurrentPane && haveSelection);
@@ -1671,6 +1707,30 @@
     emit canClearSelection(haveSelection);
     emit canEditSelection(haveSelection && haveCurrentEditableLayer);
     emit canSave(m_sessionFile != "" && m_documentModified);
+
+    emit canChangePlaybackSpeed(true);
+    int v = m_playSpeed->value();
+    emit canSpeedUpPlayback(v < m_playSpeed->maximum());
+    emit canSlowDownPlayback(v > m_playSpeed->minimum());
+
+    if (m_ffwdAction && m_rwdAction) {
+        if (haveCurrentTimeInstantsLayer) {
+            m_ffwdAction->setText(tr("Fast Forward to Next Instant"));
+            m_ffwdAction->setStatusTip(tr("Fast forward to the next time instant in the current layer"));
+            m_rwdAction->setText(tr("Rewind to Previous Instant"));
+            m_rwdAction->setStatusTip(tr("Rewind to the previous time instant in the current layer"));
+        } else if (haveCurrentTimeValueLayer) {
+            m_ffwdAction->setText(tr("Fast Forward to Next Point"));
+            m_ffwdAction->setStatusTip(tr("Fast forward to the next point in the current layer"));
+            m_rwdAction->setText(tr("Rewind to Previous Point"));
+            m_rwdAction->setStatusTip(tr("Rewind to the previous point in the current layer"));
+        } else {
+            m_ffwdAction->setText(tr("Fast Forward"));
+            m_ffwdAction->setStatusTip(tr("Fast forward"));
+            m_rwdAction->setText(tr("Rewind"));
+            m_rwdAction->setStatusTip(tr("Rewind"));
+        }
+    }
 }
 
 void
@@ -3298,27 +3358,30 @@
     int frame = m_viewManager->getPlaybackFrame();
     ++frame;
 
-    Pane *pane = m_paneStack->getCurrentPane();
-    if (!pane) return;
-
-    Layer *layer = pane->getSelectedLayer();
-
-    if (!dynamic_cast<TimeInstantLayer *>(layer) &&
-        !dynamic_cast<TimeValueLayer *>(layer)) return;
-
-    size_t resolution = 0;
-    if (!layer->snapToFeatureFrame(pane, frame, resolution, Layer::SnapRight)) {
-        frame = getMainModel()->getEndFrame();
+    Layer *layer = getSnapLayer();
+    size_t sr = getMainModel()->getSampleRate();
+
+    if (!layer) {
+
+        frame = RealTime::realTime2Frame
+            (RealTime::frame2RealTime(frame, sr) + RealTime(2, 0), sr);
+        if (frame > int(getMainModel()->getEndFrame())) {
+            frame = getMainModel()->getEndFrame();
+        }
+
+    } else {
+
+        size_t resolution = 0;
+        if (!layer->snapToFeatureFrame(m_paneStack->getCurrentPane(),
+                                       frame, resolution, Layer::SnapRight)) {
+            frame = getMainModel()->getEndFrame();
+        }
     }
+        
+    if (frame < 0) frame = 0;
 
     if (m_viewManager->getPlaySelectionMode()) {
-        MultiSelection::SelectionList sl = m_viewManager->getSelections();
-        if (!sl.empty()) {
-            MultiSelection::SelectionList::iterator i = sl.end();
-            --i;
-            int selectionEndFrame = i->getEndFrame();
-            if (frame > selectionEndFrame) frame = selectionEndFrame;
-        }
+        frame = m_viewManager->constrainFrameToSelection(size_t(frame));
     }
     
     m_viewManager->setPlaybackFrame(frame);
@@ -3332,13 +3395,7 @@
     size_t frame = getMainModel()->getEndFrame();
 
     if (m_viewManager->getPlaySelectionMode()) {
-        MultiSelection::SelectionList sl = m_viewManager->getSelections();
-        if (!sl.empty()) {
-            MultiSelection::SelectionList::iterator i = sl.end();
-            --i;
-            size_t selectionEndFrame = i->getEndFrame();
-            if (frame > selectionEndFrame) frame = selectionEndFrame;
-        }
+        frame = m_viewManager->constrainFrameToSelection(frame);
     }
 
     m_viewManager->setPlaybackFrame(frame);
@@ -3352,25 +3409,30 @@
     int frame = m_viewManager->getPlaybackFrame();
     if (frame > 0) --frame;
 
-    Pane *pane = m_paneStack->getCurrentPane();
-    if (!pane) return;
-
-    Layer *layer = pane->getSelectedLayer();
-
-    if (!dynamic_cast<TimeInstantLayer *>(layer) &&
-        !dynamic_cast<TimeValueLayer *>(layer)) return;
-
-    size_t resolution = 0;
-    if (!layer->snapToFeatureFrame(pane, frame, resolution, Layer::SnapLeft)) {
-        frame = getMainModel()->getEndFrame();
+    Layer *layer = getSnapLayer();
+    size_t sr = getMainModel()->getSampleRate();
+
+    if (!layer) {
+        
+        frame = RealTime::realTime2Frame
+            (RealTime::frame2RealTime(frame, sr) - RealTime(2, 0), sr);
+        if (frame < int(getMainModel()->getStartFrame())) {
+            frame = getMainModel()->getStartFrame();
+        }
+
+    } else {
+
+        size_t resolution = 0;
+        if (!layer->snapToFeatureFrame(m_paneStack->getCurrentPane(),
+                                       frame, resolution, Layer::SnapLeft)) {
+            frame = getMainModel()->getStartFrame();
+        }
     }
 
+    if (frame < 0) frame = 0;
+
     if (m_viewManager->getPlaySelectionMode()) {
-        MultiSelection::SelectionList sl = m_viewManager->getSelections();
-        if (!sl.empty()) {
-            int selectionStartFrame = sl.begin()->getStartFrame();
-            if (frame < selectionStartFrame) frame = selectionStartFrame;
-        }
+        frame = m_viewManager->constrainFrameToSelection(size_t(frame));
     }
 
     m_viewManager->setPlaybackFrame(frame);
@@ -3384,14 +3446,36 @@
     size_t frame = getMainModel()->getStartFrame();
 
     if (m_viewManager->getPlaySelectionMode()) {
-        MultiSelection::SelectionList sl = m_viewManager->getSelections();
-        if (!sl.empty()) {
-            size_t selectionStartFrame = sl.begin()->getStartFrame();
-            if (frame < selectionStartFrame) frame = selectionStartFrame;
+        frame = m_viewManager->constrainFrameToSelection(frame);
+    }
+
+    m_viewManager->setPlaybackFrame(frame);
+}
+
+Layer *
+MainWindow::getSnapLayer() const
+{
+    Pane *pane = m_paneStack->getCurrentPane();
+    if (!pane) return 0;
+
+    Layer *layer = pane->getSelectedLayer();
+
+    if (!dynamic_cast<TimeInstantLayer *>(layer) &&
+        !dynamic_cast<TimeValueLayer *>(layer) &&
+        !dynamic_cast<TimeRulerLayer *>(layer)) {
+
+        layer = 0;
+
+        for (int i = pane->getLayerCount(); i > 0; --i) {
+            Layer *l = pane->getLayer(i-1);
+            if (dynamic_cast<TimeRulerLayer *>(l)) {
+                layer = l;
+                break;
+            }
         }
     }
 
-    m_viewManager->setPlaybackFrame(frame);
+    return layer;
 }
 
 void
@@ -3783,28 +3867,29 @@
     PlaySpeedRangeMapper mapper(0, 200);
 
     float percent = m_playSpeed->mappedValue();
-
     float factor = mapper.getFactorForValue(percent);
 
-//    float factor = mapper.getFactorForPosition(position);
-//    float percent = mapper.getValueForPosition(position);
-
     std::cerr << "speed = " << position << " percent = " << percent << " factor = " << factor << std::endl;
 
-//!!!    bool slow = (position < 100);
     bool something = (position != 100);
-/*!!!
+
     int pc = lrintf(percent);
 
-    m_playSpeed->setToolTip(tr("Playback speed: %1%2%")
-                            .arg(!slow ? "+" : "")
-			    .arg(pc));
-*/
+    if (!something) {
+        contextHelpChanged(tr("Playback speed: Normal"));
+    } else {
+        contextHelpChanged(tr("Playback speed: %1%2%")
+                           .arg(position > 100 ? "+" : "")
+                           .arg(pc));
+    }
+
     m_playSharpen->setEnabled(something);
     m_playMono->setEnabled(something);
     bool sharpen = (something && m_playSharpen->isChecked());
     bool mono = (something && m_playMono->isChecked());
     m_playSource->setTimeStretch(factor, sharpen, mono);
+
+    updateMenuStates();
 }
 
 void
@@ -3830,6 +3915,30 @@
 }    
 
 void
+MainWindow::speedUpPlayback()
+{
+    int value = m_playSpeed->value();
+    value = value + m_playSpeed->pageStep();
+    if (value > m_playSpeed->maximum()) value = m_playSpeed->maximum();
+    m_playSpeed->setValue(value);
+}
+
+void
+MainWindow::slowDownPlayback()
+{
+    int value = m_playSpeed->value();
+    value = value - m_playSpeed->pageStep();
+    if (value < m_playSpeed->minimum()) value = m_playSpeed->minimum();
+    m_playSpeed->setValue(value);
+}
+
+void
+MainWindow::restoreNormalPlayback()
+{
+    m_playSpeed->setValue(m_playSpeed->defaultValue());
+}
+
+void
 MainWindow::playbackFrameChanged(unsigned long frame)
 {
     if (!(m_playSource && m_playSource->isPlaying()) || !getMainModel()) return;
@@ -4106,8 +4215,9 @@
 {
     QTreeView *view = new QTreeView();
     LayerTreeModel *tree = new LayerTreeModel(m_paneStack);
-    view->expand(tree->index(0, 0, QModelIndex()));
     view->setModel(tree);
+//    view->expand(tree->index(0, 0, QModelIndex()));
+    view->expandAll();
     view->show();
 }
 
--- a/main/MainWindow.h	Tue Jun 26 12:27:47 2007 +0000
+++ b/main/MainWindow.h	Thu Jun 28 14:50:58 2007 +0000
@@ -109,6 +109,9 @@
     void canFfwd(bool);
     void canRewind(bool);
     void canPlaySelection(bool);
+    void canSpeedUpPlayback(bool);
+    void canSlowDownPlayback(bool);
+    void canChangePlaybackSpeed(bool);
     void canSave(bool);
 
 public slots:
@@ -167,6 +170,9 @@
     void playSpeedChanged(int);
     void playSharpenToggled();
     void playMonoToggled();
+    void speedUpPlayback();
+    void slowDownPlayback();
+    void restoreNormalPlayback();
     void sampleRateMismatch(size_t, size_t, bool);
     void audioOverloadPluginDisabled();
 
@@ -266,6 +272,7 @@
     QMenu                   *m_paneMenu;
     QMenu                   *m_layerMenu;
     QMenu                   *m_transformsMenu;
+    QMenu                   *m_playbackMenu;
     QMenu                   *m_existingLayersMenu;
     QMenu                   *m_sliceMenu;
     QMenu                   *m_recentFilesMenu;
@@ -273,6 +280,10 @@
     QMenu                   *m_rightButtonMenu;
     QMenu                   *m_rightButtonLayerMenu;
     QMenu                   *m_rightButtonTransformsMenu;
+    QMenu                   *m_rightButtonPlaybackMenu;
+
+    QAction                 *m_ffwdAction;
+    QAction                 *m_rwdAction;
 
     bool                     m_documentModified;
     bool                     m_openingAudioFile;
@@ -331,6 +342,8 @@
 
     void addPane(const PaneConfiguration &configuration, QString text);
 
+    Layer *getSnapLayer() const;
+
     class PaneCallback : public SVFileReaderPaneCallback
     {
     public: