修改Qt源码支持DPI粒度到QWidget

修改Qt源码支持DPI粒度到QWidget

1、背景

在项目中总是遇到各种奇怪的需求,这不,碰到一个。

我们想要的我们的程序支持一种特性,就是有的界面支持高DPI。但是有的保持原来的大小。

这可怎么办?Qt中支持高DPI很简单的一行代码 QApplication::setAttribute(Qt::AA_EnableHighDpiScaling),但是它针对整个进程中所有的UI。

那没办法了,只能硬着头皮修改Qt的源码重新编译了。

对了,我的Qt版本是Qt 5.15.2.0。

其实我刚开始思路就是很简单,就是给QWidget设置一个flag,凡是这个flag为false表示不支持DPI缩放。这就是我整个的思路。

2、修改的文件

这里我就把修改的代码,生成patch文件,放在这里。可以通过git将patch应用到Qt源码中。

这个里面的.gitattributes,command.bat,copy_qch.sh这三个文件的修改直接忽略掉。

保存的文件格式要注意:unix格式换行符,GB2312编码

From cedae90de66f68638162198374be5ac848a5a6f9 Mon Sep 17 00:00:00 2001
From: "Shixiong.Liu" <635672377@qq.com>
Date: Fri, 16 Sep 2022 14:28:27 +0800
Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0widget=E5=8A=A8=E6=80=81?=
 =?UTF-8?q?=E5=B1=9E=E6=80=A7=E6=94=AF=E6=8C=81=E9=AB=98DPI=E7=89=B9?=
 =?UTF-8?q?=E6=80=A7?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .gitattributes                                |  1 +
 command.bat                                   | 17 ++++++++++
 copy_qch.sh                                   | 31 +++++++++++++++++++
 qtbase/src/gui/kernel/qhighdpiscaling.cpp     |  3 +-
 qtbase/src/gui/kernel/qplatformwindow.cpp     |  7 ++++-
 qtbase/src/gui/kernel/qwindow.cpp             | 28 +++++++++++++----
 qtbase/src/gui/kernel/qwindow.h               |  3 ++
 qtbase/src/gui/kernel/qwindow_p.h             |  1 +
 .../platforms/windows/qwindowscontext.cpp     |  5 +++
 .../platforms/windows/qwindowswindow.cpp      |  4 +--
 qtbase/src/widgets/kernel/qwidget.cpp         |  2 ++
 qtbase/src/widgets/kernel/qwidget.h           |  7 ++++-
 12 files changed, 97 insertions(+), 12 deletions(-)
 create mode 100644 .gitattributes
 create mode 100644 command.bat
 create mode 100644 copy_qch.sh

diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000000..dfdb8b771c
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1 @@
+*.sh text eol=lf
diff --git a/command.bat b/command.bat
new file mode 100644
index 0000000000..7d7b2af180
--- /dev/null
+++ b/command.bat
@@ -0,0 +1,17 @@
+REM 找到你的vs2019初始化环境的bat目录,我用的enterprise版本,不用的版本只需要替换下面的"Enterprise"就可以了
+CALL "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Auxiliary\Build\vcvarsall.bat" x86
+
+SET _ROOT=F:\qt5.15.2_doc
+SET PATH=D:\soft_dev\build_qt_devs\cmake-3.22.0\bin;%PATH%
+SET PATH=%_ROOT%\qtbase\bin;%PATH%
+SET PATH=D:\soft_dev\build_qt_devs\strawberry-perl-5.32.1.1\perl\bin;%PATH%
+SET PATH=D:\soft_dev\build_qt_devs\ninja-win;%PATH%
+SET LLVM_INSTALL_DIR=C:\Program Files (x86)\LLVM\
+
+set "MY_INSTALL_PATH=F:\qt5.15.2_doc\bin"
+ 
+REM configure.bat -prefix %MY_INSTALL_PATH% -DQT_NO_EXCEPTIONS=1 -debug-and-release -force-debug-info -platform win32-msvc -opensource -confirm-license 
+
+REM configure.bat -prefix %MY_INSTALL_PATH% -DQT_NO_EXCEPTIONS=0 -nomake tests -nomake examples -debug-and-release -force-debug-info -platform win32-msvc -opensource -confirm-license  OPENSSL_PREFIX=D:\qt\openssl-1.1.1p\win32-release -openssl-linked -I D:\qt\openssl-1.1.1p\win32-release\include -L D:\qt\openssl-1.1.1p\win32-release\lib OPENSSL_LIBS="libssl.lib libcrypto.lib Ws2_32.lib  Gdi32.lib Advapi32.lib Crypt32.lib User32.lib"
+
+configure.bat -prefix %MY_INSTALL_PATH% -nomake tests -nomake examples -debug -force-debug-info -platform win32-msvc -opensource -confirm-license  OPENSSL_PREFIX=D:\qt\openssl-1.1.1p\win32-release -openssl-linked -I F:\openssl-1.1.1p\win32-release\include -L F:\openssl-1.1.1p\win32-release\lib OPENSSL_LIBS="libssl.lib libcrypto.lib Ws2_32.lib  Gdi32.lib Advapi32.lib Crypt32.lib User32.lib"
diff --git a/copy_qch.sh b/copy_qch.sh
new file mode 100644
index 0000000000..b00835a808
--- /dev/null
+++ b/copy_qch.sh
@@ -0,0 +1,31 @@
+#!/bin/bash
+
+dst="./bin/doc/"
+blank=""
+
+RED='\e[1;31m' # 绾?+RES='\e[0m'
+
+function echo_color {
+	echo -e "${RED}$1${RES}"
+}
+
+for f in $(find . -name '*.qch'); 
+	do 
+		# cp ${f} ${dst}
+		file_name=`basename ${f}`
+		if [ ! -f "${dst}${file_name}" ];then
+			cp ${f} ${dst}
+		else
+			echo_color "${f} already exist"
+		fi
+		# echo ${file_name}
+		file_name_no_postfix=${file_name//.qch/${blank}}
+		dir_name="${dst}${file_name_no_postfix}"
+		if [ ! -d ${dir_name} ];then
+			mkdir ${dir_name}
+			cp -r ${f//.qch/${blank}} ${dst}
+		else
+			echo_color "${dir_name} already exist"
+		fi
+	done;
diff --git a/qtbase/src/gui/kernel/qhighdpiscaling.cpp b/qtbase/src/gui/kernel/qhighdpiscaling.cpp
index 9bbf2773a9..11e5c72447 100644
--- a/qtbase/src/gui/kernel/qhighdpiscaling.cpp
+++ b/qtbase/src/gui/kernel/qhighdpiscaling.cpp
@@ -716,9 +716,8 @@ QHighDpiScaling::ScaleAndOrigin QHighDpiScaling::scaleAndOrigin(const QScreen *s
 
 QHighDpiScaling::ScaleAndOrigin QHighDpiScaling::scaleAndOrigin(const QWindow *window, QPoint *nativePosition)
 {
-    if (!m_active)
+    if (!m_active || (window && !window->isAdaptDPI()))
         return { qreal(1), QPoint() };
-
     QScreen *screen = window ? window->screen() : QGuiApplication::primaryScreen();
     const bool searchScreen = !window || window->isTopLevel();
     return scaleAndOrigin(screen, searchScreen ? nativePosition : nullptr);
diff --git a/qtbase/src/gui/kernel/qplatformwindow.cpp b/qtbase/src/gui/kernel/qplatformwindow.cpp
index fc736033c2..be91c10792 100644
--- a/qtbase/src/gui/kernel/qplatformwindow.cpp
+++ b/qtbase/src/gui/kernel/qplatformwindow.cpp
@@ -730,7 +730,12 @@ QRect QPlatformWindow::initialGeometry(const QWindow *w, const QRect &initialGeo
             }
         }
     }
-    return QHighDpi::toNativePixels(rect, screen);
+
+    QRect geometry = rect;
+    if (w->isAdaptDPI()) {
+       geometry = QHighDpi::toNativePixels(rect, screen);
+    }
+    return geometry;
 }
 
 /*!
diff --git a/qtbase/src/gui/kernel/qwindow.cpp b/qtbase/src/gui/kernel/qwindow.cpp
index fd89e479b8..d1b5bc98c8 100644
--- a/qtbase/src/gui/kernel/qwindow.cpp
+++ b/qtbase/src/gui/kernel/qwindow.cpp
@@ -791,6 +791,18 @@ void QWindow::setModality(Qt::WindowModality modality)
     emit modalityChanged(modality);
 }
 
+bool QWindow::isAdaptDPI() const
+{
+    Q_D(const QWindow);
+    return d->adapt_dpi;
+}
+
+void QWindow::setAdaptDPI(bool adaptdpi)
+{
+    Q_D(QWindow);
+    d->adapt_dpi = adaptdpi;
+}
+
 /*! \fn void QWindow::modalityChanged(Qt::WindowModality modality)
 
     This signal is emitted when the Qwindow::modality property changes to \a modality.
@@ -1733,10 +1745,14 @@ void QWindow::setGeometry(const QRect &rect)
     if (d->platformWindow) {
         QRect nativeRect;
         QScreen *newScreen = d->screenForGeometry(rect);
-        if (newScreen && isTopLevel())
-            nativeRect = QHighDpi::toNativePixels(rect, newScreen);
-        else
-            nativeRect = QHighDpi::toNativeLocalPosition(rect, newScreen);
+        if (this->isAdaptDPI()) {
+            if (newScreen && isTopLevel())
+                nativeRect = QHighDpi::toNativePixels(rect, newScreen);
+            else
+                nativeRect = QHighDpi::toNativeLocalPosition(rect, newScreen);
+        } else {
+            nativeRect = rect;
+        }
         d->platformWindow->setGeometry(nativeRect);
     } else {
         d->geometry = rect;
@@ -2645,7 +2661,7 @@ QPoint QWindow::mapToGlobal(const QPoint &pos) const
         return QHighDpi::fromNativeLocalPosition(d->platformWindow->mapToGlobal(QHighDpi::toNativeLocalPosition(pos, this)), this);
     }
 
-    if (QHighDpiScaling::isActive())
+    if (QHighDpiScaling::isActive() && d->adapt_dpi)
         return QHighDpiScaling::mapPositionToGlobal(pos, d->globalPosition(), this);
 
     return pos + d->globalPosition();
@@ -2669,7 +2685,7 @@ QPoint QWindow::mapFromGlobal(const QPoint &pos) const
         return QHighDpi::fromNativeLocalPosition(d->platformWindow->mapFromGlobal(QHighDpi::toNativeLocalPosition(pos, this)), this);
     }
 
-    if (QHighDpiScaling::isActive())
+    if (QHighDpiScaling::isActive() && d->adapt_dpi)
         return QHighDpiScaling::mapPositionFromGlobal(pos, d->globalPosition(), this);
 
     return pos - d->globalPosition();
diff --git a/qtbase/src/gui/kernel/qwindow.h b/qtbase/src/gui/kernel/qwindow.h
index 7aae7ffffa..e9586c4413 100644
--- a/qtbase/src/gui/kernel/qwindow.h
+++ b/qtbase/src/gui/kernel/qwindow.h
@@ -168,6 +168,9 @@ public:
     Qt::WindowModality modality() const;
     void setModality(Qt::WindowModality modality);
 
+    bool isAdaptDPI() const;
+    void setAdaptDPI(bool adaptdpi);
+
     void setFormat(const QSurfaceFormat &format);
     QSurfaceFormat format() const override;
     QSurfaceFormat requestedFormat() const;
diff --git a/qtbase/src/gui/kernel/qwindow_p.h b/qtbase/src/gui/kernel/qwindow_p.h
index 5a7ec518fd..f1514c76d1 100644
--- a/qtbase/src/gui/kernel/qwindow_p.h
+++ b/qtbase/src/gui/kernel/qwindow_p.h
@@ -139,6 +139,7 @@ public:
     bool visible= false;
     bool visibilityOnDestroy = false;
     bool exposed = false;
+    bool adapt_dpi = true;
     QSurfaceFormat requestedFormat;
     QString windowTitle;
     QString windowFilePath;
diff --git a/qtbase/src/plugins/platforms/windows/qwindowscontext.cpp b/qtbase/src/plugins/platforms/windows/qwindowscontext.cpp
index fa757b0edc..ec84daa554 100644
--- a/qtbase/src/plugins/platforms/windows/qwindowscontext.cpp
+++ b/qtbase/src/plugins/platforms/windows/qwindowscontext.cpp
@@ -1438,6 +1438,11 @@ bool QWindowsContext::windowsProc(HWND hwnd, UINT message,
 #endif
     }   break;
     case QtWindows::DpiChangedEvent: {
+       // ignore WM_DPICHANGEC message, if window adapt DPI property is false.
+       if (!platformWindow->window()->isAdaptDPI()) {
+            qCDebug(lcQpaWindows) << __FUNCTION__ << "Ignore WM_DPICHANGED event";
+            return false;
+        }
         // Try to apply the suggested size first and then notify ScreenChanged
         // so that the resize event sent from QGuiApplication incorporates it
         // WM_DPICHANGED is sent with a size that avoids resize loops (by
diff --git a/qtbase/src/plugins/platforms/windows/qwindowswindow.cpp b/qtbase/src/plugins/platforms/windows/qwindowswindow.cpp
index d2c22f4100..63b2301ce6 100644
--- a/qtbase/src/plugins/platforms/windows/qwindowswindow.cpp
+++ b/qtbase/src/plugins/platforms/windows/qwindowswindow.cpp
@@ -1013,8 +1013,8 @@ void QWindowsGeometryHint::frameSizeConstraints(const QWindow *w, const QScreen
                                                 const QMargins &margins,
                                                 QSize *minimumSize, QSize *maximumSize)
 {
-    *minimumSize = toNativeSizeConstrained(w->minimumSize(), screen);
-    *maximumSize = toNativeSizeConstrained(w->maximumSize(), screen);
+    *minimumSize = w->isAdaptDPI() ? toNativeSizeConstrained(w->minimumSize(), screen) : w->minimumSize();
+    *maximumSize = w->isAdaptDPI() ? toNativeSizeConstrained(w->maximumSize(), screen) : w->maximumSize();
 
     const int maximumWidth = qMax(maximumSize->width(), minimumSize->width());
     const int maximumHeight = qMax(maximumSize->height(), minimumSize->height());
diff --git a/qtbase/src/widgets/kernel/qwidget.cpp b/qtbase/src/widgets/kernel/qwidget.cpp
index 479d91be0e..05b52f3e7c 100644
--- a/qtbase/src/widgets/kernel/qwidget.cpp
+++ b/qtbase/src/widgets/kernel/qwidget.cpp
@@ -1021,6 +1021,7 @@ void QWidgetPrivate::init(QWidget *parentWidget, Qt::WindowFlags f)
     data.in_show = 0;
     data.in_set_window_state = 0;
     data.in_destructor = false;
+    data.is_adapt_high_dpi = true;
 
     // Widgets with Qt::MSWindowsOwnDC (typically QGLWidget) must have a window handle.
     if (f & Qt::MSWindowsOwnDC) {
@@ -1311,6 +1312,7 @@ void QWidgetPrivate::create()
         QWindowPrivate::WindowFrameInclusive : QWindowPrivate::WindowFrameExclusive;
 
     if (q->windowType() != Qt::Desktop || q->testAttribute(Qt::WA_NativeWindow)) {
+        win->setAdaptDPI(data.is_adapt_high_dpi);
         win->create();
         // Enable nonclient-area events for QDockWidget and other NonClientArea-mouse event processing.
         if (QPlatformWindow *platformWindow = win->handle())
diff --git a/qtbase/src/widgets/kernel/qwidget.h b/qtbase/src/widgets/kernel/qwidget.h
index 415a738eb4..ebdc8e6de3 100644
--- a/qtbase/src/widgets/kernel/qwidget.h
+++ b/qtbase/src/widgets/kernel/qwidget.h
@@ -117,7 +117,9 @@ public:
     uint context_menu_policy : 3;
     uint window_modality : 2;
     uint in_destructor : 1;
-    uint unused : 13;
+    uint is_adapt_high_dpi: 1;
+    // uint unused : 13;
+    uint unused : 12;
     QRect crect;
     mutable QPalette pal;
     QFont fnt;
@@ -180,6 +182,7 @@ class Q_WIDGETS_EXPORT QWidget : public QObject, public QPaintDevice
     Q_PROPERTY(QString windowIconText READ windowIconText WRITE setWindowIconText NOTIFY windowIconTextChanged) // deprecated
     Q_PROPERTY(double windowOpacity READ windowOpacity WRITE setWindowOpacity)
     Q_PROPERTY(bool windowModified READ isWindowModified WRITE setWindowModified)
+    Q_PROPERTY(bool adaptHighDPI READ adaptHighDPI WRITE setAdaptHighDPI)
 #ifndef QT_NO_TOOLTIP
     Q_PROPERTY(QString toolTip READ toolTip WRITE setToolTip)
     Q_PROPERTY(int toolTipDuration READ toolTipDuration WRITE setToolTipDuration)
@@ -360,6 +363,8 @@ public:
     void grabGesture(Qt::GestureType type, Qt::GestureFlags flags = Qt::GestureFlags());
     void ungrabGesture(Qt::GestureType type);
 #endif
+    bool adaptHighDPI() const { return data->is_adapt_high_dpi; }
+    void setAdaptHighDPI(bool on) { data->is_adapt_high_dpi = on; }
 
 public Q_SLOTS:
     void setWindowTitle(const QString &);
-- 
2.33.0.windows.2

3、理解过程

1.几个文件,解释下。

qhighdpiscaling.cpp 这个文件就是UI缩放有着密切的关系,从名字就可以看出。这个基本是个静态类,内容不多。

qplatformwindow.cpp 这个class很复杂,应该是封装了平台相关的window。

qwindow.cpp 这个class和QWidget关系相当密切,在Qt的内部通过操作这个class,来达到修改QWidget属性。

qwidget.cpp我们最熟悉的QWidget控件。

这里有个class需要特别的注意,就是QScreen这就是屏幕分辨率密切相关的。每个QWiget下面都会带着个QScreen实例,而且这个是实例会不断的切换。

比如:你有多个屏幕,从屏幕1切到到屏幕2,此时QScreen就会被修改,同时根据此屏幕的DPI进行缩放。

再简单的画出class的关系:
在这里插入图片描述

再根据堆栈信息看下整个QWidget创建过程:
在这里插入图片描述

2.开始修改

我最开始是定位到qhighdpiscaling_p.h这个文件,找到如下的函数。这里是起点。

但是我一开始怎么找到这里的。没有任何的技巧,就是全局搜索关键字dpi,有时候第一次并不总是成功,所以需要多尝试和调试,就是需要耐心。

QHighDpiScaling::ScaleAndOrigin QHighDpiScaling::scaleAndOrigin(const QWindow *window, QPoint *nativePosition)
{
    if (!m_active || (window && !window->isAdaptDPI()))
        return { qreal(1), QPoint() };
    QScreen *screen = window ? window->screen() : QGuiApplication::primaryScreen();
    const bool searchScreen = !window || window->isTopLevel();
    return scaleAndOrigin(screen, searchScreen ? nativePosition : nullptr);
}

找到了起点之后,就是漫漫的编译调试之路了。接下来我一个个解释修改的地方含义。对照相应的patch片段来介绍。

1、首先QWidget增加了一个自定义的bool属性,这样可以在Designer中可视化操作。

​ 在QWidgetWindow调用QWindow父类create()函数之前就提前将属性值设置进去。否则就会出现在高DPI屏幕时初始化就会进行放大。

在这里插入图片描述

2、拒接接受系统发送的DPI change Event消息

在这里插入图片描述

3、下面这几处就是调试过程发现,同时需要屏蔽的地方。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

4、在到了进行缩放时根据属性值判断是否真的需要缩放。
在这里插入图片描述

在回顾下思路:就是给QWidget提供一个对用户的接口,让用户自己去决定。然后QWidget将属性值赋值给QWindow,然后QWindow带着属性值满世界的跑,需要的地方就进行判断。

整个思路还是很简单的。

4、总结

也是第一次修改Qt的源码,整个过程很是费劲,而且编译等待过程也是相当的漫长。所以每次编译之前一定要确保代码的正确性。

在阅读的过程中,也是受益良多。

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值