diff view/View.cpp @ 1216:dc2af6616c83

Merge from branch 3.0-integration
author Chris Cannam
date Fri, 13 Jan 2017 10:29:50 +0000
parents c603aba61702
children a34a2a25907c
line wrap: on
line diff
--- a/view/View.cpp	Fri Mar 04 12:23:31 2016 +0000
+++ b/view/View.cpp	Fri Jan 13 10:29:50 2017 +0000
@@ -20,9 +20,12 @@
 #include "base/Profiler.h"
 #include "base/Pitch.h"
 #include "base/Preferences.h"
+#include "ViewProxy.h"
 
 #include "layer/TimeRulerLayer.h"
 #include "layer/SingleColourLayer.h"
+#include "layer/PaintAssistant.h"
+
 #include "data/model/PowerOfSqrtTwoZoomConstraint.h"
 #include "data/model/RangeSummarisableTimeValueModel.h"
 
@@ -37,19 +40,19 @@
 #include <QFont>
 #include <QMessageBox>
 #include <QPushButton>
+#include <QSettings>
+#include <QSvgGenerator>
 
 #include <iostream>
 #include <cassert>
 #include <cmath>
 
-#include <unistd.h>
-
 //#define DEBUG_VIEW 1
 //#define DEBUG_VIEW_WIDGET_PAINT 1
 
-
 View::View(QWidget *w, bool showProgress) :
     QFrame(w),
+    m_id(getNextId()),
     m_centreFrame(0),
     m_zoomLevel(1024),
     m_followPan(true),
@@ -59,6 +62,7 @@
     m_playPointerFrame(0),
     m_showProgress(showProgress),
     m_cache(0),
+    m_buffer(0),
     m_cacheCentreFrame(0),
     m_cacheZoomLevel(1024),
     m_selectionCached(false),
@@ -76,6 +80,8 @@
 
     m_deleting = true;
     delete m_propertyContainer;
+    delete m_cache;
+    delete m_buffer;
 }
 
 PropertyContainer::PropertyList
@@ -362,15 +368,17 @@
 sv_frame_t
 View::getFrameForX(int x) const
 {
-    int z = m_zoomLevel;
+    sv_frame_t z = m_zoomLevel; // nb not just int, or multiplication may overflow
     sv_frame_t frame = m_centreFrame - (width()/2) * z;
 
+    frame = (frame / z) * z; // this is start frame
+    frame = frame + x * z;
+
 #ifdef DEBUG_VIEW_WIDGET_PAINT
-    SVDEBUG << "View::getFrameForX(" << x << "): z = " << z << ", m_centreFrame = " << m_centreFrame << ", width() = " << width() << ", frame = " << frame << endl;
+    cerr << "View::getFrameForX(" << x << "): z = " << z << ", m_centreFrame = " << m_centreFrame << ", width() = " << width() << ", frame = " << frame << endl;
 #endif
 
-    frame = (frame / z) * z; // this is start frame
-    return frame + x * z;
+    return frame;
 }
 
 double
@@ -408,12 +416,12 @@
 }
 
 double
-View::getFrequencyForY(int y,
+View::getFrequencyForY(double y,
 		       double minf,
 		       double maxf,
 		       bool logarithmic) const
 {
-    int h = height();
+    double h = height();
 
     if (logarithmic) {
 
@@ -448,9 +456,30 @@
     return m_zoomLevel;
 }
 
+int
+View::effectiveDevicePixelRatio() const
+{
+#ifdef Q_OS_MAC
+    int dpratio = devicePixelRatio();
+    if (dpratio > 1) {
+        QSettings settings;
+        settings.beginGroup("Preferences");
+        if (!settings.value("scaledHiDpi", true).toBool()) {
+            dpratio = 1;
+        }
+        settings.endGroup();
+    }
+    return dpratio;
+#else
+    return 1;
+#endif
+}
+
 void
 View::setZoomLevel(int z)
 {
+    int dpratio = effectiveDevicePixelRatio();
+    if (z < dpratio) return;
     if (z < 1) z = 1;
     if (m_zoomLevel != int(z)) {
 	m_zoomLevel = z;
@@ -784,56 +813,6 @@
 }
 
 void
-View::drawVisibleText(QPainter &paint, int x, int y, QString text, TextStyle style) const
-{
-    if (style == OutlinedText || style == OutlinedItalicText) {
-
-        paint.save();
-
-        if (style == OutlinedItalicText) {
-            QFont f(paint.font());
-            f.setItalic(true);
-            paint.setFont(f);
-        }
-
-        QColor penColour, surroundColour, boxColour;
-
-        penColour = getForeground();
-        surroundColour = getBackground();
-        boxColour = surroundColour;
-        boxColour.setAlpha(127);
-
-        paint.setPen(Qt::NoPen);
-        paint.setBrush(boxColour);
-        
-        QRect r = paint.fontMetrics().boundingRect(text);
-        r.translate(QPoint(x, y));
-//        cerr << "drawVisibleText: r = " << r.x() << "," <<r.y() << " " << r.width() << "x" << r.height() << endl;
-        paint.drawRect(r);
-        paint.setBrush(Qt::NoBrush);
-
-	paint.setPen(surroundColour);
-
-	for (int dx = -1; dx <= 1; ++dx) {
-	    for (int dy = -1; dy <= 1; ++dy) {
-		if (!(dx || dy)) continue;
-		paint.drawText(x + dx, y + dy, text);
-	    }
-	}
-
-	paint.setPen(penColour);
-
-	paint.drawText(x, y, text);
-
-        paint.restore();
-
-    } else {
-
-	cerr << "ERROR: View::drawVisibleText: Boxed style not yet implemented!" << endl;
-    }
-}
-
-void
 View::setPlaybackFollow(PlaybackFollowMode m)
 {
     m_followPlay = m;
@@ -1027,7 +1006,7 @@
     f = getAlignedPlaybackFrame();
 
 #ifdef DEBUG_VIEW
-    cerr << " -> aligned frame = " << af << endl;
+    cerr << " -> aligned frame = " << f << endl;
 #endif
 
     movePlayPointer(f);
@@ -1653,11 +1632,27 @@
 void
 View::setPaintFont(QPainter &paint)
 {
+    int scaleFactor = 1;
+    int dpratio = effectiveDevicePixelRatio();
+    if (dpratio > 1) {
+        QPaintDevice *dev = paint.device();
+        if (dynamic_cast<QPixmap *>(dev) || dynamic_cast<QImage *>(dev)) {
+            scaleFactor = dpratio;
+        }
+    }
+
     QFont font(paint.font());
-    font.setPointSize(Preferences::getInstance()->getViewFontSize());
+    font.setPointSize(Preferences::getInstance()->getViewFontSize()
+                      * scaleFactor);
     paint.setFont(font);
 }
 
+QRect
+View::getPaintRect() const
+{
+    return rect();
+}
+
 void
 View::paintEvent(QPaintEvent *e)
 {
@@ -1694,6 +1689,8 @@
 
     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.
 
@@ -1705,25 +1702,34 @@
 
     // 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, we can't cache.
+    // 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 (!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;
-	}
+    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
@@ -1741,6 +1747,14 @@
 	m_selectionCached = false;
     }
 
+    QSize scaledCacheSize(scaledSize(size(), dpratio));
+    QRect scaledCacheRect(scaledRect(cacheRect, dpratio));
+
+    if (!m_buffer || scaledCacheSize != m_buffer->size()) {
+        delete m_buffer;
+        m_buffer = new QPixmap(scaledCacheSize);
+    }
+    
     if (!scrollables.empty()) {
 
 #ifdef DEBUG_VIEW_WIDGET_PAINT
@@ -1750,8 +1764,7 @@
 
 	if (!m_cache ||
 	    m_cacheZoomLevel != m_zoomLevel ||
-	    width() != m_cache->width() ||
-	    height() != m_cache->height()) {
+            scaledCacheSize != m_cache->size()) {
 
 	    // cache is not valid
 
@@ -1763,7 +1776,7 @@
 #endif
 	    } else {
 		delete m_cache;
-		m_cache = new QPixmap(width(), height());
+		m_cache = new QPixmap(scaledCacheSize);
 #ifdef DEBUG_VIEW_WIDGET_PAINT
 		cerr << "View(" << this << ")::paintEvent: recreated cache" << endl;
 #endif
@@ -1778,24 +1791,10 @@
 		getXForFrame(m_centreFrame);
 
 	    if (dx > -width() && dx < width()) {
-#ifdef PIXMAP_COPY_TO_SELF
-                // This is not normally defined. Copying a pixmap to
-		// itself doesn't work properly on Windows, Mac, or
-		// X11 with the raster backend (it only works when
-		// moving in one direction and then presumably only by
-		// accident).  It does actually seem to be fine on X11
-		// with the native backend, but we prefer not to use
-		// that anyway
-		paint.begin(m_cache);
-		paint.drawPixmap(dx, 0, *m_cache);
-		paint.end();
-#else
 		static QPixmap *tmpPixmap = 0;
-		if (!tmpPixmap ||
-		    tmpPixmap->width() != width() ||
-		    tmpPixmap->height() != height()) {
+		if (!tmpPixmap || tmpPixmap->size() != scaledCacheSize) {
 		    delete tmpPixmap;
-		    tmpPixmap = new QPixmap(width(), height());
+		    tmpPixmap = new QPixmap(scaledCacheSize);
 		}
 		paint.begin(tmpPixmap);
 		paint.drawPixmap(0, 0, *m_cache);
@@ -1803,7 +1802,6 @@
 		paint.begin(m_cache);
 		paint.drawPixmap(dx, 0, *tmpPixmap);
 		paint.end();
-#endif
 		if (dx < 0) {
 		    cacheRect = QRect(width() + dx, 0, -dx, height());
 		} else {
@@ -1824,8 +1822,8 @@
 #ifdef DEBUG_VIEW_WIDGET_PAINT
 	    cerr << "View(" << this << ")::paintEvent: cache is good" << endl;
 #endif
-	    paint.begin(this);
-	    paint.drawPixmap(cacheRect, *m_cache, cacheRect);
+	    paint.begin(m_buffer);
+	    paint.drawPixmap(scaledCacheRect, *m_cache, scaledCacheRect);
 	    paint.end();
 	    QFrame::paintEvent(e);
 	    paintedCacheRect = true;
@@ -1841,16 +1839,26 @@
 
     // Scrollable (cacheable) items first
 
+    ViewProxy proxy(this, dpratio);
+    
     if (!paintedCacheRect) {
 
-	if (repaintCache) paint.begin(m_cache);
-	else paint.begin(this);
+        QRect rectToPaint;
+
+	if (repaintCache) {
+            paint.begin(m_cache);
+            rectToPaint = scaledCacheRect;
+        } else {
+            paint.begin(m_buffer);
+            rectToPaint = scaledCacheRect;
+        }
+
         setPaintFont(paint);
-	paint.setClipRect(cacheRect);
+	paint.setClipRect(rectToPaint);
 
         paint.setPen(getBackground());
         paint.setBrush(getBackground());
-	paint.drawRect(cacheRect);
+	paint.drawRect(rectToPaint);
 
 	paint.setPen(getForeground());
 	paint.setBrush(Qt::NoBrush);
@@ -1858,7 +1866,10 @@
 	for (LayerList::iterator i = scrollables.begin(); i != scrollables.end(); ++i) {
 	    paint.setRenderHint(QPainter::Antialiasing, false);
 	    paint.save();
-	    (*i)->paint(this, paint, cacheRect);
+#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();
 	}
 
@@ -1871,8 +1882,9 @@
 
 	if (repaintCache) {
 	    cacheRect |= (e ? e->rect() : rect());
-	    paint.begin(this);
-	    paint.drawPixmap(cacheRect, *m_cache, cacheRect);
+            scaledCacheRect = scaledRect(cacheRect, dpratio);
+	    paint.begin(m_buffer);
+	    paint.drawPixmap(scaledCacheRect, *m_cache, scaledCacheRect);
 	    paint.end();
 	}
     }
@@ -1883,13 +1895,15 @@
 
     nonCacheRect |= cacheRect;
 
-    paint.begin(this);
-    paint.setClipRect(nonCacheRect);
+    QRect scaledNonCacheRect = scaledRect(nonCacheRect, dpratio);
+    
+    paint.begin(m_buffer);
+    paint.setClipRect(scaledNonCacheRect);
     setPaintFont(paint);
     if (scrollables.empty()) {
         paint.setPen(getBackground());
         paint.setBrush(getBackground());
-	paint.drawRect(nonCacheRect);
+	paint.drawRect(scaledNonCacheRect);
     }
 	
     paint.setPen(getForeground());
@@ -1897,10 +1911,18 @@
 	
     for (LayerList::iterator i = nonScrollables.begin(); i != nonScrollables.end(); ++i) {
 //        Profiler profiler2("View::paintEvent non-cacheable");
-	(*i)->paint(this, paint, nonCacheRect);
+#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;
+#endif
+	(*i)->paint(&proxy, paint, scaledNonCacheRect);
     }
 	
     paint.end();
+    
+    paint.begin(this);
+    QRect finalPaintRect = e ? e->rect() : rect();
+    paint.drawPixmap(finalPaintRect, *m_buffer, scaledRect(finalPaintRect, dpratio));
+    paint.end();
 
     paint.begin(this);
     setPaintFont(paint);
@@ -1925,7 +1947,7 @@
             showPlayPointer = false;
         }
     }
-
+    
     if (showPlayPointer) {
 
 	paint.begin(this);
@@ -2081,11 +2103,15 @@
 		dx = p1 - 2 - dw;
 	    }
 
-	    paint.drawText(sx, sy, startText);
-	    paint.drawText(ex, ey, endText);
-	    paint.drawText(dx, dy, durationText);
+            PaintAssistant::drawVisibleText(this, paint, sx, sy, startText,
+                                            PaintAssistant::OutlinedText);
+            PaintAssistant::drawVisibleText(this, paint, ex, ey, endText,
+                                            PaintAssistant::OutlinedText);
+            PaintAssistant::drawVisibleText(this, paint, dx, dy, durationText,
+                                            PaintAssistant::OutlinedText);
             if (durationBothEnds) {
-                paint.drawText(sx, dy, durationText);
+                PaintAssistant::drawVisibleText(this, paint, sx, dy, durationText,
+                                                PaintAssistant::OutlinedText);
             }
 	}
     }
@@ -2300,32 +2326,32 @@
     }
     
     if (axs != "") {
-        drawVisibleText(paint, axx, axy, axs, OutlinedText);
+        PaintAssistant::drawVisibleText(this, paint, axx, axy, axs, PaintAssistant::OutlinedText);
         axy += fontHeight;
     }
     
     if (ays != "") {
-        drawVisibleText(paint, axx, axy, ays, OutlinedText);
+        PaintAssistant::drawVisibleText(this, paint, axx, axy, ays, PaintAssistant::OutlinedText);
         axy += fontHeight;
     }
 
     if (bxs != "") {
-        drawVisibleText(paint, bxx, bxy, bxs, OutlinedText);
+        PaintAssistant::drawVisibleText(this, paint, bxx, bxy, bxs, PaintAssistant::OutlinedText);
         bxy += fontHeight;
     }
 
     if (bys != "") {
-        drawVisibleText(paint, bxx, bxy, bys, OutlinedText);
+        PaintAssistant::drawVisibleText(this, paint, bxx, bxy, bys, PaintAssistant::OutlinedText);
         bxy += fontHeight;
     }
 
     if (dxs != "") {
-        drawVisibleText(paint, dxx, dxy, dxs, OutlinedText);
+        PaintAssistant::drawVisibleText(this, paint, dxx, dxy, dxs, PaintAssistant::OutlinedText);
         dxy += fontHeight;
     }
 
     if (dys != "") {
-        drawVisibleText(paint, dxx, dxy, dys, OutlinedText);
+        PaintAssistant::drawVisibleText(this, paint, dxx, dxy, dys, PaintAssistant::OutlinedText);
         dxy += fontHeight;
     }
 
@@ -2412,23 +2438,23 @@
 
 	for (LayerList::iterator i = m_layerStack.begin();
              i != m_layerStack.end(); ++i) {
-		if(!((*i)->isLayerDormant(this))){
-
-		    paint.setRenderHint(QPainter::Antialiasing, false);
-
-		    paint.save();
-	            paint.translate(xorigin + x, 0);
-
-	            cerr << "Centre frame now: " << m_centreFrame << " drawing to " << chunk.x() + x + xorigin << ", " << chunk.width() << endl;
-
-	            (*i)->setSynchronousPainting(true);
-
-		    (*i)->paint(this, paint, chunk);
-
-	            (*i)->setSynchronousPainting(false);
-
-		    paint.restore();
-		}
+            if (!((*i)->isLayerDormant(this))){
+
+                paint.setRenderHint(QPainter::Antialiasing, false);
+
+                paint.save();
+                paint.translate(xorigin + x, 0);
+
+                cerr << "Centre frame now: " << m_centreFrame << " drawing to " << chunk.x() + x + xorigin << ", " << chunk.width() << endl;
+
+                (*i)->setSynchronousPainting(true);
+
+                (*i)->paint(this, paint, chunk);
+
+                (*i)->setSynchronousPainting(false);
+
+                paint.restore();
+            }
 	}
     }
 
@@ -2438,16 +2464,16 @@
 }
 
 QImage *
-View::toNewImage()
+View::renderToNewImage()
 {
     sv_frame_t f0 = getModelsStartFrame();
     sv_frame_t f1 = getModelsEndFrame();
 
-    return toNewImage(f0, f1);
+    return renderPartToNewImage(f0, f1);
 }
 
 QImage *
-View::toNewImage(sv_frame_t f0, sv_frame_t f1)
+View::renderPartToNewImage(sv_frame_t f0, sv_frame_t f1)
 {
     int x0 = int(f0 / getZoomLevel());
     int x1 = int(f1 / getZoomLevel());
@@ -2466,16 +2492,16 @@
 }
 
 QSize
-View::getImageSize()
+View::getRenderedImageSize()
 {
     sv_frame_t f0 = getModelsStartFrame();
     sv_frame_t f1 = getModelsEndFrame();
 
-    return getImageSize(f0, f1);
+    return getRenderedPartImageSize(f0, f1);
 }
     
 QSize
-View::getImageSize(sv_frame_t f0, sv_frame_t f1)
+View::getRenderedPartImageSize(sv_frame_t f0, sv_frame_t f1)
 {
     int x0 = int(f0 / getZoomLevel());
     int x1 = int(f1 / getZoomLevel());
@@ -2483,6 +2509,35 @@
     return QSize(x1 - x0, height());
 }
 
+bool
+View::renderToSvgFile(QString filename)
+{
+    sv_frame_t f0 = getModelsStartFrame();
+    sv_frame_t f1 = getModelsEndFrame();
+
+    return renderPartToSvgFile(filename, f0, f1);
+}
+
+bool
+View::renderPartToSvgFile(QString filename, sv_frame_t f0, sv_frame_t f1)
+{
+    int x0 = int(f0 / getZoomLevel());
+    int x1 = int(f1 / getZoomLevel());
+
+    QSvgGenerator generator;
+    generator.setFileName(filename);
+    generator.setSize(QSize(x1 - x0, height()));
+    generator.setViewBox(QRect(0, 0, x1 - x0, height()));
+    generator.setTitle(tr("Exported image from %1")
+                       .arg(QApplication::applicationName()));
+    
+    QPainter paint;
+    paint.begin(&generator);
+    bool result = render(paint, 0, f0, f1);
+    paint.end();
+    return result;
+}
+
 void
 View::toXml(QTextStream &stream,
             QString indent, QString extraAttributes) const