From 5e61bbe586519c3d9bc636153d32e810da4e59a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Morten=20Johan=20S=C3=B8rvig?= Date: Tue, 20 Nov 2012 11:34:52 +0100 Subject: [PATCH] Basic high-dpi "retina" support for Qt 5. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bring Qt 5 on par with Qt 4, prepare for more comprehensive support later on. Introduce device independent pixels (dips), device pixels, and devicePixelRatio. Add high-dpi support to QPainter, QGLWidget, the cocoa platform plugin, mac and fusion styles. Dips are similar to CSS pixels, Apple points and Android density-independent pixels. Device pixels are pixels in the backing store/physical pixels on screen. devicePixelRatio is the ratio between them, which is 1.0 on standard displays and 2.0 on "retina" displays. New API: QImage::devicePixelRatio() and setDevicePixelRatio() QPixmap::devicePixelRatio() and setDevicePixelRatio() QWindow::devicePixelRatio() QScreen::devicePixelRatio() QGuiApplicaiton::devicePixelRatio() Change-Id: If98c3ca9bfdf0e1bdbcf7574cd5b912c9ff63856 Reviewed-by: Morten Johan Sørvig Reviewed-by: Gunnar Sletta --- src/gui/image/qimage.cpp | 67 +++- src/gui/image/qimage.h | 3 + src/gui/image/qimage_p.h | 7 +- src/gui/image/qimagereader.cpp | 5 + src/gui/image/qpixmap.cpp | 44 +++ src/gui/image/qpixmap.h | 3 + src/gui/image/qpixmap_blitter.cpp | 10 + src/gui/image/qpixmap_blitter_p.h | 3 + src/gui/image/qpixmap_raster.cpp | 21 +- src/gui/image/qpixmap_raster_p.h | 3 + src/gui/image/qplatformpixmap.h | 3 + src/gui/kernel/qguiapplication.cpp | 29 +- src/gui/kernel/qguiapplication.h | 1 + src/gui/kernel/qplatformscreen.cpp | 12 + src/gui/kernel/qplatformscreen.h | 1 + src/gui/kernel/qplatformwindow.cpp | 12 + src/gui/kernel/qplatformwindow.h | 2 + src/gui/kernel/qscreen.cpp | 14 + src/gui/kernel/qscreen.h | 2 + src/gui/kernel/qwindow.cpp | 18 + src/gui/kernel/qwindow.h | 2 + src/gui/painting/qpaintengine_raster.cpp | 29 +- src/gui/painting/qpaintengineex.cpp | 4 +- src/gui/painting/qpainter.cpp | 48 ++- src/gui/painting/qpainter_p.h | 1 + src/opengl/qgl.cpp | 4 +- src/opengl/qgl_qpa.cpp | 5 +- .../platforms/cocoa/qcocoabackingstore.h | 1 + .../platforms/cocoa/qcocoabackingstore.mm | 26 +- .../platforms/cocoa/qcocoaglcontext.mm | 12 + .../platforms/cocoa/qcocoaintegration.h | 1 + .../platforms/cocoa/qcocoaintegration.mm | 12 + src/plugins/platforms/cocoa/qcocoawindow.h | 2 + src/plugins/platforms/cocoa/qcocoawindow.mm | 15 + src/plugins/platforms/cocoa/qnsview.mm | 31 +- src/widgets/itemviews/qitemdelegate.cpp | 10 +- src/widgets/kernel/qwidget.cpp | 29 +- src/widgets/kernel/qwidget_p.h | 1 + src/widgets/styles/qfusionstyle.cpp | 100 ++--- src/widgets/styles/qmacstyle_mac.mm | 32 +- src/widgets/styles/qstyle.cpp | 5 +- src/widgets/styles/qstyle_p.h | 11 +- src/widgets/widgets/qlabel.cpp | 9 +- tests/manual/highdpi/highdpi.pro | 12 + tests/manual/highdpi/highdpi.qrc | 7 + tests/manual/highdpi/main.cpp | 366 ++++++++++++++++++ tests/manual/highdpi/qticon.png | Bin 0 -> 6474 bytes tests/manual/highdpi/qticon@2x.png | Bin 0 -> 17168 bytes tests/manual/highdpi/qticon_large.png | Bin 0 -> 17168 bytes tests/manual/lance/main.cpp | 18 +- 50 files changed, 934 insertions(+), 119 deletions(-) create mode 100644 tests/manual/highdpi/highdpi.pro create mode 100644 tests/manual/highdpi/highdpi.qrc create mode 100644 tests/manual/highdpi/main.cpp create mode 100644 tests/manual/highdpi/qticon.png create mode 100644 tests/manual/highdpi/qticon@2x.png create mode 100644 tests/manual/highdpi/qticon_large.png diff --git a/src/gui/image/qimage.cpp b/src/gui/image/qimage.cpp index 9da360bc26f..c40ca1545a2 100644 --- a/src/gui/image/qimage.cpp +++ b/src/gui/image/qimage.cpp @@ -95,10 +95,12 @@ static QImage rotated270(const QImage &src); QBasicAtomicInt qimage_serial_number = Q_BASIC_ATOMIC_INITIALIZER(1); QImageData::QImageData() - : ref(0), width(0), height(0), depth(0), nbytes(0), data(0), + : ref(0), width(0), height(0), depth(0), nbytes(0), devicePixelRatio(1.0), data(0), format(QImage::Format_ARGB32), bytes_per_line(0), ser_no(qimage_serial_number.fetchAndAddRelaxed(1)), detach_no(0), + ldpmx(qt_defaultDpiX() * 100 / qreal(2.54)), + ldpmy(qt_defaultDpiY() * 100 / qreal(2.54)), dpmx(qt_defaultDpiX() * 100 / qreal(2.54)), dpmy(qt_defaultDpiY() * 100 / qreal(2.54)), offset(0, 0), own_data(true), ro_data(false), has_alpha_clut(false), @@ -1216,6 +1218,7 @@ QImage QImage::copy(const QRect& r) const image.d->dpmx = dotsPerMeterX(); image.d->dpmy = dotsPerMeterY(); + image.d->devicePixelRatio = devicePixelRatio(); image.d->offset = offset(); image.d->has_alpha_clut = d->has_alpha_clut; image.d->text = d->text; @@ -1369,6 +1372,52 @@ QVector QImage::colorTable() const return d ? d->colortable : QVector(); } +/*! + Returns the device pixel ratio for the image. This is the + ratio between image pixels and device-independent pixels. + + Use this function when calculating layout geometry based on + the image size: QSize layoutSize = image.size() / image.devicePixelRatio() + + The default value is 1.0. + + \sa setDevicePixelRatio() +*/ +qreal QImage::devicePixelRatio() const +{ + if (!d) + return 1.0; + return d->devicePixelRatio; +} + +/*! + Sets the the device pixel ratio for the image. This is the + ratio between image pixels and device-independent pixels. + + The default value is 1.0. Setting it to something else has + two effects: + + QPainters that are opened on the image will be scaled. For + example, painting on a 200x200 image if with a ratio of 2.0 + will result in effective (device-independent) painting bounds + of 100x100. + + Code paths in Qt that calculate layout geometry based on the + image size will take the ratio into account: + QSize layoutSize = image.size() / image.devicePixelRatio() + The net effect of this is that the image is displayed as + high-dpi image rather than a large image. + + \sa devicePixelRatio() +*/ +void QImage::setDevicePixelRatio(qreal scaleFactor) +{ + if (!d) + return; + detach(); + d->devicePixelRatio = scaleFactor; +} + /*! \since 4.6 Returns the number of bytes occupied by the image data. @@ -3359,6 +3408,7 @@ QImage QImage::convertToFormat(Format format, Qt::ImageConversionFlags flags) co image.setDotsPerMeterY(dotsPerMeterY()); image.setDotsPerMeterX(dotsPerMeterX()); + image.setDevicePixelRatio(devicePixelRatio()); image.d->text = d->text; @@ -3479,6 +3529,7 @@ QImage QImage::convertToFormat(Format format, const QVector &colorTable, Q QImage image(d->width, d->height, format); QIMAGE_SANITYCHECK_MEMORY(image); + image.setDevicePixelRatio(devicePixelRatio()); image.d->text = d->text; @@ -4932,17 +4983,20 @@ int QImage::metric(PaintDeviceMetric metric) const return d->depth; case PdmDpiX: - return qRound(d->dpmx * 0.0254); + return qRound(d->ldpmx * 0.0254); + break; case PdmDpiY: - return qRound(d->dpmy * 0.0254); + return qRound(d->ldpmy * 0.0254); + break; case PdmPhysicalDpiX: - return qRound(d->dpmx * 0.0254); + return qRound(d->dpmx * 0.0254 * d->devicePixelRatio); + break; case PdmPhysicalDpiY: - return qRound(d->dpmy * 0.0254); - + return qRound(d->dpmy * 0.0254 * d->devicePixelRatio); + break; default: qWarning("QImage::metric(): Unhandled metric type %d", metric); break; @@ -5641,6 +5695,7 @@ QImage QImage::transformed(const QTransform &matrix, Qt::TransformationMode mode dImage.d->dpmx = dotsPerMeterX(); dImage.d->dpmy = dotsPerMeterY(); + dImage.d->devicePixelRatio = devicePixelRatio(); switch (bpp) { // initizialize the data diff --git a/src/gui/image/qimage.h b/src/gui/image/qimage.h index 0356c1cab1c..304d54a3782 100644 --- a/src/gui/image/qimage.h +++ b/src/gui/image/qimage.h @@ -211,6 +211,9 @@ public: QVector colorTable() const; void setColorTable(const QVector colors); + qreal devicePixelRatio() const; + void setDevicePixelRatio(qreal scaleFactor); + void fill(uint pixel); void fill(const QColor &color); void fill(Qt::GlobalColor color); diff --git a/src/gui/image/qimage_p.h b/src/gui/image/qimage_p.h index 74a79a84255..2bb989c03bf 100644 --- a/src/gui/image/qimage_p.h +++ b/src/gui/image/qimage_p.h @@ -74,6 +74,7 @@ struct Q_GUI_EXPORT QImageData { // internal image data int height; int depth; int nbytes; // number of bytes data + qreal devicePixelRatio; QVector colortable; uchar *data; QImage::Format format; @@ -81,8 +82,10 @@ struct Q_GUI_EXPORT QImageData { // internal image data int ser_no; // serial number int detach_no; - qreal dpmx; // dots per meter X (or 0) - qreal dpmy; // dots per meter Y (or 0) + qreal ldpmx; // logical dots per meter X (or 0) + qreal ldpmy; // logical dots per meter Y (or 0) + qreal dpmx; // device dots per meter X (or 0) + qreal dpmy; // device dots per meter Y (or 0) QPoint offset; // offset in pixels uint own_data : 1; diff --git a/src/gui/image/qimagereader.cpp b/src/gui/image/qimagereader.cpp index 3b0fe33d218..a27a7710f6c 100644 --- a/src/gui/image/qimagereader.cpp +++ b/src/gui/image/qimagereader.cpp @@ -1234,6 +1234,11 @@ bool QImageReader::read(QImage *image) } } + // successful read; check for "@2x" file name suffix and set device pixel ratio. + if (QFileInfo(fileName()).baseName().endsWith("@2x")) { + image->setDevicePixelRatio(2.0); + } + return true; } diff --git a/src/gui/image/qpixmap.cpp b/src/gui/image/qpixmap.cpp index 113369fd753..8782119b7a5 100644 --- a/src/gui/image/qpixmap.cpp +++ b/src/gui/image/qpixmap.cpp @@ -638,6 +638,50 @@ void QPixmap::setMask(const QBitmap &mask) data->fromImage(image, Qt::AutoColor); } +/*! + Returns the device pixel ratio for the pixmap. This is the + ratio between pixmap pixels and device-independent pixels. + + Use this function when calculating layout geometry based on + the pixmap size: QSize layoutSize = image.size() / image.devicePixelRatio() + + The default value is 1.0. + + \sa setDevicePixelRatio() +*/ +qreal QPixmap::devicePixelRatio() const +{ + if (!data) + return qreal(1.0); + return data->devicePixelRatio(); +} + +/*! + Sets the the device pixel ratio for the pixmap. This is the + ratio between image pixels and device-independent pixels. + + The default value is 1.0. Setting it to something else has + two effects: + + QPainters that are opened on the pixmap will be scaled. For + example, painting on a 200x200 image if with a ratio of 2.0 + will result in effective (device-independent) painting bounds + of 100x100. + + Code paths in Qt that calculate layout geometry based on the + pixmap size will take the ratio into account: + QSize layoutSize = pixmap.size() / pixmap.devicePixelRatio() + The net effect of this is that the pixmap is displayed as + high-dpi pixmap rather than a large pixmap. + + \sa devicePixelRatio() +*/ +void QPixmap::setDevicePixelRatio(qreal scaleFactor) +{ + detach(); + data->setDevicePixelRatio(scaleFactor); +} + #ifndef QT_NO_IMAGE_HEURISTIC_MASK /*! Creates and returns a heuristic mask for this pixmap. diff --git a/src/gui/image/qpixmap.h b/src/gui/image/qpixmap.h index 5f5c28def23..af141cd3259 100644 --- a/src/gui/image/qpixmap.h +++ b/src/gui/image/qpixmap.h @@ -102,6 +102,9 @@ public: QBitmap mask() const; void setMask(const QBitmap &); + qreal devicePixelRatio() const; + void setDevicePixelRatio(qreal scaleFactor); + bool hasAlpha() const; bool hasAlphaChannel() const; diff --git a/src/gui/image/qpixmap_blitter.cpp b/src/gui/image/qpixmap_blitter.cpp index 2bc3a675095..2a4576e8a8a 100644 --- a/src/gui/image/qpixmap_blitter.cpp +++ b/src/gui/image/qpixmap_blitter.cpp @@ -195,6 +195,16 @@ void QBlittablePlatformPixmap::fromImage(const QImage &image, } } +qreal QBlittablePlatformPixmap::devicePixelRatio() const +{ + return m_devicePixelRatio; +} + +void QBlittablePlatformPixmap::setDevicePixelRatio(qreal scaleFactor) +{ + m_devicePixelRatio = scaleFactor; +} + QPaintEngine *QBlittablePlatformPixmap::paintEngine() const { if (!m_engine) { diff --git a/src/gui/image/qpixmap_blitter_p.h b/src/gui/image/qpixmap_blitter_p.h index c4e0e0a4cc3..a0e5f55f694 100644 --- a/src/gui/image/qpixmap_blitter_p.h +++ b/src/gui/image/qpixmap_blitter_p.h @@ -66,6 +66,8 @@ public: QImage toImage() const; bool hasAlphaChannel() const; void fromImage(const QImage &image, Qt::ImageConversionFlags flags); + qreal devicePixelRatio() const; + void setDevicePixelRatio(qreal scaleFactor); QPaintEngine *paintEngine() const; @@ -89,6 +91,7 @@ protected: QScopedPointer m_engine; QScopedPointer m_blittable; bool m_alpha; + qreal m_devicePixelRatio; #ifdef QT_BLITTER_RASTEROVERLAY QImage *m_rasterOverlay; diff --git a/src/gui/image/qpixmap_raster.cpp b/src/gui/image/qpixmap_raster.cpp index d30c6eaae71..5c188f0362d 100644 --- a/src/gui/image/qpixmap_raster.cpp +++ b/src/gui/image/qpixmap_raster.cpp @@ -272,12 +272,14 @@ int QRasterPlatformPixmap::metric(QPaintDevice::PaintDeviceMetric metric) const return d->colortable.size(); case QPaintDevice::PdmDepth: return this->d; - case QPaintDevice::PdmDpiX: // fall-through - case QPaintDevice::PdmPhysicalDpiX: + case QPaintDevice::PdmDpiX: + return qt_defaultDpiX(); + case QPaintDevice::PdmPhysicalDpiX: + return qt_defaultDpiX() * image.devicePixelRatio(); + case QPaintDevice::PdmDpiY: return qt_defaultDpiX(); - case QPaintDevice::PdmDpiY: // fall-through case QPaintDevice::PdmPhysicalDpiY: - return qt_defaultDpiY(); + return qt_defaultDpiY() * image.devicePixelRatio(); default: qWarning("QRasterPlatformPixmap::metric(): Unhandled metric type %d", metric); break; @@ -350,6 +352,7 @@ void QRasterPlatformPixmap::createPixmapForImage(QImage &sourceImage, Qt::ImageC } is_null = (w <= 0 || h <= 0); + image.d->devicePixelRatio = sourceImage.devicePixelRatio(); setSerialNumber(image.cacheKey() >> 32); } @@ -358,4 +361,14 @@ QImage* QRasterPlatformPixmap::buffer() return ℑ } +qreal QRasterPlatformPixmap::devicePixelRatio() const +{ + return image.devicePixelRatio(); +} + +void QRasterPlatformPixmap::setDevicePixelRatio(qreal scaleFactor) +{ + image.setDevicePixelRatio(scaleFactor); +} + QT_END_NAMESPACE diff --git a/src/gui/image/qpixmap_raster_p.h b/src/gui/image/qpixmap_raster_p.h index 7bc007f67fe..e560577e17c 100644 --- a/src/gui/image/qpixmap_raster_p.h +++ b/src/gui/image/qpixmap_raster_p.h @@ -79,6 +79,9 @@ public: QImage toImage(const QRect &rect) const; QPaintEngine* paintEngine() const; QImage* buffer(); + qreal devicePixelRatio() const; + void setDevicePixelRatio(qreal scaleFactor); + protected: int metric(QPaintDevice::PaintDeviceMetric metric) const; diff --git a/src/gui/image/qplatformpixmap.h b/src/gui/image/qplatformpixmap.h index 9d28721f99b..af3abecaf8f 100644 --- a/src/gui/image/qplatformpixmap.h +++ b/src/gui/image/qplatformpixmap.h @@ -108,6 +108,9 @@ public: inline PixelType pixelType() const { return type; } inline ClassId classId() const { return static_cast(id); } + virtual qreal devicePixelRatio() const = 0; + virtual void setDevicePixelRatio(qreal scaleFactor) = 0; + virtual QImage* buffer(); inline int width() const { return w; } diff --git a/src/gui/kernel/qguiapplication.cpp b/src/gui/kernel/qguiapplication.cpp index 80e13227eec..fb14490a242 100644 --- a/src/gui/kernel/qguiapplication.cpp +++ b/src/gui/kernel/qguiapplication.cpp @@ -703,7 +703,34 @@ QList QGuiApplication::screens() /*! - Returns the top level window at the given position \a pos, if any. + Returns the highest screen device pixel ratio found on + the system. This is the ratio between physical pixels and + device-independent pixels. + + Use this function only when you don't know which window you are targeting. + If you do know the target window use QWindow::devicePixelRatio() instead. + + \sa QWindow::devicePixelRatio(); + \sa QGuiApplicaiton::devicePixelRatio(); +*/ +qreal QGuiApplication::devicePixelRatio() const +{ + // Cache topDevicePixelRatio, iterate through the screen list once only. + static qreal topDevicePixelRatio = 0.0; + if (!qFuzzyIsNull(topDevicePixelRatio)) { + return topDevicePixelRatio; + } + + topDevicePixelRatio = 1.0; // make sure we never return 0. + foreach (QScreen *screen, QGuiApplicationPrivate::screen_list) { + topDevicePixelRatio = qMax(topDevicePixelRatio, screen->devicePixelRatio()); + } + + return topDevicePixelRatio; +} + +/*! + Returns the top level window at the given position, if any. */ QWindow *QGuiApplication::topLevelAt(const QPoint &pos) { diff --git a/src/gui/kernel/qguiapplication.h b/src/gui/kernel/qguiapplication.h index 27ea86ec6ea..7c0dbbdcde7 100644 --- a/src/gui/kernel/qguiapplication.h +++ b/src/gui/kernel/qguiapplication.h @@ -104,6 +104,7 @@ public: static QScreen *primaryScreen(); static QList screens(); + qreal devicePixelRatio() const; #ifndef QT_NO_CURSOR static QCursor *overrideCursor(); diff --git a/src/gui/kernel/qplatformscreen.cpp b/src/gui/kernel/qplatformscreen.cpp index 211a9650dc8..ab2fdfa4091 100644 --- a/src/gui/kernel/qplatformscreen.cpp +++ b/src/gui/kernel/qplatformscreen.cpp @@ -160,6 +160,18 @@ QDpi QPlatformScreen::logicalDpi() const 25.4 * s.height() / ps.height()); } +/*! + Reimplement this function in subclass to return the device pixel + ratio for the screen. This is the ratio between physical pixels + and device-independent pixels. + + \sa QPlatformWindow::devicePixelRatio(); +*/ +qreal QPlatformScreen::devicePixelRatio() const +{ + return 1.0; +} + /*! Reimplement this function in subclass to return the vertical refresh rate of the screen, in Hz. diff --git a/src/gui/kernel/qplatformscreen.h b/src/gui/kernel/qplatformscreen.h index 493a1cedd00..91365b1cf07 100644 --- a/src/gui/kernel/qplatformscreen.h +++ b/src/gui/kernel/qplatformscreen.h @@ -98,6 +98,7 @@ public: virtual QSizeF physicalSize() const; virtual QDpi logicalDpi() const; + virtual qreal devicePixelRatio() const; virtual qreal refreshRate() const; diff --git a/src/gui/kernel/qplatformwindow.cpp b/src/gui/kernel/qplatformwindow.cpp index 3bf06c6ab1a..25b863c9a34 100644 --- a/src/gui/kernel/qplatformwindow.cpp +++ b/src/gui/kernel/qplatformwindow.cpp @@ -355,6 +355,18 @@ Qt::ScreenOrientation QPlatformWindow::requestWindowOrientation(Qt::ScreenOrient return Qt::PrimaryOrientation; } +/*! + Reimplement this function in subclass to return the device pixel ratio + for the window. This is the ratio between physical pixels + and device-independent pixels. + + \sa QPlatformWindow::devicePixelRatio(); +*/ +qreal QPlatformWindow::devicePixelRatio() const +{ + return 1.0; +} + bool QPlatformWindow::setKeyboardGrabEnabled(bool grab) { Q_UNUSED(grab); diff --git a/src/gui/kernel/qplatformwindow.h b/src/gui/kernel/qplatformwindow.h index 12650d6073a..607c8e4035f 100644 --- a/src/gui/kernel/qplatformwindow.h +++ b/src/gui/kernel/qplatformwindow.h @@ -117,6 +117,8 @@ public: virtual void handleContentOrientationChange(Qt::ScreenOrientation orientation); virtual Qt::ScreenOrientation requestWindowOrientation(Qt::ScreenOrientation orientation); + virtual qreal devicePixelRatio() const; + virtual bool setKeyboardGrabEnabled(bool grab); virtual bool setMouseGrabEnabled(bool grab); diff --git a/src/gui/kernel/qscreen.cpp b/src/gui/kernel/qscreen.cpp index 0c30de498c2..f5467ab742a 100644 --- a/src/gui/kernel/qscreen.cpp +++ b/src/gui/kernel/qscreen.cpp @@ -214,6 +214,20 @@ qreal QScreen::logicalDotsPerInch() const return (dpi.first + dpi.second) * qreal(0.5); } +/* + Returns the ratio between physical pixels and device-independent pixels for the screen. + + Common values are 1.0 on normal displays and 2.0 on Apple retina displays. + + \sa QWindow::devicePixelRatio(); + \sa QGuiApplicaiton::devicePixelRatio(); +*/ +qreal QScreen::devicePixelRatio() const +{ + Q_D(const QScreen); + return d->platformScreen->devicePixelRatio(); +} + /*! \property QScreen::physicalSize \brief the screen's physical size (in millimeters) diff --git a/src/gui/kernel/qscreen.h b/src/gui/kernel/qscreen.h index 48eaad94f76..fbbd8867550 100644 --- a/src/gui/kernel/qscreen.h +++ b/src/gui/kernel/qscreen.h @@ -109,6 +109,8 @@ public: qreal logicalDotsPerInchY() const; qreal logicalDotsPerInch() const; + qreal devicePixelRatio() const; + QSize availableSize() const; QRect availableGeometry() const; diff --git a/src/gui/kernel/qwindow.cpp b/src/gui/kernel/qwindow.cpp index c24609e8863..aaf2b25ad42 100644 --- a/src/gui/kernel/qwindow.cpp +++ b/src/gui/kernel/qwindow.cpp @@ -805,6 +805,24 @@ Qt::ScreenOrientation QWindow::orientation() const return d->windowOrientation; } +/*! + Returns the ratio between physical pixels and device-independent pixels + for the window. This value is dependent on the screen the window is on, + and may change when the window is moved. + + Common values are 1.0 on normal displays and 2.0 on Apple "retina" displays. + + \sa QWindow::devicePixelRatio(); + \sa QGuiApplicaiton::devicePixelRatio(); +*/ +qreal QWindow::devicePixelRatio() const +{ + Q_D(const QWindow); + if (!d->platformWindow) + return 1.0; + return d->platformWindow->devicePixelRatio(); +} + /*! \brief set the screen-occupation state of the window diff --git a/src/gui/kernel/qwindow.h b/src/gui/kernel/qwindow.h index e6c9a3736fc..229275d7c73 100644 --- a/src/gui/kernel/qwindow.h +++ b/src/gui/kernel/qwindow.h @@ -184,6 +184,8 @@ public: void reportContentOrientationChange(Qt::ScreenOrientation orientation); Qt::ScreenOrientation contentOrientation() const; + qreal devicePixelRatio() const; + bool requestOrientation(Qt::ScreenOrientation orientation); Qt::ScreenOrientation orientation() const; diff --git a/src/gui/painting/qpaintengine_raster.cpp b/src/gui/painting/qpaintengine_raster.cpp index 2841a583d53..46648fe2977 100644 --- a/src/gui/painting/qpaintengine_raster.cpp +++ b/src/gui/painting/qpaintengine_raster.cpp @@ -1072,9 +1072,11 @@ void QRasterPaintEnginePrivate::systemStateChanged() exDeviceRect = deviceRect; Q_Q(QRasterPaintEngine); - q->state()->strokeFlags |= QPaintEngine::DirtyClipRegion; - q->state()->fillFlags |= QPaintEngine::DirtyClipRegion; - q->state()->pixmapFlags |= QPaintEngine::DirtyClipRegion; + if (q->state()) { + q->state()->strokeFlags |= QPaintEngine::DirtyClipRegion; + q->state()->fillFlags |= QPaintEngine::DirtyClipRegion; + q->state()->pixmapFlags |= QPaintEngine::DirtyClipRegion; + } } void QRasterPaintEnginePrivate::updateMatrixData(QSpanData *spanData, const QBrush &b, const QTransform &m) @@ -2143,9 +2145,10 @@ void QRasterPaintEngine::drawImage(const QPointF &p, const QImage &img) Q_D(QRasterPaintEngine); QRasterPaintEngineState *s = state(); + qreal scale = img.devicePixelRatio(); - if (s->matrix.type() > QTransform::TxTranslate) { - drawImage(QRectF(p.x(), p.y(), img.width(), img.height()), + if (scale > 1.0 || s->matrix.type() > QTransform::TxTranslate) { + drawImage(QRectF(p.x(), p.y(), img.width() / scale, img.height() / scale), img, QRectF(0, 0, img.width(), img.height())); } else { @@ -2349,6 +2352,22 @@ void QRasterPaintEngine::drawImage(const QRectF &r, const QImage &img, const QRe return; } } else { + // Test for optimized high-dpi case: 2x source on 2x target. (Could be generalized to nX.) + bool sourceRect2x = r.width() * 2 == sr.width() && r.height() * 2 == sr.height(); + bool scale2x = (s->matrix.m11() == qreal(2)) && (s->matrix.m22() == qreal(2)); + if (s->matrix.type() == QTransform::TxScale && sourceRect2x && scale2x) { + SrcOverBlendFunc func = qBlendFunctions[d->rasterBuffer->format][img.format()]; + if (func) { + QPointF pt(r.x() * 2 + s->matrix.dx(), r.y() * 2 + s->matrix.dy()); + if (!clip) { + d->drawImage(pt, img, func, d->deviceRect, s->intOpacity, sr.toRect()); + return; + } else if (clip->hasRectClip) { + d->drawImage(pt, img, func, clip->clipRect, s->intOpacity, sr.toRect()); + return; + } + } + } SrcOverScaleFunc func = qScaleFunctions[d->rasterBuffer->format][img.format()]; if (func && (!clip || clip->hasRectClip)) { func(d->rasterBuffer->buffer(), d->rasterBuffer->bytesPerLine(), diff --git a/src/gui/painting/qpaintengineex.cpp b/src/gui/painting/qpaintengineex.cpp index 113cbd8a8ec..2c41ab9ff26 100644 --- a/src/gui/painting/qpaintengineex.cpp +++ b/src/gui/painting/qpaintengineex.cpp @@ -939,12 +939,12 @@ void QPaintEngineEx::drawPolygon(const QPoint *points, int pointCount, PolygonDr void QPaintEngineEx::drawPixmap(const QPointF &pos, const QPixmap &pm) { - drawPixmap(QRectF(pos, pm.size()), pm, pm.rect()); + drawPixmap(QRectF(pos, pm.size() / pm.devicePixelRatio()), pm, pm.rect()); } void QPaintEngineEx::drawImage(const QPointF &pos, const QImage &image) { - drawImage(QRectF(pos, image.size()), image, image.rect()); + drawImage(QRectF(pos, image.size() / image.devicePixelRatio()), image, image.rect()); } void QPaintEngineEx::drawTiledPixmap(const QRectF &r, const QPixmap &pixmap, const QPointF &s) diff --git a/src/gui/painting/qpainter.cpp b/src/gui/painting/qpainter.cpp index 0cfe953e434..8ec9c1648f0 100644 --- a/src/gui/painting/qpainter.cpp +++ b/src/gui/painting/qpainter.cpp @@ -225,6 +225,18 @@ QTransform QPainterPrivate::viewTransform() const return QTransform(); } +QTransform QPainterPrivate::hidpiScaleTransform() const +{ +#ifdef Q_OS_MAC + // Limited feature introduction for Qt 5.0.0, remove ifdef in a later release. + if (device->physicalDpiX() == 0 || device->logicalDpiX() == 0) + return QTransform(); + const qreal deviceScale = (device->physicalDpiX() / device->logicalDpiX()); + if (deviceScale > 1.0) + return QTransform::fromScale(deviceScale, deviceScale); +#endif + return QTransform(); +} /* \internal @@ -641,6 +653,8 @@ void QPainterPrivate::updateMatrix() else state->dirtyFlags |= QPaintEngine::DirtyTransform; + state->matrix *= hidpiScaleTransform(); + // printf("VxF=%d, WxF=%d\n", state->VxF, state->WxF); // qDebug() << " --- using matrix" << state->matrix << redirection_offset; } @@ -1827,7 +1841,14 @@ bool QPainter::begin(QPaintDevice *pd) Q_ASSERT(d->engine->isActive()); - if (!d->state->redirectionMatrix.isIdentity()) +#ifdef Q_OS_MAC + // Limited feature introduction for Qt 5.0.0, remove ifdef in a later release. + const bool isHighDpi = (d->device->physicalDpiX() == 0 || d->device->logicalDpiX() == 0) ? + false : (d->device->physicalDpiX() / d->device->logicalDpiX() > 1); +#else + const bool isHighDpi = false; +#endif + if (!d->state->redirectionMatrix.isIdentity() || isHighDpi) d->updateMatrix(); Q_ASSERT(d->engine->isActive()); @@ -5092,7 +5113,8 @@ void QPainter::drawPixmap(const QPointF &p, const QPixmap &pm) x += d->state->matrix.dx(); y += d->state->matrix.dy(); } - d->engine->drawPixmap(QRectF(x, y, w, h), pm, QRectF(0, 0, w, h)); + int scale = pm.devicePixelRatio(); + d->engine->drawPixmap(QRectF(x, y, w / scale, h / scale), pm, QRectF(0, 0, w, h)); } } @@ -5122,6 +5144,11 @@ void QPainter::drawPixmap(const QRectF &r, const QPixmap &pm, const QRectF &sr) qreal sw = sr.width(); qreal sh = sr.height(); + // Get pixmap scale. Use it when calculating the target + // rect size from pixmap size. For example, a 2X 64x64 pixel + // pixmap should result in a 32x32 point target rect. + const qreal pmscale = pm.devicePixelRatio(); + // Sanity-check clipping if (sw <= 0) sw = pm.width() - sx; @@ -5130,9 +5157,9 @@ void QPainter::drawPixmap(const QRectF &r, const QPixmap &pm, const QRectF &sr) sh = pm.height() - sy; if (w < 0) - w = sw; + w = sw / pmscale; if (h < 0) - h = sh; + h = sh / pmscale; if (sx < 0) { qreal w_ratio = sx * w/sw; @@ -5345,6 +5372,7 @@ void QPainter::drawImage(const QPointF &p, const QImage &image) int w = image.width(); int h = image.height(); + qreal scale = image.devicePixelRatio(); d->updateState(d->state); @@ -5368,8 +5396,7 @@ void QPainter::drawImage(const QPointF &p, const QImage &image) setBrush(brush); setPen(Qt::NoPen); setBrushOrigin(QPointF(0, 0)); - - drawRect(image.rect()); + drawRect(QRect(QPoint(0, 0), image.size() / scale)); restore(); return; } @@ -5380,7 +5407,7 @@ void QPainter::drawImage(const QPointF &p, const QImage &image) y += d->state->matrix.dy(); } - d->engine->drawImage(QRectF(x, y, w, h), image, QRectF(0, 0, w, h), Qt::AutoColor); + d->engine->drawImage(QRectF(x, y, w / scale, h / scale), image, QRectF(0, 0, w, h), Qt::AutoColor); } void QPainter::drawImage(const QRectF &targetRect, const QImage &image, const QRectF &sourceRect, @@ -5399,6 +5426,7 @@ void QPainter::drawImage(const QRectF &targetRect, const QImage &image, const QR qreal sy = sourceRect.y(); qreal sw = sourceRect.width(); qreal sh = sourceRect.height(); + qreal imageScale = image.devicePixelRatio(); // Sanity-check clipping if (sw <= 0) @@ -5408,9 +5436,9 @@ void QPainter::drawImage(const QRectF &targetRect, const QImage &image, const QR sh = image.height() - sy; if (w < 0) - w = sw; + w = sw / imageScale; if (h < 0) - h = sh; + h = sh / imageScale; if (sx < 0) { qreal w_ratio = sx * w/sw; @@ -8235,7 +8263,7 @@ QTransform QPainter::combinedTransform() const qWarning("QPainter::combinedTransform: Painter not active"); return QTransform(); } - return d->state->worldMatrix * d->viewTransform(); + return d->state->worldMatrix * d->viewTransform() * d->hidpiScaleTransform(); } /*! diff --git a/src/gui/painting/qpainter_p.h b/src/gui/painting/qpainter_p.h index 0e46cee4b5d..fd5d5601418 100644 --- a/src/gui/painting/qpainter_p.h +++ b/src/gui/painting/qpainter_p.h @@ -249,6 +249,7 @@ public: } QTransform viewTransform() const; + QTransform hidpiScaleTransform() const; static bool attachPainterPrivate(QPainter *q, QPaintDevice *pdev); void detachPainterPrivate(QPainter *q); diff --git a/src/opengl/qgl.cpp b/src/opengl/qgl.cpp index 8c98a0ea3a2..69f4871c6b0 100644 --- a/src/opengl/qgl.cpp +++ b/src/opengl/qgl.cpp @@ -4100,7 +4100,9 @@ void QGLWidget::glDraw() #endif if (!d->glcx->initialized()) { glInit(); - resizeGL(d->glcx->device()->width(), d->glcx->device()->height()); // New context needs this "resize" + const qreal scaleFactor = (window() && window()->windowHandle()) ? + window()->windowHandle()->devicePixelRatio() : 1.0; + resizeGL(d->glcx->device()->width() * scaleFactor, d->glcx->device()->height() * scaleFactor); // New context needs this "resize" } paintGL(); if (doubleBuffer()) { diff --git a/src/opengl/qgl_qpa.cpp b/src/opengl/qgl_qpa.cpp index ba07f6121cb..0e8b8abb4f8 100644 --- a/src/opengl/qgl_qpa.cpp +++ b/src/opengl/qgl_qpa.cpp @@ -370,7 +370,10 @@ void QGLWidget::resizeEvent(QResizeEvent *e) makeCurrent(); if (!d->glcx->initialized()) glInit(); - resizeGL(width(), height()); + const qreal scaleFactor = (window() && window()->windowHandle()) ? + window()->windowHandle()->devicePixelRatio() : 1.0; + + resizeGL(width() * scaleFactor, height() * scaleFactor); } diff --git a/src/plugins/platforms/cocoa/qcocoabackingstore.h b/src/plugins/platforms/cocoa/qcocoabackingstore.h index 0e1998170a3..192ef00649b 100644 --- a/src/plugins/platforms/cocoa/qcocoabackingstore.h +++ b/src/plugins/platforms/cocoa/qcocoabackingstore.h @@ -62,6 +62,7 @@ public: void resize (const QSize &size, const QRegion &); bool scroll(const QRegion &area, int dx, int dy); CGImageRef getBackingStoreCGImage(); + qreal getBackingStoreDevicePixelRatio(); private: QImage m_qImage; diff --git a/src/plugins/platforms/cocoa/qcocoabackingstore.mm b/src/plugins/platforms/cocoa/qcocoabackingstore.mm index 7bd7e4ce384..ec3168ce993 100644 --- a/src/plugins/platforms/cocoa/qcocoabackingstore.mm +++ b/src/plugins/platforms/cocoa/qcocoabackingstore.mm @@ -59,10 +59,22 @@ QCocoaBackingStore::~QCocoaBackingStore() QPaintDevice *QCocoaBackingStore::paintDevice() { - if (m_qImage.size() != m_requestedSize) { + if (m_qImage.size() / m_qImage.devicePixelRatio() != m_requestedSize) { CGImageRelease(m_cgImage); m_cgImage = 0; - m_qImage = QImage(m_requestedSize, QImage::Format_ARGB32_Premultiplied); + + int scaleFactor = 1; +#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_7 + if (QSysInfo::MacintoshVersion >= QSysInfo::MV_10_7) { + QCocoaWindow *cocoaWindow = static_cast(window()->handle()); + if (cocoaWindow && cocoaWindow->m_contentView) { + scaleFactor = int([[cocoaWindow->m_contentView window] backingScaleFactor]); + } + } +#endif + + m_qImage = QImage(m_requestedSize * scaleFactor, QImage::Format_ARGB32_Premultiplied); + m_qImage.setDevicePixelRatio(scaleFactor); } return &m_qImage; } @@ -90,10 +102,11 @@ void QCocoaBackingStore::resize(const QSize &size, const QRegion &) bool QCocoaBackingStore::scroll(const QRegion &area, int dx, int dy) { extern void qt_scrollRectInImage(QImage &img, const QRect &rect, const QPoint &offset); - QPoint qpoint(dx, dy); + const qreal devicePixelRatio = m_qImage.devicePixelRatio(); + QPoint qpoint(dx * devicePixelRatio, dy * devicePixelRatio); const QVector qrects = area.rects(); for (int i = 0; i < qrects.count(); ++i) { - const QRect &qrect = qrects.at(i); + const QRect &qrect = QRect(qrects.at(i).topLeft() * devicePixelRatio, qrects.at(i).size() * devicePixelRatio); qt_scrollRectInImage(m_qImage, qrect, qpoint); } return true; @@ -110,4 +123,9 @@ CGImageRef QCocoaBackingStore::getBackingStoreCGImage() return m_cgImage; } +qreal QCocoaBackingStore::getBackingStoreDevicePixelRatio() +{ + return m_qImage.devicePixelRatio(); +} + QT_END_NAMESPACE diff --git a/src/plugins/platforms/cocoa/qcocoaglcontext.mm b/src/plugins/platforms/cocoa/qcocoaglcontext.mm index d9bb9c60a92..99956a0b60f 100644 --- a/src/plugins/platforms/cocoa/qcocoaglcontext.mm +++ b/src/plugins/platforms/cocoa/qcocoaglcontext.mm @@ -114,6 +114,18 @@ void QCocoaGLContext::setActiveWindow(QWindow *window) cocoaWindow->setCurrentContext(this); [(QNSView *) cocoaWindow->contentView() setQCocoaGLContext:this]; + + // Enable high-dpi OpenGL for retina displays. Enabling has the side + // effect that Cooca will start calling glViewport(0, 0, width, height), + // overriding any glViewport calls in application code. This is usually not a + // problem, except if the applcation wants to have a "custom" viewport. + // (like the hellogl example) +#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_7 + if (QSysInfo::MacintoshVersion >= QSysInfo::MV_10_7) { + if (cocoaWindow->devicePixelRatio() > 1) + [cocoaWindow->contentView() setWantsBestResolutionOpenGLSurface:YES]; + } +#endif } void QCocoaGLContext::doneCurrent() diff --git a/src/plugins/platforms/cocoa/qcocoaintegration.h b/src/plugins/platforms/cocoa/qcocoaintegration.h index dae98725668..1bb46ea3ea6 100644 --- a/src/plugins/platforms/cocoa/qcocoaintegration.h +++ b/src/plugins/platforms/cocoa/qcocoaintegration.h @@ -68,6 +68,7 @@ public: QRect availableGeometry() const { return m_availableGeometry; } int depth() const { return m_depth; } QImage::Format format() const { return m_format; } + qreal devicePixelRatio() const; QSizeF physicalSize() const { return m_physicalSize; } QDpi logicalDpi() const { return m_logicalDpi; } qreal refreshRate() const { return m_refreshRate; } diff --git a/src/plugins/platforms/cocoa/qcocoaintegration.mm b/src/plugins/platforms/cocoa/qcocoaintegration.mm index 3767fa014d0..393c471c257 100644 --- a/src/plugins/platforms/cocoa/qcocoaintegration.mm +++ b/src/plugins/platforms/cocoa/qcocoaintegration.mm @@ -115,6 +115,18 @@ void QCocoaScreen::updateGeometry() QWindowSystemInterface::handleScreenAvailableGeometryChange(screen(), availableGeometry()); } +qreal QCocoaScreen::devicePixelRatio() const +{ +#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_7 + if (QSysInfo::MacintoshVersion >= QSysInfo::MV_10_7) { + return qreal([m_screen backingScaleFactor]); + } else +#endif + { + return 1.0; + } +} + extern CGContextRef qt_mac_cg_context(const QPaintDevice *pdev); QPixmap QCocoaScreen::grabWindow(WId window, int x, int y, int width, int height) const diff --git a/src/plugins/platforms/cocoa/qcocoawindow.h b/src/plugins/platforms/cocoa/qcocoawindow.h index a9ea135b3e4..228644c3513 100644 --- a/src/plugins/platforms/cocoa/qcocoawindow.h +++ b/src/plugins/platforms/cocoa/qcocoawindow.h @@ -140,6 +140,8 @@ public: void setMenubar(QCocoaMenuBar *mb); QCocoaMenuBar *menubar() const; + + qreal devicePixelRatio() const; protected: // NSWindow handling. The QCocoaWindow/QNSView can either be displayed // in an existing NSWindow or in one created by Qt. diff --git a/src/plugins/platforms/cocoa/qcocoawindow.mm b/src/plugins/platforms/cocoa/qcocoawindow.mm index 77073d9bc66..c3b21399988 100644 --- a/src/plugins/platforms/cocoa/qcocoawindow.mm +++ b/src/plugins/platforms/cocoa/qcocoawindow.mm @@ -39,6 +39,7 @@ ** ****************************************************************************/ #include "qcocoawindow.h" +#include "qcocoaintegration.h" #include "qnswindowdelegate.h" #include "qcocoaautoreleasepool.h" #include "qcocoaeventdispatcher.h" @@ -820,6 +821,20 @@ QCocoaMenuBar *QCocoaWindow::menubar() const return m_menubar; } +qreal QCocoaWindow::devicePixelRatio() const +{ + if (!m_nsWindow) + return 1.0; +#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_7 + if (QSysInfo::MacintoshVersion >= QSysInfo::MV_10_7) { + return qreal([m_nsWindow backingScaleFactor]); + } else +#endif + { + return 1.0; + } +} + QMargins QCocoaWindow::frameMargins() const { NSRect frameW = [m_nsWindow frame]; diff --git a/src/plugins/platforms/cocoa/qnsview.mm b/src/plugins/platforms/cocoa/qnsview.mm index d2a4685872c..b608989e43a 100644 --- a/src/plugins/platforms/cocoa/qnsview.mm +++ b/src/plugins/platforms/cocoa/qnsview.mm @@ -244,7 +244,7 @@ static QTouchDevice *touchDevice = 0; - (void) flushBackingStore:(QCocoaBackingStore *)backingStore region:(const QRegion &)region offset:(QPoint)offset { m_backingStore = backingStore; - m_backingStoreOffset = offset; + m_backingStoreOffset = offset * m_backingStore->getBackingStoreDevicePixelRatio(); QRect br = region.boundingRect(); [self setNeedsDisplayInRect:NSMakeRect(br.x(), br.y(), br.width(), br.height())]; } @@ -275,33 +275,44 @@ static QTouchDevice *touchDevice = 0; if (!m_backingStore) return; - CGRect dirtyCGRect = NSRectToCGRect(dirtyRect); + // Calculate source and target rects. The target rect is the dirtyRect: + CGRect dirtyWindowRect = NSRectToCGRect(dirtyRect); + + // The backing store source rect will be larger on retina displays. + // Scale dirtyRect by the device pixel ratio: + const qreal devicePixelRatio = m_backingStore->getBackingStoreDevicePixelRatio(); + CGRect dirtyBackingRect = CGRectMake(dirtyRect.origin.x * devicePixelRatio, + dirtyRect.origin.y * devicePixelRatio, + dirtyRect.size.width * devicePixelRatio, + dirtyRect.size.height * devicePixelRatio); + NSGraphicsContext *nsGraphicsContext = [NSGraphicsContext currentContext]; CGContextRef cgContext = (CGContextRef) [nsGraphicsContext graphicsPort]; // Translate coordiate system from CoreGraphics (bottom-left) to NSView (top-left): CGContextSaveGState(cgContext); - int dy = dirtyCGRect.origin.y + CGRectGetMaxY(dirtyCGRect); + int dy = dirtyWindowRect.origin.y + CGRectGetMaxY(dirtyWindowRect); + CGContextTranslateCTM(cgContext, 0, dy); CGContextScaleCTM(cgContext, 1, -1); // If a mask is set, modify the sub image accordingly: CGImageRef subMask = 0; if (m_maskImage) { - subMask = CGImageCreateWithImageInRect(m_maskImage, dirtyCGRect); - CGContextClipToMask(cgContext, dirtyCGRect, subMask); + subMask = CGImageCreateWithImageInRect(m_maskImage, dirtyWindowRect); + CGContextClipToMask(cgContext, dirtyWindowRect, subMask); } // Clip out and draw the correct sub image from the (shared) backingstore: CGRect backingStoreRect = CGRectMake( - dirtyRect.origin.x + m_backingStoreOffset.x(), - dirtyRect.origin.y + m_backingStoreOffset.y(), - dirtyRect.size.width, - dirtyRect.size.height + dirtyBackingRect.origin.x + m_backingStoreOffset.x(), + dirtyBackingRect.origin.y + m_backingStoreOffset.y(), + dirtyBackingRect.size.width, + dirtyBackingRect.size.height ); CGImageRef bsCGImage = m_backingStore->getBackingStoreCGImage(); CGImageRef cleanImg = CGImageCreateWithImageInRect(bsCGImage, backingStoreRect); - CGContextDrawImage(cgContext, dirtyCGRect, cleanImg); + CGContextDrawImage(cgContext, dirtyWindowRect, cleanImg); // Clean-up: CGContextRestoreGState(cgContext); diff --git a/src/widgets/itemviews/qitemdelegate.cpp b/src/widgets/itemviews/qitemdelegate.cpp index e7ca2d0c6cc..76314039331 100644 --- a/src/widgets/itemviews/qitemdelegate.cpp +++ b/src/widgets/itemviews/qitemdelegate.cpp @@ -1060,10 +1060,12 @@ QRect QItemDelegate::rect(const QStyleOptionViewItem &option, switch (value.type()) { case QVariant::Invalid: break; - case QVariant::Pixmap: - return QRect(QPoint(0, 0), qvariant_cast(value).size()); - case QVariant::Image: - return QRect(QPoint(0, 0), qvariant_cast(value).size()); + case QVariant::Pixmap: { + const QPixmap &pixmap = qvariant_cast(value); + return QRect(QPoint(0, 0), pixmap.size() / pixmap.devicePixelRatio() ); } + case QVariant::Image: { + const QImage &image = qvariant_cast(value); + return QRect(QPoint(0, 0), image.size() / image.devicePixelRatio() ); } case QVariant::Icon: { QIcon::Mode mode = d->iconMode(option.state); QIcon::State state = d->iconState(option.state); diff --git a/src/widgets/kernel/qwidget.cpp b/src/widgets/kernel/qwidget.cpp index e167e646c02..ace25fa78b2 100644 --- a/src/widgets/kernel/qwidget.cpp +++ b/src/widgets/kernel/qwidget.cpp @@ -1794,6 +1794,23 @@ QRegion QWidgetPrivate::clipRegion() const return r; } +void QWidgetPrivate::setSystemClip(QPaintDevice *paintDevice, const QRegion ®ion) +{ +// Transform the system clip region from device-independent pixels to device pixels +// Qt 5.0.0: This is a Mac-only code path for now, can be made cross-platform once +// it has been tested. + QPaintEngine *paintEngine = paintDevice->paintEngine(); +#ifdef Q_OS_MAC + const qreal devicePixelRatio = (paintDevice->physicalDpiX() == 0 || paintDevice->logicalDpiX() == 0) ? + 1.0 : (paintDevice->physicalDpiX() / paintDevice->logicalDpiX()); + QTransform scaleTransform; + scaleTransform.scale(devicePixelRatio, devicePixelRatio); + paintEngine->d_func()->systemClip = scaleTransform.map(region); +#else + paintEngine->d_func()->systemClip = region; +#endif +} + #ifndef QT_NO_GRAPHICSEFFECT void QWidgetPrivate::invalidateGraphicsEffectsRecursively() { @@ -4998,13 +5015,12 @@ void QWidgetPrivate::drawWidget(QPaintDevice *pdev, const QRegion &rgn, const QP QWidgetPaintContext context(pdev, rgn, offset, flags, sharedPainter, backingStore); sourced->context = &context; if (!sharedPainter) { - QPaintEngine *paintEngine = pdev->paintEngine(); - paintEngine->d_func()->systemClip = rgn.translated(offset); + setSystemClip(pdev, rgn.translated(offset)); QPainter p(pdev); p.translate(offset); context.painter = &p; graphicsEffect->draw(&p); - paintEngine->d_func()->systemClip = QRegion(); + setSystemClip(pdev, QRegion()); } else { context.painter = sharedPainter; if (sharedPainter->worldTransform() != sourced->lastEffectTransform) { @@ -5061,7 +5077,7 @@ void QWidgetPrivate::drawWidget(QPaintDevice *pdev, const QRegion &rgn, const QP #endif if (sharedPainter) - paintEngine->d_func()->systemClip = toBePainted; + setSystemClip(pdev, toBePainted); else paintEngine->d_func()->systemRect = q->data->crect; @@ -5073,7 +5089,7 @@ void QWidgetPrivate::drawWidget(QPaintDevice *pdev, const QRegion &rgn, const QP } if (!sharedPainter) - paintEngine->d_func()->systemClip = toBePainted.translated(offset); + setSystemClip(pdev, toBePainted.translated(offset)); if (!onScreen && !asRoot && !isOpaque && q->testAttribute(Qt::WA_TintedBackground)) { QPainter p(q); @@ -5108,7 +5124,8 @@ void QWidgetPrivate::drawWidget(QPaintDevice *pdev, const QRegion &rgn, const QP paintEngine->d_func()->systemRect = QRect(); else paintEngine->d_func()->currentClipDevice = 0; - paintEngine->d_func()->systemClip = QRegion(); + + setSystemClip(pdev, QRegion()); } q->setAttribute(Qt::WA_WState_InPaintEvent, false); if (q->paintingActive()) diff --git a/src/widgets/kernel/qwidget_p.h b/src/widgets/kernel/qwidget_p.h index 8aba276966d..1d183e41f19 100644 --- a/src/widgets/kernel/qwidget_p.h +++ b/src/widgets/kernel/qwidget_p.h @@ -402,6 +402,7 @@ public: QRect clipRect() const; QRegion clipRegion() const; + void setSystemClip(QPaintDevice *paintDevice, const QRegion ®ion); void subtractOpaqueChildren(QRegion &rgn, const QRect &clipRect) const; void subtractOpaqueSiblings(QRegion &source, bool *hasDirtySiblingsAbove = 0, bool alsoNonOpaque = false) const; diff --git a/src/widgets/styles/qfusionstyle.cpp b/src/widgets/styles/qfusionstyle.cpp index ebcdc10d590..623cdb55b9a 100644 --- a/src/widgets/styles/qfusionstyle.cpp +++ b/src/widgets/styles/qfusionstyle.cpp @@ -303,7 +303,7 @@ static void qt_fusion_draw_mdibutton(QPainter *painter, const QStyleOptionTitleB gradient.setColorAt(1, mdiButtonGradientStopColor); QColor mdiButtonBorderColor(active ? option->palette.highlight().color().darker(180): dark.darker(110)); - painter->setPen(QPen(mdiButtonBorderColor, 1)); + painter->setPen(QPen(mdiButtonBorderColor)); const QLine lines[4] = { QLine(tmp.left() + 2, tmp.top(), tmp.right() - 2, tmp.top()), QLine(tmp.left() + 2, tmp.bottom(), tmp.right() - 2, tmp.bottom()), @@ -457,7 +457,7 @@ void QFusionStyle::drawPrimitive(PrimitiveElement elem, if (const QStyleOptionTabBarBase *tbb = qstyleoption_cast(option)) { painter->save(); - painter->setPen(QPen(outline.lighter(110), 0)); + painter->setPen(QPen(outline.lighter(110))); switch (tbb->shape) { case QTabBar::RoundedNorth: { QRegion region(tbb->rect); @@ -603,7 +603,7 @@ void QFusionStyle::drawPrimitive(PrimitiveElement elem, } } break; - case PE_Frame: + case PE_Frame: { if (widget && widget->inherits("QComboBoxPrivateContainer")){ QStyleOption copy = *option; copy.state |= State_Raised; @@ -611,14 +611,16 @@ void QFusionStyle::drawPrimitive(PrimitiveElement elem, break; } painter->save(); - painter->setPen(outline.lighter(108)); + QPen thePen(outline.lighter(108)); + thePen.setCosmetic(false); + painter->setPen(thePen); painter->drawRect(option->rect.adjusted(0, 0, -1, -1)); - painter->restore(); + painter->restore(); } break; case PE_FrameMenu: painter->save(); { - painter->setPen(QPen(outline, 1)); + painter->setPen(QPen(outline)); painter->drawRect(option->rect.adjusted(0, 0, -1, -1)); QColor frameLight = option->palette.background().color().lighter(160); QColor frameShadow = option->palette.background().color().darker(110); @@ -644,9 +646,9 @@ void QFusionStyle::drawPrimitive(PrimitiveElement elem, QRect rect= option->rect; painter->setPen(softshadow); painter->drawRect(option->rect.adjusted(0, 0, -1, -1)); - painter->setPen(QPen(option->palette.light(), 0)); + painter->setPen(QPen(option->palette.light(), 1)); painter->drawLine(QPoint(rect.left() + 1, rect.top() + 1), QPoint(rect.left() + 1, rect.bottom() - 1)); - painter->setPen(QPen(option->palette.background().color().darker(120), 0)); + painter->setPen(QPen(option->palette.background().color().darker(120))); painter->drawLine(QPoint(rect.left() + 1, rect.bottom() - 1), QPoint(rect.right() - 2, rect.bottom() - 1)); painter->drawLine(QPoint(rect.right() - 1, rect.top() + 1), QPoint(rect.right() - 1, rect.bottom() - 1)); @@ -680,12 +682,12 @@ void QFusionStyle::drawPrimitive(PrimitiveElement elem, painter->save(); { QRect rect= option->rect; - painter->setPen(QPen(outline.darker(150), 0)); + painter->setPen(QPen(outline.darker(150))); painter->drawRect(option->rect.adjusted(0, 0, -1, -1)); - painter->setPen(QPen(option->palette.light(), 0)); + painter->setPen(QPen(option->palette.light(), 1)); painter->drawLine(QPoint(rect.left() + 1, rect.top() + 1), QPoint(rect.left() + 1, rect.bottom() - 1)); - painter->setPen(QPen(option->palette.background().color().darker(120), 0)); + painter->setPen(QPen(option->palette.background().color().darker(120))); painter->drawLine(QPoint(rect.left() + 1, rect.bottom() - 1), QPoint(rect.right() - 2, rect.bottom() - 1)); painter->drawLine(QPoint(rect.right() - 1, rect.top() + 1), @@ -705,7 +707,7 @@ void QFusionStyle::drawPrimitive(PrimitiveElement elem, painter->translate(0.5, 0.5); // Draw Outline - painter->setPen( QPen(hasFocus ? highlightedOutline : outline, 0)); + painter->setPen( QPen(hasFocus ? highlightedOutline : outline)); painter->setBrush(option->palette.base()); painter->drawRoundedRect(r.adjusted(0, 0, -1, -1), 2, 2); @@ -740,10 +742,10 @@ void QFusionStyle::drawPrimitive(PrimitiveElement elem, gradient.setColorAt(1, (state & State_Sunken) ? pressedColor : option->palette.base().color()); painter->setBrush((state & State_Sunken) ? QBrush(pressedColor) : gradient); - painter->setPen(QPen(outline.lighter(110), 1)); + painter->setPen(QPen(outline.lighter(110))); if (option->state & State_HasFocus && option->state & State_KeyboardFocusChange) - painter->setPen(QPen(highlightedOutline, 1)); + painter->setPen(QPen(highlightedOutline)); painter->drawRect(rect); QColor checkMarkColor = option->palette.text().color().darker(120); @@ -785,9 +787,9 @@ void QFusionStyle::drawPrimitive(PrimitiveElement elem, painter->setRenderHint(QPainter::Antialiasing, true); QPainterPath circle; circle.addEllipse(rect.center() + QPoint(1.0, 1.0), 6.5, 6.5); - painter->setPen(QPen(option->palette.background().color().darker(150), 1)); + painter->setPen(QPen(option->palette.background().color().darker(150))); if (option->state & State_HasFocus && option->state & State_KeyboardFocusChange) - painter->setPen(QPen(highlightedOutline, 1)); + painter->setPen(QPen(highlightedOutline)); painter->drawPath(circle); if (state & (State_On )) { @@ -862,7 +864,7 @@ void QFusionStyle::drawPrimitive(PrimitiveElement elem, if (isFlat && !isDown) { if (isDefault) { r = option->rect.adjusted(0, 1, 0, -1); - painter->setPen(QPen(Qt::black, 0)); + painter->setPen(QPen(Qt::black)); const QLine lines[4] = { QLine(QPoint(r.left() + 2, r.top()), QPoint(r.right() - 2, r.top())), @@ -910,7 +912,7 @@ void QFusionStyle::drawPrimitive(PrimitiveElement elem, p->setBrush(Qt::NoBrush); // Outline - p->setPen(!isEnabled ? QPen(darkOutline.lighter(115)) : QPen(darkOutline, 1)); + p->setPen(!isEnabled ? QPen(darkOutline.lighter(115)) : QPen(darkOutline)); p->drawRoundedRect(r, 2.0, 2.0); p->setPen(d->innerContrastLine()); @@ -1300,7 +1302,7 @@ void QFusionStyle::drawControl(ControlElement element, const QStyleOption *optio painter->drawLine(rect.topLeft() - QPoint(0, 1), rect.topRight() - QPoint(0, 1)); painter->setBrush(option->palette.base()); - painter->setPen(QPen(outline, 0)); + painter->setPen(QPen(outline)); painter->drawRoundedRect(rect.adjusted(0, 0, -1, -1), 2, 2); // Inner shadow @@ -1359,14 +1361,14 @@ void QFusionStyle::drawControl(ControlElement element, const QStyleOption *optio progressBar.setRect(rect.left(), rect.top(), width - 1, rect.height() - 1); if (!complete) { painter->drawLine(progressBar.topRight() + QPoint(2, 1), progressBar.bottomRight() + QPoint(2, 0)); - painter->setPen(QPen(highlight.darker(140), 0)); + painter->setPen(QPen(highlight.darker(140))); painter->drawLine(progressBar.topRight() + QPoint(1, 1), progressBar.bottomRight() + QPoint(1, 0)); } } else { progressBar.setRect(rect.right() - width - 1, rect.top(), width + 2, rect.height() - 1); if (!complete) { painter->drawLine(progressBar.topLeft() + QPoint(-2, 1), progressBar.bottomLeft() + QPoint(-2, 0)); - painter->setPen(QPen(highlight.darker(140), 0)); + painter->setPen(QPen(highlight.darker(140))); painter->drawLine(progressBar.topLeft() + QPoint(-1, 1), progressBar.bottomLeft() + QPoint(-1, 0)); } } @@ -1376,7 +1378,7 @@ void QFusionStyle::drawControl(ControlElement element, const QStyleOption *optio if (indeterminate || bar->progress > bar->minimum) { - painter->setPen(QPen(outline, 0)); + painter->setPen(QPen(outline)); QColor highlightedGradientStartColor = highlight.lighter(120); QColor highlightedGradientStopColor = highlight; @@ -1471,7 +1473,7 @@ void QFusionStyle::drawControl(ControlElement element, const QStyleOption *optio QRect r = option->rect; if (act) { painter->setBrush(option->palette.highlight().color()); - painter->setPen(QPen(highlightOutline, 0)); + painter->setPen(QPen(highlightOutline)); painter->drawRect(r.adjusted(0, 0, -1, -1)); // painter->drawRoundedRect(r.adjusted(1, 1, -1, -1), 2, 2); @@ -1518,7 +1520,7 @@ void QFusionStyle::drawControl(ControlElement element, const QStyleOption *optio if (selected) { QRect r = option->rect; painter->fillRect(r, highlight); - painter->setPen(QPen(highlightOutline, 0)); + painter->setPen(QPen(highlightOutline)); const QLine lines[4] = { QLine(QPoint(r.left() + 1, r.bottom()), QPoint(r.right() - 1, r.bottom())), QLine(QPoint(r.left() + 1, r.top()), QPoint(r.right() - 1, r.top())), @@ -2141,15 +2143,18 @@ void QFusionStyle::drawComplexControl(ComplexControl control, const QStyleOption QPixmap upArrow = colorizedImage(QLatin1String(":/qt-project.org/styles/commonstyle/images/fusion_arrow.png"), (spinBox->stepEnabled & QAbstractSpinBox::StepUpEnabled) ? arrowColor : disabledColor); - cachePainter.drawPixmap(QRect(upRect.center().x() - upArrow.width() / 4 + 1, - upRect.center().y() - upArrow.height() / 4 + 1, - upArrow.width()/2, upArrow.height()/2), upArrow); + QRectF upArrowRect = QRectF(upRect.center().x() - upArrow.width() / 4.0 + 1.0, + upRect.center().y() - upArrow.height() / 4.0 + 1.0, + upArrow.width() / 2.0, upArrow.height() / 2.0); + + cachePainter.drawPixmap(upArrowRect, upArrow, QRectF(QPointF(0.0, 0.0), upArrow.size())); QPixmap downArrow = colorizedImage(QLatin1String(":/qt-project.org/styles/commonstyle/images/fusion_arrow.png"), (spinBox->stepEnabled & QAbstractSpinBox::StepDownEnabled) ? arrowColor : disabledColor, 180); - cachePainter.drawPixmap(QRect(downRect.center().x() - downArrow.width() / 4 + 1, - downRect.center().y() - downArrow.height() / 4 + 1, - downArrow.width()/2, downArrow.height()/2), downArrow); + QRectF downArrowRect = QRectF(downRect.center().x() - downArrow.width() / 4.0 + 1.0, + downRect.center().y() - downArrow.height() / 4.0 + 1.0, + downArrow.width() / 2.0, downArrow.height() / 2.0); + cachePainter.drawPixmap(downArrowRect, downArrow, QRectF(QPointF(0.0, 0.0), downArrow.size())); } cachePainter.end(); @@ -2486,7 +2491,7 @@ void QFusionStyle::drawComplexControl(ComplexControl control, const QStyleOption // Paint slider if (scrollBar->subControls & SC_ScrollBarSlider) { QRect pixmapRect = scrollBarSlider; - painter->setPen(QPen(alphaOutline, 0)); + painter->setPen(QPen(alphaOutline)); if (option->state & State_Sunken && scrollBar->activeSubControls & SC_ScrollBarSlider) painter->setBrush(midColor2); else if (option->state & State_MouseOver && scrollBar->activeSubControls & SC_ScrollBarSlider) @@ -2521,7 +2526,7 @@ void QFusionStyle::drawComplexControl(ComplexControl control, const QStyleOption painter->setPen(Qt::NoPen); painter->drawRect(scrollBarSubLine.adjusted(horizontal ? 0 : 1, horizontal ? 1 : 0, 0, 0)); - painter->setPen(QPen(alphaOutline, 1)); + painter->setPen(QPen(alphaOutline)); if (option->state & State_Horizontal) { if (option->direction == Qt::RightToLeft) { pixmapRect.setLeft(scrollBarSubLine.left()); @@ -2545,9 +2550,10 @@ void QFusionStyle::drawComplexControl(ComplexControl control, const QStyleOption rotation = option->direction == Qt::LeftToRight ? -90 : 90; QRect upRect = scrollBarSubLine.translated(horizontal ? -2 : -1, 0); QPixmap arrowPixmap = colorizedImage(QLatin1String(":/qt-project.org/styles/commonstyle/images/fusion_arrow.png"), arrowColor, rotation); - painter->drawPixmap(QRect(upRect.center().x() - arrowPixmap.width() / 4 + 2, - upRect.center().y() - arrowPixmap.height() / 4 + 1, - arrowPixmap.width()/2, arrowPixmap.height()/2), arrowPixmap); + painter->drawPixmap(QRectF(upRect.center().x() - arrowPixmap.width() / 4.0 + 2.0, + upRect.center().y() - arrowPixmap.height() / 4.0 + 1.0, + arrowPixmap.width() / 2.0, arrowPixmap.height() / 2.0), + arrowPixmap, QRectF(QPoint(0.0, 0.0), arrowPixmap.size())); } // The AddLine (down/right) button @@ -2584,9 +2590,10 @@ void QFusionStyle::drawComplexControl(ComplexControl control, const QStyleOption rotation = option->direction == Qt::LeftToRight ? 90 : -90; QRect downRect = scrollBarAddLine.translated(-1, 1); QPixmap arrowPixmap = colorizedImage(QLatin1String(":/qt-project.org/styles/commonstyle/images/fusion_arrow.png"), arrowColor, rotation); - painter->drawPixmap(QRect(downRect.center().x() - arrowPixmap.width() / 4 + 2, - downRect.center().y() - arrowPixmap.height() / 4, - arrowPixmap.width()/2, arrowPixmap.height()/2), arrowPixmap); + painter->drawPixmap(QRectF(downRect.center().x() - arrowPixmap.width() / 4.0 + 2.0, + downRect.center().y() - arrowPixmap.height() / 4.0, + arrowPixmap.width() / 2.0, arrowPixmap.height() / 2.0), + arrowPixmap, QRectF(QPoint(0.0, 0.0), arrowPixmap.size())); } } @@ -2640,7 +2647,7 @@ void QFusionStyle::drawComplexControl(ComplexControl control, const QStyleOption downArrowRect.left() - 6: downArrowRect.right() + 6); proxy()->drawPrimitive(PE_PanelButtonCommand, &buttonOption, &cachePainter, widget); cachePainter.restore(); - cachePainter.setPen( QPen(hasFocus ? option->palette.highlight() : outline.lighter(110), 0)); + cachePainter.setPen( QPen(hasFocus ? option->palette.highlight() : outline.lighter(110), 1)); if (!sunken) { int borderSize = 1; @@ -2677,9 +2684,10 @@ void QFusionStyle::drawComplexControl(ComplexControl control, const QStyleOption QColor arrowColor = option->palette.buttonText().color(); arrowColor.setAlpha(220); QPixmap downArrow = colorizedImage(QLatin1String(":/qt-project.org/styles/commonstyle/images/fusion_arrow.png"), arrowColor, 180); - cachePainter.drawPixmap(QRect(downArrowRect.center().x() - downArrow.width() / 4 + 1, - downArrowRect.center().y() - downArrow.height() / 4 + 1, - downArrow.width()/2, downArrow.height()/2), downArrow); + cachePainter.drawPixmap(QRectF(downArrowRect.center().x() - downArrow.width() / 4.0 + 1.0, + downArrowRect.center().y() - downArrow.height() / 4.0 + 1.0, + downArrow.width() / 2.0, downArrow.height() / 2.0), + downArrow, QRectF(QPointF(0.0, 0.0), downArrow.size())); } cachePainter.end(); QPixmapCache::insert(pixmapName, cache); @@ -2730,7 +2738,7 @@ void QFusionStyle::drawComplexControl(ComplexControl control, const QStyleOption gradient.setStart(pixmapRect.left(), pixmapRect.center().y()); gradient.setFinalStop(pixmapRect.right(), pixmapRect.center().y()); } - groovePainter.setPen(QPen(outline, 0)); + groovePainter.setPen(QPen(outline)); gradient.setColorAt(0, grooveColor.darker(110)); gradient.setColorAt(1, grooveColor.lighter(110));//palette.button().color().darker(115)); groovePainter.setBrush(gradient); @@ -2764,7 +2772,7 @@ void QFusionStyle::drawComplexControl(ComplexControl control, const QStyleOption groovePainter.setRenderHint(QPainter::Antialiasing, true); groovePainter.translate(0.5, 0.5); - groovePainter.setPen(QPen(outline, 0)); + groovePainter.setPen(QPen(outline)); gradient.setColorAt(0, activeHighlight); gradient.setColorAt(1, activeHighlight.lighter(130)); groovePainter.setBrush(gradient); @@ -2813,9 +2821,9 @@ void QFusionStyle::drawComplexControl(ComplexControl control, const QStyleOption handlePainter.setBrush(QColor(0, 0, 0, 40)); handlePainter.drawRect(r.adjusted(-1, 2, 1, -2)); - handlePainter.setPen(QPen(d->outline(option->palette), 1)); + handlePainter.setPen(QPen(d->outline(option->palette))); if (option->state & State_HasFocus && option->state & State_KeyboardFocusChange) - handlePainter.setPen(QPen(d->highlightedOutline(option->palette), 1)); + handlePainter.setPen(QPen(d->highlightedOutline(option->palette))); handlePainter.setBrush(gradient); handlePainter.drawRoundedRect(r, 2, 2); diff --git a/src/widgets/styles/qmacstyle_mac.mm b/src/widgets/styles/qmacstyle_mac.mm index 759b3678d76..89ea4d553ac 100644 --- a/src/widgets/styles/qmacstyle_mac.mm +++ b/src/widgets/styles/qmacstyle_mac.mm @@ -462,7 +462,7 @@ class QMacCGContext { CGContextRef context; public: - QMacCGContext(QPainter *p); //qpaintengine_mac.cpp + QMacCGContext(QPainter *p); inline QMacCGContext() { context = 0; } inline QMacCGContext(const QPaintDevice *pdev) { extern CGContextRef qt_mac_cg_context(const QPaintDevice *); @@ -6476,6 +6476,18 @@ void qt_mac_clip_cg(CGContextRef hd, const QRegion &rgn, CGAffineTransform *orig } } +// move to QRegion? +void qt_mac_scale_region(QRegion *region, qreal scaleFactor) +{ + QVector scaledRects; + scaledRects.reserve(region->rects().count()); + + foreach (const QRect &rect, region->rects()) { + scaledRects.append(QRect(rect.topLeft(), rect.size() * scaleFactor)); + } + region->setRects(&scaledRects[0], scaledRects.count()); +} + QMacCGContext::QMacCGContext(QPainter *p) { QPaintEngine *pe = p->paintEngine(); @@ -6502,20 +6514,28 @@ QMacCGContext::QMacCGContext(QPainter *p) CGContextScaleCTM(context, 1, -1); if (devType == QInternal::Widget) { - QRegion clip = p->paintEngine()->systemClip(); - QTransform native = p->deviceTransform(); + // Set the clip rect which is an intersection of the system clip + // and the painter clip. To make matters more interesting these + // are in device pixels and device-independent pixels, respectively. + const qreal devicePixelRatio = image->devicePixelRatio(); + + QRegion clip = p->paintEngine()->systemClip(); // get system clip in device pixels + QTransform native = p->deviceTransform(); // get device transform. dx/dy is in device pixels if (p->hasClipping()) { - QRegion r = p->clipRegion(); + QRegion r = p->clipRegion(); // get painter clip, which is in device-independent pixels + qt_mac_scale_region(&r, devicePixelRatio); // scale painter clip to device pixels r.translate(native.dx(), native.dy()); if (clip.isEmpty()) clip = r; else clip &= r; } - qt_mac_clip_cg(context, clip, 0); + qt_mac_clip_cg(context, clip, 0); // clip in device pixels - CGContextTranslateCTM(context, native.dx(), native.dy()); + // Scale the context so that painting happens in device-independet pixels. + CGContextScaleCTM(context, devicePixelRatio, devicePixelRatio); + CGContextTranslateCTM(context, native.dx() / devicePixelRatio, native.dy() / devicePixelRatio); } } else { qDebug() << "QMacCGContext:: Unsupported painter devtype type" << devType; diff --git a/src/widgets/styles/qstyle.cpp b/src/widgets/styles/qstyle.cpp index 76d6efadeec..ab66cdae9f0 100644 --- a/src/widgets/styles/qstyle.cpp +++ b/src/widgets/styles/qstyle.cpp @@ -618,10 +618,11 @@ void QStyle::drawItemText(QPainter *painter, const QRect &rect, int alignment, c void QStyle::drawItemPixmap(QPainter *painter, const QRect &rect, int alignment, const QPixmap &pixmap) const { - QRect aligned = alignedRect(QApplication::layoutDirection(), QFlag(alignment), pixmap.size(), rect); + int scale = pixmap.devicePixelRatio(); + QRect aligned = alignedRect(QApplication::layoutDirection(), QFlag(alignment), pixmap.size() / scale, rect); QRect inter = aligned.intersected(rect); - painter->drawPixmap(inter.x(), inter.y(), pixmap, inter.x() - aligned.x(), inter.y() - aligned.y(), inter.width(), inter.height()); + painter->drawPixmap(inter.x(), inter.y(), pixmap, inter.x() - aligned.x(), inter.y() - aligned.y(), inter.width() * scale, inter.height() *scale); } /*! diff --git a/src/widgets/styles/qstyle_p.h b/src/widgets/styles/qstyle_p.h index 85e8e54b16b..78dfc6fed95 100644 --- a/src/widgets/styles/qstyle_p.h +++ b/src/widgets/styles/qstyle_p.h @@ -43,6 +43,7 @@ #define QSTYLE_P_H #include "private/qobject_p.h" +#include #include QT_BEGIN_NAMESPACE @@ -74,12 +75,18 @@ public: inline QImage styleCacheImage(const QSize &size) { - return QImage(size, QImage::Format_ARGB32_Premultiplied); + const qreal pixelRatio = qApp->devicePixelRatio(); + QImage cacheImage = QImage(size * pixelRatio, QImage::Format_ARGB32_Premultiplied); + cacheImage.setDevicePixelRatio(pixelRatio); + return cacheImage; } inline QPixmap styleCachePixmap(const QSize &size) { - return QPixmap(size); + const qreal pixelRatio = qApp->devicePixelRatio(); + QPixmap cachePixmap = QPixmap(size * pixelRatio); + cachePixmap.setDevicePixelRatio(pixelRatio); + return cachePixmap; } #define BEGIN_STYLE_PIXMAPCACHE(a) \ diff --git a/src/widgets/widgets/qlabel.cpp b/src/widgets/widgets/qlabel.cpp index a133b9c310c..3b3d15f6d0b 100644 --- a/src/widgets/widgets/qlabel.cpp +++ b/src/widgets/widgets/qlabel.cpp @@ -562,17 +562,18 @@ QSize QLabelPrivate::sizeForWidth(int w) const int vextra = hextra; QFontMetrics fm = q->fontMetrics(); - if (pixmap && !pixmap->isNull()) + if (pixmap && !pixmap->isNull()) { br = pixmap->rect(); + br.setSize(br.size() / pixmap->devicePixelRatio()); #ifndef QT_NO_PICTURE - else if (picture && !picture->isNull()) + } else if (picture && !picture->isNull()) { br = picture->boundingRect(); #endif #ifndef QT_NO_MOVIE - else if (movie && !movie->currentPixmap().isNull()) + } else if (movie && !movie->currentPixmap().isNull()) { br = movie->currentPixmap().rect(); #endif - else if (isTextLabel) { + } else if (isTextLabel) { int align = QStyle::visualAlignment(textDirection(), QFlag(this->align)); // Add indentation int m = indent; diff --git a/tests/manual/highdpi/highdpi.pro b/tests/manual/highdpi/highdpi.pro new file mode 100644 index 00000000000..635ba37a383 --- /dev/null +++ b/tests/manual/highdpi/highdpi.pro @@ -0,0 +1,12 @@ +TEMPLATE = app +TARGET = highdpi +DEPENDPATH += . +INCLUDEPATH += . +QT += widgets + +# Input +SOURCES += main.cpp + +RESOURCES += \ + highdpi.qrc + diff --git a/tests/manual/highdpi/highdpi.qrc b/tests/manual/highdpi/highdpi.qrc new file mode 100644 index 00000000000..b43c2c07ad9 --- /dev/null +++ b/tests/manual/highdpi/highdpi.qrc @@ -0,0 +1,7 @@ + + + qticon.png + qticon@2x.png + qticon_large.png + + diff --git a/tests/manual/highdpi/main.cpp b/tests/manual/highdpi/main.cpp new file mode 100644 index 00000000000..ee2d8ed29e2 --- /dev/null +++ b/tests/manual/highdpi/main.cpp @@ -0,0 +1,366 @@ +/**************************************************************************** + ** + ** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies). + ** Contact: http://www.qt-project.org/legal + ** + ** This file is part of the QtGui module of the Qt Toolkit. + ** + ** $QT_BEGIN_LICENSE:LGPL$ + ** Commercial License Usage + ** Licensees holding valid commercial Qt licenses may use this file in + ** accordance with the commercial license agreement provided with the + ** Software or, alternatively, in accordance with the terms contained in + ** a written agreement between you and Digia. For licensing terms and + ** conditions see http://qt.digia.com/licensing. For further information + ** use the contact form at http://qt.digia.com/contact-us. + ** + ** GNU Lesser General Public License Usage + ** Alternatively, this file may be used under the terms of the GNU Lesser + ** General Public License version 2.1 as published by the Free Software + ** Foundation and appearing in the file LICENSE.LGPL included in the + ** packaging of this file. Please review the following information to + ** ensure the GNU Lesser General Public License version 2.1 requirements + ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. + ** + ** In addition, as a special exception, Digia gives you certain additional + ** rights. These rights are described in the Digia Qt LGPL Exception + ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. + ** + ** GNU General Public License Usage + ** Alternatively, this file may be used under the terms of the GNU + ** General Public License version 3.0 as published by the Free Software + ** Foundation and appearing in the file LICENSE.GPL included in the + ** packaging of this file. Please review the following information to + ** ensure the GNU General Public License version 3.0 requirements will be + ** met: http://www.gnu.org/copyleft/gpl.html. + ** + ** + ** $QT_END_LICENSE$ + ** + ****************************************************************************/ + +#include +#include +#include + + +class PixmapPainter : public QWidget +{ +public: + PixmapPainter(); + void paintEvent(QPaintEvent *event); + + QPixmap pixmap1X; + QPixmap pixmap2X; + QPixmap pixmapLarge; + QImage image1X; + QImage image2X; + QImage imageLarge; + QIcon qtIcon; +}; + + +PixmapPainter::PixmapPainter() +{ + pixmap1X = QPixmap(":/qticon.png"); + pixmap2X = QPixmap(":/qticon@2x.png"); + pixmapLarge = QPixmap(":/qticon_large.png"); + + image1X = QImage(":/qticon.png"); + image2X = QImage(":/qticon@2x.png"); + imageLarge = QImage(":/qticon_large.png"); + + qtIcon.addFile(":/qticon.png"); + qtIcon.addFile(":/qticon@2x.png"); +} + +void PixmapPainter::paintEvent(QPaintEvent *event) +{ + QPainter p(this); + p.fillRect(QRect(QPoint(0, 0), size()), QBrush(Qt::gray)); + + int pixmapPointSize = 64; + int y = 30; + int dy = 150; + + int x = 10; + int dx = 80; + // draw at point +// qDebug() << "paint pixmap" << pixmap1X.devicePixelRatio(); + p.drawPixmap(x, y, pixmap1X); + x+=dx;p.drawPixmap(x, y, pixmap2X); + x+=dx;p.drawPixmap(x, y, pixmapLarge); + x+=dx*2;p.drawPixmap(x, y, qtIcon.pixmap(QSize(pixmapPointSize, pixmapPointSize))); + x+=dx;p.drawImage(x, y, image1X); + x+=dx;p.drawImage(x, y, image2X); + x+=dx;p.drawImage(x, y, imageLarge); + + // draw at 64x64 rect + y+=dy; + x = 10; + p.drawPixmap(QRect(x, y, pixmapPointSize, pixmapPointSize), pixmap1X); + x+=dx;p.drawPixmap(QRect(x, y, pixmapPointSize, pixmapPointSize), pixmap2X); + x+=dx;p.drawPixmap(QRect(x, y, pixmapPointSize, pixmapPointSize), pixmapLarge); + x+=dx;p.drawPixmap(QRect(x, y, pixmapPointSize, pixmapPointSize), qtIcon.pixmap(QSize(pixmapPointSize, pixmapPointSize))); + x+=dx;p.drawImage(QRect(x, y, pixmapPointSize, pixmapPointSize), image1X); + x+=dx;p.drawImage(QRect(x, y, pixmapPointSize, pixmapPointSize), image2X); + x+=dx;p.drawImage(QRect(x, y, pixmapPointSize, pixmapPointSize), imageLarge); + + + // draw at 128x128 rect + y+=dy - 50; + x = 10; + p.drawPixmap(QRect(x, y, pixmapPointSize * 2, pixmapPointSize * 2), pixmap1X); + x+=dx * 2; p.drawPixmap(QRect(x, y, pixmapPointSize * 2, pixmapPointSize * 2), pixmap2X); + x+=dx * 2; p.drawPixmap(QRect(x, y, pixmapPointSize * 2, pixmapPointSize * 2), pixmapLarge); + x+=dx * 2; p.drawPixmap(QRect(x, y, pixmapPointSize * 2, pixmapPointSize * 2), qtIcon.pixmap(QSize(pixmapPointSize, pixmapPointSize))); + x+=dx * 2; p.drawImage(QRect(x, y, pixmapPointSize * 2, pixmapPointSize * 2), image1X); + x+=dx * 2; p.drawImage(QRect(x, y, pixmapPointSize * 2, pixmapPointSize * 2), image2X); + x+=dx * 2; p.drawImage(QRect(x, y, pixmapPointSize * 2, pixmapPointSize * 2), imageLarge); + } + +class Labels : public QWidget +{ +public: + Labels(); + + QPixmap pixmap1X; + QPixmap pixmap2X; + QPixmap pixmapLarge; + QIcon qtIcon; +}; + +Labels::Labels() +{ + pixmap1X = QPixmap(":/qticon.png"); + pixmap2X = QPixmap(":/qticon@2x.png"); + pixmapLarge = QPixmap(":/qticon_large.png"); + + qtIcon.addFile(":/qticon.png"); + qtIcon.addFile(":/qticon@2x.png"); + setWindowIcon(qtIcon); + setWindowTitle("Labels"); + + QLabel *label1x = new QLabel(); + label1x->setPixmap(pixmap1X); + QLabel *label2x = new QLabel(); + label2x->setPixmap(pixmap2X); + QLabel *labelIcon = new QLabel(); + labelIcon->setPixmap(qtIcon.pixmap(QSize(64,64))); + QLabel *labelLarge = new QLabel(); + labelLarge->setPixmap(pixmapLarge); + + QHBoxLayout *layout = new QHBoxLayout(this); +// layout->addWidget(label1x); //expected low-res on high-dpi displays + layout->addWidget(label2x); +// layout->addWidget(labelIcon); +// layout->addWidget(labelLarge); // expected large size and low-res + setLayout(layout); +} + +class MainWindow : public QMainWindow +{ +public: + MainWindow(); + + QIcon qtIcon; + QIcon qtIcon1x; + QIcon qtIcon2x; + + QToolBar *fileToolBar; +}; + +MainWindow::MainWindow() +{ + qtIcon.addFile(":/qticon.png"); + qtIcon.addFile(":/qticon@2x.png"); + qtIcon1x.addFile(":/qticon.png"); + qtIcon2x.addFile(":/qticon@2x.png"); + setWindowIcon(qtIcon); + setWindowTitle("MainWindow"); + + fileToolBar = addToolBar(tr("File")); +// fileToolBar->setToolButtonStyle(Qt::ToolButtonTextUnderIcon); + fileToolBar->addAction(new QAction(qtIcon, QString("1x and 2x"), this)); + fileToolBar->addAction(new QAction(qtIcon1x, QString("1x"), this)); + fileToolBar->addAction(new QAction(qtIcon2x, QString("2x"), this)); +} + +class StandardIcons : public QWidget +{ +public: + void paintEvent(QPaintEvent *event) + { + int x = 10; + int y = 10; + int dx = 50; + int dy = 50; + int maxX = 500; + + for (int iconIndex = QStyle::SP_TitleBarMenuButton; iconIndex < QStyle::SP_MediaVolumeMuted; ++iconIndex) { + QIcon icon = qApp->style()->standardIcon(QStyle::StandardPixmap(iconIndex)); + QPainter p(this); + p.drawPixmap(x, y, icon.pixmap(dx - 5, dy - 5)); + if (x + dx > maxX) + y+=dy; + x = ((x + dx) % maxX); + } + }; +}; + +class Caching : public QWidget +{ +public: + void paintEvent(QPaintEvent *event) + { + QSize layoutSize(75, 75); + + QPainter widgetPainter(this); + widgetPainter.fillRect(QRect(QPoint(0, 0), this->size()), Qt::gray); + + { + const qreal devicePixelRatio = this->windowHandle()->devicePixelRatio(); + QPixmap cache(layoutSize * devicePixelRatio); + cache.setDevicePixelRatio(devicePixelRatio); + + QPainter cachedPainter(&cache); + cachedPainter.fillRect(QRect(0,0, 75, 75), Qt::blue); + cachedPainter.fillRect(QRect(10,10, 55, 55), Qt::red); + cachedPainter.drawEllipse(QRect(10,10, 55, 55)); + + QPainter widgetPainter(this); + widgetPainter.drawPixmap(QPoint(10, 10), cache); + } + + { + const qreal devicePixelRatio = this->windowHandle()->devicePixelRatio(); + QImage cache = QImage(layoutSize * devicePixelRatio, QImage::QImage::Format_ARGB32_Premultiplied); + cache.setDevicePixelRatio(devicePixelRatio); + + QPainter cachedPainter(&cache); + cachedPainter.fillRect(QRect(0,0, 75, 75), Qt::blue); + cachedPainter.fillRect(QRect(10,10, 55, 55), Qt::red); + cachedPainter.drawEllipse(QRect(10,10, 55, 55)); + + QPainter widgetPainter(this); + widgetPainter.drawImage(QPoint(95, 10), cache); + } + + } +}; + +class Style : public QWidget { +public: + QPushButton *button; + QLineEdit *lineEdit; + QSlider *slider; + QHBoxLayout *row1; + + Style() { + row1 = new QHBoxLayout(); + setLayout(row1); + + button = new QPushButton(); + button->setText("Test Button"); + row1->addWidget(button); + + lineEdit = new QLineEdit(); + lineEdit->setText("Test Lineedit"); + row1->addWidget(lineEdit); + + slider = new QSlider(); + row1->addWidget(slider); + + row1->addWidget(new QSpinBox); + row1->addWidget(new QScrollBar); + + QTabBar *tab = new QTabBar(); + tab->addTab("Foo"); + tab->addTab("Bar"); + row1->addWidget(tab); + } +}; + +class Fonts : public QWidget +{ +public: + void paintEvent(QPaintEvent *event) + { + QPainter painter(this); + int y = 40; + for (int fontSize = 2; fontSize < 18; fontSize += 2) { + QFont font; + font.setPointSize(fontSize); + QString string = QString(QStringLiteral("%1 The quick brown fox jumped over the lazy Doug.")).arg(fontSize); + painter.setFont(font); + painter.drawText(10, y, string); + y += (fontSize * 2.5); + } + } +}; + + +template +void apiTestdevicePixelRatioGetter() +{ + if (0) { + T *t = 0; + t->devicePixelRatio(); + } +} + +template +void apiTestdevicePixelRatioSetter() +{ + if (0) { + T *t = 0; + t->setDevicePixelRatio(2.0); + } +} + +void apiTest() +{ + // compile call to devicePixelRatio getter and setter (verify spelling) + apiTestdevicePixelRatioGetter(); + apiTestdevicePixelRatioGetter(); + apiTestdevicePixelRatioGetter(); + + apiTestdevicePixelRatioGetter(); + apiTestdevicePixelRatioSetter(); + apiTestdevicePixelRatioGetter(); + apiTestdevicePixelRatioSetter(); +} + +int main(int argc, char **argv) +{ + qputenv("QT_HIGHDPI_AWARE", "1"); + QApplication app(argc, argv); + + PixmapPainter pixmapPainter; + +// Enable for lots of pixmap drawing + pixmapPainter.show(); + + Labels label; + label.resize(200, 200); + label.show(); + + MainWindow mainWindow; + mainWindow.show(); + + StandardIcons icons; + icons.resize(510, 510); + icons.show(); + + Caching caching; + caching.resize(300, 300); + caching.show(); + + Style style; + style.show(); + + Fonts fonts; + fonts.show(); + + return app.exec(); +} diff --git a/tests/manual/highdpi/qticon.png b/tests/manual/highdpi/qticon.png new file mode 100644 index 0000000000000000000000000000000000000000..76f02c6c96dd41917c1307ecfa4e5d2140d27740 GIT binary patch literal 6474 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 literal 0 HcmV?d00001 diff --git a/tests/manual/highdpi/qticon@2x.png b/tests/manual/highdpi/qticon@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..0b00c00c96f423c8145a5692c97169436ff509ec GIT binary patch literal 17168 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::fromImage(image));