changeset 1361:2e3b3fadba27

Merge
author Chris Cannam
date Fri, 12 Oct 2018 11:17:29 +0100
parents e7da9c9635ba (diff) e848ea0850fe (current diff)
children d79e21855aef
files
diffstat 6 files changed, 354 insertions(+), 380 deletions(-) [+]
line wrap: on
line diff
--- a/layer/TimeRulerLayer.cpp	Fri Oct 05 10:25:52 2018 +0100
+++ b/layer/TimeRulerLayer.cpp	Fri Oct 12 11:17:29 2018 +0100
@@ -154,8 +154,12 @@
 
     sv_frame_t startFrame = v->getStartFrame();
     sv_frame_t endFrame = v->getEndFrame();
+    if (endFrame == startFrame) {
+        endFrame = startFrame + 1;
+    }
 
-    int minPixelSpacing = ViewManager::scalePixelSize(50);
+    int exampleWidth = QFontMetrics(QFont()).width("10:42.987654");
+    int minPixelSpacing = v->getXForViewX(exampleWidth);
 
     RealTime rtStart = RealTime::frame2RealTime(startFrame, sampleRate);
     RealTime rtEnd = RealTime::frame2RealTime(endFrame, sampleRate);
@@ -164,6 +168,15 @@
     if (count < 1) count = 1;
     RealTime rtGap = (rtEnd - rtStart) / count;
 
+#ifdef DEBUG_TIME_RULER_LAYER
+    SVCERR << "zoomLevel = " << v->getZoomLevel()
+           << ", startFrame = " << startFrame << ", endFrame = " << endFrame
+           << ", rtStart = " << rtStart << ", rtEnd = " << rtEnd
+           << ", paint width = " << v->getPaintWidth()
+           << ", minPixelSpacing = " << minPixelSpacing
+           << ", count = " << count << ", rtGap = " << rtGap << endl;
+#endif
+
     int64_t incus;
     quarterTicks = false;
 
@@ -197,6 +210,10 @@
         if (us > 0) { incus *= 2; us /= 2; }
     }
 
+#ifdef DEBUG_TIME_RULER_LAYER
+    SVCERR << "getMajorTickUSec: returning incus = " << incus << endl;
+#endif
+
     return incus;
 }
 
@@ -277,7 +294,7 @@
     // We always use the exact incus in our calculations for where to
     // draw the actual ticks or lines.
 
-    int minPixelSpacing = 50;
+    int minPixelSpacing = v->getXForViewX(50);
     sv_frame_t incFrame = lrint((double(incus) * sampleRate) / 1000000);
     int incX = int(round(v->getZoomLevel().framesToPixels(double(incFrame))));
     int ticks = 10;
--- a/view/Pane.cpp	Fri Oct 05 10:25:52 2018 +0100
+++ b/view/Pane.cpp	Fri Oct 12 11:17:29 2018 +0100
@@ -110,23 +110,6 @@
 
     if (!isVisible()) return;
 
-/*
-    int count = 0;
-    int currentLevel = 1;
-    int level = 1;
-    while (true) {
-        if (getZoomLevel() == level) currentLevel = count;
-        int newLevel = getZoomConstraintBlockSize(level + 1,
-                                                  ZoomConstraint::RoundUp);
-        if (newLevel == level) break;
-        if (newLevel == 131072) break; //!!! just because
-        level = newLevel;
-        ++count;
-    }
-
-    cerr << "Have " << count+1 << " zoom levels" << endl;
-*/
-
     Layer *layer = 0;
     if (getLayerCount() > 0) layer = getLayer(getLayerCount() - 1);
 
@@ -199,68 +182,17 @@
         connect(m_reset, SIGNAL(mouseLeft()), this, SLOT(mouseLeftWidget()));
     }
 
-    int count = 0;
-    int current = 0;
-    ZoomLevel level;
-
-    //!!! pull out into function (presumably in View)
-    bool haveConstraint = false;
-    for (LayerList::const_iterator i = m_layerStack.begin(); i != m_layerStack.end();
-         ++i) {
-        if ((*i)->getZoomConstraint() && !(*i)->supportsOtherZoomLevels()) {
-            haveConstraint = true;
-            break;
-        }
-    }
-
-    SVCERR << "haveConstraint = " << haveConstraint << endl;
-            
-    if (haveConstraint) {
-        while (true) {
-            //!!! this won't terminate if level is in the PixelsPerFrame zone
-            if (getZoomLevel() == level) current = count;
-            ZoomLevel newLevel = getZoomConstraintLevel(level.incremented(),
-                                                        ZoomConstraint::RoundUp);
-            SVCERR << "newLevel = " << newLevel << endl;
-            if (newLevel == level) break;
-            level = newLevel;
-            if (++count == 50) break;
-        }
-    } else {
-        // if we have no particular constraints, we can really spread out
-        //!!! this is nonsense in PixelsPerFrame zone
-        while (true) {
-            using namespace std::rel_ops;
-            if (getZoomLevel() >= level) current = count;
-            int step = level.level / 10;
-            int pwr = 0;
-            while (step > 0) {
-                ++pwr;
-                step /= 2;
-            }
-            step = 1;
-            while (pwr > 0) {
-                step *= 2;
-                --pwr;
-            }
-            cerr << level.level << ", step " << step << endl;
-            level.level += step;
-            if (++count == 100 || level.level > 262144) break;
-        }
-    }
-
-    //!!!
-    SVCERR << "Have " << count << " zoom levels" << endl;
-
-    m_hthumb->setMinimumValue(0);
+    int count = countZoomLevels();
+    int current = getZoomLevelIndex(getZoomLevel());
+    
+    m_hthumb->setMinimumValue(1);
     m_hthumb->setMaximumValue(count);
     m_hthumb->setValue(count - current);
 
-//    cerr << "set value to " << count-current << endl;
-
+//    cerr << "set value to " << count - 1 - current << endl;
 //    cerr << "default value is " << m_hthumb->getDefaultValue() << endl;
 
-    if (count != 50 && m_hthumb->getDefaultValue() == 0) {
+    if (m_hthumb->getDefaultValue() == 0) {
         m_hthumb->setDefaultValue(count - current);
 //        cerr << "set default value to " << m_hthumb->getDefaultValue() << endl;
     }
@@ -2441,53 +2373,7 @@
 void
 Pane::horizontalThumbwheelMoved(int value)
 {
-    //!!! dupe with updateHeadsUpDisplay
-
-    int count = 0;
-    ZoomLevel level;
-
-    //!!! pull out into function (presumably in View)
-    bool haveConstraint = false;
-    for (LayerList::const_iterator i = m_layerStack.begin(); i != m_layerStack.end();
-         ++i) {
-        if ((*i)->getZoomConstraint() && !(*i)->supportsOtherZoomLevels()) {
-            haveConstraint = true;
-            break;
-        }
-    }
-
-    if (haveConstraint) {
-        while (true) {
-            //!!! this won't terminate if level is in the PixelsPerFrame zone
-            if (m_hthumb->getMaximumValue() - value == count) break;
-            ZoomLevel newLevel = getZoomConstraintLevel(level.incremented(),
-                                                        ZoomConstraint::RoundUp);
-            if (newLevel == level) break;
-            level = newLevel;
-            if (++count == 50) break;
-        }
-    } else {
-        //!!! this is nonsense in PixelsPerFrame zone
-        while (true) {
-            if (m_hthumb->getMaximumValue() - value == count) break;
-            int step = level.level / 10;
-            int pwr = 0;
-            while (step > 0) {
-                ++pwr;
-                step /= 2;
-            }
-            step = 1;
-            while (pwr > 0) {
-                step *= 2;
-                --pwr;
-            }
-//            cerr << level << endl;
-            level.level += step;
-            if (++count == 100 || level.level > 262144) break;
-        }
-    }
-        
-//    cerr << "new level is " << level << endl;
+    ZoomLevel level = getZoomLevelByIndex(m_hthumb->getMaximumValue() - value);
     setZoomLevel(level);
 }    
 
--- a/view/Pane.h	Fri Oct 05 10:25:52 2018 +0100
+++ b/view/Pane.h	Fri Oct 12 11:17:29 2018 +0100
@@ -13,8 +13,8 @@
     COPYING included with this distribution for more information.
 */
 
-#ifndef _PANE_H_
-#define _PANE_H_
+#ifndef SV_PANE_H
+#define SV_PANE_H
 
 #include <QFrame>
 #include <QPoint>
@@ -212,7 +212,7 @@
 
     bool m_playbackFrameMoveScheduled;
     sv_frame_t m_playbackFrameMoveTo;
-
+    
     static QCursor *m_measureCursor1;
     static QCursor *m_measureCursor2;
 };
--- a/view/View.cpp	Fri Oct 05 10:25:52 2018 +0100
+++ b/view/View.cpp	Fri Oct 12 11:17:29 2018 +0100
@@ -20,13 +20,14 @@
 #include "base/Profiler.h"
 #include "base/Pitch.h"
 #include "base/Preferences.h"
+#include "base/HitCount.h"
 #include "ViewProxy.h"
 
 #include "layer/TimeRulerLayer.h"
 #include "layer/SingleColourLayer.h"
 #include "layer/PaintAssistant.h"
 
-#include "data/model/PowerOfSqrtTwoZoomConstraint.h"
+#include "data/model/RelativelyFineZoomConstraint.h"
 #include "data/model/RangeSummarisableTimeValueModel.h"
 
 #include "widgets/IconLoader.h"
@@ -63,6 +64,7 @@
     m_showProgress(showProgress),
     m_cache(0),
     m_buffer(0),
+    m_cacheValid(false),
     m_cacheCentreFrame(0),
     m_cacheZoomLevel(ZoomLevel::FramesPerPixel, 1024),
     m_selectionCached(false),
@@ -260,8 +262,7 @@
         return;
     }
 
-    delete m_cache;
-    m_cache = 0;
+    m_cacheValid = false;
 
     Layer *selectedLayer = 0;
 
@@ -293,8 +294,7 @@
 void
 View::overlayModeChanged()
 {
-    delete m_cache;
-    m_cache = 0;
+    m_cacheValid = false;
     update();
 }
 
@@ -617,8 +617,7 @@
 void
 View::addLayer(Layer *layer)
 {
-    delete m_cache;
-    m_cache = 0;
+    m_cacheValid = false;
 
     SingleColourLayer *scl = dynamic_cast<SingleColourLayer *>(layer);
     if (scl) scl->setDefaultColourFor(this);
@@ -689,8 +688,7 @@
         return;
     }
 
-    delete m_cache;
-    m_cache = 0;
+    m_cacheValid = false;
 
     for (LayerList::iterator i = m_fixedOrderLayers.begin();
          i != m_fixedOrderLayers.end();
@@ -907,8 +905,7 @@
     }
 
     if (recreate) {
-        delete m_cache;
-        m_cache = 0;
+        m_cacheValid = false;
     }
 
     emit layerModelChanged();
@@ -955,8 +952,7 @@
     }
 
     if (recreate) {
-        delete m_cache;
-        m_cache = 0;
+        m_cacheValid = false;
     }
 
     if (startFrame < myStartFrame) startFrame = myStartFrame;
@@ -991,9 +987,7 @@
 #ifdef DEBUG_VIEW_WIDGET_PAINT
     cerr << "View(" << this << ")::modelReplaced()" << endl;
 #endif
-    delete m_cache;
-    m_cache = 0;
-
+    m_cacheValid = false;
     update();
 }
 
@@ -1006,8 +1000,7 @@
     SVDEBUG << "View::layerParametersChanged()" << endl;
 #endif
 
-    delete m_cache;
-    m_cache = 0;
+    m_cacheValid = false;
     update();
 
     if (layer) {
@@ -1203,8 +1196,7 @@
 View::selectionChanged()
 {
     if (m_selectionCached) {
-        delete m_cache;
-        m_cache = 0;
+        m_cacheValid = false;
         m_selectionCached = false;
     }
     update();
@@ -1469,32 +1461,98 @@
 {
     using namespace std::rel_ops;
     
-    ZoomLevel candidate = zoomLevel;
-    bool haveCandidate = false;
-
-    PowerOfSqrtTwoZoomConstraint defaultZoomConstraint;
-
-    for (auto i = m_layerStack.begin(); i != m_layerStack.end(); ++i) {
-
-        const ZoomConstraint *zoomConstraint = (*i)->getZoomConstraint();
-        if (!zoomConstraint) zoomConstraint = &defaultZoomConstraint;
-
+    ZoomLevel candidate =
+        RelativelyFineZoomConstraint().getNearestZoomLevel(zoomLevel, dir);
+
+    for (auto i : m_layerStack) {
+
+        if (i->supportsOtherZoomLevels() || !(i->getZoomConstraint())) {
+            continue;
+        }
+        
         ZoomLevel thisLevel =
-            zoomConstraint->getNearestZoomLevel(zoomLevel, dir);
+            i->getZoomConstraint()->getNearestZoomLevel(zoomLevel, dir);
 
         // Go for the block size that's furthest from the one
         // passed in.  Most of the time, that's what we want.
-        if (!haveCandidate ||
-            (thisLevel > zoomLevel && thisLevel > candidate) ||
+        if ((thisLevel > zoomLevel && thisLevel > candidate) ||
             (thisLevel < zoomLevel && thisLevel < candidate)) {
             candidate = thisLevel;
-            haveCandidate = true;
         }
     }
 
     return candidate;
 }
 
+int
+View::countZoomLevels() const
+{
+    int n = 0;
+    ZoomLevel min = ZoomConstraint().getMinZoomLevel();
+    ZoomLevel max = ZoomConstraint().getMaxZoomLevel();
+    ZoomLevel level = min;
+    while (true) {
+        ++n;
+        if (level == max) {
+            break;
+        }
+        level = getZoomConstraintLevel
+            (level.incremented(), ZoomConstraint::RoundUp);
+    }
+//    cerr << "View::countZoomLevels: " << n << endl;
+    return n;
+}
+
+ZoomLevel
+View::getZoomLevelByIndex(int ix) const
+{
+    int n = 0;
+    ZoomLevel min = ZoomConstraint().getMinZoomLevel();
+    ZoomLevel max = ZoomConstraint().getMaxZoomLevel();
+    ZoomLevel level = min;
+    while (true) {
+        if (n == ix) {
+//            cerr << "View::getZoomLevelByIndex: " << ix << " -> " << level
+//                 << endl;
+            return level;
+        }
+        ++n;
+        if (level == max) {
+            break;
+        }
+        level = getZoomConstraintLevel
+            (level.incremented(), ZoomConstraint::RoundUp);
+    }
+//    cerr << "View::getZoomLevelByIndex: " << ix << " -> " << max << " (max)"
+//         << endl;
+    return max;
+}
+
+int
+View::getZoomLevelIndex(ZoomLevel z) const
+{
+    int n = 0;
+    ZoomLevel min = ZoomConstraint().getMinZoomLevel();
+    ZoomLevel max = ZoomConstraint().getMaxZoomLevel();
+    ZoomLevel level = min;
+    while (true) {
+        if (z == level) {
+//            cerr << "View::getZoomLevelIndex: " << z << " -> " << n
+//                 << endl;
+            return n;
+        }
+        ++n;
+        if (level == max) {
+            break;
+        }
+        level = getZoomConstraintLevel
+            (level.incremented(), ZoomConstraint::RoundUp);
+    }
+//    cerr << "View::getZoomLevelIndex: " << z << " -> " << n << " (max)"
+//         << endl;
+    return n;
+}
+
 bool
 View::areLayerColoursSignificant() const
 {
@@ -1734,98 +1792,93 @@
     }
 
     // ensure our constraints are met
-
-/*!!! Should we do this only if we have layers that can't support other
-  zoom levels?
-
-    m_zoomLevel = getZoomConstraintBlockSize(m_zoomLevel,
-                                             ZoomConstraint::RoundUp);
-*/
-
-    QPainter paint;
-    bool repaintCache = false;
-    bool paintedCacheRect = false;
-
-    QRect cacheRect(rect());
-
+    m_zoomLevel = getZoomConstraintLevel
+        (m_zoomLevel, ZoomConstraint::RoundNearest);
+
+    // We have a cache, which retains the state of scrollable (back)
+    // layers from one paint to the next, and a buffer, which we paint
+    // onto before copying directly to the widget. Both are at scaled
+    // resolution (e.g. 2x on a pixel-doubled display), whereas the
+    // paint event always comes in at formal (1x) resolution.
+
+    // If we touch the cache, we always leave it in a valid state
+    // across its whole extent. When another method invalidates the
+    // cache, it does so by setting m_cacheValid false, so if that
+    // flag is true on entry, then the cache is valid across its whole
+    // extent - although it may be valid for a different centre frame,
+    // zoom level, or view size from those now in effect.
+
+    // Our process goes:
+    // 
+    // 1. Check whether we have any scrollable (cacheable) layers.  If
+    //    we don't, then invalidate and ignore the cache and go to
+    //    step 5.  Otherwise:
+    // 
+    // 2. Check the cache, scroll as necessary, identify any area that
+    //    needs to be refreshed (this might be the whole cache).
+    //
+    // 3. Paint to cache the area that needs to be refreshed, from the
+    //    stack of scrollable layers.
+    //
+    // 4. Paint to buffer from cache: if there are no non-cached areas
+    //    or selections and the cache has not scrolled, then paint the
+    //    union of the area of cache that has changed and the area
+    //    that the paint event reported as exposed; otherwise paint
+    //    the whole.
+    //
+    // 5. Paint the exposed area to the buffer from the cache plus all
+    //    the layers that haven't been cached, plus selections etc.
+    //
+    // 6. Paint the exposed rect from the buffer.
+    //
+    // Note that all rects except the target for the final step are at
+    // cache (scaled, 2x as applicable) resolution.
+
+    int dpratio = effectiveDevicePixelRatio();
+
+    QRect requestedPaintArea(scaledRect(rect(), dpratio));
     if (e) {
-        cacheRect &= e->rect();
-#ifdef DEBUG_VIEW_WIDGET_PAINT
-        cerr << "paint rect " << cacheRect.width() << "x" << cacheRect.height()
-                  << ", my rect " << width() << "x" << height() << endl;
-#endif
+        // cut down to only the area actually exposed
+        requestedPaintArea &= scaledRect(e->rect(), dpratio);
     }
 
-    QRect nonCacheRect(cacheRect);
-
-    int dpratio = effectiveDevicePixelRatio();
-
     // If not all layers are scrollable, but some of the back layers
     // are, we should store only those in the cache.
 
     bool layersChanged = false;
     LayerList scrollables = getScrollableBackLayers(true, layersChanged);
     LayerList nonScrollables = getNonScrollableFrontLayers(true, layersChanged);
-    bool selectionCacheable = nonScrollables.empty();
-    bool haveSelections = m_manager && !m_manager->getSelections().empty();
-
-    // If all the non-scrollable layers are non-opaque, then we draw
-    // the selection rectangle behind them and cache it.  If any are
-    // opaque, however, or if our device-pixel ratio is not 1 (so we
-    // need to paint direct to the widget), then we can't cache.
-    //
-    if (dpratio == 1) {
-
-        if (!selectionCacheable) {
-            selectionCacheable = true;
-            for (LayerList::const_iterator i = nonScrollables.begin();
-                 i != nonScrollables.end(); ++i) {
-                if ((*i)->isLayerOpaque()) {
-                    selectionCacheable = false;
-                    break;
-                }
-            }
-        }
-
-        if (selectionCacheable) {
-            QPoint localPos;
-            bool closeToLeft, closeToRight;
-            if (shouldIlluminateLocalSelection
-                (localPos, closeToLeft, closeToRight)) {
-                selectionCacheable = false;
-            }
-        }
-
-    } else {
-
-        selectionCacheable = false;
-    }
 
 #ifdef DEBUG_VIEW_WIDGET_PAINT
     cerr << "View(" << this << ")::paintEvent: have " << scrollables.size()
               << " scrollable back layers and " << nonScrollables.size()
               << " non-scrollable front layers" << endl;
-    cerr << "haveSelections " << haveSelections << ", selectionCacheable "
-              << selectionCacheable << ", m_selectionCached " << m_selectionCached << endl;
 #endif
 
-    if (layersChanged || scrollables.empty() ||
-        (haveSelections && (selectionCacheable != m_selectionCached))) {
-        delete m_cache;
-        m_cache = 0;
-        m_selectionCached = false;
+    if (layersChanged || scrollables.empty()) {
+        m_cacheValid = false;
     }
 
-    QSize scaledCacheSize(scaledSize(size(), dpratio));
-    QRect scaledCacheRect(scaledRect(cacheRect, dpratio));
-
-    if (!m_buffer || scaledCacheSize != m_buffer->size()) {
+    QRect wholeArea(scaledRect(rect(), dpratio));
+    QSize wholeSize(scaledSize(size(), dpratio));
+
+    if (!m_buffer || wholeSize != m_buffer->size()) {
         delete m_buffer;
-        m_buffer = new QPixmap(scaledCacheSize);
+        m_buffer = new QPixmap(wholeSize);
     }
+
+    bool shouldUseCache = false;
+    bool shouldRepaintCache = false;
+    QRect cacheAreaToRepaint;
     
+    static HitCount count("View cache");
+
     if (!scrollables.empty()) {
 
+        shouldUseCache = true;
+        shouldRepaintCache = true;
+        cacheAreaToRepaint = wholeArea;
+
 #ifdef DEBUG_VIEW_WIDGET_PAINT
         cerr << "View(" << this << "): cache " << m_cache << ", cache zoom "
                   << m_cacheZoomLevel << ", zoom " << m_zoomLevel << endl;
@@ -1833,209 +1886,185 @@
 
         using namespace std::rel_ops;
     
-        if (!m_cache ||
+        if (!m_cacheValid ||
+            !m_cache ||
             m_cacheZoomLevel != m_zoomLevel ||
-            scaledCacheSize != m_cache->size()) {
-
-            // cache is not valid
-
-            if (cacheRect.width() < width()/10) {
-                delete m_cache;
-                m_cache = 0;
+            m_cache->size() != wholeSize) {
+
+            // cache is not valid at all
+
+            if (requestedPaintArea.width() < wholeSize.width() / 10) {
+
+                m_cacheValid = false;
+                shouldUseCache = false;
+                shouldRepaintCache = false;
+
 #ifdef DEBUG_VIEW_WIDGET_PAINT
-                cerr << "View(" << this << ")::paintEvent: small repaint, not bothering to recreate cache" << endl;
+                cerr << "View(" << this << ")::paintEvent: cache is invalid but only small area requested, will repaint directly instead" << endl;
 #endif
             } else {
-                delete m_cache;
-                m_cache = new QPixmap(scaledCacheSize);
+
+                if (!m_cache ||
+                    m_cache->size() != wholeSize) {
+                    delete m_cache;
+                    m_cache = new QPixmap(wholeSize);
+                }
+
 #ifdef DEBUG_VIEW_WIDGET_PAINT
-                cerr << "View(" << this << ")::paintEvent: recreated cache" << endl;
+                cerr << "View(" << this << ")::paintEvent: cache is invalid, will repaint whole" << endl;
 #endif
-                cacheRect = rect();
-                repaintCache = true;
             }
 
+            count.miss();
+            
         } else if (m_cacheCentreFrame != m_centreFrame) {
 
-            int dx =
-                getXForFrame(m_cacheCentreFrame) -
-                getXForFrame(m_centreFrame);
-
-            if (dx > -width() && dx < width()) {
-                static QPixmap *tmpPixmap = 0;
-                if (!tmpPixmap || tmpPixmap->size() != scaledCacheSize) {
-                    delete tmpPixmap;
-                    tmpPixmap = new QPixmap(scaledCacheSize);
+            int dx = dpratio * (getXForFrame(m_cacheCentreFrame) -
+                                getXForFrame(m_centreFrame));
+
+            if (dx > -m_cache->width() && dx < m_cache->width()) {
+
+                m_cache->scroll(dx, 0, m_cache->rect(), 0);
+
+                if (dx < 0) {
+                    cacheAreaToRepaint = 
+                        QRect(m_cache->width() + dx, 0, -dx, m_cache->height());
+                } else {
+                    cacheAreaToRepaint = 
+                        QRect(0, 0, dx, m_cache->height());
                 }
-                paint.begin(tmpPixmap);
-                paint.drawPixmap(0, 0, *m_cache);
-                paint.end();
-                paint.begin(m_cache);
-                paint.drawPixmap(dx, 0, *tmpPixmap);
-                paint.end();
-                if (dx < 0) {
-                    cacheRect = QRect(width() + dx, 0, -dx, height());
-                } else {
-                    cacheRect = QRect(0, 0, dx, height());
-                }
+
+                count.partial();
+
 #ifdef DEBUG_VIEW_WIDGET_PAINT
                 cerr << "View(" << this << ")::paintEvent: scrolled cache by " << dx << endl;
 #endif
             } else {
-                cacheRect = rect();
+                count.miss();
 #ifdef DEBUG_VIEW_WIDGET_PAINT
                 cerr << "View(" << this << ")::paintEvent: scrolling too far" << endl;
 #endif
             }
-            repaintCache = true;
 
         } else {
 #ifdef DEBUG_VIEW_WIDGET_PAINT
             cerr << "View(" << this << ")::paintEvent: cache is good" << endl;
 #endif
-            paint.begin(m_buffer);
-            paint.drawPixmap(scaledCacheRect, *m_cache, scaledCacheRect);
-            paint.end();
-            QFrame::paintEvent(e);
-            paintedCacheRect = true;
+            count.hit();
+            shouldRepaintCache = false;
         }
-
+    }
+
+#ifdef DEBUG_VIEW_WIDGET_PAINT
+    cerr << "View(" << this << ")::paintEvent: m_cacheValid = " << m_cacheValid << ", shouldUseCache = " << shouldUseCache << ", shouldRepaintCache = " << shouldRepaintCache << ", cacheAreaToRepaint = " << cacheAreaToRepaint.x() << "," << cacheAreaToRepaint.y() << " " << cacheAreaToRepaint.width() << "x" << cacheAreaToRepaint.height() << endl;
+#endif
+
+    if (shouldRepaintCache && !shouldUseCache) {
+        // If we are repainting the cache, then we paint the
+        // scrollables only to the cache, not to the buffer. So if
+        // shouldUseCache is also false, then the scrollables can't
+        // appear because they will only be on the cache
+        throw std::logic_error("ERROR: shouldRepaintCache is true, but shouldUseCache is false: this can't lead to the correct result");
+    }
+
+    // Scrollable (cacheable) items first. If we are repainting the
+    // cache, then we paint these to the cache; otherwise straight to
+    // the buffer.
+
+    ViewProxy proxy(this, dpratio);
+    QRect areaToPaint;
+    QPainter paint;
+
+    if (shouldRepaintCache) {
+        paint.begin(m_cache);
+        areaToPaint = cacheAreaToRepaint;
+    } else {
+        paint.begin(m_buffer);
+        areaToPaint = requestedPaintArea;
+    }
+
+    setPaintFont(paint);
+    paint.setClipRect(areaToPaint);
+
+    paint.setPen(getBackground());
+    paint.setBrush(getBackground());
+    paint.drawRect(areaToPaint);
+
+    paint.setPen(getForeground());
+    paint.setBrush(Qt::NoBrush);
+        
+    for (LayerList::iterator i = scrollables.begin();
+         i != scrollables.end(); ++i) {
+
+        paint.setRenderHint(QPainter::Antialiasing, false);
+        paint.save();
+
+#ifdef DEBUG_VIEW_WIDGET_PAINT
+        cerr << "Painting scrollable layer " << *i << " using proxy with shouldRepaintCache = " << shouldRepaintCache << ", dpratio = " << dpratio << ", areaToPaint = " << areaToPaint.x() << "," << areaToPaint.y() << " " << areaToPaint.width() << "x" << areaToPaint.height() << endl;
+#endif
+
+        (*i)->paint(&proxy, paint, areaToPaint);
+
+        paint.restore();
+    }
+
+    paint.end();
+
+    if (shouldRepaintCache) {
+        // and now we have
+        m_cacheValid = true;
         m_cacheCentreFrame = m_centreFrame;
         m_cacheZoomLevel = m_zoomLevel;
     }
 
-#ifdef DEBUG_VIEW_WIDGET_PAINT
-//    cerr << "View(" << this << ")::paintEvent: cacheRect " << cacheRect << ", nonCacheRect " << (nonCacheRect | cacheRect) << ", repaintCache " << repaintCache << ", paintedCacheRect " << paintedCacheRect << endl;
-#endif
-
-    // Scrollable (cacheable) items first
-
-    ViewProxy proxy(this, dpratio);
-    
-    if (!paintedCacheRect) {
-
-        QRect rectToPaint;
-
-        if (repaintCache) {
-            paint.begin(m_cache);
-            rectToPaint = scaledCacheRect;
-        } else {
-            paint.begin(m_buffer);
-            rectToPaint = scaledCacheRect;
-        }
-
-        setPaintFont(paint);
-        paint.setClipRect(rectToPaint);
-
-        paint.setPen(getBackground());
-        paint.setBrush(getBackground());
-        paint.drawRect(rectToPaint);
-
-        paint.setPen(getForeground());
-        paint.setBrush(Qt::NoBrush);
-        
-        for (LayerList::iterator i = scrollables.begin(); i != scrollables.end(); ++i) {
-            paint.setRenderHint(QPainter::Antialiasing, false);
-            paint.save();
-#ifdef DEBUG_VIEW_WIDGET_PAINT
-            cerr << "Painting scrollable layer " << *i << " using proxy with repaintCache = " << repaintCache << ", dpratio = " << dpratio << ", rectToPaint = " << rectToPaint.x() << "," << rectToPaint.y() << " " << rectToPaint.width() << "x" << rectToPaint.height() << endl;
-#endif
-            (*i)->paint(&proxy, paint, rectToPaint);
-            paint.restore();
-        }
-
-        if (haveSelections && selectionCacheable) {
-            drawSelections(paint);
-            m_selectionCached = repaintCache;
-        }
-        
+    if (shouldUseCache) {
+        paint.begin(m_buffer);
+        paint.drawPixmap(requestedPaintArea, *m_cache, requestedPaintArea);
         paint.end();
-
-        if (repaintCache) {
-            cacheRect |= (e ? e->rect() : rect());
-            scaledCacheRect = scaledRect(cacheRect, dpratio);
-            paint.begin(m_buffer);
-            paint.drawPixmap(scaledCacheRect, *m_cache, scaledCacheRect);
-            paint.end();
-        }
     }
 
-    // Now non-cacheable items.  We always need to redraw the
-    // non-cacheable items across at least the area we drew of the
-    // cacheable items.
-
-    nonCacheRect |= cacheRect;
-
-    QRect scaledNonCacheRect = scaledRect(nonCacheRect, dpratio);
-    
+    // Now non-cacheable items.
+
     paint.begin(m_buffer);
-    paint.setClipRect(scaledNonCacheRect);
+    paint.setClipRect(requestedPaintArea);
     setPaintFont(paint);
     if (scrollables.empty()) {
         paint.setPen(getBackground());
         paint.setBrush(getBackground());
-        paint.drawRect(scaledNonCacheRect);
+        paint.drawRect(requestedPaintArea);
     }
         
     paint.setPen(getForeground());
     paint.setBrush(Qt::NoBrush);
         
-    for (LayerList::iterator i = nonScrollables.begin(); i != nonScrollables.end(); ++i) {
+    for (LayerList::iterator i = nonScrollables.begin(); 
+         i != nonScrollables.end(); ++i) {
+
 //        Profiler profiler2("View::paintEvent non-cacheable");
 #ifdef DEBUG_VIEW_WIDGET_PAINT
-        cerr << "Painting non-scrollable layer " << *i << " without proxy with repaintCache = " << repaintCache << ", dpratio = " << dpratio << ", rectToPaint = " << nonCacheRect.x() << "," << nonCacheRect.y() << " " << nonCacheRect.width() << "x" << nonCacheRect.height() << endl;
+        cerr << "Painting non-scrollable layer " << *i << " without proxy with shouldRepaintCache = " << shouldRepaintCache << ", dpratio = " << dpratio << ", requestedPaintArea = " << requestedPaintArea.x() << "," << requestedPaintArea.y() << " " << requestedPaintArea.width() << "x" << requestedPaintArea.height() << endl;
 #endif
-        (*i)->paint(&proxy, paint, scaledNonCacheRect);
+        (*i)->paint(&proxy, paint, requestedPaintArea);
     }
         
     paint.end();
-    
-    paint.begin(this);
-    QRect finalPaintRect = e ? e->rect() : rect();
-    paint.drawPixmap(finalPaintRect, *m_buffer, scaledRect(finalPaintRect, dpratio));
-    paint.end();
+
+    // Now paint to widget from buffer: target rects from here on,
+    // unlike all the preceding, are at formal (1x) resolution
 
     paint.begin(this);
     setPaintFont(paint);
     if (e) paint.setClipRect(e->rect());
-    if (!m_selectionCached) {
-        drawSelections(paint);
-    }
+
+    QRect finalPaintRect = e ? e->rect() : rect();
+    paint.drawPixmap(finalPaintRect, *m_buffer, 
+                     scaledRect(finalPaintRect, dpratio));
+
+    drawSelections(paint);
+    drawPlayPointer(paint);
+
     paint.end();
 
-    bool showPlayPointer = true;
-    if (m_followPlay == PlaybackScrollContinuous) {
-        showPlayPointer = false;
-    } else if (m_playPointerFrame <= getStartFrame() ||
-               m_playPointerFrame >= getEndFrame()) {
-        showPlayPointer = false;
-    } else if (m_manager && !m_manager->isPlaying()) {
-        if (m_playPointerFrame == getCentreFrame() &&
-            m_manager->shouldShowCentreLine() &&
-            m_followPlay != PlaybackIgnore) {
-            // Don't show the play pointer when it is redundant with
-            // the centre line
-            showPlayPointer = false;
-        }
-    }
-    
-    if (showPlayPointer) {
-
-        paint.begin(this);
-
-        int playx = getXForFrame(m_playPointerFrame);
-        
-        paint.setPen(getForeground());
-        paint.drawLine(playx - 1, 0, playx - 1, height() - 1);
-        paint.drawLine(playx + 1, 0, playx + 1, height() - 1);
-        paint.drawPoint(playx, 0);
-        paint.drawPoint(playx, height() - 1);
-        paint.setPen(getBackground());
-        paint.drawLine(playx, 1, playx, height() - 2);
-
-        paint.end();
-    }
-
     QFrame::paintEvent(e);
 }
 
@@ -2197,6 +2226,40 @@
 }
 
 void
+View::drawPlayPointer(QPainter &paint)
+{
+    bool showPlayPointer = true;
+
+    if (m_followPlay == PlaybackScrollContinuous) {
+        showPlayPointer = false;
+    } else if (m_playPointerFrame <= getStartFrame() ||
+               m_playPointerFrame >= getEndFrame()) {
+        showPlayPointer = false;
+    } else if (m_manager && !m_manager->isPlaying()) {
+        if (m_playPointerFrame == getCentreFrame() &&
+            m_manager->shouldShowCentreLine() &&
+            m_followPlay != PlaybackIgnore) {
+            // Don't show the play pointer when it is redundant with
+            // the centre line
+            showPlayPointer = false;
+        }
+    }
+    
+    if (showPlayPointer) {
+
+        int playx = getXForFrame(m_playPointerFrame);
+        
+        paint.setPen(getForeground());
+        paint.drawLine(playx - 1, 0, playx - 1, height() - 1);
+        paint.drawLine(playx + 1, 0, playx + 1, height() - 1);
+        paint.drawPoint(playx, 0);
+        paint.drawPoint(playx, height() - 1);
+        paint.setPen(getBackground());
+        paint.drawLine(playx, 1, playx, height() - 2);
+    }
+}
+
+void
 View::drawMeasurementRect(QPainter &paint, const Layer *topLayer, QRect r,
                           bool focus) const
 {
--- a/view/View.h	Fri Oct 05 10:25:52 2018 +0100
+++ b/view/View.h	Fri Oct 12 11:17:29 2018 +0100
@@ -440,6 +440,7 @@
     virtual void paintEvent(QPaintEvent *e);
     virtual void drawSelections(QPainter &);
     virtual bool shouldLabelSelections() const { return true; }
+    virtual void drawPlayPointer(QPainter &);
     virtual bool render(QPainter &paint, int x0, sv_frame_t f0, sv_frame_t f1);
     virtual void setPaintFont(QPainter &paint);
 
@@ -457,10 +458,16 @@
     bool areLayersScrollable() const;
     LayerList getScrollableBackLayers(bool testChanged, bool &changed) const;
     LayerList getNonScrollableFrontLayers(bool testChanged, bool &changed) const;
+
     ZoomLevel getZoomConstraintLevel(ZoomLevel level,
                                      ZoomConstraint::RoundingDirection dir =
                                      ZoomConstraint::RoundNearest) const;
 
+    // These three are slow, intended for indexing GUI thumbwheel stuff
+    int countZoomLevels() const;
+    int getZoomLevelIndex(ZoomLevel level) const;
+    ZoomLevel getZoomLevelByIndex(int ix) const;
+    
     // True if the top layer(s) use colours for meaningful things.  If
     // this is the case, selections will be shown using unfilled boxes
     // rather than with a translucent fill.
@@ -493,6 +500,7 @@
 
     QPixmap            *m_cache;  // I own this
     QPixmap            *m_buffer; // I own this
+    bool                m_cacheValid;
     sv_frame_t          m_cacheCentreFrame;
     ZoomLevel           m_cacheZoomLevel;
     bool                m_selectionCached;
--- a/widgets/SubdividingMenu.cpp	Fri Oct 05 10:25:52 2018 +0100
+++ b/widgets/SubdividingMenu.cpp	Fri Oct 12 11:17:29 2018 +0100
@@ -85,7 +85,7 @@
                           return QString::localeAwareCompare(s1, s2) < 0;
                       };
     
-    set<QString, typeof(comparator)> sortedEntries(comparator);
+    set<QString, decltype(comparator)> sortedEntries(comparator);
     sortedEntries.insert(entries.begin(), entries.end());
     
     for (auto j = sortedEntries.begin(); j != sortedEntries.end(); ++j) {
@@ -180,7 +180,7 @@
     auto comparator = [](QString s1, QString s2) -> bool {
                           return QString::localeAwareCompare(s1, s2) < 0;
                       };
-    set<QString, typeof(comparator)> sortedEntries(comparator);
+    set<QString, decltype(comparator)> sortedEntries(comparator);
     for (auto i: m_pendingEntries) {
         sortedEntries.insert(i.first);
     }