summaryrefslogtreecommitdiffstats
path: root/src/plugins
diff options
context:
space:
mode:
Diffstat (limited to 'src/plugins')
-rw-r--r--src/plugins/CMakeLists.txt7
-rw-r--r--src/plugins/doc/snippets/code/src_plugins_platforms_qnx_qqnxwindow.cpp2
-rw-r--r--src/plugins/generic/CMakeLists.txt8
-rw-r--r--src/plugins/generic/evdevkeyboard/CMakeLists.txt8
-rw-r--r--src/plugins/generic/evdevkeyboard/main.cpp3
-rw-r--r--src/plugins/generic/evdevmouse/CMakeLists.txt8
-rw-r--r--src/plugins/generic/evdevmouse/main.cpp3
-rw-r--r--src/plugins/generic/evdevtablet/CMakeLists.txt8
-rw-r--r--src/plugins/generic/evdevtablet/main.cpp2
-rw-r--r--src/plugins/generic/evdevtouch/CMakeLists.txt8
-rw-r--r--src/plugins/generic/evdevtouch/main.cpp2
-rw-r--r--src/plugins/generic/libinput/CMakeLists.txt8
-rw-r--r--src/plugins/generic/libinput/main.cpp2
-rw-r--r--src/plugins/generic/tslib/CMakeLists.txt10
-rw-r--r--src/plugins/generic/tslib/main.cpp2
-rw-r--r--src/plugins/generic/tuiotouch/CMakeLists.txt8
-rw-r--r--src/plugins/generic/tuiotouch/main.cpp2
-rw-r--r--src/plugins/generic/tuiotouch/qtuiohandler.cpp41
-rw-r--r--src/plugins/imageformats/CMakeLists.txt4
-rw-r--r--src/plugins/imageformats/gif/CMakeLists.txt13
-rw-r--r--src/plugins/imageformats/gif/qgifhandler.cpp6
-rw-r--r--src/plugins/imageformats/ico/CMakeLists.txt8
-rw-r--r--src/plugins/imageformats/ico/qicohandler.cpp9
-rw-r--r--src/plugins/imageformats/ico/qicohandler.h2
-rw-r--r--src/plugins/imageformats/jpeg/CMakeLists.txt4
-rw-r--r--src/plugins/imageformats/jpeg/qjpeghandler.cpp117
-rw-r--r--src/plugins/networkinformation/CMakeLists.txt2
-rw-r--r--src/plugins/networkinformation/android/CMakeLists.txt8
-rw-r--r--src/plugins/networkinformation/android/jar/build.gradle6
-rw-r--r--src/plugins/networkinformation/android/wrapper/androidconnectivitymanager.cpp4
-rw-r--r--src/plugins/networkinformation/glib/CMakeLists.txt2
-rw-r--r--src/plugins/networkinformation/glib/qglibnetworkinformationbackend.cpp31
-rw-r--r--src/plugins/networkinformation/networklistmanager/CMakeLists.txt6
-rw-r--r--src/plugins/networkinformation/networklistmanager/qnetworklistmanagerevents.cpp118
-rw-r--r--src/plugins/networkinformation/networklistmanager/qnetworklistmanagerevents.h27
-rw-r--r--src/plugins/networkinformation/networklistmanager/qnetworklistmanagernetworkinformationbackend.cpp40
-rw-r--r--src/plugins/networkinformation/networkmanager/CMakeLists.txt3
-rw-r--r--src/plugins/networkinformation/networkmanager/qnetworkmanagernetworkinformationbackend.cpp102
-rw-r--r--src/plugins/networkinformation/networkmanager/qnetworkmanagernetworkinformationbackend.h60
-rw-r--r--src/plugins/networkinformation/networkmanager/qnetworkmanagerservice.cpp95
-rw-r--r--src/plugins/networkinformation/networkmanager/qnetworkmanagerservice.h17
-rw-r--r--src/plugins/networkinformation/scnetworkreachability/CMakeLists.txt2
-rw-r--r--src/plugins/platforminputcontexts/CMakeLists.txt4
-rw-r--r--src/plugins/platforminputcontexts/compose/CMakeLists.txt12
-rw-r--r--src/plugins/platforminputcontexts/compose/qcomposeplatforminputcontext.cpp3
-rw-r--r--src/plugins/platforminputcontexts/ibus/CMakeLists.txt8
-rw-r--r--src/plugins/platforminputcontexts/ibus/interfaces/org.freedesktop.IBus.InputContext.xml18
-rw-r--r--src/plugins/platforminputcontexts/ibus/main.cpp5
-rw-r--r--src/plugins/platforminputcontexts/ibus/qibusinputcontextproxy.cpp3
-rw-r--r--src/plugins/platforminputcontexts/ibus/qibusinputcontextproxy.h73
-rw-r--r--src/plugins/platforminputcontexts/ibus/qibusplatforminputcontext.cpp167
-rw-r--r--src/plugins/platforminputcontexts/ibus/qibusplatforminputcontext.h3
-rw-r--r--src/plugins/platforminputcontexts/ibus/qibusproxy.h2
-rw-r--r--src/plugins/platforminputcontexts/ibus/qibusproxyportal.cpp3
-rw-r--r--src/plugins/platforminputcontexts/ibus/qibusproxyportal.h19
-rw-r--r--src/plugins/platforminputcontexts/ibus/qibustypes.cpp40
-rw-r--r--src/plugins/platforminputcontexts/ibus/qibustypes.h40
-rw-r--r--src/plugins/platforms/CMakeLists.txt10
-rw-r--r--src/plugins/platforms/android/CMakeLists.txt37
-rw-r--r--src/plugins/platforms/android/androidcontentfileengine.cpp820
-rw-r--r--src/plugins/platforms/android/androidcontentfileengine.h83
-rw-r--r--src/plugins/platforms/android/androidjniaccessibility.cpp71
-rw-r--r--src/plugins/platforms/android/androidjniaccessibility.h4
-rw-r--r--src/plugins/platforms/android/androidjniclipboard.cpp98
-rw-r--r--src/plugins/platforms/android/androidjniclipboard.h27
-rw-r--r--src/plugins/platforms/android/androidjniinput.cpp253
-rw-r--r--src/plugins/platforms/android/androidjniinput.h27
-rw-r--r--src/plugins/platforms/android/androidjnimain.cpp484
-rw-r--r--src/plugins/platforms/android/androidjnimain.h25
-rw-r--r--src/plugins/platforms/android/androidjnimenu.cpp68
-rw-r--r--src/plugins/platforms/android/androidjnimenu.h5
-rw-r--r--src/plugins/platforms/android/androidsurfaceclient.h24
-rw-r--r--src/plugins/platforms/android/androidwindowembedding.cpp75
-rw-r--r--src/plugins/platforms/android/androidwindowembedding.h41
-rw-r--r--src/plugins/platforms/android/qandroidassetsfileenginehandler.cpp68
-rw-r--r--src/plugins/platforms/android/qandroidassetsfileenginehandler.h2
-rw-r--r--src/plugins/platforms/android/qandroideventdispatcher.cpp8
-rw-r--r--src/plugins/platforms/android/qandroidinputcontext.cpp156
-rw-r--r--src/plugins/platforms/android/qandroidinputcontext.h6
-rw-r--r--src/plugins/platforms/android/qandroidplatformaccessibility.cpp2
-rw-r--r--src/plugins/platforms/android/qandroidplatformbackingstore.cpp52
-rw-r--r--src/plugins/platforms/android/qandroidplatformbackingstore.h29
-rw-r--r--src/plugins/platforms/android/qandroidplatformclipboard.cpp83
-rw-r--r--src/plugins/platforms/android/qandroidplatformclipboard.h16
-rw-r--r--src/plugins/platforms/android/qandroidplatformdialoghelpers.cpp19
-rw-r--r--src/plugins/platforms/android/qandroidplatformdialoghelpers.h5
-rw-r--r--src/plugins/platforms/android/qandroidplatformfiledialoghelper.cpp47
-rw-r--r--src/plugins/platforms/android/qandroidplatformfiledialoghelper.h6
-rw-r--r--src/plugins/platforms/android/qandroidplatformfontdatabase.cpp33
-rw-r--r--src/plugins/platforms/android/qandroidplatformforeignwindow.cpp89
-rw-r--r--src/plugins/platforms/android/qandroidplatformforeignwindow.h14
-rw-r--r--src/plugins/platforms/android/qandroidplatformiconengine.cpp616
-rw-r--r--src/plugins/platforms/android/qandroidplatformiconengine.h44
-rw-r--r--src/plugins/platforms/android/qandroidplatformintegration.cpp170
-rw-r--r--src/plugins/platforms/android/qandroidplatformintegration.h22
-rw-r--r--src/plugins/platforms/android/qandroidplatformmenu.cpp2
-rw-r--r--src/plugins/platforms/android/qandroidplatformopenglwindow.cpp72
-rw-r--r--src/plugins/platforms/android/qandroidplatformopenglwindow.h9
-rw-r--r--src/plugins/platforms/android/qandroidplatformscreen.cpp405
-rw-r--r--src/plugins/platforms/android/qandroidplatformscreen.h45
-rw-r--r--src/plugins/platforms/android/qandroidplatformservices.cpp51
-rw-r--r--src/plugins/platforms/android/qandroidplatformtheme.cpp113
-rw-r--r--src/plugins/platforms/android/qandroidplatformtheme.h14
-rw-r--r--src/plugins/platforms/android/qandroidplatformvulkanwindow.cpp50
-rw-r--r--src/plugins/platforms/android/qandroidplatformvulkanwindow.h11
-rw-r--r--src/plugins/platforms/android/qandroidplatformwindow.cpp259
-rw-r--r--src/plugins/platforms/android/qandroidplatformwindow.h68
-rw-r--r--src/plugins/platforms/android/qandroidsystemlocale.cpp45
-rw-r--r--src/plugins/platforms/android/qandroidsystemlocale.h3
-rw-r--r--src/plugins/platforms/bsdfb/qbsdfbscreen.cpp2
-rw-r--r--src/plugins/platforms/cocoa/CMakeLists.txt24
-rw-r--r--src/plugins/platforms/cocoa/main.mm2
-rw-r--r--src/plugins/platforms/cocoa/qcocoaaccessibility.mm10
-rw-r--r--src/plugins/platforms/cocoa/qcocoaaccessibilityelement.h4
-rw-r--r--src/plugins/platforms/cocoa/qcocoaaccessibilityelement.mm633
-rw-r--r--src/plugins/platforms/cocoa/qcocoaapplicationdelegate.mm72
-rw-r--r--src/plugins/platforms/cocoa/qcocoabackingstore.h4
-rw-r--r--src/plugins/platforms/cocoa/qcocoabackingstore.mm135
-rw-r--r--src/plugins/platforms/cocoa/qcocoaclipboard.mm6
-rw-r--r--src/plugins/platforms/cocoa/qcocoacolordialoghelper.mm12
-rw-r--r--src/plugins/platforms/cocoa/qcocoadrag.h2
-rw-r--r--src/plugins/platforms/cocoa/qcocoadrag.mm38
-rw-r--r--src/plugins/platforms/cocoa/qcocoaeventdispatcher.h25
-rw-r--r--src/plugins/platforms/cocoa/qcocoaeventdispatcher.mm151
-rw-r--r--src/plugins/platforms/cocoa/qcocoafiledialoghelper.h1
-rw-r--r--src/plugins/platforms/cocoa/qcocoafiledialoghelper.mm369
-rw-r--r--src/plugins/platforms/cocoa/qcocoafontdialoghelper.mm12
-rw-r--r--src/plugins/platforms/cocoa/qcocoaglcontext.mm4
-rw-r--r--src/plugins/platforms/cocoa/qcocoahelpers.h17
-rw-r--r--src/plugins/platforms/cocoa/qcocoahelpers.mm11
-rw-r--r--src/plugins/platforms/cocoa/qcocoainputcontext.mm11
-rw-r--r--src/plugins/platforms/cocoa/qcocoaintegration.h13
-rw-r--r--src/plugins/platforms/cocoa/qcocoaintegration.mm89
-rw-r--r--src/plugins/platforms/cocoa/qcocoamenu.mm40
-rw-r--r--src/plugins/platforms/cocoa/qcocoamenubar.h4
-rw-r--r--src/plugins/platforms/cocoa/qcocoamenubar.mm200
-rw-r--r--src/plugins/platforms/cocoa/qcocoamenuitem.h2
-rw-r--r--src/plugins/platforms/cocoa/qcocoamenuitem.mm109
-rw-r--r--src/plugins/platforms/cocoa/qcocoamenuloader.mm4
-rw-r--r--src/plugins/platforms/cocoa/qcocoamessagedialog.h38
-rw-r--r--src/plugins/platforms/cocoa/qcocoamessagedialog.mm425
-rw-r--r--src/plugins/platforms/cocoa/qcocoamimetypes.mm52
-rw-r--r--src/plugins/platforms/cocoa/qcocoanativeinterface.h15
-rw-r--r--src/plugins/platforms/cocoa/qcocoanativeinterface.mm55
-rw-r--r--src/plugins/platforms/cocoa/qcocoansmenu.mm2
-rw-r--r--src/plugins/platforms/cocoa/qcocoascreen.h4
-rw-r--r--src/plugins/platforms/cocoa/qcocoascreen.mm124
-rw-r--r--src/plugins/platforms/cocoa/qcocoaservices.h4
-rw-r--r--src/plugins/platforms/cocoa/qcocoaservices.mm39
-rw-r--r--src/plugins/platforms/cocoa/qcocoasystemtrayicon.h3
-rw-r--r--src/plugins/platforms/cocoa/qcocoasystemtrayicon.mm53
-rw-r--r--src/plugins/platforms/cocoa/qcocoatheme.h6
-rw-r--r--src/plugins/platforms/cocoa/qcocoatheme.mm83
-rw-r--r--src/plugins/platforms/cocoa/qcocoawindow.h51
-rw-r--r--src/plugins/platforms/cocoa/qcocoawindow.mm473
-rw-r--r--src/plugins/platforms/cocoa/qmacclipboard.h38
-rw-r--r--src/plugins/platforms/cocoa/qmacclipboard.mm243
-rw-r--r--src/plugins/platforms/cocoa/qmultitouch_mac.mm8
-rw-r--r--src/plugins/platforms/cocoa/qnsview.h8
-rw-r--r--src/plugins/platforms/cocoa/qnsview.mm59
-rw-r--r--src/plugins/platforms/cocoa/qnsview_accessibility.mm14
-rw-r--r--src/plugins/platforms/cocoa/qnsview_complextext.mm80
-rw-r--r--src/plugins/platforms/cocoa/qnsview_dragging.mm17
-rw-r--r--src/plugins/platforms/cocoa/qnsview_drawing.mm41
-rw-r--r--src/plugins/platforms/cocoa/qnsview_keys.mm101
-rw-r--r--src/plugins/platforms/cocoa/qnsview_menus.mm101
-rw-r--r--src/plugins/platforms/cocoa/qnsview_mouse.mm41
-rw-r--r--src/plugins/platforms/cocoa/qnsview_touch.mm20
-rw-r--r--src/plugins/platforms/cocoa/qnswindow.h5
-rw-r--r--src/plugins/platforms/cocoa/qnswindow.mm87
-rw-r--r--src/plugins/platforms/cocoa/qnswindowdelegate.mm4
-rw-r--r--src/plugins/platforms/direct2d/CMakeLists.txt38
-rw-r--r--src/plugins/platforms/direct2d/qwindowsdirect2dpaintengine.cpp2
-rw-r--r--src/plugins/platforms/directfb/CMakeLists.txt17
-rw-r--r--src/plugins/platforms/directfb/main.cpp2
-rw-r--r--src/plugins/platforms/directfb/qdirectfbbackingstore.h8
-rw-r--r--src/plugins/platforms/directfb/qdirectfbblitter.cpp4
-rw-r--r--src/plugins/platforms/directfb/qdirectfbblitter.h31
-rw-r--r--src/plugins/platforms/directfb/qdirectfbconvenience.h2
-rw-r--r--src/plugins/platforms/directfb/qdirectfbcursor.h2
-rw-r--r--src/plugins/platforms/directfb/qdirectfbinput.cpp13
-rw-r--r--src/plugins/platforms/directfb/qdirectfbinput.h2
-rw-r--r--src/plugins/platforms/directfb/qdirectfbintegration.h20
-rw-r--r--src/plugins/platforms/directfb/qdirectfbscreen.h10
-rw-r--r--src/plugins/platforms/directfb/qdirectfbwindow.h20
-rw-r--r--src/plugins/platforms/eglfs/CMakeLists.txt43
-rw-r--r--src/plugins/platforms/eglfs/api/qeglfscontext.cpp15
-rw-r--r--src/plugins/platforms/eglfs/api/qeglfscursor.cpp69
-rw-r--r--src/plugins/platforms/eglfs/api/qeglfscursor_p.h2
-rw-r--r--src/plugins/platforms/eglfs/api/qeglfsdeviceintegration.cpp6
-rw-r--r--src/plugins/platforms/eglfs/api/qeglfsintegration.cpp11
-rw-r--r--src/plugins/platforms/eglfs/api/qeglfsintegration_p.h2
-rw-r--r--src/plugins/platforms/eglfs/api/qeglfsscreen.cpp6
-rw-r--r--src/plugins/platforms/eglfs/api/qeglfswindow.cpp38
-rw-r--r--src/plugins/platforms/eglfs/api/qeglfswindow_p.h4
-rw-r--r--src/plugins/platforms/eglfs/deviceintegration/CMakeLists.txt10
-rw-r--r--src/plugins/platforms/eglfs/deviceintegration/eglfs_emu/CMakeLists.txt8
-rw-r--r--src/plugins/platforms/eglfs/deviceintegration/eglfs_emu/qeglfsemulatorintegration.cpp2
-rw-r--r--src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/CMakeLists.txt8
-rw-r--r--src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmdevice.cpp17
-rw-r--r--src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmdevice_p.h6
-rw-r--r--src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmscreen.cpp216
-rw-r--r--src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmscreen_p.h9
-rw-r--r--src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_egldevice/CMakeLists.txt7
-rw-r--r--src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_egldevice/qeglfskmsegldeviceintegration.cpp7
-rw-r--r--src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_egldevice/qeglfskmsegldevicescreen.cpp64
-rw-r--r--src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_egldevice/qeglfskmsegldevicescreen.h3
-rw-r--r--src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_support/CMakeLists.txt5
-rw-r--r--src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_support/qeglfskmsdevice_p.h6
-rw-r--r--src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_support/qeglfskmsscreen.cpp2
-rw-r--r--src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_vsp2/qlinuxmediadevice.cpp2
-rw-r--r--src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_vsp2/qvsp2blendingdevice.cpp4
-rw-r--r--src/plugins/platforms/eglfs/deviceintegration/eglfs_mali/CMakeLists.txt7
-rw-r--r--src/plugins/platforms/eglfs/deviceintegration/eglfs_openwfd/CMakeLists.txt7
-rw-r--r--src/plugins/platforms/eglfs/deviceintegration/eglfs_viv/CMakeLists.txt7
-rw-r--r--src/plugins/platforms/eglfs/deviceintegration/eglfs_viv_wl/CMakeLists.txt7
-rw-r--r--src/plugins/platforms/eglfs/deviceintegration/eglfs_x11/CMakeLists.txt8
-rw-r--r--src/plugins/platforms/eglfs/deviceintegration/eglfs_x11/qeglfsx11integration.cpp4
-rw-r--r--src/plugins/platforms/haiku/qhaikuintegration.cpp4
-rw-r--r--src/plugins/platforms/haiku/qhaikuwindow.cpp2
-rw-r--r--src/plugins/platforms/ios/CMakeLists.txt27
-rw-r--r--src/plugins/platforms/ios/optional/CMakeLists.txt4
-rw-r--r--src/plugins/platforms/ios/optional/nsphotolibrarysupport/CMakeLists.txt15
-rw-r--r--src/plugins/platforms/ios/optional/nsphotolibrarysupport/qiosfileengineassetslibrary.h4
-rw-r--r--src/plugins/platforms/ios/optional/nsphotolibrarysupport/qiosfileengineassetslibrary.mm41
-rw-r--r--src/plugins/platforms/ios/optional/nsphotolibrarysupport/qiosfileenginefactory.h8
-rw-r--r--src/plugins/platforms/ios/qiosclipboard.mm72
-rw-r--r--src/plugins/platforms/ios/qioscolordialog.h38
-rw-r--r--src/plugins/platforms/ios/qioscolordialog.mm159
-rw-r--r--src/plugins/platforms/ios/qioscontext.mm2
-rw-r--r--src/plugins/platforms/ios/qiosdocumentpickercontroller.h1
-rw-r--r--src/plugins/platforms/ios/qiosdocumentpickercontroller.mm72
-rw-r--r--src/plugins/platforms/ios/qiosfiledialog.h1
-rw-r--r--src/plugins/platforms/ios/qiosfiledialog.mm60
-rw-r--r--src/plugins/platforms/ios/qiosfontdialog.h41
-rw-r--r--src/plugins/platforms/ios/qiosfontdialog.mm190
-rw-r--r--src/plugins/platforms/ios/qiosglobal.h2
-rw-r--r--src/plugins/platforms/ios/qiosglobal.mm2
-rw-r--r--src/plugins/platforms/ios/qiosinputcontext.mm16
-rw-r--r--src/plugins/platforms/ios/qiosintegration.h6
-rw-r--r--src/plugins/platforms/ios/qiosintegration.mm33
-rw-r--r--src/plugins/platforms/ios/qiosmenu.h2
-rw-r--r--src/plugins/platforms/ios/qiosmenu.mm6
-rw-r--r--src/plugins/platforms/ios/qiosmessagedialog.mm27
-rw-r--r--src/plugins/platforms/ios/qiosplatformaccessibility.mm30
-rw-r--r--src/plugins/platforms/ios/qiosscreen.mm76
-rw-r--r--src/plugins/platforms/ios/qiostextinputoverlay.mm31
-rw-r--r--src/plugins/platforms/ios/qiostextresponder.mm28
-rw-r--r--src/plugins/platforms/ios/qiostheme.h3
-rw-r--r--src/plugins/platforms/ios/qiostheme.mm43
-rw-r--r--src/plugins/platforms/ios/qiosviewcontroller.mm7
-rw-r--r--src/plugins/platforms/ios/qioswindow.h13
-rw-r--r--src/plugins/platforms/ios/qioswindow.mm208
-rw-r--r--src/plugins/platforms/ios/quiaccessibilityelement.h2
-rw-r--r--src/plugins/platforms/ios/quiaccessibilityelement.mm17
-rw-r--r--src/plugins/platforms/ios/quiview.h1
-rw-r--r--src/plugins/platforms/ios/quiview.mm133
-rw-r--r--src/plugins/platforms/ios/quiview_accessibility.mm5
-rw-r--r--src/plugins/platforms/linuxfb/CMakeLists.txt12
-rw-r--r--src/plugins/platforms/linuxfb/main.cpp2
-rw-r--r--src/plugins/platforms/linuxfb/qlinuxfbdrmscreen.cpp9
-rw-r--r--src/plugins/platforms/linuxfb/qlinuxfbintegration.cpp4
-rw-r--r--src/plugins/platforms/linuxfb/qlinuxfbscreen.cpp10
-rw-r--r--src/plugins/platforms/minimal/CMakeLists.txt14
-rw-r--r--src/plugins/platforms/minimal/main.cpp2
-rw-r--r--src/plugins/platforms/minimal/qminimalintegration.cpp14
-rw-r--r--src/plugins/platforms/minimal/qminimalintegration.h5
-rw-r--r--src/plugins/platforms/minimalegl/CMakeLists.txt15
-rw-r--r--src/plugins/platforms/minimalegl/main.cpp2
-rw-r--r--src/plugins/platforms/minimalegl/qminimaleglbackingstore.cpp2
-rw-r--r--src/plugins/platforms/minimalegl/qminimaleglscreen.cpp8
-rw-r--r--src/plugins/platforms/offscreen/CMakeLists.txt12
-rw-r--r--src/plugins/platforms/offscreen/qoffscreencommon.cpp4
-rw-r--r--src/plugins/platforms/offscreen/qoffscreenintegration.cpp6
-rw-r--r--src/plugins/platforms/offscreen/qoffscreenwindow.cpp8
-rw-r--r--src/plugins/platforms/offscreen/qoffscreenwindow.h2
-rw-r--r--src/plugins/platforms/qnx/CMakeLists.txt18
-rw-r--r--src/plugins/platforms/qnx/qqnxbuttoneventnotifier.cpp4
-rw-r--r--src/plugins/platforms/qnx/qqnxbuttoneventnotifier.h3
-rw-r--r--src/plugins/platforms/qnx/qqnxclipboard.cpp2
-rw-r--r--src/plugins/platforms/qnx/qqnxintegration.cpp6
-rw-r--r--src/plugins/platforms/qnx/qqnxintegration.h4
-rw-r--r--src/plugins/platforms/qnx/qqnxnavigatoreventnotifier.cpp6
-rw-r--r--src/plugins/platforms/qnx/qqnxnavigatoreventnotifier.h3
-rw-r--r--src/plugins/platforms/qnx/qqnxnavigatorpps.cpp6
-rw-r--r--src/plugins/platforms/qnx/qqnxnavigatorpps.h3
-rw-r--r--src/plugins/platforms/qnx/qqnxscreen.cpp2
-rw-r--r--src/plugins/platforms/qnx/qqnxscreeneventhandler.cpp4
-rw-r--r--src/plugins/platforms/qnx/qqnxwindow.cpp2
-rw-r--r--src/plugins/platforms/vkkhrdisplay/CMakeLists.txt2
-rw-r--r--src/plugins/platforms/vkkhrdisplay/main.cpp2
-rw-r--r--src/plugins/platforms/vkkhrdisplay/qvkkhrdisplayintegration.cpp6
-rw-r--r--src/plugins/platforms/vkkhrdisplay/qvkkhrdisplayvulkaninstance.cpp17
-rw-r--r--src/plugins/platforms/vkkhrdisplay/qvkkhrdisplayvulkaninstance.h1
-rw-r--r--src/plugins/platforms/vnc/CMakeLists.txt12
-rw-r--r--src/plugins/platforms/vnc/qvnc.cpp11
-rw-r--r--src/plugins/platforms/vnc/qvncscreen.cpp7
-rw-r--r--src/plugins/platforms/wasm/CMakeLists.txt38
-rw-r--r--src/plugins/platforms/wasm/main.cpp4
-rw-r--r--src/plugins/platforms/wasm/qtloader.js806
-rw-r--r--src/plugins/platforms/wasm/qtlogo.svg40
-rw-r--r--src/plugins/platforms/wasm/qwasmaccessibility.cpp599
-rw-r--r--src/plugins/platforms/wasm/qwasmaccessibility.h50
-rw-r--r--src/plugins/platforms/wasm/qwasmbackingstore.cpp88
-rw-r--r--src/plugins/platforms/wasm/qwasmbackingstore.h11
-rw-r--r--src/plugins/platforms/wasm/qwasmbase64iconstore.cpp40
-rw-r--r--src/plugins/platforms/wasm/qwasmbase64iconstore.h37
-rw-r--r--src/plugins/platforms/wasm/qwasmclipboard.cpp227
-rw-r--r--src/plugins/platforms/wasm/qwasmclipboard.h26
-rw-r--r--src/plugins/platforms/wasm/qwasmcompositor.cpp1266
-rw-r--r--src/plugins/platforms/wasm/qwasmcompositor.h227
-rw-r--r--src/plugins/platforms/wasm/qwasmcssstyle.cpp248
-rw-r--r--src/plugins/platforms/wasm/qwasmcssstyle.h18
-rw-r--r--src/plugins/platforms/wasm/qwasmcursor.cpp145
-rw-r--r--src/plugins/platforms/wasm/qwasmcursor.h8
-rw-r--r--src/plugins/platforms/wasm/qwasmdom.cpp303
-rw-r--r--src/plugins/platforms/wasm/qwasmdom.h63
-rw-r--r--src/plugins/platforms/wasm/qwasmdrag.cpp410
-rw-r--r--src/plugins/platforms/wasm/qwasmdrag.h52
-rw-r--r--src/plugins/platforms/wasm/qwasmevent.cpp307
-rw-r--r--src/plugins/platforms/wasm/qwasmevent.h117
-rw-r--r--src/plugins/platforms/wasm/qwasmeventdispatcher.cpp24
-rw-r--r--src/plugins/platforms/wasm/qwasmeventdispatcher.h3
-rw-r--r--src/plugins/platforms/wasm/qwasmeventtranslator.cpp360
-rw-r--r--src/plugins/platforms/wasm/qwasmeventtranslator.h48
-rw-r--r--src/plugins/platforms/wasm/qwasmfontdatabase.cpp363
-rw-r--r--src/plugins/platforms/wasm/qwasmfontdatabase.h26
-rw-r--r--src/plugins/platforms/wasm/qwasminputcontext.cpp98
-rw-r--r--src/plugins/platforms/wasm/qwasminputcontext.h9
-rw-r--r--src/plugins/platforms/wasm/qwasmintegration.cpp250
-rw-r--r--src/plugins/platforms/wasm/qwasmintegration.h30
-rw-r--r--src/plugins/platforms/wasm/qwasmkeytranslator.cpp295
-rw-r--r--src/plugins/platforms/wasm/qwasmkeytranslator.h34
-rw-r--r--src/plugins/platforms/wasm/qwasmoffscreensurface.cpp22
-rw-r--r--src/plugins/platforms/wasm/qwasmoffscreensurface.h17
-rw-r--r--src/plugins/platforms/wasm/qwasmopenglcontext.cpp158
-rw-r--r--src/plugins/platforms/wasm/qwasmopenglcontext.h28
-rw-r--r--src/plugins/platforms/wasm/qwasmplatform.cpp5
-rw-r--r--src/plugins/platforms/wasm/qwasmplatform.h2
-rw-r--r--src/plugins/platforms/wasm/qwasmscreen.cpp308
-rw-r--r--src/plugins/platforms/wasm/qwasmscreen.h40
-rw-r--r--src/plugins/platforms/wasm/qwasmservices.cpp5
-rw-r--r--src/plugins/platforms/wasm/qwasmstring.cpp36
-rw-r--r--src/plugins/platforms/wasm/qwasmstring.h19
-rw-r--r--src/plugins/platforms/wasm/qwasmstylepixmaps_p.h159
-rw-r--r--src/plugins/platforms/wasm/qwasmtheme.cpp1
-rw-r--r--src/plugins/platforms/wasm/qwasmwindow.cpp742
-rw-r--r--src/plugins/platforms/wasm/qwasmwindow.h159
-rw-r--r--src/plugins/platforms/wasm/qwasmwindowclientarea.cpp195
-rw-r--r--src/plugins/platforms/wasm/qwasmwindowclientarea.h52
-rw-r--r--src/plugins/platforms/wasm/qwasmwindownonclientarea.cpp460
-rw-r--r--src/plugins/platforms/wasm/qwasmwindownonclientarea.h227
-rw-r--r--src/plugins/platforms/wasm/qwasmwindowstack.cpp203
-rw-r--r--src/plugins/platforms/wasm/qwasmwindowstack.h75
-rw-r--r--src/plugins/platforms/wasm/qwasmwindowtreenode.cpp108
-rw-r--r--src/plugins/platforms/wasm/qwasmwindowtreenode.h53
-rw-r--r--src/plugins/platforms/wasm/resources/maximize.svg1
-rw-r--r--src/plugins/platforms/wasm/resources/qtlogo.svg1
-rw-r--r--src/plugins/platforms/wasm/resources/restore.svg1
-rw-r--r--src/plugins/platforms/wasm/resources/x.svg1
-rw-r--r--src/plugins/platforms/wasm/wasm_shell.html81
-rw-r--r--src/plugins/platforms/windows/CMakeLists.txt38
-rw-r--r--src/plugins/platforms/windows/qtwindowsglobal.h36
-rw-r--r--src/plugins/platforms/windows/qwindowsapplication.cpp26
-rw-r--r--src/plugins/platforms/windows/qwindowsapplication.h8
-rw-r--r--src/plugins/platforms/windows/qwindowsbackingstore.cpp2
-rw-r--r--src/plugins/platforms/windows/qwindowsclipboard.cpp4
-rw-r--r--src/plugins/platforms/windows/qwindowscombase.h32
-rw-r--r--src/plugins/platforms/windows/qwindowscontext.cpp326
-rw-r--r--src/plugins/platforms/windows/qwindowscontext.h23
-rw-r--r--src/plugins/platforms/windows/qwindowscursor.cpp27
-rw-r--r--src/plugins/platforms/windows/qwindowsdialoghelpers.cpp84
-rw-r--r--src/plugins/platforms/windows/qwindowsdrag.cpp95
-rw-r--r--src/plugins/platforms/windows/qwindowsdrag.h6
-rw-r--r--src/plugins/platforms/windows/qwindowsdropdataobject.cpp10
-rw-r--r--src/plugins/platforms/windows/qwindowsglcontext.cpp16
-rw-r--r--src/plugins/platforms/windows/qwindowsiconengine.cpp394
-rw-r--r--src/plugins/platforms/windows/qwindowsiconengine.h47
-rw-r--r--src/plugins/platforms/windows/qwindowsinputcontext.cpp2
-rw-r--r--src/plugins/platforms/windows/qwindowsintegration.cpp269
-rw-r--r--src/plugins/platforms/windows/qwindowsintegration.h13
-rw-r--r--src/plugins/platforms/windows/qwindowsinternalmimedata.cpp12
-rw-r--r--src/plugins/platforms/windows/qwindowskeymapper.cpp137
-rw-r--r--src/plugins/platforms/windows/qwindowskeymapper.h8
-rw-r--r--src/plugins/platforms/windows/qwindowsmenu.cpp3
-rw-r--r--src/plugins/platforms/windows/qwindowsmenu.h1
-rw-r--r--src/plugins/platforms/windows/qwindowsmimeregistry.cpp (renamed from src/plugins/platforms/windows/qwindowsmime.cpp)280
-rw-r--r--src/plugins/platforms/windows/qwindowsmimeregistry.h (renamed from src/plugins/platforms/windows/qwindowsmime.h)28
-rw-r--r--src/plugins/platforms/windows/qwindowsmousehandler.cpp39
-rw-r--r--src/plugins/platforms/windows/qwindowsole.cpp8
-rw-r--r--src/plugins/platforms/windows/qwindowsole.h6
-rw-r--r--src/plugins/platforms/windows/qwindowsopengltester.cpp1
-rw-r--r--src/plugins/platforms/windows/qwindowspointerhandler.cpp67
-rw-r--r--src/plugins/platforms/windows/qwindowsscreen.cpp240
-rw-r--r--src/plugins/platforms/windows/qwindowsscreen.h15
-rw-r--r--src/plugins/platforms/windows/qwindowsservices.cpp81
-rw-r--r--src/plugins/platforms/windows/qwindowssystemtrayicon.cpp78
-rw-r--r--src/plugins/platforms/windows/qwindowssystemtrayicon.h2
-rw-r--r--src/plugins/platforms/windows/qwindowstabletsupport.cpp3
-rw-r--r--src/plugins/platforms/windows/qwindowstheme.cpp500
-rw-r--r--src/plugins/platforms/windows/qwindowstheme.h5
-rw-r--r--src/plugins/platforms/windows/qwindowswindow.cpp513
-rw-r--r--src/plugins/platforms/windows/qwindowswindow.h10
-rw-r--r--src/plugins/platforms/windows/uiautomation/qwindowsuiaaccessibility.cpp26
-rw-r--r--src/plugins/platforms/windows/uiautomation/qwindowsuiaaccessibility.h1
-rw-r--r--src/plugins/platforms/windows/uiautomation/qwindowsuiabaseprovider.h4
-rw-r--r--src/plugins/platforms/windows/uiautomation/qwindowsuiaexpandcollapseprovider.h2
-rw-r--r--src/plugins/platforms/windows/uiautomation/qwindowsuiagriditemprovider.h2
-rw-r--r--src/plugins/platforms/windows/uiautomation/qwindowsuiagridprovider.h3
-rw-r--r--src/plugins/platforms/windows/uiautomation/qwindowsuiainvokeprovider.h3
-rw-r--r--src/plugins/platforms/windows/uiautomation/qwindowsuiamainprovider.cpp137
-rw-r--r--src/plugins/platforms/windows/uiautomation/qwindowsuiamainprovider.h3
-rw-r--r--src/plugins/platforms/windows/uiautomation/qwindowsuiaprovidercache.cpp2
-rw-r--r--src/plugins/platforms/windows/uiautomation/qwindowsuiarangevalueprovider.h2
-rw-r--r--src/plugins/platforms/windows/uiautomation/qwindowsuiaselectionitemprovider.cpp38
-rw-r--r--src/plugins/platforms/windows/uiautomation/qwindowsuiaselectionitemprovider.h2
-rw-r--r--src/plugins/platforms/windows/uiautomation/qwindowsuiaselectionprovider.cpp168
-rw-r--r--src/plugins/platforms/windows/uiautomation/qwindowsuiaselectionprovider.h21
-rw-r--r--src/plugins/platforms/windows/uiautomation/qwindowsuiatableitemprovider.h2
-rw-r--r--src/plugins/platforms/windows/uiautomation/qwindowsuiatableprovider.h3
-rw-r--r--src/plugins/platforms/windows/uiautomation/qwindowsuiatextprovider.cpp21
-rw-r--r--src/plugins/platforms/windows/uiautomation/qwindowsuiatextprovider.h19
-rw-r--r--src/plugins/platforms/windows/uiautomation/qwindowsuiatextrangeprovider.cpp53
-rw-r--r--src/plugins/platforms/windows/uiautomation/qwindowsuiatextrangeprovider.h4
-rw-r--r--src/plugins/platforms/windows/uiautomation/qwindowsuiatoggleprovider.h3
-rw-r--r--src/plugins/platforms/windows/uiautomation/qwindowsuiautils.cpp3
-rw-r--r--src/plugins/platforms/windows/uiautomation/qwindowsuiautils.h2
-rw-r--r--src/plugins/platforms/windows/uiautomation/qwindowsuiautomation.cpp74
-rw-r--r--src/plugins/platforms/windows/uiautomation/qwindowsuiautomation.h70
-rw-r--r--src/plugins/platforms/windows/uiautomation/qwindowsuiavalueprovider.h3
-rw-r--r--src/plugins/platforms/windows/uiautomation/qwindowsuiawindowprovider.h3
-rw-r--r--src/plugins/platforms/xcb/CMakeLists.txt49
-rw-r--r--src/plugins/platforms/xcb/gl_integrations/CMakeLists.txt4
-rw-r--r--src/plugins/platforms/xcb/gl_integrations/xcb_egl/CMakeLists.txt9
-rw-r--r--src/plugins/platforms/xcb/gl_integrations/xcb_egl/qxcbeglinclude.h1
-rw-r--r--src/plugins/platforms/xcb/gl_integrations/xcb_egl/qxcbeglintegration.cpp5
-rw-r--r--src/plugins/platforms/xcb/gl_integrations/xcb_egl/qxcbeglintegration.h3
-rw-r--r--src/plugins/platforms/xcb/gl_integrations/xcb_egl/qxcbeglwindow.cpp17
-rw-r--r--src/plugins/platforms/xcb/gl_integrations/xcb_glx/CMakeLists.txt5
-rw-r--r--src/plugins/platforms/xcb/gl_integrations/xcb_glx/qglxintegration.cpp11
-rw-r--r--src/plugins/platforms/xcb/nativepainting/qbackingstore_x11.cpp11
-rw-r--r--src/plugins/platforms/xcb/nativepainting/qpaintengine_x11.cpp2
-rw-r--r--src/plugins/platforms/xcb/nativepainting/qpixmap_x11.cpp2
-rw-r--r--src/plugins/platforms/xcb/nativepainting/qtessellator.cpp1
-rw-r--r--src/plugins/platforms/xcb/qt_xlib_wrapper.c7
-rw-r--r--src/plugins/platforms/xcb/qt_xlib_wrapper.h17
-rw-r--r--src/plugins/platforms/xcb/qxcbatom.cpp33
-rw-r--r--src/plugins/platforms/xcb/qxcbatom.h343
-rw-r--r--src/plugins/platforms/xcb/qxcbbackingstore.cpp8
-rw-r--r--src/plugins/platforms/xcb/qxcbclipboard.cpp52
-rw-r--r--src/plugins/platforms/xcb/qxcbconnection.cpp81
-rw-r--r--src/plugins/platforms/xcb/qxcbconnection.h8
-rw-r--r--src/plugins/platforms/xcb/qxcbconnection_basic.cpp8
-rw-r--r--src/plugins/platforms/xcb/qxcbconnection_screens.cpp48
-rw-r--r--src/plugins/platforms/xcb/qxcbconnection_xi2.cpp209
-rw-r--r--src/plugins/platforms/xcb/qxcbcursor.cpp155
-rw-r--r--src/plugins/platforms/xcb/qxcbcursor.h12
-rw-r--r--src/plugins/platforms/xcb/qxcbcursorfont.h88
-rw-r--r--src/plugins/platforms/xcb/qxcbdrag.cpp122
-rw-r--r--src/plugins/platforms/xcb/qxcbdrag.h3
-rw-r--r--src/plugins/platforms/xcb/qxcbeventqueue.cpp4
-rw-r--r--src/plugins/platforms/xcb/qxcbimage.h1
-rw-r--r--src/plugins/platforms/xcb/qxcbintegration.cpp78
-rw-r--r--src/plugins/platforms/xcb/qxcbintegration.h5
-rw-r--r--src/plugins/platforms/xcb/qxcbkeyboard.cpp14
-rw-r--r--src/plugins/platforms/xcb/qxcbkeyboard.h8
-rw-r--r--src/plugins/platforms/xcb/qxcbmime.cpp35
-rw-r--r--src/plugins/platforms/xcb/qxcbnativeinterface.cpp2
-rw-r--r--src/plugins/platforms/xcb/qxcbscreen.cpp32
-rw-r--r--src/plugins/platforms/xcb/qxcbsessionmanager.cpp6
-rw-r--r--src/plugins/platforms/xcb/qxcbsystemtraytracker.cpp4
-rw-r--r--src/plugins/platforms/xcb/qxcbwindow.cpp560
-rw-r--r--src/plugins/platforms/xcb/qxcbwindow.h14
-rw-r--r--src/plugins/platforms/xcb/qxcbwmsupport.cpp6
-rw-r--r--src/plugins/platforms/xcb/qxcbxsettings.cpp6
-rw-r--r--src/plugins/platformthemes/CMakeLists.txt3
-rw-r--r--src/plugins/platformthemes/gtk3/CMakeLists.txt20
-rw-r--r--src/plugins/platformthemes/gtk3/qgtk3dialoghelpers.cpp109
-rw-r--r--src/plugins/platformthemes/gtk3/qgtk3dialoghelpers.h9
-rw-r--r--src/plugins/platformthemes/gtk3/qgtk3interface.cpp702
-rw-r--r--src/plugins/platformthemes/gtk3/qgtk3interface_p.h211
-rw-r--r--src/plugins/platformthemes/gtk3/qgtk3json.cpp404
-rw-r--r--src/plugins/platformthemes/gtk3/qgtk3json_p.h102
-rw-r--r--src/plugins/platformthemes/gtk3/qgtk3menu.cpp6
-rw-r--r--src/plugins/platformthemes/gtk3/qgtk3storage.cpp673
-rw-r--r--src/plugins/platformthemes/gtk3/qgtk3storage_p.h235
-rw-r--r--src/plugins/platformthemes/gtk3/qgtk3theme.cpp102
-rw-r--r--src/plugins/platformthemes/gtk3/qgtk3theme.h10
-rw-r--r--src/plugins/platformthemes/xdgdesktopportal/CMakeLists.txt7
-rw-r--r--src/plugins/platformthemes/xdgdesktopportal/qxdgdesktopportalfiledialog.cpp25
-rw-r--r--src/plugins/platformthemes/xdgdesktopportal/qxdgdesktopportaltheme.cpp26
-rw-r--r--src/plugins/platformthemes/xdgdesktopportal/qxdgdesktopportaltheme.h2
-rw-r--r--src/plugins/printsupport/CMakeLists.txt4
-rw-r--r--src/plugins/printsupport/cups/CMakeLists.txt10
-rw-r--r--src/plugins/printsupport/cups/qcupsprintengine.cpp24
-rw-r--r--src/plugins/printsupport/cups/qcupsprintersupport.cpp14
-rw-r--r--src/plugins/printsupport/cups/qppdprintdevice.cpp4
-rw-r--r--src/plugins/sqldrivers/.cmake.conf2
-rw-r--r--src/plugins/sqldrivers/CMakeLists.txt26
-rw-r--r--src/plugins/sqldrivers/configure.cmake9
-rw-r--r--src/plugins/sqldrivers/db2/CMakeLists.txt7
-rw-r--r--src/plugins/sqldrivers/db2/qsql_db2.cpp89
-rw-r--r--src/plugins/sqldrivers/ibase/CMakeLists.txt3
-rw-r--r--src/plugins/sqldrivers/ibase/qsql_ibase.cpp307
-rw-r--r--src/plugins/sqldrivers/mimer/CMakeLists.txt24
-rw-r--r--src/plugins/sqldrivers/mimer/README6
-rw-r--r--src/plugins/sqldrivers/mimer/main.cpp33
-rw-r--r--src/plugins/sqldrivers/mimer/mimer.json5
-rw-r--r--src/plugins/sqldrivers/mimer/qsql_mimer.cpp1610
-rw-r--r--src/plugins/sqldrivers/mimer/qsql_mimer.h50
-rw-r--r--src/plugins/sqldrivers/mysql/CMakeLists.txt8
-rw-r--r--src/plugins/sqldrivers/mysql/qsql_mysql.cpp572
-rw-r--r--src/plugins/sqldrivers/oci/CMakeLists.txt7
-rw-r--r--src/plugins/sqldrivers/oci/main.cpp2
-rw-r--r--src/plugins/sqldrivers/oci/qsql_oci.cpp136
-rw-r--r--src/plugins/sqldrivers/oci/qsql_oci_p.h3
-rw-r--r--src/plugins/sqldrivers/odbc/CMakeLists.txt10
-rw-r--r--src/plugins/sqldrivers/odbc/qsql_odbc.cpp1259
-rw-r--r--src/plugins/sqldrivers/psql/CMakeLists.txt10
-rw-r--r--src/plugins/sqldrivers/psql/qsql_psql.cpp171
-rw-r--r--src/plugins/sqldrivers/qt_cmdline.cmake6
-rw-r--r--src/plugins/sqldrivers/sqlite/CMakeLists.txt20
-rw-r--r--src/plugins/sqldrivers/sqlite/qsql_sqlite.cpp273
-rw-r--r--src/plugins/sqldrivers/sqlite/qsql_sqlite_p.h7
-rw-r--r--src/plugins/sqldrivers/sqlite/qsql_sqlite_vfs.cpp258
-rw-r--r--src/plugins/sqldrivers/sqlite/qsql_sqlite_vfs_p.h21
-rw-r--r--src/plugins/sqldrivers/sqlite/smain.cpp5
-rw-r--r--src/plugins/styles/CMakeLists.txt6
-rw-r--r--src/plugins/styles/android/CMakeLists.txt7
-rw-r--r--src/plugins/styles/android/qandroidstyle.cpp8
-rw-r--r--src/plugins/styles/mac/CMakeLists.txt9
-rw-r--r--src/plugins/styles/mac/main.mm2
-rw-r--r--src/plugins/styles/mac/qmacstyle_mac.mm147
-rw-r--r--src/plugins/styles/mac/qmacstyle_mac_p_p.h4
-rw-r--r--src/plugins/styles/modernwindows/CMakeLists.txt (renamed from src/plugins/styles/windowsvista/CMakeLists.txt)18
-rw-r--r--src/plugins/styles/modernwindows/main.cpp36
-rw-r--r--src/plugins/styles/modernwindows/modernwindowsstyles.json3
-rw-r--r--src/plugins/styles/modernwindows/qwindows11style.cpp2116
-rw-r--r--src/plugins/styles/modernwindows/qwindows11style_p.h69
-rw-r--r--src/plugins/styles/modernwindows/qwindowsthemedata.cpp59
-rw-r--r--src/plugins/styles/modernwindows/qwindowsthemedata_p.h183
-rw-r--r--src/plugins/styles/modernwindows/qwindowsvistaanimation.cpp15
-rw-r--r--src/plugins/styles/modernwindows/qwindowsvistaanimation_p.h49
-rw-r--r--src/plugins/styles/modernwindows/qwindowsvistastyle.cpp5012
-rw-r--r--src/plugins/styles/modernwindows/qwindowsvistastyle_p.h (renamed from src/plugins/styles/windowsvista/qwindowsvistastyle_p.h)8
-rw-r--r--src/plugins/styles/modernwindows/qwindowsvistastyle_p_p.h176
-rw-r--r--src/plugins/styles/windowsvista/main.cpp28
-rw-r--r--src/plugins/styles/windowsvista/qwindowsvistastyle.cpp2448
-rw-r--r--src/plugins/styles/windowsvista/qwindowsvistastyle_p_p.h167
-rw-r--r--src/plugins/styles/windowsvista/qwindowsxpstyle.cpp3669
-rw-r--r--src/plugins/styles/windowsvista/qwindowsxpstyle_p.h69
-rw-r--r--src/plugins/styles/windowsvista/qwindowsxpstyle_p_p.h309
-rw-r--r--src/plugins/styles/windowsvista/windowsvistastyle.json3
-rw-r--r--src/plugins/tls/CMakeLists.txt2
-rw-r--r--src/plugins/tls/certonly/CMakeLists.txt2
-rw-r--r--src/plugins/tls/openssl/CMakeLists.txt6
-rw-r--r--src/plugins/tls/openssl/qdtls_openssl.cpp18
-rw-r--r--src/plugins/tls/openssl/qsslcontext_openssl.cpp12
-rw-r--r--src/plugins/tls/openssl/qssldiffiehellmanparameters_openssl.cpp3
-rw-r--r--src/plugins/tls/openssl/qsslsocket_openssl_symbols.cpp727
-rw-r--r--src/plugins/tls/openssl/qsslsocket_openssl_symbols_p.h12
-rw-r--r--src/plugins/tls/openssl/qtls_openssl.cpp18
-rw-r--r--src/plugins/tls/openssl/qtls_openssl_p.h2
-rw-r--r--src/plugins/tls/openssl/qtlsbackend_openssl.cpp64
-rw-r--r--src/plugins/tls/openssl/qtlsbackend_openssl_p.h5
-rw-r--r--src/plugins/tls/openssl/qtlskey_openssl.cpp12
-rw-r--r--src/plugins/tls/openssl/qwindowscarootfetcher.cpp2
-rw-r--r--src/plugins/tls/openssl/qx509_openssl.cpp17
-rw-r--r--src/plugins/tls/schannel/CMakeLists.txt4
-rw-r--r--src/plugins/tls/schannel/qtls_schannel.cpp544
-rw-r--r--src/plugins/tls/schannel/qtls_schannel_p.h1
-rw-r--r--src/plugins/tls/schannel/qtlsbackend_schannel_p.h1
-rw-r--r--src/plugins/tls/schannel/qx509_schannel.cpp170
-rw-r--r--src/plugins/tls/schannel/qx509_schannel_p.h4
-rw-r--r--src/plugins/tls/securetransport/CMakeLists.txt2
-rw-r--r--src/plugins/tls/securetransport/qtls_st.cpp5
-rw-r--r--src/plugins/tls/securetransport/qtls_st_p.h5
-rw-r--r--src/plugins/tls/securetransport/qtlsbackend_st.cpp2
-rw-r--r--src/plugins/tls/shared/qasn1element.cpp40
-rw-r--r--src/plugins/tls/shared/qsslsocket_mac_shared.cpp64
-rw-r--r--src/plugins/tls/shared/qwincrypt_p.h10
-rw-r--r--src/plugins/tls/shared/qx509_generic.cpp8
-rw-r--r--src/plugins/tracing/CMakeLists.txt32
-rw-r--r--src/plugins/tracing/metadata_template.txt77
-rw-r--r--src/plugins/tracing/qctflib.cpp447
-rw-r--r--src/plugins/tracing/qctflib_p.h125
-rw-r--r--src/plugins/tracing/qctfplugin.cpp63
-rw-r--r--src/plugins/tracing/qctfplugin_p.h28
-rw-r--r--src/plugins/tracing/qctfserver.cpp404
-rw-r--r--src/plugins/tracing/qctfserver_p.h194
-rw-r--r--src/plugins/tracing/trace.json3
591 files changed, 35111 insertions, 19143 deletions
diff --git a/src/plugins/CMakeLists.txt b/src/plugins/CMakeLists.txt
index cce7569eab..d26d2aae44 100644
--- a/src/plugins/CMakeLists.txt
+++ b/src/plugins/CMakeLists.txt
@@ -1,7 +1,5 @@
# Copyright (C) 2022 The Qt Company Ltd.
-# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-
-# Generated from plugins.pro.
+# SPDX-License-Identifier: BSD-3-Clause
if(TARGET Qt::Sql)
add_subdirectory(sqldrivers)
@@ -27,3 +25,6 @@ if (TARGET Qt::Network)
add_subdirectory(networkinformation)
add_subdirectory(tls)
endif()
+if (QT_FEATURE_ctf AND TARGET Qt::Network)
+ add_subdirectory(tracing)
+endif()
diff --git a/src/plugins/doc/snippets/code/src_plugins_platforms_qnx_qqnxwindow.cpp b/src/plugins/doc/snippets/code/src_plugins_platforms_qnx_qqnxwindow.cpp
index f0071aeafe..6b6e2966b2 100644
--- a/src/plugins/doc/snippets/code/src_plugins_platforms_qnx_qqnxwindow.cpp
+++ b/src/plugins/doc/snippets/code/src_plugins_platforms_qnx_qqnxwindow.cpp
@@ -1,5 +1,5 @@
// Copyright (C) 2018 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
//! [0]
QQuickView *view = new QQuickView(parent);
diff --git a/src/plugins/generic/CMakeLists.txt b/src/plugins/generic/CMakeLists.txt
index 84bccb04a6..6d3cf2a925 100644
--- a/src/plugins/generic/CMakeLists.txt
+++ b/src/plugins/generic/CMakeLists.txt
@@ -1,7 +1,5 @@
# Copyright (C) 2022 The Qt Company Ltd.
-# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-
-# Generated from generic.pro.
+# SPDX-License-Identifier: BSD-3-Clause
if(QT_FEATURE_evdev)
add_subdirectory(evdevmouse)
@@ -21,6 +19,6 @@ if(QT_FEATURE_libinput)
add_subdirectory(libinput)
endif()
if(FREEBSD)
- # add_subdirectory(bsdkeyboard) # special case TODO
- # add_subdirectory(bsdmouse) # special case TODO
+ # add_subdirectory(bsdkeyboard) # TODO: QTBUG-112770
+ # add_subdirectory(bsdmouse) # TODO: QTBUG-112770
endif()
diff --git a/src/plugins/generic/evdevkeyboard/CMakeLists.txt b/src/plugins/generic/evdevkeyboard/CMakeLists.txt
index 56118e020f..53be12face 100644
--- a/src/plugins/generic/evdevkeyboard/CMakeLists.txt
+++ b/src/plugins/generic/evdevkeyboard/CMakeLists.txt
@@ -1,7 +1,5 @@
# Copyright (C) 2022 The Qt Company Ltd.
-# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-
-# Generated from evdevkeyboard.pro.
+# SPDX-License-Identifier: BSD-3-Clause
#####################################################################
## QEvdevKeyboardPlugin Plugin:
@@ -20,7 +18,3 @@ qt_internal_add_plugin(QEvdevKeyboardPlugin
Qt::GuiPrivate
Qt::InputSupportPrivate
)
-
-#### Keys ignored in scope 1:.:.:evdevkeyboard.pro:<TRUE>:
-# OTHER_FILES = "evdevkeyboard.json"
-# PLUGIN_EXTENDS = "-"
diff --git a/src/plugins/generic/evdevkeyboard/main.cpp b/src/plugins/generic/evdevkeyboard/main.cpp
index 00d4c216d8..2931fea907 100644
--- a/src/plugins/generic/evdevkeyboard/main.cpp
+++ b/src/plugins/generic/evdevkeyboard/main.cpp
@@ -27,7 +27,8 @@ QObject* QEvdevKeyboardPlugin::create(const QString &key,
{
if (!key.compare(QLatin1String("EvdevKeyboard"), Qt::CaseInsensitive))
return new QEvdevKeyboardManager(key, specification);
- return 0;
+
+ return nullptr;
}
QT_END_NAMESPACE
diff --git a/src/plugins/generic/evdevmouse/CMakeLists.txt b/src/plugins/generic/evdevmouse/CMakeLists.txt
index 0071ce2641..e0836ecccc 100644
--- a/src/plugins/generic/evdevmouse/CMakeLists.txt
+++ b/src/plugins/generic/evdevmouse/CMakeLists.txt
@@ -1,7 +1,5 @@
# Copyright (C) 2022 The Qt Company Ltd.
-# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-
-# Generated from evdevmouse.pro.
+# SPDX-License-Identifier: BSD-3-Clause
#####################################################################
## QEvdevMousePlugin Plugin:
@@ -20,7 +18,3 @@ qt_internal_add_plugin(QEvdevMousePlugin
Qt::GuiPrivate
Qt::InputSupportPrivate
)
-
-#### Keys ignored in scope 1:.:.:evdevmouse.pro:<TRUE>:
-# OTHER_FILES = "evdevmouse.json"
-# PLUGIN_EXTENDS = "-"
diff --git a/src/plugins/generic/evdevmouse/main.cpp b/src/plugins/generic/evdevmouse/main.cpp
index dd24c85d81..9629d2c927 100644
--- a/src/plugins/generic/evdevmouse/main.cpp
+++ b/src/plugins/generic/evdevmouse/main.cpp
@@ -27,7 +27,8 @@ QObject* QEvdevMousePlugin::create(const QString &key,
{
if (!key.compare(QLatin1String("EvdevMouse"), Qt::CaseInsensitive))
return new QEvdevMouseManager(key, specification);
- return 0;
+
+ return nullptr;
}
QT_END_NAMESPACE
diff --git a/src/plugins/generic/evdevtablet/CMakeLists.txt b/src/plugins/generic/evdevtablet/CMakeLists.txt
index 867fb069f2..ef21469f70 100644
--- a/src/plugins/generic/evdevtablet/CMakeLists.txt
+++ b/src/plugins/generic/evdevtablet/CMakeLists.txt
@@ -1,7 +1,5 @@
# Copyright (C) 2022 The Qt Company Ltd.
-# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-
-# Generated from evdevtablet.pro.
+# SPDX-License-Identifier: BSD-3-Clause
#####################################################################
## QEvdevTabletPlugin Plugin:
@@ -20,7 +18,3 @@ qt_internal_add_plugin(QEvdevTabletPlugin
Qt::GuiPrivate
Qt::InputSupportPrivate
)
-
-#### Keys ignored in scope 1:.:.:evdevtablet.pro:<TRUE>:
-# OTHER_FILES = "evdevtablet.json"
-# PLUGIN_EXTENDS = "-"
diff --git a/src/plugins/generic/evdevtablet/main.cpp b/src/plugins/generic/evdevtablet/main.cpp
index 4ac58333e9..6313c09921 100644
--- a/src/plugins/generic/evdevtablet/main.cpp
+++ b/src/plugins/generic/evdevtablet/main.cpp
@@ -27,7 +27,7 @@ QObject* QEvdevTabletPlugin::create(const QString &key,
if (!key.compare(QLatin1String("EvdevTablet"), Qt::CaseInsensitive))
return new QEvdevTabletManager(key, spec);
- return 0;
+ return nullptr;
}
QT_END_NAMESPACE
diff --git a/src/plugins/generic/evdevtouch/CMakeLists.txt b/src/plugins/generic/evdevtouch/CMakeLists.txt
index 6477de192e..f3f489586a 100644
--- a/src/plugins/generic/evdevtouch/CMakeLists.txt
+++ b/src/plugins/generic/evdevtouch/CMakeLists.txt
@@ -1,7 +1,5 @@
# Copyright (C) 2022 The Qt Company Ltd.
-# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-
-# Generated from evdevtouch.pro.
+# SPDX-License-Identifier: BSD-3-Clause
#####################################################################
## QEvdevTouchScreenPlugin Plugin:
@@ -20,7 +18,3 @@ qt_internal_add_plugin(QEvdevTouchScreenPlugin
Qt::GuiPrivate
Qt::InputSupportPrivate
)
-
-#### Keys ignored in scope 1:.:.:evdevtouch.pro:<TRUE>:
-# OTHER_FILES = "evdevtouch.json"
-# PLUGIN_EXTENDS = "-"
diff --git a/src/plugins/generic/evdevtouch/main.cpp b/src/plugins/generic/evdevtouch/main.cpp
index 958a69f032..f39f9645bc 100644
--- a/src/plugins/generic/evdevtouch/main.cpp
+++ b/src/plugins/generic/evdevtouch/main.cpp
@@ -27,7 +27,7 @@ QObject* QEvdevTouchScreenPlugin::create(const QString &key,
if (!key.compare(QLatin1String("EvdevTouch"), Qt::CaseInsensitive))
return new QEvdevTouchManager(key, spec);
- return 0;
+ return nullptr;
}
QT_END_NAMESPACE
diff --git a/src/plugins/generic/libinput/CMakeLists.txt b/src/plugins/generic/libinput/CMakeLists.txt
index 3bac413c38..f92b3d5353 100644
--- a/src/plugins/generic/libinput/CMakeLists.txt
+++ b/src/plugins/generic/libinput/CMakeLists.txt
@@ -1,7 +1,5 @@
# Copyright (C) 2022 The Qt Company Ltd.
-# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-
-# Generated from libinput.pro.
+# SPDX-License-Identifier: BSD-3-Clause
#####################################################################
## QLibInputPlugin Plugin:
@@ -20,7 +18,3 @@ qt_internal_add_plugin(QLibInputPlugin
Qt::GuiPrivate
Qt::InputSupportPrivate
)
-
-#### Keys ignored in scope 1:.:.:libinput.pro:<TRUE>:
-# OTHER_FILES = "libinput.json"
-# PLUGIN_EXTENDS = "-"
diff --git a/src/plugins/generic/libinput/main.cpp b/src/plugins/generic/libinput/main.cpp
index 3005868b57..a191fd3d9b 100644
--- a/src/plugins/generic/libinput/main.cpp
+++ b/src/plugins/generic/libinput/main.cpp
@@ -20,7 +20,7 @@ QObject *QLibInputPlugin::create(const QString &key, const QString &specificatio
if (!key.compare(QLatin1String("libinput"), Qt::CaseInsensitive))
return new QLibInputHandler(key, specification);
- return 0;
+ return nullptr;
}
QT_END_NAMESPACE
diff --git a/src/plugins/generic/tslib/CMakeLists.txt b/src/plugins/generic/tslib/CMakeLists.txt
index fe3e8afff5..8512133799 100644
--- a/src/plugins/generic/tslib/CMakeLists.txt
+++ b/src/plugins/generic/tslib/CMakeLists.txt
@@ -1,9 +1,7 @@
# Copyright (C) 2022 The Qt Company Ltd.
-# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+# SPDX-License-Identifier: BSD-3-Clause
-# Generated from tslib.pro.
-
-qt_find_package(Tslib) # special case
+qt_find_package(Tslib)
#####################################################################
## QTsLibPlugin Plugin:
@@ -23,7 +21,3 @@ qt_internal_add_plugin(QTsLibPlugin
Qt::GuiPrivate
Qt::InputSupportPrivate
)
-
-#### Keys ignored in scope 1:.:.:tslib.pro:<TRUE>:
-# OTHER_FILES = "tslib.json"
-# PLUGIN_EXTENDS = "-"
diff --git a/src/plugins/generic/tslib/main.cpp b/src/plugins/generic/tslib/main.cpp
index bb3041eb56..c2f4ccb105 100644
--- a/src/plugins/generic/tslib/main.cpp
+++ b/src/plugins/generic/tslib/main.cpp
@@ -22,7 +22,7 @@ QObject* QTsLibPlugin::create(const QString &key,
|| !key.compare(QLatin1String("TslibRaw"), Qt::CaseInsensitive))
return new QTsLibMouseHandler(key, specification);
- return 0;
+ return nullptr;
}
QT_END_NAMESPACE
diff --git a/src/plugins/generic/tuiotouch/CMakeLists.txt b/src/plugins/generic/tuiotouch/CMakeLists.txt
index 379c4fc7b5..b03ed9e360 100644
--- a/src/plugins/generic/tuiotouch/CMakeLists.txt
+++ b/src/plugins/generic/tuiotouch/CMakeLists.txt
@@ -1,7 +1,5 @@
# Copyright (C) 2022 The Qt Company Ltd.
-# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-
-# Generated from tuiotouch.pro.
+# SPDX-License-Identifier: BSD-3-Clause
#####################################################################
## QTuioTouchPlugin Plugin:
@@ -27,7 +25,3 @@ qt_internal_add_plugin(QTuioTouchPlugin
Qt::GuiPrivate
Qt::Network
)
-
-#### Keys ignored in scope 1:.:.:tuiotouch.pro:<TRUE>:
-# OTHER_FILES = "tuiotouch.json"
-# PLUGIN_EXTENDS = "-"
diff --git a/src/plugins/generic/tuiotouch/main.cpp b/src/plugins/generic/tuiotouch/main.cpp
index 30f3af3597..037a5d64a0 100644
--- a/src/plugins/generic/tuiotouch/main.cpp
+++ b/src/plugins/generic/tuiotouch/main.cpp
@@ -30,7 +30,7 @@ QObject* QTuioTouchPlugin::create(const QString &key,
if (!key.compare(QLatin1String("TuioTouch"), Qt::CaseInsensitive))
return new QTuioHandler(spec);
- return 0;
+ return nullptr;
}
QT_END_NAMESPACE
diff --git a/src/plugins/generic/tuiotouch/qtuiohandler.cpp b/src/plugins/generic/tuiotouch/qtuiohandler.cpp
index 3e67050589..2815368e84 100644
--- a/src/plugins/generic/tuiotouch/qtuiohandler.cpp
+++ b/src/plugins/generic/tuiotouch/qtuiohandler.cpp
@@ -39,7 +39,7 @@ QTuioHandler::QTuioHandler(const QString &specification)
bool invertx = false;
bool inverty = false;
- for (int i = 0; i < args.count(); ++i) {
+ for (int i = 0; i < args.size(); ++i) {
if (args.at(i).startsWith("udp=")) {
QString portString = args.at(i).section('=', 1, 1);
portNumber = portString.toInt();
@@ -59,6 +59,7 @@ QTuioHandler::QTuioHandler(const QString &specification)
case 180:
case 270:
rotationAngle = argValue;
+ break;
default:
break;
}
@@ -136,10 +137,10 @@ void QTuioHandler::processPackets()
messages.push_back(msg);
}
- for (const QOscMessage &message : qAsConst(messages)) {
+ for (const QOscMessage &message : std::as_const(messages)) {
if (message.addressPattern() == "/tuio/2Dcur") {
QList<QVariant> arguments = message.arguments();
- if (arguments.count() == 0) {
+ if (arguments.size() == 0) {
qCWarning(lcTuioHandler, "Ignoring TUIO message with no arguments");
continue;
}
@@ -159,7 +160,7 @@ void QTuioHandler::processPackets()
}
} else if (message.addressPattern() == "/tuio/2Dobj") {
QList<QVariant> arguments = message.arguments();
- if (arguments.count() == 0) {
+ if (arguments.size() == 0) {
qCWarning(lcTuioHandler, "Ignoring TUIO message with no arguments");
continue;
}
@@ -188,8 +189,8 @@ void QTuioHandler::processPackets()
void QTuioHandler::process2DCurSource(const QOscMessage &message)
{
QList<QVariant> arguments = message.arguments();
- if (arguments.count() != 2) {
- qCWarning(lcTuioSource) << "Ignoring malformed TUIO source message: " << arguments.count();
+ if (arguments.size() != 2) {
+ qCWarning(lcTuioSource) << "Ignoring malformed TUIO source message: " << arguments.size();
return;
}
@@ -214,7 +215,7 @@ void QTuioHandler::process2DCurAlive(const QOscMessage &message)
QMap<int, QTuioCursor> oldActiveCursors = m_activeCursors;
QMap<int, QTuioCursor> newActiveCursors;
- for (int i = 1; i < arguments.count(); ++i) {
+ for (int i = 1; i < arguments.size(); ++i) {
if (QMetaType::Type(arguments.at(i).userType()) != QMetaType::Int) {
qCWarning(lcTuioHandler) << "Ignoring malformed TUIO alive message (bad argument on position" << i << arguments << ')';
return;
@@ -255,8 +256,8 @@ void QTuioHandler::process2DCurAlive(const QOscMessage &message)
void QTuioHandler::process2DCurSet(const QOscMessage &message)
{
QList<QVariant> arguments = message.arguments();
- if (arguments.count() < 7) {
- qCWarning(lcTuioSet) << "Ignoring malformed TUIO set message with too few arguments: " << arguments.count();
+ if (arguments.size() < 7) {
+ qCWarning(lcTuioSet) << "Ignoring malformed TUIO set message with too few arguments: " << arguments.size();
return;
}
@@ -326,7 +327,7 @@ void QTuioHandler::process2DCurFseq(const QOscMessage &message)
Q_UNUSED(message); // TODO: do we need to do anything with the frame id?
QWindow *win = QGuiApplication::focusWindow();
- if (!win && QGuiApplication::topLevelWindows().length() > 0 && forceDelivery)
+ if (!win && QGuiApplication::topLevelWindows().size() > 0 && forceDelivery)
win = QGuiApplication::topLevelWindows().at(0);
if (!win)
@@ -335,12 +336,12 @@ void QTuioHandler::process2DCurFseq(const QOscMessage &message)
QList<QWindowSystemInterface::TouchPoint> tpl;
tpl.reserve(m_activeCursors.size() + m_deadCursors.size());
- for (const QTuioCursor &tc : qAsConst(m_activeCursors)) {
+ for (const QTuioCursor &tc : std::as_const(m_activeCursors)) {
QWindowSystemInterface::TouchPoint tp = cursorToTouchPoint(tc, win);
tpl.append(tp);
}
- for (const QTuioCursor &tc : qAsConst(m_deadCursors)) {
+ for (const QTuioCursor &tc : std::as_const(m_deadCursors)) {
QWindowSystemInterface::TouchPoint tp = cursorToTouchPoint(tc, win);
tp.state = QEventPoint::State::Released;
tpl.append(tp);
@@ -353,8 +354,8 @@ void QTuioHandler::process2DCurFseq(const QOscMessage &message)
void QTuioHandler::process2DObjSource(const QOscMessage &message)
{
QList<QVariant> arguments = message.arguments();
- if (arguments.count() != 2) {
- qCWarning(lcTuioSource, ) << "Ignoring malformed TUIO source message: " << arguments.count();
+ if (arguments.size() != 2) {
+ qCWarning(lcTuioSource ) << "Ignoring malformed TUIO source message: " << arguments.size();
return;
}
@@ -379,7 +380,7 @@ void QTuioHandler::process2DObjAlive(const QOscMessage &message)
QMap<int, QTuioToken> oldActiveTokens = m_activeTokens;
QMap<int, QTuioToken> newActiveTokens;
- for (int i = 1; i < arguments.count(); ++i) {
+ for (int i = 1; i < arguments.size(); ++i) {
if (QMetaType::Type(arguments.at(i).userType()) != QMetaType::Int) {
qCWarning(lcTuioHandler) << "Ignoring malformed TUIO alive message (bad argument on position" << i << arguments << ')';
return;
@@ -420,8 +421,8 @@ void QTuioHandler::process2DObjAlive(const QOscMessage &message)
void QTuioHandler::process2DObjSet(const QOscMessage &message)
{
QList<QVariant> arguments = message.arguments();
- if (arguments.count() < 7) {
- qCWarning(lcTuioSet) << "Ignoring malformed TUIO set message with too few arguments: " << arguments.count();
+ if (arguments.size() < 7) {
+ qCWarning(lcTuioSet) << "Ignoring malformed TUIO set message with too few arguments: " << arguments.size();
return;
}
@@ -499,7 +500,7 @@ void QTuioHandler::process2DObjFseq(const QOscMessage &message)
Q_UNUSED(message); // TODO: do we need to do anything with the frame id?
QWindow *win = QGuiApplication::focusWindow();
- if (!win && QGuiApplication::topLevelWindows().length() > 0 && forceDelivery)
+ if (!win && QGuiApplication::topLevelWindows().size() > 0 && forceDelivery)
win = QGuiApplication::topLevelWindows().at(0);
if (!win)
@@ -508,12 +509,12 @@ void QTuioHandler::process2DObjFseq(const QOscMessage &message)
QList<QWindowSystemInterface::TouchPoint> tpl;
tpl.reserve(m_activeTokens.size() + m_deadTokens.size());
- for (const QTuioToken & t : qAsConst(m_activeTokens)) {
+ for (const QTuioToken & t : std::as_const(m_activeTokens)) {
QWindowSystemInterface::TouchPoint tp = tokenToTouchPoint(t, win);
tpl.append(tp);
}
- for (const QTuioToken & t : qAsConst(m_deadTokens)) {
+ for (const QTuioToken & t : std::as_const(m_deadTokens)) {
QWindowSystemInterface::TouchPoint tp = tokenToTouchPoint(t, win);
tp.state = QEventPoint::State::Released;
tp.velocity = QVector2D();
diff --git a/src/plugins/imageformats/CMakeLists.txt b/src/plugins/imageformats/CMakeLists.txt
index 17f70c861d..c5ab716d00 100644
--- a/src/plugins/imageformats/CMakeLists.txt
+++ b/src/plugins/imageformats/CMakeLists.txt
@@ -1,7 +1,5 @@
# Copyright (C) 2022 The Qt Company Ltd.
-# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-
-# Generated from imageformats.pro.
+# SPDX-License-Identifier: BSD-3-Clause
if(QT_FEATURE_ico)
add_subdirectory(ico)
diff --git a/src/plugins/imageformats/gif/CMakeLists.txt b/src/plugins/imageformats/gif/CMakeLists.txt
index 05cb8b45ec..b56859a264 100644
--- a/src/plugins/imageformats/gif/CMakeLists.txt
+++ b/src/plugins/imageformats/gif/CMakeLists.txt
@@ -1,7 +1,5 @@
# Copyright (C) 2022 The Qt Company Ltd.
-# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-
-# Generated from gif.pro.
+# SPDX-License-Identifier: BSD-3-Clause
#####################################################################
## QGifPlugin Plugin:
@@ -13,10 +11,7 @@ qt_internal_add_plugin(QGifPlugin
SOURCES
main.cpp
qgifhandler.cpp qgifhandler_p.h
- LIBRARIES # special case
- Qt::Gui # special case
- Qt::GuiPrivate # special case
+ LIBRARIES
+ Qt::Gui
+ Qt::GuiPrivate
)
-
-#### Keys ignored in scope 1:.:.:gif.pro:<TRUE>:
-# OTHER_FILES = "gif.json"
diff --git a/src/plugins/imageformats/gif/qgifhandler.cpp b/src/plugins/imageformats/gif/qgifhandler.cpp
index add6b64855..8ad4ff7510 100644
--- a/src/plugins/imageformats/gif/qgifhandler.cpp
+++ b/src/plugins/imageformats/gif/qgifhandler.cpp
@@ -1146,9 +1146,9 @@ QVariant QGifHandler::option(ImageOption option) const
}
// before the first frame is read, or we have an empty data stream
if (frameNumber == -1)
- return (imageSizes.count() > 0) ? QVariant(imageSizes.at(0)) : QVariant();
+ return (imageSizes.size() > 0) ? QVariant(imageSizes.at(0)) : QVariant();
// after the last frame has been read, the next size is undefined
- if (frameNumber >= imageSizes.count() - 1)
+ if (frameNumber >= imageSizes.size() - 1)
return QVariant();
// and the last case: the size of the next frame
return imageSizes.at(frameNumber + 1);
@@ -1175,7 +1175,7 @@ int QGifHandler::imageCount() const
QGIFFormat::scan(device(), &imageSizes, &loopCnt);
scanIsCached = true;
}
- return imageSizes.count();
+ return imageSizes.size();
}
int QGifHandler::loopCount() const
diff --git a/src/plugins/imageformats/ico/CMakeLists.txt b/src/plugins/imageformats/ico/CMakeLists.txt
index c13aee0838..c9cd0f0d40 100644
--- a/src/plugins/imageformats/ico/CMakeLists.txt
+++ b/src/plugins/imageformats/ico/CMakeLists.txt
@@ -1,7 +1,5 @@
# Copyright (C) 2022 The Qt Company Ltd.
-# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-
-# Generated from ico.pro.
+# SPDX-License-Identifier: BSD-3-Clause
#####################################################################
## QICOPlugin Plugin:
@@ -17,7 +15,5 @@ qt_internal_add_plugin(QICOPlugin
Qt::Core
Qt::CorePrivate
Qt::Gui
+ NO_UNITY_BUILD
)
-
-#### Keys ignored in scope 1:.:.:ico.pro:<TRUE>:
-# OTHER_FILES = "ico.json"
diff --git a/src/plugins/imageformats/ico/qicohandler.cpp b/src/plugins/imageformats/ico/qicohandler.cpp
index 496e927919..18b39766f5 100644
--- a/src/plugins/imageformats/ico/qicohandler.cpp
+++ b/src/plugins/imageformats/ico/qicohandler.cpp
@@ -499,7 +499,7 @@ QImage ICOReader::iconAt(int index)
if (!image.isNull()) {
readBMP(image);
if (!image.isNull()) {
- if (icoAttrib.depth == 32) {
+ if (icoAttrib.nbits == 32) {
img = std::move(image).convertToFormat(QImage::Format_ARGB32_Premultiplied);
} else {
QImage mask(image.width(), image.height(), QImage::Format_Mono);
@@ -565,14 +565,14 @@ bool ICOReader::write(QIODevice *device, const QList<QImage> &images)
{
bool retValue = false;
- if (images.count()) {
+ if (images.size()) {
qint64 origOffset = device->pos();
ICONDIR id;
id.idReserved = 0;
id.idType = 1;
- id.idCount = images.count();
+ id.idCount = images.size();
ICONDIRENTRY * entries = new ICONDIRENTRY[id.idCount];
BMP_INFOHDR * bmpHeaders = new BMP_INFOHDR[id.idCount];
@@ -735,6 +735,8 @@ bool QtIcoHandler::supportsOption(ImageOption option) const
*/
bool QtIcoHandler::canRead() const
{
+ if (knownCanRead)
+ return true;
bool bCanRead = false;
QIODevice *device = QImageIOHandler::device();
if (device) {
@@ -744,6 +746,7 @@ bool QtIcoHandler::canRead() const
} else {
qCWarning(lcIco, "QtIcoHandler::canRead() called with no device");
}
+ knownCanRead = bCanRead;
return bCanRead;
}
diff --git a/src/plugins/imageformats/ico/qicohandler.h b/src/plugins/imageformats/ico/qicohandler.h
index 0fe251ab60..61c3eea465 100644
--- a/src/plugins/imageformats/ico/qicohandler.h
+++ b/src/plugins/imageformats/ico/qicohandler.h
@@ -30,7 +30,7 @@ public:
private:
int m_currentIconIndex;
ICOReader *m_pICOReader;
-
+ mutable bool knownCanRead = false;
};
QT_END_NAMESPACE
diff --git a/src/plugins/imageformats/jpeg/CMakeLists.txt b/src/plugins/imageformats/jpeg/CMakeLists.txt
index 94499f2f8d..6b077a7647 100644
--- a/src/plugins/imageformats/jpeg/CMakeLists.txt
+++ b/src/plugins/imageformats/jpeg/CMakeLists.txt
@@ -1,7 +1,5 @@
# Copyright (C) 2022 The Qt Company Ltd.
-# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-
-# Generated from jpeg.pro.
+# SPDX-License-Identifier: BSD-3-Clause
qt_find_package(WrapJpeg PROVIDED_TARGETS WrapJpeg::WrapJpeg)
diff --git a/src/plugins/imageformats/jpeg/qjpeghandler.cpp b/src/plugins/imageformats/jpeg/qjpeghandler.cpp
index eebc5940ce..59b73587c5 100644
--- a/src/plugins/imageformats/jpeg/qjpeghandler.cpp
+++ b/src/plugins/imageformats/jpeg/qjpeghandler.cpp
@@ -49,6 +49,7 @@ extern "C" {
static void my_error_exit (j_common_ptr cinfo)
{
+ (*cinfo->err->output_message)(cinfo);
my_error_mgr* myerr = (my_error_mgr*) cinfo->err;
longjmp(myerr->setjmp_buffer, 1);
}
@@ -171,9 +172,14 @@ inline static bool read_jpeg_format(QImage::Format &format, j_decompress_ptr cin
format = QImage::Format_Grayscale8;
break;
case 3:
- case 4:
format = QImage::Format_RGB32;
break;
+ case 4:
+ if (cinfo->out_color_space == JCS_CMYK)
+ format = QImage::Format_CMYK8888;
+ else
+ format = QImage::Format_RGB32;
+ break;
default:
result = false;
break;
@@ -191,9 +197,14 @@ static bool ensureValidImage(QImage *dest, struct jpeg_decompress_struct *info,
format = QImage::Format_Grayscale8;
break;
case 3:
- case 4:
format = QImage::Format_RGB32;
break;
+ case 4:
+ if (info->out_color_space == JCS_CMYK)
+ format = QImage::Format_CMYK8888;
+ else
+ format = QImage::Format_RGB32;
+ break;
default:
return false; // unsupported format
}
@@ -205,7 +216,7 @@ static bool read_jpeg_image(QImage *outImage,
QSize scaledSize, QRect scaledClipRect,
QRect clipRect, int quality,
Rgb888ToRgb32Converter converter,
- j_decompress_ptr info, struct my_error_mgr* err )
+ j_decompress_ptr info, struct my_error_mgr* err, bool invertCMYK)
{
if (!setjmp(err->setjmp_buffer)) {
// -1 means default quality.
@@ -347,14 +358,15 @@ static bool read_jpeg_image(QImage *outImage,
QRgb *out = (QRgb*)outImage->scanLine(y);
converter(out, in, clip.width());
} else if (info->out_color_space == JCS_CMYK) {
- // Convert CMYK->RGB.
uchar *in = rows[0] + clip.x() * 4;
- QRgb *out = (QRgb*)outImage->scanLine(y);
- for (int i = 0; i < clip.width(); ++i) {
- int k = in[3];
- *out++ = qRgb(k * in[0] / 255, k * in[1] / 255,
- k * in[2] / 255);
- in += 4;
+ quint32 *out = (quint32*)outImage->scanLine(y);
+ if (invertCMYK) {
+ for (int i = 0; i < clip.width(); ++i) {
+ *out++ = 0xffffffffu - (in[0] | in[1] << 8 | in[2] << 16 | in[3] << 24);
+ in += 4;
+ }
+ } else {
+ memcpy(out, in, clip.width() * 4);
}
} else if (info->output_components == 1) {
// Grayscale.
@@ -458,7 +470,7 @@ static inline void set_text(const QImage &image, j_compress_ptr cinfo, const QSt
if (!comment.isEmpty())
comment += ": ";
comment += it.value().toUtf8();
- if (comment.length() > maxMarkerSize)
+ if (comment.size() > maxMarkerSize)
comment.truncate(maxMarkerSize);
jpeg_write_marker(cinfo, JPEG_COM, (const JOCTET *)comment.constData(), comment.size());
}
@@ -492,7 +504,8 @@ static bool do_write_jpeg_image(struct jpeg_compress_struct &cinfo,
int sourceQuality,
const QString &description,
bool optimize,
- bool progressive)
+ bool progressive,
+ bool invertCMYK)
{
bool success = false;
const QList<QRgb> cmap = image.colorTable();
@@ -532,10 +545,15 @@ static bool do_write_jpeg_image(struct jpeg_compress_struct &cinfo,
cinfo.in_color_space = gray ? JCS_GRAYSCALE : JCS_RGB;
break;
case QImage::Format_Grayscale8:
+ case QImage::Format_Grayscale16:
gray = true;
cinfo.input_components = 1;
cinfo.in_color_space = JCS_GRAYSCALE;
break;
+ case QImage::Format_CMYK8888:
+ cinfo.input_components = 4;
+ cinfo.in_color_space = JCS_CMYK;
+ break;
default:
cinfo.input_components = 3;
cinfo.in_color_space = JCS_RGB;
@@ -568,7 +586,7 @@ static bool do_write_jpeg_image(struct jpeg_compress_struct &cinfo,
jpeg_start_compress(&cinfo, TRUE);
set_text(image, &cinfo, description);
- if (cinfo.in_color_space == JCS_RGB)
+ if (cinfo.in_color_space == JCS_RGB || cinfo.in_color_space == JCS_CMYK)
write_icc_profile(image, &cinfo);
row_pointer[0] = new uchar[cinfo.image_width*cinfo.input_components];
@@ -630,6 +648,12 @@ static bool do_write_jpeg_image(struct jpeg_compress_struct &cinfo,
case QImage::Format_Grayscale8:
memcpy(row, image.constScanLine(cinfo.next_scanline), w);
break;
+ case QImage::Format_Grayscale16:
+ {
+ QImage rowImg = image.copy(0, cinfo.next_scanline, w, 1).convertToFormat(QImage::Format_Grayscale8);
+ memcpy(row, rowImg.constScanLine(0), w);
+ }
+ break;
case QImage::Format_RGB888:
memcpy(row, image.constScanLine(cinfo.next_scanline), w * 3);
break;
@@ -646,6 +670,17 @@ static bool do_write_jpeg_image(struct jpeg_compress_struct &cinfo,
}
}
break;
+ case QImage::Format_CMYK8888: {
+ auto *cmykIn = reinterpret_cast<const quint32 *>(image.constScanLine(cinfo.next_scanline));
+ auto *cmykOut = reinterpret_cast<quint32 *>(row);
+ if (invertCMYK) {
+ for (int i = 0; i < w; ++i)
+ cmykOut[i] = 0xffffffffu - cmykIn[i];
+ } else {
+ memcpy(cmykOut, cmykIn, w * 4);
+ }
+ break;
+ }
default:
{
// (Testing shows that this way is actually faster than converting to RGB888 + memcpy)
@@ -681,7 +716,8 @@ static bool write_jpeg_image(const QImage &image,
int sourceQuality,
const QString &description,
bool optimize,
- bool progressive)
+ bool progressive,
+ bool invertCMYK)
{
// protect these objects from the setjmp/longjmp pair inside
// do_write_jpeg_image (by making them non-local).
@@ -692,7 +728,7 @@ static bool write_jpeg_image(const QImage &image,
const bool success = do_write_jpeg_image(cinfo, row_pointer,
image, device,
sourceQuality, description,
- optimize, progressive);
+ optimize, progressive, invertCMYK);
delete [] row_pointer[0];
return success;
@@ -737,6 +773,21 @@ public:
QStringList readTexts;
QByteArray iccProfile;
+ // Photoshop historically invertes the quantities in CMYK JPEG files:
+ // 0 means 100% ink, 255 means no ink. Every reader does the same,
+ // for compatibility reasons.
+ // Use such an interpretation by default, but also offer the alternative
+ // of not inverting the channels.
+ // This is just a "fancy" API; it could be reduced to a boolean setting
+ // for CMYK files.
+ enum class SubType {
+ Automatic,
+ Inverted_CMYK,
+ CMYK,
+ NSubTypes
+ };
+ SubType subType = SubType::Automatic;
+
struct jpeg_decompress_struct info;
struct my_jpeg_source_mgr * iod_src;
struct my_error_mgr err;
@@ -751,6 +802,14 @@ public:
QJpegHandler *q;
};
+static const char SupportedJPEGSubtypes[][14] = {
+ "Automatic",
+ "Inverted_CMYK",
+ "CMYK"
+};
+
+static_assert(std::size(SupportedJPEGSubtypes) == size_t(QJpegHandlerPrivate::SubType::NSubTypes));
+
static bool readExifHeader(QDataStream &stream)
{
char prefix[6];
@@ -967,7 +1026,8 @@ bool QJpegHandlerPrivate::read(QImage *image)
if (state == ReadHeader)
{
- bool success = read_jpeg_image(image, scaledSize, scaledClipRect, clipRect, quality, rgb888ToRgb32ConverterPtr, &info, &err);
+ const bool invertCMYK = subType != QJpegHandlerPrivate::SubType::CMYK;
+ bool success = read_jpeg_image(image, scaledSize, scaledClipRect, clipRect, quality, rgb888ToRgb32ConverterPtr, &info, &err, invertCMYK);
if (success) {
for (int i = 0; i < readTexts.size()-1; i+=2)
image->setText(readTexts.at(i), readTexts.at(i+1));
@@ -1053,13 +1113,14 @@ extern void qt_imageTransform(QImage &src, QImageIOHandler::Transformations orie
bool QJpegHandler::write(const QImage &image)
{
+ const bool invertCMYK = d->subType != QJpegHandlerPrivate::SubType::CMYK;
if (d->transformation != QImageIOHandler::TransformationNone) {
// We don't support writing EXIF headers so apply the transform to the data.
QImage img = image;
qt_imageTransform(img, d->transformation);
- return write_jpeg_image(img, device(), d->quality, d->description, d->optimize, d->progressive);
+ return write_jpeg_image(img, device(), d->quality, d->description, d->optimize, d->progressive, invertCMYK);
}
- return write_jpeg_image(image, device(), d->quality, d->description, d->optimize, d->progressive);
+ return write_jpeg_image(image, device(), d->quality, d->description, d->optimize, d->progressive, invertCMYK);
}
bool QJpegHandler::supportsOption(ImageOption option) const
@@ -1070,6 +1131,8 @@ bool QJpegHandler::supportsOption(ImageOption option) const
|| option == ClipRect
|| option == Description
|| option == Size
+ || option == SubType
+ || option == SupportedSubTypes
|| option == ImageFormat
|| option == OptimizedWrite
|| option == ProgressiveScanWrite
@@ -1093,6 +1156,13 @@ QVariant QJpegHandler::option(ImageOption option) const
case Size:
d->readJpegHeader(device());
return d->size;
+ case SubType:
+ return QByteArray(SupportedJPEGSubtypes[int(d->subType)]);
+ case SupportedSubTypes: {
+ QByteArrayList list(std::begin(SupportedJPEGSubtypes),
+ std::end(SupportedJPEGSubtypes));
+ return QVariant::fromValue(list);
+ }
case ImageFormat:
d->readJpegHeader(device());
return d->format;
@@ -1128,6 +1198,16 @@ void QJpegHandler::setOption(ImageOption option, const QVariant &value)
case Description:
d->description = value.toString();
break;
+ case SubType: {
+ const QByteArray subType = value.toByteArray();
+ for (size_t i = 0; i < std::size(SupportedJPEGSubtypes); ++i) {
+ if (subType == SupportedJPEGSubtypes[i]) {
+ d->subType = QJpegHandlerPrivate::SubType(i);
+ break;
+ }
+ }
+ break;
+ }
case OptimizedWrite:
d->optimize = value.toBool();
break;
@@ -1138,6 +1218,7 @@ void QJpegHandler::setOption(ImageOption option, const QVariant &value)
int transformation = value.toInt();
if (transformation > 0 && transformation < 8)
d->transformation = QImageIOHandler::Transformations(transformation);
+ break;
}
default:
break;
diff --git a/src/plugins/networkinformation/CMakeLists.txt b/src/plugins/networkinformation/CMakeLists.txt
index 43aeb3d22a..04da816264 100644
--- a/src/plugins/networkinformation/CMakeLists.txt
+++ b/src/plugins/networkinformation/CMakeLists.txt
@@ -1,5 +1,5 @@
# Copyright (C) 2022 The Qt Company Ltd.
-# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+# SPDX-License-Identifier: BSD-3-Clause
if(WIN32 AND QT_FEATURE_networklistmanager)
add_subdirectory(networklistmanager)
diff --git a/src/plugins/networkinformation/android/CMakeLists.txt b/src/plugins/networkinformation/android/CMakeLists.txt
index 7f1087aabd..07d9201bbb 100644
--- a/src/plugins/networkinformation/android/CMakeLists.txt
+++ b/src/plugins/networkinformation/android/CMakeLists.txt
@@ -1,5 +1,5 @@
# Copyright (C) 2022 The Qt Company Ltd.
-# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+# SPDX-License-Identifier: BSD-3-Clause
set(java_sources
@@ -12,8 +12,10 @@ qt_internal_add_jar(Qt${QtBase_VERSION_MAJOR}AndroidNetworkInformationBackend
OUTPUT_DIR "${QT_BUILD_DIR}/jar"
)
+qt_path_join(destination ${INSTALL_DATADIR} "jar")
+
install_jar(Qt${QtBase_VERSION_MAJOR}AndroidNetworkInformationBackend
- DESTINATION jar
+ DESTINATION ${destination}
COMPONENT Devel
)
@@ -27,8 +29,6 @@ qt_internal_add_plugin(QAndroidNetworkInformationPlugin
wrapper/androidconnectivitymanager.cpp wrapper/androidconnectivitymanager.h
LIBRARIES
Qt::NetworkPrivate
- DEFINES
- QT_USE_QSTRINGBUILDER
)
set_property(
diff --git a/src/plugins/networkinformation/android/jar/build.gradle b/src/plugins/networkinformation/android/jar/build.gradle
index c947852f79..68a9381ad2 100644
--- a/src/plugins/networkinformation/android/jar/build.gradle
+++ b/src/plugins/networkinformation/android/jar/build.gradle
@@ -7,7 +7,7 @@ buildscript {
}
dependencies {
- classpath 'com.android.tools.build:gradle:7.0.2'
+ classpath 'com.android.tools.build:gradle:8.0.2'
}
}
@@ -23,12 +23,10 @@ repositories {
}
android {
- compileSdkVersion 31
- buildToolsVersion "31.0.3"
+ compileSdk 34
defaultConfig {
minSdkVersion 23
- targetSdkVersion 31
}
sourceSets {
diff --git a/src/plugins/networkinformation/android/wrapper/androidconnectivitymanager.cpp b/src/plugins/networkinformation/android/wrapper/androidconnectivitymanager.cpp
index 203772971b..3c9f952968 100644
--- a/src/plugins/networkinformation/android/wrapper/androidconnectivitymanager.cpp
+++ b/src/plugins/networkinformation/android/wrapper/androidconnectivitymanager.cpp
@@ -48,7 +48,7 @@ static void transportMediumChanged(JNIEnv *env, jobject obj, jint enumValue)
}
Q_DECLARE_JNI_NATIVE_METHOD(transportMediumChanged)
-Q_DECLARE_JNI_TYPE(ConnectivityManager, "Landroid/net/ConnectivityManager;")
+Q_DECLARE_JNI_CLASS(ConnectivityManager, "android/net/ConnectivityManager")
AndroidConnectivityManager::AndroidConnectivityManager()
{
@@ -93,3 +93,5 @@ bool AndroidConnectivityManager::registerNatives() const
}
QT_END_NAMESPACE
+
+#include "moc_androidconnectivitymanager.cpp"
diff --git a/src/plugins/networkinformation/glib/CMakeLists.txt b/src/plugins/networkinformation/glib/CMakeLists.txt
index f2e5260b7f..019f4f1358 100644
--- a/src/plugins/networkinformation/glib/CMakeLists.txt
+++ b/src/plugins/networkinformation/glib/CMakeLists.txt
@@ -1,5 +1,5 @@
# Copyright (C) 2022 The Qt Company Ltd.
-# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+# SPDX-License-Identifier: BSD-3-Clause
qt_internal_add_plugin(QGlibNetworkInformationPlugin
OUTPUT_NAME qglib
diff --git a/src/plugins/networkinformation/glib/qglibnetworkinformationbackend.cpp b/src/plugins/networkinformation/glib/qglibnetworkinformationbackend.cpp
index 10ce5dbc60..0b45eb9ce3 100644
--- a/src/plugins/networkinformation/glib/qglibnetworkinformationbackend.cpp
+++ b/src/plugins/networkinformation/glib/qglibnetworkinformationbackend.cpp
@@ -51,7 +51,8 @@ public:
static QNetworkInformation::Features featuresSupportedStatic()
{
using Feature = QNetworkInformation::Feature;
- return QNetworkInformation::Features(Feature::Reachability | Feature::CaptivePortal);
+ return QNetworkInformation::Features(Feature::Reachability | Feature::CaptivePortal
+ | Feature::Metered);
}
bool isValid() const;
@@ -59,10 +60,12 @@ public:
private:
Q_DISABLE_COPY_MOVE(QGlibNetworkInformationBackend)
- static void updateInformation(QGlibNetworkInformationBackend *backend);
+ static void updateConnectivity(QGlibNetworkInformationBackend *backend);
+ static void updateMetered(QGlibNetworkInformationBackend *backend);
GNetworkMonitor *networkMonitor = nullptr;
- gulong handlerId = 0;
+ gulong connectivityHandlerId = 0;
+ gulong meteredHandlerId = 0;
};
class QGlibNetworkInformationBackendFactory : public QNetworkInformationBackendFactory
@@ -95,23 +98,28 @@ private:
QGlibNetworkInformationBackend::QGlibNetworkInformationBackend()
: networkMonitor(g_network_monitor_get_default())
{
- updateInformation(this);
+ updateConnectivity(this);
+ updateMetered(this);
- handlerId = g_signal_connect_swapped(networkMonitor, "notify::connectivity",
- G_CALLBACK(updateInformation), this);
+ connectivityHandlerId = g_signal_connect_swapped(networkMonitor, "notify::connectivity",
+ G_CALLBACK(updateConnectivity), this);
+
+ meteredHandlerId = g_signal_connect_swapped(networkMonitor, "notify::network-metered",
+ G_CALLBACK(updateMetered), this);
}
QGlibNetworkInformationBackend::~QGlibNetworkInformationBackend()
{
- g_signal_handler_disconnect(networkMonitor, handlerId);
+ g_signal_handler_disconnect(networkMonitor, meteredHandlerId);
+ g_signal_handler_disconnect(networkMonitor, connectivityHandlerId);
}
bool QGlibNetworkInformationBackend::isValid() const
{
- return G_OBJECT_TYPE_NAME(networkMonitor) != "GNetworkMonitorBase"_L1;
+ return QLatin1StringView(G_OBJECT_TYPE_NAME(networkMonitor)) != "GNetworkMonitorBase"_L1;
}
-void QGlibNetworkInformationBackend::updateInformation(QGlibNetworkInformationBackend *backend)
+void QGlibNetworkInformationBackend::updateConnectivity(QGlibNetworkInformationBackend *backend)
{
const auto connectivityState = g_network_monitor_get_connectivity(backend->networkMonitor);
const bool behindPortal = (connectivityState == G_NETWORK_CONNECTIVITY_PORTAL);
@@ -119,6 +127,11 @@ void QGlibNetworkInformationBackend::updateInformation(QGlibNetworkInformationBa
backend->setBehindCaptivePortal(behindPortal);
}
+void QGlibNetworkInformationBackend::updateMetered(QGlibNetworkInformationBackend *backend)
+{
+ backend->setMetered(g_network_monitor_get_network_metered(backend->networkMonitor));
+}
+
QT_END_NAMESPACE
#include "qglibnetworkinformationbackend.moc"
diff --git a/src/plugins/networkinformation/networklistmanager/CMakeLists.txt b/src/plugins/networkinformation/networklistmanager/CMakeLists.txt
index c4b65bb805..acd3754f4e 100644
--- a/src/plugins/networkinformation/networklistmanager/CMakeLists.txt
+++ b/src/plugins/networkinformation/networklistmanager/CMakeLists.txt
@@ -1,11 +1,12 @@
# Copyright (C) 2022 The Qt Company Ltd.
-# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+# SPDX-License-Identifier: BSD-3-Clause
qt_internal_add_plugin(QNLMNIPlugin
OUTPUT_NAME qnetworklistmanager
CLASS_NAME QNetworkListManagerNetworkInformationBackendFactory
PLUGIN_TYPE networkinformation
DEFAULT_IF WIN32 AND QT_FEATURE_networklistmanager
+ EXCEPTIONS
SOURCES
qnetworklistmanagernetworkinformationbackend.cpp
qnetworklistmanagerevents.h qnetworklistmanagerevents.cpp
@@ -13,9 +14,10 @@ qt_internal_add_plugin(QNLMNIPlugin
Qt::NetworkPrivate
)
-qt_internal_extend_target(QNLMNIPlugin CONDITION WIN32 AND MSVC AND NOT CLANG
+qt_internal_extend_target(QNLMNIPlugin CONDITION WIN32
LIBRARIES
runtimeobject
+ oleaut32
)
# Don't repeat the target name in AUTOGEN_BUILD_DIR to work around issues with overlong paths.
diff --git a/src/plugins/networkinformation/networklistmanager/qnetworklistmanagerevents.cpp b/src/plugins/networkinformation/networklistmanager/qnetworklistmanagerevents.cpp
index 72023c6628..caa5046751 100644
--- a/src/plugins/networkinformation/networklistmanager/qnetworklistmanagerevents.cpp
+++ b/src/plugins/networkinformation/networklistmanager/qnetworklistmanagerevents.cpp
@@ -2,19 +2,17 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#include "qnetworklistmanagerevents.h"
+#include <QtCore/private/qsystemerror_p.h>
-#ifdef SUPPORTS_WINRT
-#include <winrt/base.h>
-// Workaround for Windows SDK bug.
-// See https://github.com/microsoft/Windows.UI.Composition-Win32-Samples/issues/47
-namespace winrt::impl
-{
- template <typename Async>
- auto wait_for(Async const& async, Windows::Foundation::TimeSpan const& timeout);
-}
+#include <QtCore/qpointer.h>
+
+#include <mutex>
+
+#if QT_CONFIG(cpp_winrt)
+#include <QtCore/private/qt_winrtbase_p.h>
#include <winrt/Windows.Networking.Connectivity.h>
-#endif
+#endif // QT_CONFIG(cpp_winrt)
QT_BEGIN_NAMESPACE
@@ -37,7 +35,7 @@ QNetworkListManagerEvents::QNetworkListManagerEvents() : QObject(nullptr)
IID_INetworkListManager, &networkListManager);
if (FAILED(hr)) {
qCWarning(lcNetInfoNLM) << "Could not get a NetworkListManager instance:"
- << errorStringFromHResult(hr);
+ << QSystemError::windowsComString(hr);
return;
}
@@ -49,7 +47,7 @@ QNetworkListManagerEvents::QNetworkListManagerEvents() : QObject(nullptr)
}
if (FAILED(hr)) {
qCWarning(lcNetInfoNLM) << "Failed to get connection point for network list manager events:"
- << errorStringFromHResult(hr);
+ << QSystemError::windowsComString(hr);
}
}
@@ -87,26 +85,39 @@ bool QNetworkListManagerEvents::start()
auto hr = connectionPoint->Advise(this, &cookie);
if (FAILED(hr)) {
qCWarning(lcNetInfoNLM) << "Failed to subscribe to network connectivity events:"
- << errorStringFromHResult(hr);
+ << QSystemError::windowsComString(hr);
return false;
}
// Update connectivity since it might have changed since this class was constructed
NLM_CONNECTIVITY connectivity;
hr = networkListManager->GetConnectivity(&connectivity);
- if (FAILED(hr))
- qCWarning(lcNetInfoNLM) << "Could not get connectivity:" << errorStringFromHResult(hr);
- else
+ if (FAILED(hr)) {
+ qCWarning(lcNetInfoNLM) << "Could not get connectivity:"
+ << QSystemError::windowsComString(hr);
+ } else {
emit connectivityChanged(connectivity);
+ }
-#ifdef SUPPORTS_WINRT
+#if QT_CONFIG(cpp_winrt)
using namespace winrt::Windows::Networking::Connectivity;
- // Register for changes in the network and store a token to unregister later:
- token = NetworkInformation::NetworkStatusChanged(
- [this](const winrt::Windows::Foundation::IInspectable sender) {
- Q_UNUSED(sender);
- emitWinRTUpdates();
- });
+ using winrt::Windows::Foundation::IInspectable;
+ try {
+ // Register for changes in the network and store a token to unregister later:
+ token = NetworkInformation::NetworkStatusChanged(
+ [owner = QPointer(this)](const IInspectable sender) {
+ Q_UNUSED(sender);
+ if (owner) {
+ std::scoped_lock locker(owner->winrtLock);
+ if (owner->token)
+ owner->emitWinRTUpdates();
+ }
+ });
+ } catch (const winrt::hresult_error &ex) {
+ qCWarning(lcNetInfoNLM) << "Failed to register network status changed callback:"
+ << QSystemError::windowsComString(ex.code());
+ }
+
// Emit initial state
emitWinRTUpdates();
#endif
@@ -114,24 +125,28 @@ bool QNetworkListManagerEvents::start()
return true;
}
-bool QNetworkListManagerEvents::stop()
+void QNetworkListManagerEvents::stop()
{
Q_ASSERT(connectionPoint);
auto hr = connectionPoint->Unadvise(cookie);
if (FAILED(hr)) {
qCWarning(lcNetInfoNLM) << "Failed to unsubscribe from network connectivity events:"
- << errorStringFromHResult(hr);
- return false;
+ << QSystemError::windowsComString(hr);
+ } else {
+ cookie = 0;
}
- cookie = 0;
+ // Even if we fail we should still try to unregister from winrt events:
-#ifdef SUPPORTS_WINRT
- using namespace winrt::Windows::Networking::Connectivity;
- // Pass the token we stored earlier to unregister:
- NetworkInformation::NetworkStatusChanged(token);
- token = {};
+#if QT_CONFIG(cpp_winrt)
+ // Try to synchronize unregistering with potentially in-progress callbacks
+ std::scoped_lock locker(winrtLock);
+ if (token) {
+ using namespace winrt::Windows::Networking::Connectivity;
+ // Pass the token we stored earlier to unregister:
+ NetworkInformation::NetworkStatusChanged(token);
+ token = {};
+ }
#endif
- return true;
}
bool QNetworkListManagerEvents::checkBehindCaptivePortal()
@@ -155,7 +170,7 @@ bool QNetworkListManagerEvents::checkBehindCaptivePortal()
VariantInit(&variant);
const auto scopedVariantClear = qScopeGuard([&variant]() { VariantClear(&variant); });
- const wchar_t *versions[] = { NA_InternetConnectivityV6, NA_InternetConnectivityV4 };
+ const wchar_t *versions[] = { L"NA_InternetConnectivityV6", L"NA_InternetConnectivityV4" };
for (const auto version : versions) {
hr = propertyBag->Read(version, &variant, nullptr);
if (SUCCEEDED(hr)
@@ -172,7 +187,7 @@ bool QNetworkListManagerEvents::checkBehindCaptivePortal()
return false;
}
-#ifdef SUPPORTS_WINRT
+#if QT_CONFIG(cpp_winrt)
namespace {
using namespace winrt::Windows::Networking::Connectivity;
// NB: this isn't part of "network list manager", but sadly NLM doesn't have an
@@ -185,7 +200,14 @@ QNetworkInformation::TransportMedium getTransportMedium(const ConnectionProfile
if (profile.IsWlanConnectionProfile())
return QNetworkInformation::TransportMedium::WiFi;
- NetworkAdapter adapter = profile.NetworkAdapter();
+ NetworkAdapter adapter(nullptr);
+ try {
+ adapter = profile.NetworkAdapter();
+ } catch (const winrt::hresult_error &ex) {
+ qCWarning(lcNetInfoNLM) << "Failed to obtain network adapter:"
+ << QSystemError::windowsComString(ex.code());
+ // pass, we will return Unknown anyway
+ }
if (adapter == nullptr)
return QNetworkInformation::TransportMedium::Unknown;
@@ -208,7 +230,16 @@ QNetworkInformation::TransportMedium getTransportMedium(const ConnectionProfile
[[nodiscard]] bool getMetered(const ConnectionProfile &profile)
{
- ConnectionCost cost = profile.GetConnectionCost();
+ ConnectionCost cost(nullptr);
+ try {
+ cost = profile.GetConnectionCost();
+ } catch (const winrt::hresult_error &ex) {
+ qCWarning(lcNetInfoNLM) << "Failed to obtain connection cost:"
+ << QSystemError::windowsComString(ex.code());
+ // pass, we return false if we get an empty object back anyway
+ }
+ if (cost == nullptr)
+ return false;
NetworkCostType type = cost.NetworkCostType();
return type == NetworkCostType::Fixed || type == NetworkCostType::Variable;
}
@@ -217,12 +248,21 @@ QNetworkInformation::TransportMedium getTransportMedium(const ConnectionProfile
void QNetworkListManagerEvents::emitWinRTUpdates()
{
using namespace winrt::Windows::Networking::Connectivity;
- ConnectionProfile profile = NetworkInformation::GetInternetConnectionProfile();
+ ConnectionProfile profile = nullptr;
+ try {
+ profile = NetworkInformation::GetInternetConnectionProfile();
+ } catch (const winrt::hresult_error &ex) {
+ qCWarning(lcNetInfoNLM) << "Failed to obtain connection profile:"
+ << QSystemError::windowsComString(ex.code());
+ // pass, we would just return early if we get an empty object back anyway
+ }
if (profile == nullptr)
return;
emit transportMediumChanged(getTransportMedium(profile));
emit isMeteredChanged(getMetered(profile));
}
-#endif
+#endif // QT_CONFIG(cpp_winrt)
QT_END_NAMESPACE
+
+#include "moc_qnetworklistmanagerevents.cpp"
diff --git a/src/plugins/networkinformation/networklistmanager/qnetworklistmanagerevents.h b/src/plugins/networkinformation/networklistmanager/qnetworklistmanagerevents.h
index cf9a08ca84..d91cd8a4cc 100644
--- a/src/plugins/networkinformation/networklistmanager/qnetworklistmanagerevents.h
+++ b/src/plugins/networkinformation/networklistmanager/qnetworklistmanagerevents.h
@@ -1,6 +1,9 @@
// Copyright (C) 2021 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+#ifndef QNETWORKLISTMANAGEREVENTS_H
+#define QNETWORKLISTMANAGEREVENTS_H
+
#include <QtNetwork/private/qtnetworkglobal_p.h>
#include <QtNetwork/qnetworkinformation.h>
@@ -8,19 +11,16 @@
#include <QtCore/qstring.h>
#include <QtCore/qobject.h>
#include <QtCore/qloggingcategory.h>
+#include <QtCore/qmutex.h>
#include <objbase.h>
+#include <ocidl.h>
#include <netlistmgr.h>
#include <wrl/client.h>
#include <wrl/wrappers/corewrappers.h>
-#include <comdef.h>
-
-#if QT_CONFIG(cpp_winrt) && !defined(Q_CC_CLANG)
-#define SUPPORTS_WINRT 1
-#endif
-#ifdef SUPPORTS_WINRT
-#include <winrt/base.h>
+#if QT_CONFIG(cpp_winrt)
+#include <QtCore/private/qt_winrtbase_p.h>
#endif
using namespace Microsoft::WRL;
@@ -28,12 +28,6 @@ using namespace Microsoft::WRL;
QT_BEGIN_NAMESPACE
Q_DECLARE_LOGGING_CATEGORY(lcNetInfoNLM)
-inline QString errorStringFromHResult(HRESULT hr)
-{
- _com_error error(hr);
- return QString::fromWCharArray(error.ErrorMessage());
-}
-
class QNetworkListManagerEvents : public QObject, public INetworkListManagerEvents
{
Q_OBJECT
@@ -56,7 +50,7 @@ public:
HRESULT STDMETHODCALLTYPE ConnectivityChanged(NLM_CONNECTIVITY newConnectivity) override;
[[nodiscard]] bool start();
- bool stop();
+ void stop();
[[nodiscard]] bool checkBehindCaptivePortal();
@@ -69,10 +63,11 @@ private:
ComPtr<INetworkListManager> networkListManager = nullptr;
ComPtr<IConnectionPoint> connectionPoint = nullptr;
-#ifdef SUPPORTS_WINRT
+#if QT_CONFIG(cpp_winrt)
void emitWinRTUpdates();
winrt::event_token token;
+ QMutex winrtLock;
#endif
QAtomicInteger<ULONG> ref = 0;
@@ -80,3 +75,5 @@ private:
};
QT_END_NAMESPACE
+
+#endif // QNETWORKLISTMANAGEREVENTS_H
diff --git a/src/plugins/networkinformation/networklistmanager/qnetworklistmanagernetworkinformationbackend.cpp b/src/plugins/networkinformation/networklistmanager/qnetworklistmanagernetworkinformationbackend.cpp
index 9673d2cf69..766648486e 100644
--- a/src/plugins/networkinformation/networklistmanager/qnetworklistmanagernetworkinformationbackend.cpp
+++ b/src/plugins/networkinformation/networklistmanager/qnetworklistmanagernetworkinformationbackend.cpp
@@ -9,6 +9,8 @@
#include <QtCore/private/qobject_p.h>
#include <QtCore/qscopeguard.h>
+#include <QtCore/private/qfunctions_win_p.h>
+
QT_BEGIN_NAMESPACE
// Declared in qnetworklistmanagerevents.h
@@ -68,8 +70,9 @@ public:
{
return QNetworkInformation::Features(QNetworkInformation::Feature::Reachability
| QNetworkInformation::Feature::CaptivePortal
-#ifdef SUPPORTS_WINRT
+#if QT_CONFIG(cpp_winrt)
| QNetworkInformation::Feature::TransportMedium
+ | QNetworkInformation::Feature::Metered
#endif
);
}
@@ -82,12 +85,13 @@ private:
void setConnectivity(NLM_CONNECTIVITY newConnectivity);
void checkCaptivePortal();
+ QComHelper comHelper;
+
ComPtr<QNetworkListManagerEvents> managerEvents;
NLM_CONNECTIVITY connectivity = NLM_CONNECTIVITY_DISCONNECTED;
bool monitoring = false;
- bool comInitFailed = false;
};
class QNetworkListManagerNetworkInformationBackendFactory : public QNetworkInformationBackendFactory
@@ -121,12 +125,9 @@ public:
QNetworkListManagerNetworkInformationBackend::QNetworkListManagerNetworkInformationBackend()
{
- auto hr = CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
- if (FAILED(hr)) {
- qCWarning(lcNetInfoNLM) << "Failed to initialize COM:" << errorStringFromHResult(hr);
- comInitFailed = true;
+ if (!comHelper.isValid())
return;
- }
+
managerEvents = new QNetworkListManagerEvents();
connect(managerEvents.Get(), &QNetworkListManagerEvents::connectivityChanged, this,
&QNetworkListManagerNetworkInformationBackend::setConnectivity);
@@ -140,8 +141,6 @@ QNetworkListManagerNetworkInformationBackend::QNetworkListManagerNetworkInformat
QNetworkListManagerNetworkInformationBackend::~QNetworkListManagerNetworkInformationBackend()
{
- if (comInitFailed)
- return;
stop();
}
@@ -164,11 +163,8 @@ void QNetworkListManagerNetworkInformationBackend::checkCaptivePortal()
bool QNetworkListManagerNetworkInformationBackend::event(QEvent *event)
{
- if (event->type() == QEvent::ThreadChange && monitoring) {
- stop();
- QMetaObject::invokeMethod(this, &QNetworkListManagerNetworkInformationBackend::start,
- Qt::QueuedConnection);
- }
+ if (event->type() == QEvent::ThreadChange)
+ qFatal("Moving QNetworkListManagerNetworkInformationBackend to different thread is not supported");
return QObject::event(event);
}
@@ -177,15 +173,9 @@ bool QNetworkListManagerNetworkInformationBackend::start()
{
Q_ASSERT(!monitoring);
- if (comInitFailed) {
- auto hr = CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
- if (FAILED(hr)) {
- qCWarning(lcNetInfoNLM) << "Failed to initialize COM:" << errorStringFromHResult(hr);
- comInitFailed = true;
- return false;
- }
- comInitFailed = false;
- }
+ if (!comHelper.isValid())
+ return false;
+
if (!managerEvents)
managerEvents = new QNetworkListManagerEvents();
@@ -198,14 +188,10 @@ void QNetworkListManagerNetworkInformationBackend::stop()
{
if (monitoring) {
Q_ASSERT(managerEvents);
- // Can return false but realistically shouldn't since that would break everything:
managerEvents->stop();
monitoring = false;
managerEvents.Reset();
}
-
- CoUninitialize();
- comInitFailed = true; // we check this value in start() to see if we need to re-initialize
}
QT_END_NAMESPACE
diff --git a/src/plugins/networkinformation/networkmanager/CMakeLists.txt b/src/plugins/networkinformation/networkmanager/CMakeLists.txt
index e1407040e8..9d76dbe7b4 100644
--- a/src/plugins/networkinformation/networkmanager/CMakeLists.txt
+++ b/src/plugins/networkinformation/networkmanager/CMakeLists.txt
@@ -1,5 +1,5 @@
# Copyright (C) 2022 The Qt Company Ltd.
-# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+# SPDX-License-Identifier: BSD-3-Clause
qt_internal_add_plugin(QNetworkManagerNetworkInformationPlugin
OUTPUT_NAME qnetworkmanager
@@ -7,6 +7,7 @@ qt_internal_add_plugin(QNetworkManagerNetworkInformationPlugin
PLUGIN_TYPE networkinformation
DEFAULT_IF LINUX
SOURCES
+ qnetworkmanagernetworkinformationbackend.h
qnetworkmanagernetworkinformationbackend.cpp
qnetworkmanagerservice.h
qnetworkmanagerservice.cpp
diff --git a/src/plugins/networkinformation/networkmanager/qnetworkmanagernetworkinformationbackend.cpp b/src/plugins/networkinformation/networkmanager/qnetworkmanagernetworkinformationbackend.cpp
index 67129af2de..f583d1dcf6 100644
--- a/src/plugins/networkinformation/networkmanager/qnetworkmanagernetworkinformationbackend.cpp
+++ b/src/plugins/networkinformation/networkmanager/qnetworkmanagernetworkinformationbackend.cpp
@@ -1,9 +1,7 @@
// Copyright (C) 2021 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
-#include <QtNetwork/private/qnetworkinformation_p.h>
-
-#include "qnetworkmanagerservice.h"
+#include "qnetworkmanagernetworkinformationbackend.h"
#include <QtCore/qglobal.h>
#include <QtCore/private/qobject_p.h>
@@ -94,46 +92,20 @@ bool isMeteredFromNMMetered(QNetworkManagerInterface::NMMetered metered)
case QNetworkManagerInterface::NM_METERED_UNKNOWN:
return false;
}
- Q_UNREACHABLE();
- return false;
+ Q_UNREACHABLE_RETURN(false);
}
} // unnamed namespace
static QString backendName()
{
- return QString::fromUtf16(QNetworkInformationBackend::PluginNames
- [QNetworkInformationBackend::PluginNamesLinuxIndex]);
+ return QStringView(QNetworkInformationBackend::PluginNames
+ [QNetworkInformationBackend::PluginNamesLinuxIndex]).toString();
}
-class QNetworkManagerNetworkInformationBackend : public QNetworkInformationBackend
+QString QNetworkManagerNetworkInformationBackend::name() const
{
- Q_OBJECT
-public:
- QNetworkManagerNetworkInformationBackend();
- ~QNetworkManagerNetworkInformationBackend() = default;
-
- QString name() const override { return backendName(); }
- QNetworkInformation::Features featuresSupported() const override
- {
- if (!isValid())
- return {};
- return featuresSupportedStatic();
- }
-
- static QNetworkInformation::Features featuresSupportedStatic()
- {
- using Feature = QNetworkInformation::Feature;
- return QNetworkInformation::Features(Feature::Reachability | Feature::CaptivePortal
- | Feature::TransportMedium | Feature::Metered);
- }
-
- bool isValid() const { return iface.isValid(); }
-
-private:
- Q_DISABLE_COPY_MOVE(QNetworkManagerNetworkInformationBackend)
-
- QNetworkManagerInterface iface;
-};
+ return backendName();
+}
class QNetworkManagerNetworkInformationBackendFactory : public QNetworkInformationBackendFactory
{
@@ -168,34 +140,42 @@ private:
QNetworkManagerNetworkInformationBackend::QNetworkManagerNetworkInformationBackend()
{
- auto updateReachability = [this](QNetworkManagerInterface::NMState newState) {
- setReachability(reachabilityFromNMState(newState));
- };
- updateReachability(iface.state());
- connect(&iface, &QNetworkManagerInterface::stateChanged, this, std::move(updateReachability));
-
- auto updateBehindCaptivePortal = [this](QNetworkManagerInterface::NMConnectivityState state) {
- const bool behindPortal = (state == QNetworkManagerInterface::NM_CONNECTIVITY_PORTAL);
- setBehindCaptivePortal(behindPortal);
- };
- updateBehindCaptivePortal(iface.connectivityState());
- connect(&iface, &QNetworkManagerInterface::connectivityChanged, this,
- std::move(updateBehindCaptivePortal));
-
- auto updateTransportMedium = [this](QNetworkManagerInterface::NMDeviceType newDevice) {
- setTransportMedium(transportMediumFromDeviceType(newDevice));
- };
- updateTransportMedium(iface.deviceType());
- connect(&iface, &QNetworkManagerInterface::deviceTypeChanged, this,
- std::move(updateTransportMedium));
-
- auto updateMetered = [this](QNetworkManagerInterface::NMMetered metered) {
- setMetered(isMeteredFromNMMetered(metered));
- };
- updateMetered(iface.meteredState());
- connect(&iface, &QNetworkManagerInterface::meteredChanged, this, std::move(updateMetered));
+ if (!iface.isValid())
+ return;
+ iface.setBackend(this);
+ onStateChanged(iface.state());
+ onConnectivityChanged(iface.connectivityState());
+ onDeviceTypeChanged(iface.deviceType());
+ onMeteredChanged(iface.meteredState());
+}
+
+void QNetworkManagerNetworkInformationBackend::onStateChanged(
+ QNetworkManagerInterface::NMState newState)
+{
+ setReachability(reachabilityFromNMState(newState));
}
+void QNetworkManagerNetworkInformationBackend::onConnectivityChanged(
+ QNetworkManagerInterface::NMConnectivityState connectivityState)
+{
+ const bool behindPortal =
+ (connectivityState == QNetworkManagerInterface::NM_CONNECTIVITY_PORTAL);
+ setBehindCaptivePortal(behindPortal);
+}
+
+void QNetworkManagerNetworkInformationBackend::onDeviceTypeChanged(
+ QNetworkManagerInterface::NMDeviceType newDevice)
+{
+ setTransportMedium(transportMediumFromDeviceType(newDevice));
+}
+
+void QNetworkManagerNetworkInformationBackend::onMeteredChanged(
+ QNetworkManagerInterface::NMMetered metered)
+{
+ setMetered(isMeteredFromNMMetered(metered));
+};
+
QT_END_NAMESPACE
#include "qnetworkmanagernetworkinformationbackend.moc"
+#include "moc_qnetworkmanagernetworkinformationbackend.cpp"
diff --git a/src/plugins/networkinformation/networkmanager/qnetworkmanagernetworkinformationbackend.h b/src/plugins/networkinformation/networkmanager/qnetworkmanagernetworkinformationbackend.h
new file mode 100644
index 0000000000..3b60f0949c
--- /dev/null
+++ b/src/plugins/networkinformation/networkmanager/qnetworkmanagernetworkinformationbackend.h
@@ -0,0 +1,60 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QNETWORKMANAGERINFORMATIONBACKEND_H
+#define QNETWORKMANAGERINFORMATIONBACKEND_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtNetwork/private/qnetworkinformation_p.h>
+#include "qnetworkmanagerservice.h"
+
+QT_BEGIN_NAMESPACE
+
+class QNetworkManagerNetworkInformationBackend : public QNetworkInformationBackend
+{
+ Q_OBJECT
+public:
+ QNetworkManagerNetworkInformationBackend();
+ ~QNetworkManagerNetworkInformationBackend() = default;
+
+ QString name() const override;
+ QNetworkInformation::Features featuresSupported() const override
+ {
+ if (!isValid())
+ return {};
+ return featuresSupportedStatic();
+ }
+
+ static QNetworkInformation::Features featuresSupportedStatic()
+ {
+ using Feature = QNetworkInformation::Feature;
+ return QNetworkInformation::Features(Feature::Reachability | Feature::CaptivePortal
+ | Feature::TransportMedium | Feature::Metered);
+ }
+
+ bool isValid() const { return iface.isValid(); }
+
+ void onStateChanged(QNetworkManagerInterface::NMState state);
+ void onConnectivityChanged(QNetworkManagerInterface::NMConnectivityState connectivityState);
+ void onDeviceTypeChanged(QNetworkManagerInterface::NMDeviceType deviceType);
+ void onMeteredChanged(QNetworkManagerInterface::NMMetered metered);
+
+private:
+ Q_DISABLE_COPY_MOVE(QNetworkManagerNetworkInformationBackend)
+
+ QNetworkManagerInterface iface;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/plugins/networkinformation/networkmanager/qnetworkmanagerservice.cpp b/src/plugins/networkinformation/networkmanager/qnetworkmanagerservice.cpp
index 3a64c1892c..c055555cac 100644
--- a/src/plugins/networkinformation/networkmanager/qnetworkmanagerservice.cpp
+++ b/src/plugins/networkinformation/networkmanager/qnetworkmanagerservice.cpp
@@ -2,6 +2,7 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#include "qnetworkmanagerservice.h"
+#include "qnetworkmanagernetworkinformationbackend.h"
#include <QObject>
#include <QList>
@@ -14,21 +15,40 @@
#include <QtDBus/QDBusObjectPath>
#include <QtDBus/QDBusPendingCall>
-#define DBUS_PROPERTIES_INTERFACE "org.freedesktop.DBus.Properties"
+#define DBUS_PROPERTIES_INTERFACE "org.freedesktop.DBus.Properties"_L1
-#define NM_DBUS_SERVICE "org.freedesktop.NetworkManager"
+#define NM_DBUS_INTERFACE "org.freedesktop.NetworkManager"
+#define NM_DBUS_SERVICE NM_DBUS_INTERFACE ""_L1
-#define NM_DBUS_PATH "/org/freedesktop/NetworkManager"
-#define NM_DBUS_INTERFACE NM_DBUS_SERVICE
-#define NM_CONNECTION_DBUS_INTERFACE NM_DBUS_SERVICE ".Connection.Active"
-#define NM_DEVICE_DBUS_INTERFACE NM_DBUS_SERVICE ".Device"
+#define NM_DBUS_PATH "/org/freedesktop/NetworkManager"_L1
+#define NM_CONNECTION_DBUS_INTERFACE NM_DBUS_SERVICE ".Connection.Active"_L1
+#define NM_DEVICE_DBUS_INTERFACE NM_DBUS_SERVICE ".Device"_L1
QT_BEGIN_NAMESPACE
using namespace Qt::StringLiterals;
+namespace {
+constexpr QLatin1StringView propertiesChangedKey = "PropertiesChanged"_L1;
+const QString &stateKey()
+{
+ static auto key = u"State"_s;
+ return key;
+}
+const QString &connectivityKey()
+{
+ static auto key = u"Connectivity"_s;
+ return key;
+}
+const QString &primaryConnectionKey()
+{
+ static auto key = u"PrimaryConnection"_s;
+ return key;
+}
+}
+
QNetworkManagerInterfaceBase::QNetworkManagerInterfaceBase(QObject *parent)
- : QDBusAbstractInterface(NM_DBUS_SERVICE ""_L1, NM_DBUS_PATH ""_L1,
+ : QDBusAbstractInterface(NM_DBUS_SERVICE, NM_DBUS_PATH,
NM_DBUS_INTERFACE, QDBusConnection::systemBus(), parent)
{
}
@@ -41,45 +61,49 @@ bool QNetworkManagerInterfaceBase::networkManagerAvailable()
QNetworkManagerInterface::QNetworkManagerInterface(QObject *parent)
: QNetworkManagerInterfaceBase(parent)
{
- if (!isValid())
+ if (!QDBusAbstractInterface::isValid())
return;
PropertiesDBusInterface managerPropertiesInterface(
- NM_DBUS_SERVICE ""_L1, NM_DBUS_PATH ""_L1, DBUS_PROPERTIES_INTERFACE,
+ NM_DBUS_SERVICE, NM_DBUS_PATH, DBUS_PROPERTIES_INTERFACE,
QDBusConnection::systemBus());
QList<QVariant> argumentList;
- argumentList << NM_DBUS_INTERFACE ""_L1;
+ argumentList << NM_DBUS_SERVICE;
QDBusPendingReply<QVariantMap> propsReply = managerPropertiesInterface.callWithArgumentList(
QDBus::Block, "GetAll"_L1, argumentList);
- if (!propsReply.isError()) {
- propertyMap = propsReply.value();
- } else {
- qWarning() << "propsReply" << propsReply.error().message();
+ if (propsReply.isError()) {
+ validDBusConnection = false;
+ if (auto error = propsReply.error(); error.type() != QDBusError::AccessDenied)
+ qWarning() << "Failed to query NetworkManager properties:" << error.message();
+ return;
}
+ propertyMap = propsReply.value();
- QDBusConnection::systemBus().connect(NM_DBUS_SERVICE ""_L1, NM_DBUS_PATH ""_L1,
- DBUS_PROPERTIES_INTERFACE""_L1, "PropertiesChanged"_L1, this,
- SLOT(setProperties(QString, QMap<QString, QVariant>, QList<QString>)));
+ validDBusConnection = QDBusConnection::systemBus().connect(NM_DBUS_SERVICE, NM_DBUS_PATH,
+ DBUS_PROPERTIES_INTERFACE, propertiesChangedKey, this,
+ SLOT(setProperties(QString,QMap<QString,QVariant>,QList<QString>)));
}
QNetworkManagerInterface::~QNetworkManagerInterface()
{
- QDBusConnection::systemBus().disconnect(NM_DBUS_SERVICE ""_L1, NM_DBUS_PATH ""_L1,
- DBUS_PROPERTIES_INTERFACE ""_L1, "PropertiesChanged"_L1, this,
- SLOT(setProperties(QString, QMap<QString, QVariant>, QList<QString>)));
+ QDBusConnection::systemBus().disconnect(NM_DBUS_SERVICE, NM_DBUS_PATH,
+ DBUS_PROPERTIES_INTERFACE, propertiesChangedKey, this,
+ SLOT(setProperties(QString,QMap<QString,QVariant>,QList<QString>)));
}
QNetworkManagerInterface::NMState QNetworkManagerInterface::state() const
{
- if (propertyMap.contains("State"))
- return static_cast<QNetworkManagerInterface::NMState>(propertyMap.value("State").toUInt());
+ auto it = propertyMap.constFind(stateKey());
+ if (it != propertyMap.cend())
+ return static_cast<QNetworkManagerInterface::NMState>(it->toUInt());
return QNetworkManagerInterface::NM_STATE_UNKNOWN;
}
QNetworkManagerInterface::NMConnectivityState QNetworkManagerInterface::connectivityState() const
{
- if (propertyMap.contains("Connectivity"))
- return static_cast<NMConnectivityState>(propertyMap.value("Connectivity").toUInt());
+ auto it = propertyMap.constFind(connectivityKey());
+ if (it != propertyMap.cend())
+ return static_cast<NMConnectivityState>(it->toUInt());
return QNetworkManagerInterface::NM_CONNECTIVITY_UNKNOWN;
}
@@ -102,7 +126,7 @@ static std::optional<QDBusInterface> getPrimaryDevice(const QDBusObjectPath &dev
std::optional<QDBusObjectPath> QNetworkManagerInterface::primaryConnectionDevicePath() const
{
- auto it = propertyMap.constFind(u"PrimaryConnection"_s);
+ auto it = propertyMap.constFind(primaryConnectionKey());
if (it != propertyMap.cend())
return it->value<QDBusObjectPath>();
return std::nullopt;
@@ -150,6 +174,11 @@ auto QNetworkManagerInterface::extractDeviceMetered(const QDBusObjectPath &devic
return static_cast<NMMetered>(metered.toUInt());
}
+void QNetworkManagerInterface::setBackend(QNetworkManagerNetworkInformationBackend *ourBackend)
+{
+ backend = ourBackend;
+}
+
void QNetworkManagerInterface::setProperties(const QString &interfaceName,
const QMap<QString, QVariant> &map,
const QStringList &invalidatedProperties)
@@ -169,18 +198,18 @@ void QNetworkManagerInterface::setProperties(const QString &interfaceName,
}
if (valueChanged) {
- if (i.key() == "State"_L1) {
+ if (i.key() == stateKey()) {
quint32 state = i.value().toUInt();
- Q_EMIT stateChanged(static_cast<NMState>(state));
- } else if (i.key() == "Connectivity"_L1) {
+ backend->onStateChanged(static_cast<NMState>(state));
+ } else if (i.key() == connectivityKey()) {
quint32 state = i.value().toUInt();
- Q_EMIT connectivityChanged(static_cast<NMConnectivityState>(state));
- } else if (i.key() == "PrimaryConnection"_L1) {
+ backend->onConnectivityChanged(static_cast<NMConnectivityState>(state));
+ } else if (i.key() == primaryConnectionKey()) {
const QDBusObjectPath devicePath = i->value<QDBusObjectPath>();
- Q_EMIT deviceTypeChanged(extractDeviceType(devicePath));
- Q_EMIT meteredChanged(extractDeviceMetered(devicePath));
+ backend->onDeviceTypeChanged(extractDeviceType(devicePath));
+ backend->onMeteredChanged(extractDeviceMetered(devicePath));
} else if (i.key() == "Metered"_L1) {
- Q_EMIT meteredChanged(static_cast<NMMetered>(i->toUInt()));
+ backend->onMeteredChanged(static_cast<NMMetered>(i->toUInt()));
}
}
}
diff --git a/src/plugins/networkinformation/networkmanager/qnetworkmanagerservice.h b/src/plugins/networkinformation/networkmanager/qnetworkmanagerservice.h
index 30e7945d28..5201e8485b 100644
--- a/src/plugins/networkinformation/networkmanager/qnetworkmanagerservice.h
+++ b/src/plugins/networkinformation/networkmanager/qnetworkmanagerservice.h
@@ -39,6 +39,7 @@ enum NMDeviceState {
QT_BEGIN_NAMESPACE
class QDBusObjectPath;
+class QNetworkManagerNetworkInformationBackend;
// This tiny class exists for the purpose of seeing if NetworkManager is available without
// initializing everything the derived/full class needs.
@@ -46,7 +47,7 @@ class QNetworkManagerInterfaceBase : public QDBusAbstractInterface
{
Q_OBJECT
public:
- QNetworkManagerInterfaceBase(QObject *parent = nullptr);
+ explicit QNetworkManagerInterfaceBase(QObject *parent = nullptr);
~QNetworkManagerInterfaceBase() = default;
static bool networkManagerAvailable();
@@ -128,19 +129,17 @@ public:
NM_METERED_GUESS_NO,
};
- QNetworkManagerInterface(QObject *parent = nullptr);
+ explicit QNetworkManagerInterface(QObject *parent = nullptr);
~QNetworkManagerInterface();
+ void setBackend(QNetworkManagerNetworkInformationBackend *ourBackend);
+
NMState state() const;
NMConnectivityState connectivityState() const;
NMDeviceType deviceType() const;
NMMetered meteredState() const;
-Q_SIGNALS:
- void stateChanged(NMState);
- void connectivityChanged(NMConnectivityState);
- void deviceTypeChanged(NMDeviceType);
- void meteredChanged(NMMetered);
+ bool isValid() const { return QDBusAbstractInterface::isValid() && validDBusConnection; }
private Q_SLOTS:
void setProperties(const QString &interfaceName, const QMap<QString, QVariant> &map,
@@ -155,13 +154,15 @@ private:
std::optional<QDBusObjectPath> primaryConnectionDevicePath() const;
QVariantMap propertyMap;
+ QNetworkManagerNetworkInformationBackend *backend = nullptr;
+ bool validDBusConnection = true;
};
class PropertiesDBusInterface : public QDBusAbstractInterface
{
public:
PropertiesDBusInterface(const QString &service, const QString &path, const QString &interface,
- const QDBusConnection &connection, QObject *parent = 0)
+ const QDBusConnection &connection, QObject *parent = nullptr)
: QDBusAbstractInterface(service, path, interface.toLatin1().data(), connection, parent)
{
}
diff --git a/src/plugins/networkinformation/scnetworkreachability/CMakeLists.txt b/src/plugins/networkinformation/scnetworkreachability/CMakeLists.txt
index 1c16d53587..a939ab4405 100644
--- a/src/plugins/networkinformation/scnetworkreachability/CMakeLists.txt
+++ b/src/plugins/networkinformation/scnetworkreachability/CMakeLists.txt
@@ -1,5 +1,5 @@
# Copyright (C) 2022 The Qt Company Ltd.
-# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+# SPDX-License-Identifier: BSD-3-Clause
qt_internal_add_plugin(QSCNetworkReachabilityNetworkInformationPlugin
OUTPUT_NAME qscnetworkreachability
diff --git a/src/plugins/platforminputcontexts/CMakeLists.txt b/src/plugins/platforminputcontexts/CMakeLists.txt
index 219ced16b9..78b3ec99d9 100644
--- a/src/plugins/platforminputcontexts/CMakeLists.txt
+++ b/src/plugins/platforminputcontexts/CMakeLists.txt
@@ -1,7 +1,5 @@
# Copyright (C) 2022 The Qt Company Ltd.
-# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-
-# Generated from platforminputcontexts.pro.
+# SPDX-License-Identifier: BSD-3-Clause
if(QT_FEATURE_xkbcommon)
add_subdirectory(compose)
diff --git a/src/plugins/platforminputcontexts/compose/CMakeLists.txt b/src/plugins/platforminputcontexts/compose/CMakeLists.txt
index 9d7e142577..9e71a7a07c 100644
--- a/src/plugins/platforminputcontexts/compose/CMakeLists.txt
+++ b/src/plugins/platforminputcontexts/compose/CMakeLists.txt
@@ -1,14 +1,12 @@
# Copyright (C) 2022 The Qt Company Ltd.
-# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-
-# Generated from compose.pro.
+# SPDX-License-Identifier: BSD-3-Clause
#####################################################################
## QComposePlatformInputContextPlugin Plugin:
#####################################################################
-qt_find_package(XKB) # special case
-pkg_get_variable(PKG_X11_PREFIX x11 prefix) # special case
+qt_find_package(XKB)
+pkg_get_variable(PKG_X11_PREFIX x11 prefix)
qt_internal_add_plugin(QComposePlatformInputContextPlugin
OUTPUT_NAME composeplatforminputcontextplugin
@@ -24,7 +22,3 @@ qt_internal_add_plugin(QComposePlatformInputContextPlugin
Qt::GuiPrivate
XKB::XKB
)
-
-#### Keys ignored in scope 1:.:.:compose.pro:<TRUE>:
-# OTHER_FILES = "$$PWD/compose.json"
-# PLUGIN_EXTENDS = "-"
diff --git a/src/plugins/platforminputcontexts/compose/qcomposeplatforminputcontext.cpp b/src/plugins/platforminputcontexts/compose/qcomposeplatforminputcontext.cpp
index 7e66e5fae8..3e74189076 100644
--- a/src/plugins/platforminputcontexts/compose/qcomposeplatforminputcontext.cpp
+++ b/src/plugins/platforminputcontexts/compose/qcomposeplatforminputcontext.cpp
@@ -108,8 +108,7 @@ bool QComposeInputContext::filterEvent(const QEvent *event)
case XKB_COMPOSE_NOTHING:
return false;
default:
- Q_UNREACHABLE();
- return false;
+ Q_UNREACHABLE_RETURN(false);
}
}
diff --git a/src/plugins/platforminputcontexts/ibus/CMakeLists.txt b/src/plugins/platforminputcontexts/ibus/CMakeLists.txt
index 4df2833470..54847e86fd 100644
--- a/src/plugins/platforminputcontexts/ibus/CMakeLists.txt
+++ b/src/plugins/platforminputcontexts/ibus/CMakeLists.txt
@@ -1,7 +1,5 @@
# Copyright (C) 2022 The Qt Company Ltd.
-# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-
-# Generated from ibus.pro.
+# SPDX-License-Identifier: BSD-3-Clause
#####################################################################
## QIbusPlatformInputContextPlugin Plugin:
@@ -25,7 +23,3 @@ qt_internal_add_plugin(QIbusPlatformInputContextPlugin
Qt::GuiPrivate
XKB::XKB
)
-
-#### Keys ignored in scope 1:.:.:ibus.pro:<TRUE>:
-# OTHER_FILES = "$$PWD/ibus.json"
-# PLUGIN_EXTENDS = "-"
diff --git a/src/plugins/platforminputcontexts/ibus/interfaces/org.freedesktop.IBus.InputContext.xml b/src/plugins/platforminputcontexts/ibus/interfaces/org.freedesktop.IBus.InputContext.xml
index 9c67a38c57..30fa7431c3 100644
--- a/src/plugins/platforminputcontexts/ibus/interfaces/org.freedesktop.IBus.InputContext.xml
+++ b/src/plugins/platforminputcontexts/ibus/interfaces/org.freedesktop.IBus.InputContext.xml
@@ -2,6 +2,12 @@
"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
<node>
<interface name="org.freedesktop.IBus.InputContext">
+ <property name="ClientCommitPreedit" type="(b)" access="readwrite">
+ <annotation name="org.qtproject.QtDBus.QtTypeName" value="QIBusPropTypeClientCommitPreedit"/>
+ </property>
+ <property name='ContentType' type='(uu)' access='readwrite'>
+ <annotation name="org.qtproject.QtDBus.QtTypeName" value="QIBusPropTypeContentType"/>
+ </property>
<method name="ProcessKeyEvent">
<arg name="keyval" direction="in" type="u"/>
<arg name="keycode" direction="in" type="u"/>
@@ -14,6 +20,12 @@
<arg name="w" direction="in" type="i"/>
<arg name="h" direction="in" type="i"/>
</method>
+ <method name='SetCursorLocationRelative'>
+ <arg name="x" direction="in" type="i"/>
+ <arg name="y" direction="in" type="i"/>
+ <arg name="w" direction="in" type="i"/>
+ <arg name="h" direction="in" type="i"/>
+ </method>
<method name="FocusIn"/>
<method name="FocusOut"/>
<method name="Reset"/>
@@ -56,6 +68,12 @@
<arg name="cursor_pos" type="u"/>
<arg name="visible" type="b"/>
</signal>
+ <signal name="UpdatePreeditTextWithMode">
+ <arg name="text" type="v"/>
+ <arg name="cursor_pos" type="u"/>
+ <arg name="visible" type="b"/>
+ <arg name="mode" type="u"/>
+ </signal>
<signal name="ShowPreeditText"/>
<signal name="HidePreeditText"/>
<signal name="UpdateAuxiliaryText">
diff --git a/src/plugins/platforminputcontexts/ibus/main.cpp b/src/plugins/platforminputcontexts/ibus/main.cpp
index 52d2cd0084..d74be4bedf 100644
--- a/src/plugins/platforminputcontexts/ibus/main.cpp
+++ b/src/plugins/platforminputcontexts/ibus/main.cpp
@@ -28,9 +28,12 @@ QIBusPlatformInputContext *QIbusPlatformInputContextPlugin::create(const QString
qDBusRegisterMetaType<QIBusAttribute>();
qDBusRegisterMetaType<QIBusAttributeList>();
qDBusRegisterMetaType<QIBusText>();
+ qDBusRegisterMetaType<QIBusPropTypeClientCommitPreedit>();
+ qDBusRegisterMetaType<QIBusPropTypeContentType>();
return new QIBusPlatformInputContext;
}
- return 0;
+
+ return nullptr;
}
QT_END_NAMESPACE
diff --git a/src/plugins/platforminputcontexts/ibus/qibusinputcontextproxy.cpp b/src/plugins/platforminputcontexts/ibus/qibusinputcontextproxy.cpp
index 8e2027272a..248abbc32b 100644
--- a/src/plugins/platforminputcontexts/ibus/qibusinputcontextproxy.cpp
+++ b/src/plugins/platforminputcontexts/ibus/qibusinputcontextproxy.cpp
@@ -2,7 +2,7 @@
* This file was generated by qdbusxml2cpp version 0.8
* Command line was: qdbusxml2cpp -N -p qibusinputcontextproxy -c QIBusInputContextProxy interfaces/org.freedesktop.IBus.InputContext.xml
*
- * qdbusxml2cpp is Copyright (C) 2015 The Qt Company Ltd.
+ * qdbusxml2cpp is Copyright (C) 2023 The Qt Company Ltd and other contributors.
*
* This is an auto-generated file.
* This file may have been hand-edited. Look for HAND-EDIT comments
@@ -24,4 +24,3 @@ QIBusInputContextProxy::~QIBusInputContextProxy()
{
}
-#include "moc_qibusinputcontextproxy.cpp"
diff --git a/src/plugins/platforminputcontexts/ibus/qibusinputcontextproxy.h b/src/plugins/platforminputcontexts/ibus/qibusinputcontextproxy.h
index 3d11706c06..82e78aa35b 100644
--- a/src/plugins/platforminputcontexts/ibus/qibusinputcontextproxy.h
+++ b/src/plugins/platforminputcontexts/ibus/qibusinputcontextproxy.h
@@ -2,25 +2,26 @@
* This file was generated by qdbusxml2cpp version 0.8
* Command line was: qdbusxml2cpp -N -p qibusinputcontextproxy -c QIBusInputContextProxy interfaces/org.freedesktop.IBus.InputContext.xml
*
- * qdbusxml2cpp is Copyright (C) 2015 The Qt Company Ltd.
+ * qdbusxml2cpp is Copyright (C) 2023 The Qt Company Ltd and other contributors.
*
* This is an auto-generated file.
* Do not edit! All changes made to it will be lost.
*/
-#ifndef QIBUSINPUTCONTEXTPROXY_H_1394889529
-#define QIBUSINPUTCONTEXTPROXY_H_1394889529
+#ifndef QIBUSINPUTCONTEXTPROXY_H
+#define QIBUSINPUTCONTEXTPROXY_H
-#include <QObject>
-#include <QByteArray>
-#include <QList>
-#include <QMap>
-#include <QString>
-#include <QStringList>
-#include <QVariant>
-#include <QDBusAbstractInterface>
-#include <QDBusPendingReply>
+#include <QtCore/QObject>
+#include <QtCore/QByteArray>
+#include <QtCore/QList>
+#include <QtCore/QMap>
+#include <QtCore/QString>
+#include <QtCore/QStringList>
+#include <QtCore/QVariant>
+#include <QtDBus/QtDBus>
+// Added for QIBusPropTypeClientCommitPreedit and QIBusPropTypeContentType
+#include "qibustypes.h"
/*
* Proxy class for interface org.freedesktop.IBus.InputContext
@@ -37,95 +38,114 @@ public:
~QIBusInputContextProxy();
+ Q_PROPERTY(QIBusPropTypeClientCommitPreedit ClientCommitPreedit READ clientCommitPreedit WRITE setClientCommitPreedit)
+ inline QIBusPropTypeClientCommitPreedit clientCommitPreedit() const
+ { return qvariant_cast< QIBusPropTypeClientCommitPreedit >(property("ClientCommitPreedit")); }
+ inline void setClientCommitPreedit(const QIBusPropTypeClientCommitPreedit &value)
+ { setProperty("ClientCommitPreedit", QVariant::fromValue(value)); }
+
+ Q_PROPERTY(QIBusPropTypeContentType ContentType READ contentType WRITE setContentType)
+ inline QIBusPropTypeContentType contentType() const
+ { return qvariant_cast< QIBusPropTypeContentType >(property("ContentType")); }
+ inline void setContentType(const QIBusPropTypeContentType &value)
+ { setProperty("ContentType", QVariant::fromValue(value)); }
+
public Q_SLOTS: // METHODS
inline QDBusPendingReply<> Destroy()
{
QList<QVariant> argumentList;
- return asyncCallWithArgumentList(QLatin1String("Destroy"), argumentList);
+ return asyncCallWithArgumentList(QStringLiteral("Destroy"), argumentList);
}
inline QDBusPendingReply<> Disable()
{
QList<QVariant> argumentList;
- return asyncCallWithArgumentList(QLatin1String("Disable"), argumentList);
+ return asyncCallWithArgumentList(QStringLiteral("Disable"), argumentList);
}
inline QDBusPendingReply<> Enable()
{
QList<QVariant> argumentList;
- return asyncCallWithArgumentList(QLatin1String("Enable"), argumentList);
+ return asyncCallWithArgumentList(QStringLiteral("Enable"), argumentList);
}
inline QDBusPendingReply<> FocusIn()
{
QList<QVariant> argumentList;
- return asyncCallWithArgumentList(QLatin1String("FocusIn"), argumentList);
+ return asyncCallWithArgumentList(QStringLiteral("FocusIn"), argumentList);
}
inline QDBusPendingReply<> FocusOut()
{
QList<QVariant> argumentList;
- return asyncCallWithArgumentList(QLatin1String("FocusOut"), argumentList);
+ return asyncCallWithArgumentList(QStringLiteral("FocusOut"), argumentList);
}
inline QDBusPendingReply<QDBusVariant> GetEngine()
{
QList<QVariant> argumentList;
- return asyncCallWithArgumentList(QLatin1String("GetEngine"), argumentList);
+ return asyncCallWithArgumentList(QStringLiteral("GetEngine"), argumentList);
}
inline QDBusPendingReply<bool> IsEnabled()
{
QList<QVariant> argumentList;
- return asyncCallWithArgumentList(QLatin1String("IsEnabled"), argumentList);
+ return asyncCallWithArgumentList(QStringLiteral("IsEnabled"), argumentList);
}
inline QDBusPendingReply<bool> ProcessKeyEvent(uint keyval, uint keycode, uint state)
{
QList<QVariant> argumentList;
argumentList << QVariant::fromValue(keyval) << QVariant::fromValue(keycode) << QVariant::fromValue(state);
- return asyncCallWithArgumentList(QLatin1String("ProcessKeyEvent"), argumentList);
+ return asyncCallWithArgumentList(QStringLiteral("ProcessKeyEvent"), argumentList);
}
inline QDBusPendingReply<> PropertyActivate(const QString &name, int state)
{
QList<QVariant> argumentList;
argumentList << QVariant::fromValue(name) << QVariant::fromValue(state);
- return asyncCallWithArgumentList(QLatin1String("PropertyActivate"), argumentList);
+ return asyncCallWithArgumentList(QStringLiteral("PropertyActivate"), argumentList);
}
inline QDBusPendingReply<> Reset()
{
QList<QVariant> argumentList;
- return asyncCallWithArgumentList(QLatin1String("Reset"), argumentList);
+ return asyncCallWithArgumentList(QStringLiteral("Reset"), argumentList);
}
inline QDBusPendingReply<> SetCapabilities(uint caps)
{
QList<QVariant> argumentList;
argumentList << QVariant::fromValue(caps);
- return asyncCallWithArgumentList(QLatin1String("SetCapabilities"), argumentList);
+ return asyncCallWithArgumentList(QStringLiteral("SetCapabilities"), argumentList);
}
inline QDBusPendingReply<> SetCursorLocation(int x, int y, int w, int h)
{
QList<QVariant> argumentList;
argumentList << QVariant::fromValue(x) << QVariant::fromValue(y) << QVariant::fromValue(w) << QVariant::fromValue(h);
- return asyncCallWithArgumentList(QLatin1String("SetCursorLocation"), argumentList);
+ return asyncCallWithArgumentList(QStringLiteral("SetCursorLocation"), argumentList);
+ }
+
+ inline QDBusPendingReply<> SetCursorLocationRelative(int x, int y, int w, int h)
+ {
+ QList<QVariant> argumentList;
+ argumentList << QVariant::fromValue(x) << QVariant::fromValue(y) << QVariant::fromValue(w) << QVariant::fromValue(h);
+ return asyncCallWithArgumentList(QStringLiteral("SetCursorLocationRelative"), argumentList);
}
inline QDBusPendingReply<> SetEngine(const QString &name)
{
QList<QVariant> argumentList;
argumentList << QVariant::fromValue(name);
- return asyncCallWithArgumentList(QLatin1String("SetEngine"), argumentList);
+ return asyncCallWithArgumentList(QStringLiteral("SetEngine"), argumentList);
}
inline QDBusPendingReply<> SetSurroundingText(const QDBusVariant &text, uint cursor_pos, uint anchor_pos)
{
QList<QVariant> argumentList;
argumentList << QVariant::fromValue(text) << QVariant::fromValue(cursor_pos) << QVariant::fromValue(anchor_pos);
- return asyncCallWithArgumentList(QLatin1String("SetSurroundingText"), argumentList);
+ return asyncCallWithArgumentList(QStringLiteral("SetSurroundingText"), argumentList);
}
Q_SIGNALS: // SIGNALS
@@ -149,6 +169,7 @@ Q_SIGNALS: // SIGNALS
void UpdateAuxiliaryText(const QDBusVariant &text, bool visible);
void UpdateLookupTable(const QDBusVariant &table, bool visible);
void UpdatePreeditText(const QDBusVariant &text, uint cursor_pos, bool visible);
+ void UpdatePreeditTextWithMode(const QDBusVariant &text, uint cursor_pos, bool visible, uint mode);
void UpdateProperty(const QDBusVariant &prop);
};
diff --git a/src/plugins/platforminputcontexts/ibus/qibusplatforminputcontext.cpp b/src/plugins/platforminputcontexts/ibus/qibusplatforminputcontext.cpp
index ca468f5ada..00c7884cda 100644
--- a/src/plugins/platforminputcontexts/ibus/qibusplatforminputcontext.cpp
+++ b/src/plugins/platforminputcontexts/ibus/qibusplatforminputcontext.cpp
@@ -8,6 +8,7 @@
#include <QWindow>
#include <QEvent>
#include <QFile>
+#include <QFileInfo>
#include <QStandardPaths>
#include <QDBusVariant>
#include <QDBusPendingReply>
@@ -26,6 +27,8 @@
#include <private/qguiapplication_p.h>
#include <private/qxkbcommon_p.h>
+#include <memory>
+
#include <sys/types.h>
#include <signal.h>
@@ -46,26 +49,35 @@ enum { debug = 0 };
class QIBusPlatformInputContextPrivate
{
+ Q_DISABLE_COPY_MOVE(QIBusPlatformInputContextPrivate)
public:
+ // This enum might be synced with IBusPreeditFocusMode
+ // in ibustypes.h of IBUS project
+ enum PreeditFocusMode {
+ PREEDIT_CLEAR = 0,
+ PREEDIT_COMMIT = 1,
+ };
+
QIBusPlatformInputContextPrivate();
~QIBusPlatformInputContextPrivate()
{
- delete context;
- delete bus;
- delete portalBus;
- delete connection;
+ // dereference QDBusConnection to actually disconnect
+ serviceWatcher.setConnection(QDBusConnection(QString()));
+ context = nullptr;
+ portalBus = nullptr;
+ bus = nullptr;
+ QDBusConnection::disconnectFromBus("QIBusProxy"_L1);
}
static QString getSocketPath();
- QDBusConnection *createConnection();
+ void createConnection();
void initBus();
void createBusProxy();
- QDBusConnection *connection;
- QIBusProxy *bus;
- QIBusProxyPortal *portalBus; // bus and portalBus are alternative.
- QIBusInputContextProxy *context;
+ std::unique_ptr<QIBusProxy> bus;
+ std::unique_ptr<QIBusProxyPortal> portalBus; // bus and portalBus are alternative.
+ std::unique_ptr<QIBusInputContextProxy> context;
QDBusServiceWatcher serviceWatcher;
bool usePortal; // return value of shouldConnectIbusPortal
@@ -75,6 +87,7 @@ public:
QList<QInputMethodEvent::Attribute> attributes;
bool needsSurroundingText;
QLocale locale;
+ PreeditFocusMode preeditFocusMode = PREEDIT_COMMIT; // for backward compatibility
};
@@ -166,10 +179,18 @@ void QIBusPlatformInputContext::commit()
return;
}
- if (!d->predit.isEmpty()) {
- QInputMethodEvent event;
- event.setCommitString(d->predit);
- QCoreApplication::sendEvent(input, &event);
+ if (d->preeditFocusMode == QIBusPlatformInputContextPrivate::PREEDIT_COMMIT) {
+ if (!d->predit.isEmpty()) {
+ QInputMethodEvent event;
+ event.setCommitString(d->predit);
+ QCoreApplication::sendEvent(input, &event);
+ }
+ } else {
+ if (!d->predit.isEmpty()) {
+ // Clear the existing preedit
+ QInputMethodEvent event;
+ QCoreApplication::sendEvent(input, &event);
+ }
}
d->context->Reset();
@@ -218,10 +239,31 @@ void QIBusPlatformInputContext::cursorRectChanged()
QWindow *inputWindow = qApp->focusWindow();
if (!inputWindow)
return;
- r.moveTopLeft(inputWindow->mapToGlobal(r.topLeft()));
+ if (!inputWindow->screen())
+ return;
+
+ if (QGuiApplication::platformName().startsWith("wayland"_L1)) {
+ auto margins = inputWindow->frameMargins();
+ r.translate(margins.left(), margins.top());
+ qreal scale = inputWindow->devicePixelRatio();
+ QRect newRect = QRect(r.x() * scale, r.y() * scale, r.width() * scale, r.height() * scale);
+ if (debug)
+ qDebug() << "microFocus" << newRect;
+ d->context->SetCursorLocationRelative(newRect.x(), newRect.y(),
+ newRect.width(), newRect.height());
+ return;
+ }
+
+ // x11/xcb
+ auto screenGeometry = inputWindow->screen()->geometry();
+ auto point = inputWindow->mapToGlobal(r.topLeft());
+ qreal scale = inputWindow->devicePixelRatio();
+ auto native = (point - screenGeometry.topLeft()) * scale + screenGeometry.topLeft();
+ QRect newRect(native, r.size() * scale);
if (debug)
- qDebug() << "microFocus" << r;
- d->context->SetCursorLocation(r.x(), r.y(), r.width(), r.height());
+ qDebug() << "microFocus" << newRect;
+ d->context->SetCursorLocation(newRect.x(), newRect.y(),
+ newRect.width(), newRect.height());
}
void QIBusPlatformInputContext::setFocusObject(QObject *object)
@@ -232,7 +274,7 @@ void QIBusPlatformInputContext::setFocusObject(QObject *object)
// It would seem natural here to call FocusOut() on the input method if we
// transition from an IME accepted focus object to one that does not accept it.
// Mysteriously however that is not sufficient to fix bug QTBUG-63066.
- if (!inputMethodAccepted())
+ if (object && !inputMethodAccepted())
return;
if (debug)
@@ -292,6 +334,15 @@ void QIBusPlatformInputContext::updatePreeditText(const QDBusVariant &text, uint
d->predit = t.text;
}
+void QIBusPlatformInputContext::updatePreeditTextWithMode(const QDBusVariant &text, uint cursorPos, bool visible, uint mode)
+{
+ updatePreeditText(text, cursorPos, visible);
+ if (mode > 0)
+ d->preeditFocusMode = QIBusPlatformInputContextPrivate::PreeditFocusMode::PREEDIT_COMMIT;
+ else
+ d->preeditFocusMode = QIBusPlatformInputContextPrivate::PreeditFocusMode::PREEDIT_CLEAR;
+}
+
void QIBusPlatformInputContext::forwardKeyEvent(uint keyval, uint keycode, uint state)
{
if (!qApp)
@@ -468,7 +519,7 @@ void QIBusPlatformInputContext::filterEventFinished(QDBusPendingCallWatcher *cal
if (!filtered) {
#ifndef QT_NO_CONTEXTMENU
if (type == QEvent::KeyPress && qtcode == Qt::Key_Menu
- && window != NULL) {
+ && window != nullptr) {
const QPoint globalPos = window->screen()->handle()->cursor()->pos();
const QPoint pos = window->mapFromGlobal(globalPos);
QWindowSystemInterfacePrivate::ContextMenuEvent contextMenuEvent(window, false, pos,
@@ -498,12 +549,12 @@ void QIBusPlatformInputContext::socketChanged(const QString &str)
m_timer.stop();
- if (d->context)
- disconnect(d->context);
- if (d->bus && d->bus->isValid())
- disconnect(d->bus);
- if (d->connection)
- d->connection->disconnectFromBus("QIBusProxy"_L1);
+ // dereference QDBusConnection to actually disconnect
+ d->serviceWatcher.setConnection(QDBusConnection(QString()));
+ d->context = nullptr;
+ d->bus = nullptr;
+ d->busConnected = false;
+ QDBusConnection::disconnectFromBus("QIBusProxy"_L1);
m_timer.start(100);
}
@@ -555,37 +606,34 @@ void QIBusPlatformInputContext::globalEngineChanged(const QString &engine_name)
void QIBusPlatformInputContext::connectToContextSignals()
{
if (d->bus && d->bus->isValid()) {
- connect(d->bus, SIGNAL(GlobalEngineChanged(QString)), this, SLOT(globalEngineChanged(QString)));
+ connect(d->bus.get(), SIGNAL(GlobalEngineChanged(QString)), this, SLOT(globalEngineChanged(QString)));
}
if (d->context) {
- connect(d->context, SIGNAL(CommitText(QDBusVariant)), SLOT(commitText(QDBusVariant)));
- connect(d->context, SIGNAL(UpdatePreeditText(QDBusVariant,uint,bool)), this, SLOT(updatePreeditText(QDBusVariant,uint,bool)));
- connect(d->context, SIGNAL(ForwardKeyEvent(uint,uint,uint)), this, SLOT(forwardKeyEvent(uint,uint,uint)));
- connect(d->context, SIGNAL(DeleteSurroundingText(int,uint)), this, SLOT(deleteSurroundingText(int,uint)));
- connect(d->context, SIGNAL(RequireSurroundingText()), this, SLOT(surroundingTextRequired()));
- connect(d->context, SIGNAL(HidePreeditText()), this, SLOT(hidePreeditText()));
- connect(d->context, SIGNAL(ShowPreeditText()), this, SLOT(showPreeditText()));
+ connect(d->context.get(), SIGNAL(CommitText(QDBusVariant)), SLOT(commitText(QDBusVariant)));
+ connect(d->context.get(), SIGNAL(UpdatePreeditText(QDBusVariant,uint,bool)), this, SLOT(updatePreeditText(QDBusVariant,uint,bool)));
+ connect(d->context.get(), SIGNAL(UpdatePreeditTextWithMode(QDBusVariant,uint,bool,uint)), this, SLOT(updatePreeditTextWithMode(QDBusVariant,uint,bool,uint)));
+ connect(d->context.get(), SIGNAL(ForwardKeyEvent(uint,uint,uint)), this, SLOT(forwardKeyEvent(uint,uint,uint)));
+ connect(d->context.get(), SIGNAL(DeleteSurroundingText(int,uint)), this, SLOT(deleteSurroundingText(int,uint)));
+ connect(d->context.get(), SIGNAL(RequireSurroundingText()), this, SLOT(surroundingTextRequired()));
+ connect(d->context.get(), SIGNAL(HidePreeditText()), this, SLOT(hidePreeditText()));
+ connect(d->context.get(), SIGNAL(ShowPreeditText()), this, SLOT(showPreeditText()));
}
}
-static inline bool checkRunningUnderFlatpak()
+static inline bool checkNeedPortalSupport()
{
- return !QStandardPaths::locate(QStandardPaths::RuntimeLocation, "flatpak-info"_L1).isEmpty();
+ return QFileInfo::exists("/.flatpak-info"_L1) || qEnvironmentVariableIsSet("SNAP");
}
static bool shouldConnectIbusPortal()
{
// honor the same env as ibus-gtk
- return (checkRunningUnderFlatpak() || !qgetenv("IBUS_USE_PORTAL").isNull());
+ return (checkNeedPortalSupport() || qEnvironmentVariableIsSet("IBUS_USE_PORTAL"));
}
QIBusPlatformInputContextPrivate::QIBusPlatformInputContextPrivate()
- : connection(0),
- bus(0),
- portalBus(0),
- context(0),
- usePortal(shouldConnectIbusPortal()),
+ : usePortal(shouldConnectIbusPortal()),
valid(false),
busConnected(false),
needsSurroundingText(false)
@@ -609,22 +657,23 @@ QIBusPlatformInputContextPrivate::QIBusPlatformInputContextPrivate()
void QIBusPlatformInputContextPrivate::initBus()
{
- connection = createConnection();
+ createConnection();
busConnected = false;
createBusProxy();
}
void QIBusPlatformInputContextPrivate::createBusProxy()
{
- if (!connection || !connection->isConnected())
+ QDBusConnection connection("QIBusProxy"_L1);
+ if (!connection.isConnected())
return;
const char* ibusService = usePortal ? "org.freedesktop.portal.IBus" : "org.freedesktop.IBus";
QDBusReply<QDBusObjectPath> ic;
if (usePortal) {
- portalBus = new QIBusProxyPortal(QLatin1StringView(ibusService),
- "/org/freedesktop/IBus"_L1,
- *connection);
+ portalBus = std::make_unique<QIBusProxyPortal>(QLatin1StringView(ibusService),
+ "/org/freedesktop/IBus"_L1,
+ connection);
if (!portalBus->isValid()) {
qWarning("QIBusPlatformInputContext: invalid portal bus.");
return;
@@ -632,9 +681,9 @@ void QIBusPlatformInputContextPrivate::createBusProxy()
ic = portalBus->CreateInputContext("QIBusInputContext"_L1);
} else {
- bus = new QIBusProxy(QLatin1StringView(ibusService),
- "/org/freedesktop/IBus"_L1,
- *connection);
+ bus = std::make_unique<QIBusProxy>(QLatin1StringView(ibusService),
+ "/org/freedesktop/IBus"_L1,
+ connection);
if (!bus->isValid()) {
qWarning("QIBusPlatformInputContext: invalid bus.");
return;
@@ -644,7 +693,7 @@ void QIBusPlatformInputContextPrivate::createBusProxy()
}
serviceWatcher.removeWatchedService(ibusService);
- serviceWatcher.setConnection(*connection);
+ serviceWatcher.setConnection(connection);
serviceWatcher.addWatchedService(ibusService);
if (!ic.isValid()) {
@@ -652,7 +701,7 @@ void QIBusPlatformInputContextPrivate::createBusProxy()
return;
}
- context = new QIBusInputContextProxy(QLatin1StringView(ibusService), ic.value().path(), *connection);
+ context = std::make_unique<QIBusInputContextProxy>(QLatin1StringView(ibusService), ic.value().path(), connection);
if (!context->isValid()) {
qWarning("QIBusPlatformInputContext: invalid input context.");
@@ -669,6 +718,8 @@ void QIBusPlatformInputContextPrivate::createBusProxy()
};
context->SetCapabilities(IBUS_CAP_PREEDIT_TEXT|IBUS_CAP_FOCUS|IBUS_CAP_SURROUNDING_TEXT);
+ context->setClientCommitPreedit(QIBusPropTypeClientCommitPreedit(true));
+
if (debug)
qDebug(">>>> bus connected!");
busConnected = true;
@@ -714,14 +765,16 @@ QString QIBusPlatformInputContextPrivate::getSocketPath()
u'-' + QString::fromLocal8Bit(host) + u'-' + QString::fromLocal8Bit(displayNumber);
}
-QDBusConnection *QIBusPlatformInputContextPrivate::createConnection()
+void QIBusPlatformInputContextPrivate::createConnection()
{
- if (usePortal)
- return new QDBusConnection(QDBusConnection::connectToBus(QDBusConnection::SessionBus, "QIBusProxy"_L1));
- QFile file(getSocketPath());
+ if (usePortal) {
+ QDBusConnection::connectToBus(QDBusConnection::SessionBus, "QIBusProxy"_L1);
+ return;
+ }
+ QFile file(getSocketPath());
if (!file.open(QFile::ReadOnly))
- return 0;
+ return;
QByteArray address;
int pid = -1;
@@ -740,9 +793,9 @@ QDBusConnection *QIBusPlatformInputContextPrivate::createConnection()
if (debug)
qDebug() << "IBUS_ADDRESS=" << address << "PID=" << pid;
if (address.isEmpty() || pid < 0 || kill(pid, 0) != 0)
- return 0;
+ return;
- return new QDBusConnection(QDBusConnection::connectToBus(QString::fromLatin1(address), "QIBusProxy"_L1));
+ QDBusConnection::connectToBus(QString::fromLatin1(address), "QIBusProxy"_L1);
}
QT_END_NAMESPACE
diff --git a/src/plugins/platforminputcontexts/ibus/qibusplatforminputcontext.h b/src/plugins/platforminputcontexts/ibus/qibusplatforminputcontext.h
index 33ddb7af5d..ef8c0b7c8f 100644
--- a/src/plugins/platforminputcontexts/ibus/qibusplatforminputcontext.h
+++ b/src/plugins/platforminputcontexts/ibus/qibusplatforminputcontext.h
@@ -14,6 +14,8 @@
#include <QTimer>
#include <QWindow>
+#include "qibustypes.h"
+
QT_BEGIN_NAMESPACE
class QIBusPlatformInputContextPrivate;
@@ -66,6 +68,7 @@ public:
public Q_SLOTS:
void commitText(const QDBusVariant &text);
void updatePreeditText(const QDBusVariant &text, uint cursor_pos, bool visible);
+ void updatePreeditTextWithMode(const QDBusVariant &text, uint cursor_pos, bool visible, uint mode);
void forwardKeyEvent(uint keyval, uint keycode, uint state);
void cursorRectChanged();
void deleteSurroundingText(int offset, uint n_chars);
diff --git a/src/plugins/platforminputcontexts/ibus/qibusproxy.h b/src/plugins/platforminputcontexts/ibus/qibusproxy.h
index c66e900664..73aff1a3d9 100644
--- a/src/plugins/platforminputcontexts/ibus/qibusproxy.h
+++ b/src/plugins/platforminputcontexts/ibus/qibusproxy.h
@@ -110,7 +110,7 @@ public Q_SLOTS: // METHODS
#endif
QIBusEngineDesc getGlobalEngine();
-private:
+private Q_SLOTS:
void globalEngineChanged(const QString &engine_name);
Q_SIGNALS: // SIGNALS
diff --git a/src/plugins/platforminputcontexts/ibus/qibusproxyportal.cpp b/src/plugins/platforminputcontexts/ibus/qibusproxyportal.cpp
index 54d8f731fb..dc5b37aa6e 100644
--- a/src/plugins/platforminputcontexts/ibus/qibusproxyportal.cpp
+++ b/src/plugins/platforminputcontexts/ibus/qibusproxyportal.cpp
@@ -2,7 +2,7 @@
* This file was generated by qdbusxml2cpp version 0.8
* Command line was: qdbusxml2cpp -N -p qibusproxyportal -c QIBusProxyPortal interfaces/org.freedesktop.IBus.Portal.xml
*
- * qdbusxml2cpp is Copyright (C) 2017 The Qt Company Ltd.
+ * qdbusxml2cpp is Copyright (C) 2023 The Qt Company Ltd and other contributors.
*
* This is an auto-generated file.
* This file may have been hand-edited. Look for HAND-EDIT comments
@@ -24,4 +24,3 @@ QIBusProxyPortal::~QIBusProxyPortal()
{
}
-#include "moc_qibusproxyportal.cpp"
diff --git a/src/plugins/platforminputcontexts/ibus/qibusproxyportal.h b/src/plugins/platforminputcontexts/ibus/qibusproxyportal.h
index 4b921db814..450205f12a 100644
--- a/src/plugins/platforminputcontexts/ibus/qibusproxyportal.h
+++ b/src/plugins/platforminputcontexts/ibus/qibusproxyportal.h
@@ -2,7 +2,7 @@
* This file was generated by qdbusxml2cpp version 0.8
* Command line was: qdbusxml2cpp -N -p qibusproxyportal -c QIBusProxyPortal interfaces/org.freedesktop.IBus.Portal.xml
*
- * qdbusxml2cpp is Copyright (C) 2017 The Qt Company Ltd.
+ * qdbusxml2cpp is Copyright (C) 2023 The Qt Company Ltd and other contributors.
*
* This is an auto-generated file.
* Do not edit! All changes made to it will be lost.
@@ -11,15 +11,14 @@
#ifndef QIBUSPROXYPORTAL_H
#define QIBUSPROXYPORTAL_H
-#include <QObject>
-#include <QByteArray>
-#include <QList>
-#include <QMap>
-#include <QString>
-#include <QStringList>
-#include <QVariant>
-#include <QDBusAbstractInterface>
-#include <QDBusPendingReply>
+#include <QtCore/QObject>
+#include <QtCore/QByteArray>
+#include <QtCore/QList>
+#include <QtCore/QMap>
+#include <QtCore/QString>
+#include <QtCore/QStringList>
+#include <QtCore/QVariant>
+#include <QtDBus/QtDBus>
/*
* Proxy class for interface org.freedesktop.IBus.Portal
diff --git a/src/plugins/platforminputcontexts/ibus/qibustypes.cpp b/src/plugins/platforminputcontexts/ibus/qibustypes.cpp
index 9d61d61eb3..ab1a244b6d 100644
--- a/src/plugins/platforminputcontexts/ibus/qibustypes.cpp
+++ b/src/plugins/platforminputcontexts/ibus/qibustypes.cpp
@@ -322,4 +322,44 @@ newest:
argument.endStructure();
}
+QIBusPropTypeClientCommitPreedit::QIBusPropTypeClientCommitPreedit(bool inClientCommitPreedit)
+ : clientCommitPreedit(inClientCommitPreedit)
+{
+}
+
+void QIBusPropTypeClientCommitPreedit::serializeTo(QDBusArgument &argument) const
+{
+ argument.beginStructure();
+ argument << clientCommitPreedit;
+ argument.endStructure();
+}
+
+void QIBusPropTypeClientCommitPreedit::deserializeFrom(const QDBusArgument &argument)
+{
+ argument.beginStructure();
+ argument >> clientCommitPreedit;
+ argument.endStructure();
+}
+
+QIBusPropTypeContentType::QIBusPropTypeContentType(unsigned int inPurpose, unsigned int inHints)
+ : purpose(inPurpose)
+ , hints(inHints)
+{
+}
+
+void QIBusPropTypeContentType::serializeTo(QDBusArgument &argument) const
+{
+ argument.beginStructure();
+ argument << purpose << hints;
+ argument.endStructure();
+}
+
+void QIBusPropTypeContentType::deserializeFrom(const QDBusArgument &argument)
+{
+ argument.beginStructure();
+ argument >> purpose;
+ argument >> hints;
+ argument.endStructure();
+}
+
QT_END_NAMESPACE
diff --git a/src/plugins/platforminputcontexts/ibus/qibustypes.h b/src/plugins/platforminputcontexts/ibus/qibustypes.h
index 60f24bcf54..b697e432a0 100644
--- a/src/plugins/platforminputcontexts/ibus/qibustypes.h
+++ b/src/plugins/platforminputcontexts/ibus/qibustypes.h
@@ -133,6 +133,44 @@ inline QDBusArgument &operator<<(QDBusArgument &argument, const QIBusEngineDesc
inline const QDBusArgument &operator>>(const QDBusArgument &argument, QIBusEngineDesc &desc)
{ desc.deserializeFrom(argument); return argument; }
+class QIBusPropTypeClientCommitPreedit
+{
+public:
+ QIBusPropTypeClientCommitPreedit() {};
+
+ QIBusPropTypeClientCommitPreedit(bool inClientCommitPreedit);
+
+ void serializeTo(QDBusArgument &argument) const;
+ void deserializeFrom(const QDBusArgument &argument);
+
+ bool clientCommitPreedit;
+};
+inline QDBusArgument &operator<<(QDBusArgument &argument, const QIBusPropTypeClientCommitPreedit &data)
+{ data.serializeTo(argument); return argument; }
+inline const QDBusArgument &operator>>(const QDBusArgument &argument, QIBusPropTypeClientCommitPreedit &data)
+{ data.deserializeFrom(argument); return argument; }
+
+class QIBusPropTypeContentType
+{
+public:
+ QIBusPropTypeContentType() {};
+
+ QIBusPropTypeContentType(unsigned int inPurpose, unsigned int inHint);
+
+ void serializeTo(QDBusArgument &argument) const;
+ void deserializeFrom(const QDBusArgument &argument);
+
+ unsigned int purpose;
+ unsigned int hints;
+};
+inline QDBusArgument &operator<<(QDBusArgument &argument, const QIBusPropTypeContentType &data)
+{ data.serializeTo(argument); return argument; }
+inline const QDBusArgument &operator>>(const QDBusArgument &argument, QIBusPropTypeContentType &data)
+{ data.deserializeFrom(argument); return argument; }
+
+Q_DECLARE_TYPEINFO(QIBusPropTypeClientCommitPreedit, Q_RELOCATABLE_TYPE);
+Q_DECLARE_TYPEINFO(QIBusPropTypeContentType, Q_RELOCATABLE_TYPE);
+
QT_END_NAMESPACE
Q_DECLARE_METATYPE(QIBusAttribute)
@@ -140,4 +178,6 @@ Q_DECLARE_METATYPE(QIBusAttributeList)
Q_DECLARE_METATYPE(QIBusText)
Q_DECLARE_METATYPE(QIBusEngineDesc)
+Q_DECLARE_METATYPE(QIBusPropTypeClientCommitPreedit)
+Q_DECLARE_METATYPE(QIBusPropTypeContentType)
#endif
diff --git a/src/plugins/platforms/CMakeLists.txt b/src/plugins/platforms/CMakeLists.txt
index 654fb6d1f2..69071a22c2 100644
--- a/src/plugins/platforms/CMakeLists.txt
+++ b/src/plugins/platforms/CMakeLists.txt
@@ -1,7 +1,5 @@
# Copyright (C) 2022 The Qt Company Ltd.
-# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-
-# Generated from platforms.pro.
+# SPDX-License-Identifier: BSD-3-Clause
if(ANDROID)
add_subdirectory(android)
@@ -44,16 +42,16 @@ if(QT_FEATURE_vnc AND TARGET Qt::Network)
add_subdirectory(vnc)
endif()
if(FREEBSD)
- # add_subdirectory(bsdfb) # special case TODO
+ # add_subdirectory(bsdfb) # TODO: QTBUG-112768
endif()
if(HAIKU)
- # add_subdirectory(haiku) # special case TODO
+ # add_subdirectory(haiku) # TODO: QTBUG-112768
endif()
if(WASM)
add_subdirectory(wasm)
endif()
if(QT_FEATURE_integrityfb)
- # add_subdirectory(integrity) # special case TODO
+ # add_subdirectory(integrity) # TODO: QTBUG-112768
endif()
if(QT_FEATURE_vkkhrdisplay)
add_subdirectory(vkkhrdisplay)
diff --git a/src/plugins/platforms/android/CMakeLists.txt b/src/plugins/platforms/android/CMakeLists.txt
index d12e36e206..d5a275a76c 100644
--- a/src/plugins/platforms/android/CMakeLists.txt
+++ b/src/plugins/platforms/android/CMakeLists.txt
@@ -1,37 +1,33 @@
-# Copyright (C) 2022 The Qt Company Ltd.
-# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-
-# Generated from android.pro.
+# Copyright (C) 2023 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
#####################################################################
## QAndroidIntegrationPlugin Plugin:
#####################################################################
-qt_find_package(EGL) # special case
+qt_find_package(EGL)
qt_internal_add_plugin(QAndroidIntegrationPlugin
OUTPUT_NAME qtforandroid
PLUGIN_TYPE platforms
- DEFAULT_IF ${QT_QPA_DEFAULT_PLATFORM} MATCHES android # special case
+ DEFAULT_IF ${QT_QPA_DEFAULT_PLATFORM} MATCHES android
SOURCES
androidcontentfileengine.cpp androidcontentfileengine.h
androiddeadlockprotector.h
androidjniaccessibility.cpp androidjniaccessibility.h
- androidjniclipboard.cpp androidjniclipboard.h
androidjniinput.cpp androidjniinput.h
androidjnimain.cpp androidjnimain.h
androidjnimenu.cpp androidjnimenu.h
- androidsurfaceclient.h
main.cpp
qandroidassetsfileenginehandler.cpp qandroidassetsfileenginehandler.h
qandroideventdispatcher.cpp qandroideventdispatcher.h
qandroidinputcontext.cpp qandroidinputcontext.h
qandroidplatformaccessibility.cpp qandroidplatformaccessibility.h
- qandroidplatformbackingstore.cpp qandroidplatformbackingstore.h
qandroidplatformclipboard.cpp qandroidplatformclipboard.h
qandroidplatformdialoghelpers.cpp qandroidplatformdialoghelpers.h
qandroidplatformfiledialoghelper.cpp qandroidplatformfiledialoghelper.h
qandroidplatformfontdatabase.cpp qandroidplatformfontdatabase.h
qandroidplatformforeignwindow.cpp qandroidplatformforeignwindow.h
+ qandroidplatformiconengine.cpp qandroidplatformiconengine.h
qandroidplatformintegration.cpp qandroidplatformintegration.h
qandroidplatformmenu.cpp qandroidplatformmenu.h
qandroidplatformmenubar.cpp qandroidplatformmenubar.h
@@ -44,8 +40,19 @@ qt_internal_add_plugin(QAndroidIntegrationPlugin
qandroidplatformtheme.cpp qandroidplatformtheme.h
qandroidplatformwindow.cpp qandroidplatformwindow.h
qandroidsystemlocale.cpp qandroidsystemlocale.h
- DEFINES
- QT_USE_QSTRINGBUILDER
+ androidwindowembedding.cpp androidwindowembedding.h
+ NO_UNITY_BUILD_SOURCES
+ # Conflicting symbols and macros with androidjnimain.cpp
+ # TODO: Unify the usage of FIND_AND_CHECK_CLASS, and similar
+ # macros. Q_JNI_FIND_AND_CHECK_CLASS in `qjnihelpers_p.h`
+ # seems to be doing most of the work already.
+ androidjnimenu.cpp
+ qandroidinputcontext.cpp
+ androidjniaccessibility.cpp
+ qandroidplatformdialoghelpers.cpp
+ # Conflicting JNI classes, and types
+ androidcontentfileengine.cpp
+ qandroidplatformintegration.cpp
INCLUDE_DIRECTORIES
${CMAKE_CURRENT_SOURCE_DIR}
${QtBase_SOURCE_DIR}/src/3rdparty/android
@@ -57,12 +64,9 @@ qt_internal_add_plugin(QAndroidIntegrationPlugin
Qt::GuiPrivate
android
jnigraphics
- EGL::EGL # special case
+ EGL::EGL
)
-#### Keys ignored in scope 1:.:.:android.pro:<TRUE>:
-# OTHER_FILES = "$$PWD/android.json"
-
## Scopes:
#####################################################################
@@ -80,4 +84,7 @@ qt_internal_extend_target(QAndroidIntegrationPlugin CONDITION QT_FEATURE_vulkan
SOURCES
qandroidplatformvulkaninstance.cpp qandroidplatformvulkaninstance.h
qandroidplatformvulkanwindow.cpp qandroidplatformvulkanwindow.h
+ NO_UNITY_BUILD_SOURCES
+ # To avoid undefined symbols due to missing VK_USE_PLATFORM_ANDROID_KHR
+ qandroidplatformvulkaninstance.cpp qandroidplatformvulkanwindow.cpp
)
diff --git a/src/plugins/platforms/android/androidcontentfileengine.cpp b/src/plugins/platforms/android/androidcontentfileengine.cpp
index 04862ccba6..db6c601f33 100644
--- a/src/plugins/platforms/android/androidcontentfileengine.cpp
+++ b/src/plugins/platforms/android/androidcontentfileengine.cpp
@@ -1,5 +1,5 @@
-// Copyright (C) 2019 Volker Krause <vkrause@kde.org>
-// Copyright (C) 2021 The Qt Company Ltd.
+// Copyright (C) 2019 Volker Krause <vkrause@kde.org>
+// Copyright (C) 2022 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#include "androidcontentfileengine.h"
@@ -7,16 +7,38 @@
#include <QtCore/qcoreapplication.h>
#include <QtCore/qjnienvironment.h>
#include <QtCore/qjniobject.h>
+#include <QtCore/qurl.h>
+#include <QtCore/qdatetime.h>
+#include <QtCore/qmimedatabase.h>
-#include <QDebug>
+QT_BEGIN_NAMESPACE
using namespace QNativeInterface;
using namespace Qt::StringLiterals;
-AndroidContentFileEngine::AndroidContentFileEngine(const QString &f)
- : m_file(f)
+Q_DECLARE_JNI_CLASS(ContentResolverType, "android/content/ContentResolver");
+Q_DECLARE_JNI_CLASS(UriType, "android/net/Uri");
+Q_DECLARE_JNI_CLASS(Uri, "android/net/Uri");
+Q_DECLARE_JNI_CLASS(ParcelFileDescriptorType, "android/os/ParcelFileDescriptor");
+Q_DECLARE_JNI_CLASS(CursorType, "android/database/Cursor");
+Q_DECLARE_JNI_TYPE(StringArray, "[Ljava/lang/String;");
+
+static QJniObject &contentResolverInstance()
+{
+ static QJniObject contentResolver;
+ if (!contentResolver.isValid()) {
+ contentResolver = QJniObject(QNativeInterface::QAndroidApplication::context())
+ .callMethod<QtJniTypes::ContentResolverType>("getContentResolver");
+ }
+
+ return contentResolver;
+}
+
+AndroidContentFileEngine::AndroidContentFileEngine(const QString &filename)
+ : m_initialFile(filename),
+ m_documentFile(DocumentFile::parseFromAnyUri(filename))
{
- setFileName(f);
+ setFileName(filename);
}
bool AndroidContentFileEngine::open(QIODevice::OpenMode openMode,
@@ -29,6 +51,27 @@ bool AndroidContentFileEngine::open(QIODevice::OpenMode openMode,
}
if (openMode & QFileDevice::WriteOnly) {
openModeStr += u'w';
+ if (!m_documentFile->exists()) {
+ if (QUrl(m_initialFile).path().startsWith("/tree/"_L1)) {
+ const int lastSeparatorIndex = m_initialFile.lastIndexOf('/');
+ const QString fileName = m_initialFile.mid(lastSeparatorIndex + 1);
+
+ QString mimeType;
+ const auto mimeTypes = QMimeDatabase().mimeTypesForFileName(fileName);
+ if (!mimeTypes.empty())
+ mimeType = mimeTypes.first().name();
+ else
+ mimeType = "application/octet-stream";
+
+ if (m_documentFile->parent()) {
+ auto createdFile = m_documentFile->parent()->createFile(mimeType, fileName);
+ if (createdFile)
+ m_documentFile = createdFile;
+ }
+ } else {
+ qWarning() << "open(): non-existent content URI with a document type provided";
+ }
+ }
}
if (openMode & QFileDevice::Truncate) {
openModeStr += u't';
@@ -36,21 +79,19 @@ bool AndroidContentFileEngine::open(QIODevice::OpenMode openMode,
openModeStr += u'a';
}
- m_pfd = QJniObject::callStaticObjectMethod("org/qtproject/qt/android/QtNative",
- "openParcelFdForContentUrl",
- "(Landroid/content/Context;Ljava/lang/String;Ljava/lang/String;)Landroid/os/ParcelFileDescriptor;",
- QAndroidApplication::context(),
- QJniObject::fromString(fileName(DefaultName)).object(),
- QJniObject::fromString(openModeStr).object());
+ m_pfd = contentResolverInstance().callMethod<
+ QtJniTypes::ParcelFileDescriptorType, QtJniTypes::UriType, jstring>(
+ "openFileDescriptor",
+ m_documentFile->uri().object(),
+ QJniObject::fromString(openModeStr).object<jstring>());
if (!m_pfd.isValid())
return false;
- const auto fd = m_pfd.callMethod<jint>("getFd", "()I");
+ const auto fd = m_pfd.callMethod<jint>("getFd");
if (fd < 0) {
- m_pfd.callMethod<void>("close", "()V");
- m_pfd = QJniObject();
+ closeNativeFileDescriptor();
return false;
}
@@ -59,47 +100,130 @@ bool AndroidContentFileEngine::open(QIODevice::OpenMode openMode,
bool AndroidContentFileEngine::close()
{
+ closeNativeFileDescriptor();
+ return QFSFileEngine::close();
+}
+
+void AndroidContentFileEngine::closeNativeFileDescriptor()
+{
if (m_pfd.isValid()) {
- m_pfd.callMethod<void>("close", "()V");
+ m_pfd.callMethod<void>("close");
m_pfd = QJniObject();
}
-
- return QFSFileEngine::close();
}
qint64 AndroidContentFileEngine::size() const
{
- const jlong size = QJniObject::callStaticMethod<jlong>(
- "org/qtproject/qt/android/QtNative", "getSize",
- "(Landroid/content/Context;Ljava/lang/String;)J", QAndroidApplication::context(),
- QJniObject::fromString(fileName(DefaultName)).object());
- return (qint64)size;
+ return m_documentFile->length();
+}
+
+bool AndroidContentFileEngine::remove()
+{
+ return m_documentFile->remove();
+}
+
+bool AndroidContentFileEngine::rename(const QString &newName)
+{
+ if (m_documentFile->rename(newName)) {
+ m_initialFile = m_documentFile->uri().toString();
+ return true;
+ }
+ return false;
+}
+
+bool AndroidContentFileEngine::mkdir(const QString &dirName, bool createParentDirectories,
+ std::optional<QFileDevice::Permissions> permissions) const
+{
+ Q_UNUSED(permissions)
+
+ QString tmp = dirName;
+ tmp.remove(m_initialFile);
+
+ QStringList dirParts = tmp.split(u'/');
+ dirParts.removeAll("");
+
+ if (dirParts.isEmpty())
+ return false;
+
+ auto createdDir = m_documentFile;
+ bool allDirsCreated = true;
+ for (const auto &dir : dirParts) {
+ // Find if the sub-dir already exists and then don't re-create it
+ bool subDirExists = false;
+ for (const DocumentFilePtr &subDir : m_documentFile->listFiles()) {
+ if (dir == subDir->name() && subDir->isDirectory()) {
+ createdDir = subDir;
+ subDirExists = true;
+ }
+ }
+
+ if (!subDirExists) {
+ createdDir = createdDir->createDirectory(dir);
+ if (!createdDir) {
+ allDirsCreated = false;
+ break;
+ }
+ }
+
+ if (!createParentDirectories)
+ break;
+ }
+
+ return allDirsCreated;
+}
+
+bool AndroidContentFileEngine::rmdir(const QString &dirName, bool recurseParentDirectories) const
+{
+ if (recurseParentDirectories)
+ qWarning() << "rmpath(): Unsupported for Content URIs";
+
+ const QString dirFileName = QUrl(dirName).fileName();
+ bool deleted = false;
+ for (const DocumentFilePtr &dir : m_documentFile->listFiles()) {
+ if (dirFileName == dir->name() && dir->isDirectory()) {
+ deleted = dir->remove();
+ break;
+ }
+ }
+
+ return deleted;
+}
+
+QByteArray AndroidContentFileEngine::id() const
+{
+ return m_documentFile->id().toUtf8();
+}
+
+QDateTime AndroidContentFileEngine::fileTime(QFile::FileTime time) const
+{
+ switch (time) {
+ case QFile::FileModificationTime:
+ return m_documentFile->lastModified();
+ break;
+ default:
+ break;
+ }
+
+ return QDateTime();
}
AndroidContentFileEngine::FileFlags AndroidContentFileEngine::fileFlags(FileFlags type) const
{
- FileFlags commonFlags(ReadOwnerPerm|ReadUserPerm|ReadGroupPerm|ReadOtherPerm|ExistsFlag);
FileFlags flags;
- const bool isDir = QJniObject::callStaticMethod<jboolean>(
- "org/qtproject/qt/android/QtNative", "checkIfDir",
- "(Landroid/content/Context;Ljava/lang/String;)Z", QAndroidApplication::context(),
- QJniObject::fromString(fileName(DefaultName)).object());
- // If it is a directory then we know it exists so there is no reason to explicitly check
- const bool exists = isDir ? true : QJniObject::callStaticMethod<jboolean>(
- "org/qtproject/qt/android/QtNative", "checkFileExists",
- "(Landroid/content/Context;Ljava/lang/String;)Z", QAndroidApplication::context(),
- QJniObject::fromString(fileName(DefaultName)).object());
- if (!exists && !isDir)
+ if (!m_documentFile->exists())
+ return flags;
+
+ flags = ExistsFlag;
+ if (!m_documentFile->canRead())
return flags;
- if (isDir) {
- flags = DirectoryType | commonFlags;
+
+ flags |= ReadOwnerPerm|ReadUserPerm|ReadGroupPerm|ReadOtherPerm;
+
+ if (m_documentFile->isDirectory()) {
+ flags |= DirectoryType;
} else {
- flags = FileType | commonFlags;
- const bool writable = QJniObject::callStaticMethod<jboolean>(
- "org/qtproject/qt/android/QtNative", "checkIfWritable",
- "(Landroid/content/Context;Ljava/lang/String;)Z", QAndroidApplication::context(),
- QJniObject::fromString(fileName(DefaultName)).object());
- if (writable)
+ flags |= FileType;
+ if (m_documentFile->canWrite())
flags |= WriteOwnerPerm|WriteUserPerm|WriteGroupPerm|WriteOtherPerm;
}
return type & flags;
@@ -114,41 +238,39 @@ QString AndroidContentFileEngine::fileName(FileName f) const
case DefaultName:
case AbsoluteName:
case CanonicalName:
- return m_file;
+ return m_documentFile->uri().toString();
case BaseName:
- {
- const qsizetype pos = m_file.lastIndexOf(u'/');
- return m_file.mid(pos);
- }
+ return m_documentFile->name();
default:
- return QString();
+ break;
}
-}
-QAbstractFileEngine::Iterator *AndroidContentFileEngine::beginEntryList(QDir::Filters filters, const QStringList &filterNames)
-{
- return new AndroidContentFileEngineIterator(filters, filterNames);
+ return QString();
}
-QAbstractFileEngine::Iterator *AndroidContentFileEngine::endEntryList()
+QAbstractFileEngine::IteratorUniquePtr
+AndroidContentFileEngine::beginEntryList(const QString &path, QDir::Filters filters,
+ const QStringList &filterNames)
{
- return nullptr;
+ return std::make_unique<AndroidContentFileEngineIterator>(path, filters, filterNames);
}
AndroidContentFileEngineHandler::AndroidContentFileEngineHandler() = default;
AndroidContentFileEngineHandler::~AndroidContentFileEngineHandler() = default;
-QAbstractFileEngine* AndroidContentFileEngineHandler::create(const QString &fileName) const
+std::unique_ptr<QAbstractFileEngine>
+AndroidContentFileEngineHandler::create(const QString &fileName) const
{
- if (!fileName.startsWith("content"_L1))
- return nullptr;
+ if (fileName.startsWith("content"_L1))
+ return std::make_unique<AndroidContentFileEngine>(fileName);
+
+ return {};
- return new AndroidContentFileEngine(fileName);
}
-AndroidContentFileEngineIterator::AndroidContentFileEngineIterator(QDir::Filters filters,
- const QStringList &filterNames)
- : QAbstractFileEngineIterator(filters, filterNames)
+AndroidContentFileEngineIterator::AndroidContentFileEngineIterator(
+ const QString &path, QDir::Filters filters, const QStringList &filterNames)
+ : QAbstractFileEngineIterator(path, filters, filterNames)
{
}
@@ -156,47 +278,557 @@ AndroidContentFileEngineIterator::~AndroidContentFileEngineIterator()
{
}
-QString AndroidContentFileEngineIterator::next()
+bool AndroidContentFileEngineIterator::advance()
{
- if (!hasNext())
- return QString();
- ++m_index;
- return currentFilePath();
-}
+ if (m_index == -1 && m_files.isEmpty()) {
+ const auto currentPath = path();
+ if (currentPath.isEmpty())
+ return false;
-bool AndroidContentFileEngineIterator::hasNext() const
-{
- if (m_index == -1) {
- if (path().isEmpty())
+ const auto iterDoc = DocumentFile::parseFromAnyUri(currentPath);
+ if (iterDoc->isDirectory())
+ for (const auto &doc : iterDoc->listFiles())
+ m_files.append(doc);
+ if (m_files.isEmpty())
return false;
- const bool isDir = QJniObject::callStaticMethod<jboolean>(
- "org/qtproject/qt/android/QtNative", "checkIfDir",
- "(Landroid/content/Context;Ljava/lang/String;)Z",
- QAndroidApplication::context(),
- QJniObject::fromString(path()).object());
- if (isDir) {
- QJniObject objArray = QJniObject::callStaticObjectMethod("org/qtproject/qt/android/QtNative",
- "listContentsFromTreeUri",
- "(Landroid/content/Context;Ljava/lang/String;)[Ljava/lang/String;",
- QAndroidApplication::context(),
- QJniObject::fromString(path()).object());
- if (objArray.isValid()) {
- QJniEnvironment env;
- const jsize length = env->GetArrayLength(objArray.object<jarray>());
- for (int i = 0; i != length; ++i) {
- m_entries << QJniObject(env->GetObjectArrayElement(
- objArray.object<jobjectArray>(), i)).toString();
- }
- }
- }
m_index = 0;
+ return true;
+ }
+
+ if (m_index < m_files.size() - 1) {
+ ++m_index;
+ return true;
}
- return m_index < m_entries.size();
+
+ return false;
}
QString AndroidContentFileEngineIterator::currentFileName() const
{
- if (m_index <= 0 || m_index > m_entries.size())
+ if (m_index < 0 || m_index > m_files.size())
+ return QString();
+ return m_files.at(m_index)->name();
+}
+
+QString AndroidContentFileEngineIterator::currentFilePath() const
+{
+ if (m_index < 0 || m_index > m_files.size())
return QString();
- return m_entries.at(m_index - 1);
+ return m_files.at(m_index)->uri().toString();
+}
+
+// Start of Cursor
+
+class Cursor
+{
+public:
+ explicit Cursor(const QJniObject &object)
+ : m_object{object} { }
+
+ ~Cursor()
+ {
+ if (m_object.isValid())
+ m_object.callMethod<void>("close");
+ }
+
+ enum Type {
+ FIELD_TYPE_NULL = 0x00000000,
+ FIELD_TYPE_INTEGER = 0x00000001,
+ FIELD_TYPE_FLOAT = 0x00000002,
+ FIELD_TYPE_STRING = 0x00000003,
+ FIELD_TYPE_BLOB = 0x00000004
+ };
+
+ QVariant data(int columnIndex) const
+ {
+ int type = m_object.callMethod<jint>("getType", columnIndex);
+ switch (type) {
+ case FIELD_TYPE_NULL:
+ return {};
+ case FIELD_TYPE_INTEGER:
+ return QVariant::fromValue(m_object.callMethod<jlong>("getLong", columnIndex));
+ case FIELD_TYPE_FLOAT:
+ return QVariant::fromValue(m_object.callMethod<jdouble>("getDouble", columnIndex));
+ case FIELD_TYPE_STRING:
+ return QVariant::fromValue(m_object.callMethod<jstring>("getString",
+ columnIndex).toString());
+ case FIELD_TYPE_BLOB: {
+ auto blob = m_object.callMethod<jbyteArray>("getBlob", columnIndex);
+ QJniEnvironment env;
+ const auto blobArray = blob.object<jbyteArray>();
+ const int size = env->GetArrayLength(blobArray);
+ const auto byteArray = env->GetByteArrayElements(blobArray, nullptr);
+ QByteArray data{reinterpret_cast<const char *>(byteArray), size};
+ env->ReleaseByteArrayElements(blobArray, byteArray, 0);
+ return QVariant::fromValue(data);
+ }
+ }
+ return {};
+ }
+
+ static std::unique_ptr<Cursor> queryUri(const QJniObject &uri,
+ const QStringList &projection = {},
+ const QString &selection = {},
+ const QStringList &selectionArgs = {},
+ const QString &sortOrder = {})
+ {
+ auto cursor = contentResolverInstance().callMethod<QtJniTypes::CursorType>(
+ "query",
+ uri.object<QtJniTypes::UriType>(),
+ projection.isEmpty() ?
+ nullptr : fromStringList(projection).object<QtJniTypes::StringArray>(),
+ selection.isEmpty() ? nullptr : QJniObject::fromString(selection).object<jstring>(),
+ selectionArgs.isEmpty() ?
+ nullptr : fromStringList(selectionArgs).object<QtJniTypes::StringArray>(),
+ sortOrder.isEmpty() ? nullptr : QJniObject::fromString(sortOrder).object<jstring>());
+ if (!cursor.isValid())
+ return {};
+ return std::make_unique<Cursor>(cursor);
+ }
+
+ static QVariant queryColumn(const QJniObject &uri, const QString &column)
+ {
+ const auto query = queryUri(uri, {column});
+ if (!query)
+ return {};
+
+ if (query->rowCount() != 1 || query->columnCount() != 1)
+ return {};
+ query->moveToFirst();
+ return query->data(0);
+ }
+
+ bool isNull(int columnIndex) const
+ {
+ return m_object.callMethod<jboolean>("isNull", columnIndex);
+ }
+
+ int columnCount() const { return m_object.callMethod<jint>("getColumnCount"); }
+ int rowCount() const { return m_object.callMethod<jint>("getCount"); }
+ int row() const { return m_object.callMethod<jint>("getPosition"); }
+ bool isFirst() const { return m_object.callMethod<jboolean>("isFirst"); }
+ bool isLast() const { return m_object.callMethod<jboolean>("isLast"); }
+ bool moveToFirst() { return m_object.callMethod<jboolean>("moveToFirst"); }
+ bool moveToLast() { return m_object.callMethod<jboolean>("moveToLast"); }
+ bool moveToNext() { return m_object.callMethod<jboolean>("moveToNext"); }
+
+private:
+ static QJniObject fromStringList(const QStringList &list)
+ {
+ QJniEnvironment env;
+ auto array = env->NewObjectArray(list.size(), env.findClass("java/lang/String"), nullptr);
+ for (int i = 0; i < list.size(); ++i)
+ env->SetObjectArrayElement(array, i, QJniObject::fromString(list[i]).object());
+ return QJniObject::fromLocalRef(array);
+ }
+
+ QJniObject m_object;
+};
+
+// End of Cursor
+
+// Start of DocumentsContract
+
+Q_DECLARE_JNI_CLASS(DocumentsContract, "android/provider/DocumentsContract");
+
+/*!
+ *
+ * DocumentsContract Api.
+ * Check https://developer.android.com/reference/android/provider/DocumentsContract
+ * for more information.
+ *
+ * \note This does not implement all facilities of the native API.
+ *
+ */
+namespace DocumentsContract
+{
+
+namespace Document {
+const QLatin1String COLUMN_DISPLAY_NAME("_display_name");
+const QLatin1String COLUMN_DOCUMENT_ID("document_id");
+const QLatin1String COLUMN_FLAGS("flags");
+const QLatin1String COLUMN_LAST_MODIFIED("last_modified");
+const QLatin1String COLUMN_MIME_TYPE("mime_type");
+const QLatin1String COLUMN_SIZE("_size");
+
+constexpr int FLAG_DIR_SUPPORTS_CREATE = 0x00000008;
+constexpr int FLAG_SUPPORTS_DELETE = 0x00000004;
+constexpr int FLAG_SUPPORTS_MOVE = 0x00000100;
+constexpr int FLAG_SUPPORTS_RENAME = 0x00000040;
+constexpr int FLAG_SUPPORTS_WRITE = 0x00000002;
+constexpr int FLAG_VIRTUAL_DOCUMENT = 0x00000200;
+
+const QLatin1String MIME_TYPE_DIR("vnd.android.document/directory");
+} // namespace Document
+
+QString documentId(const QJniObject &uri)
+{
+ return QJniObject::callStaticMethod<jstring, QtJniTypes::UriType>(
+ QtJniTypes::Traits<QtJniTypes::DocumentsContract>::className(),
+ "getDocumentId",
+ uri.object()).toString();
+}
+
+QString treeDocumentId(const QJniObject &uri)
+{
+ return QJniObject::callStaticMethod<jstring, QtJniTypes::UriType>(
+ QtJniTypes::Traits<QtJniTypes::DocumentsContract>::className(),
+ "getTreeDocumentId",
+ uri.object()).toString();
+}
+
+QJniObject buildChildDocumentsUriUsingTree(const QJniObject &uri, const QString &parentDocumentId)
+{
+ return QJniObject::callStaticMethod<QtJniTypes::UriType>(
+ QtJniTypes::Traits<QtJniTypes::DocumentsContract>::className(),
+ "buildChildDocumentsUriUsingTree",
+ uri.object<QtJniTypes::UriType>(),
+ QJniObject::fromString(parentDocumentId).object<jstring>());
+
}
+
+QJniObject buildDocumentUriUsingTree(const QJniObject &treeUri, const QString &documentId)
+{
+ return QJniObject::callStaticMethod<QtJniTypes::UriType>(
+ QtJniTypes::Traits<QtJniTypes::DocumentsContract>::className(),
+ "buildDocumentUriUsingTree",
+ treeUri.object<QtJniTypes::UriType>(),
+ QJniObject::fromString(documentId).object<jstring>());
+}
+
+bool isDocumentUri(const QJniObject &uri)
+{
+ return QJniObject::callStaticMethod<jboolean>(
+ QtJniTypes::Traits<QtJniTypes::DocumentsContract>::className(),
+ "isDocumentUri",
+ QNativeInterface::QAndroidApplication::context(),
+ uri.object<QtJniTypes::UriType>());
+}
+
+bool isTreeUri(const QJniObject &uri)
+{
+ return QJniObject::callStaticMethod<jboolean>(
+ QtJniTypes::Traits<QtJniTypes::DocumentsContract>::className(),
+ "isTreeUri",
+ uri.object<QtJniTypes::UriType>());
+}
+
+QJniObject createDocument(const QJniObject &parentDocumentUri, const QString &mimeType,
+ const QString &displayName)
+{
+ return QJniObject::callStaticMethod<QtJniTypes::UriType>(
+ QtJniTypes::Traits<QtJniTypes::DocumentsContract>::className(),
+ "createDocument",
+ contentResolverInstance().object<QtJniTypes::ContentResolverType>(),
+ parentDocumentUri.object<QtJniTypes::UriType>(),
+ QJniObject::fromString(mimeType).object<jstring>(),
+ QJniObject::fromString(displayName).object<jstring>());
+}
+
+bool deleteDocument(const QJniObject &documentUri)
+{
+ const int flags = Cursor::queryColumn(documentUri, Document::COLUMN_FLAGS).toInt();
+ if (!(flags & Document::FLAG_SUPPORTS_DELETE))
+ return {};
+
+ return QJniObject::callStaticMethod<jboolean>(
+ QtJniTypes::Traits<QtJniTypes::DocumentsContract>::className(),
+ "deleteDocument",
+ contentResolverInstance().object<QtJniTypes::ContentResolverType>(),
+ documentUri.object<QtJniTypes::UriType>());
+}
+
+QJniObject moveDocument(const QJniObject &sourceDocumentUri,
+ const QJniObject &sourceParentDocumentUri,
+ const QJniObject &targetParentDocumentUri)
+{
+ const int flags = Cursor::queryColumn(sourceDocumentUri, Document::COLUMN_FLAGS).toInt();
+ if (!(flags & Document::FLAG_SUPPORTS_MOVE))
+ return {};
+
+ return QJniObject::callStaticMethod<QtJniTypes::UriType>(
+ QtJniTypes::Traits<QtJniTypes::DocumentsContract>::className(),
+ "moveDocument",
+ contentResolverInstance().object<QtJniTypes::ContentResolverType>(),
+ sourceDocumentUri.object<QtJniTypes::UriType>(),
+ sourceParentDocumentUri.object<QtJniTypes::UriType>(),
+ targetParentDocumentUri.object<QtJniTypes::UriType>());
+}
+
+QJniObject renameDocument(const QJniObject &documentUri, const QString &displayName)
+{
+ const int flags = Cursor::queryColumn(documentUri, Document::COLUMN_FLAGS).toInt();
+ if (!(flags & Document::FLAG_SUPPORTS_RENAME))
+ return {};
+
+ return QJniObject::callStaticMethod<QtJniTypes::UriType>(
+ QtJniTypes::Traits<QtJniTypes::DocumentsContract>::className(),
+ "renameDocument",
+ contentResolverInstance().object<QtJniTypes::ContentResolverType>(),
+ documentUri.object<QtJniTypes::UriType>(),
+ QJniObject::fromString(displayName).object<jstring>());
+}
+} // End DocumentsContract namespace
+
+// Start of DocumentFile
+
+using namespace DocumentsContract;
+
+namespace {
+class MakeableDocumentFile : public DocumentFile
+{
+public:
+ MakeableDocumentFile(const QJniObject &uri, const DocumentFilePtr &parent = {})
+ : DocumentFile(uri, parent)
+ {}
+};
+}
+
+DocumentFile::DocumentFile(const QJniObject &uri,
+ const DocumentFilePtr &parent)
+ : m_uri{uri}
+ , m_parent{parent}
+{}
+
+QJniObject parseUri(const QString &uri)
+{
+ QString uriToParse = uri;
+ if (uriToParse.contains(' '))
+ uriToParse.replace(' ', QUrl::toPercentEncoding(" "));
+
+ return QJniObject::callStaticMethod<QtJniTypes::UriType>(
+ QtJniTypes::Traits<QtJniTypes::Uri>::className(),
+ "parse",
+ QJniObject::fromString(uriToParse).object<jstring>());
+}
+
+DocumentFilePtr DocumentFile::parseFromAnyUri(const QString &fileName)
+{
+ const QString encodedUri = QUrl(fileName).toEncoded();
+ const QJniObject uri = parseUri(encodedUri);
+
+ if (DocumentsContract::isDocumentUri(uri) || !DocumentsContract::isTreeUri(uri))
+ return fromSingleUri(uri);
+
+ const QString documentType = "/document/"_L1;
+ const QString treeType = "/tree/"_L1;
+
+ const int treeIndex = encodedUri.indexOf(treeType);
+ const int documentIndex = encodedUri.indexOf(documentType);
+ const int index = fileName.lastIndexOf("/");
+
+ if (index <= treeIndex + treeType.size() || index <= documentIndex + documentType.size())
+ return fromTreeUri(uri);
+
+ const QString parentUrl = encodedUri.left(index);
+ DocumentFilePtr parentDocFile = fromTreeUri(parseUri(parentUrl));
+
+ const QString baseName = encodedUri.mid(index);
+ const QString fileUrl = parentUrl + QUrl::toPercentEncoding(baseName);
+
+ DocumentFilePtr docFile = std::make_shared<MakeableDocumentFile>(parseUri(fileUrl));
+ if (parentDocFile && parentDocFile->isDirectory())
+ docFile->m_parent = parentDocFile;
+
+ return docFile;
+}
+
+DocumentFilePtr DocumentFile::fromSingleUri(const QJniObject &uri)
+{
+ return std::make_shared<MakeableDocumentFile>(uri);
+}
+
+DocumentFilePtr DocumentFile::fromTreeUri(const QJniObject &treeUri)
+{
+ QString docId;
+ if (isDocumentUri(treeUri))
+ docId = documentId(treeUri);
+ else
+ docId = treeDocumentId(treeUri);
+
+ return std::make_shared<MakeableDocumentFile>(buildDocumentUriUsingTree(treeUri, docId));
+}
+
+DocumentFilePtr DocumentFile::createFile(const QString &mimeType, const QString &displayName)
+{
+ if (isDirectory()) {
+ return std::make_shared<MakeableDocumentFile>(
+ createDocument(m_uri, mimeType, displayName),
+ shared_from_this());
+ }
+ return {};
+}
+
+DocumentFilePtr DocumentFile::createDirectory(const QString &displayName)
+{
+ if (isDirectory()) {
+ return std::make_shared<MakeableDocumentFile>(
+ createDocument(m_uri, Document::MIME_TYPE_DIR, displayName),
+ shared_from_this());
+ }
+ return {};
+}
+
+const QJniObject &DocumentFile::uri() const
+{
+ return m_uri;
+}
+
+const DocumentFilePtr &DocumentFile::parent() const
+{
+ return m_parent;
+}
+
+QString DocumentFile::name() const
+{
+ return Cursor::queryColumn(m_uri, Document::COLUMN_DISPLAY_NAME).toString();
+}
+
+QString DocumentFile::id() const
+{
+ return DocumentsContract::documentId(uri());
+}
+
+QString DocumentFile::mimeType() const
+{
+ return Cursor::queryColumn(m_uri, Document::COLUMN_MIME_TYPE).toString();
+}
+
+bool DocumentFile::isDirectory() const
+{
+ return mimeType() == Document::MIME_TYPE_DIR;
+}
+
+bool DocumentFile::isFile() const
+{
+ const QString type = mimeType();
+ return type != Document::MIME_TYPE_DIR && !type.isEmpty();
+}
+
+bool DocumentFile::isVirtual() const
+{
+ return isDocumentUri(m_uri) && (Cursor::queryColumn(m_uri,
+ Document::COLUMN_FLAGS).toInt() & Document::FLAG_VIRTUAL_DOCUMENT);
+}
+
+QDateTime DocumentFile::lastModified() const
+{
+ const auto timeVariant = Cursor::queryColumn(m_uri, Document::COLUMN_LAST_MODIFIED);
+ if (timeVariant.isValid())
+ return QDateTime::fromMSecsSinceEpoch(timeVariant.toLongLong());
+ return {};
+}
+
+int64_t DocumentFile::length() const
+{
+ return Cursor::queryColumn(m_uri, Document::COLUMN_SIZE).toLongLong();
+}
+
+namespace {
+constexpr int FLAG_GRANT_READ_URI_PERMISSION = 0x00000001;
+constexpr int FLAG_GRANT_WRITE_URI_PERMISSION = 0x00000002;
+}
+
+bool DocumentFile::canRead() const
+{
+ const auto context = QJniObject(QNativeInterface::QAndroidApplication::context());
+ const bool selfUriPermission = context.callMethod<jint>("checkCallingOrSelfUriPermission",
+ m_uri.object<QtJniTypes::UriType>(),
+ FLAG_GRANT_READ_URI_PERMISSION);
+ if (selfUriPermission != 0)
+ return false;
+
+ return !mimeType().isEmpty();
+}
+
+bool DocumentFile::canWrite() const
+{
+ const auto context = QJniObject(QNativeInterface::QAndroidApplication::context());
+ const bool selfUriPermission = context.callMethod<jint>("checkCallingOrSelfUriPermission",
+ m_uri.object<QtJniTypes::UriType>(),
+ FLAG_GRANT_WRITE_URI_PERMISSION);
+ if (selfUriPermission != 0)
+ return false;
+
+ const QString type = mimeType();
+ if (type.isEmpty())
+ return false;
+
+ const int flags = Cursor::queryColumn(m_uri, Document::COLUMN_FLAGS).toInt();
+ if (flags & Document::FLAG_SUPPORTS_DELETE)
+ return true;
+
+ const bool supportsWrite = (flags & Document::FLAG_SUPPORTS_WRITE);
+ const bool isDir = (type == Document::MIME_TYPE_DIR);
+ const bool dirSupportsCreate = (isDir && (flags & Document::FLAG_DIR_SUPPORTS_CREATE));
+
+ return dirSupportsCreate || supportsWrite;
+}
+
+bool DocumentFile::remove()
+{
+ return deleteDocument(m_uri);
+}
+
+bool DocumentFile::exists() const
+{
+ return !name().isEmpty();
+}
+
+std::vector<DocumentFilePtr> DocumentFile::listFiles()
+{
+ std::vector<DocumentFilePtr> res;
+ const auto childrenUri = buildChildDocumentsUriUsingTree(m_uri, documentId(m_uri));
+ const auto query = Cursor::queryUri(childrenUri, {Document::COLUMN_DOCUMENT_ID});
+ if (!query)
+ return res;
+
+ while (query->moveToNext()) {
+ const auto uri = buildDocumentUriUsingTree(m_uri, query->data(0).toString());
+ res.push_back(std::make_shared<MakeableDocumentFile>(uri, shared_from_this()));
+ }
+ return res;
+}
+
+bool DocumentFile::rename(const QString &newName)
+{
+ QJniObject uri;
+ if (newName.startsWith("content://"_L1)) {
+ auto lastSeparatorIndex = [](const QString &file) {
+ int posDecoded = file.lastIndexOf("/");
+ int posEncoded = file.lastIndexOf(QUrl::toPercentEncoding("/"));
+ return posEncoded > posDecoded ? posEncoded : posDecoded;
+ };
+
+ // first try to see if the new file is under the same tree and thus used rename only
+ const QString parent = m_uri.toString().left(lastSeparatorIndex(m_uri.toString()));
+ if (newName.contains(parent)) {
+ QString displayName = newName.mid(lastSeparatorIndex(newName));
+ if (displayName.startsWith('/'))
+ displayName.remove(0, 1);
+ else if (displayName.startsWith(QUrl::toPercentEncoding("/")))
+ displayName.remove(0, 3);
+
+ uri = renameDocument(m_uri, displayName);
+ } else {
+ // Move
+ QJniObject srcParentUri = fromTreeUri(parseUri(parent))->uri();
+ const QString destParent = newName.left(lastSeparatorIndex(newName));
+ QJniObject targetParentUri = fromTreeUri(parseUri(destParent))->uri();
+ uri = moveDocument(m_uri, srcParentUri, targetParentUri);
+ }
+ } else {
+ uri = renameDocument(m_uri, newName);
+ }
+
+ if (uri.isValid()) {
+ m_uri = uri;
+ return true;
+ }
+
+ return false;
+}
+
+QT_END_NAMESPACE
+
+// End of DocumentFile
diff --git a/src/plugins/platforms/android/androidcontentfileengine.h b/src/plugins/platforms/android/androidcontentfileengine.h
index e58c990c51..11d0cd7201 100644
--- a/src/plugins/platforms/android/androidcontentfileengine.h
+++ b/src/plugins/platforms/android/androidcontentfileengine.h
@@ -5,7 +5,13 @@
#define ANDROIDCONTENTFILEENGINE_H
#include <private/qfsfileengine_p.h>
+
#include <QtCore/qjniobject.h>
+#include <QtCore/qlist.h>
+
+QT_BEGIN_NAMESPACE
+
+using DocumentFilePtr = std::shared_ptr<class DocumentFile>;
class AndroidContentFileEngine : public QFSFileEngine
{
@@ -14,14 +20,25 @@ public:
bool open(QIODevice::OpenMode openMode, std::optional<QFile::Permissions> permissions) override;
bool close() override;
qint64 size() const override;
+ bool remove() override;
+ bool rename(const QString &newName) override;
+ bool mkdir(const QString &dirName, bool createParentDirectories,
+ std::optional<QFile::Permissions> permissions = std::nullopt) const override;
+ bool rmdir(const QString &dirName, bool recurseParentDirectories) const override;
+ QByteArray id() const override;
+ bool caseSensitive() const override { return true; }
+ QDateTime fileTime(QFile::FileTime time) const override;
FileFlags fileFlags(FileFlags type = FileInfoAll) const override;
QString fileName(FileName file = DefaultName) const override;
- QAbstractFileEngine::Iterator *beginEntryList(QDir::Filters filters, const QStringList &filterNames) override;
- QAbstractFileEngine::Iterator *endEntryList() override;
+ IteratorUniquePtr beginEntryList(const QString &path, QDir::Filters filters,
+ const QStringList &filterNames) override;
+
private:
- QString m_file;
- QJniObject m_pfd;
+ void closeNativeFileDescriptor();
+ QString m_initialFile;
+ QJniObject m_pfd;
+ DocumentFilePtr m_documentFile;
};
class AndroidContentFileEngineHandler : public QAbstractFileEngineHandler
@@ -29,20 +46,66 @@ class AndroidContentFileEngineHandler : public QAbstractFileEngineHandler
public:
AndroidContentFileEngineHandler();
~AndroidContentFileEngineHandler();
- QAbstractFileEngine *create(const QString &fileName) const override;
+ std::unique_ptr<QAbstractFileEngine> create(const QString &fileName) const override;
};
class AndroidContentFileEngineIterator : public QAbstractFileEngineIterator
{
public:
- AndroidContentFileEngineIterator(QDir::Filters filters, const QStringList &filterNames);
+ AndroidContentFileEngineIterator(const QString &path, QDir::Filters filters,
+ const QStringList &filterNames);
~AndroidContentFileEngineIterator();
- QString next() override;
- bool hasNext() const override;
+
+ bool advance() override;
+
QString currentFileName() const override;
+ QString currentFilePath() const override;
private:
- mutable QStringList m_entries;
- mutable int m_index = -1;
+ mutable QList<DocumentFilePtr> m_files;
+ mutable qsizetype m_index = -1;
};
+/*!
+ *
+ * DocumentFile Api.
+ * Check https://developer.android.com/reference/androidx/documentfile/provider/DocumentFile
+ * for more information.
+ *
+ */
+class DocumentFile : public std::enable_shared_from_this<DocumentFile>
+{
+public:
+ static DocumentFilePtr parseFromAnyUri(const QString &filename);
+ static DocumentFilePtr fromSingleUri(const QJniObject &uri);
+ static DocumentFilePtr fromTreeUri(const QJniObject &treeUri);
+
+ DocumentFilePtr createFile(const QString &mimeType, const QString &displayName);
+ DocumentFilePtr createDirectory(const QString &displayName);
+ const QJniObject &uri() const;
+ const DocumentFilePtr &parent() const;
+ QString name() const;
+ QString id() const;
+ QString mimeType() const;
+ bool isDirectory() const;
+ bool isFile() const;
+ bool isVirtual() const;
+ QDateTime lastModified() const;
+ int64_t length() const;
+ bool canRead() const;
+ bool canWrite() const;
+ bool remove();
+ bool exists() const;
+ std::vector<DocumentFilePtr> listFiles();
+ bool rename(const QString &newName);
+
+protected:
+ DocumentFile(const QJniObject &uri, const std::shared_ptr<DocumentFile> &parent);
+
+protected:
+ QJniObject m_uri;
+ DocumentFilePtr m_parent;
+};
+
+QT_END_NAMESPACE
+
#endif // ANDROIDCONTENTFILEENGINE_H
diff --git a/src/plugins/platforms/android/androidjniaccessibility.cpp b/src/plugins/platforms/android/androidjniaccessibility.cpp
index 38b1ed0952..8f1c76ca26 100644
--- a/src/plugins/platforms/android/androidjniaccessibility.cpp
+++ b/src/plugins/platforms/android/androidjniaccessibility.cpp
@@ -1,6 +1,7 @@
// Copyright (C) 2021 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+#include "androiddeadlockprotector.h"
#include "androidjniaccessibility.h"
#include "androidjnimain.h"
#include "qandroidplatformintegration.h"
@@ -14,11 +15,12 @@
#include <QtCore/private/qjnihelpers_p.h>
#include <QtCore/QJniObject>
#include <QtGui/private/qhighdpiscaling_p.h>
+
#include <QtCore/QObject>
+#include <QtCore/qpointer.h>
#include <QtCore/qvarlengtharray.h>
static const char m_qtTag[] = "Qt A11Y";
-static const char m_classErrorMsg[] = "Can't find class \"%s\"";
QT_BEGIN_NAMESPACE
@@ -47,7 +49,7 @@ namespace QtAndroidAccessibility
// Because of that almost every method here is split into two parts.
// The _helper part is executed in the context of m_accessibilityContext
// on the main thread. The other part is executed in Java thread.
- static QPointer<QObject> m_accessibilityContext = nullptr;
+ Q_CONSTINIT static QPointer<QObject> m_accessibilityContext = {};
// This method is called from the Qt main thread, and normally a
// QGuiApplication instance will be used as a parent.
@@ -61,6 +63,14 @@ namespace QtAndroidAccessibility
template <typename Func, typename Ret>
void runInObjectContext(QObject *context, Func &&func, Ret *retVal)
{
+ AndroidDeadlockProtector protector;
+ if (!protector.acquire()) {
+ __android_log_print(ANDROID_LOG_WARN, m_qtTag,
+ "Could not run accessibility call in object context, accessing "
+ "main thread could lead to deadlock");
+ return;
+ }
+
if (!QtAndroid::blockEventLoopsWhenSuspended()
|| QGuiApplication::applicationState() != Qt::ApplicationSuspended) {
QMetaObject::invokeMethod(context, func, Qt::BlockingQueuedConnection, retVal);
@@ -72,8 +82,7 @@ namespace QtAndroidAccessibility
void initialize()
{
- QJniObject::callStaticMethod<void>(QtAndroid::applicationClass(),
- "initializeAccessibility");
+ QtAndroid::qtActivityDelegate().callMethod<void>("initializeAccessibility");
}
bool isActive()
@@ -131,6 +140,11 @@ namespace QtAndroidAccessibility
QtAndroid::notifyValueChanged(accessibilityObjectId, value);
}
+ void notifyScrolledEvent(uint accessiblityObjectId)
+ {
+ QtAndroid::notifyScrolledEvent(accessiblityObjectId);
+ }
+
static QVarLengthArray<int, 8> childIdListForAccessibleObject_helper(int objectId)
{
QAccessibleInterface *iface = interfaceFromId(objectId);
@@ -188,7 +202,7 @@ namespace QtAndroidAccessibility
return result;
}
- static QRect screenRect_helper(int objectId)
+ static QRect screenRect_helper(int objectId, bool clip = true)
{
QRect rect;
QAccessibleInterface *iface = interfaceFromId(objectId);
@@ -196,7 +210,7 @@ namespace QtAndroidAccessibility
rect = QHighDpi::toNativePixels(iface->rect(), iface->window());
}
// If the widget is not fully in-bound in its parent then we have to clip the rectangle to draw
- if (iface && iface->parent() && iface->parent()->isValid()) {
+ if (clip && iface && iface->parent() && iface->parent()->isValid()) {
const auto parentRect = QHighDpi::toNativePixels(iface->parent()->rect(), iface->parent()->window());
rect = rect.intersected(parentRect);
}
@@ -298,34 +312,44 @@ namespace QtAndroidAccessibility
static jboolean scrollForward(JNIEnv */*env*/, jobject /*thiz*/, jint objectId)
{
bool result = false;
+
+ const auto& ids = childIdListForAccessibleObject_helper(objectId);
+ if (ids.isEmpty())
+ return false;
+
+ const int firstChildId = ids.first();
+ const QRect oldPosition = screenRect_helper(firstChildId, false);
+
if (m_accessibilityContext) {
runInObjectContext(m_accessibilityContext, [objectId]() {
return scroll_helper(objectId, QAccessibleActionInterface::increaseAction());
}, &result);
}
- return result;
+
+ // Don't check for position change if the call was not successful
+ return result && oldPosition != screenRect_helper(firstChildId, false);
}
static jboolean scrollBackward(JNIEnv */*env*/, jobject /*thiz*/, jint objectId)
{
bool result = false;
+
+ const auto& ids = childIdListForAccessibleObject_helper(objectId);
+ if (ids.isEmpty())
+ return false;
+
+ const int firstChildId = ids.first();
+ const QRect oldPosition = screenRect_helper(firstChildId, false);
+
if (m_accessibilityContext) {
runInObjectContext(m_accessibilityContext, [objectId]() {
return scroll_helper(objectId, QAccessibleActionInterface::decreaseAction());
}, &result);
}
- return result;
- }
-
-
-#define FIND_AND_CHECK_CLASS(CLASS_NAME) \
-clazz = env->FindClass(CLASS_NAME); \
-if (!clazz) { \
- __android_log_print(ANDROID_LOG_FATAL, m_qtTag, m_classErrorMsg, CLASS_NAME); \
- return JNI_FALSE; \
-}
- //__android_log_print(ANDROID_LOG_FATAL, m_qtTag, m_methodErrorMsg, METHOD_NAME, METHOD_SIGNATURE);
+ // Don't check for position change if the call was not successful
+ return result && oldPosition != screenRect_helper(firstChildId, false);
+ }
static QString textFromValue(QAccessibleInterface *iface)
{
@@ -523,7 +547,7 @@ if (!clazz) { \
return true;
}
- static JNINativeMethod methods[] = {
+ static const JNINativeMethod methods[] = {
{"setActive","(Z)V",(void*)setActive},
{"childIdListForAccessibleObject", "(I)[I", (jintArray)childIdListForAccessibleObject},
{"parentId", "(I)I", (void*)parentId},
@@ -543,13 +567,10 @@ if (!clazz) { \
return false; \
}
- bool registerNatives(JNIEnv *env)
+ bool registerNatives(QJniEnvironment &env)
{
- jclass clazz;
- FIND_AND_CHECK_CLASS("org/qtproject/qt/android/accessibility/QtNativeAccessibility");
- jclass appClass = static_cast<jclass>(env->NewGlobalRef(clazz));
-
- if (env->RegisterNatives(appClass, methods, sizeof(methods) / sizeof(methods[0])) < 0) {
+ if (!env.registerNativeMethods("org/qtproject/qt/android/QtNativeAccessibility",
+ methods, sizeof(methods) / sizeof(methods[0]))) {
__android_log_print(ANDROID_LOG_FATAL,"Qt A11y", "RegisterNatives failed");
return false;
}
diff --git a/src/plugins/platforms/android/androidjniaccessibility.h b/src/plugins/platforms/android/androidjniaccessibility.h
index 94e64762e4..d967dde3ff 100644
--- a/src/plugins/platforms/android/androidjniaccessibility.h
+++ b/src/plugins/platforms/android/androidjniaccessibility.h
@@ -9,16 +9,18 @@
QT_BEGIN_NAMESPACE
class QObject;
+class QJniEnvironment;
namespace QtAndroidAccessibility
{
void initialize();
bool isActive();
- bool registerNatives(JNIEnv *env);
+ bool registerNatives(QJniEnvironment &env);
void notifyLocationChange(uint accessibilityObjectId);
void notifyObjectHide(uint accessibilityObjectId);
void notifyObjectFocus(uint accessibilityObjectId);
void notifyValueChanged(uint accessibilityObjectId);
+ void notifyScrolledEvent(uint accessibilityObjectId);
void createAccessibilityContextObject(QObject *parent);
}
diff --git a/src/plugins/platforms/android/androidjniclipboard.cpp b/src/plugins/platforms/android/androidjniclipboard.cpp
deleted file mode 100644
index 7b2c2c0443..0000000000
--- a/src/plugins/platforms/android/androidjniclipboard.cpp
+++ /dev/null
@@ -1,98 +0,0 @@
-// Copyright (C) 2012 BogDan Vatra <bogdan@kde.org>
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
-
-#include "androidjniclipboard.h"
-#include <QtCore/QUrl>
-#include <QtCore/QJniObject>
-#include <QtCore/QJniEnvironment>
-
-QT_BEGIN_NAMESPACE
-
-using namespace QtAndroid;
-namespace QtAndroidClipboard
-{
- QAndroidPlatformClipboard *m_manager = nullptr;
-
- static JNINativeMethod methods[] = {
- {"onClipboardDataChanged", "()V", (void *)onClipboardDataChanged}
- };
-
- void setClipboardManager(QAndroidPlatformClipboard *manager)
- {
- m_manager = manager;
- QJniObject::callStaticMethod<void>(applicationClass(), "registerClipboardManager");
- jclass appClass = QtAndroid::applicationClass();
- QJniEnvironment env;
- if (env->RegisterNatives(appClass, methods, sizeof(methods) / sizeof(methods[0])) < 0) {
- __android_log_print(ANDROID_LOG_FATAL,"Qt", "RegisterNatives failed");
- return;
- }
- }
- void clearClipboardData()
- {
- QJniObject::callStaticMethod<void>(applicationClass(), "clearClipData");
- }
- void setClipboardMimeData(QMimeData *data)
- {
- clearClipboardData();
- if (data->hasText()) {
- QJniObject::callStaticMethod<void>(applicationClass(),
- "setClipboardText", "(Ljava/lang/String;)V",
- QJniObject::fromString(data->text()).object());
- }
- if (data->hasHtml()) {
- QJniObject::callStaticMethod<void>(applicationClass(),
- "setClipboardHtml",
- "(Ljava/lang/String;Ljava/lang/String;)V",
- QJniObject::fromString(data->text()).object(),
- QJniObject::fromString(data->html()).object());
- }
- if (data->hasUrls()) {
- QList<QUrl> urls = data->urls();
- for (const auto &u : qAsConst(urls)) {
- QJniObject::callStaticMethod<void>(applicationClass(),
- "setClipboardUri",
- "(Ljava/lang/String;)V",
- QJniObject::fromString(u.toEncoded()).object());
- }
- }
- }
-
- QMimeData *getClipboardMimeData()
- {
- QMimeData *data = new QMimeData;
- if (QJniObject::callStaticMethod<jboolean>(applicationClass(), "hasClipboardText")) {
- data->setText(QJniObject::callStaticObjectMethod(applicationClass(),
- "getClipboardText",
- "()Ljava/lang/String;").toString());
- }
- if (QJniObject::callStaticMethod<jboolean>(applicationClass(), "hasClipboardHtml")) {
- data->setHtml(QJniObject::callStaticObjectMethod(applicationClass(),
- "getClipboardHtml",
- "()Ljava/lang/String;").toString());
- }
- if (QJniObject::callStaticMethod<jboolean>(applicationClass(), "hasClipboardUri")) {
- QJniObject uris = QJniObject::callStaticObjectMethod(applicationClass(),
- "getClipboardUris",
- "()[Ljava/lang/String;");
- if (uris.isValid()) {
- QList<QUrl> urls;
- QJniEnvironment env;
- jobjectArray juris = uris.object<jobjectArray>();
- const jint nUris = env->GetArrayLength(juris);
- urls.reserve(static_cast<int>(nUris));
- for (int i = 0; i < nUris; ++i)
- urls << QUrl(QJniObject(env->GetObjectArrayElement(juris, i)).toString());
- data->setUrls(urls);
- }
- }
- return data;
- }
-
- void onClipboardDataChanged(JNIEnv */*env*/, jobject /*thiz*/)
- {
- m_manager->emitChanged(QClipboard::Clipboard);
- }
-}
-
-QT_END_NAMESPACE
diff --git a/src/plugins/platforms/android/androidjniclipboard.h b/src/plugins/platforms/android/androidjniclipboard.h
deleted file mode 100644
index 24feeef9b3..0000000000
--- a/src/plugins/platforms/android/androidjniclipboard.h
+++ /dev/null
@@ -1,27 +0,0 @@
-// Copyright (C) 2012 BogDan Vatra <bogdan@kde.org>
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
-
-#ifndef ANDROIDJNICLIPBOARD_H
-#define ANDROIDJNICLIPBOARD_H
-
-#include <QString>
-#include "qandroidplatformclipboard.h"
-#include "androidjnimain.h"
-
-QT_BEGIN_NAMESPACE
-
-class QAndroidPlatformClipboard;
-namespace QtAndroidClipboard
-{
- // Clipboard support
- void setClipboardManager(QAndroidPlatformClipboard *manager);
- void setClipboardMimeData(QMimeData *data);
- QMimeData *getClipboardMimeData();
- void clearClipboardData();
- void onClipboardDataChanged(JNIEnv */*env*/, jobject /*thiz*/);
- // Clipboard support
-}
-
-QT_END_NAMESPACE
-
-#endif // ANDROIDJNICLIPBOARD_H
diff --git a/src/plugins/platforms/android/androidjniinput.cpp b/src/plugins/platforms/android/androidjniinput.cpp
index fae8668f63..00e6b7ca51 100644
--- a/src/plugins/platforms/android/androidjniinput.cpp
+++ b/src/plugins/platforms/android/androidjniinput.cpp
@@ -1,3 +1,4 @@
+// Copyright (C) 2023 The Qt Company Ltd.
// Copyright (C) 2012 BogDan Vatra <bogdan@kde.org>
// Copyright (C) 2016 Olivier Goffart <ogoffart@woboq.com>
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
@@ -8,6 +9,7 @@
#include "androidjnimain.h"
#include "qandroidplatformintegration.h"
+#include <qpa/qplatformwindow.h>
#include <qpa/qwindowsysteminterface.h>
#include <QTouchEvent>
#include <QPointer>
@@ -17,8 +19,12 @@
QT_BEGIN_NAMESPACE
+Q_LOGGING_CATEGORY(lcQpaInputMethods, "qt.qpa.input.methods");
+
using namespace QtAndroid;
+Q_DECLARE_JNI_CLASS(QtLayout, "org/qtproject/qt/android/QtLayout")
+
namespace QtAndroidInput
{
static bool m_ignoreMouseEvents = false;
@@ -28,14 +34,75 @@ namespace QtAndroidInput
static QPointer<QWindow> m_mouseGrabber;
+ GenericMotionEventListener::~GenericMotionEventListener() {}
+ namespace {
+ struct GenericMotionEventListeners {
+ QMutex mutex;
+ QList<QtAndroidInput::GenericMotionEventListener *> listeners;
+ };
+ }
+ Q_GLOBAL_STATIC(GenericMotionEventListeners, g_genericMotionEventListeners)
+
+ static jboolean dispatchGenericMotionEvent(JNIEnv *, jclass, jobject event)
+ {
+ jboolean ret = JNI_FALSE;
+ QMutexLocker locker(&g_genericMotionEventListeners()->mutex);
+ for (auto *listener : std::as_const(g_genericMotionEventListeners()->listeners))
+ ret |= listener->handleGenericMotionEvent(event);
+ return ret;
+ }
+
+ KeyEventListener::~KeyEventListener() {}
+ namespace {
+ struct KeyEventListeners {
+ QMutex mutex;
+ QList<QtAndroidInput::KeyEventListener *> listeners;
+ };
+ }
+ Q_GLOBAL_STATIC(KeyEventListeners, g_keyEventListeners)
+
+ static jboolean dispatchKeyEvent(JNIEnv *, jclass, jobject event)
+ {
+ jboolean ret = JNI_FALSE;
+ QMutexLocker locker(&g_keyEventListeners()->mutex);
+ for (auto *listener : std::as_const(g_keyEventListeners()->listeners))
+ ret |= listener->handleKeyEvent(event);
+ return ret;
+ }
+
+ void registerGenericMotionEventListener(QtAndroidInput::GenericMotionEventListener *listener)
+ {
+ QMutexLocker locker(&g_genericMotionEventListeners()->mutex);
+ g_genericMotionEventListeners()->listeners.push_back(listener);
+ }
+
+ void unregisterGenericMotionEventListener(QtAndroidInput::GenericMotionEventListener *listener)
+ {
+ QMutexLocker locker(&g_genericMotionEventListeners()->mutex);
+ g_genericMotionEventListeners()->listeners.removeOne(listener);
+ }
+
+ void registerKeyEventListener(QtAndroidInput::KeyEventListener *listener)
+ {
+ QMutexLocker locker(&g_keyEventListeners()->mutex);
+ g_keyEventListeners()->listeners.push_back(listener);
+ }
+
+ void unregisterKeyEventListener(QtAndroidInput::KeyEventListener *listener)
+ {
+ QMutexLocker locker(&g_keyEventListeners()->mutex);
+ g_keyEventListeners()->listeners.removeOne(listener);
+ }
+
+ QJniObject qtLayout()
+ {
+ return qtActivityDelegate().callMethod<QtJniTypes::QtLayout>("getQtLayout");
+ }
+
void updateSelection(int selStart, int selEnd, int candidatesStart, int candidatesEnd)
{
-#ifdef QT_DEBUG_ANDROID_IM_PROTOCOL
- qDebug() << ">>> UPDATESELECTION" << selStart << selEnd << candidatesStart << candidatesEnd;
-#endif
- QJniObject::callStaticMethod<void>(applicationClass(),
- "updateSelection",
- "(IIII)V",
+ qCDebug(lcQpaInputMethods) << ">>> UPDATESELECTION" << selStart << selEnd << candidatesStart << candidatesEnd;
+ qtInputDelegate().callMethod<void>("updateSelection",
selStart,
selEnd,
candidatesStart,
@@ -44,39 +111,33 @@ namespace QtAndroidInput
void showSoftwareKeyboard(int left, int top, int width, int height, int inputHints, int enterKeyType)
{
- QJniObject::callStaticMethod<void>(applicationClass(),
- "showSoftwareKeyboard",
- "(IIIIII)V",
+ qtInputDelegate().callMethod<void>("showSoftwareKeyboard",
+ QtAndroidPrivate::activity(),
+ qtLayout().object<QtJniTypes::QtLayout>(),
left,
top,
width,
height,
inputHints,
enterKeyType);
-#ifdef QT_DEBUG_ANDROID_IM_PROTOCOL
- qDebug() << "@@@ SHOWSOFTWAREKEYBOARD" << left << top << width << height << inputHints << enterKeyType;
-#endif
+ qCDebug(lcQpaInputMethods) << "@@@ SHOWSOFTWAREKEYBOARD" << left << top << width << height << inputHints << enterKeyType;
}
void resetSoftwareKeyboard()
{
- QJniObject::callStaticMethod<void>(applicationClass(), "resetSoftwareKeyboard");
-#ifdef QT_DEBUG_ANDROID_IM_PROTOCOL
- qDebug("@@@ RESETSOFTWAREKEYBOARD");
-#endif
+ qtInputDelegate().callMethod<void>("resetSoftwareKeyboard");
+ qCDebug(lcQpaInputMethods) << "@@@ RESETSOFTWAREKEYBOARD";
}
void hideSoftwareKeyboard()
{
- QJniObject::callStaticMethod<void>(applicationClass(), "hideSoftwareKeyboard");
-#ifdef QT_DEBUG_ANDROID_IM_PROTOCOL
- qDebug("@@@ HIDESOFTWAREKEYBOARD");
-#endif
+ qtInputDelegate().callMethod<void>("hideSoftwareKeyboard");
+ qCDebug(lcQpaInputMethods) << "@@@ HIDESOFTWAREKEYBOARD";
}
bool isSoftwareKeyboardVisible()
{
- return QJniObject::callStaticMethod<jboolean>(applicationClass(), "isSoftwareKeyboardVisible");
+ return qtInputDelegate().callMethod<jboolean>("isSoftwareKeyboardVisible");
}
QRect softwareKeyboardRect()
@@ -86,81 +147,88 @@ namespace QtAndroidInput
int getSelectHandleWidth()
{
- return QJniObject::callStaticMethod<jint>(applicationClass(), "getSelectHandleWidth");
+ return qtInputDelegate().callMethod<jint>("getSelectHandleWidth");
}
void updateHandles(int mode, QPoint editMenuPos, uint32_t editButtons, QPoint cursor, QPoint anchor, bool rtl)
{
- QJniObject::callStaticMethod<void>(applicationClass(), "updateHandles", "(IIIIIIIIZ)V",
+ qtInputDelegate().callMethod<void>("updateHandles",
+ QtAndroidPrivate::activity(),
+ qtLayout().object<QtJniTypes::QtLayout>(),
mode, editMenuPos.x(), editMenuPos.y(), editButtons,
cursor.x(), cursor.y(),
anchor.x(), anchor.y(), rtl);
}
- static void mouseDown(JNIEnv */*env*/, jobject /*thiz*/, jint /*winId*/, jint x, jint y)
+ static void mouseDown(JNIEnv */*env*/, jobject /*thiz*/, jint winId, jint x, jint y)
{
if (m_ignoreMouseEvents)
return;
- QPoint globalPos(x,y);
- QWindow *tlw = topLevelWindowAt(globalPos);
- m_mouseGrabber = tlw;
- QPoint localPos = tlw ? (globalPos - tlw->position()) : globalPos;
- QWindowSystemInterface::handleMouseEvent(tlw, localPos, globalPos,
+ const QPoint globalPos(x,y);
+ QWindow *window = windowFromId(winId);
+ m_mouseGrabber = window;
+ const QPoint localPos = window && window->handle() ?
+ window->handle()->mapFromGlobal(globalPos) : globalPos;
+ QWindowSystemInterface::handleMouseEvent(window, localPos, globalPos,
Qt::MouseButtons(Qt::LeftButton),
Qt::LeftButton, QEvent::MouseButtonPress);
}
- static void mouseUp(JNIEnv */*env*/, jobject /*thiz*/, jint /*winId*/, jint x, jint y)
+ static void mouseUp(JNIEnv */*env*/, jobject /*thiz*/, jint winId, jint x, jint y)
{
- QPoint globalPos(x,y);
- QWindow *tlw = m_mouseGrabber.data();
- if (!tlw)
- tlw = topLevelWindowAt(globalPos);
- QPoint localPos = tlw ? (globalPos -tlw->position()) : globalPos;
- QWindowSystemInterface::handleMouseEvent(tlw, localPos, globalPos,
+ const QPoint globalPos(x,y);
+ QWindow *window = m_mouseGrabber.data();
+ if (!window)
+ window = windowFromId(winId);
+
+ const QPoint localPos = window && window->handle() ?
+ window->handle()->mapFromGlobal(globalPos) : globalPos;
+ QWindowSystemInterface::handleMouseEvent(window, localPos, globalPos,
Qt::MouseButtons(Qt::NoButton),
Qt::LeftButton, QEvent::MouseButtonRelease);
m_ignoreMouseEvents = false;
- m_mouseGrabber = 0;
+ m_mouseGrabber.clear();
}
- static void mouseMove(JNIEnv */*env*/, jobject /*thiz*/, jint /*winId*/, jint x, jint y)
+ static void mouseMove(JNIEnv */*env*/, jobject /*thiz*/, jint winId, jint x, jint y)
{
if (m_ignoreMouseEvents)
return;
- QPoint globalPos(x,y);
- QWindow *tlw = m_mouseGrabber.data();
- if (!tlw)
- tlw = topLevelWindowAt(globalPos);
- QPoint localPos = tlw ? (globalPos-tlw->position()) : globalPos;
- QWindowSystemInterface::handleMouseEvent(tlw, localPos, globalPos,
+ const QPoint globalPos(x,y);
+ QWindow *window = m_mouseGrabber.data();
+ if (!window)
+ window = windowFromId(winId);
+ const QPoint localPos = window && window->handle() ?
+ window->handle()->mapFromGlobal(globalPos) : globalPos;
+ QWindowSystemInterface::handleMouseEvent(window, localPos, globalPos,
Qt::MouseButtons(m_mouseGrabber ? Qt::LeftButton : Qt::NoButton),
Qt::NoButton, QEvent::MouseMove);
}
- static void mouseWheel(JNIEnv */*env*/, jobject /*thiz*/, jint /*winId*/, jint x, jint y, jfloat hdelta, jfloat vdelta)
+ static void mouseWheel(JNIEnv */*env*/, jobject /*thiz*/, jint winId, jint x, jint y, jfloat hdelta, jfloat vdelta)
{
if (m_ignoreMouseEvents)
return;
- QPoint globalPos(x,y);
- QWindow *tlw = m_mouseGrabber.data();
- if (!tlw)
- tlw = topLevelWindowAt(globalPos);
- QPoint localPos = tlw ? (globalPos-tlw->position()) : globalPos;
- QPoint angleDelta(hdelta * 120, vdelta * 120);
+ const QPoint globalPos(x,y);
+ QWindow *window = m_mouseGrabber.data();
+ if (!window)
+ window = windowFromId(winId);
+ const QPoint localPos = window && window->handle() ?
+ window->handle()->mapFromGlobal(globalPos) : globalPos;
+ const QPoint angleDelta(hdelta * 120, vdelta * 120);
- QWindowSystemInterface::handleWheelEvent(tlw,
+ QWindowSystemInterface::handleWheelEvent(window,
localPos,
globalPos,
QPoint(),
angleDelta);
}
- static void longPress(JNIEnv */*env*/, jobject /*thiz*/, jint /*winId*/, jint x, jint y)
+ static void longPress(JNIEnv */*env*/, jobject /*thiz*/, jint winId, jint x, jint y)
{
QAndroidInputContext *inputContext = QAndroidInputContext::androidInputContext();
if (inputContext && qGuiApp)
@@ -171,16 +239,17 @@ namespace QtAndroidInput
if (!rightMouseFromLongPress)
return;
m_ignoreMouseEvents = true;
- QPoint globalPos(x,y);
- QWindow *tlw = topLevelWindowAt(globalPos);
- QPoint localPos = tlw ? (globalPos-tlw->position()) : globalPos;
+ const QPoint globalPos(x,y);
+ QWindow *window = windowFromId(winId);
+ const QPoint localPos = window && window->handle() ?
+ window->handle()->mapFromGlobal(globalPos) : globalPos;
// Click right button if no other button is already pressed.
if (!m_mouseGrabber) {
- QWindowSystemInterface::handleMouseEvent(tlw, localPos, globalPos,
+ QWindowSystemInterface::handleMouseEvent(window, localPos, globalPos,
Qt::MouseButtons(Qt::RightButton), Qt::RightButton,
QEvent::MouseButtonPress);
- QWindowSystemInterface::handleMouseEvent(tlw, localPos, globalPos,
+ QWindowSystemInterface::handleMouseEvent(window, localPos, globalPos,
Qt::MouseButtons(Qt::NoButton), Qt::RightButton,
QEvent::MouseButtonRelease);
}
@@ -218,12 +287,11 @@ namespace QtAndroidInput
touchPoint.rotation = qRadiansToDegrees(rotation);
touchPoint.normalPosition = QPointF(double(x / dw), double(y / dh));
touchPoint.state = state;
- touchPoint.area = QRectF(x - double(minor),
- y - double(major),
- double(minor * 2),
- double(major * 2));
+ touchPoint.area = QRectF(x - double(minor * 0.5f),
+ y - double(major * 0.5f),
+ double(minor),
+ double(major));
m_touchPoints.push_back(touchPoint);
-
if (state == QEventPoint::State::Pressed) {
QAndroidInputContext *inputContext = QAndroidInputContext::androidInputContext();
if (inputContext && qGuiApp)
@@ -254,31 +322,35 @@ namespace QtAndroidInput
return touchDevice;
}
- static void touchEnd(JNIEnv * /*env*/, jobject /*thiz*/, jint /*winId*/, jint /*action*/)
+ static void touchEnd(JNIEnv * /*env*/, jobject /*thiz*/, jint winId, jint /*action*/)
{
if (m_touchPoints.isEmpty())
return;
QMutexLocker lock(QtAndroid::platformInterfaceMutex());
- QPointingDevice *touchDevice = getTouchDevice();
+ const QPointingDevice *touchDevice = getTouchDevice();
if (!touchDevice)
return;
- QWindow *window = QtAndroid::topLevelWindowAt(m_touchPoints.at(0).area.center().toPoint());
+ QWindow *window = QtAndroid::windowFromId(winId);
+ if (!window)
+ return;
QWindowSystemInterface::handleTouchEvent(window, touchDevice, m_touchPoints);
}
- static void touchCancel(JNIEnv * /*env*/, jobject /*thiz*/, jint /*winId*/)
+ static void touchCancel(JNIEnv * /*env*/, jobject /*thiz*/, jint winId)
{
if (m_touchPoints.isEmpty())
return;
QMutexLocker lock(QtAndroid::platformInterfaceMutex());
- QPointingDevice *touchDevice = getTouchDevice();
+ const QPointingDevice *touchDevice = getTouchDevice();
if (!touchDevice)
return;
- QWindow *window = QtAndroid::topLevelWindowAt(m_touchPoints.at(0).area.center().toPoint());
+ QWindow *window = QtAndroid::windowFromId(winId);
+ if (!window)
+ return;
QWindowSystemInterface::handleTouchCancelEvent(window, touchDevice);
}
@@ -291,14 +363,14 @@ namespace QtAndroidInput
#endif // QT_CONFIG(tabletevent)
}
- static void tabletEvent(JNIEnv */*env*/, jobject /*thiz*/, jint /*winId*/, jint deviceId, jlong time, jint action,
+ static void tabletEvent(JNIEnv */*env*/, jobject /*thiz*/, jint winId, jint deviceId, jlong time, jint action,
jint pointerType, jint buttonState, jfloat x, jfloat y, jfloat pressure)
{
#if QT_CONFIG(tabletevent)
- QPointF globalPosF(x, y);
- QPoint globalPos((int)x, (int)y);
- QWindow *tlw = topLevelWindowAt(globalPos);
- QPointF localPos = tlw ? (globalPosF - tlw->position()) : globalPosF;
+ const QPointF globalPosF(x, y);
+ QWindow *window = windowFromId(winId);
+ const QPointF localPos = window && window->handle() ?
+ window->handle()->mapFromGlobalF(globalPosF) : globalPosF;
// Galaxy Note with plain Android:
// 0 1 0 stylus press
@@ -318,6 +390,7 @@ namespace QtAndroidInput
Qt::MouseButtons buttons = Qt::NoButton;
switch (action) {
case 1: // ACTION_UP
+ case 6: // ACTION_POINTER_UP, happens if stylus is not the primary pointer
case 212: // stylus release while side-button held on Galaxy Note 4
buttons = Qt::NoButton;
break;
@@ -329,11 +402,9 @@ namespace QtAndroidInput
break;
}
-#ifdef QT_DEBUG_ANDROID_STYLUS
- qDebug() << action << pointerType << buttonState << '@' << x << y << "pressure" << pressure << ": buttons" << buttons;
-#endif
+ qCDebug(lcQpaInputMethods) << action << pointerType << buttonState << '@' << x << y << "pressure" << pressure << ": buttons" << buttons;
- QWindowSystemInterface::handleTabletEvent(tlw, ulong(time),
+ QWindowSystemInterface::handleTabletEvent(window, ulong(time),
localPos, globalPosF, int(QInputDevice::DeviceType::Stylus), pointerType,
buttons, pressure, 0, 0, 0., 0., 0, deviceId, Qt::NoModifier);
#endif // QT_CONFIG(tabletevent)
@@ -797,9 +868,7 @@ namespace QtAndroidInput
QMetaObject::invokeMethod(inputContext, "hideSelectionHandles", Qt::QueuedConnection);
}
}
-#ifdef QT_DEBUG_ANDROID_IM_PROTOCOL
- qDebug() << "@@@ KEYBOARDVISIBILITYCHANGED" << inputContext;
-#endif
+ qCDebug(lcQpaInputMethods) << "@@@ KEYBOARDVISIBILITYCHANGED" << inputContext;
}
static void keyboardGeometryChanged(JNIEnv */*env*/, jobject /*thiz*/, jint x, jint y, jint w, jint h)
@@ -812,16 +881,12 @@ namespace QtAndroidInput
if (inputContext && qGuiApp)
inputContext->emitKeyboardRectChanged();
-#ifdef QT_DEBUG_ANDROID_IM_PROTOCOL
- qDebug() << "@@@ KEYBOARDRECTCHANGED" << m_softwareKeyboardRect;
-#endif
+ qCDebug(lcQpaInputMethods) << "@@@ KEYBOARDRECTCHANGED" << m_softwareKeyboardRect;
}
static void handleLocationChanged(JNIEnv */*env*/, jobject /*thiz*/, int id, int x, int y)
{
-#ifdef QT_DEBUG_ANDROID_IM_PROTOCOL
- qDebug() << "@@@ handleLocationChanged" << id << x << y;
-#endif
+ qCDebug(lcQpaInputMethods) << "@@@ handleLocationChanged" << id << x << y;
QAndroidInputContext *inputContext = QAndroidInputContext::androidInputContext();
if (inputContext && qGuiApp)
QMetaObject::invokeMethod(inputContext, "handleLocationChanged", Qt::BlockingQueuedConnection,
@@ -829,7 +894,8 @@ namespace QtAndroidInput
}
- static JNINativeMethod methods[] = {
+
+ static const JNINativeMethod methods[] = {
{"touchBegin","(I)V",(void*)touchBegin},
{"touchAdd","(IIIZIIFFFF)V",(void*)touchAdd},
{"touchEnd","(II)V",(void*)touchEnd},
@@ -845,14 +911,15 @@ namespace QtAndroidInput
{"keyUp", "(IIIZ)V", (void *)keyUp},
{"keyboardVisibilityChanged", "(Z)V", (void *)keyboardVisibilityChanged},
{"keyboardGeometryChanged", "(IIII)V", (void *)keyboardGeometryChanged},
- {"handleLocationChanged", "(III)V", (void *)handleLocationChanged}
+ {"handleLocationChanged", "(III)V", (void *)handleLocationChanged},
+ {"dispatchGenericMotionEvent", "(Landroid/view/MotionEvent;)Z", reinterpret_cast<void *>(dispatchGenericMotionEvent)},
+ {"dispatchKeyEvent", "(Landroid/view/KeyEvent;)Z", reinterpret_cast<void *>(dispatchKeyEvent)},
};
- bool registerNatives(JNIEnv *env)
+ bool registerNatives(QJniEnvironment &env)
{
- jclass appClass = QtAndroid::applicationClass();
-
- if (env->RegisterNatives(appClass, methods, sizeof(methods) / sizeof(methods[0])) < 0) {
+ if (!env.registerNativeMethods(QtJniTypes::Traits<QtJniTypes::QtInputDelegate>::className(),
+ methods, sizeof(methods) / sizeof(methods[0]))) {
__android_log_print(ANDROID_LOG_FATAL,"Qt", "RegisterNatives failed");
return false;
}
diff --git a/src/plugins/platforms/android/androidjniinput.h b/src/plugins/platforms/android/androidjniinput.h
index 7ef51ef9f7..28a2665bf6 100644
--- a/src/plugins/platforms/android/androidjniinput.h
+++ b/src/plugins/platforms/android/androidjniinput.h
@@ -6,10 +6,15 @@
#include <jni.h>
#include <QtCore/qglobal.h>
+#include <QtCore/QLoggingCategory>
#include <QtCore/QRect>
QT_BEGIN_NAMESPACE
+Q_DECLARE_LOGGING_CATEGORY(lcQpaInputMethods);
+
+class QJniEnvironment;
+
namespace QtAndroidInput
{
// Software keyboard support
@@ -26,7 +31,27 @@ namespace QtAndroidInput
QPoint cursor = QPoint(), QPoint anchor = QPoint(), bool rtl = false);
int getSelectHandleWidth();
- bool registerNatives(JNIEnv *env);
+ class GenericMotionEventListener
+ {
+ public:
+ virtual ~GenericMotionEventListener();
+ virtual bool handleGenericMotionEvent(jobject event) = 0;
+ };
+
+ class KeyEventListener
+ {
+ public:
+ virtual ~KeyEventListener();
+ virtual bool handleKeyEvent(jobject event) = 0;
+ };
+
+ void registerGenericMotionEventListener(GenericMotionEventListener *listener);
+ void unregisterGenericMotionEventListener(GenericMotionEventListener *listener);
+
+ void registerKeyEventListener(KeyEventListener *listener);
+ void unregisterKeyEventListener(KeyEventListener *listener);
+
+ bool registerNatives(QJniEnvironment &env);
}
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/android/androidjnimain.cpp b/src/plugins/platforms/android/androidjnimain.cpp
index fee7c438e5..206bbd03d6 100644
--- a/src/plugins/platforms/android/androidjnimain.cpp
+++ b/src/plugins/platforms/android/androidjnimain.cpp
@@ -10,14 +10,16 @@
#include "androidcontentfileengine.h"
#include "androiddeadlockprotector.h"
#include "androidjniaccessibility.h"
-#include "androidjniclipboard.h"
#include "androidjniinput.h"
#include "androidjnimain.h"
#include "androidjnimenu.h"
+#include "androidwindowembedding.h"
#include "qandroidassetsfileenginehandler.h"
#include "qandroideventdispatcher.h"
#include "qandroidplatformdialoghelpers.h"
#include "qandroidplatformintegration.h"
+#include "qandroidplatformclipboard.h"
+#include "qandroidplatformwindow.h"
#include <android/api-level.h>
#include <android/asset_manager_jni.h>
@@ -29,12 +31,16 @@
#include <QtCore/qjniobject.h>
#include <QtCore/qprocess.h>
#include <QtCore/qresource.h>
+#include <QtCore/qscopeguard.h>
#include <QtCore/qthread.h>
#include <QtGui/private/qguiapplication_p.h>
#include <QtGui/private/qhighdpiscaling_p.h>
#include <qpa/qwindowsysteminterface.h>
+
+using namespace Qt::StringLiterals;
+
QT_BEGIN_NAMESPACE
static JavaVM *m_javaVM = nullptr;
@@ -44,11 +50,12 @@ static jmethodID m_loadClassMethodID = nullptr;
static AAssetManager *m_assetManager = nullptr;
static jobject m_assets = nullptr;
static jobject m_resourcesObj = nullptr;
-static QtJniTypes::Activity m_activityObject = nullptr;
-static jmethodID m_createSurfaceMethodID = nullptr;
-static QtJniTypes::Service m_serviceObject = nullptr;
-static jmethodID m_setSurfaceGeometryMethodID = nullptr;
-static jmethodID m_destroySurfaceMethodID = nullptr;
+
+static jclass m_qtActivityClass = nullptr;
+static jclass m_qtServiceClass = nullptr;
+
+static QtJniTypes::QtActivityDelegateBase m_activityDelegate = nullptr;
+static QtJniTypes::QtInputDelegate m_inputDelegate = nullptr;
static int m_pendingApplicationState = -1;
static QBasicMutex m_platformMutex;
@@ -67,10 +74,6 @@ static void *m_mainLibraryHnd = nullptr;
static QList<QByteArray> m_applicationParams;
static sem_t m_exitSemaphore, m_terminateSemaphore;
-QHash<int, AndroidSurfaceClient *> m_surfaces;
-
-static QBasicMutex m_surfacesMutex;
-
static QAndroidPlatformIntegration *m_androidPlatformIntegration = nullptr;
@@ -88,7 +91,9 @@ static const char m_qtTag[] = "Qt";
static const char m_classErrorMsg[] = "Can't find class \"%s\"";
static const char m_methodErrorMsg[] = "Can't find method \"%s%s\"";
-static QBasicAtomicInt startQtAndroidPluginCalled = Q_BASIC_ATOMIC_INITIALIZER(0);
+Q_CONSTINIT static QBasicAtomicInt startQtAndroidPluginCalled = Q_BASIC_ATOMIC_INITIALIZER(0);
+
+Q_DECLARE_JNI_CLASS(QtEmbeddedDelegateFactory, "org/qtproject/qt/android/QtEmbeddedDelegateFactory")
namespace QtAndroid
{
@@ -100,6 +105,7 @@ namespace QtAndroid
void setAndroidPlatformIntegration(QAndroidPlatformIntegration *androidPlatformIntegration)
{
m_androidPlatformIntegration = androidPlatformIntegration;
+ QtAndroid::notifyNativePluginIntegrationReady((bool)m_androidPlatformIntegration);
// flush the pending state if necessary.
if (m_androidPlatformIntegration && (m_pendingApplicationState != -1)) {
@@ -125,6 +131,21 @@ namespace QtAndroid
: 0;
}
+ QWindow *windowFromId(int windowId)
+ {
+ if (!qGuiApp)
+ return nullptr;
+
+ for (QWindow *w : qGuiApp->allWindows()) {
+ if (!w->handle())
+ continue;
+ QAndroidPlatformWindow *window = static_cast<QAndroidPlatformWindow *>(w->handle());
+ if (window->nativeViewId() == windowId)
+ return w;
+ }
+ return nullptr;
+ }
+
int availableWidthPixels()
{
return m_availableWidthPixels;
@@ -160,47 +181,90 @@ namespace QtAndroid
return m_applicationClass;
}
- QtJniTypes::Activity activity()
+ // TODO move calls from here to where they logically belong
+ void setSystemUiVisibility(SystemUiVisibility uiVisibility)
{
- return m_activityObject;
+ qtActivityDelegate().callMethod<void>("setSystemUiVisibility", jint(uiVisibility));
}
- QtJniTypes::Service service()
+ // FIXME: avoid direct access to QtActivityDelegate
+ QtJniTypes::QtActivityDelegateBase qtActivityDelegate()
{
- return m_serviceObject;
+ using namespace QtJniTypes;
+ if (!m_activityDelegate.isValid()) {
+ if (isQtApplication()) {
+ auto context = QtAndroidPrivate::activity();
+ m_activityDelegate = context.callMethod<QtActivityDelegateBase>("getActivityDelegate");
+ } else {
+ m_activityDelegate = QJniObject::callStaticMethod<QtActivityDelegateBase>(
+ Traits<QtEmbeddedDelegateFactory>::className(),
+ "getActivityDelegate",
+ QtAndroidPrivate::activity());
+ }
+ }
+
+ return m_activityDelegate;
}
- void setSystemUiVisibility(SystemUiVisibility uiVisibility)
+ QtJniTypes::QtInputDelegate qtInputDelegate()
+ {
+ if (!m_inputDelegate.isValid()) {
+ m_inputDelegate = qtActivityDelegate().callMethod<QtJniTypes::QtInputDelegate>(
+ "getInputDelegate");
+ }
+
+ return m_inputDelegate;
+ }
+
+ bool isQtApplication()
{
- QJniObject::callStaticMethod<void>(m_applicationClass, "setSystemUiVisibility", "(I)V", jint(uiVisibility));
+ // Returns true if the app is a Qt app, i.e. Qt controls the whole app and
+ // the Activity/Service is created by Qt. Returns false if instead Qt is
+ // embedded into a native Android app, where the Activity/Service is created
+ // by the user, outside of Qt, and Qt content is added as a view.
+ JNIEnv *env = QJniEnvironment::getJniEnv();
+ auto activity = QtAndroidPrivate::activity();
+ if (activity.isValid())
+ return env->IsInstanceOf(activity.object(), m_qtActivityClass);
+ auto service = QtAndroidPrivate::service();
+ if (service.isValid())
+ return env->IsInstanceOf(QtAndroidPrivate::service().object(), m_qtServiceClass);
+ // return true as default as Qt application is our default use case.
+ // famous last words: we should not end up here
+ return true;
}
void notifyAccessibilityLocationChange(uint accessibilityObjectId)
{
- QJniObject::callStaticMethod<void>(m_applicationClass, "notifyAccessibilityLocationChange",
- "(I)V", accessibilityObjectId);
+ qtActivityDelegate().callMethod<void>("notifyLocationChange", accessibilityObjectId);
}
void notifyObjectHide(uint accessibilityObjectId, uint parentObjectId)
{
- QJniObject::callStaticMethod<void>(m_applicationClass, "notifyObjectHide", "(II)V",
- accessibilityObjectId, parentObjectId);
+ qtActivityDelegate().callMethod<void>("notifyObjectHide",
+ accessibilityObjectId, parentObjectId);
}
void notifyObjectFocus(uint accessibilityObjectId)
{
- QJniObject::callStaticMethod<void>(m_applicationClass, "notifyObjectFocus","(I)V", accessibilityObjectId);
+ qtActivityDelegate().callMethod<void>("notifyObjectFocus", accessibilityObjectId);
}
void notifyValueChanged(uint accessibilityObjectId, jstring value)
{
- QJniObject::callStaticMethod<void>(m_applicationClass, "notifyValueChanged",
- "(ILjava/lang/String;)V", accessibilityObjectId, value);
+ qtActivityDelegate().callMethod<void>("notifyValueChanged", accessibilityObjectId, value);
}
- void notifyQtAndroidPluginRunning(bool running)
+ void notifyScrolledEvent(uint accessibilityObjectId)
{
- QJniObject::callStaticMethod<void>(m_applicationClass, "notifyQtAndroidPluginRunning","(Z)V", running);
+ qtActivityDelegate().callMethod<void>("notifyScrolledEvent", accessibilityObjectId);
+ }
+
+ void notifyNativePluginIntegrationReady(bool ready)
+ {
+ QJniObject::callStaticMethod<void>(m_applicationClass,
+ "notifyNativePluginIntegrationReady",
+ ready);
}
jobject createBitmap(QImage img, JNIEnv *env)
@@ -209,7 +273,7 @@ namespace QtAndroid
return 0;
if (img.format() != QImage::Format_RGBA8888 && img.format() != QImage::Format_RGB16)
- img = img.convertToFormat(QImage::Format_RGBA8888);
+ img = std::move(img).convertToFormat(QImage::Format_RGBA8888);
jobject bitmap = env->CallStaticObjectMethod(m_bitmapClass,
m_createBitmapMethodID,
@@ -297,62 +361,6 @@ namespace QtAndroid
return manufacturer + u' ' + model;
}
- jint generateViewId()
- {
- return QJniObject::callStaticMethod<jint>("android/view/View", "generateViewId", "()I");
- }
-
- int createSurface(AndroidSurfaceClient *client, const QRect &geometry, bool onTop, int imageDepth)
- {
- QJniEnvironment env;
- if (!env.jniEnv())
- return -1;
-
- m_surfacesMutex.lock();
- jint surfaceId = generateViewId();
- m_surfaces[surfaceId] = client;
- m_surfacesMutex.unlock();
-
- jint x = 0, y = 0, w = -1, h = -1;
- if (!geometry.isNull()) {
- x = geometry.x();
- y = geometry.y();
- w = std::max(geometry.width(), 1);
- h = std::max(geometry.height(), 1);
- }
- env->CallStaticVoidMethod(m_applicationClass,
- m_createSurfaceMethodID,
- surfaceId,
- jboolean(onTop),
- x, y, w, h,
- imageDepth);
- return surfaceId;
- }
-
- int insertNativeView(jobject view, const QRect &geometry)
- {
- m_surfacesMutex.lock();
- jint surfaceId = generateViewId();
- m_surfaces[surfaceId] = nullptr; // dummy
- m_surfacesMutex.unlock();
-
- jint x = 0, y = 0, w = -1, h = -1;
- if (!geometry.isNull())
- geometry.getRect(&x, &y, &w, &h);
-
- QJniObject::callStaticMethod<void>(m_applicationClass,
- "insertNativeView",
- "(ILandroid/view/View;IIII)V",
- surfaceId,
- view,
- x,
- y,
- qMax(w, 1),
- qMax(h, 1));
-
- return surfaceId;
- }
-
void setViewVisibility(jobject view, bool visible)
{
QJniObject::callStaticMethod<void>(m_applicationClass,
@@ -362,69 +370,6 @@ namespace QtAndroid
visible);
}
- void setSurfaceGeometry(int surfaceId, const QRect &geometry)
- {
- if (surfaceId == -1)
- return;
-
- QJniEnvironment env;
- if (!env.jniEnv())
- return;
- jint x = 0, y = 0, w = -1, h = -1;
- if (!geometry.isNull()) {
- x = geometry.x();
- y = geometry.y();
- w = geometry.width();
- h = geometry.height();
- }
- env->CallStaticVoidMethod(m_applicationClass,
- m_setSurfaceGeometryMethodID,
- surfaceId,
- x, y, w, h);
- }
-
-
- void destroySurface(int surfaceId)
- {
- if (surfaceId == -1)
- return;
-
- {
- QMutexLocker lock(&m_surfacesMutex);
- const auto &it = m_surfaces.find(surfaceId);
- if (it != m_surfaces.end())
- m_surfaces.erase(it);
- }
-
- QJniEnvironment env;
- if (env.jniEnv())
- env->CallStaticVoidMethod(m_applicationClass,
- m_destroySurfaceMethodID,
- surfaceId);
- }
-
- void bringChildToFront(int surfaceId)
- {
- if (surfaceId == -1)
- return;
-
- QJniObject::callStaticMethod<void>(m_applicationClass,
- "bringChildToFront",
- "(I)V",
- surfaceId);
- }
-
- void bringChildToBack(int surfaceId)
- {
- if (surfaceId == -1)
- return;
-
- QJniObject::callStaticMethod<void>(m_applicationClass,
- "bringChildToBack",
- "(I)V",
- surfaceId);
- }
-
bool blockEventLoopsWhenSuspended()
{
static bool block = qEnvironmentVariableIntValue("QT_BLOCK_EVENT_LOOPS_WHEN_SUSPENDED");
@@ -440,14 +385,14 @@ namespace QtAndroid
static jboolean startQtAndroidPlugin(JNIEnv *env, jobject /*object*/, jstring paramsString)
{
+ Q_UNUSED(env)
+
m_androidPlatformIntegration = nullptr;
m_androidAssetsFileEngineHandler = new AndroidAssetsFileEngineHandler();
m_androidContentFileEngineHandler = new AndroidContentFileEngineHandler();
m_mainLibraryHnd = nullptr;
- const char *nativeString = env->GetStringUTFChars(paramsString, 0);
- const QStringList argsList = QProcess::splitCommand(QString::fromUtf8(nativeString));
- env->ReleaseStringUTFChars(paramsString, nativeString);
+ const QStringList argsList = QProcess::splitCommand(QJniObject(paramsString).toString());
for (const QString &arg : argsList)
m_applicationParams.append(arg.toUtf8());
@@ -490,7 +435,7 @@ static void waitForServiceSetup(JNIEnv *env, jclass /*clazz*/)
Q_UNUSED(env);
// The service must wait until the QCoreApplication starts otherwise onBind will be
// called too early
- if (m_serviceObject)
+ if (QtAndroidPrivate::service().isValid())
QtAndroidPrivate::waitForServiceSetup();
}
@@ -521,7 +466,8 @@ static void startQtApplication(JNIEnv */*env*/, jclass /*clazz*/)
argv[argc] = nullptr;
startQtAndroidPluginCalled.fetchAndAddRelease(1);
- int ret = m_main(argc, argv.data());
+ const int ret = m_main(argc, argv.data());
+ qInfo() << "main() returned" << ret;
if (m_mainLibraryHnd) {
int res = dlclose(m_mainLibraryHnd);
@@ -529,10 +475,8 @@ static void startQtApplication(JNIEnv */*env*/, jclass /*clazz*/)
qWarning() << "dlclose failed:" << dlerror();
}
- if (m_applicationClass) {
- qWarning("exit app 0");
+ if (m_applicationClass)
QJniObject::callStaticMethod<void>(m_applicationClass, "quitApp", "()V");
- }
sem_post(&m_terminateSemaphore);
sem_wait(&m_exitSemaphore);
@@ -577,10 +521,6 @@ static void terminateQt(JNIEnv *env, jclass /*clazz*/)
env->DeleteGlobalRef(m_classLoaderObject);
if (m_resourcesObj)
env->DeleteGlobalRef(m_resourcesObj);
- if (m_activityObject)
- env->DeleteGlobalRef(m_activityObject);
- if (m_serviceObject)
- env->DeleteGlobalRef(m_serviceObject);
if (m_bitmapClass)
env->DeleteGlobalRef(m_bitmapClass);
if (m_ARGB_8888_BitmapConfigValue)
@@ -591,53 +531,49 @@ static void terminateQt(JNIEnv *env, jclass /*clazz*/)
env->DeleteGlobalRef(m_bitmapDrawableClass);
if (m_assets)
env->DeleteGlobalRef(m_assets);
+ if (m_qtActivityClass)
+ env->DeleteGlobalRef(m_qtActivityClass);
+ if (m_qtServiceClass)
+ env->DeleteGlobalRef(m_qtServiceClass);
m_androidPlatformIntegration = nullptr;
delete m_androidAssetsFileEngineHandler;
m_androidAssetsFileEngineHandler = nullptr;
sem_post(&m_exitSemaphore);
}
-static void setSurface(JNIEnv *env, jobject /*thiz*/, jint id, jobject jSurface, jint w, jint h)
-{
- QMutexLocker lock(&m_surfacesMutex);
- const auto &it = m_surfaces.find(id);
- if (it == m_surfaces.end())
- return;
-
- auto surfaceClient = it.value();
- if (surfaceClient)
- surfaceClient->surfaceChanged(env, jSurface, w, h);
-}
-
static void setDisplayMetrics(JNIEnv * /*env*/, jclass /*clazz*/, jint screenWidthPixels,
jint screenHeightPixels, jint availableLeftPixels,
jint availableTopPixels, jint availableWidthPixels,
jint availableHeightPixels, jdouble xdpi, jdouble ydpi,
jdouble scaledDensity, jdouble density, jfloat refreshRate)
{
+ Q_UNUSED(availableLeftPixels)
+ Q_UNUSED(availableTopPixels)
+
m_availableWidthPixels = availableWidthPixels;
m_availableHeightPixels = availableHeightPixels;
m_scaledDensity = scaledDensity;
m_density = density;
+ const QSize screenSize(screenWidthPixels, screenHeightPixels);
+ // available geometry always starts from top left
+ const QRect availableGeometry(0, 0, availableWidthPixels, availableHeightPixels);
+ const QSize physicalSize(qRound(double(screenWidthPixels) / xdpi * 25.4),
+ qRound(double(screenHeightPixels) / ydpi * 25.4));
+
QMutexLocker lock(&m_platformMutex);
if (!m_androidPlatformIntegration) {
QAndroidPlatformIntegration::setDefaultDisplayMetrics(
- availableLeftPixels, availableTopPixels, availableWidthPixels,
- availableHeightPixels, qRound(double(screenWidthPixels) / xdpi * 25.4),
- qRound(double(screenHeightPixels) / ydpi * 25.4), screenWidthPixels,
- screenHeightPixels);
+ availableGeometry.left(), availableGeometry.top(), availableGeometry.width(),
+ availableGeometry.height(), physicalSize.width(), physicalSize.height(),
+ screenSize.width(), screenSize.height());
} else {
- const QSize physicalSize(qRound(double(screenWidthPixels) / xdpi * 25.4),
- qRound(double(screenHeightPixels) / ydpi * 25.4));
- const QSize screenSize(screenWidthPixels, screenHeightPixels);
- const QRect availableGeometry(availableLeftPixels, availableTopPixels,
- availableWidthPixels, availableHeightPixels);
m_androidPlatformIntegration->setScreenSizeParameters(physicalSize, screenSize,
availableGeometry);
m_androidPlatformIntegration->setRefreshRate(refreshRate);
}
}
+Q_DECLARE_JNI_NATIVE_METHOD(setDisplayMetrics)
static void updateWindow(JNIEnv */*env*/, jobject /*thiz*/)
{
@@ -657,10 +593,6 @@ static void updateWindow(JNIEnv */*env*/, jobject /*thiz*/)
QWindowSystemInterface::handleExposeEvent(w, QRegion(QRect(QPoint(), w->geometry().size())));
}
}
-
- QAndroidPlatformScreen *screen = static_cast<QAndroidPlatformScreen *>(m_androidPlatformIntegration->screen());
- if (screen->rasterSurfaces())
- QMetaObject::invokeMethod(screen, "setDirty", Qt::QueuedConnection, Q_ARG(QRect,screen->geometry()));
}
static void updateApplicationState(JNIEnv */*env*/, jobject /*thiz*/, jint state)
@@ -739,12 +671,42 @@ static void handleOrientationChanged(JNIEnv */*env*/, jobject /*thiz*/, jint new
}
}
}
+Q_DECLARE_JNI_NATIVE_METHOD(handleOrientationChanged)
static void handleRefreshRateChanged(JNIEnv */*env*/, jclass /*cls*/, jfloat refreshRate)
{
if (m_androidPlatformIntegration)
m_androidPlatformIntegration->setRefreshRate(refreshRate);
}
+Q_DECLARE_JNI_NATIVE_METHOD(handleRefreshRateChanged)
+
+static void handleScreenAdded(JNIEnv */*env*/, jclass /*cls*/, jint displayId)
+{
+ if (m_androidPlatformIntegration)
+ m_androidPlatformIntegration->handleScreenAdded(displayId);
+}
+Q_DECLARE_JNI_NATIVE_METHOD(handleScreenAdded)
+
+static void handleScreenChanged(JNIEnv */*env*/, jclass /*cls*/, jint displayId)
+{
+ if (m_androidPlatformIntegration)
+ m_androidPlatformIntegration->handleScreenChanged(displayId);
+}
+Q_DECLARE_JNI_NATIVE_METHOD(handleScreenChanged)
+
+static void handleScreenRemoved(JNIEnv */*env*/, jclass /*cls*/, jint displayId)
+{
+ if (m_androidPlatformIntegration)
+ m_androidPlatformIntegration->handleScreenRemoved(displayId);
+}
+Q_DECLARE_JNI_NATIVE_METHOD(handleScreenRemoved)
+
+static void handleUiDarkModeChanged(JNIEnv */*env*/, jobject /*thiz*/, jint newUiMode)
+{
+ QAndroidPlatformIntegration::setColorScheme(
+ (newUiMode == 1 ) ? Qt::ColorScheme::Dark : Qt::ColorScheme::Light);
+}
+Q_DECLARE_JNI_NATIVE_METHOD(handleUiDarkModeChanged)
static void onActivityResult(JNIEnv */*env*/, jclass /*cls*/,
jint requestCode,
@@ -771,112 +733,131 @@ static JNINativeMethod methods[] = {
{ "quitQtCoreApplication", "()V", (void *)quitQtCoreApplication },
{ "terminateQt", "()V", (void *)terminateQt },
{ "waitForServiceSetup", "()V", (void *)waitForServiceSetup },
- { "setDisplayMetrics", "(IIIIIIDDDDF)V", (void *)setDisplayMetrics },
- { "setSurface", "(ILjava/lang/Object;II)V", (void *)setSurface },
{ "updateWindow", "()V", (void *)updateWindow },
{ "updateApplicationState", "(I)V", (void *)updateApplicationState },
- { "handleOrientationChanged", "(II)V", (void *)handleOrientationChanged },
{ "onActivityResult", "(IILandroid/content/Intent;)V", (void *)onActivityResult },
{ "onNewIntent", "(Landroid/content/Intent;)V", (void *)onNewIntent },
- { "onBind", "(Landroid/content/Intent;)Landroid/os/IBinder;", (void *)onBind },
- { "handleRefreshRateChanged", "(F)V", (void *)handleRefreshRateChanged }
+ { "onBind", "(Landroid/content/Intent;)Landroid/os/IBinder;", (void *)onBind }
};
#define FIND_AND_CHECK_CLASS(CLASS_NAME) \
clazz = env->FindClass(CLASS_NAME); \
if (!clazz) { \
__android_log_print(ANDROID_LOG_FATAL, m_qtTag, m_classErrorMsg, CLASS_NAME); \
- return JNI_FALSE; \
+ return false; \
}
#define GET_AND_CHECK_METHOD(VAR, CLASS, METHOD_NAME, METHOD_SIGNATURE) \
VAR = env->GetMethodID(CLASS, METHOD_NAME, METHOD_SIGNATURE); \
if (!VAR) { \
__android_log_print(ANDROID_LOG_FATAL, m_qtTag, m_methodErrorMsg, METHOD_NAME, METHOD_SIGNATURE); \
- return JNI_FALSE; \
+ return false; \
}
#define GET_AND_CHECK_STATIC_METHOD(VAR, CLASS, METHOD_NAME, METHOD_SIGNATURE) \
VAR = env->GetStaticMethodID(CLASS, METHOD_NAME, METHOD_SIGNATURE); \
if (!VAR) { \
__android_log_print(ANDROID_LOG_FATAL, m_qtTag, m_methodErrorMsg, METHOD_NAME, METHOD_SIGNATURE); \
- return JNI_FALSE; \
+ return false; \
}
#define GET_AND_CHECK_FIELD(VAR, CLASS, FIELD_NAME, FIELD_SIGNATURE) \
VAR = env->GetFieldID(CLASS, FIELD_NAME, FIELD_SIGNATURE); \
if (!VAR) { \
__android_log_print(ANDROID_LOG_FATAL, m_qtTag, m_methodErrorMsg, FIELD_NAME, FIELD_SIGNATURE); \
- return JNI_FALSE; \
+ return false; \
}
#define GET_AND_CHECK_STATIC_FIELD(VAR, CLASS, FIELD_NAME, FIELD_SIGNATURE) \
VAR = env->GetStaticFieldID(CLASS, FIELD_NAME, FIELD_SIGNATURE); \
if (!VAR) { \
__android_log_print(ANDROID_LOG_FATAL, m_qtTag, m_methodErrorMsg, FIELD_NAME, FIELD_SIGNATURE); \
- return JNI_FALSE; \
+ return false; \
}
-static int registerNatives(JNIEnv *env)
+Q_DECLARE_JNI_CLASS(QtDisplayManager, "org/qtproject/qt/android/QtDisplayManager")
+
+static bool registerNatives(QJniEnvironment &env)
{
jclass clazz;
FIND_AND_CHECK_CLASS("org/qtproject/qt/android/QtNative");
m_applicationClass = static_cast<jclass>(env->NewGlobalRef(clazz));
- if (env->RegisterNatives(m_applicationClass, methods, sizeof(methods) / sizeof(methods[0])) < 0) {
+ if (!env.registerNativeMethods(m_applicationClass,
+ methods, sizeof(methods) / sizeof(methods[0]))) {
__android_log_print(ANDROID_LOG_FATAL,"Qt", "RegisterNatives failed");
- return JNI_FALSE;
+ return false;
}
- GET_AND_CHECK_STATIC_METHOD(m_createSurfaceMethodID, m_applicationClass, "createSurface", "(IZIIIII)V");
- GET_AND_CHECK_STATIC_METHOD(m_setSurfaceGeometryMethodID, m_applicationClass, "setSurfaceGeometry", "(IIIII)V");
- GET_AND_CHECK_STATIC_METHOD(m_destroySurfaceMethodID, m_applicationClass, "destroySurface", "(I)V");
+ bool success = env.registerNativeMethods(
+ QtJniTypes::Traits<QtJniTypes::QtDisplayManager>::className(),
+ {
+ Q_JNI_NATIVE_METHOD(setDisplayMetrics),
+ Q_JNI_NATIVE_METHOD(handleOrientationChanged),
+ Q_JNI_NATIVE_METHOD(handleRefreshRateChanged),
+ Q_JNI_NATIVE_METHOD(handleScreenAdded),
+ Q_JNI_NATIVE_METHOD(handleScreenChanged),
+ Q_JNI_NATIVE_METHOD(handleScreenRemoved),
+ Q_JNI_NATIVE_METHOD(handleUiDarkModeChanged)
+ });
+
+ if (!success) {
+ qCritical() << "QtDisplayManager: registerNativeMethods() failed";
+ return JNI_FALSE;
+ }
jmethodID methodID;
GET_AND_CHECK_STATIC_METHOD(methodID, m_applicationClass, "activity", "()Landroid/app/Activity;");
- jobject activityObject = env->CallStaticObjectMethod(m_applicationClass, methodID);
- GET_AND_CHECK_STATIC_METHOD(methodID, m_applicationClass, "service", "()Landroid/app/Service;");
- jobject serviceObject = env->CallStaticObjectMethod(m_applicationClass, methodID);
+ jobject contextObject = env->CallStaticObjectMethod(m_applicationClass, methodID);
+ if (!contextObject) {
+ GET_AND_CHECK_STATIC_METHOD(methodID, m_applicationClass, "service", "()Landroid/app/Service;");
+ contextObject = env->CallStaticObjectMethod(m_applicationClass, methodID);
+ }
+
+ if (!contextObject) {
+ __android_log_print(ANDROID_LOG_FATAL,"Qt", "Failed to get Activity or Service object");
+ return false;
+ }
+ const auto releaseContextObject = qScopeGuard([&env, contextObject]{
+ env->DeleteLocalRef(contextObject);
+ });
+
GET_AND_CHECK_STATIC_METHOD(methodID, m_applicationClass, "classLoader", "()Ljava/lang/ClassLoader;");
m_classLoaderObject = env->NewGlobalRef(env->CallStaticObjectMethod(m_applicationClass, methodID));
clazz = env->GetObjectClass(m_classLoaderObject);
GET_AND_CHECK_METHOD(m_loadClassMethodID, clazz, "loadClass", "(Ljava/lang/String;)Ljava/lang/Class;");
- if (serviceObject)
- m_serviceObject = env->NewGlobalRef(serviceObject);
-
- if (activityObject)
- m_activityObject = env->NewGlobalRef(activityObject);
-
- jobject object = activityObject ? activityObject : serviceObject;
- if (object) {
- FIND_AND_CHECK_CLASS("android/content/ContextWrapper");
- GET_AND_CHECK_METHOD(methodID, clazz, "getAssets", "()Landroid/content/res/AssetManager;");
- m_assets = env->NewGlobalRef(env->CallObjectMethod(object, methodID));
- m_assetManager = AAssetManager_fromJava(env, m_assets);
-
- GET_AND_CHECK_METHOD(methodID, clazz, "getResources", "()Landroid/content/res/Resources;");
- m_resourcesObj = env->NewGlobalRef(env->CallObjectMethod(object, methodID));
-
- FIND_AND_CHECK_CLASS("android/graphics/Bitmap");
- m_bitmapClass = static_cast<jclass>(env->NewGlobalRef(clazz));
- GET_AND_CHECK_STATIC_METHOD(m_createBitmapMethodID, m_bitmapClass
- , "createBitmap", "(IILandroid/graphics/Bitmap$Config;)Landroid/graphics/Bitmap;");
- FIND_AND_CHECK_CLASS("android/graphics/Bitmap$Config");
- jfieldID fieldId;
- GET_AND_CHECK_STATIC_FIELD(fieldId, clazz, "ARGB_8888", "Landroid/graphics/Bitmap$Config;");
- m_ARGB_8888_BitmapConfigValue = env->NewGlobalRef(env->GetStaticObjectField(clazz, fieldId));
- GET_AND_CHECK_STATIC_FIELD(fieldId, clazz, "RGB_565", "Landroid/graphics/Bitmap$Config;");
- m_RGB_565_BitmapConfigValue = env->NewGlobalRef(env->GetStaticObjectField(clazz, fieldId));
-
- FIND_AND_CHECK_CLASS("android/graphics/drawable/BitmapDrawable");
- m_bitmapDrawableClass = static_cast<jclass>(env->NewGlobalRef(clazz));
- GET_AND_CHECK_METHOD(m_bitmapDrawableConstructorMethodID,
- m_bitmapDrawableClass,
- "<init>",
- "(Landroid/content/res/Resources;Landroid/graphics/Bitmap;)V");
- }
-
- return JNI_TRUE;
+
+ FIND_AND_CHECK_CLASS("android/content/ContextWrapper");
+ GET_AND_CHECK_METHOD(methodID, clazz, "getAssets", "()Landroid/content/res/AssetManager;");
+ m_assets = env->NewGlobalRef(env->CallObjectMethod(contextObject, methodID));
+ m_assetManager = AAssetManager_fromJava(env.jniEnv(), m_assets);
+
+ GET_AND_CHECK_METHOD(methodID, clazz, "getResources", "()Landroid/content/res/Resources;");
+ m_resourcesObj = env->NewGlobalRef(env->CallObjectMethod(contextObject, methodID));
+
+ FIND_AND_CHECK_CLASS("android/graphics/Bitmap");
+ m_bitmapClass = static_cast<jclass>(env->NewGlobalRef(clazz));
+ GET_AND_CHECK_STATIC_METHOD(m_createBitmapMethodID, m_bitmapClass,
+ "createBitmap", "(IILandroid/graphics/Bitmap$Config;)Landroid/graphics/Bitmap;");
+ FIND_AND_CHECK_CLASS("android/graphics/Bitmap$Config");
+ jfieldID fieldId;
+ GET_AND_CHECK_STATIC_FIELD(fieldId, clazz, "ARGB_8888", "Landroid/graphics/Bitmap$Config;");
+ m_ARGB_8888_BitmapConfigValue = env->NewGlobalRef(env->GetStaticObjectField(clazz, fieldId));
+ GET_AND_CHECK_STATIC_FIELD(fieldId, clazz, "RGB_565", "Landroid/graphics/Bitmap$Config;");
+ m_RGB_565_BitmapConfigValue = env->NewGlobalRef(env->GetStaticObjectField(clazz, fieldId));
+
+ FIND_AND_CHECK_CLASS("android/graphics/drawable/BitmapDrawable");
+ m_bitmapDrawableClass = static_cast<jclass>(env->NewGlobalRef(clazz));
+ GET_AND_CHECK_METHOD(m_bitmapDrawableConstructorMethodID,
+ m_bitmapDrawableClass,
+ "<init>", "(Landroid/content/res/Resources;Landroid/graphics/Bitmap;)V");
+
+ FIND_AND_CHECK_CLASS("org/qtproject/qt/android/QtActivityBase");
+ m_qtActivityClass = static_cast<jclass>(env->NewGlobalRef(clazz));
+ FIND_AND_CHECK_CLASS("org/qtproject/qt/android/QtServiceBase");
+ m_qtServiceClass = static_cast<jclass>(env->NewGlobalRef(clazz));
+
+ return true;
}
QT_END_NAMESPACE
@@ -889,36 +870,29 @@ Q_DECL_EXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void */*reserved*/)
initialized = true;
QT_USE_NAMESPACE
- typedef union {
- JNIEnv *nativeEnvironment;
- void *venv;
- } UnionJNIEnvToVoid;
-
- UnionJNIEnvToVoid uenv;
- uenv.venv = nullptr;
- m_javaVM = nullptr;
-
- if (vm->GetEnv(&uenv.venv, JNI_VERSION_1_6) != JNI_OK) {
- __android_log_print(ANDROID_LOG_FATAL, "Qt", "GetEnv failed");
+ m_javaVM = vm;
+ QJniEnvironment env;
+ if (!env.isValid()) {
+ m_javaVM = nullptr;
+ __android_log_print(ANDROID_LOG_FATAL, "Qt", "Failed to initialize the JNI Environment");
return -1;
}
- JNIEnv *env = uenv.nativeEnvironment;
if (!registerNatives(env)
|| !QtAndroidInput::registerNatives(env)
|| !QtAndroidMenu::registerNatives(env)
|| !QtAndroidAccessibility::registerNatives(env)
- || !QtAndroidDialogHelpers::registerNatives(env)) {
+ || !QtAndroidDialogHelpers::registerNatives(env)
+ || !QAndroidPlatformClipboard::registerNatives(env)
+ || !QAndroidPlatformWindow::registerNatives(env)
+ || !QtAndroidWindowEmbedding::registerNatives(env)) {
__android_log_print(ANDROID_LOG_FATAL, "Qt", "registerNatives failed");
return -1;
}
QWindowSystemInterfacePrivate::TabletEvent::setPlatformSynthesizesMouse(false);
- m_javaVM = vm;
// attach qt main thread data to this thread
- QObject threadSetter;
- if (threadSetter.thread())
- threadSetter.thread()->setObjectName("QtMainLoopThread");
+ QThread::currentThread()->setObjectName("QtMainLoopThread");
__android_log_print(ANDROID_LOG_INFO, "Qt", "qt started");
return JNI_VERSION_1_6;
}
diff --git a/src/plugins/platforms/android/androidjnimain.h b/src/plugins/platforms/android/androidjnimain.h
index 8d05e31f66..57a71a8650 100644
--- a/src/plugins/platforms/android/androidjnimain.h
+++ b/src/plugins/platforms/android/androidjnimain.h
@@ -12,6 +12,7 @@
#include <QImage>
#include <private/qjnihelpers_p.h>
+#include <QtCore/QJniObject>
QT_BEGIN_NAMESPACE
@@ -22,26 +23,22 @@ class QAndroidPlatformIntegration;
class QWidget;
class QString;
class QWindow;
-class AndroidSurfaceClient;
+class QAndroidPlatformWindow;
class QBasicMutex;
+Q_DECLARE_JNI_CLASS(QtActivityDelegateBase, "org/qtproject/qt/android/QtActivityDelegateBase")
+Q_DECLARE_JNI_CLASS(QtInputDelegate, "org/qtproject/qt/android/QtInputDelegate")
+
namespace QtAndroid
{
QBasicMutex *platformInterfaceMutex();
QAndroidPlatformIntegration *androidPlatformIntegration();
void setAndroidPlatformIntegration(QAndroidPlatformIntegration *androidPlatformIntegration);
void setQtThread(QThread *thread);
-
-
- int createSurface(AndroidSurfaceClient * client, const QRect &geometry, bool onTop, int imageDepth);
- int insertNativeView(jobject view, const QRect &geometry);
void setViewVisibility(jobject view, bool visible);
- void setSurfaceGeometry(int surfaceId, const QRect &geometry);
- void destroySurface(int surfaceId);
- void bringChildToFront(int surfaceId);
- void bringChildToBack(int surfaceId);
QWindow *topLevelWindowAt(const QPoint &globalPos);
+ QWindow *windowFromId(int windowId);
int availableWidthPixels();
int availableHeightPixels();
double scaledDensity();
@@ -50,8 +47,9 @@ namespace QtAndroid
jobject assets();
AAssetManager *assetManager();
jclass applicationClass();
- QtJniTypes::Activity activity();
- QtJniTypes::Service service();
+
+ QtJniTypes::QtActivityDelegateBase qtActivityDelegate();
+ QtJniTypes::QtInputDelegate qtInputDelegate();
// Keep synchronized with flags in ActivityDelegate.java
enum SystemUiVisibility {
@@ -69,7 +67,8 @@ namespace QtAndroid
void notifyObjectHide(uint accessibilityObjectId, uint parentObjectId);
void notifyObjectFocus(uint accessibilityObjectId);
void notifyValueChanged(uint accessibilityObjectId, jstring value);
- void notifyQtAndroidPluginRunning(bool running);
+ void notifyScrolledEvent(uint accessibilityObjectId);
+ void notifyNativePluginIntegrationReady(bool ready);
const char *classErrorMsgFmt();
const char *methodErrorMsgFmt();
@@ -77,6 +76,8 @@ namespace QtAndroid
QString deviceName();
bool blockEventLoopsWhenSuspended();
+
+ bool isQtApplication();
}
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/android/androidjnimenu.cpp b/src/plugins/platforms/android/androidjnimenu.cpp
index 7b0091d277..8bf37d1af2 100644
--- a/src/plugins/platforms/android/androidjnimenu.cpp
+++ b/src/plugins/platforms/android/androidjnimenu.cpp
@@ -24,14 +24,12 @@ namespace QtAndroidMenu
{
static QList<QAndroidPlatformMenu *> pendingContextMenus;
static QAndroidPlatformMenu *visibleMenu = nullptr;
- static QRecursiveMutex visibleMenuMutex;
+ Q_CONSTINIT static QRecursiveMutex visibleMenuMutex;
static QSet<QAndroidPlatformMenuBar *> menuBars;
static QAndroidPlatformMenuBar *visibleMenuBar = nullptr;
static QWindow *activeTopLevelWindow = nullptr;
- static QRecursiveMutex menuBarMutex;
-
- static jmethodID openContextMenuMethodID = 0;
+ Q_CONSTINIT static QRecursiveMutex menuBarMutex;
static jmethodID clearMenuMethodID = 0;
static jmethodID addMenuItemMethodID = 0;
@@ -46,29 +44,31 @@ namespace QtAndroidMenu
void resetMenuBar()
{
- QJniObject::callStaticMethod<void>(applicationClass(), "resetOptionsMenu");
+ qtActivityDelegate().callMethod<void>("resetOptionsMenu");
}
void openOptionsMenu()
{
- QJniObject::callStaticMethod<void>(applicationClass(), "openOptionsMenu");
+ qtActivityDelegate().callMethod<void>("openOptionsMenu");
}
- void showContextMenu(QAndroidPlatformMenu *menu, const QRect &anchorRect, JNIEnv *env)
+ void showContextMenu(QAndroidPlatformMenu *menu, const QRect &anchorRect)
{
QMutexLocker lock(&visibleMenuMutex);
if (visibleMenu)
pendingContextMenus.append(visibleMenu);
visibleMenu = menu;
menu->aboutToShow();
- env->CallStaticVoidMethod(applicationClass(), openContextMenuMethodID, anchorRect.x(), anchorRect.y(), anchorRect.width(), anchorRect.height());
+ qtActivityDelegate().callMethod<void>("openContextMenu",
+ anchorRect.x(), anchorRect.y(),
+ anchorRect.width(), anchorRect.height());
}
void hideContextMenu(QAndroidPlatformMenu *menu)
{
QMutexLocker lock(&visibleMenuMutex);
if (visibleMenu == menu) {
- QJniObject::callStaticMethod<void>(applicationClass(), "closeContextMenu");
+ qtActivityDelegate().callMethod<void>("closeContextMenu");
pendingContextMenus.clear();
} else {
pendingContextMenus.removeOne(menu);
@@ -117,7 +117,7 @@ namespace QtAndroidMenu
visibleMenuBar = 0;
activeTopLevelWindow = window;
- for (QAndroidPlatformMenuBar *menuBar : qAsConst(menuBars)) {
+ for (QAndroidPlatformMenuBar *menuBar : std::as_const(menuBars)) {
if (menuBar->parentWindow() == window) {
visibleMenuBar = menuBar;
resetMenuBar();
@@ -211,8 +211,10 @@ namespace QtAndroidMenu
return order;
}
- static jboolean onPrepareOptionsMenu(JNIEnv *env, jobject /*thiz*/, jobject menu)
+ static jboolean onPrepareOptionsMenu(JNIEnv *env, jobject thiz, jobject menu)
{
+ Q_UNUSED(thiz)
+
env->CallVoidMethod(menu, clearMenuMethodID);
QMutexLocker lock(&menuBarMutex);
if (!visibleMenuBar)
@@ -249,8 +251,11 @@ namespace QtAndroidMenu
return order ? JNI_TRUE : JNI_FALSE;
}
- static jboolean onOptionsItemSelected(JNIEnv *env, jobject /*thiz*/, jint menuId, jboolean checked)
+ static jboolean onOptionsItemSelected(JNIEnv *env, jobject thiz, jint menuId, jboolean checked)
{
+ Q_UNUSED(env)
+ Q_UNUSED(thiz)
+
QMutexLocker lock(&menuBarMutex);
if (!visibleMenuBar)
return JNI_FALSE;
@@ -260,7 +265,7 @@ namespace QtAndroidMenu
QAndroidPlatformMenuItem *item = static_cast<QAndroidPlatformMenuItem *>(menus.front()->menuItemForId(menuId));
if (item) {
if (item->menu()) {
- showContextMenu(item->menu(), QRect(), env);
+ showContextMenu(item->menu(), QRect());
} else {
if (item->isCheckable())
item->setChecked(checked);
@@ -270,18 +275,23 @@ namespace QtAndroidMenu
} else {
QAndroidPlatformMenu *menu = static_cast<QAndroidPlatformMenu *>(visibleMenuBar->menuForId(menuId));
if (menu)
- showContextMenu(menu, QRect(), env);
+ showContextMenu(menu, QRect());
}
return JNI_TRUE;
}
- static void onOptionsMenuClosed(JNIEnv */*env*/, jobject /*thiz*/, jobject /*menu*/)
+ static void onOptionsMenuClosed(JNIEnv *env, jobject thiz, jobject menu)
{
+ Q_UNUSED(env)
+ Q_UNUSED(thiz)
+ Q_UNUSED(menu)
}
- static void onCreateContextMenu(JNIEnv *env, jobject /*thiz*/, jobject menu)
+ static void onCreateContextMenu(JNIEnv *env, jobject thiz, jobject menu)
{
+ Q_UNUSED(thiz)
+
env->CallVoidMethod(menu, clearMenuMethodID);
QMutexLocker lock(&visibleMenuMutex);
if (!visibleMenu)
@@ -295,8 +305,9 @@ namespace QtAndroidMenu
addAllMenuItemsToMenu(env, menu, visibleMenu);
}
- static void fillContextMenu(JNIEnv *env, jobject /*thiz*/, jobject menu)
+ static void fillContextMenu(JNIEnv *env, jobject thiz, jobject menu)
{
+ Q_UNUSED(thiz)
env->CallVoidMethod(menu, clearMenuMethodID);
QMutexLocker lock(&visibleMenuMutex);
if (!visibleMenu)
@@ -305,20 +316,23 @@ namespace QtAndroidMenu
addAllMenuItemsToMenu(env, menu, visibleMenu);
}
- static jboolean onContextItemSelected(JNIEnv *env, jobject /*thiz*/, jint menuId, jboolean checked)
+ static jboolean onContextItemSelected(JNIEnv *env, jobject thiz, jint menuId, jboolean checked)
{
+ Q_UNUSED(env)
+ Q_UNUSED(thiz)
+
QMutexLocker lock(&visibleMenuMutex);
QAndroidPlatformMenuItem * item = static_cast<QAndroidPlatformMenuItem *>(visibleMenu->menuItemForId(menuId));
if (item) {
if (item->menu()) {
- showContextMenu(item->menu(), QRect(), env);
+ showContextMenu(item->menu(), QRect());
} else {
if (item->isCheckable())
item->setChecked(checked);
item->activated();
visibleMenu->aboutToHide();
visibleMenu = 0;
- for (QAndroidPlatformMenu *menu : qAsConst(pendingContextMenus)) {
+ for (QAndroidPlatformMenu *menu : std::as_const(pendingContextMenus)) {
if (menu->isVisible())
menu->aboutToHide();
}
@@ -328,8 +342,12 @@ namespace QtAndroidMenu
return JNI_TRUE;
}
- static void onContextMenuClosed(JNIEnv *env, jobject /*thiz*/, jobject /*menu*/)
+ static void onContextMenuClosed(JNIEnv *env, jobject thiz, jobject menu)
{
+ Q_UNUSED(env)
+ Q_UNUSED(thiz)
+ Q_UNUSED(menu)
+
QMutexLocker lock(&visibleMenuMutex);
if (!visibleMenu)
return;
@@ -337,7 +355,7 @@ namespace QtAndroidMenu
visibleMenu->aboutToHide();
visibleMenu = 0;
if (!pendingContextMenus.empty())
- showContextMenu(pendingContextMenus.takeLast(), QRect(), env);
+ showContextMenu(pendingContextMenus.takeLast(), QRect());
}
static JNINativeMethod methods[] = {
@@ -378,17 +396,15 @@ namespace QtAndroidMenu
return false; \
}
- bool registerNatives(JNIEnv *env)
+ bool registerNatives(QJniEnvironment &env)
{
jclass appClass = applicationClass();
- if (env->RegisterNatives(appClass, methods, sizeof(methods) / sizeof(methods[0])) < 0) {
+ if (!env.registerNativeMethods(appClass, methods, sizeof(methods) / sizeof(methods[0]))) {
__android_log_print(ANDROID_LOG_FATAL,"Qt", "RegisterNatives failed");
return false;
}
- GET_AND_CHECK_STATIC_METHOD(openContextMenuMethodID, appClass, "openContextMenu", "(IIII)V");
-
jclass clazz;
FIND_AND_CHECK_CLASS("android/view/Menu");
GET_AND_CHECK_METHOD(clearMenuMethodID, clazz, "clear", "()V");
diff --git a/src/plugins/platforms/android/androidjnimenu.h b/src/plugins/platforms/android/androidjnimenu.h
index 1645ce750b..e10ad930d9 100644
--- a/src/plugins/platforms/android/androidjnimenu.h
+++ b/src/plugins/platforms/android/androidjnimenu.h
@@ -15,12 +15,13 @@ class QAndroidPlatformMenuItem;
class QWindow;
class QRect;
class QPoint;
+class QJniEnvironment;
namespace QtAndroidMenu
{
// Menu support
void openOptionsMenu();
- void showContextMenu(QAndroidPlatformMenu *menu, const QRect &anchorRect, JNIEnv *env);
+ void showContextMenu(QAndroidPlatformMenu *menu, const QRect &anchorRect);
void hideContextMenu(QAndroidPlatformMenu *menu);
void syncMenu(QAndroidPlatformMenu *menu);
void androidPlatformMenuDestroyed(QAndroidPlatformMenu *menu);
@@ -31,7 +32,7 @@ namespace QtAndroidMenu
void removeMenuBar(QAndroidPlatformMenuBar *menuBar);
// Menu support
- bool registerNatives(JNIEnv *env);
+ bool registerNatives(QJniEnvironment &env);
}
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/android/androidsurfaceclient.h b/src/plugins/platforms/android/androidsurfaceclient.h
deleted file mode 100644
index dded9a1f66..0000000000
--- a/src/plugins/platforms/android/androidsurfaceclient.h
+++ /dev/null
@@ -1,24 +0,0 @@
-// Copyright (C) 2014 BogDan Vatra <bogdan@kde.org>
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
-
-#ifndef ANDROIDSURFACECLIENT_H
-#define ANDROIDSURFACECLIENT_H
-#include <QMutex>
-#include <jni.h>
-
-QT_BEGIN_NAMESPACE
-
-class AndroidSurfaceClient
-{
-public:
- virtual void surfaceChanged(JNIEnv *jniEnv, jobject surface, int w, int h) = 0;
- void lockSurface() { m_surfaceMutex.lock(); }
- void unlockSurface() { m_surfaceMutex.unlock(); }
-
-protected:
- QMutex m_surfaceMutex;
-};
-
-QT_END_NAMESPACE
-
-#endif // ANDROIDSURFACECLIENT_H
diff --git a/src/plugins/platforms/android/androidwindowembedding.cpp b/src/plugins/platforms/android/androidwindowembedding.cpp
new file mode 100644
index 0000000000..230776f571
--- /dev/null
+++ b/src/plugins/platforms/android/androidwindowembedding.cpp
@@ -0,0 +1,75 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "androidwindowembedding.h"
+
+#include <QtCore/qcoreapplication.h>
+#include <QtCore/qjnienvironment.h>
+#include <QtCore/qjniobject.h>
+#include <QtCore/qjnitypes.h>
+#include <QtGui/qwindow.h>
+
+QT_BEGIN_NAMESPACE
+
+Q_DECLARE_JNI_CLASS(QtView, "org/qtproject/qt/android/QtView");
+Q_DECLARE_JNI_CLASS(QtEmbeddedDelegate, "org/qtproject/qt/android/QtEmbeddedDelegate");
+
+namespace QtAndroidWindowEmbedding {
+ void createRootWindow(JNIEnv *, jclass, QtJniTypes::View rootView,
+ jint x, jint y, jint width, jint height)
+ {
+ // QWindow should be constructed on the Qt thread rather than directly in the caller thread
+ // To avoid hitting checkReceiverThread assert in QCoreApplication::doNotify
+ QMetaObject::invokeMethod(qApp, [rootView, x, y, width, height] {
+ QWindow *parentWindow = QWindow::fromWinId(reinterpret_cast<WId>(rootView.object()));
+ parentWindow->setGeometry(x, y, width, height);
+ rootView.callMethod<void>("createWindow", reinterpret_cast<jlong>(parentWindow));
+ });
+ }
+
+ void deleteWindow(JNIEnv *, jclass, jlong windowRef)
+ {
+ QWindow *window = reinterpret_cast<QWindow*>(windowRef);
+ window->deleteLater();
+ }
+
+ void setWindowVisible(JNIEnv *, jclass, jlong windowRef, jboolean visible)
+ {
+ QMetaObject::invokeMethod(qApp, [windowRef, visible] {
+ QWindow *window = reinterpret_cast<QWindow*>(windowRef);
+ if (visible) {
+ window->showNormal();
+ if (!window->parent()->isVisible())
+ window->parent()->showNormal();
+ } else {
+ window->hide();
+ }
+ });
+ }
+
+ void resizeWindow(JNIEnv *, jclass, jlong windowRef, jint x, jint y, jint width, jint height)
+ {
+ QMetaObject::invokeMethod(qApp, [windowRef, x, y, width, height] {
+ QWindow *window = reinterpret_cast<QWindow*>(windowRef);
+ QWindow *parent = window->parent();
+ if (parent)
+ parent->setGeometry(x, y, width, height);
+ window->setGeometry(0, 0, width, height);
+ });
+ }
+
+ bool registerNatives(QJniEnvironment& env) {
+ using namespace QtJniTypes;
+ bool success = env.registerNativeMethods(Traits<QtEmbeddedDelegate>::className(),
+ {Q_JNI_NATIVE_SCOPED_METHOD(createRootWindow, QtAndroidWindowEmbedding),
+ Q_JNI_NATIVE_SCOPED_METHOD(deleteWindow, QtAndroidWindowEmbedding)});
+
+ success &= env.registerNativeMethods(Traits<QtView>::className(),
+ {Q_JNI_NATIVE_SCOPED_METHOD(setWindowVisible, QtAndroidWindowEmbedding),
+ Q_JNI_NATIVE_SCOPED_METHOD(resizeWindow, QtAndroidWindowEmbedding)});
+ return success;
+
+ }
+}
+
+QT_END_NAMESPACE
diff --git a/src/plugins/platforms/android/androidwindowembedding.h b/src/plugins/platforms/android/androidwindowembedding.h
new file mode 100644
index 0000000000..b7b0e1205f
--- /dev/null
+++ b/src/plugins/platforms/android/androidwindowembedding.h
@@ -0,0 +1,41 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QTANDROIDWINDOWEMBEDDING_H
+#define QTANDROIDWINDOWEMBEDDING_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtCore/qjnienvironment.h>
+#include <QtCore/qjnitypes.h>
+
+QT_BEGIN_NAMESPACE
+
+Q_DECLARE_JNI_CLASS(View, "android/view/View");
+
+namespace QtAndroidWindowEmbedding
+{
+ bool registerNatives(QJniEnvironment& env);
+ void createRootWindow(JNIEnv *, jclass, QtJniTypes::View rootView,
+ jint x, jint y,jint width, jint height);
+ Q_DECLARE_JNI_NATIVE_METHOD_IN_CURRENT_SCOPE(createRootWindow)
+ void deleteWindow(JNIEnv *, jclass, jlong window);
+ Q_DECLARE_JNI_NATIVE_METHOD_IN_CURRENT_SCOPE(deleteWindow)
+ void setWindowVisible(JNIEnv *, jclass, jlong window, jboolean visible);
+ Q_DECLARE_JNI_NATIVE_METHOD_IN_CURRENT_SCOPE(setWindowVisible)
+ void resizeWindow(JNIEnv *, jclass, jlong windowRef, jint x, jint y, jint width, jint height);
+ Q_DECLARE_JNI_NATIVE_METHOD_IN_CURRENT_SCOPE(resizeWindow)
+};
+
+QT_END_NAMESPACE
+
+#endif // QTANDROIDWINDOWEMBEDDING_H
diff --git a/src/plugins/platforms/android/qandroidassetsfileenginehandler.cpp b/src/plugins/platforms/android/qandroidassetsfileenginehandler.cpp
index 36fa2dd945..4ea6536cef 100644
--- a/src/plugins/platforms/android/qandroidassetsfileenginehandler.cpp
+++ b/src/plugins/platforms/android/qandroidassetsfileenginehandler.cpp
@@ -108,6 +108,8 @@ public:
FolderIterator(const QString &path)
: m_path(path)
{
+ // Note that empty dirs in the assets dir before the build are not going to be
+ // included in the final apk, so no empty folders should expected to be listed.
QJniObject files = QJniObject::callStaticObjectMethod(QtAndroid::applicationClass(),
"listAssetContent",
"(Landroid/content/res/AssetManager;Ljava/lang/String;)[Ljava/lang/String;",
@@ -140,17 +142,13 @@ public:
return m_path + at(m_index).name;
}
- bool hasNext() const
+ bool advance()
{
- return !empty() && m_index + 1 < size();
- }
-
- std::optional<std::pair<QString, AssetItem>> next()
- {
- if (!hasNext())
- return {};
- ++m_index;
- return std::pair<QString, AssetItem>(currentFileName(), at(m_index));
+ if (!empty() && m_index + 1 < size()) {
+ ++m_index;
+ return true;
+ }
+ return false;
}
private:
@@ -161,7 +159,7 @@ private:
};
QCache<QString, QSharedPointer<FolderIterator>> FolderIterator::m_assetsCache(std::max(50, qEnvironmentVariableIntValue("QT_ANDROID_MAX_ASSETS_CACHE_SIZE")));
-QMutex FolderIterator::m_assetsCacheMutex;
+Q_CONSTINIT QMutex FolderIterator::m_assetsCacheMutex;
class AndroidAbstractFileEngineIterator: public QAbstractFileEngineIterator
{
@@ -169,7 +167,7 @@ public:
AndroidAbstractFileEngineIterator(QDir::Filters filters,
const QStringList &nameFilters,
const QString &path)
- : QAbstractFileEngineIterator(filters, nameFilters)
+ : QAbstractFileEngineIterator(path, filters, nameFilters)
{
m_currentIterator = FolderIterator::fromCache(cleanedAssetPath(path), true);
}
@@ -186,28 +184,16 @@ public:
return m_currentIterator->currentFileName();
}
- virtual QString currentFilePath() const
+ QString currentFilePath() const override
{
if (!m_currentIterator)
return {};
return m_currentIterator->currentFilePath();
}
- bool hasNext() const override
- {
- if (!m_currentIterator)
- return false;
- return m_currentIterator->hasNext();
- }
-
- QString next() override
+ bool advance() override
{
- if (!m_currentIterator)
- return {};
- auto res = m_currentIterator->next();
- if (!res)
- return {};
- return res->first;
+ return m_currentIterator ? m_currentIterator->advance() : false;
}
private:
@@ -305,9 +291,9 @@ public:
return prefixedPath(m_fileName);
case BaseName:
if ((pos = m_fileName.lastIndexOf(u'/')) != -1)
- return prefixedPath(m_fileName.mid(pos));
+ return m_fileName.mid(pos + 1);
else
- return prefixedPath(m_fileName);
+ return m_fileName;
case PathName:
case AbsolutePathName:
case CanonicalPathName:
@@ -350,8 +336,13 @@ public:
} else {
auto *assetDir = AAssetManager_openDir(m_assetManager, m_fileName.toUtf8());
if (assetDir) {
- if (AAssetDir_getNextFileName(assetDir))
+ if (AAssetDir_getNextFileName(assetDir)
+ || (!FolderIterator::fromCache(m_fileName, false)->empty())) {
+ // If AAssetDir_getNextFileName is not valid, it still can be a directory that
+ // contains only other directories (no files). FolderIterator will not be called
+ // on the directory containing files so it should not be too time consuming now.
m_assetInfo->type = AssetItem::Type::Folder;
+ }
AAssetDir_close(assetDir);
}
}
@@ -360,10 +351,12 @@ public:
m_assetsInfoCache.insert(m_fileName, newAssetInfoPtr);
}
- Iterator *beginEntryList(QDir::Filters filters, const QStringList &filterNames) override
+ IteratorUniquePtr
+ beginEntryList(const QString &, QDir::Filters filters, const QStringList &filterNames) override
{
+ // AndroidAbstractFileEngineIterator use `m_fileName` as the path
if (m_assetInfo && m_assetInfo->type == AssetItem::Type::Folder)
- return new AndroidAbstractFileEngineIterator(filters, filterNames, m_fileName);
+ return std::make_unique<AndroidAbstractFileEngineIterator>(filters, filterNames, m_fileName);
return nullptr;
}
@@ -379,20 +372,21 @@ private:
};
QCache<QString, QSharedPointer<AssetItem>> AndroidAbstractFileEngine::m_assetsInfoCache(std::max(200, qEnvironmentVariableIntValue("QT_ANDROID_MAX_FILEINFO_ASSETS_CACHE_SIZE")));
-QMutex AndroidAbstractFileEngine::m_assetsInfoCacheMutex;
+Q_CONSTINIT QMutex AndroidAbstractFileEngine::m_assetsInfoCacheMutex;
AndroidAssetsFileEngineHandler::AndroidAssetsFileEngineHandler()
{
m_assetManager = QtAndroid::assetManager();
}
-QAbstractFileEngine * AndroidAssetsFileEngineHandler::create(const QString &fileName) const
+std::unique_ptr<QAbstractFileEngine>
+AndroidAssetsFileEngineHandler::create(const QString &fileName) const
{
if (fileName.isEmpty())
- return nullptr;
+ return {};
if (!fileName.startsWith(assetsPrefix))
- return nullptr;
+ return {};
QString path = fileName.mid(prefixSize);
path.replace("//"_L1, "/"_L1);
@@ -400,7 +394,7 @@ QAbstractFileEngine * AndroidAssetsFileEngineHandler::create(const QString &file
path.remove(0, 1);
if (path.endsWith(u'/'))
path.chop(1);
- return new AndroidAbstractFileEngine(m_assetManager, path);
+ return std::make_unique<AndroidAbstractFileEngine>(m_assetManager, path);
}
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/android/qandroidassetsfileenginehandler.h b/src/plugins/platforms/android/qandroidassetsfileenginehandler.h
index 50c6914c24..8e5f87a51a 100644
--- a/src/plugins/platforms/android/qandroidassetsfileenginehandler.h
+++ b/src/plugins/platforms/android/qandroidassetsfileenginehandler.h
@@ -17,7 +17,7 @@ class AndroidAssetsFileEngineHandler: public QAbstractFileEngineHandler
{
public:
AndroidAssetsFileEngineHandler();
- QAbstractFileEngine *create(const QString &fileName) const override;
+ std::unique_ptr<QAbstractFileEngine> create(const QString &fileName) const override;
private:
AAssetManager *m_assetManager;
diff --git a/src/plugins/platforms/android/qandroideventdispatcher.cpp b/src/plugins/platforms/android/qandroideventdispatcher.cpp
index 238addee58..8d1a085844 100644
--- a/src/plugins/platforms/android/qandroideventdispatcher.cpp
+++ b/src/plugins/platforms/android/qandroideventdispatcher.cpp
@@ -65,7 +65,7 @@ bool QAndroidEventDispatcher::processEvents(QEventLoop::ProcessEventsFlags flags
QAndroidEventDispatcherStopper *QAndroidEventDispatcherStopper::instance()
{
- static QAndroidEventDispatcherStopper androidEventDispatcherStopper;
+ Q_CONSTINIT static QAndroidEventDispatcherStopper androidEventDispatcherStopper;
return &androidEventDispatcherStopper;
}
@@ -75,7 +75,7 @@ void QAndroidEventDispatcherStopper::startAll()
if (!m_started.testAndSetOrdered(0, 1))
return;
- for (QAndroidEventDispatcher *d : qAsConst(m_dispatchers))
+ for (QAndroidEventDispatcher *d : std::as_const(m_dispatchers))
d->start();
}
@@ -85,7 +85,7 @@ void QAndroidEventDispatcherStopper::stopAll()
if (!m_started.testAndSetOrdered(1, 0))
return;
- for (QAndroidEventDispatcher *d : qAsConst(m_dispatchers))
+ for (QAndroidEventDispatcher *d : std::as_const(m_dispatchers))
d->stop();
}
@@ -104,6 +104,6 @@ void QAndroidEventDispatcherStopper::removeEventDispatcher(QAndroidEventDispatch
void QAndroidEventDispatcherStopper::goingToStop(bool stop)
{
QMutexLocker lock(&m_mutex);
- for (QAndroidEventDispatcher *d : qAsConst(m_dispatchers))
+ for (QAndroidEventDispatcher *d : std::as_const(m_dispatchers))
d->goingToStop(stop);
}
diff --git a/src/plugins/platforms/android/qandroidinputcontext.cpp b/src/plugins/platforms/android/qandroidinputcontext.cpp
index 679c142289..62212ff63d 100644
--- a/src/plugins/platforms/android/qandroidinputcontext.cpp
+++ b/src/plugins/platforms/android/qandroidinputcontext.cpp
@@ -80,9 +80,7 @@ static jboolean beginBatchEdit(JNIEnv */*env*/, jobject /*thiz*/)
if (!m_androidInputContext)
return JNI_FALSE;
-#ifdef QT_DEBUG_ANDROID_IM_PROTOCOL
- qDebug("@@@ BEGINBATCH");
-#endif
+ qCDebug(lcQpaInputMethods) << "@@@ BEGINBATCH";
jboolean res = JNI_FALSE;
runOnQtThread([&res]{res = m_androidInputContext->beginBatchEdit();});
return res;
@@ -93,9 +91,7 @@ static jboolean endBatchEdit(JNIEnv */*env*/, jobject /*thiz*/)
if (!m_androidInputContext)
return JNI_FALSE;
-#ifdef QT_DEBUG_ANDROID_IM_PROTOCOL
- qDebug("@@@ ENDBATCH");
-#endif
+ qCDebug(lcQpaInputMethods) << "@@@ ENDBATCH";
jboolean res = JNI_FALSE;
runOnQtThread([&res]{res = m_androidInputContext->endBatchEdit();});
@@ -113,9 +109,7 @@ static jboolean commitText(JNIEnv *env, jobject /*thiz*/, jstring text, jint new
QString str(reinterpret_cast<const QChar *>(jstr), env->GetStringLength(text));
env->ReleaseStringChars(text, jstr);
-#ifdef QT_DEBUG_ANDROID_IM_PROTOCOL
- qDebug() << "@@@ COMMIT" << str << newCursorPosition;
-#endif
+ qCDebug(lcQpaInputMethods) << "@@@ COMMIT" << str << newCursorPosition;
jboolean res = JNI_FALSE;
runOnQtThread([&]{res = m_androidInputContext->commitText(str, newCursorPosition);});
return res;
@@ -126,9 +120,7 @@ static jboolean deleteSurroundingText(JNIEnv */*env*/, jobject /*thiz*/, jint le
if (!m_androidInputContext)
return JNI_FALSE;
-#ifdef QT_DEBUG_ANDROID_IM_PROTOCOL
- qDebug() << "@@@ DELETE" << leftLength << rightLength;
-#endif
+ qCDebug(lcQpaInputMethods) << "@@@ DELETE" << leftLength << rightLength;
jboolean res = JNI_FALSE;
runOnQtThread([&]{res = m_androidInputContext->deleteSurroundingText(leftLength, rightLength);});
return res;
@@ -139,9 +131,7 @@ static jboolean finishComposingText(JNIEnv */*env*/, jobject /*thiz*/)
if (!m_androidInputContext)
return JNI_FALSE;
-#ifdef QT_DEBUG_ANDROID_IM_PROTOCOL
- qDebug("@@@ FINISH");
-#endif
+ qCDebug(lcQpaInputMethods) << "@@@ FINISH";
jboolean res = JNI_FALSE;
runOnQtThread([&]{res = m_androidInputContext->finishComposingText();});
return res;
@@ -165,9 +155,7 @@ static jobject getExtractedText(JNIEnv *env, jobject /*thiz*/, int hintMaxChars,
QAndroidInputContext::ExtractedText extractedText;
runOnQtThread([&]{extractedText = m_androidInputContext->getExtractedText(hintMaxChars, hintMaxLines, flags);});
-#ifdef QT_DEBUG_ANDROID_IM_PROTOCOL
- qDebug() << "@@@ GETEX" << hintMaxChars << hintMaxLines << QString::fromLatin1("0x") + QString::number(flags,16) << extractedText.text << "partOff:" << extractedText.partialStartOffset << extractedText.partialEndOffset << "sel:" << extractedText.selectionStart << extractedText.selectionEnd << "offset:" << extractedText.startOffset;
-#endif
+ qCDebug(lcQpaInputMethods) << "@@@ GETEX" << hintMaxChars << hintMaxLines << QString::fromLatin1("0x") + QString::number(flags,16) << extractedText.text << "partOff:" << extractedText.partialStartOffset << extractedText.partialEndOffset << "sel:" << extractedText.selectionStart << extractedText.selectionEnd << "offset:" << extractedText.startOffset;
jobject object = env->NewObject(m_extractedTextClass, m_classConstructorMethodID);
env->SetIntField(object, m_partialStartOffsetFieldID, extractedText.partialStartOffset);
@@ -190,9 +178,7 @@ static jstring getSelectedText(JNIEnv *env, jobject /*thiz*/, jint flags)
QString text;
runOnQtThread([&]{text = m_androidInputContext->getSelectedText(flags);});
-#ifdef QT_DEBUG_ANDROID_IM_PROTOCOL
- qDebug() << "@@@ GETSEL" << text;
-#endif
+ qCDebug(lcQpaInputMethods) << "@@@ GETSEL" << text;
if (text.isEmpty())
return 0;
return env->NewString(reinterpret_cast<const jchar *>(text.constData()), jsize(text.length()));
@@ -205,9 +191,7 @@ static jstring getTextAfterCursor(JNIEnv *env, jobject /*thiz*/, jint length, ji
QString text;
runOnQtThread([&]{text = m_androidInputContext->getTextAfterCursor(length, flags);});
-#ifdef QT_DEBUG_ANDROID_IM_PROTOCOL
- qDebug() << "@@@ GETA" << length << text;
-#endif
+ qCDebug(lcQpaInputMethods) << "@@@ GETA" << length << text;
return env->NewString(reinterpret_cast<const jchar *>(text.constData()), jsize(text.length()));
}
@@ -218,9 +202,7 @@ static jstring getTextBeforeCursor(JNIEnv *env, jobject /*thiz*/, jint length, j
QString text;
runOnQtThread([&]{text = m_androidInputContext->getTextBeforeCursor(length, flags);});
-#ifdef QT_DEBUG_ANDROID_IM_PROTOCOL
- qDebug() << "@@@ GETB" << length << text;
-#endif
+ qCDebug(lcQpaInputMethods) << "@@@ GETB" << length << text;
return env->NewString(reinterpret_cast<const jchar *>(text.constData()), jsize(text.length()));
}
@@ -234,9 +216,7 @@ static jboolean setComposingText(JNIEnv *env, jobject /*thiz*/, jstring text, ji
QString str(reinterpret_cast<const QChar *>(jstr), env->GetStringLength(text));
env->ReleaseStringChars(text, jstr);
-#ifdef QT_DEBUG_ANDROID_IM_PROTOCOL
- qDebug() << "@@@ SET" << str << newCursorPosition;
-#endif
+ qCDebug(lcQpaInputMethods) << "@@@ SET" << str << newCursorPosition;
jboolean res = JNI_FALSE;
runOnQtThread([&]{res = m_androidInputContext->setComposingText(str, newCursorPosition);});
return res;
@@ -247,9 +227,7 @@ static jboolean setComposingRegion(JNIEnv */*env*/, jobject /*thiz*/, jint start
if (!m_androidInputContext)
return JNI_FALSE;
-#ifdef QT_DEBUG_ANDROID_IM_PROTOCOL
- qDebug() << "@@@ SETR" << start << end;
-#endif
+ qCDebug(lcQpaInputMethods) << "@@@ SETR" << start << end;
jboolean res = JNI_FALSE;
runOnQtThread([&]{res = m_androidInputContext->setComposingRegion(start, end);});
return res;
@@ -261,9 +239,7 @@ static jboolean setSelection(JNIEnv */*env*/, jobject /*thiz*/, jint start, jint
if (!m_androidInputContext)
return JNI_FALSE;
-#ifdef QT_DEBUG_ANDROID_IM_PROTOCOL
- qDebug() << "@@@ SETSEL" << start << end;
-#endif
+ qCDebug(lcQpaInputMethods) << "@@@ SETSEL" << start << end;
jboolean res = JNI_FALSE;
runOnQtThread([&]{res = m_androidInputContext->setSelection(start, end);});
return res;
@@ -275,9 +251,7 @@ static jboolean selectAll(JNIEnv */*env*/, jobject /*thiz*/)
if (!m_androidInputContext)
return JNI_FALSE;
-#ifdef QT_DEBUG_ANDROID_IM_PROTOCOL
- qDebug("@@@ SELALL");
-#endif
+ qCDebug(lcQpaInputMethods) << "@@@ SELALL";
jboolean res = JNI_FALSE;
runOnQtThread([&]{res = m_androidInputContext->selectAll();});
return res;
@@ -288,9 +262,7 @@ static jboolean cut(JNIEnv */*env*/, jobject /*thiz*/)
if (!m_androidInputContext)
return JNI_FALSE;
-#ifdef QT_DEBUG_ANDROID_IM_PROTOCOL
- qDebug("@@@");
-#endif
+ qCDebug(lcQpaInputMethods) << "@@@";
jboolean res = JNI_FALSE;
runOnQtThread([&]{res = m_androidInputContext->cut();});
return res;
@@ -301,9 +273,7 @@ static jboolean copy(JNIEnv */*env*/, jobject /*thiz*/)
if (!m_androidInputContext)
return JNI_FALSE;
-#ifdef QT_DEBUG_ANDROID_IM_PROTOCOL
- qDebug("@@@");
-#endif
+ qCDebug(lcQpaInputMethods) << "@@@";
jboolean res = JNI_FALSE;
runOnQtThread([&]{res = m_androidInputContext->copy();});
return res;
@@ -314,9 +284,7 @@ static jboolean copyURL(JNIEnv */*env*/, jobject /*thiz*/)
if (!m_androidInputContext)
return JNI_FALSE;
-#ifdef QT_DEBUG_ANDROID_IM_PROTOCOL
- qDebug("@@@");
-#endif
+ qCDebug(lcQpaInputMethods) << "@@@";
jboolean res = JNI_FALSE;
runOnQtThread([&]{res = m_androidInputContext->copyURL();});
return res;
@@ -327,9 +295,7 @@ static jboolean paste(JNIEnv */*env*/, jobject /*thiz*/)
if (!m_androidInputContext)
return JNI_FALSE;
-#ifdef QT_DEBUG_ANDROID_IM_PROTOCOL
- qDebug("@@@ PASTE");
-#endif
+ qCDebug(lcQpaInputMethods) << "@@@ PASTE";
jboolean res = JNI_FALSE;
runOnQtThread([&]{res = m_androidInputContext->paste();});
return res;
@@ -340,14 +306,24 @@ static jboolean updateCursorPosition(JNIEnv */*env*/, jobject /*thiz*/)
if (!m_androidInputContext)
return JNI_FALSE;
-#ifdef QT_DEBUG_ANDROID_IM_PROTOCOL
- qDebug("@@@ UPDATECURSORPOS");
-#endif
+ qCDebug(lcQpaInputMethods) << "@@@ UPDATECURSORPOS";
runOnQtThread([&]{m_androidInputContext->updateCursorPosition();});
return true;
}
+static void reportFullscreenMode(JNIEnv */*env*/, jobject /*thiz*/, jboolean enabled)
+{
+ if (!m_androidInputContext)
+ return;
+
+ runOnQtThread([&]{m_androidInputContext->reportFullscreenMode(enabled);});
+}
+
+static jboolean fullscreenMode(JNIEnv */*env*/, jobject /*thiz*/)
+{
+ return m_androidInputContext ? m_androidInputContext->fullscreenMode() : false;
+}
static JNINativeMethod methods[] = {
{"beginBatchEdit", "()Z", (void *)beginBatchEdit},
@@ -368,7 +344,9 @@ static JNINativeMethod methods[] = {
{"copy", "()Z", (void *)copy},
{"copyURL", "()Z", (void *)copyURL},
{"paste", "()Z", (void *)paste},
- {"updateCursorPosition", "()Z", (void *)updateCursorPosition}
+ {"updateCursorPosition", "()Z", (void *)updateCursorPosition},
+ {"reportFullscreenMode", "(Z)V", (void *)reportFullscreenMode},
+ {"fullscreenMode", "()Z", (void *)fullscreenMode}
};
static QRect screenInputItemRectangle()
@@ -385,6 +363,7 @@ QAndroidInputContext::QAndroidInputContext()
, m_handleMode(Hidden)
, m_batchEditNestingLevel(0)
, m_focusObject(0)
+ , m_fullScreenMode(false)
{
QJniEnvironment env;
jclass clazz = env.findClass(QtNativeInputConnectionClassName);
@@ -567,12 +546,29 @@ void QAndroidInputContext::updateCursorPosition()
}
}
+bool QAndroidInputContext::isImhNoTextHandlesSet()
+{
+ QSharedPointer<QInputMethodQueryEvent> query = focusObjectInputMethodQuery();
+ if (query.isNull())
+ return false;
+ return query->value(Qt::ImHints).toUInt() & Qt::ImhNoTextHandles;
+}
+
void QAndroidInputContext::updateSelectionHandles()
{
+ if (m_fullScreenMode) {
+ QtAndroidInput::updateHandles(Hidden);
+ return;
+ }
static bool noHandles = qEnvironmentVariableIntValue("QT_QPA_NO_TEXT_HANDLES");
if (noHandles || !m_focusObject)
return;
+ if (isImhNoTextHandlesSet()) {
+ QtAndroidInput::updateHandles(Hidden);
+ return;
+ }
+
auto im = qGuiApp->inputMethod();
QInputMethodQueryEvent query(Qt::ImCursorPosition | Qt::ImAnchorPosition | Qt::ImEnabled
@@ -586,6 +582,11 @@ void QAndroidInputContext::updateSelectionHandles()
bool readOnly = readOnlyVariant.toBool();
QPlatformWindow *qPlatformWindow = qGuiApp->focusWindow()->handle();
+ if (!readOnly && ((m_handleMode & 0xff) == Hidden)) {
+ QtAndroidInput::updateHandles(Hidden);
+ return;
+ }
+
if ( cpos == anchor && (!readOnlyVariant.isValid() || readOnly)) {
QtAndroidInput::updateHandles(Hidden);
return;
@@ -612,9 +613,8 @@ void QAndroidInputContext::updateSelectionHandles()
if (!query.value(Qt::ImSurroundingText).toString().isEmpty())
buttons |= EditContext::SelectAllButton;
QtAndroidInput::updateHandles(m_handleMode, editMenuPoint, buttons, cursorPointGlobal);
- // The VK is hidden, reset the timer
- if (m_hideCursorHandleTimer.isActive())
- m_hideCursorHandleTimer.start();
+ m_hideCursorHandleTimer.start();
+
return;
}
@@ -788,7 +788,15 @@ void QAndroidInputContext::touchDown(int x, int y)
focusObjectStopComposing();
}
- updateSelectionHandles();
+ // Check if cursor is visible in focused window before updating handles
+ QPlatformWindow *window = qGuiApp->focusWindow()->handle();
+ const QRectF curRect = cursorRectangle();
+ const QPoint cursorGlobalPoint = window->mapToGlobal(QPoint(curRect.x(), curRect.y()));
+ const QRect windowRect = QPlatformInputContext::inputItemClipRectangle().toRect();
+ const QRect windowGlobalRect = QRect(window->mapToGlobal(windowRect.topLeft()), windowRect.size());
+
+ if (windowGlobalRect.contains(cursorGlobalPoint.x(), cursorGlobalPoint.y()))
+ updateSelectionHandles();
}
}
@@ -895,10 +903,15 @@ void QAndroidInputContext::showInputPanel()
if (query.isNull())
return;
+ if (!qGuiApp->focusWindow()->handle())
+ return; // not a real window, probably VR/XR
+
disconnect(m_updateCursorPosConnection);
+ m_updateCursorPosConnection = {};
+
if (qGuiApp->focusObject()->metaObject()->indexOfSignal("cursorPositionChanged(int,int)") >= 0) // QLineEdit breaks the pattern
m_updateCursorPosConnection = connect(qGuiApp->focusObject(), SIGNAL(cursorPositionChanged(int,int)), this, SLOT(updateCursorPosition()));
- else
+ else if (qGuiApp->focusObject()->metaObject()->indexOfSignal("cursorPositionChanged()") >= 0)
m_updateCursorPosConnection = connect(qGuiApp->focusObject(), SIGNAL(cursorPositionChanged()), this, SLOT(updateCursorPosition()));
QRect rect = screenInputItemRectangle();
@@ -1110,6 +1123,25 @@ jboolean QAndroidInputContext::finishComposingText()
return JNI_TRUE;
}
+void QAndroidInputContext::reportFullscreenMode(jboolean enabled)
+{
+ m_fullScreenMode = enabled;
+ BatchEditLock batchEditLock(this);
+ if (!focusObjectStopComposing())
+ return;
+
+ if (enabled)
+ m_handleMode = Hidden;
+
+ updateSelectionHandles();
+}
+
+// Called in calling thread's context
+jboolean QAndroidInputContext::fullscreenMode()
+{
+ return m_fullScreenMode;
+}
+
bool QAndroidInputContext::focusObjectIsComposing() const
{
return m_composingCursor != -1;
@@ -1532,9 +1564,7 @@ jboolean QAndroidInputContext::setComposingRegion(jint start, jint end)
}
if (start < textOffset || end - textOffset > text.length()) {
-#ifdef QT_DEBUG_ANDROID_IM_PROTOCOL
- qWarning("setComposingRegion: failed to retrieve text from composing region");
-#endif
+ qCDebug(lcQpaInputMethods) << "Warning: setComposingRegion: failed to retrieve text from composing region";
return JNI_TRUE;
}
diff --git a/src/plugins/platforms/android/qandroidinputcontext.h b/src/plugins/platforms/android/qandroidinputcontext.h
index c93aae142a..038286c4b8 100644
--- a/src/plugins/platforms/android/qandroidinputcontext.h
+++ b/src/plugins/platforms/android/qandroidinputcontext.h
@@ -9,6 +9,8 @@
#include <functional>
#include <jni.h>
#include <qevent.h>
+
+#include <QtCore/qpointer.h>
#include <QTimer>
QT_BEGIN_NAMESPACE
@@ -98,6 +100,8 @@ public:
jboolean copy();
jboolean copyURL();
jboolean paste();
+ void reportFullscreenMode(jboolean enabled);
+ jboolean fullscreenMode();
public slots:
void safeCall(const std::function<void()> &func, Qt::ConnectionType conType = Qt::BlockingQueuedConnection);
@@ -113,6 +117,7 @@ private slots:
void showInputPanelLater(Qt::ApplicationState);
private:
+ bool isImhNoTextHandlesSet();
void sendInputMethodEvent(QInputMethodEvent *event);
QSharedPointer<QInputMethodQueryEvent> focusObjectInputMethodQuery(Qt::InputMethodQueries queries = Qt::ImQueryAll);
bool focusObjectIsComposing() const;
@@ -129,6 +134,7 @@ private:
int m_batchEditNestingLevel;
QPointer<QObject> m_focusObject;
QTimer m_hideCursorHandleTimer;
+ bool m_fullScreenMode;
};
Q_DECLARE_OPERATORS_FOR_FLAGS(QAndroidInputContext::HandleModes)
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/android/qandroidplatformaccessibility.cpp b/src/plugins/platforms/android/qandroidplatformaccessibility.cpp
index 33eafd54aa..61fc21a6bc 100644
--- a/src/plugins/platforms/android/qandroidplatformaccessibility.cpp
+++ b/src/plugins/platforms/android/qandroidplatformaccessibility.cpp
@@ -32,6 +32,8 @@ void QAndroidPlatformAccessibility::notifyAccessibilityUpdate(QAccessibleEvent *
QtAndroidAccessibility::notifyObjectFocus(event->uniqueId());
} else if (event->type() == QAccessible::ValueChanged) {
QtAndroidAccessibility::notifyValueChanged(event->uniqueId());
+ } else if (event->type() == QAccessible::ScrollingEnd) {
+ QtAndroidAccessibility::notifyScrolledEvent(event->uniqueId());
}
}
diff --git a/src/plugins/platforms/android/qandroidplatformbackingstore.cpp b/src/plugins/platforms/android/qandroidplatformbackingstore.cpp
deleted file mode 100644
index 07a1c835d3..0000000000
--- a/src/plugins/platforms/android/qandroidplatformbackingstore.cpp
+++ /dev/null
@@ -1,52 +0,0 @@
-// Copyright (C) 2014 BogDan Vatra <bogdan@kde.org>
-// Copyright (C) 2016 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
-
-#include "qandroidplatformbackingstore.h"
-#include "qandroidplatformscreen.h"
-#include "qandroidplatformwindow.h"
-#include <qpa/qplatformscreen.h>
-
-QT_BEGIN_NAMESPACE
-
-QAndroidPlatformBackingStore::QAndroidPlatformBackingStore(QWindow *window)
- : QPlatformBackingStore(window)
-{
- if (window->handle())
- setBackingStore(window);
-}
-
-QPaintDevice *QAndroidPlatformBackingStore::paintDevice()
-{
- return &m_image;
-}
-
-void QAndroidPlatformBackingStore::flush(QWindow *window, const QRegion &region, const QPoint &offset)
-{
- Q_UNUSED(offset);
-
- if (!m_backingStoreSet)
- setBackingStore(window);
-
- (static_cast<QAndroidPlatformWindow *>(window->handle()))->repaint(region);
-}
-
-void QAndroidPlatformBackingStore::resize(const QSize &size, const QRegion &staticContents)
-{
- Q_UNUSED(staticContents);
-
- if (m_image.size() != size)
- m_image = QImage(size, window()->screen()->handle()->format());
-}
-
-void QAndroidPlatformBackingStore::setBackingStore(QWindow *window)
-{
- if (window->surfaceType() == QSurface::RasterSurface || window->surfaceType() == QSurface::RasterGLSurface) {
- (static_cast<QAndroidPlatformWindow *>(window->handle()))->setBackingStore(this);
- m_backingStoreSet = true;
- } else {
- qWarning("QAndroidPlatformBackingStore does not support OpenGL-only windows.");
- }
-}
-
-QT_END_NAMESPACE
diff --git a/src/plugins/platforms/android/qandroidplatformbackingstore.h b/src/plugins/platforms/android/qandroidplatformbackingstore.h
deleted file mode 100644
index 810305ac45..0000000000
--- a/src/plugins/platforms/android/qandroidplatformbackingstore.h
+++ /dev/null
@@ -1,29 +0,0 @@
-// Copyright (C) 2014 BogDan Vatra <bogdan@kde.org>
-// Copyright (C) 2016 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
-
-#ifndef QANDROIDPLATFORMBACKINGSTORE_H
-#define QANDROIDPLATFORMBACKINGSTORE_H
-
-#include <qpa/qplatformbackingstore.h>
-#include <qpa/qwindowsysteminterface.h>
-
-QT_BEGIN_NAMESPACE
-
-class QAndroidPlatformBackingStore : public QPlatformBackingStore
-{
-public:
- explicit QAndroidPlatformBackingStore(QWindow *window);
- QPaintDevice *paintDevice() override;
- void flush(QWindow *window, const QRegion &region, const QPoint &offset) override;
- void resize(const QSize &size, const QRegion &staticContents) override;
- QImage toImage() const override { return m_image; }
- void setBackingStore(QWindow *window);
-protected:
- QImage m_image;
- bool m_backingStoreSet = false;
-};
-
-QT_END_NAMESPACE
-
-#endif // QANDROIDPLATFORMBACKINGSTORE_H
diff --git a/src/plugins/platforms/android/qandroidplatformclipboard.cpp b/src/plugins/platforms/android/qandroidplatformclipboard.cpp
index 39a508a1b3..e5ed33b9b0 100644
--- a/src/plugins/platforms/android/qandroidplatformclipboard.cpp
+++ b/src/plugins/platforms/android/qandroidplatformclipboard.cpp
@@ -1,15 +1,34 @@
+// Copyright (C) 2023 The Qt Company Ltd.
// Copyright (C) 2012 BogDan Vatra <bogdan@kde.org>
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#include "qandroidplatformclipboard.h"
-#include "androidjniclipboard.h"
+
+#include <QtCore/QUrl>
+#include <QtCore/QJniEnvironment>
+#include <QtCore/QJniObject>
+#include <QtCore/private/qjnihelpers_p.h>
+
#ifndef QT_NO_CLIPBOARD
+using namespace QtJniTypes;
+
QT_BEGIN_NAMESPACE
+void QAndroidPlatformClipboard::onClipboardDataChanged(JNIEnv *env, jobject obj, jlong nativePointer)
+{
+ Q_UNUSED(env)
+ Q_UNUSED(obj)
+
+ auto *clipboardManager = reinterpret_cast<QAndroidPlatformClipboard *>(nativePointer);
+ if (clipboardManager)
+ clipboardManager->emitChanged(QClipboard::Clipboard);
+}
+
QAndroidPlatformClipboard::QAndroidPlatformClipboard()
{
- QtAndroidClipboard::setClipboardManager(this);
+ m_clipboardManager = QtClipboardManager::construct(QtAndroidPrivate::context(),
+ reinterpret_cast<jlong>(this));
}
QAndroidPlatformClipboard::~QAndroidPlatformClipboard()
@@ -18,24 +37,66 @@ QAndroidPlatformClipboard::~QAndroidPlatformClipboard()
delete data;
}
+QMimeData *QAndroidPlatformClipboard::getClipboardMimeData()
+{
+ QMimeData *data = new QMimeData;
+ if (m_clipboardManager.callMethod<jboolean>("hasClipboardText")) {
+ data->setText(m_clipboardManager.callMethod<QString>("getClipboardText"));
+ }
+ if (m_clipboardManager.callMethod<jboolean>("hasClipboardHtml")) {
+ data->setHtml(m_clipboardManager.callMethod<QString>("getClipboardHtml"));
+ }
+ if (m_clipboardManager.callMethod<jboolean>("hasClipboardUri")) {
+ auto uris = m_clipboardManager.callMethod<QString[]>("getClipboardUris");
+ if (uris.isValid()) {
+ QList<QUrl> urls;
+ for (const QString &uri : uris)
+ urls << QUrl(uri);
+ data->setUrls(urls);
+ }
+ }
+ return data;
+}
+
QMimeData *QAndroidPlatformClipboard::mimeData(QClipboard::Mode mode)
{
Q_UNUSED(mode);
Q_ASSERT(supportsMode(mode));
if (data)
data->deleteLater();
- data = QtAndroidClipboard::getClipboardMimeData();
+ data = getClipboardMimeData();
return data;
}
+void QAndroidPlatformClipboard::clearClipboardData()
+{
+ m_clipboardManager.callMethod<void>("clearClipData");
+}
+
+void QAndroidPlatformClipboard::setClipboardMimeData(QMimeData *data)
+{
+ clearClipboardData();
+ auto context = QtAndroidPrivate::context();
+ if (data->hasUrls()) {
+ QList<QUrl> urls = data->urls();
+ for (const auto &u : std::as_const(urls))
+ m_clipboardManager.callMethod<void>("setClipboardUri", context, u.toEncoded());
+ } else if (data->hasHtml()) { // html can contain text
+ m_clipboardManager.callMethod<void>("setClipboardHtml",
+ context, data->text(), data->html());
+ } else if (data->hasText()) { // hasText must be the last (the order matter here)
+ m_clipboardManager.callMethod<void>("setClipboardText", context, data->text());
+ }
+}
+
void QAndroidPlatformClipboard::setMimeData(QMimeData *data, QClipboard::Mode mode)
{
if (!data) {
- QtAndroidClipboard::clearClipboardData();
+ clearClipboardData();
return;
}
if (data && supportsMode(mode))
- QtAndroidClipboard::setClipboardMimeData(data);
+ setClipboardMimeData(data);
if (data != 0)
data->deleteLater();
}
@@ -45,6 +106,18 @@ bool QAndroidPlatformClipboard::supportsMode(QClipboard::Mode mode) const
return QClipboard::Clipboard == mode;
}
+bool QAndroidPlatformClipboard::registerNatives(QJniEnvironment &env)
+{
+ bool success = env.registerNativeMethods(Traits<QtClipboardManager>::className(),
+ { Q_JNI_NATIVE_SCOPED_METHOD(onClipboardDataChanged, QAndroidPlatformClipboard) });
+ if (!success) {
+ qCritical() << "QtClipboardManager: registerNativeMethods() failed";
+ return false;
+ }
+
+ return true;
+}
+
QT_END_NAMESPACE
#endif // QT_NO_CLIPBOARD
diff --git a/src/plugins/platforms/android/qandroidplatformclipboard.h b/src/plugins/platforms/android/qandroidplatformclipboard.h
index 1778ca5b28..ab5c527f88 100644
--- a/src/plugins/platforms/android/qandroidplatformclipboard.h
+++ b/src/plugins/platforms/android/qandroidplatformclipboard.h
@@ -1,3 +1,4 @@
+// Copyright (C) 2023 The Qt Company Ltd.
// Copyright (C) 2012 BogDan Vatra <bogdan@kde.org>
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
@@ -6,10 +7,14 @@
#include <qpa/qplatformclipboard.h>
#include <QMimeData>
+#include <QtCore/qjnitypes.h>
#ifndef QT_NO_CLIPBOARD
+
QT_BEGIN_NAMESPACE
+Q_DECLARE_JNI_CLASS(QtClipboardManager, "org/qtproject/qt/android/QtClipboardManager");
+
class QAndroidPlatformClipboard : public QPlatformClipboard
{
public:
@@ -18,8 +23,19 @@ public:
QMimeData *mimeData(QClipboard::Mode mode = QClipboard::Clipboard) override;
void setMimeData(QMimeData *data, QClipboard::Mode mode = QClipboard::Clipboard) override;
bool supportsMode(QClipboard::Mode mode) const override;
+
+ static bool registerNatives(QJniEnvironment &env);
+
private:
+ QMimeData *getClipboardMimeData();
+ void setClipboardMimeData(QMimeData *data);
+ void clearClipboardData();
+
+ static void onClipboardDataChanged(JNIEnv *env, jobject obj, jlong nativePointer);
+ Q_DECLARE_JNI_NATIVE_METHOD_IN_CURRENT_SCOPE(onClipboardDataChanged)
+
QMimeData *data = nullptr;
+ QtJniTypes::QtClipboardManager m_clipboardManager = nullptr;
};
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/android/qandroidplatformdialoghelpers.cpp b/src/plugins/platforms/android/qandroidplatformdialoghelpers.cpp
index 97ae5064bd..2b9a1194d2 100644
--- a/src/plugins/platforms/android/qandroidplatformdialoghelpers.cpp
+++ b/src/plugins/platforms/android/qandroidplatformdialoghelpers.cpp
@@ -19,7 +19,7 @@ static jclass g_messageDialogHelperClass = nullptr;
QAndroidPlatformMessageDialogHelper::QAndroidPlatformMessageDialogHelper()
: m_javaMessageDialog(g_messageDialogHelperClass, "(Landroid/app/Activity;)V",
- QtAndroid::activity())
+ QtAndroidPrivate::activity().object())
{
}
@@ -54,7 +54,10 @@ bool QAndroidPlatformMessageDialogHelper::show(Qt::WindowFlags windowFlags,
if (!opt.data())
return false;
- m_javaMessageDialog.callMethod<void>("setIcon", "(I)V", opt->icon());
+ if (!opt->checkBoxLabel().isNull())
+ return false; // Can't support
+
+ m_javaMessageDialog.callMethod<void>("setStandardIcon", "(I)V", opt->standardIcon());
QString str = htmlText(opt->windowTitle());
if (!str.isEmpty()) {
@@ -147,7 +150,7 @@ static void dialogResult(JNIEnv * /*env*/, jobject /*thiz*/, jlong handler, int
QMetaObject::invokeMethod(object, "dialogResult", Qt::QueuedConnection, Q_ARG(int, buttonID));
}
-static JNINativeMethod methods[] = {
+static const JNINativeMethod methods[] = {
{"dialogResult", "(JI)V", (void *)dialogResult}
};
@@ -159,21 +162,19 @@ static JNINativeMethod methods[] = {
return false; \
}
-bool registerNatives(JNIEnv *env)
+bool registerNatives(QJniEnvironment &env)
{
const char QtMessageHandlerHelperClassName[] = "org/qtproject/qt/android/QtMessageDialogHelper";
- QJniEnvironment qenv;
- jclass clazz = qenv.findClass(QtMessageHandlerHelperClassName);
+ jclass clazz = env.findClass(QtMessageHandlerHelperClassName);
if (!clazz) {
__android_log_print(ANDROID_LOG_FATAL, QtAndroid::qtTagText(), QtAndroid::classErrorMsgFmt()
, QtMessageHandlerHelperClassName);
return false;
}
g_messageDialogHelperClass = static_cast<jclass>(env->NewGlobalRef(clazz));
- FIND_AND_CHECK_CLASS("org/qtproject/qt/android/QtNativeDialogHelper");
- jclass appClass = static_cast<jclass>(env->NewGlobalRef(clazz));
- if (env->RegisterNatives(appClass, methods, sizeof(methods) / sizeof(methods[0])) < 0) {
+ if (!env.registerNativeMethods("org/qtproject/qt/android/QtNativeDialogHelper",
+ methods, sizeof(methods) / sizeof(methods[0]))) {
__android_log_print(ANDROID_LOG_FATAL, "Qt", "RegisterNatives failed");
return false;
}
diff --git a/src/plugins/platforms/android/qandroidplatformdialoghelpers.h b/src/plugins/platforms/android/qandroidplatformdialoghelpers.h
index f06d2a9990..86f1cf77e7 100644
--- a/src/plugins/platforms/android/qandroidplatformdialoghelpers.h
+++ b/src/plugins/platforms/android/qandroidplatformdialoghelpers.h
@@ -8,12 +8,13 @@
#include <jni.h>
#include <QEventLoop>
-#include <QtCore/QJniEnvironment>
#include <QtCore/QJniObject>
#include <qpa/qplatformdialoghelper.h>
QT_BEGIN_NAMESPACE
+class QJniEnvironment;
+
namespace QtAndroidDialogHelpers {
class QAndroidPlatformMessageDialogHelper: public QPlatformMessageDialogHelper
@@ -41,7 +42,7 @@ private:
};
-bool registerNatives(JNIEnv *env);
+bool registerNatives(QJniEnvironment &env);
}
diff --git a/src/plugins/platforms/android/qandroidplatformfiledialoghelper.cpp b/src/plugins/platforms/android/qandroidplatformfiledialoghelper.cpp
index 6d28bd2388..d8a5c58d2c 100644
--- a/src/plugins/platforms/android/qandroidplatformfiledialoghelper.cpp
+++ b/src/plugins/platforms/android/qandroidplatformfiledialoghelper.cpp
@@ -25,7 +25,7 @@ const char JniIntentClass[] = "android/content/Intent";
QAndroidPlatformFileDialogHelper::QAndroidPlatformFileDialogHelper()
: QPlatformFileDialogHelper(),
- m_activity(QtAndroid::activity())
+ m_activity(QtAndroidPrivate::activity())
{
}
@@ -45,8 +45,8 @@ bool QAndroidPlatformFileDialogHelper::handleActivityResult(jint requestCode, ji
if (uri.isValid()) {
takePersistableUriPermission(uri);
m_selectedFile.append(QUrl(uri.toString()));
- Q_EMIT fileSelected(m_selectedFile.first());
- Q_EMIT currentChanged(m_selectedFile.first());
+ Q_EMIT fileSelected(m_selectedFile.constFirst());
+ Q_EMIT currentChanged(m_selectedFile.constFirst());
Q_EMIT accept();
return true;
@@ -65,7 +65,7 @@ bool QAndroidPlatformFileDialogHelper::handleActivityResult(jint requestCode, ji
m_selectedFile.append(itemUri.toString());
}
Q_EMIT filesSelected(m_selectedFile);
- Q_EMIT currentChanged(m_selectedFile.first());
+ Q_EMIT currentChanged(m_selectedFile.constFirst());
Q_EMIT accept();
}
@@ -97,6 +97,22 @@ void QAndroidPlatformFileDialogHelper::setInitialFileName(const QString &title)
extraTitle.object(), QJniObject::fromString(title).object());
}
+void QAndroidPlatformFileDialogHelper::setInitialDirectoryUri(const QString &directory)
+{
+ if (directory.isEmpty())
+ return;
+
+ if (QNativeInterface::QAndroidApplication::sdkVersion() < 26)
+ return;
+
+ const auto extraInitialUri = QJniObject::getStaticObjectField(
+ "android/provider/DocumentsContract", "EXTRA_INITIAL_URI", "Ljava/lang/String;");
+ m_intent.callObjectMethod("putExtra",
+ "(Ljava/lang/String;Ljava/lang/String;)Landroid/content/Intent;",
+ extraInitialUri.object(),
+ QJniObject::fromString(directory).object());
+}
+
void QAndroidPlatformFileDialogHelper::setOpenableCategory()
{
const QJniObject CATEGORY_OPENABLE = QJniObject::getStaticObjectField(
@@ -130,12 +146,15 @@ void QAndroidPlatformFileDialogHelper::setMimeTypes()
{
QStringList mimeTypes = options()->mimeTypeFilters();
const QStringList nameFilters = options()->nameFilters();
- const QString nameFilter = nameFilters.isEmpty() ? QString() : nameFilters.first();
- if (!nameFilter.isEmpty()) {
+ if (!nameFilters.isEmpty()) {
QMimeDatabase db;
- for (const QString &filter : nameFilterExtensions(nameFilter))
- mimeTypes.append(db.mimeTypeForFile(filter, QMimeDatabase::MatchExtension).name());
+ for (auto filter : nameFilters) {
+ if (!filter.isEmpty()) {
+ for (const QString &filter : nameFilterExtensions(filter))
+ mimeTypes.append(db.mimeTypeForFile(filter, QMimeDatabase::MatchExtension).name());
+ }
+ }
}
const QString initialType = mimeTypes.size() == 1 ? mimeTypes.at(0) : "*/*"_L1;
@@ -179,11 +198,8 @@ bool QAndroidPlatformFileDialogHelper::show(Qt::WindowFlags windowFlags, Qt::Win
if (options()->acceptMode() == QFileDialogOptions::AcceptSave) {
m_intent = getFileDialogIntent("ACTION_CREATE_DOCUMENT");
const QList<QUrl> selectedFiles = options()->initiallySelectedFiles();
- if (selectedFiles.size() > 0) {
- // TODO: The initial folder to show at the start should be handled by EXTRA_INITIAL_URI
- // Take only the file name.
+ if (selectedFiles.size() > 0)
setInitialFileName(selectedFiles.first().fileName());
- }
} else if (options()->acceptMode() == QFileDialogOptions::AcceptOpen) {
switch (options()->fileMode()) {
case QFileDialogOptions::FileMode::DirectoryOnly:
@@ -207,6 +223,8 @@ bool QAndroidPlatformFileDialogHelper::show(Qt::WindowFlags windowFlags, Qt::Win
setMimeTypes();
}
+ setInitialDirectoryUri(m_directory.toString());
+
QtAndroidPrivate::registerActivityResultListener(this);
m_activity.callMethod<void>("startActivityForResult", "(Landroid/content/Intent;I)V",
m_intent.object(), REQUEST_CODE);
@@ -220,6 +238,11 @@ void QAndroidPlatformFileDialogHelper::hide()
QtAndroidPrivate::unregisterActivityResultListener(this);
}
+void QAndroidPlatformFileDialogHelper::setDirectory(const QUrl &directory)
+{
+ m_directory = directory;
+}
+
void QAndroidPlatformFileDialogHelper::exec()
{
m_eventLoop.exec(QEventLoop::DialogExec);
diff --git a/src/plugins/platforms/android/qandroidplatformfiledialoghelper.h b/src/plugins/platforms/android/qandroidplatformfiledialoghelper.h
index 4281cb8198..156eda9142 100644
--- a/src/plugins/platforms/android/qandroidplatformfiledialoghelper.h
+++ b/src/plugins/platforms/android/qandroidplatformfiledialoghelper.h
@@ -32,8 +32,8 @@ public:
void setFilter() override {}
QList<QUrl> selectedFiles() const override { return m_selectedFile; }
void selectFile(const QUrl &) override {}
- QUrl directory() const override { return QUrl(); }
- void setDirectory(const QUrl &) override {}
+ QUrl directory() const override { return m_directory; }
+ void setDirectory(const QUrl &directory) override;
bool defaultNameFilterDisables() const override { return false; }
bool handleActivityResult(jint requestCode, jint resultCode, jobject data) override;
@@ -41,12 +41,14 @@ private:
QJniObject getFileDialogIntent(const QString &intentType);
void takePersistableUriPermission(const QJniObject &uri);
void setInitialFileName(const QString &title);
+ void setInitialDirectoryUri(const QString &directory);
void setOpenableCategory();
void setAllowMultipleSelections(bool allowMultiple);
void setMimeTypes();
QEventLoop m_eventLoop;
QList<QUrl> m_selectedFile;
+ QUrl m_directory;
QJniObject m_intent;
const QJniObject m_activity;
};
diff --git a/src/plugins/platforms/android/qandroidplatformfontdatabase.cpp b/src/plugins/platforms/android/qandroidplatformfontdatabase.cpp
index 01636d2ba5..82a10dac07 100644
--- a/src/plugins/platforms/android/qandroidplatformfontdatabase.cpp
+++ b/src/plugins/platforms/android/qandroidplatformfontdatabase.cpp
@@ -2,6 +2,7 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#include <QDir>
+#include <QLocale>
#include "qandroidplatformfontdatabase.h"
@@ -47,6 +48,38 @@ QStringList QAndroidPlatformFontDatabase::fallbacksForFamily(const QString &fami
QChar::Script script) const
{
QStringList result;
+
+ // Prepend CJK fonts by the locale.
+ QLocale locale = QLocale::system();
+ switch (locale.language()) {
+ case QLocale::Chinese: {
+ switch (locale.territory()) {
+ case QLocale::China:
+ case QLocale::Singapore:
+ result.append(QStringLiteral("Noto Sans Mono CJK SC"));
+ break;
+ case QLocale::Taiwan:
+ case QLocale::HongKong:
+ case QLocale::Macao:
+ result.append(QStringLiteral("Noto Sans Mono CJK TC"));
+ break;
+ default:
+ // no modifications.
+ break;
+ }
+ break;
+ }
+ case QLocale::Japanese:
+ result.append(QStringLiteral("Noto Sans Mono CJK JP"));
+ break;
+ case QLocale::Korean:
+ result.append(QStringLiteral("Noto Sans Mono CJK KR"));
+ break;
+ default:
+ // no modifications.
+ break;
+ }
+
if (styleHint == QFont::Monospace || styleHint == QFont::Courier)
result.append(QString(qgetenv("QT_ANDROID_FONTS_MONOSPACE")).split(u';'));
else if (styleHint == QFont::Serif)
diff --git a/src/plugins/platforms/android/qandroidplatformforeignwindow.cpp b/src/plugins/platforms/android/qandroidplatformforeignwindow.cpp
index c2ce2c84b2..e84a481a2b 100644
--- a/src/plugins/platforms/android/qandroidplatformforeignwindow.cpp
+++ b/src/plugins/platforms/android/qandroidplatformforeignwindow.cpp
@@ -6,84 +6,101 @@
#include <QtCore/qvariant.h>
#include <qpa/qwindowsysteminterface.h>
#include <QtCore/private/qjnihelpers_p.h>
+#include <QtCore/qjnitypes.h>
QT_BEGIN_NAMESPACE
QAndroidPlatformForeignWindow::QAndroidPlatformForeignWindow(QWindow *window, WId nativeHandle)
- : QAndroidPlatformWindow(window),
- m_surfaceId(-1)
+ : QAndroidPlatformWindow(window), m_view(nullptr), m_nativeViewInserted(false)
{
m_view = reinterpret_cast<jobject>(nativeHandle);
- if (m_view.isValid())
- QtAndroid::setViewVisibility(m_view.object(), false);
-}
+ if (isEmbeddingContainer()) {
+ m_nativeViewId = m_view.callMethod<jint>("getId");
+ return;
+ }
-QAndroidPlatformForeignWindow::~QAndroidPlatformForeignWindow()
-{
if (m_view.isValid())
QtAndroid::setViewVisibility(m_view.object(), false);
- if (m_surfaceId != -1)
- QtAndroid::destroySurface(m_surfaceId);
}
-void QAndroidPlatformForeignWindow::lower()
+QAndroidPlatformForeignWindow::~QAndroidPlatformForeignWindow()
{
- if (m_surfaceId == -1)
+ if (isEmbeddingContainer())
return;
- QAndroidPlatformWindow::lower();
- QtAndroid::bringChildToBack(m_surfaceId);
-}
+ if (m_view.isValid())
+ QtAndroid::setViewVisibility(m_view.object(), false);
-void QAndroidPlatformForeignWindow::raise()
-{
- if (m_surfaceId == -1)
- return;
+ m_nativeQtWindow.callMethod<void>("removeNativeView");
- QAndroidPlatformWindow::raise();
- QtAndroid::bringChildToFront(m_surfaceId);
}
void QAndroidPlatformForeignWindow::setGeometry(const QRect &rect)
{
QAndroidPlatformWindow::setGeometry(rect);
- if (m_surfaceId != -1)
- QtAndroid::setSurfaceGeometry(m_surfaceId, rect);
+ if (isEmbeddingContainer())
+ return;
+
+ if (m_nativeViewInserted)
+ setNativeGeometry(rect);
}
void QAndroidPlatformForeignWindow::setVisible(bool visible)
{
+ if (isEmbeddingContainer()) {
+ QAndroidPlatformWindow::setVisible(visible);
+ return;
+ }
+
if (!m_view.isValid())
return;
QtAndroid::setViewVisibility(m_view.object(), visible);
- QAndroidPlatformWindow::setVisible(visible);
- if (!visible && m_surfaceId != -1) {
- QtAndroid::destroySurface(m_surfaceId);
- m_surfaceId = -1;
- } else if (m_surfaceId == -1) {
- m_surfaceId = QtAndroid::insertNativeView(m_view.object(), geometry());
+ if (!visible && m_nativeViewInserted) {
+ m_nativeQtWindow.callMethod<void>("removeNativeView");
+ m_nativeViewInserted = false;
+ } else if (!m_nativeViewInserted) {
+ addViewToWindow();
}
}
void QAndroidPlatformForeignWindow::applicationStateChanged(Qt::ApplicationState state)
{
- if (state <= Qt::ApplicationHidden
- && m_surfaceId != -1) {
- QtAndroid::destroySurface(m_surfaceId);
- m_surfaceId = -1;
- } else if (m_view.isValid() && m_surfaceId == -1){
- m_surfaceId = QtAndroid::insertNativeView(m_view.object(), geometry());
+ if (!isEmbeddingContainer()) {
+ if (state <= Qt::ApplicationHidden
+ && m_nativeViewInserted) {
+ m_nativeQtWindow.callMethod<void>("removeNativeView");
+ m_nativeViewInserted = false;
+ } else if (m_view.isValid() && !m_nativeViewInserted){
+ addViewToWindow();
+ }
}
QAndroidPlatformWindow::applicationStateChanged(state);
}
-void QAndroidPlatformForeignWindow::setParent(const QPlatformWindow *window)
+WId QAndroidPlatformForeignWindow::winId() const
{
- Q_UNUSED(window);
+ if (isEmbeddingContainer() && m_view.isValid())
+ return reinterpret_cast<WId>(m_view.object());
+ if (m_nativeQtWindow.isValid())
+ return reinterpret_cast<WId>(m_nativeQtWindow.object());
+ return 0L;
+}
+
+void QAndroidPlatformForeignWindow::addViewToWindow()
+{
+ if (isEmbeddingContainer())
+ return;
+
+ jint x = 0, y = 0, w = -1, h = -1;
+ if (!geometry().isNull())
+ geometry().getRect(&x, &y, &w, &h);
+
+ m_nativeQtWindow.callMethod<void>("setNativeView", m_view, x, y, qMax(w, 1), qMax(h, 1));
+ m_nativeViewInserted = true;
}
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/android/qandroidplatformforeignwindow.h b/src/plugins/platforms/android/qandroidplatformforeignwindow.h
index 7b576ecec4..503524cced 100644
--- a/src/plugins/platforms/android/qandroidplatformforeignwindow.h
+++ b/src/plugins/platforms/android/qandroidplatformforeignwindow.h
@@ -4,29 +4,31 @@
#ifndef QANDROIDPLATFORMFOREIGNWINDOW_H
#define QANDROIDPLATFORMFOREIGNWINDOW_H
-#include "androidsurfaceclient.h"
#include "qandroidplatformwindow.h"
#include <QtCore/QJniObject>
QT_BEGIN_NAMESPACE
+Q_DECLARE_JNI_CLASS(View, "android/view/View")
+
class QAndroidPlatformForeignWindow : public QAndroidPlatformWindow
{
public:
explicit QAndroidPlatformForeignWindow(QWindow *window, WId nativeHandle);
~QAndroidPlatformForeignWindow();
- void lower() override;
- void raise() override;
void setGeometry(const QRect &rect) override;
void setVisible(bool visible) override;
void applicationStateChanged(Qt::ApplicationState state) override;
- void setParent(const QPlatformWindow *window) override;
bool isForeignWindow() const override { return true; }
+ WId winId() const override;
+
private:
- int m_surfaceId;
- QJniObject m_view;
+ void addViewToWindow();
+
+ QtJniTypes::View m_view;
+ bool m_nativeViewInserted;
};
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/android/qandroidplatformiconengine.cpp b/src/plugins/platforms/android/qandroidplatformiconengine.cpp
new file mode 100644
index 0000000000..faa63dcca1
--- /dev/null
+++ b/src/plugins/platforms/android/qandroidplatformiconengine.cpp
@@ -0,0 +1,616 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "qandroidplatformiconengine.h"
+#include "androidjnimain.h"
+
+#include <QtCore/qdebug.h>
+#include <QtCore/qjniarray.h>
+#include <QtCore/qjniobject.h>
+#include <QtCore/qloggingcategory.h>
+#include <QtCore/qfile.h>
+#include <QtCore/qset.h>
+
+#include <QtGui/qfontdatabase.h>
+#include <QtGui/qpainter.h>
+#include <QtGui/qpalette.h>
+
+QT_BEGIN_NAMESPACE
+
+using namespace Qt::StringLiterals;
+Q_LOGGING_CATEGORY(lcIconEngineFontDownload, "qt.qpa.iconengine.fontdownload")
+
+// the primary types to work with the FontRequest API
+Q_DECLARE_JNI_CLASS(FontRequest, "androidx/core/provider/FontRequest")
+Q_DECLARE_JNI_CLASS(FontsContractCompat, "androidx/core/provider/FontsContractCompat")
+Q_DECLARE_JNI_CLASS(FontFamilyResult, "androidx/core/provider/FontsContractCompat$FontFamilyResult")
+Q_DECLARE_JNI_CLASS(FontInfo, "androidx/core/provider/FontsContractCompat$FontInfo")
+
+// various utility types
+Q_DECLARE_JNI_CLASS(List, "java/util/List"); // List is just an Interface
+Q_DECLARE_JNI_CLASS(ArrayList, "java/util/ArrayList");
+Q_DECLARE_JNI_CLASS(HashSet, "java/util/HashSet");
+Q_DECLARE_JNI_CLASS(Uri, "android/net/Uri")
+Q_DECLARE_JNI_CLASS(CancellationSignal, "android/os/CancellationSignal")
+Q_DECLARE_JNI_CLASS(ParcelFileDescriptor, "android/os/ParcelFileDescriptor")
+Q_DECLARE_JNI_CLASS(ContentResolver, "android/content/ContentResolver")
+Q_DECLARE_JNI_CLASS(PackageManager, "android/content/pm/PackageManager")
+Q_DECLARE_JNI_CLASS(ProviderInfo, "android/content/pm/ProviderInfo")
+Q_DECLARE_JNI_CLASS(PackageInfo, "android/content/pm/PackageInfo")
+Q_DECLARE_JNI_CLASS(Signature, "android/content/pm/Signature")
+
+namespace FontProvider {
+
+static QString fetchFont(const QString &query)
+{
+ using namespace QtJniTypes;
+
+ static QMap<QString, QString> triedFonts;
+ const auto it = triedFonts.find(query);
+ if (it != triedFonts.constEnd())
+ return it.value();
+
+ QString fontFamily;
+ triedFonts[query] = fontFamily; // mark as tried
+
+ QStringList loadedFamilies;
+ if (QFile file(query); file.open(QIODevice::ReadOnly)) {
+ qCDebug(lcIconEngineFontDownload) << "Loading font from resource" << query;
+ const QByteArray fontData = file.readAll();
+ int fontId = QFontDatabase::addApplicationFontFromData(fontData);
+ loadedFamilies << QFontDatabase::applicationFontFamilies(fontId);
+ } else if (!query.startsWith(u":/"_s)) {
+ const QString package = u"com.google.android.gms"_s;
+ const QString authority = u"com.google.android.gms.fonts"_s;
+
+ // First we access the content provider to get the signatures of the authority for the package
+ const auto context = QtAndroidPrivate::context();
+
+ auto packageManager = context.callMethod<PackageManager>("getPackageManager");
+ if (!packageManager.isValid()) {
+ qCWarning(lcIconEngineFontDownload, "Failed to instantiate PackageManager");
+ return fontFamily;
+ }
+ const int signaturesField = PackageManager::getStaticField<int>("GET_SIGNATURES");
+ auto providerInfo = packageManager.callMethod<ProviderInfo>("resolveContentProvider",
+ authority, 0);
+ if (!providerInfo.isValid()) {
+ qCWarning(lcIconEngineFontDownload, "Failed to resolve content provider");
+ return fontFamily;
+ }
+ const QString packageName = providerInfo.getField<QString>("packageName");
+ if (packageName != package) {
+ qCWarning(lcIconEngineFontDownload, "Mismatched provider package - expected '%s', got '%s'",
+ package.toUtf8().constData(), packageName.toUtf8().constData());
+ return fontFamily;
+ }
+ auto packageInfo = packageManager.callMethod<PackageInfo>("getPackageInfo",
+ package, signaturesField);
+ if (!packageInfo.isValid()) {
+ qCWarning(lcIconEngineFontDownload, "Failed to get package info with signature field %d",
+ signaturesField);
+ return fontFamily;
+ }
+ const auto signatures = packageInfo.getField<Signature[]>("signatures");
+ if (!signatures.isValid()) {
+ qCWarning(lcIconEngineFontDownload, "Failed to get signature array from package info");
+ return fontFamily;
+ }
+
+ // FontRequest wants a list of sets for the certificates
+ ArrayList outerList;
+ HashSet innerSet;
+ Q_ASSERT(outerList.isValid() && innerSet.isValid());
+
+ for (const auto &signature : signatures) {
+ const QJniArray<jbyte> byteArray = signature.callMethod<jbyte[]>("toByteArray");
+
+ // add takes an Object, not an Array
+ if (!innerSet.callMethod<jboolean>("add", byteArray.object<jobject>()))
+ qCWarning(lcIconEngineFontDownload, "Failed to add signature to set");
+ }
+ // Add the set to the list
+ if (!outerList.callMethod<jboolean>("add", innerSet.object()))
+ qCWarning(lcIconEngineFontDownload, "Failed to add set to certificate list");
+
+ // FontRequest constructor wants a List interface, not an ArrayList
+ FontRequest fontRequest(authority, package, query, outerList.object<List>());
+ if (!fontRequest.isValid()) {
+ qCWarning(lcIconEngineFontDownload, "Failed to create font request for '%s'",
+ query.toUtf8().constData());
+ return fontFamily;
+ }
+
+ // Call FontsContractCompat::fetchFonts with the FontRequest object
+ auto fontFamilyResult = FontsContractCompat::callStaticMethod<FontFamilyResult>(
+ "fetchFonts",
+ context,
+ CancellationSignal(nullptr),
+ fontRequest);
+ if (!fontFamilyResult.isValid()) {
+ qCWarning(lcIconEngineFontDownload, "Failed to fetch fonts for query '%s'",
+ query.toUtf8().constData());
+ return fontFamily;
+ }
+
+ enum class StatusCode {
+ OK = 0,
+ UNEXPECTED_DATA_PROVIDED = 1,
+ WRONG_CERTIFICATES = 2,
+ };
+
+ const StatusCode statusCode = fontFamilyResult.callMethod<StatusCode>("getStatusCode");
+ switch (statusCode) {
+ case StatusCode::OK:
+ break;
+ case StatusCode::UNEXPECTED_DATA_PROVIDED:
+ qCWarning(lcIconEngineFontDownload, "Provider returned unexpected data for query '%s'",
+ query.toUtf8().constData());
+ return fontFamily;
+ case StatusCode::WRONG_CERTIFICATES:
+ qCWarning(lcIconEngineFontDownload, "Wrong Certificates provided in query '%s'",
+ query.toUtf8().constData());
+ return fontFamily;
+ }
+
+ const auto fontInfos = fontFamilyResult.callMethod<FontInfo[]>("getFonts");
+ if (!fontInfos.isValid()) {
+ qCWarning(lcIconEngineFontDownload, "FontFamilyResult::getFonts returned null object for '%s'",
+ query.toUtf8().constData());
+ return fontFamily;
+ }
+
+ auto contentResolver = context.callMethod<ContentResolver>("getContentResolver");
+
+ for (QJniObject fontInfo : fontInfos) {
+ if (!fontInfo.isValid()) {
+ qCDebug(lcIconEngineFontDownload, "Received null-fontInfo object, skipping");
+ continue;
+ }
+ enum class ResultCode {
+ OK = 0,
+ FONT_NOT_FOUND = 1,
+ FONT_UNAVAILABLE = 2,
+ MALFORMED_QUERY = 3,
+ };
+ const ResultCode resultCode = fontInfo.callMethod<ResultCode>("getResultCode");
+ switch (resultCode) {
+ case ResultCode::OK:
+ break;
+ case ResultCode::FONT_NOT_FOUND:
+ qCWarning(lcIconEngineFontDownload, "Font '%s' could not be found",
+ query.toUtf8().constData());
+ return fontFamily;
+ case ResultCode::FONT_UNAVAILABLE:
+ qCWarning(lcIconEngineFontDownload, "Font '%s' is unavailable at",
+ query.toUtf8().constData());
+ return fontFamily;
+ case ResultCode::MALFORMED_QUERY:
+ qCWarning(lcIconEngineFontDownload, "Query string '%s' is malformed",
+ query.toUtf8().constData());
+ return fontFamily;
+ }
+ auto fontUri = fontInfo.callMethod<Uri>("getUri");
+ // in this case the Font URI is always a content scheme file, made
+ // so the app requesting it has permissions to open
+ auto fileDescriptor = contentResolver.callMethod<ParcelFileDescriptor>("openFileDescriptor",
+ fontUri, u"r"_s);
+ if (!fileDescriptor.isValid()) {
+ qCWarning(lcIconEngineFontDownload, "Font file '%s' not accessible",
+ fontUri.toString().toUtf8().constData());
+ continue;
+ }
+
+ int fd = fileDescriptor.callMethod<int>("detachFd");
+ QFile file;
+ file.open(fd, QFile::OpenModeFlag::ReadOnly, QFile::FileHandleFlag::AutoCloseHandle);
+ const QByteArray fontData = file.readAll();
+ qCDebug(lcIconEngineFontDownload) << "Font file read:" << fontData.size() << "bytes";
+ int fontId = QFontDatabase::addApplicationFontFromData(fontData);
+ loadedFamilies << QFontDatabase::applicationFontFamilies(fontId);
+ }
+ }
+
+ qCDebug(lcIconEngineFontDownload) << "Query '" << query << "' added families" << loadedFamilies;
+ if (!loadedFamilies.isEmpty())
+ fontFamily = loadedFamilies.first();
+ triedFonts[query] = fontFamily;
+ return fontFamily;
+}
+}
+
+QString QAndroidPlatformIconEngine::glyphs() const
+{
+ if (!QFontInfo(m_iconFont).exactMatch())
+ return {};
+
+ static constexpr std::pair<QLatin1StringView, QStringView> glyphMap[] = {
+ {"address-book-new"_L1, u"\ue0e0"},
+ {"application-exit"_L1, u"\ue5cd"},
+ {"appointment-new"_L1, u"\ue878"},
+ {"call-start"_L1, u"\ue0b0"},
+ {"call-stop"_L1, u"\ue0b1"},
+ {"contact-new"_L1, u"\uf22e"},
+ {"document-new"_L1, u"\ue89c"},
+ {"document-open"_L1, u"\ue2c8"},
+ {"document-open-recent"_L1, u"\ue4a7"},
+ {"document-page-setup"_L1, u"\uf88c"},
+ {"document-print"_L1, u"\ue8ad"},
+ {"document-print-preview"_L1, u"\uefb2"},
+ {"document-properties"_L1, u"\uf775"},
+ {"document-revert"_L1, u"\ue929"},
+ {"document-save"_L1, u"\ue161"},
+ {"document-save-as"_L1, u"\ueb60"},
+ {"document-send"_L1, u"\uf09b"},
+ {"edit-clear"_L1, u"\ue872"},
+ {"edit-copy"_L1, u"\ue14d"},
+ {"edit-cut"_L1, u"\ue14e"},
+ {"edit-delete"_L1, u"\ue14a"},
+ {"edit-find"_L1, u"\ue8b6"},
+ {"edit-find-replace"_L1, u"\ue881"},
+ {"edit-paste"_L1, u"\ue14f"},
+ {"edit-redo"_L1, u"\ue15a"},
+ {"edit-select-all"_L1, u"\ue162"},
+ {"edit-undo"_L1, u"\ue166"},
+ {"folder-new"_L1, u"\ue2cc"},
+ {"format-indent-less"_L1, u"\ue23d"},
+ {"format-indent-more"_L1, u"\ue23e"},
+ {"format-justify-center"_L1, u"\ue234"},
+ {"format-justify-fill"_L1, u"\ue235"},
+ {"format-justify-left"_L1, u"\ue236"},
+ {"format-justify-right"_L1, u"\ue237"},
+ {"format-text-direction-ltr"_L1, u"\ue247"},
+ {"format-text-direction-rtl"_L1, u"\ue248"},
+ {"format-text-bold"_L1, u"\ue238"},
+ {"format-text-italic"_L1, u"\ue23f"},
+ {"format-text-underline"_L1, u"\ue249"},
+ {"format-text-strikethrough"_L1, u"\ue246"},
+ {"go-bottom"_L1,u"\ue258"},
+ {"go-down"_L1,u"\uf1e3"},
+ {"go-first"_L1, u"\ue5dc"},
+ {"go-home"_L1, u"\ue88a"},
+ {"go-jump"_L1, u"\uf719"},
+ {"go-last"_L1, u"\ue5dd"},
+ {"go-next"_L1, u"\ue5c8"},
+ {"go-previous"_L1, u"\ue5c4"},
+ {"go-top"_L1, u"\ue25a"},
+ {"go-up"_L1, u"\uf1e0"},
+ {"help-about"_L1, u"\ue88e"},
+ {"help-contents"_L1, u"\ue8de"},
+ {"help-faq"_L1, u"\uf04c"},
+ {"insert-image"_L1, u"\ue43e"},
+ {"insert-link"_L1, u"\ue178"},
+ //{"insert-object"_L1, u"\u"},
+ {"insert-text"_L1, u"\uf827"},
+ {"list-add"_L1, u"\ue145"},
+ {"list-remove"_L1, u"\ue15b"},
+ {"mail-forward"_L1, u"\ue154"},
+ {"mail-mark-important"_L1, u"\ue937"},
+ //{"mail-mark-junk"_L1, u"\u"},
+ //{"mail-mark-notjunk"_L1, u"\u"},
+ {"mail-mark-read"_L1, u"\uf18c"},
+ {"mail-mark-unread"_L1, u"\ue9bc"},
+ {"mail-message-new"_L1, u"\ue3c9"},
+ {"mail-reply-all"_L1, u"\ue15f"},
+ {"mail-reply-sender"_L1, u"\ue15e"},
+ {"mail-send"_L1, u"\ue163"},
+ //{"mail-send-receive"_L1, u"\u"},
+ {"media-eject"_L1, u"\ue8fb"},
+ {"media-playback-pause"_L1, u"\ue034"},
+ {"media-playback-start"_L1, u"\ue037"},
+ {"media-playback-stop"_L1, u"\ue047"},
+ {"media-record"_L1, u"\uf679"},
+ {"media-seek-backward"_L1, u"\ue020"},
+ {"media-seek-forward"_L1, u"\ue01f"},
+ {"media-skip-backward"_L1, u"\ue045"},
+ {"media-skip-forward"_L1, u"\ue044"},
+ //{"object-flip-horizontal"_L1, u"\u"},
+ //{"object-flip-vertical"_L1, u"\u"},
+ {"object-rotate-left"_L1, u"\ue419"},
+ {"object-rotate-right"_L1, u"\ue41a"},
+ {"process-stop"_L1, u"\ue5c9"},
+ {"system-lock-screen"_L1, u"\ue897"},
+ {"system-log-out"_L1, u"\ue9ba"},
+ //{"system-run"_L1, u"\u"},
+ {"system-search"_L1, u"\uef70"},
+ {"system-reboot"_L1, u"\uf053"},
+ {"system-shutdown"_L1, u"\ue8ac"},
+ {"tools-check-spelling"_L1, u"\ue8ce"},
+ {"view-fullscreen"_L1, u"\ue5d0"},
+ {"view-refresh"_L1, u"\ue5d5"},
+ {"view-restore"_L1, u"\uf1cf"},
+ {"view-sort-ascending"_L1, u"\ue25a"},
+ {"view-sort-descending"_L1, u"\ue258"},
+ {"window-close"_L1, u"\ue5cd"},
+ {"window-new"_L1, u"\uf710"},
+ {"zoom-fit-best"_L1, u"\uea10"},
+ {"zoom-in"_L1, u"\ue8ff"},
+ {"zoom-original"_L1, u"\ue5d1"},
+ {"zoom-out"_L1, u"\ue900"},
+ {"process-working"_L1, u"\uef64"},
+ {"accessories-calculator"_L1, u"\uea5f"},
+ {"accessories-character-map"_L1, u"\uf8a3"},
+ {"accessories-dictionary"_L1, u"\uf539"},
+ {"accessories-text-editor"_L1, u"\ue262"},
+ {"help-browser"_L1, u"\ue887"},
+ {"multimedia-volume-control"_L1, u"\ue050"},
+ {"preferences-desktop-accessibility"_L1, u"\uf05d"},
+ {"preferences-desktop-font"_L1, u"\ue165"},
+ {"preferences-desktop-keyboard"_L1, u"\ue312"},
+ //{"preferences-desktop-locale"_L1, u"\u"},
+ {"preferences-desktop-multimedia"_L1, u"\uea75"},
+ //{"preferences-desktop-screensaver"_L1, u"\u"},
+ {"preferences-desktop-theme"_L1, u"\uf560"},
+ {"preferences-desktop-wallpaper"_L1, u"\ue1bc"},
+ {"system-file-manager"_L1, u"\ue2c7"},
+ {"system-software-install"_L1, u"\ueb71"},
+ {"system-software-update"_L1, u"\ue8d7"},
+ {"utilities-system-monitor"_L1, u"\uef5b"},
+ {"utilities-terminal"_L1, u"\ueb8e"},
+ //{"applications-accessories"_L1, u"\u"},
+ {"applications-development"_L1, u"\ue720"},
+ {"applications-engineering"_L1, u"\uea3d"},
+ {"applications-games"_L1, u"\uf135"},
+ //{"applications-graphics"_L1, u"\u"},
+ {"applications-internet"_L1, u"\ue80b"},
+ {"applications-multimedia"_L1, u"\uf06a"},
+ //{"applications-office"_L1, u"\u"},
+ //{"applications-other"_L1, u"\u"},
+ {"applications-science"_L1, u"\uea4b"},
+ //{"applications-system"_L1, u"\u"},
+ //{"applications-utilities"_L1, u"\u"},
+ {"preferences-desktop"_L1, u"\ueb97"},
+ //{"preferences-desktop-peripherals"_L1, u"\u"},
+ {"preferences-desktop-personal"_L1, u"\uf835"},
+ //{"preferences-other"_L1, u"\u"},
+ {"preferences-system"_L1, u"\ue8b8"},
+ {"preferences-system-network"_L1, u"\ue894"},
+ {"system-help"_L1, u"\ue887"},
+ //{"audio-card"_L1, u"\u"},
+ {"audio-input-microphone"_L1, u"\ue029"},
+ {"battery"_L1, u"\ue1a4"},
+ {"camera-photo"_L1, u"\ue412"},
+ {"camera-video"_L1, u"\ue04b"},
+ {"camera-web"_L1, u"\uf7a6"},
+ {"computer"_L1, u"\ue30a"},
+ {"drive-harddisk"_L1, u"\uf80e"},
+ {"drive-optical"_L1, u"\ue019"}, // same as media-optical
+ //{"drive-removable-media"_L1, u"\u"},
+ {"input-gaming"_L1, u"\uf5ee"},
+ {"input-keyboard"_L1, u"\ue312"},
+ {"input-mouse"_L1, u"\ue323"},
+ //{"input-tablet"_L1, u"\u"},
+ //{"media-flash"_L1, u"\u"},
+ //{"media-floppy"_L1, u"\u"},
+ {"media-optical"_L1, u"\ue019"},
+ //{"media-tape"_L1, u"\u"},
+ //{"modem"_L1, u"\u"},
+ //{"multimedia-player"_L1, u"\u"},
+ //{"network-wired"_L1, u"\u"},
+ {"network-wireless"_L1, u"\ue63e"},
+ //{"pda"_L1, u"\u"},
+ {"phone"_L1, u"\ue32c"},
+ {"printer"_L1, u"\ue8ad"},
+ {"scanner"_L1, u"\ue329"},
+ {"video-display"_L1, u"\uf06a"},
+ //{"emblem-default"_L1, u"\u"},
+ {"emblem-documents"_L1, u"\ue873"},
+ {"emblem-downloads"_L1, u"\uf090"},
+ {"emblem-favorite"_L1, u"\uf090"},
+ {"emblem-important"_L1, u"\ue645"},
+ {"emblem-mail"_L1, u"\ue158"},
+ {"emblem-photos"_L1, u"\ue413"},
+ //{"emblem-readonly"_L1, u"\u"},
+ {"emblem-shared"_L1, u"\ue413"},
+ //{"emblem-symbolic-link"_L1, u"\u"},
+ //{"emblem-synchronized"_L1, u"\u"},
+ {"emblem-system"_L1, u"\ue8b8"},
+ //{"emblem-unreadable"_L1, u"\u"},
+ {"folder"_L1, u"\ue2c7"},
+ //{"folder-remote"_L1, u"\u"},
+ {"network-server"_L1, u"\ue875"},
+ {"network-workgroup"_L1, u"\ue1a0"},
+ {"start-here"_L1, u"\ue089"},
+ {"user-bookmarks"_L1, u"\ue98b"},
+ {"user-desktop"_L1, u"\ue30a"},
+ {"user-home"_L1, u"\ue88a"},
+ {"user-trash"_L1, u"\ue872"},
+ {"appointment-missed"_L1, u"\ue615"},
+ {"appointment-soon"_L1, u"\uf540"},
+ {"audio-volume-high"_L1, u"\ue050"},
+ {"audio-volume-low"_L1, u"\ue04d"},
+ //{"audio-volume-medium"_L1, u"\u"},
+ {"audio-volume-muted"_L1, u"\ue04e"},
+ {"battery-caution"_L1, u"\ue19c"},
+ {"battery-low"_L1, u"\uf147"},
+ {"dialog-error"_L1, u"\ue000"},
+ {"dialog-information"_L1, u"\ue88e"},
+ {"dialog-password"_L1, u"\uf042"},
+ {"dialog-question"_L1, u"\ueb8b"},
+ {"dialog-warning"_L1, u"\ue002"},
+ {"folder-drag-accept"_L1, u"\ue9a3"},
+ {"folder-open"_L1, u"\ue2c8"},
+ {"folder-visiting"_L1, u"\ue8a7"},
+ {"image-loading"_L1, u"\ue41a"},
+ {"image-missing"_L1, u"\ue3ad"},
+ {"mail-attachment"_L1, u"\ue2bc"},
+ {"mail-unread"_L1, u"\uf18a"},
+ {"mail-read"_L1, u"\uf18c"},
+ //{"mail-replied"_L1, u"\u"},
+ //{"mail-signed"_L1, u"\u"},
+ //{"mail-signed-verified"_L1, u"\u"},
+ {"media-playlist-repeat"_L1, u"\ue040"},
+ {"media-playlist-shuffle"_L1, u"\ue043"},
+ {"network-error"_L1, u"\uead9"},
+ {"network-idle"_L1, u"\ue51f"},
+ {"network-offline"_L1, u"\uf239"},
+ {"network-receive"_L1, u"\ue2c0"},
+ {"network-transmit"_L1, u"\ue2c3"},
+ {"network-transmit-receive"_L1, u"\uea18"},
+ {"printer-error"_L1, u"\uf7a0"},
+ {"printer-printing"_L1, u"\uf7a1"},
+ {"security-high"_L1, u"\ue32a"},
+ {"security-medium"_L1, u"\ue9e0"},
+ {"security-low"_L1, u"\uf012"},
+ {"software-update-available"_L1, u"\ue923"},
+ {"software-update-urgent"_L1, u"\uf05a"},
+ {"sync-error"_L1, u"\ue629"},
+ {"sync-synchronizing"_L1, u"\ue627"},
+ //{"task-due"_L1, u"\u"},
+ //{"task-past-due"_L1, u"\u"},
+ {"user-available"_L1, u"\uf565"},
+ {"user-away"_L1, u"\ue510"},
+ //{"user-idle"_L1, u"\u"},
+ {"user-offline"_L1, u"\uf7b3"},
+ {"user-trash-full"_L1, u"\ue872"}, //delete
+ //{"user-trash-full"_L1, u"\ue92b"}, //delete_forever
+ {"weather-clear"_L1, u"\uf157"},
+ {"weather-clear-night"_L1, u"\uf159"},
+ {"weather-few-clouds"_L1, u"\uf172"},
+ {"weather-few-clouds-night"_L1, u"\uf174"},
+ {"weather-fog"_L1, u"\ue818"},
+ //{"weather-overcast"_L1, u"\u"},
+ {"weather-severe-alert"_L1, u"\ue002"}, //warning
+ //{"weather-severe-alert"_L1, u"\uebd3"},//severe_cold
+ {"weather-showers"_L1, u"\uf176"},
+ //{"weather-showers-scattered"_L1, u"\u"},
+ {"weather-snow"_L1, u"\ue80f"}, //snowing
+ //{"weather-snow"_L1, u"\ue2cd"}, //weather_snowy
+ //{"weather-snow"_L1, u"\ue810"},//cloudy_snowing
+ {"weather-storm"_L1, u"\uf070"},
+ };
+
+ const auto it = std::find_if(std::begin(glyphMap), std::end(glyphMap), [this](const auto &c){
+ return c.first == m_iconName;
+ });
+ return it != std::end(glyphMap) ? it->second.toString()
+ : (m_iconName.length() == 1 ? m_iconName : QString());
+}
+
+QAndroidPlatformIconEngine::QAndroidPlatformIconEngine(const QString &iconName)
+ : m_iconName(iconName)
+ , m_glyphs(glyphs())
+{
+ QString fontFamily;
+ // The MaterialIcons-*.ttf and MaterialSymbols* font files are available from
+ // https://github.com/google/material-design-icons/tree/master. If one of them is
+ // packaged as a resource with the application, then we use it. We prioritize
+ // a variable font.
+ const QStringList fontCandidates = {
+ "MaterialSymbolsOutlined[FILL,GRAD,opsz,wght].ttf",
+ "MaterialSymbolsRounded[FILL,GRAD,opsz,wght].ttf",
+ "MaterialSymbolsSharp[FILL,GRAD,opsz,wght].ttf",
+ "MaterialIcons-Regular.ttf",
+ "MaterialIconsOutlined-Regular.otf",
+ "MaterialIconsRound-Regular.otf",
+ "MaterialIconsSharp-Regular.otf",
+ "MaterialIconsTwoTone-Regular.otf",
+ };
+ for (const auto &fontCandidate : fontCandidates) {
+ fontFamily = FontProvider::fetchFont(u":/qt-project.org/icons/%1"_s.arg(fontCandidate));
+ if (!fontFamily.isEmpty())
+ break;
+ }
+
+ // Otherwise we try to download the Outlined version of Material Symbols
+ const QString key = qEnvironmentVariable("QT_GOOGLE_FONTS_KEY");
+ if (fontFamily.isEmpty() && !key.isEmpty())
+ fontFamily = FontProvider::fetchFont(u"key=%1&name=Material+Symbols+Outlined"_s.arg(key));
+
+ // last resort - use any Material Icons
+ if (fontFamily.isEmpty())
+ fontFamily = u"Material Icons"_s;
+ m_iconFont = QFont(fontFamily);
+}
+
+QAndroidPlatformIconEngine::~QAndroidPlatformIconEngine()
+{}
+
+QIconEngine *QAndroidPlatformIconEngine::clone() const
+{
+ return new QAndroidPlatformIconEngine(m_iconName);
+}
+
+QString QAndroidPlatformIconEngine::key() const
+{
+ return u"QAndroidPlatformIconEngine"_s;
+}
+
+QString QAndroidPlatformIconEngine::iconName()
+{
+ return m_iconName;
+}
+
+bool QAndroidPlatformIconEngine::isNull()
+{
+ if (m_glyphs.isEmpty())
+ return true;
+ const QChar c0 = m_glyphs.at(0);
+ const QFontMetrics fontMetrics(m_iconFont);
+ if (c0.category() == QChar::Other_Surrogate && m_glyphs.size() > 1)
+ return !fontMetrics.inFontUcs4(QChar::surrogateToUcs4(c0, m_glyphs.at(1)));
+ return !fontMetrics.inFont(c0);
+}
+
+QList<QSize> QAndroidPlatformIconEngine::availableSizes(QIcon::Mode, QIcon::State)
+{
+ return {{16, 16}, {24, 24}, {48, 48}, {128, 128}};
+}
+
+QSize QAndroidPlatformIconEngine::actualSize(const QSize &size, QIcon::Mode mode, QIcon::State state)
+{
+ return QIconEngine::actualSize(size, mode, state);
+}
+
+QPixmap QAndroidPlatformIconEngine::pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state)
+{
+ return scaledPixmap(size, mode, state, 1.0);
+}
+
+QPixmap QAndroidPlatformIconEngine::scaledPixmap(const QSize &size, QIcon::Mode mode, QIcon::State state, qreal scale)
+{
+ const quint64 cacheKey = calculateCacheKey(mode, state);
+ if (cacheKey != m_cacheKey || m_pixmap.size() != size || m_pixmap.devicePixelRatio() != scale) {
+ m_pixmap = QPixmap(size * scale);
+ m_pixmap.fill(Qt::transparent);
+ m_pixmap.setDevicePixelRatio(scale);
+
+ QPainter painter(&m_pixmap);
+ paint(&painter, QRect(QPoint(), size), mode, state);
+
+ m_cacheKey = cacheKey;
+ }
+
+ return m_pixmap;
+}
+
+void QAndroidPlatformIconEngine::paint(QPainter *painter, const QRect &rect, QIcon::Mode mode, QIcon::State state)
+{
+ Q_UNUSED(state);
+
+ painter->save();
+ QFont renderFont(m_iconFont);
+ renderFont.setPixelSize(rect.height());
+ painter->setFont(renderFont);
+
+ QPalette palette;
+ switch (mode) {
+ case QIcon::Active:
+ painter->setPen(palette.color(QPalette::Active, QPalette::Text));
+ break;
+ case QIcon::Normal:
+ painter->setPen(palette.color(QPalette::Active, QPalette::Text));
+ break;
+ case QIcon::Disabled:
+ painter->setPen(palette.color(QPalette::Disabled, QPalette::Text));
+ break;
+ case QIcon::Selected:
+ painter->setPen(palette.color(QPalette::Active, QPalette::HighlightedText));
+ break;
+ }
+
+ painter->drawText(rect, Qt::AlignCenter, m_glyphs);
+ painter->restore();
+}
+
+QT_END_NAMESPACE
diff --git a/src/plugins/platforms/android/qandroidplatformiconengine.h b/src/plugins/platforms/android/qandroidplatformiconengine.h
new file mode 100644
index 0000000000..cac54481d9
--- /dev/null
+++ b/src/plugins/platforms/android/qandroidplatformiconengine.h
@@ -0,0 +1,44 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QANDROIDPLATFORMICONENGINE_H
+#define QANDROIDPLATFORMICONENGINE_H
+
+#include <QtGui/qiconengine.h>
+#include <QtGui/qfont.h>
+
+QT_BEGIN_NAMESPACE
+
+class QAndroidPlatformIconEngine : public QIconEngine
+{
+public:
+ QAndroidPlatformIconEngine(const QString &iconName);
+ ~QAndroidPlatformIconEngine();
+ QIconEngine *clone() const override;
+ QString key() const override;
+ QString iconName() override;
+ bool isNull() override;
+
+ QList<QSize> availableSizes(QIcon::Mode, QIcon::State) override;
+ QSize actualSize(const QSize &size, QIcon::Mode mode, QIcon::State state) override;
+ QPixmap pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state) override;
+ QPixmap scaledPixmap(const QSize &size, QIcon::Mode mode, QIcon::State state, qreal scale) override;
+ void paint(QPainter *painter, const QRect &rect, QIcon::Mode mode, QIcon::State state) override;
+
+private:
+ static constexpr quint64 calculateCacheKey(QIcon::Mode mode, QIcon::State state)
+ {
+ return (quint64(mode) << 32) | state;
+ }
+ QString glyphs() const;
+
+ const QString m_iconName;
+ QFont m_iconFont;
+ const QString m_glyphs;
+ mutable QPixmap m_pixmap;
+ mutable quint64 m_cacheKey = {};
+};
+
+QT_END_NAMESPACE
+
+#endif // QANDROIDPLATFORMICONENGINE_H
diff --git a/src/plugins/platforms/android/qandroidplatformintegration.cpp b/src/plugins/platforms/android/qandroidplatformintegration.cpp
index 19a7326115..038fe5172a 100644
--- a/src/plugins/platforms/android/qandroidplatformintegration.cpp
+++ b/src/plugins/platforms/android/qandroidplatformintegration.cpp
@@ -9,7 +9,6 @@
#include "qabstracteventdispatcher.h"
#include "qandroideventdispatcher.h"
#include "qandroidplatformaccessibility.h"
-#include "qandroidplatformbackingstore.h"
#include "qandroidplatformclipboard.h"
#include "qandroidplatformfontdatabase.h"
#include "qandroidplatformforeignwindow.h"
@@ -29,6 +28,7 @@
#include <QtGui/private/qeglpbuffer_p.h>
#include <QtGui/private/qguiapplication_p.h>
#include <QtGui/private/qoffscreensurface_p.h>
+#include <QtGui/private/qrhibackingstore_p.h>
#include <qpa/qplatformoffscreensurface.h>
#include <qpa/qplatformwindow.h>
#include <qpa/qwindowsysteminterface.h>
@@ -46,24 +46,46 @@ QT_BEGIN_NAMESPACE
using namespace Qt::StringLiterals;
-QSize QAndroidPlatformIntegration::m_defaultScreenSize = QSize(320, 455);
-QRect QAndroidPlatformIntegration::m_defaultAvailableGeometry = QRect(0, 0, 320, 455);
-QSize QAndroidPlatformIntegration::m_defaultPhysicalSize = QSize(50, 71);
+Q_CONSTINIT QSize QAndroidPlatformIntegration::m_defaultScreenSize = QSize(320, 455);
+Q_CONSTINIT QRect QAndroidPlatformIntegration::m_defaultAvailableGeometry = QRect(0, 0, 320, 455);
+Q_CONSTINIT QSize QAndroidPlatformIntegration::m_defaultPhysicalSize = QSize(50, 71);
Qt::ScreenOrientation QAndroidPlatformIntegration::m_orientation = Qt::PrimaryOrientation;
Qt::ScreenOrientation QAndroidPlatformIntegration::m_nativeOrientation = Qt::PrimaryOrientation;
bool QAndroidPlatformIntegration::m_showPasswordEnabled = false;
-static bool m_running = false;
+
+Q_DECLARE_JNI_CLASS(QtNative, "org/qtproject/qt/android/QtNative")
+Q_DECLARE_JNI_CLASS(QtDisplayManager, "org/qtproject/qt/android/QtDisplayManager")
+Q_DECLARE_JNI_CLASS(Display, "android/view/Display")
+
+Q_DECLARE_JNI_CLASS(List, "java/util/List")
+
+namespace {
+
+QAndroidPlatformScreen* createScreenForDisplayId(int displayId)
+{
+ const QJniObject display = QtJniTypes::QtDisplayManager::callStaticMethod<QtJniTypes::Display>(
+ "getDisplay", QtAndroidPrivate::context(), displayId);
+ if (!display.isValid())
+ return nullptr;
+ return new QAndroidPlatformScreen(display);
+}
+
+} // anonymous namespace
void *QAndroidPlatformNativeInterface::nativeResourceForIntegration(const QByteArray &resource)
{
if (resource=="JavaVM")
return QtAndroid::javaVM();
- if (resource == "QtActivity")
- return QtAndroid::activity();
- if (resource == "QtService")
- return QtAndroid::service();
+ if (resource == "QtActivity") {
+ extern Q_CORE_EXPORT jobject qt_androidActivity();
+ return qt_androidActivity();
+ }
+ if (resource == "QtService") {
+ extern Q_CORE_EXPORT jobject qt_androidService();
+ return qt_androidService();
+ }
if (resource == "AndroidStyleData") {
if (m_androidStyle) {
if (m_androidStyle->m_styleData.isEmpty())
@@ -136,10 +158,6 @@ void QAndroidPlatformNativeInterface::customEvent(QEvent *event)
api->accessibility()->setActive(QtAndroidAccessibility::isActive());
#endif // QT_CONFIG(accessibility)
- if (!m_running) {
- m_running = true;
- QtAndroid::notifyQtAndroidPluginRunning(m_running);
- }
api->flushPendingUpdates();
}
@@ -163,10 +181,32 @@ QAndroidPlatformIntegration::QAndroidPlatformIntegration(const QStringList &para
if (Q_UNLIKELY(!eglBindAPI(EGL_OPENGL_ES_API)))
qFatal("Could not bind GL_ES API");
- m_primaryScreen = new QAndroidPlatformScreen();
- QWindowSystemInterface::handleScreenAdded(m_primaryScreen);
- m_primaryScreen->setSizeParameters(m_defaultPhysicalSize, m_defaultScreenSize,
- m_defaultAvailableGeometry);
+ using namespace QtJniTypes;
+ m_primaryDisplayId = Display::getStaticField<jint>("DEFAULT_DISPLAY");
+ const QJniObject nativeDisplaysList = QtDisplayManager::callStaticMethod<List>(
+ "getAvailableDisplays", QtAndroidPrivate::context());
+
+ const int numberOfAvailableDisplays = nativeDisplaysList.callMethod<jint>("size");
+ for (int i = 0; i < numberOfAvailableDisplays; ++i) {
+ const QJniObject display =
+ nativeDisplaysList.callObjectMethod<jobject, jint>("get", jint(i));
+ const int displayId = display.callMethod<jint>("getDisplayId");
+ const bool isPrimary = (m_primaryDisplayId == displayId);
+ auto screen = new QAndroidPlatformScreen(display);
+
+ if (isPrimary)
+ m_primaryScreen = screen;
+
+ QWindowSystemInterface::handleScreenAdded(screen, isPrimary);
+ m_screens[displayId] = screen;
+ }
+
+ if (numberOfAvailableDisplays == 0) {
+ // If no displays are found, add a dummy display
+ auto defaultScreen = new QAndroidPlatformScreen(QJniObject {});
+ m_primaryScreen = defaultScreen;
+ QWindowSystemInterface::handleScreenAdded(defaultScreen, true);
+ }
m_mainThread = QThread::currentThread();
@@ -183,9 +223,9 @@ QAndroidPlatformIntegration::QAndroidPlatformIntegration(const QStringList &para
m_accessibility = new QAndroidPlatformAccessibility();
#endif // QT_CONFIG(accessibility)
- QJniObject javaActivity(QtAndroid::activity());
+ QJniObject javaActivity = QtAndroidPrivate::activity();
if (!javaActivity.isValid())
- javaActivity = QtAndroid::service();
+ javaActivity = QtAndroidPrivate::service();
if (javaActivity.isValid()) {
QJniObject resources = javaActivity.callObjectMethod("getResources", "()Landroid/content/res/Resources;");
@@ -225,6 +265,10 @@ QAndroidPlatformIntegration::QAndroidPlatformIntegration(const QStringList &para
maxTouchPoints,
0);
QWindowSystemInterface::registerInputDevice(m_touchDevice);
+
+ QWindowSystemInterface::registerInputDevice(
+ new QInputDevice("Virtual keyboard"_L1, 0, QInputDevice::DeviceType::Keyboard,
+ {}, qApp));
}
auto contentResolver = javaActivity.callObjectMethod("getContentResolver", "()Landroid/content/ContentResolver;");
@@ -260,11 +304,11 @@ static bool needsBasicRenderloopWorkaround()
void QAndroidPlatformIntegration::initialize()
{
- const QString icStr = QPlatformInputContextFactory::requested();
- if (icStr.isNull())
+ const auto icStrs = QPlatformInputContextFactory::requested();
+ if (icStrs.isEmpty())
m_inputContext.reset(new QAndroidInputContext);
else
- m_inputContext.reset(QPlatformInputContextFactory::create(icStr));
+ m_inputContext.reset(QPlatformInputContextFactory::create(icStrs));
}
bool QAndroidPlatformIntegration::hasCapability(Capability cap) const
@@ -272,13 +316,16 @@ bool QAndroidPlatformIntegration::hasCapability(Capability cap) const
switch (cap) {
case ApplicationState: return true;
case ThreadedPixmaps: return true;
- case NativeWidgets: return QtAndroid::activity();
- case OpenGL: return QtAndroid::activity();
- case ForeignWindows: return QtAndroid::activity();
- case ThreadedOpenGL: return !needsBasicRenderloopWorkaround() && QtAndroid::activity();
- case RasterGLSurface: return QtAndroid::activity();
+ case NativeWidgets: return QtAndroidPrivate::activity().isValid();
+ case OpenGL: return QtAndroidPrivate::activity().isValid();
+ case ForeignWindows: return QtAndroidPrivate::activity().isValid();
+ case ThreadedOpenGL: return !needsBasicRenderloopWorkaround() && QtAndroidPrivate::activity().isValid();
+ case RasterGLSurface: return QtAndroidPrivate::activity().isValid();
case TopStackedNativeChildWindows: return false;
case MaximizeUsingFullscreenGeometry: return true;
+ // FIXME QTBUG-118849 - we do not implement grabWindow() anymore, calling it will return
+ // a null QPixmap also for raster windows - for OpenGL windows this was always true
+ case ScreenWindowGrabbing: return false;
default:
return QPlatformIntegration::hasCapability(cap);
}
@@ -286,15 +333,15 @@ bool QAndroidPlatformIntegration::hasCapability(Capability cap) const
QPlatformBackingStore *QAndroidPlatformIntegration::createPlatformBackingStore(QWindow *window) const
{
- if (!QtAndroid::activity())
+ if (!QtAndroidPrivate::activity().isValid())
return nullptr;
- return new QAndroidPlatformBackingStore(window);
+ return new QRhiBackingStore(window);
}
QPlatformOpenGLContext *QAndroidPlatformIntegration::createPlatformOpenGLContext(QOpenGLContext *context) const
{
- if (!QtAndroid::activity())
+ if (!QtAndroidPrivate::activity().isValid())
return nullptr;
QSurfaceFormat format(context->format());
format.setAlphaBufferSize(8);
@@ -312,7 +359,7 @@ QOpenGLContext *QAndroidPlatformIntegration::createOpenGLContext(EGLContext cont
QPlatformOffscreenSurface *QAndroidPlatformIntegration::createPlatformOffscreenSurface(QOffscreenSurface *surface) const
{
- if (!QtAndroid::activity())
+ if (!QtAndroidPrivate::activity().isValid())
return nullptr;
QSurfaceFormat format(surface->requestedFormat());
@@ -326,7 +373,7 @@ QPlatformOffscreenSurface *QAndroidPlatformIntegration::createPlatformOffscreenS
QOffscreenSurface *QAndroidPlatformIntegration::createOffscreenSurface(ANativeWindow *nativeSurface) const
{
- if (!QtAndroid::activity() || !nativeSurface)
+ if (!QtAndroidPrivate::activity().isValid() || !nativeSurface)
return nullptr;
auto *surface = new QOffscreenSurface;
@@ -337,7 +384,7 @@ QOffscreenSurface *QAndroidPlatformIntegration::createOffscreenSurface(ANativeWi
QPlatformWindow *QAndroidPlatformIntegration::createPlatformWindow(QWindow *window) const
{
- if (!QtAndroid::activity())
+ if (!QtAndroidPrivate::activity().isValid())
return nullptr;
#if QT_CONFIG(vulkan)
@@ -432,7 +479,7 @@ QStringList QAndroidPlatformIntegration::themeNames() const
QPlatformTheme *QAndroidPlatformIntegration::createPlatformTheme(const QString &name) const
{
if (androidThemeName == name)
- return new QAndroidPlatformTheme(m_androidPlatformNativeInterface);
+ return QAndroidPlatformTheme::instance(m_androidPlatformNativeInterface);
return 0;
}
@@ -488,6 +535,18 @@ void QAndroidPlatformIntegration::setScreenSize(int width, int height)
QMetaObject::invokeMethod(m_primaryScreen, "setSize", Qt::AutoConnection, Q_ARG(QSize, QSize(width, height)));
}
+Qt::ColorScheme QAndroidPlatformIntegration::m_colorScheme = Qt::ColorScheme::Light;
+
+void QAndroidPlatformIntegration::setColorScheme(Qt::ColorScheme colorScheme)
+{
+ if (m_colorScheme == colorScheme)
+ return;
+ m_colorScheme = colorScheme;
+
+ QMetaObject::invokeMethod(qGuiApp,
+ [] () { QAndroidPlatformTheme::instance()->updateColorScheme();});
+}
+
void QAndroidPlatformIntegration::setScreenSizeParameters(const QSize &physicalSize,
const QSize &screenSize,
const QRect &availableGeometry)
@@ -505,6 +564,49 @@ void QAndroidPlatformIntegration::setRefreshRate(qreal refreshRate)
QMetaObject::invokeMethod(m_primaryScreen, "setRefreshRate", Qt::AutoConnection,
Q_ARG(qreal, refreshRate));
}
+
+void QAndroidPlatformIntegration::handleScreenAdded(int displayId)
+{
+ auto result = m_screens.insert(displayId, nullptr);
+ if (result.first->second == nullptr) {
+ auto it = result.first;
+ it->second = createScreenForDisplayId(displayId);
+ if (it->second == nullptr)
+ return;
+ const bool isPrimary = (m_primaryDisplayId == displayId);
+ if (isPrimary)
+ m_primaryScreen = it->second;
+ QWindowSystemInterface::handleScreenAdded(it->second, isPrimary);
+ } else {
+ qWarning() << "Display with id" << displayId << "already exists.";
+ }
+}
+
+void QAndroidPlatformIntegration::handleScreenChanged(int displayId)
+{
+ auto it = m_screens.find(displayId);
+ if (it == m_screens.end() || it->second == nullptr) {
+ handleScreenAdded(displayId);
+ }
+ // We do not do anything more here as handling of change of
+ // rotation and refresh rate is done in QtActivityDelegate java class
+ // which calls QAndroidPlatformIntegration::setOrientation, and
+ // QAndroidPlatformIntegration::setRefreshRate accordingly.
+}
+
+void QAndroidPlatformIntegration::handleScreenRemoved(int displayId)
+{
+ auto it = m_screens.find(displayId);
+
+ if (it == m_screens.end())
+ return;
+
+ if (it->second != nullptr)
+ QWindowSystemInterface::handleScreenRemoved(it->second);
+
+ m_screens.erase(it);
+}
+
#if QT_CONFIG(vulkan)
QPlatformVulkanInstance *QAndroidPlatformIntegration::createPlatformVulkanInstance(QVulkanInstance *instance) const
diff --git a/src/plugins/platforms/android/qandroidplatformintegration.h b/src/plugins/platforms/android/qandroidplatformintegration.h
index 6e87c9c02b..5f1126fafc 100644
--- a/src/plugins/platforms/android/qandroidplatformintegration.h
+++ b/src/plugins/platforms/android/qandroidplatformintegration.h
@@ -13,6 +13,9 @@
#include <qpa/qplatformnativeinterface.h>
#include <qpa/qplatformopenglcontext.h>
#include <qpa/qplatformoffscreensurface.h>
+#include <qpa/qplatformtheme.h>
+#include <private/qflatmap_p.h>
+#include <QtCore/qvarlengtharray.h>
#include <EGL/egl.h>
#include <memory>
@@ -74,6 +77,10 @@ public:
QPlatformFontDatabase *fontDatabase() const override;
+ void handleScreenAdded(int displayId);
+ void handleScreenChanged(int displayId);
+ void handleScreenRemoved(int displayId);
+
#ifndef QT_NO_CLIPBOARD
QPlatformClipboard *clipboard() const override;
#endif
@@ -103,6 +110,8 @@ public:
void flushPendingUpdates();
+ static void setColorScheme(Qt::ColorScheme colorScheme);
+ static Qt::ColorScheme colorScheme() { return m_colorScheme; }
#if QT_CONFIG(vulkan)
QPlatformVulkanInstance *createPlatformVulkanInstance(QVulkanInstance *instance) const override;
#endif
@@ -115,6 +124,8 @@ private:
QThread *m_mainThread;
+ static Qt::ColorScheme m_colorScheme;
+
static QRect m_defaultAvailableGeometry;
static QSize m_defaultPhysicalSize;
static QSize m_defaultScreenSize;
@@ -127,6 +138,17 @@ private:
QAndroidPlatformNativeInterface *m_androidPlatformNativeInterface;
QAndroidPlatformServices *m_androidPlatformServices;
+ // Handling the multiple screens connected. Every display is identified
+ // with an unique (autoincremented) displayID. The values of this ID will
+ // not repeat during the OS runtime. We use this value as the key in the
+ // storage of screens.
+ QFlatMap<int, QAndroidPlatformScreen *, std::less<int>
+ , QVarLengthArray<int, 10>
+ , QVarLengthArray<QAndroidPlatformScreen *, 10> > m_screens;
+ // ID of the primary display, in documentation it is said to be always 0,
+ // but nevertheless it is retrieved
+ int m_primaryDisplayId = 0;
+
#ifndef QT_NO_CLIPBOARD
QPlatformClipboard *m_androidPlatformClipboard;
#endif
diff --git a/src/plugins/platforms/android/qandroidplatformmenu.cpp b/src/plugins/platforms/android/qandroidplatformmenu.cpp
index 4ddd6ea29a..e59fd2089d 100644
--- a/src/plugins/platforms/android/qandroidplatformmenu.cpp
+++ b/src/plugins/platforms/android/qandroidplatformmenu.cpp
@@ -119,7 +119,7 @@ void QAndroidPlatformMenu::showPopup(const QWindow *parentWindow, const QRect &t
Q_UNUSED(parentWindow);
Q_UNUSED(item);
setVisible(true);
- QtAndroidMenu::showContextMenu(this, targetRect, QJniEnvironment().jniEnv());
+ QtAndroidMenu::showContextMenu(this, targetRect);
}
QPlatformMenuItem *QAndroidPlatformMenu::menuItemForTag(quintptr tag) const
diff --git a/src/plugins/platforms/android/qandroidplatformopenglwindow.cpp b/src/plugins/platforms/android/qandroidplatformopenglwindow.cpp
index c1ec2fbdd6..13d14eb391 100644
--- a/src/plugins/platforms/android/qandroidplatformopenglwindow.cpp
+++ b/src/plugins/platforms/android/qandroidplatformopenglwindow.cpp
@@ -24,41 +24,19 @@ QT_BEGIN_NAMESPACE
QAndroidPlatformOpenGLWindow::QAndroidPlatformOpenGLWindow(QWindow *window, EGLDisplay display)
:QAndroidPlatformWindow(window), m_eglDisplay(display)
{
+ if (window->surfaceType() == QSurface::RasterSurface)
+ window->setSurfaceType(QSurface::OpenGLSurface);
}
QAndroidPlatformOpenGLWindow::~QAndroidPlatformOpenGLWindow()
{
m_surfaceWaitCondition.wakeOne();
lockSurface();
- if (m_nativeSurfaceId != -1)
- QtAndroid::destroySurface(m_nativeSurfaceId);
+ destroySurface();
clearEgl();
unlockSurface();
}
-void QAndroidPlatformOpenGLWindow::repaint(const QRegion &region)
-{
- // This is only for real raster top-level windows. Stop in all other cases.
- if ((window()->surfaceType() == QSurface::RasterGLSurface && qt_window_private(window())->compositing)
- || window()->surfaceType() == QSurface::OpenGLSurface
- || QAndroidPlatformWindow::parent())
- return;
-
- QRect currentGeometry = geometry();
-
- QRect dirtyClient = region.boundingRect();
- QRect dirtyRegion(currentGeometry.left() + dirtyClient.left(),
- currentGeometry.top() + dirtyClient.top(),
- dirtyClient.width(),
- dirtyClient.height());
- QRect mOldGeometryLocal = m_oldGeometry;
- m_oldGeometry = currentGeometry;
- // If this is a move, redraw the previous location
- if (mOldGeometryLocal != currentGeometry)
- platformScreen()->setDirty(mOldGeometryLocal);
- platformScreen()->setDirty(dirtyRegion);
-}
-
void QAndroidPlatformOpenGLWindow::setGeometry(const QRect &rect)
{
if (rect == geometry())
@@ -67,8 +45,9 @@ void QAndroidPlatformOpenGLWindow::setGeometry(const QRect &rect)
m_oldGeometry = geometry();
QAndroidPlatformWindow::setGeometry(rect);
- if (m_nativeSurfaceId != -1)
- QtAndroid::setSurfaceGeometry(m_nativeSurfaceId, rect);
+
+
+ setNativeGeometry(rect);
QRect availableGeometry = screen()->availableGeometry();
if (rect.width() > 0
@@ -77,25 +56,23 @@ void QAndroidPlatformOpenGLWindow::setGeometry(const QRect &rect)
&& availableGeometry.height() > 0) {
QWindowSystemInterface::handleExposeEvent(window(), QRect(QPoint(0, 0), rect.size()));
}
-
- if (rect.topLeft() != m_oldGeometry.topLeft())
- repaint(QRegion(rect));
}
EGLSurface QAndroidPlatformOpenGLWindow::eglSurface(EGLConfig config)
{
- if (QAndroidEventDispatcherStopper::stopped() || QGuiApplication::applicationState() == Qt::ApplicationSuspended)
+ if (QAndroidEventDispatcherStopper::stopped() ||
+ QGuiApplication::applicationState() == Qt::ApplicationSuspended) {
return m_eglSurface;
+ }
QMutexLocker lock(&m_surfaceMutex);
- if (m_nativeSurfaceId == -1) {
+ if (!m_surfaceCreated) {
AndroidDeadlockProtector protector;
if (!protector.acquire())
return m_eglSurface;
- const bool windowStaysOnTop = bool(window()->flags() & Qt::WindowStaysOnTopHint);
- m_nativeSurfaceId = QtAndroid::createSurface(this, geometry(), windowStaysOnTop, 32);
+ createSurface();
m_surfaceWaitCondition.wait(&m_surfaceMutex);
}
@@ -110,7 +87,7 @@ EGLSurface QAndroidPlatformOpenGLWindow::eglSurface(EGLConfig config)
bool QAndroidPlatformOpenGLWindow::checkNativeSurface(EGLConfig config)
{
QMutexLocker lock(&m_surfaceMutex);
- if (m_nativeSurfaceId == -1 || !m_androidSurfaceObject.isValid())
+ if (!m_surfaceCreated || !m_androidSurfaceObject.isValid())
return false; // makeCurrent is NOT needed.
createEgl(config);
@@ -127,10 +104,7 @@ void QAndroidPlatformOpenGLWindow::applicationStateChanged(Qt::ApplicationState
QAndroidPlatformWindow::applicationStateChanged(state);
if (state <= Qt::ApplicationHidden) {
lockSurface();
- if (m_nativeSurfaceId != -1) {
- QtAndroid::destroySurface(m_nativeSurfaceId);
- m_nativeSurfaceId = -1;
- }
+ destroySurface();
clearEgl();
unlockSurface();
}
@@ -173,24 +147,4 @@ void QAndroidPlatformOpenGLWindow::clearEgl()
}
}
-void QAndroidPlatformOpenGLWindow::surfaceChanged(JNIEnv *jniEnv, jobject surface, int w, int h)
-{
- Q_UNUSED(jniEnv);
- Q_UNUSED(w);
- Q_UNUSED(h);
-
- lockSurface();
- m_androidSurfaceObject = surface;
- if (surface) // wait until we have a valid surface to draw into
- m_surfaceWaitCondition.wakeOne();
- unlockSurface();
-
- if (surface) {
- // repaint the window, when we have a valid surface
- QRect availableGeometry = screen()->availableGeometry();
- if (geometry().width() > 0 && geometry().height() > 0 && availableGeometry.width() > 0 && availableGeometry.height() > 0)
- QWindowSystemInterface::handleExposeEvent(window(), QRegion(QRect(QPoint(), geometry().size())));
- }
-}
-
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/android/qandroidplatformopenglwindow.h b/src/plugins/platforms/android/qandroidplatformopenglwindow.h
index 8c31368b65..c1ae57fe85 100644
--- a/src/plugins/platforms/android/qandroidplatformopenglwindow.h
+++ b/src/plugins/platforms/android/qandroidplatformopenglwindow.h
@@ -5,7 +5,6 @@
#ifndef QANDROIDPLATFORMOPENGLWINDOW_H
#define QANDROIDPLATFORMOPENGLWINDOW_H
-#include "androidsurfaceclient.h"
#include "qandroidplatformwindow.h"
#include <QWaitCondition>
@@ -16,7 +15,7 @@
QT_BEGIN_NAMESPACE
-class QAndroidPlatformOpenGLWindow : public QAndroidPlatformWindow, public AndroidSurfaceClient
+class QAndroidPlatformOpenGLWindow : public QAndroidPlatformWindow
{
public:
explicit QAndroidPlatformOpenGLWindow(QWindow *window, EGLDisplay display);
@@ -30,10 +29,7 @@ public:
void applicationStateChanged(Qt::ApplicationState) override;
- void repaint(const QRegion &region) override;
-
protected:
- void surfaceChanged(JNIEnv *jniEnv, jobject surface, int w, int h) override;
void createEgl(EGLConfig config);
void clearEgl();
@@ -42,9 +38,6 @@ private:
EGLSurface m_eglSurface = EGL_NO_SURFACE;
EGLNativeWindowType m_nativeWindow = nullptr;
- int m_nativeSurfaceId = -1;
- QJniObject m_androidSurfaceObject;
- QWaitCondition m_surfaceWaitCondition;
QSurfaceFormat m_format;
QRect m_oldGeometry;
};
diff --git a/src/plugins/platforms/android/qandroidplatformscreen.cpp b/src/plugins/platforms/android/qandroidplatformscreen.cpp
index d11b7b197b..9e20b7ac4b 100644
--- a/src/plugins/platforms/android/qandroidplatformscreen.cpp
+++ b/src/plugins/platforms/android/qandroidplatformscreen.cpp
@@ -8,7 +8,6 @@
#include <qpa/qwindowsysteminterface.h>
#include "qandroidplatformscreen.h"
-#include "qandroidplatformbackingstore.h"
#include "qandroidplatformintegration.h"
#include "qandroidplatformwindow.h"
#include "androidjnimain.h"
@@ -52,11 +51,22 @@ private:
# define PROFILE_SCOPE
#endif
-QAndroidPlatformScreen::QAndroidPlatformScreen()
+Q_DECLARE_JNI_CLASS(Display, "android/view/Display")
+Q_DECLARE_JNI_CLASS(DisplayMetrics, "android/util/DisplayMetrics")
+Q_DECLARE_JNI_CLASS(Resources, "android/content/res/Resources")
+Q_DECLARE_JNI_CLASS(Size, "android/util/Size")
+Q_DECLARE_JNI_CLASS(QtNative, "org/qtproject/qt/android/QtNative")
+Q_DECLARE_JNI_CLASS(QtDisplayManager, "org/qtproject/qt/android/QtDisplayManager")
+
+Q_DECLARE_JNI_CLASS(DisplayMode, "android/view/Display$Mode")
+
+QAndroidPlatformScreen::QAndroidPlatformScreen(const QJniObject &displayObject)
: QObject(), QPlatformScreen()
{
m_availableGeometry = QAndroidPlatformIntegration::m_defaultAvailableGeometry;
m_size = QAndroidPlatformIntegration::m_defaultScreenSize;
+ m_physicalSize = QAndroidPlatformIntegration::m_defaultPhysicalSize;
+
// Raster only apps should set QT_ANDROID_RASTER_IMAGE_DEPTH to 16
// is way much faster than 32
if (qEnvironmentVariableIntValue("QT_ANDROID_RASTER_IMAGE_DEPTH") == 16) {
@@ -66,69 +76,72 @@ QAndroidPlatformScreen::QAndroidPlatformScreen()
m_format = QImage::Format_ARGB32_Premultiplied;
m_depth = 32;
}
- m_physicalSize = QAndroidPlatformIntegration::m_defaultPhysicalSize;
- connect(qGuiApp, &QGuiApplication::applicationStateChanged, this, &QAndroidPlatformScreen::applicationStateChanged);
-
- QJniObject activity(QtAndroid::activity());
- if (!activity.isValid())
- return;
- QJniObject display;
- if (QNativeInterface::QAndroidApplication::sdkVersion() < 30) {
- display = activity.callObjectMethod("getWindowManager", "()Landroid/view/WindowManager;")
- .callObjectMethod("getDefaultDisplay", "()Landroid/view/Display;");
- } else {
- display = activity.callObjectMethod("getDisplay", "()Landroid/view/Display;");
- }
- if (!display.isValid())
- return;
- m_name = display.callObjectMethod("getName", "()Ljava/lang/String;").toString();
- m_refreshRate = display.callMethod<jfloat>("getRefreshRate");
+ connect(qGuiApp, &QGuiApplication::applicationStateChanged, this,
+ &QAndroidPlatformScreen::applicationStateChanged);
- if (QNativeInterface::QAndroidApplication::sdkVersion() < 23) {
- m_modes << Mode { .size = m_physicalSize.toSize(), .refreshRate = m_refreshRate };
+ if (!displayObject.isValid())
return;
- }
- QJniEnvironment env;
- const jint currentMode = display.callObjectMethod("getMode", "()Landroid/view/Display$Mode;")
- .callMethod<jint>("getModeId");
- const auto modes = display.callObjectMethod("getSupportedModes",
- "()[Landroid/view/Display$Mode;");
- const auto modesArray = jobjectArray(modes.object());
- const auto sz = env->GetArrayLength(modesArray);
- for (jsize i = 0; i < sz; ++i) {
- auto mode = QJniObject::fromLocalRef(env->GetObjectArrayElement(modesArray, i));
- if (currentMode == mode.callMethod<jint>("getModeId"))
- m_currentMode = m_modes.size();
- m_modes << Mode { .size = QSize { mode.callMethod<jint>("getPhysicalHeight"),
- mode.callMethod<jint>("getPhysicalWidth") },
- .refreshRate = mode.callMethod<jfloat>("getRefreshRate") };
+ m_name = displayObject.callObjectMethod<jstring>("getName").toString();
+ m_refreshRate = displayObject.callMethod<jfloat>("getRefreshRate");
+ m_displayId = displayObject.callMethod<jint>("getDisplayId");
+
+ const QJniObject context = QNativeInterface::QAndroidApplication::context();
+ const auto displayContext = context.callMethod<QtJniTypes::Context>("createDisplayContext",
+ displayObject.object<QtJniTypes::Display>());
+
+ const auto sizeObj = QtJniTypes::QtDisplayManager::callStaticMethod<QtJniTypes::Size>(
+ "getDisplaySize", displayContext,
+ displayObject.object<QtJniTypes::Display>());
+ m_size = QSize(sizeObj.callMethod<int>("getWidth"), sizeObj.callMethod<int>("getHeight"));
+
+ const auto resources = displayContext.callMethod<QtJniTypes::Resources>("getResources");
+ const auto metrics = resources.callMethod<QtJniTypes::DisplayMetrics>("getDisplayMetrics");
+ const float xdpi = metrics.getField<float>("xdpi");
+ const float ydpi = metrics.getField<float>("ydpi");
+
+ // Potentially densityDpi could be used instead of xpdi/ydpi to do the calculation,
+ // but the results are not consistent with devices specs.
+ // (https://issuetracker.google.com/issues/194120500)
+ m_physicalSize.setWidth(qRound(m_size.width() / xdpi * 25.4));
+ m_physicalSize.setHeight(qRound(m_size.height() / ydpi * 25.4));
+
+ if (QNativeInterface::QAndroidApplication::sdkVersion() >= 23) {
+ const QJniObject currentMode = displayObject.callObjectMethod<QtJniTypes::DisplayMode>("getMode");
+ m_currentMode = currentMode.callMethod<jint>("getModeId");
+
+ const QJniObject supportedModes = displayObject.callObjectMethod<QtJniTypes::DisplayMode[]>(
+ "getSupportedModes");
+ const auto modeArray = jobjectArray(supportedModes.object());
+
+ QJniEnvironment env;
+ const auto size = env->GetArrayLength(modeArray);
+ for (jsize i = 0; i < size; ++i) {
+ const auto mode = QJniObject::fromLocalRef(env->GetObjectArrayElement(modeArray, i));
+ m_modes << QPlatformScreen::Mode {
+ .size = QSize { mode.callMethod<jint>("getPhysicalWidth"),
+ mode.callMethod<jint>("getPhysicalHeight") },
+ .refreshRate = mode.callMethod<jfloat>("getRefreshRate")
+ };
+ }
}
-
- if (m_modes.isEmpty())
- m_modes << Mode { .size = m_physicalSize.toSize(), .refreshRate = m_refreshRate };
}
QAndroidPlatformScreen::~QAndroidPlatformScreen()
{
- if (m_id != -1) {
- QtAndroid::destroySurface(m_id);
- m_surfaceWaitCondition.wakeOne();
- releaseSurface();
- }
}
-QWindow *QAndroidPlatformScreen::topWindow() const
+QWindow *QAndroidPlatformScreen::topVisibleWindow() const
{
for (QAndroidPlatformWindow *w : m_windowStack) {
- if (w->window()->type() == Qt::Window ||
- w->window()->type() == Qt::Popup ||
- w->window()->type() == Qt::Dialog) {
+ Qt::WindowType type = w->window()->type();
+ if (w->window()->isVisible() &&
+ (type == Qt::Window || type == Qt::Popup || type == Qt::Dialog)) {
return w->window();
}
}
- return 0;
+ return nullptr;
}
QWindow *QAndroidPlatformScreen::topLevelAt(const QPoint &p) const
@@ -140,16 +153,6 @@ QWindow *QAndroidPlatformScreen::topLevelAt(const QPoint &p) const
return 0;
}
-bool QAndroidPlatformScreen::event(QEvent *event)
-{
- if (event->type() == QEvent::UpdateRequest) {
- doRedraw();
- m_updatePending = false;
- return true;
- }
- return QObject::event(event);
-}
-
void QAndroidPlatformScreen::addWindow(QAndroidPlatformWindow *window)
{
if (window->parent() && window->isRaster())
@@ -159,83 +162,45 @@ void QAndroidPlatformScreen::addWindow(QAndroidPlatformWindow *window)
return;
m_windowStack.prepend(window);
- if (window->isRaster()) {
- m_rasterSurfaces.ref();
- setDirty(window->geometry());
- }
+ QtAndroid::qtActivityDelegate().callMethod<void>("addTopLevelWindow", window->nativeWindow());
- QWindow *w = topWindow();
- QWindowSystemInterface::handleWindowActivated(w, Qt::ActiveWindowFocusReason);
- topWindowChanged(w);
+ if (window->window()->isVisible())
+ topVisibleWindowChanged();
}
void QAndroidPlatformScreen::removeWindow(QAndroidPlatformWindow *window)
{
- if (window->parent() && window->isRaster())
- return;
-
m_windowStack.removeOne(window);
if (m_windowStack.contains(window))
qWarning() << "Failed to remove window";
- if (window->isRaster()) {
- m_rasterSurfaces.deref();
- setDirty(window->geometry());
- }
+ QtAndroid::qtActivityDelegate().callMethod<void>("removeTopLevelWindow", window->nativeViewId());
- QWindow *w = topWindow();
- QWindowSystemInterface::handleWindowActivated(w, Qt::ActiveWindowFocusReason);
- topWindowChanged(w);
+ topVisibleWindowChanged();
}
void QAndroidPlatformScreen::raise(QAndroidPlatformWindow *window)
{
- if (window->parent() && window->isRaster())
- return;
-
int index = m_windowStack.indexOf(window);
- if (index <= 0)
+ if (index < 0)
return;
- m_windowStack.move(index, 0);
- if (window->isRaster()) {
- setDirty(window->geometry());
+ if (index > 0) {
+ m_windowStack.move(index, 0);
+ QtAndroid::qtActivityDelegate().callMethod<void>("bringChildToFront", window->nativeViewId());
}
- QWindow *w = topWindow();
- QWindowSystemInterface::handleWindowActivated(w, Qt::ActiveWindowFocusReason);
- topWindowChanged(w);
+ topVisibleWindowChanged();
}
void QAndroidPlatformScreen::lower(QAndroidPlatformWindow *window)
{
- if (window->parent() && window->isRaster())
- return;
-
int index = m_windowStack.indexOf(window);
if (index == -1 || index == (m_windowStack.size() - 1))
return;
m_windowStack.move(index, m_windowStack.size() - 1);
- if (window->isRaster()) {
- setDirty(window->geometry());
- }
- QWindow *w = topWindow();
- QWindowSystemInterface::handleWindowActivated(w, Qt::ActiveWindowFocusReason);
- topWindowChanged(w);
-}
+ QtAndroid::qtActivityDelegate().callMethod<void>("bringChildToBack", window->nativeViewId());
-void QAndroidPlatformScreen::scheduleUpdate()
-{
- if (!m_updatePending) {
- m_updatePending = true;
- QCoreApplication::postEvent(this, new QEvent(QEvent::UpdateRequest));
- }
-}
-
-void QAndroidPlatformScreen::setDirty(const QRect &rect)
-{
- QRect intersection = rect.intersected(m_availableGeometry);
- m_dirtyRect |= intersection;
- scheduleUpdate();
+ topVisibleWindowChanged();
}
void QAndroidPlatformScreen::setPhysicalSize(const QSize &size)
@@ -267,6 +232,11 @@ void QAndroidPlatformScreen::setSizeParameters(const QSize &physicalSize, const
}
}
+int QAndroidPlatformScreen::displayId() const
+{
+ return m_displayId;
+}
+
void QAndroidPlatformScreen::setRefreshRate(qreal refreshRate)
{
if (refreshRate == m_refreshRate)
@@ -282,7 +252,6 @@ void QAndroidPlatformScreen::setOrientation(Qt::ScreenOrientation orientation)
void QAndroidPlatformScreen::setAvailableGeometry(const QRect &rect)
{
- QMutexLocker lock(&m_surfaceMutex);
if (m_availableGeometry == rect)
return;
@@ -303,170 +272,31 @@ void QAndroidPlatformScreen::setAvailableGeometry(const QRect &rect)
}
}
}
-
- if (m_id != -1) {
- releaseSurface();
- QtAndroid::setSurfaceGeometry(m_id, rect);
- }
}
void QAndroidPlatformScreen::applicationStateChanged(Qt::ApplicationState state)
{
- for (QAndroidPlatformWindow *w : qAsConst(m_windowStack))
+ for (QAndroidPlatformWindow *w : std::as_const(m_windowStack))
w->applicationStateChanged(state);
-
- if (state <= Qt::ApplicationHidden) {
- lockSurface();
- QtAndroid::destroySurface(m_id);
- m_id = -1;
- releaseSurface();
- unlockSurface();
- }
}
-void QAndroidPlatformScreen::topWindowChanged(QWindow *w)
+void QAndroidPlatformScreen::topVisibleWindowChanged()
{
+ QWindow *w = topVisibleWindow();
+ QWindowSystemInterface::handleFocusWindowChanged(w, Qt::ActiveWindowFocusReason);
QtAndroidMenu::setActiveTopLevelWindow(w);
-
- if (w != 0) {
+ if (w && w->handle()) {
QAndroidPlatformWindow *platformWindow = static_cast<QAndroidPlatformWindow *>(w->handle());
- if (platformWindow != 0)
+ if (platformWindow)
platformWindow->updateSystemUiVisibility();
}
}
-int QAndroidPlatformScreen::rasterSurfaces()
-{
- return m_rasterSurfaces;
-}
-
-void QAndroidPlatformScreen::doRedraw(QImage* screenGrabImage)
-{
- PROFILE_SCOPE;
- if (!QtAndroid::activity())
- return;
-
- if (m_dirtyRect.isEmpty())
- return;
-
- // Stop if there are no visible raster windows. If we only have RasterGLSurface
- // windows that have renderToTexture children (i.e. they need the OpenGL path) then
- // we do not need an overlay surface.
- bool hasVisibleRasterWindows = false;
- for (QAndroidPlatformWindow *window : qAsConst(m_windowStack)) {
- if (window->window()->isVisible() && window->isRaster() && !qt_window_private(window->window())->compositing) {
- hasVisibleRasterWindows = true;
- break;
- }
- }
- if (!hasVisibleRasterWindows) {
- lockSurface();
- if (m_id != -1) {
- QtAndroid::destroySurface(m_id);
- releaseSurface();
- m_id = -1;
- }
- unlockSurface();
- return;
- }
- QMutexLocker lock(&m_surfaceMutex);
- if (m_id == -1 && m_rasterSurfaces) {
- m_id = QtAndroid::createSurface(this, geometry(), true, m_depth);
- AndroidDeadlockProtector protector;
- if (!protector.acquire())
- return;
- m_surfaceWaitCondition.wait(&m_surfaceMutex);
- }
-
- if (!m_nativeSurface)
- return;
-
- ANativeWindow_Buffer nativeWindowBuffer;
- ARect nativeWindowRect;
- nativeWindowRect.top = m_dirtyRect.top();
- nativeWindowRect.left = m_dirtyRect.left();
- nativeWindowRect.bottom = m_dirtyRect.bottom() + 1; // for some reason that I don't understand the QRect bottom needs to +1 to be the same with ARect bottom
- nativeWindowRect.right = m_dirtyRect.right() + 1; // same for the right
-
- int ret;
- if ((ret = ANativeWindow_lock(m_nativeSurface, &nativeWindowBuffer, &nativeWindowRect)) < 0) {
- qWarning() << "ANativeWindow_lock() failed! error=" << ret;
- return;
- }
-
- int bpp = 4;
- if (nativeWindowBuffer.format == WINDOW_FORMAT_RGB_565) {
- bpp = 2;
- m_pixelFormat = QImage::Format_RGB16;
- }
-
- QImage screenImage(reinterpret_cast<uchar *>(nativeWindowBuffer.bits)
- , nativeWindowBuffer.width, nativeWindowBuffer.height
- , nativeWindowBuffer.stride * bpp , m_pixelFormat);
-
- QPainter compositePainter(&screenImage);
- compositePainter.setCompositionMode(QPainter::CompositionMode_Source);
-
- QRegion visibleRegion(m_dirtyRect);
- for (QAndroidPlatformWindow *window : qAsConst(m_windowStack)) {
- if (!window->window()->isVisible()
- || qt_window_private(window->window())->compositing
- || !window->isRaster())
- continue;
-
- for (const QRect &rect : std::vector<QRect>(visibleRegion.begin(), visibleRegion.end())) {
- QRect targetRect = window->geometry();
- targetRect &= rect;
-
- if (targetRect.isNull())
- continue;
-
- visibleRegion -= targetRect;
- QRect windowRect = targetRect.translated(-window->geometry().topLeft());
- QAndroidPlatformBackingStore *backingStore = static_cast<QAndroidPlatformWindow *>(window)->backingStore();
- if (backingStore)
- compositePainter.drawImage(targetRect.topLeft(), backingStore->toImage(), windowRect);
- }
- }
-
- for (const QRect &rect : visibleRegion)
- compositePainter.fillRect(rect, QColor(Qt::transparent));
-
- ret = ANativeWindow_unlockAndPost(m_nativeSurface);
- if (ret >= 0)
- m_dirtyRect = QRect();
-
- if (screenGrabImage) {
- if (screenGrabImage->size() != screenImage.size()) {
- uchar* bytes = static_cast<uchar*>(malloc(screenImage.height() * screenImage.bytesPerLine()));
- *screenGrabImage = QImage(bytes, screenImage.width(), screenImage.height(),
- screenImage.bytesPerLine(), m_pixelFormat,
- [](void* ptr){ if (ptr) free (ptr);});
- }
- memcpy(screenGrabImage->bits(),
- screenImage.bits(),
- screenImage.bytesPerLine() * screenImage.height());
- }
- m_repaintOccurred = true;
-}
-
-QPixmap QAndroidPlatformScreen::doScreenShot(QRect grabRect)
-{
- if (!m_repaintOccurred)
- return QPixmap::fromImage(m_lastScreenshot.copy(grabRect));
- QRect tmp = m_dirtyRect;
- m_dirtyRect = geometry();
- doRedraw(&m_lastScreenshot);
- m_dirtyRect = tmp;
- m_repaintOccurred = false;
- return QPixmap::fromImage(m_lastScreenshot.copy(grabRect));
-}
-
static const int androidLogicalDpi = 72;
QDpi QAndroidPlatformScreen::logicalDpi() const
{
- qreal lDpi = QtAndroid::scaledDensity() * androidLogicalDpi;
+ qreal lDpi = QtAndroid::pixelDensity() * androidLogicalDpi;
return QDpi(lDpi, lDpi);
}
@@ -484,67 +314,4 @@ Qt::ScreenOrientation QAndroidPlatformScreen::nativeOrientation() const
{
return QAndroidPlatformIntegration::m_nativeOrientation;
}
-
-void QAndroidPlatformScreen::surfaceChanged(JNIEnv *env, jobject surface, int w, int h)
-{
- lockSurface();
- if (surface && w > 0 && h > 0) {
- releaseSurface();
- m_nativeSurface = ANativeWindow_fromSurface(env, surface);
- QMetaObject::invokeMethod(this, "setDirty", Qt::QueuedConnection, Q_ARG(QRect, QRect(0, 0, w, h)));
- } else {
- releaseSurface();
- }
- unlockSurface();
- m_surfaceWaitCondition.wakeOne();
-}
-
-void QAndroidPlatformScreen::releaseSurface()
-{
- if (m_nativeSurface) {
- ANativeWindow_release(m_nativeSurface);
- m_nativeSurface = 0;
- }
-}
-
-/*!
- This function is called when Qt needs to be able to grab the content of a window.
-
- Returns the content of the window specified with the WId handle within the boundaries of
- QRect(x, y, width, height).
-*/
-QPixmap QAndroidPlatformScreen::grabWindow(WId window, int x, int y, int width, int height) const
-{
- QRectF screenshotRect(x, y, width, height);
- QWindow* wnd = 0;
- if (window)
- {
- const auto windowList = qApp->allWindows();
- for (QWindow *w : windowList)
- if (w->winId() == window) {
- wnd = w;
- break;
- }
- }
- if (wnd) {
- const qreal factor = logicalDpi().first / androidLogicalDpi; //HighDPI factor;
- QRectF wndRect = wnd->geometry();
- if (wnd->parent())
- wndRect.moveTopLeft(wnd->parent()->mapToGlobal(wndRect.topLeft().toPoint()));
- if (!qFuzzyCompare(factor, 1))
- wndRect = QRectF(wndRect.left() * factor, wndRect.top() * factor,
- wndRect.width() * factor, wndRect.height() * factor);
-
- if (!screenshotRect.isEmpty()) {
- screenshotRect.moveTopLeft(wndRect.topLeft() + screenshotRect.topLeft());
- screenshotRect = screenshotRect.intersected(wndRect);
- } else {
- screenshotRect = wndRect;
- }
- } else {
- screenshotRect = screenshotRect.isValid() ? screenshotRect : geometry();
- }
- return const_cast<QAndroidPlatformScreen *>(this)->doScreenShot(screenshotRect.toRect());
-}
-
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/android/qandroidplatformscreen.h b/src/plugins/platforms/android/qandroidplatformscreen.h
index ef8371e72d..d850d0db09 100644
--- a/src/plugins/platforms/android/qandroidplatformscreen.h
+++ b/src/plugins/platforms/android/qandroidplatformscreen.h
@@ -5,26 +5,26 @@
#ifndef QANDROIDPLATFORMSCREEN_H
#define QANDROIDPLATFORMSCREEN_H
-#include "androidsurfaceclient.h"
-
#include <QList>
#include <QPainter>
#include <QTimer>
#include <QWaitCondition>
#include <QtCore/QJniObject>
#include <qpa/qplatformscreen.h>
-
-#include <android/native_window.h>
+#include <QtGui/qscreen_platform.h>
QT_BEGIN_NAMESPACE
class QAndroidPlatformWindow;
-class QAndroidPlatformScreen: public QObject, public QPlatformScreen, public AndroidSurfaceClient
+
+class QAndroidPlatformScreen : public QObject,
+ public QPlatformScreen,
+ public QNativeInterface::QAndroidScreen
{
Q_OBJECT
public:
- QAndroidPlatformScreen();
+ QAndroidPlatformScreen(const QJniObject &displayObject);
~QAndroidPlatformScreen();
QRect geometry() const override { return QRect(QPoint(), m_size); }
@@ -38,22 +38,17 @@ public:
int currentMode() const override { return m_currentMode; }
int preferredMode() const override { return m_currentMode; }
qreal refreshRate() const override { return m_refreshRate; }
-
- inline QWindow *topWindow() const;
+ inline QWindow *topVisibleWindow() const;
QWindow *topLevelAt(const QPoint & p) const override;
- // compositor api
void addWindow(QAndroidPlatformWindow *window);
void removeWindow(QAndroidPlatformWindow *window);
void raise(QAndroidPlatformWindow *window);
void lower(QAndroidPlatformWindow *window);
-
- void scheduleUpdate();
- void topWindowChanged(QWindow *w);
- int rasterSurfaces();
+ void topVisibleWindowChanged();
+ int displayId() const override;
public slots:
- void setDirty(const QRect &rect);
void setPhysicalSize(const QSize &size);
void setAvailableGeometry(const QRect &rect);
void setSize(const QSize &size);
@@ -63,13 +58,8 @@ public slots:
void setOrientation(Qt::ScreenOrientation orientation);
protected:
- bool event(QEvent *event) override;
-
typedef QList<QAndroidPlatformWindow *> WindowStackType;
WindowStackType m_windowStack;
- QRect m_dirtyRect;
- bool m_updatePending = false;
-
QRect m_availableGeometry;
int m_depth;
QImage::Format m_format;
@@ -78,31 +68,16 @@ protected:
QString m_name;
QList<Mode> m_modes;
int m_currentMode = 0;
+ int m_displayId = -1;
private:
QDpi logicalDpi() const override;
QDpi logicalBaseDpi() const override;
Qt::ScreenOrientation orientation() const override;
Qt::ScreenOrientation nativeOrientation() const override;
- QPixmap grabWindow(WId window, int x, int y, int width, int height) const override;
- void surfaceChanged(JNIEnv *env, jobject surface, int w, int h) override;
- void releaseSurface();
void applicationStateChanged(Qt::ApplicationState);
- QPixmap doScreenShot(QRect grabRect = QRect());
-
-private slots:
- void doRedraw(QImage *screenGrabImage = nullptr);
-
private:
- int m_id = -1;
- QAtomicInt m_rasterSurfaces = 0;
- ANativeWindow* m_nativeSurface = nullptr;
- QWaitCondition m_surfaceWaitCondition;
QSize m_size;
-
- QImage m_lastScreenshot;
- QImage::Format m_pixelFormat = QImage::Format_RGBA8888_Premultiplied;
- bool m_repaintOccurred = false;
};
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/android/qandroidplatformservices.cpp b/src/plugins/platforms/android/qandroidplatformservices.cpp
index 8f8f702011..f43e7cdd6a 100644
--- a/src/plugins/platforms/android/qandroidplatformservices.cpp
+++ b/src/plugins/platforms/android/qandroidplatformservices.cpp
@@ -35,6 +35,11 @@ QAndroidPlatformServices::QAndroidPlatformServices()
Qt::QueuedConnection);
}
+Q_DECLARE_JNI_CLASS(UriType, "android/net/Uri")
+Q_DECLARE_JNI_CLASS(FileType, "java/io/File")
+Q_DECLARE_JNI_CLASS(File, "java/io/File")
+Q_DECLARE_JNI_CLASS(FileProvider, "androidx/core/content/FileProvider");
+
bool QAndroidPlatformServices::openUrl(const QUrl &theUrl)
{
QString mime;
@@ -47,20 +52,44 @@ bool QAndroidPlatformServices::openUrl(const QUrl &theUrl)
// if the file is local, we need to pass the MIME type, otherwise Android
// does not start an Intent to view this file
const auto fileScheme = "file"_L1;
- if ((url.scheme().isEmpty() || url.scheme() == fileScheme) && QFile::exists(url.path())) {
- // a real URL including the scheme is needed, else the Intent can not be started
+
+ // a real URL including the scheme is needed, else the Intent can not be started
+ if (url.scheme().isEmpty())
url.setScheme(fileScheme);
- QMimeDatabase mimeDb;
- mime = mimeDb.mimeTypeForUrl(url).name();
- }
+
+ if (url.scheme() == fileScheme)
+ mime = QMimeDatabase().mimeTypeForUrl(url).name();
+
+ const QJniObject mimeString = QJniObject::fromString(mime);
using namespace QNativeInterface;
- QJniObject urlString = QJniObject::fromString(url.toString());
- QJniObject mimeString = QJniObject::fromString(mime);
- return QJniObject::callStaticMethod<jboolean>(
- QtAndroid::applicationClass(), "openURL",
- "(Landroid/content/Context;Ljava/lang/String;Ljava/lang/String;)Z",
- QAndroidApplication::context(), urlString.object(), mimeString.object());
+
+ auto openUrl = [mimeString](const QJniObject &url) {
+ return QJniObject::callStaticMethod<jboolean>(QtAndroid::applicationClass(), "openURL",
+ QAndroidApplication::context(), url.object<jstring>(), mimeString.object<jstring>());
+ };
+
+ if (url.scheme() != fileScheme || QNativeInterface::QAndroidApplication::sdkVersion() < 24)
+ return openUrl(QJniObject::fromString(url.toString()));
+
+ // Use FileProvider for file scheme with sdk >= 24
+ const QJniObject context = QAndroidApplication::context();
+ const auto appId = context.callMethod<jstring>("getPackageName").toString();
+ const auto providerName = QJniObject::fromString(appId + ".qtprovider"_L1);
+
+ const auto urlPath = QJniObject::fromString(url.path());
+ const auto urlFile = QJniObject(QtJniTypes::Traits<QtJniTypes::File>::className(),
+ urlPath.object<jstring>());
+
+ const auto fileProviderUri = QJniObject::callStaticMethod<QtJniTypes::UriType>(
+ QtJniTypes::Traits<QtJniTypes::FileProvider>::className(), "getUriForFile",
+ QAndroidApplication::context(), providerName.object<jstring>(),
+ urlFile.object<QtJniTypes::FileType>());
+
+ if (fileProviderUri.isValid())
+ return openUrl(fileProviderUri.callMethod<jstring>("toString"));
+
+ return false;
}
bool QAndroidPlatformServices::openDocument(const QUrl &url)
diff --git a/src/plugins/platforms/android/qandroidplatformtheme.cpp b/src/plugins/platforms/android/qandroidplatformtheme.cpp
index c5b9ba9dee..7b9072df69 100644
--- a/src/plugins/platforms/android/qandroidplatformtheme.cpp
+++ b/src/plugins/platforms/android/qandroidplatformtheme.cpp
@@ -4,6 +4,7 @@
#include "androidjnimain.h"
#include "androidjnimenu.h"
#include "qandroidplatformtheme.h"
+#include "qandroidplatformiconengine.h"
#include "qandroidplatformmenubar.h"
#include "qandroidplatformmenu.h"
#include "qandroidplatformmenuitem.h"
@@ -22,6 +23,8 @@
QT_BEGIN_NAMESPACE
+Q_LOGGING_CATEGORY(lcQpaMenus, "qt.qpa.menus")
+
using namespace Qt::StringLiterals;
namespace {
@@ -158,14 +161,10 @@ QJsonObject AndroidStyle::loadStyleData()
if (!stylePath.isEmpty() && !stylePath.endsWith(slashChar))
stylePath += slashChar;
- Q_ASSERT(!stylePath.isEmpty());
-
- QString androidTheme = QLatin1StringView(qgetenv("QT_ANDROID_THEME"));
- if (!androidTheme.isEmpty() && !androidTheme.endsWith(slashChar))
- androidTheme += slashChar;
+ if (QAndroidPlatformIntegration::colorScheme() == Qt::ColorScheme::Dark)
+ stylePath += "darkUiMode/"_L1;
- if (!androidTheme.isEmpty() && QFileInfo::exists(stylePath + androidTheme + "style.json"_L1))
- stylePath += androidTheme;
+ Q_ASSERT(!stylePath.isEmpty());
QFile f(stylePath + "style.json"_L1);
if (!f.open(QIODevice::ReadOnly))
@@ -185,13 +184,22 @@ QJsonObject AndroidStyle::loadStyleData()
return document.object();
}
-static std::shared_ptr<AndroidStyle> loadAndroidStyle(QPalette *defaultPalette)
+static void loadAndroidStyle(QPalette *defaultPalette, std::shared_ptr<AndroidStyle> &style)
{
double pixelDensity = QHighDpiScaling::isActive() ? QtAndroid::pixelDensity() : 1.0;
- std::shared_ptr<AndroidStyle> style = std::make_shared<AndroidStyle>();
+ if (style) {
+ style->m_standardPalette = QPalette();
+ style->m_palettes.clear();
+ style->m_fonts.clear();
+ style->m_QWidgetsFonts.clear();
+ } else {
+ style = std::make_shared<AndroidStyle>();
+ }
+
style->m_styleData = AndroidStyle::loadStyleData();
+
if (style->m_styleData.isEmpty())
- return std::shared_ptr<AndroidStyle>();
+ return;
{
QFont font("Droid Sans Mono"_L1, 14.0 * 100 / 72);
@@ -293,11 +301,43 @@ static std::shared_ptr<AndroidStyle> loadAndroidStyle(QPalette *defaultPalette)
// Extract palette information
}
}
- return style;
+}
+
+QAndroidPlatformTheme *QAndroidPlatformTheme::m_instance = nullptr;
+
+QAndroidPlatformTheme *QAndroidPlatformTheme::instance(
+ QAndroidPlatformNativeInterface *androidPlatformNativeInterface)
+{
+ if (androidPlatformNativeInterface && !m_instance) {
+ m_instance = new QAndroidPlatformTheme(androidPlatformNativeInterface);
+ }
+ return m_instance;
}
QAndroidPlatformTheme::QAndroidPlatformTheme(QAndroidPlatformNativeInterface *androidPlatformNativeInterface)
{
+ updateStyle();
+
+ androidPlatformNativeInterface->m_androidStyle = m_androidStyleData;
+
+ // default in case the style has not set a font
+ m_systemFont = QFont("Roboto"_L1, 14.0 * 100 / 72); // keep default size the same after changing from 100 dpi to 72 dpi
+}
+
+QAndroidPlatformTheme::~QAndroidPlatformTheme()
+{
+ m_instance = nullptr;
+}
+
+void QAndroidPlatformTheme::updateColorScheme()
+{
+ updateStyle();
+ QWindowSystemInterface::handleThemeChange();
+}
+
+void QAndroidPlatformTheme::updateStyle()
+{
+ QColor windowText = Qt::black;
QColor background(229, 229, 229);
QColor light = background.lighter(150);
QColor mid(background.darker(130));
@@ -314,7 +354,27 @@ QAndroidPlatformTheme::QAndroidPlatformTheme(QAndroidPlatformNativeInterface *an
QColor highlight(148, 210, 231);
QColor disabledShadow = shadow.lighter(150);
- m_defaultPalette = QPalette(Qt::black,background,light,dark,mid,text,base);
+ if (colorScheme() == Qt::ColorScheme::Dark) {
+ // Colors were prepared based on Theme.DeviceDefault.DayNight
+ windowText = QColor(250, 250, 250);
+ background = QColor(48, 48, 48);
+ light = background.darker(150);
+ mid = background.lighter(130);
+ midLight = mid.darker(110);
+ base = background;
+ disabledBase = background;
+ dark = background.darker(150);
+ darkDisabled = dark.darker(110);
+ text = QColor(250, 250, 250);
+ highlightedText = QColor(250, 250, 250);
+ disabledText = QColor(96, 96, 96);
+ button = QColor(48, 48, 48);
+ shadow = QColor(32, 32, 32);
+ highlight = QColor(102, 178, 204);
+ disabledShadow = shadow.darker(150);
+ }
+
+ m_defaultPalette = QPalette(windowText,background,light,dark,mid,text,base);
m_defaultPalette.setBrush(QPalette::Midlight, midLight);
m_defaultPalette.setBrush(QPalette::Button, button);
m_defaultPalette.setBrush(QPalette::Shadow, shadow);
@@ -330,34 +390,42 @@ QAndroidPlatformTheme::QAndroidPlatformTheme(QAndroidPlatformNativeInterface *an
m_defaultPalette.setBrush(QPalette::Active, QPalette::Highlight, highlight);
m_defaultPalette.setBrush(QPalette::Inactive, QPalette::Highlight, highlight);
m_defaultPalette.setBrush(QPalette::Disabled, QPalette::Highlight, highlight.lighter(150));
- m_androidStyleData = loadAndroidStyle(&m_defaultPalette);
- QGuiApplication::setPalette(m_defaultPalette);
- androidPlatformNativeInterface->m_androidStyle = m_androidStyleData;
- // default in case the style has not set a font
- m_systemFont = QFont("Roboto"_L1, 14.0 * 100 / 72); // keep default size the same after changing from 100 dpi to 72 dpi
+ loadAndroidStyle(&m_defaultPalette, m_androidStyleData);
}
QPlatformMenuBar *QAndroidPlatformTheme::createPlatformMenuBar() const
{
- return new QAndroidPlatformMenuBar;
+ auto *menuBar = new QAndroidPlatformMenuBar;
+ qCDebug(lcQpaMenus) << "Created" << menuBar;
+ return menuBar;
}
QPlatformMenu *QAndroidPlatformTheme::createPlatformMenu() const
{
- return new QAndroidPlatformMenu;
+ auto *menu = new QAndroidPlatformMenu;
+ qCDebug(lcQpaMenus) << "Created" << menu;
+ return menu;
}
QPlatformMenuItem *QAndroidPlatformTheme::createPlatformMenuItem() const
{
- return new QAndroidPlatformMenuItem;
+ auto *menuItem = new QAndroidPlatformMenuItem;
+ qCDebug(lcQpaMenus) << "Created" << menuItem;
+ return menuItem;
}
void QAndroidPlatformTheme::showPlatformMenuBar()
{
+ qCDebug(lcQpaMenus) << "Showing platform menu bar";
QtAndroidMenu::openOptionsMenu();
}
+Qt::ColorScheme QAndroidPlatformTheme::colorScheme() const
+{
+ return QAndroidPlatformIntegration::colorScheme();
+}
+
static inline int paletteType(QPlatformTheme::Palette type)
{
switch (type) {
@@ -423,6 +491,11 @@ const QFont *QAndroidPlatformTheme::font(Font type) const
return 0;
}
+QIconEngine *QAndroidPlatformTheme::createIconEngine(const QString &iconName) const
+{
+ return new QAndroidPlatformIconEngine(iconName);
+}
+
QVariant QAndroidPlatformTheme::themeHint(ThemeHint hint) const
{
switch (hint) {
diff --git a/src/plugins/platforms/android/qandroidplatformtheme.h b/src/plugins/platforms/android/qandroidplatformtheme.h
index ec39ed4794..ce3d6d5f73 100644
--- a/src/plugins/platforms/android/qandroidplatformtheme.h
+++ b/src/plugins/platforms/android/qandroidplatformtheme.h
@@ -9,13 +9,15 @@
#include <QtGui/qpalette.h>
#include <QtCore/qhash.h>
#include <QtCore/qbytearray.h>
-
+#include <QtCore/qloggingcategory.h>
#include <QJsonObject>
#include <memory>
QT_BEGIN_NAMESPACE
+Q_DECLARE_LOGGING_CATEGORY(lcQpaMenus)
+
struct AndroidStyle
{
static QJsonObject loadStyleData();
@@ -30,20 +32,28 @@ class QAndroidPlatformNativeInterface;
class QAndroidPlatformTheme: public QPlatformTheme
{
public:
- QAndroidPlatformTheme(QAndroidPlatformNativeInterface * androidPlatformNativeInterface);
+ ~QAndroidPlatformTheme();
+ void updateColorScheme();
+ void updateStyle();
QPlatformMenuBar *createPlatformMenuBar() const override;
QPlatformMenu *createPlatformMenu() const override;
QPlatformMenuItem *createPlatformMenuItem() const override;
void showPlatformMenuBar() override;
+ Qt::ColorScheme colorScheme() const override;
const QPalette *palette(Palette type = SystemPalette) const override;
const QFont *font(Font type = SystemFont) const override;
+ QIconEngine *createIconEngine(const QString &iconName) const override;
QVariant themeHint(ThemeHint hint) const override;
QString standardButtonText(int button) const override;
bool usePlatformNativeDialog(DialogType type) const override;
QPlatformDialogHelper *createPlatformDialogHelper(DialogType type) const override;
+ static QAndroidPlatformTheme *instance(
+ QAndroidPlatformNativeInterface * androidPlatformNativeInterface = nullptr);
private:
+ QAndroidPlatformTheme(QAndroidPlatformNativeInterface * androidPlatformNativeInterface);
+ static QAndroidPlatformTheme * m_instance;
std::shared_ptr<AndroidStyle> m_androidStyleData;
QPalette m_defaultPalette;
QFont m_systemFont;
diff --git a/src/plugins/platforms/android/qandroidplatformvulkanwindow.cpp b/src/plugins/platforms/android/qandroidplatformvulkanwindow.cpp
index cb6bb6d390..4bf4f44fa1 100644
--- a/src/plugins/platforms/android/qandroidplatformvulkanwindow.cpp
+++ b/src/plugins/platforms/android/qandroidplatformvulkanwindow.cpp
@@ -18,7 +18,6 @@ QT_BEGIN_NAMESPACE
QAndroidPlatformVulkanWindow::QAndroidPlatformVulkanWindow(QWindow *window)
: QAndroidPlatformWindow(window),
- m_nativeSurfaceId(-1),
m_nativeWindow(nullptr),
m_vkSurface(0),
m_createVkSurface(nullptr),
@@ -29,11 +28,7 @@ QAndroidPlatformVulkanWindow::QAndroidPlatformVulkanWindow(QWindow *window)
QAndroidPlatformVulkanWindow::~QAndroidPlatformVulkanWindow()
{
m_surfaceWaitCondition.wakeOne();
- lockSurface();
- if (m_nativeSurfaceId != -1)
- QtAndroid::destroySurface(m_nativeSurfaceId);
- clearSurface();
- unlockSurface();
+ destroyAndClearSurface();
}
void QAndroidPlatformVulkanWindow::setGeometry(const QRect &rect)
@@ -44,8 +39,8 @@ void QAndroidPlatformVulkanWindow::setGeometry(const QRect &rect)
m_oldGeometry = geometry();
QAndroidPlatformWindow::setGeometry(rect);
- if (m_nativeSurfaceId != -1)
- QtAndroid::setSurfaceGeometry(m_nativeSurfaceId, rect);
+ if (m_surfaceCreated)
+ setNativeGeometry(rect);
QRect availableGeometry = screen()->availableGeometry();
if (rect.width() > 0
@@ -54,22 +49,13 @@ void QAndroidPlatformVulkanWindow::setGeometry(const QRect &rect)
&& availableGeometry.height() > 0) {
QWindowSystemInterface::handleExposeEvent(window(), QRect(QPoint(0, 0), rect.size()));
}
-
- if (rect.topLeft() != m_oldGeometry.topLeft())
- repaint(QRegion(rect));
}
void QAndroidPlatformVulkanWindow::applicationStateChanged(Qt::ApplicationState state)
{
QAndroidPlatformWindow::applicationStateChanged(state);
if (state <= Qt::ApplicationHidden) {
- lockSurface();
- if (m_nativeSurfaceId != -1) {
- QtAndroid::destroySurface(m_nativeSurfaceId);
- m_nativeSurfaceId = -1;
- }
- clearSurface();
- unlockSurface();
+ destroyAndClearSurface();
}
}
@@ -91,27 +77,12 @@ void QAndroidPlatformVulkanWindow::clearSurface()
}
}
-void QAndroidPlatformVulkanWindow::sendExpose()
-{
- QRect availableGeometry = screen()->availableGeometry();
- if (geometry().width() > 0 && geometry().height() > 0 && availableGeometry.width() > 0 && availableGeometry.height() > 0)
- QWindowSystemInterface::handleExposeEvent(window(), QRegion(QRect(QPoint(), geometry().size())));
-}
-
-void QAndroidPlatformVulkanWindow::surfaceChanged(JNIEnv *jniEnv, jobject surface, int w, int h)
+void QAndroidPlatformVulkanWindow::destroyAndClearSurface()
{
- Q_UNUSED(jniEnv);
- Q_UNUSED(w);
- Q_UNUSED(h);
-
lockSurface();
- m_androidSurfaceObject = surface;
- if (surface)
- m_surfaceWaitCondition.wakeOne();
+ destroySurface();
+ clearSurface();
unlockSurface();
-
- if (surface)
- sendExpose();
}
VkSurfaceKHR *QAndroidPlatformVulkanWindow::vkSurface()
@@ -124,16 +95,15 @@ VkSurfaceKHR *QAndroidPlatformVulkanWindow::vkSurface()
clearSurface();
QMutexLocker lock(&m_surfaceMutex);
- if (m_nativeSurfaceId == -1) {
+ if (!m_surfaceCreated) {
AndroidDeadlockProtector protector;
if (!protector.acquire())
return &m_vkSurface;
- const bool windowStaysOnTop = bool(window()->flags() & Qt::WindowStaysOnTopHint);
- m_nativeSurfaceId = QtAndroid::createSurface(this, geometry(), windowStaysOnTop, 32);
+ createSurface();
m_surfaceWaitCondition.wait(&m_surfaceMutex);
}
- if (m_nativeSurfaceId == -1 || !m_androidSurfaceObject.isValid())
+ if (!m_surfaceCreated || !m_androidSurfaceObject.isValid())
return &m_vkSurface;
QJniEnvironment env;
diff --git a/src/plugins/platforms/android/qandroidplatformvulkanwindow.h b/src/plugins/platforms/android/qandroidplatformvulkanwindow.h
index 14d5b7fc0e..fa959239d1 100644
--- a/src/plugins/platforms/android/qandroidplatformvulkanwindow.h
+++ b/src/plugins/platforms/android/qandroidplatformvulkanwindow.h
@@ -10,7 +10,6 @@
#define VK_USE_PLATFORM_ANDROID_KHR
-#include "androidsurfaceclient.h"
#include "qandroidplatformvulkaninstance.h"
#include "qandroidplatformwindow.h"
@@ -20,7 +19,7 @@
QT_BEGIN_NAMESPACE
-class QAndroidPlatformVulkanWindow : public QAndroidPlatformWindow, public AndroidSurfaceClient
+class QAndroidPlatformVulkanWindow : public QAndroidPlatformWindow
{
public:
explicit QAndroidPlatformVulkanWindow(QWindow *window);
@@ -32,17 +31,11 @@ public:
VkSurfaceKHR *vkSurface();
-protected:
- void surfaceChanged(JNIEnv *jniEnv, jobject surface, int w, int h) override;
-
private:
- void sendExpose();
void clearSurface();
+ void destroyAndClearSurface();
- int m_nativeSurfaceId;
ANativeWindow *m_nativeWindow;
- QJniObject m_androidSurfaceObject;
- QWaitCondition m_surfaceWaitCondition;
QSurfaceFormat m_format;
QRect m_oldGeometry;
VkSurfaceKHR m_vkSurface;
diff --git a/src/plugins/platforms/android/qandroidplatformwindow.cpp b/src/plugins/platforms/android/qandroidplatformwindow.cpp
index 76c034db50..e47281664d 100644
--- a/src/plugins/platforms/android/qandroidplatformwindow.cpp
+++ b/src/plugins/platforms/android/qandroidplatformwindow.cpp
@@ -14,40 +14,86 @@
QT_BEGIN_NAMESPACE
-static QBasicAtomicInt winIdGenerator = Q_BASIC_ATOMIC_INITIALIZER(0);
+Q_LOGGING_CATEGORY(lcQpaWindow, "qt.qpa.window")
QAndroidPlatformWindow::QAndroidPlatformWindow(QWindow *window)
- : QPlatformWindow(window)
+ : QPlatformWindow(window), m_nativeQtWindow(nullptr),
+ m_surfaceContainerType(SurfaceContainer::TextureView), m_nativeParentQtWindow(nullptr),
+ m_androidSurfaceObject(nullptr)
{
m_windowFlags = Qt::Widget;
m_windowState = Qt::WindowNoState;
- m_windowId = winIdGenerator.fetchAndAddRelaxed(1) + 1;
+ // the surfaceType is overwritten in QAndroidPlatformOpenGLWindow ctor so let's save
+ // the fact that it's a raster window for now
+ m_isRaster = window->surfaceType() == QSurface::RasterSurface;
setWindowState(window->windowStates());
// the following is in relation to the virtual geometry
const bool forceMaximize = m_windowState & (Qt::WindowMaximized | Qt::WindowFullScreen);
- const QRect requestedNativeGeometry =
- forceMaximize ? QRect() : QHighDpi::toNativePixels(window->geometry(), window);
- const QRect availableDeviceIndependentGeometry = (window->parent())
- ? window->parent()->geometry()
- : QHighDpi::fromNativePixels(platformScreen()->availableGeometry(), window);
+ const QRect nativeScreenGeometry = platformScreen()->availableGeometry();
+ if (forceMaximize) {
+ setGeometry(nativeScreenGeometry);
+ } else {
+ const QRect requestedNativeGeometry = QHighDpi::toNativePixels(window->geometry(), window);
+ const QRect availableDeviceIndependentGeometry = (window->parent())
+ ? window->parent()->geometry()
+ : QHighDpi::fromNativePixels(nativeScreenGeometry, window);
+ // initialGeometry returns in native pixels
+ const QRect finalNativeGeometry = QPlatformWindow::initialGeometry(
+ window, requestedNativeGeometry, availableDeviceIndependentGeometry.width(),
+ availableDeviceIndependentGeometry.height());
+ if (requestedNativeGeometry != finalNativeGeometry)
+ setGeometry(finalNativeGeometry);
+ }
+
+ if (isEmbeddingContainer())
+ return;
+
+ if (parent()) {
+ QAndroidPlatformWindow *androidParent = static_cast<QAndroidPlatformWindow*>(parent());
+ if (!androidParent->isEmbeddingContainer())
+ m_nativeParentQtWindow = androidParent->nativeWindow();
+ }
+
+ m_nativeQtWindow = QJniObject::construct<QtJniTypes::QtWindow>(
+ QNativeInterface::QAndroidApplication::context(),
+ m_nativeParentQtWindow,
+ QtAndroid::qtInputDelegate());
+ m_nativeViewId = m_nativeQtWindow.callMethod<jint>("getId");
- // initialGeometry returns in native pixels
- const QRect finalNativeGeometry = QPlatformWindow::initialGeometry(
- window, requestedNativeGeometry, availableDeviceIndependentGeometry.width(),
- availableDeviceIndependentGeometry.height());
+ if (window->isTopLevel())
+ platformScreen()->addWindow(this);
- if (requestedNativeGeometry != finalNativeGeometry)
- setGeometry(finalNativeGeometry);
+ // TODO should handle case where this changes at runtime -> need to change existing window
+ // into TextureView (or perhaps not, if the parent window would be SurfaceView, as long as
+ // onTop was false it would stay below the children)
+ if (platformScreen()->windows().size() <= 1)
+ m_surfaceContainerType = SurfaceContainer::SurfaceView;
}
+QAndroidPlatformWindow::~QAndroidPlatformWindow()
+{
+ if (window()->isTopLevel())
+ platformScreen()->removeWindow(this);
+}
+
+
void QAndroidPlatformWindow::lower()
{
+ if (m_nativeParentQtWindow.isValid()) {
+ m_nativeParentQtWindow.callMethod<void>("bringChildToBack", nativeViewId());
+ return;
+ }
platformScreen()->lower(this);
}
void QAndroidPlatformWindow::raise()
{
+ if (m_nativeParentQtWindow.isValid()) {
+ m_nativeParentQtWindow.callMethod<void>("bringChildToFront", nativeViewId());
+ QWindowSystemInterface::handleFocusWindowChanged(window(), Qt::ActiveWindowFocusReason);
+ return;
+ }
updateSystemUiVisibility();
platformScreen()->raise(this);
}
@@ -71,23 +117,25 @@ void QAndroidPlatformWindow::setGeometry(const QRect &rect)
void QAndroidPlatformWindow::setVisible(bool visible)
{
- if (visible)
- updateSystemUiVisibility();
+ if (isEmbeddingContainer())
+ return;
+ m_nativeQtWindow.callMethod<void>("setVisible", visible);
if (visible) {
- if ((m_windowState & Qt::WindowFullScreen)
- || ((m_windowState & Qt::WindowMaximized) && (window()->flags() & Qt::MaximizeUsingFullscreenGeometryHint))) {
- setGeometry(platformScreen()->geometry());
- } else if (m_windowState & Qt::WindowMaximized) {
- setGeometry(platformScreen()->availableGeometry());
+ if (window()->isTopLevel()) {
+ updateSystemUiVisibility();
+ if ((m_windowState & Qt::WindowFullScreen)
+ || ((m_windowState & Qt::WindowMaximized) && (window()->flags() & Qt::MaximizeUsingFullscreenGeometryHint))) {
+ setGeometry(platformScreen()->geometry());
+ } else if (m_windowState & Qt::WindowMaximized) {
+ setGeometry(platformScreen()->availableGeometry());
+ }
+ requestActivateWindow();
}
+ } else if (window()->isTopLevel() && window() == qGuiApp->focusWindow()) {
+ platformScreen()->topVisibleWindowChanged();
}
- if (visible)
- platformScreen()->addWindow(this);
- else
- platformScreen()->removeWindow(this);
-
QRect availableGeometry = screen()->availableGeometry();
if (geometry().width() > 0 && geometry().height() > 0 && availableGeometry.width() > 0 && availableGeometry.height() > 0)
QPlatformWindow::setVisible(visible);
@@ -120,7 +168,30 @@ Qt::WindowFlags QAndroidPlatformWindow::windowFlags() const
void QAndroidPlatformWindow::setParent(const QPlatformWindow *window)
{
- Q_UNUSED(window);
+ using namespace QtJniTypes;
+
+ if (window) {
+ auto androidWindow = static_cast<const QAndroidPlatformWindow*>(window);
+ if (androidWindow->isEmbeddingContainer())
+ return;
+ // If we were a top level window, remove from screen
+ if (!m_nativeParentQtWindow.isValid())
+ platformScreen()->removeWindow(this);
+
+ const QtWindow parentWindow = androidWindow->nativeWindow();
+ // If this was a child window of another window, the java method takes care of that
+ m_nativeQtWindow.callMethod<void, QtWindow>("setParent", parentWindow.object());
+ m_nativeParentQtWindow = parentWindow;
+ } else if (QtAndroid::isQtApplication()) {
+ m_nativeQtWindow.callMethod<void, QtWindow>("setParent", nullptr);
+ m_nativeParentQtWindow = QJniObject();
+ platformScreen()->addWindow(this);
+ }
+}
+
+WId QAndroidPlatformWindow::winId() const
+{
+ return m_nativeQtWindow.isValid() ? reinterpret_cast<WId>(m_nativeQtWindow.object()) : 0L;
}
QAndroidPlatformScreen *QAndroidPlatformWindow::platformScreen() const
@@ -135,7 +206,9 @@ void QAndroidPlatformWindow::propagateSizeHints()
void QAndroidPlatformWindow::requestActivateWindow()
{
- platformScreen()->topWindowChanged(window());
+ // raise() will handle differences between top level and child windows, and requesting focus
+ if (!blockedByModal())
+ raise();
}
void QAndroidPlatformWindow::updateSystemUiVisibility()
@@ -169,4 +242,134 @@ void QAndroidPlatformWindow::applicationStateChanged(Qt::ApplicationState)
QWindowSystemInterface::flushWindowSystemEvents();
}
+void QAndroidPlatformWindow::createSurface()
+{
+ const QRect rect = geometry();
+ jint x = 0, y = 0, w = -1, h = -1;
+ if (!rect.isNull()) {
+ x = rect.x();
+ y = rect.y();
+ w = std::max(rect.width(), 1);
+ h = std::max(rect.height(), 1);
+ }
+
+ const bool windowStaysOnTop = bool(window()->flags() & Qt::WindowStaysOnTopHint);
+ const bool isOpaque = !format().hasAlpha() && qFuzzyCompare(window()->opacity(), 1.0);
+
+ m_nativeQtWindow.callMethod<void>("createSurface", windowStaysOnTop, x, y, w, h, 32, isOpaque,
+ m_surfaceContainerType);
+ m_surfaceCreated = true;
+}
+
+void QAndroidPlatformWindow::destroySurface()
+{
+ if (m_surfaceCreated) {
+ m_nativeQtWindow.callMethod<void>("destroySurface");
+ m_surfaceCreated = false;
+ }
+}
+
+void QAndroidPlatformWindow::setNativeGeometry(const QRect &geometry)
+{
+ if (!m_surfaceCreated)
+ return;
+
+ jint x = 0;
+ jint y = 0;
+ jint w = -1;
+ jint h = -1;
+ if (!geometry.isNull()) {
+ x = geometry.x();
+ y = geometry.y();
+ w = geometry.width();
+ h = geometry.height();
+ }
+ m_nativeQtWindow.callMethod<void>("setGeometry", x, y, w, h);
+}
+
+void QAndroidPlatformWindow::onSurfaceChanged(QtJniTypes::Surface surface)
+{
+ lockSurface();
+ m_androidSurfaceObject = surface;
+ if (m_androidSurfaceObject.isValid()) // wait until we have a valid surface to draw into
+ m_surfaceWaitCondition.wakeOne();
+ unlockSurface();
+
+ if (m_androidSurfaceObject.isValid()) {
+ // repaint the window, when we have a valid surface
+ sendExpose();
+ }
+}
+
+void QAndroidPlatformWindow::sendExpose() const
+{
+ QRect availableGeometry = screen()->availableGeometry();
+ if (!geometry().isNull() && !availableGeometry.isNull()) {
+ QWindowSystemInterface::handleExposeEvent(window(),
+ QRegion(QRect(QPoint(), geometry().size())));
+ }
+}
+
+bool QAndroidPlatformWindow::blockedByModal() const
+{
+ QWindow *modalWindow = QGuiApplication::modalWindow();
+ return modalWindow && modalWindow != window();
+}
+
+bool QAndroidPlatformWindow::isEmbeddingContainer() const
+{
+ // Returns true if the window is a wrapper for a foreign window solely to allow embedding Qt
+ // into a native Android app, in which case we should not try to control it more than we "need" to
+ return !QtAndroid::isQtApplication() && window()->isTopLevel();
+}
+
+void QAndroidPlatformWindow::setSurface(JNIEnv *env, jobject object, jint windowId,
+ QtJniTypes::Surface surface)
+{
+ Q_UNUSED(env)
+ Q_UNUSED(object)
+
+ if (!qGuiApp)
+ return;
+
+ const QList<QWindow*> windows = qGuiApp->allWindows();
+ for (QWindow * window : windows) {
+ if (!window->handle())
+ continue;
+ QAndroidPlatformWindow *platformWindow =
+ static_cast<QAndroidPlatformWindow *>(window->handle());
+ if (platformWindow->nativeViewId() == windowId)
+ platformWindow->onSurfaceChanged(surface);
+ }
+}
+
+void QAndroidPlatformWindow::windowFocusChanged(JNIEnv *env, jobject object,
+ jboolean focus, jint windowId)
+{
+ Q_UNUSED(env)
+ Q_UNUSED(object)
+ QWindow* window = QtAndroid::windowFromId(windowId);
+ Q_ASSERT_X(window, "QAndroidPlatformWindow", "windowFocusChanged event window should exist");
+ if (focus) {
+ QWindowSystemInterface::handleFocusWindowChanged(window);
+ } else if (!focus && window == qGuiApp->focusWindow()) {
+ // Clear focus if current window has lost focus
+ QWindowSystemInterface::handleFocusWindowChanged(nullptr);
+ }
+}
+
+bool QAndroidPlatformWindow::registerNatives(QJniEnvironment &env)
+{
+ if (!env.registerNativeMethods(QtJniTypes::Traits<QtJniTypes::QtWindow>::className(),
+ {
+ Q_JNI_NATIVE_SCOPED_METHOD(setSurface, QAndroidPlatformWindow),
+ Q_JNI_NATIVE_SCOPED_METHOD(windowFocusChanged, QAndroidPlatformWindow)
+ })) {
+ qCCritical(lcQpaWindow) << "RegisterNatives failed for"
+ << QtJniTypes::Traits<QtJniTypes::QtWindow>::className();
+ return false;
+ }
+ return true;
+}
+
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/android/qandroidplatformwindow.h b/src/plugins/platforms/android/qandroidplatformwindow.h
index 6fccc2e7fe..3f1e8ac992 100644
--- a/src/plugins/platforms/android/qandroidplatformwindow.h
+++ b/src/plugins/platforms/android/qandroidplatformwindow.h
@@ -7,17 +7,32 @@
#include <qobject.h>
#include <qrect.h>
#include <qpa/qplatformwindow.h>
+#include <QtCore/qjnienvironment.h>
+#include <QtCore/qjniobject.h>
+#include <QtCore/qjnitypes.h>
+#include <QtCore/qloggingcategory.h>
+#include <QtCore/qmutex.h>
+#include <QtCore/qwaitcondition.h>
+#include <jni.h>
QT_BEGIN_NAMESPACE
+Q_DECLARE_LOGGING_CATEGORY(lcQpaWindow)
+Q_DECLARE_JNI_CLASS(QtWindow, "org/qtproject/qt/android/QtWindow")
+Q_DECLARE_JNI_CLASS(Surface, "android/view/Surface")
+
class QAndroidPlatformScreen;
-class QAndroidPlatformBackingStore;
class QAndroidPlatformWindow: public QPlatformWindow
{
public:
- explicit QAndroidPlatformWindow(QWindow *window);
+ enum class SurfaceContainer {
+ SurfaceView,
+ TextureView
+ };
+ explicit QAndroidPlatformWindow(QWindow *window);
+ ~QAndroidPlatformWindow();
void lower() override;
void raise() override;
@@ -27,7 +42,8 @@ public:
void setWindowFlags(Qt::WindowFlags flags) override;
Qt::WindowFlags windowFlags() const;
void setParent(const QPlatformWindow *window) override;
- WId winId() const override { return m_windowId; }
+
+ WId winId() const override;
bool setMouseGrabEnabled(bool grab) override { Q_UNUSED(grab); return false; }
bool setKeyboardGrabEnabled(bool grab) override { Q_UNUSED(grab); return false; }
@@ -39,32 +55,46 @@ public:
void propagateSizeHints() override;
void requestActivateWindow() override;
void updateSystemUiVisibility();
- inline bool isRaster() const {
- if (isForeignWindow())
- return false;
-
- return window()->surfaceType() == QSurface::RasterSurface
- || window()->surfaceType() == QSurface::RasterGLSurface;
- }
+ inline bool isRaster() const { return m_isRaster; }
bool isExposed() const override;
+ QtJniTypes::QtWindow nativeWindow() const { return m_nativeQtWindow; }
virtual void applicationStateChanged(Qt::ApplicationState);
+ int nativeViewId() const { return m_nativeViewId; }
- void setBackingStore(QAndroidPlatformBackingStore *store) { m_backingStore = store; }
- QAndroidPlatformBackingStore *backingStore() const { return m_backingStore; }
-
- virtual void repaint(const QRegion &) { }
+ static bool registerNatives(QJniEnvironment &env);
+ void onSurfaceChanged(QtJniTypes::Surface surface);
protected:
void setGeometry(const QRect &rect) override;
+ void lockSurface() { m_surfaceMutex.lock(); }
+ void unlockSurface() { m_surfaceMutex.unlock(); }
+ void createSurface();
+ void destroySurface();
+ void setNativeGeometry(const QRect &geometry);
+ void sendExpose() const;
+ bool blockedByModal() const;
+ bool isEmbeddingContainer() const;
-protected:
Qt::WindowFlags m_windowFlags;
Qt::WindowStates m_windowState;
-
- WId m_windowId;
-
- QAndroidPlatformBackingStore *m_backingStore = nullptr;
+ bool m_isRaster;
+
+ int m_nativeViewId = -1;
+ QtJniTypes::QtWindow m_nativeQtWindow;
+ SurfaceContainer m_surfaceContainerType = SurfaceContainer::SurfaceView;
+ QtJniTypes::QtWindow m_nativeParentQtWindow;
+ // The Android Surface, accessed from multiple threads, guarded by m_surfaceMutex
+ QtJniTypes::Surface m_androidSurfaceObject;
+ QWaitCondition m_surfaceWaitCondition;
+ bool m_surfaceCreated = false;
+ QMutex m_surfaceMutex;
+
+private:
+ static void setSurface(JNIEnv *env, jobject obj, jint windowId, QtJniTypes::Surface surface);
+ Q_DECLARE_JNI_NATIVE_METHOD_IN_CURRENT_SCOPE(setSurface)
+ static void windowFocusChanged(JNIEnv *env, jobject object, jboolean focus, jint windowId);
+ Q_DECLARE_JNI_NATIVE_METHOD_IN_CURRENT_SCOPE(windowFocusChanged)
};
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/android/qandroidsystemlocale.cpp b/src/plugins/platforms/android/qandroidsystemlocale.cpp
index 858934b1f8..c5f2e59097 100644
--- a/src/plugins/platforms/android/qandroidsystemlocale.cpp
+++ b/src/plugins/platforms/android/qandroidsystemlocale.cpp
@@ -12,34 +12,38 @@
QT_BEGIN_NAMESPACE
+Q_DECLARE_JNI_CLASS(Locale, "java/util/Locale")
+Q_DECLARE_JNI_CLASS(Resources, "android/content/res/Resources")
+Q_DECLARE_JNI_CLASS(Configuration, "android/content/res/Configuration")
+Q_DECLARE_JNI_CLASS(LocaleList, "android/os/LocaleList")
+
+using namespace QtJniTypes;
+
QAndroidSystemLocale::QAndroidSystemLocale() : m_locale(QLocale::C)
{
}
void QAndroidSystemLocale::getLocaleFromJava() const
{
- QWriteLocker locker(&m_lock);
-
- QJniObject javaLocaleObject;
- QJniObject javaActivity(QtAndroid::activity());
- if (!javaActivity.isValid())
- javaActivity = QtAndroid::service();
- if (javaActivity.isValid()) {
- QJniObject resources = javaActivity.callObjectMethod("getResources", "()Landroid/content/res/Resources;");
- QJniObject configuration = resources.callObjectMethod("getConfiguration", "()Landroid/content/res/Configuration;");
-
- javaLocaleObject = configuration.getObjectField("locale", "Ljava/util/Locale;");
- } else {
- javaLocaleObject = QJniObject::callStaticObjectMethod("java/util/Locale", "getDefault", "()Ljava/util/Locale;");
- }
+ const Locale javaLocaleObject = []{
+ const QJniObject javaContext = QtAndroidPrivate::context();
+ if (javaContext.isValid()) {
+ const QJniObject resources = javaContext.callMethod<Resources>("getResources");
+ const QJniObject configuration = resources.callMethod<Configuration>("getConfiguration");
+ return configuration.getField<Locale>("locale");
+ } else {
+ return Locale::callStaticMethod<Locale>("getDefault");
+ }
+ }();
- QString languageCode = javaLocaleObject.callObjectMethod("getLanguage", "()Ljava/lang/String;").toString();
- QString countryCode = javaLocaleObject.callObjectMethod("getCountry", "()Ljava/lang/String;").toString();
+ const QString languageCode = javaLocaleObject.callMethod<QString>("getLanguage");
+ const QString countryCode = javaLocaleObject.callMethod<QString>("getCountry");
+ QWriteLocker locker(&m_lock);
m_locale = QLocale(languageCode + u'_' + countryCode);
}
-QVariant QAndroidSystemLocale::query(QueryType type, QVariant in) const
+QVariant QAndroidSystemLocale::query(QueryType type, QVariant &&in) const
{
if (type == LocaleChanged) {
getLocaleFromJava();
@@ -142,12 +146,9 @@ QVariant QAndroidSystemLocale::query(QueryType type, QVariant in) const
Q_ASSERT_X(false, Q_FUNC_INFO, "This can't happen.");
case UILanguages: {
if (QtAndroidPrivate::androidSdkVersion() >= 24) {
- QJniObject localeListObject =
- QJniObject::callStaticObjectMethod("android/os/LocaleList", "getDefault",
- "()Landroid/os/LocaleList;");
+ LocaleList localeListObject = LocaleList::callStaticMethod<LocaleList>("getDefault");
if (localeListObject.isValid()) {
- QString lang = localeListObject.callObjectMethod("toLanguageTags",
- "()Ljava/lang/String;").toString();
+ QString lang = localeListObject.callMethod<QString>("toLanguageTags");
// Some devices return with it enclosed in []'s so check if both exists before
// removing to ensure it is formatted correctly
if (lang.startsWith(QChar('[')) && lang.endsWith(QChar(']')))
diff --git a/src/plugins/platforms/android/qandroidsystemlocale.h b/src/plugins/platforms/android/qandroidsystemlocale.h
index 48e1d94a56..cd37b48270 100644
--- a/src/plugins/platforms/android/qandroidsystemlocale.h
+++ b/src/plugins/platforms/android/qandroidsystemlocale.h
@@ -11,10 +11,11 @@ QT_BEGIN_NAMESPACE
class QAndroidSystemLocale : public QSystemLocale
{
+ Q_DISABLE_COPY_MOVE(QAndroidSystemLocale)
public:
QAndroidSystemLocale();
- QVariant query(QueryType type, QVariant in) const override;
+ QVariant query(QueryType type, QVariant &&in) const override;
QLocale fallbackLocale() const override;
private:
diff --git a/src/plugins/platforms/bsdfb/qbsdfbscreen.cpp b/src/plugins/platforms/bsdfb/qbsdfbscreen.cpp
index 564e670290..31f8dab091 100644
--- a/src/plugins/platforms/bsdfb/qbsdfbscreen.cpp
+++ b/src/plugins/platforms/bsdfb/qbsdfbscreen.cpp
@@ -128,7 +128,7 @@ bool QBsdFbScreen::initialize()
QRect userGeometry;
// Parse arguments
- for (const QString &arg : qAsConst(m_arguments)) {
+ for (const QString &arg : std::as_const(m_arguments)) {
QRegularExpressionMatch match;
if (arg.contains(mmSizeRx, &match))
userMmSize = QSize(match.captured(1).toInt(), match.captured(2).toInt());
diff --git a/src/plugins/platforms/cocoa/CMakeLists.txt b/src/plugins/platforms/cocoa/CMakeLists.txt
index 32baf20c97..92e681d8fb 100644
--- a/src/plugins/platforms/cocoa/CMakeLists.txt
+++ b/src/plugins/platforms/cocoa/CMakeLists.txt
@@ -1,9 +1,5 @@
# Copyright (C) 2022 The Qt Company Ltd.
-# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-
-# Generated from cocoa.pro.
-
-# special case:
+# SPDX-License-Identifier: BSD-3-Clause
#####################################################################
## QCocoaIntegrationPlugin Plugin:
@@ -11,7 +7,7 @@
qt_internal_add_plugin(QCocoaIntegrationPlugin
OUTPUT_NAME qcocoa
- DEFAULT_IF ${QT_QPA_DEFAULT_PLATFORM} MATCHES cocoa # special case
+ DEFAULT_IF ${QT_QPA_DEFAULT_PLATFORM} MATCHES cocoa
PLUGIN_TYPE platforms
SOURCES
main.mm
@@ -19,12 +15,9 @@ qt_internal_add_plugin(QCocoaIntegrationPlugin
qcocoaapplicationdelegate.h qcocoaapplicationdelegate.mm
qcocoabackingstore.h qcocoabackingstore.mm
qcocoaclipboard.h qcocoaclipboard.mm
- qcocoacolordialoghelper.h qcocoacolordialoghelper.mm
qcocoacursor.h qcocoacursor.mm
qcocoadrag.h qcocoadrag.mm
qcocoaeventdispatcher.h qcocoaeventdispatcher.mm
- qcocoafiledialoghelper.h qcocoafiledialoghelper.mm
- qcocoafontdialoghelper.h qcocoafontdialoghelper.mm
qcocoahelpers.h qcocoahelpers.mm
qcocoainputcontext.h qcocoainputcontext.mm
qcocoaintegration.h qcocoaintegration.mm
@@ -51,6 +44,7 @@ qt_internal_add_plugin(QCocoaIntegrationPlugin
qcocoacolordialoghelper.h qcocoacolordialoghelper.mm
qcocoafiledialoghelper.h qcocoafiledialoghelper.mm
qcocoafontdialoghelper.h qcocoafontdialoghelper.mm
+ qcocoamessagedialog.h qcocoamessagedialog.mm
DEFINES
QT_NO_FOREACH
LIBRARIES
@@ -62,15 +56,14 @@ qt_internal_add_plugin(QCocoaIntegrationPlugin
${FWIOSurface}
${FWMetal}
${FWQuartzCore}
+ ${FWUniformTypeIdentifiers}
Qt::Core
Qt::CorePrivate
Qt::Gui
Qt::GuiPrivate
)
-# special case begin
qt_disable_apple_app_extension_api_only(QCocoaIntegrationPlugin)
-# special case end
# Resources:
set(qcocoaresources_resource_files
@@ -86,10 +79,6 @@ qt_internal_add_resource(QCocoaIntegrationPlugin "qcocoaresources"
${qcocoaresources_resource_files}
)
-
-#### Keys ignored in scope 1:.:.:cocoa.pro:<TRUE>:
-# OTHER_FILES = "cocoa.json"
-
## Scopes:
#####################################################################
@@ -113,8 +102,3 @@ qt_internal_extend_target(QCocoaIntegrationPlugin CONDITION QT_FEATURE_sessionma
SOURCES
qcocoasessionmanager.cpp qcocoasessionmanager.h
)
-
-#### Keys ignored in scope 7:.:.:cocoa.pro:TARGET Qt::Widgets:
-# QT_FOR_CONFIG = "widgets"
-#### Keys ignored in scope 12:.:.:cocoa.pro:NOT TARGET___equals____ss_QT_DEFAULT_QPA_PLUGIN:
-# PLUGIN_EXTENDS = "-"
diff --git a/src/plugins/platforms/cocoa/main.mm b/src/plugins/platforms/cocoa/main.mm
index 39b3aa2458..7fbf26cb75 100644
--- a/src/plugins/platforms/cocoa/main.mm
+++ b/src/plugins/platforms/cocoa/main.mm
@@ -8,6 +8,8 @@
#include "qcocoaintegration.h"
#include "qcocoatheme.h"
+#include <QtCore/private/qcore_mac_p.h>
+
QT_BEGIN_NAMESPACE
using namespace Qt::StringLiterals;
diff --git a/src/plugins/platforms/cocoa/qcocoaaccessibility.mm b/src/plugins/platforms/cocoa/qcocoaaccessibility.mm
index ccd5f7b665..c5e40a4087 100644
--- a/src/plugins/platforms/cocoa/qcocoaaccessibility.mm
+++ b/src/plugins/platforms/cocoa/qcocoaaccessibility.mm
@@ -54,6 +54,10 @@ void QCocoaAccessibility::notifyAccessibilityUpdate(QAccessibleEvent *event)
case QAccessible::NameChanged:
NSAccessibilityPostNotification(element, NSAccessibilityTitleChangedNotification);
break;
+ case QAccessible::TableModelChanged:
+ // ### Could NSAccessibilityRowCountChangedNotification be relevant here?
+ [element updateTableModel];
+ break;
default:
break;
}
@@ -113,7 +117,6 @@ static void populateRoleMap()
roleMap[QAccessible::ColumnHeader] = NSAccessibilityColumnRole;
roleMap[QAccessible::Row] = NSAccessibilityRowRole;
roleMap[QAccessible::RowHeader] = NSAccessibilityRowRole;
- roleMap[QAccessible::Cell] = NSAccessibilityTextFieldRole;
roleMap[QAccessible::Button] = NSAccessibilityButtonRole;
roleMap[QAccessible::EditableText] = NSAccessibilityTextFieldRole;
roleMap[QAccessible::Link] = NSAccessibilityLinkRole;
@@ -121,7 +124,7 @@ static void populateRoleMap()
roleMap[QAccessible::Splitter] = NSAccessibilitySplitGroupRole;
roleMap[QAccessible::List] = NSAccessibilityListRole;
roleMap[QAccessible::ListItem] = NSAccessibilityStaticTextRole;
- roleMap[QAccessible::Cell] = NSAccessibilityStaticTextRole;
+ roleMap[QAccessible::Cell] = NSAccessibilityCellRole;
roleMap[QAccessible::Client] = NSAccessibilityGroupRole;
roleMap[QAccessible::Paragraph] = NSAccessibilityGroupRole;
roleMap[QAccessible::Section] = NSAccessibilityGroupRole;
@@ -133,6 +136,7 @@ static void populateRoleMap()
roleMap[QAccessible::Note] = NSAccessibilityGroupRole;
roleMap[QAccessible::ComplementaryContent] = NSAccessibilityGroupRole;
roleMap[QAccessible::Graphic] = NSAccessibilityImageRole;
+ roleMap[QAccessible::Tree] = NSAccessibilityOutlineRole;
}
/*
@@ -151,6 +155,8 @@ NSString *macRole(QAccessibleInterface *interface)
if (roleMap.contains(qtRole)) {
// MAC_ACCESSIBILTY_DEBUG() << "return" << roleMap[qtRole];
+ if (roleMap[qtRole] == NSAccessibilityComboBoxRole && !interface->state().editable)
+ return NSAccessibilityMenuButtonRole;
if (roleMap[qtRole] == NSAccessibilityTextFieldRole && interface->state().multiLine)
return NSAccessibilityTextAreaRole;
return roleMap[qtRole];
diff --git a/src/plugins/platforms/cocoa/qcocoaaccessibilityelement.h b/src/plugins/platforms/cocoa/qcocoaaccessibilityelement.h
index 806359888f..a96ab55735 100644
--- a/src/plugins/platforms/cocoa/qcocoaaccessibilityelement.h
+++ b/src/plugins/platforms/cocoa/qcocoaaccessibilityelement.h
@@ -13,7 +13,11 @@
QT_DECLARE_NAMESPACED_OBJC_INTERFACE(QMacAccessibilityElement, NSObject <NSAccessibilityElement>
- (instancetype)initWithId:(QAccessible::Id)anId;
+- (instancetype)initWithId:(QAccessible::Id)anId role:(NSAccessibilityRole)role;
+ (instancetype)elementWithId:(QAccessible::Id)anId;
++ (instancetype)elementWithInterface:(QAccessibleInterface *)iface;
+- (void)updateTableModel;
+- (QAccessibleInterface *)qtInterface;
)
#endif // QT_CONFIG(accessibility)
diff --git a/src/plugins/platforms/cocoa/qcocoaaccessibilityelement.mm b/src/plugins/platforms/cocoa/qcocoaaccessibilityelement.mm
index f3c3d0bfaa..8d4d6d683d 100644
--- a/src/plugins/platforms/cocoa/qcocoaaccessibilityelement.mm
+++ b/src/plugins/platforms/cocoa/qcocoaaccessibilityelement.mm
@@ -9,12 +9,17 @@
#include "qcocoawindow.h"
#include "qcocoascreen.h"
+#include <QtCore/qlogging.h>
#include <QtGui/private/qaccessiblecache_p.h>
#include <QtGui/private/qaccessiblebridgeutils_p.h>
#include <QtGui/qaccessible.h>
QT_USE_NAMESPACE
+Q_LOGGING_CATEGORY(lcAccessibilityTable, "qt.accessibility.table")
+
+using namespace Qt::Literals::StringLiterals;
+
#if QT_CONFIG(accessibility)
/**
@@ -80,14 +85,92 @@ static void convertLineOffset(QAccessibleTextInterface *text, int *line, int *of
@implementation QMacAccessibilityElement {
QAccessible::Id axid;
+ int m_rowIndex;
+ int m_columnIndex;
+
+ // used by NSAccessibilityTable
+ NSMutableArray<QMacAccessibilityElement *> *rows; // corresponds to accessibilityRows
+ NSMutableArray<QMacAccessibilityElement *> *columns; // corresponds to accessibilityColumns
+
+ // If synthesizedRole is set, this means that this objects does not have a corresponding
+ // QAccessibleInterface, but it is synthesized by the cocoa plugin in order to meet the
+ // NSAccessibility requirements.
+ // The ownership is controlled by the parent object identified with the axid member variable.
+ // (Therefore, if this member is set, this objects axid member is the same as the parents axid
+ // member)
+ NSString *synthesizedRole;
}
- (instancetype)initWithId:(QAccessible::Id)anId
{
+ return [self initWithId:anId role:nil];
+}
+
+- (instancetype)initWithId:(QAccessible::Id)anId role:(NSAccessibilityRole)role
+{
Q_ASSERT((int)anId < 0);
self = [super init];
if (self) {
axid = anId;
+ m_rowIndex = -1;
+ m_columnIndex = -1;
+ rows = nil;
+ columns = nil;
+ synthesizedRole = role;
+ // table: if this is not created as an element managed by the table, then
+ // it's either the table itself, or an element created for an already existing
+ // cell interface (or an element that's not at all related to a table).
+ if (!synthesizedRole) {
+ if (QAccessibleInterface *iface = QAccessible::accessibleInterface(axid)) {
+ if (iface->tableInterface()) {
+ [self updateTableModel];
+ } else if (const auto *cell = iface->tableCellInterface()) {
+ // If we create an element for a table cell, initialize it with row/column
+ // and insert it into the corresponding row's columns array.
+ m_rowIndex = cell->rowIndex();
+ m_columnIndex = cell->columnIndex();
+ QAccessibleInterface *table = cell->table();
+ Q_ASSERT(table);
+ QAccessibleTableInterface *tableInterface = table->tableInterface();
+ if (tableInterface) {
+ auto *tableElement = [QMacAccessibilityElement elementWithInterface:table];
+ Q_ASSERT(tableElement);
+ if (!tableElement->rows
+ || int(tableElement->rows.count) <= m_rowIndex
+ || int(tableElement->rows.count) != tableInterface->rowCount()) {
+ qCWarning(lcAccessibilityTable)
+ << "Cell requested for row" << m_rowIndex << "is out of"
+ << "bounds for table with" << (tableElement->rows ?
+ tableElement->rows.count : tableInterface->rowCount())
+ << "rows! Resizing table model.";
+ [tableElement updateTableModel];
+ }
+
+ Q_ASSERT(tableElement->rows);
+ Q_ASSERT(int(tableElement->rows.count) > m_rowIndex);
+
+ auto *rowElement = tableElement->rows[m_rowIndex];
+ if (!rowElement->columns || int(rowElement->columns.count) != tableInterface->columnCount()) {
+ if (rowElement->columns) {
+ qCWarning(lcAccessibilityTable)
+ << "Table representation column count is out of sync:"
+ << rowElement->columns.count << "!=" << tableInterface->columnCount();
+ }
+ rowElement->columns = [rowElement populateTableRow:rowElement->columns
+ count:tableInterface->columnCount()];
+ }
+
+ qCDebug(lcAccessibilityTable) << "Creating cell representation for"
+ << m_rowIndex << m_columnIndex
+ << "in table with"
+ << tableElement->rows.count << "rows and"
+ << rowElement->columns.count << "columns";
+
+ rowElement->columns[m_columnIndex] = self;
+ }
+ }
+ }
+ }
}
return self;
@@ -103,30 +186,45 @@ static void convertLineOffset(QAccessibleTextInterface *text, int *line, int *of
QMacAccessibilityElement *element = cache->elementForId(anId);
if (!element) {
- QAccessibleInterface *iface = QAccessible::accessibleInterface(anId);
- Q_ASSERT(iface);
- if (!iface || !iface->isValid())
- return nil;
+ Q_ASSERT(QAccessible::accessibleInterface(anId));
element = [[self alloc] initWithId:anId];
cache->insertElement(anId, element);
}
return element;
}
++ (instancetype)elementWithInterface:(QAccessibleInterface *)iface
+{
+ Q_ASSERT(iface);
+ if (!iface)
+ return nil;
+
+ const QAccessible::Id anId = QAccessible::uniqueId(iface);
+ return [self elementWithId:anId];
+}
+
- (void)invalidate {
axid = 0;
+ rows = nil;
+ columns = nil;
+ synthesizedRole = nil;
+
NSAccessibilityPostNotification(self, NSAccessibilityUIElementDestroyedNotification);
[self release];
}
- (void)dealloc {
+ if (rows)
+ [rows release]; // will also release all entries first
+ if (columns)
+ [columns release]; // will also release all entries first
[super dealloc];
}
- (BOOL)isEqual:(id)object {
if ([object isKindOfClass:[QMacAccessibilityElement class]]) {
QMacAccessibilityElement *other = object;
- return other->axid == axid;
+ return other->axid == axid && other->synthesizedRole == synthesizedRole;
} else {
return NO;
}
@@ -136,16 +234,137 @@ static void convertLineOffset(QAccessibleTextInterface *text, int *line, int *of
return axid;
}
+- (BOOL)isManagedByParent {
+ return synthesizedRole != nil;
+}
+
+- (NSMutableArray *)populateTableArray:(NSMutableArray *)array role:(NSAccessibilityRole)role count:(int)count
+{
+ if (QAccessibleInterface *iface = self.qtInterface) {
+ if (array && int(array.count) != count) {
+ [array release];
+ array = nil;
+ }
+ if (!array) {
+ array = [NSMutableArray<QMacAccessibilityElement *> arrayWithCapacity:count];
+ [array retain];
+ } else {
+ [array removeAllObjects];
+ }
+ Q_ASSERT(array);
+ for (int n = 0; n < count; ++n) {
+ // columns will have same axid as table (but not inserted in cache)
+ QMacAccessibilityElement *element =
+ [[QMacAccessibilityElement alloc] initWithId:axid role:role];
+ if (element) {
+ if (role == NSAccessibilityRowRole)
+ element->m_rowIndex = n;
+ else if (role == NSAccessibilityColumnRole)
+ element->m_columnIndex = n;
+ [array addObject:element];
+ [element release];
+ } else {
+ qWarning("QCocoaAccessibility: invalid child");
+ }
+ }
+ return array;
+ }
+ return nil;
+}
+
+- (NSMutableArray *)populateTableRow:(NSMutableArray *)array count:(int)count
+{
+ Q_ASSERT(synthesizedRole == NSAccessibilityRowRole);
+ if (array && int(array.count) != count) {
+ [array release];
+ array = nil;
+ }
+
+ if (!array) {
+ array = [NSMutableArray<QMacAccessibilityElement *> arrayWithCapacity:count];
+ [array retain];
+ // When macOS asks for the children of a row, then we populate the row's column
+ // array with synthetic elements as place holders. This way, we don't have to
+ // create QAccessibleInterfaces for every cell before they are really needed.
+ // We don't add those synthetic elements into the cache, and we give them the
+ // same axid as the table. This way, we can get easily to the table, and from
+ // there to the QAccessibleInterface for the cell, when we have to eventually
+ // associate such an interface with the element (at which point it is no longer
+ // a placeholder).
+ for (int n = 0; n < count; ++n) {
+ // columns will have same axid as table (but not inserted in cache)
+ QMacAccessibilityElement *cell =
+ [[QMacAccessibilityElement alloc] initWithId:axid role:NSAccessibilityCellRole];
+ if (cell) {
+ cell->m_rowIndex = m_rowIndex;
+ cell->m_columnIndex = n;
+ [array addObject:cell];
+ }
+ }
+ }
+ Q_ASSERT(array);
+ return array;
+}
+
+- (void)updateTableModel
+{
+ if (QAccessibleInterface *iface = self.qtInterface) {
+ if (QAccessibleTableInterface *table = iface->tableInterface()) {
+ Q_ASSERT(!self.isManagedByParent);
+ qCDebug(lcAccessibilityTable) << "Updating table representation with"
+ << table->rowCount() << table->columnCount();
+ rows = [self populateTableArray:rows role:NSAccessibilityRowRole count:table->rowCount()];
+ columns = [self populateTableArray:columns role:NSAccessibilityColumnRole count:table->columnCount()];
+ }
+ }
+}
+
+- (QAccessibleInterface *)qtInterface
+{
+ QAccessibleInterface *iface = QAccessible::accessibleInterface(axid);
+ if (!iface || !iface->isValid())
+ return nullptr;
+
+ // If this is a placeholder element for a table cell, associate it with the
+ // cell interface (which will be created now, if needed). The current axid is
+ // for the table to which the cell belongs, so iface is pointing at the table.
+ if (synthesizedRole == NSAccessibilityCellRole) {
+ // get the cell interface - there must be a valid one
+ QAccessibleTableInterface *table = iface->tableInterface();
+ Q_ASSERT(table);
+ QAccessibleInterface *cell = table->cellAt(m_rowIndex, m_columnIndex);
+ if (!cell)
+ return nullptr;
+ Q_ASSERT(cell->isValid());
+ iface = cell;
+
+ // no longer a placeholder
+ axid = QAccessible::uniqueId(cell);
+ synthesizedRole = nil;
+
+ QAccessibleCache *cache = QAccessibleCache::instance();
+ if (QMacAccessibilityElement *cellElement = cache->elementForId(axid)) {
+ // there already is another, non-placeholder element in the cache
+ Q_ASSERT(cellElement->synthesizedRole == nil);
+ // we have to release it if it's not us
+ if (cellElement != self) {
+ // for the same cell position
+ Q_ASSERT(cellElement->m_rowIndex == m_rowIndex && cellElement->m_columnIndex == m_columnIndex);
+ [cellElement release];
+ }
+ }
+
+ cache->insertElement(axid, self);
+ }
+ return iface;
+}
+
//
// accessibility protocol
//
- (BOOL)isAccessibilityFocused
{
- QAccessibleInterface *iface = QAccessible::accessibleInterface(axid);
- if (!iface || !iface->isValid()) {
- return false;
- }
// Just check if the app thinks we're focused.
id focusedElement = NSApp.accessibilityApplicationFocusedUIElement;
return [focusedElement isEqual:self];
@@ -165,31 +384,119 @@ static void convertLineOffset(QAccessibleTextInterface *text, int *line, int *of
}
- (NSString *) accessibilityRole {
- QAccessibleInterface *iface = QAccessible::accessibleInterface(axid);
- if (!iface || !iface->isValid())
- return NSAccessibilityUnknownRole;
- return QCocoaAccessible::macRole(iface);
+ // shortcut for cells, rows, and columns in a table
+ if (synthesizedRole)
+ return synthesizedRole;
+ if (QAccessibleInterface *iface = self.qtInterface)
+ return QCocoaAccessible::macRole(iface);
+ return NSAccessibilityUnknownRole;
}
- (NSString *) accessibilitySubRole {
- QAccessibleInterface *iface = QAccessible::accessibleInterface(axid);
- if (!iface || !iface->isValid())
- return NSAccessibilityUnknownRole;
- return QCocoaAccessible::macSubrole(iface);
+ if (QAccessibleInterface *iface = self.qtInterface)
+ return QCocoaAccessible::macSubrole(iface);
+ return NSAccessibilityUnknownRole;
}
- (NSString *) accessibilityRoleDescription {
- QAccessibleInterface *iface = QAccessible::accessibleInterface(axid);
- if (!iface || !iface->isValid())
- return NSAccessibilityUnknownRole;
- return NSAccessibilityRoleDescription(self.accessibilityRole, self.accessibilitySubRole);
+ if (QAccessibleInterface *iface = self.qtInterface)
+ return NSAccessibilityRoleDescription(self.accessibilityRole, self.accessibilitySubRole);
+ return NSAccessibilityUnknownRole;
}
- (NSArray *) accessibilityChildren {
+ // shortcut for cells
+ if (synthesizedRole == NSAccessibilityCellRole)
+ return nil;
+
+ QAccessibleInterface *iface = self.qtInterface;
+ if (!iface)
+ return nil;
+ if (QAccessibleTableInterface *table = iface->tableInterface()) {
+ // either a table or table rows/columns
+ if (!synthesizedRole) {
+ // This is the table element, parent of all rows and columns
+ /*
+ * Typical 2x2 table hierarchy as can be observed in a table found under
+ * Apple -> System Settings -> General -> Login Items (macOS 13)
+ *
+ * (AXTable)
+ * | Columns: NSArray* (2 items)
+ * | Rows: NSArray* (2 items)
+ * | Visible Columns: NSArray* (2 items)
+ * | Visible Rows: NSArray* (2 items)
+ * | Children: NSArray* (5 items)
+ +----<--| Header: (AXGroup)
+ | * +-- (AXRow)
+ | * | +-- (AXText)
+ | * | +-- (AXTextField)
+ | * +-- (AXRow)
+ | * | +-- (AXText)
+ | * | +-- (AXTextField)
+ | * +-- (AXColumn)
+ | * | Header: "Item" (sort button)
+ | * | Index: 0
+ | * | Rows: NSArray* (2 items)
+ | * | Visible Rows: NSArray* (2 items)
+ | * +-- (AXColumn)
+ | * | Header: "Kind" (sort button)
+ | * | Index: 1
+ | * | Rows: NSArray* (2 items)
+ | * | Visible Rows: NSArray* (2 items)
+ +----> +-- (AXGroup)
+ * +-- (AXButton/AXSortButton) Item [NSAccessibilityTableHeaderCellProxy]
+ * +-- (AXButton/AXSortButton) Kind [NSAccessibilityTableHeaderCellProxy]
+ */
+ NSArray *rs = [self accessibilityRows];
+ NSArray *cs = [self accessibilityColumns];
+ const int rCount = int([rs count]);
+ const int cCount = int([cs count]);
+ int childCount = rCount + cCount;
+ NSMutableArray<QMacAccessibilityElement *> *tableChildren =
+ [NSMutableArray<QMacAccessibilityElement *> arrayWithCapacity:childCount];
+ for (int i = 0; i < rCount; ++i) {
+ [tableChildren addObject:[rs objectAtIndex:i]];
+ }
+ for (int i = 0; i < cCount; ++i) {
+ [tableChildren addObject:[cs objectAtIndex:i]];
+ }
+ return NSAccessibilityUnignoredChildren(tableChildren);
+ } else if (synthesizedRole == NSAccessibilityColumnRole) {
+ return nil;
+ } else if (synthesizedRole == NSAccessibilityRowRole) {
+ // axid matches the parent table axid so that we can easily find the parent table
+ // children of row are cell/any items
+ Q_ASSERT(m_rowIndex >= 0);
+ const int numColumns = table->columnCount();
+ columns = [self populateTableRow:columns count:numColumns];
+ return NSAccessibilityUnignoredChildren(columns);
+ }
+ }
+ return QCocoaAccessible::unignoredChildren(iface);
+}
+
+- (NSArray *) accessibilitySelectedChildren {
QAccessibleInterface *iface = QAccessible::accessibleInterface(axid);
if (!iface || !iface->isValid())
return nil;
- return QCocoaAccessible::unignoredChildren(iface);
+
+ QAccessibleSelectionInterface *selection = iface->selectionInterface();
+ if (!selection)
+ return nil;
+
+ const QList<QAccessibleInterface *> selectedList = selection->selectedItems();
+ const qsizetype numSelected = selectedList.size();
+ NSMutableArray<QMacAccessibilityElement *> *selectedChildren =
+ [NSMutableArray<QMacAccessibilityElement *> arrayWithCapacity:numSelected];
+ for (QAccessibleInterface *selectedChild : selectedList) {
+ if (selectedChild && selectedChild->isValid()) {
+ QAccessible::Id id = QAccessible::uniqueId(selectedChild);
+ QMacAccessibilityElement *element = [QMacAccessibilityElement elementWithId:id];
+ if (element)
+ [selectedChildren addObject:element];
+ }
+ }
+ return NSAccessibilityUnignoredChildren(selectedChildren);
}
- (id) accessibilityWindow {
@@ -203,36 +510,66 @@ static void convertLineOffset(QAccessibleTextInterface *text, int *line, int *of
}
- (NSString *) accessibilityTitle {
- QAccessibleInterface *iface = QAccessible::accessibleInterface(axid);
- if (!iface || !iface->isValid())
- return nil;
- if (iface->role() == QAccessible::StaticText)
- return nil;
- return iface->text(QAccessible::Name).toNSString();
+ if (QAccessibleInterface *iface = self.qtInterface) {
+ if (iface->role() == QAccessible::StaticText)
+ return nil;
+ if (self.isManagedByParent)
+ return nil;
+ return iface->text(QAccessible::Name).toNSString();
+ }
+ return nil;
}
- (BOOL) isAccessibilityEnabled {
- QAccessibleInterface *iface = QAccessible::accessibleInterface(axid);
- if (!iface || !iface->isValid())
- return false;
- return !iface->state().disabled;
+ if (QAccessibleInterface *iface = self.qtInterface)
+ return !iface->state().disabled;
+ return false;
}
- (id)accessibilityParent {
- QAccessibleInterface *iface = QAccessible::accessibleInterface(axid);
- if (!iface || !iface->isValid())
+ if (synthesizedRole == NSAccessibilityCellRole) {
+ // a synthetic cell without interface - shortcut to the row
+ QMacAccessibilityElement *tableElement =
+ [QMacAccessibilityElement elementWithId:axid];
+ Q_ASSERT(tableElement && tableElement->rows);
+ Q_ASSERT(int(tableElement->rows.count) > m_rowIndex);
+ QMacAccessibilityElement *rowElement = tableElement->rows[m_rowIndex];
+ return rowElement;
+ }
+
+ QAccessibleInterface *iface = self.qtInterface;
+ if (!iface)
return nil;
+ if (self.isManagedByParent) {
+ // axid is the same for the parent element
+ return NSAccessibilityUnignoredAncestor([QMacAccessibilityElement elementWithId:axid]);
+ }
+
// macOS expects that the hierarchy is:
// App -> Window -> Children
// We don't actually have the window reflected properly in QAccessibility.
// Check if the parent is the application and then instead return the native window.
if (QAccessibleInterface *parent = iface->parent()) {
- if (parent->role() != QAccessible::Application) {
- QAccessible::Id parentId = QAccessible::uniqueId(parent);
- return NSAccessibilityUnignoredAncestor([QMacAccessibilityElement elementWithId: parentId]);
+ if (parent->tableInterface()) {
+ QMacAccessibilityElement *tableElement =
+ [QMacAccessibilityElement elementWithInterface:parent];
+
+ // parent of cell should be row
+ int rowIndex = -1;
+ if (m_rowIndex >= 0 && m_columnIndex >= 0)
+ rowIndex = m_rowIndex;
+ else if (QAccessibleTableCellInterface *cell = iface->tableCellInterface())
+ rowIndex = cell->rowIndex();
+ Q_ASSERT(tableElement->rows);
+ if (rowIndex > int([tableElement->rows count]) || rowIndex == -1)
+ return nil;
+ QMacAccessibilityElement *rowElement = tableElement->rows[rowIndex];
+ return NSAccessibilityUnignoredAncestor(rowElement);
}
+ if (parent->role() != QAccessible::Application)
+ return NSAccessibilityUnignoredAncestor([QMacAccessibilityElement elementWithInterface: parent]);
}
if (QWindow *window = iface->window()) {
@@ -246,78 +583,91 @@ static void convertLineOffset(QAccessibleTextInterface *text, int *line, int *of
}
- (NSRect)accessibilityFrame {
- QAccessibleInterface *iface = QAccessible::accessibleInterface(axid);
- if (!iface || !iface->isValid())
+ QAccessibleInterface *iface = self.qtInterface;
+ if (!iface)
return NSZeroRect;
- return QCocoaScreen::mapToNative(iface->rect());
-}
-- (NSString*)accessibilityLabel {
- QAccessibleInterface *iface = QAccessible::accessibleInterface(axid);
- if (!iface || !iface->isValid()) {
- qWarning() << "Called accessibilityLabel on invalid object: " << axid;
- return nil;
+ QRect rect;
+ if (self.isManagedByParent) {
+ if (QAccessibleTableInterface *table = iface->tableInterface()) {
+ // Construct the geometry of the Row/Column by looking at the individual table cells
+ // ### Assumes that cells logical coordinates have spatial ordering (e.g finds the
+ // rows width by taking the union between the leftmost item and the rightmost item in
+ // a row).
+ // Otherwise, we have to iterate over *all* cells in a row/columns to
+ // find out the Row/Column geometry
+ const bool isRow = synthesizedRole == NSAccessibilityRowRole;
+ QPoint cellPos;
+ int &row = isRow ? cellPos.ry() : cellPos.rx();
+ int &col = isRow ? cellPos.rx() : cellPos.ry();
+
+ NSUInteger trackIndex = self.accessibilityIndex;
+ if (trackIndex != NSNotFound) {
+ row = int(trackIndex);
+ if (QAccessibleInterface *firstCell = table->cellAt(cellPos.y(), cellPos.x())) {
+ rect = firstCell->rect();
+ col = isRow ? table->columnCount() : table->rowCount();
+ if (col > 1) {
+ --col;
+ if (QAccessibleInterface *lastCell =
+ table->cellAt(cellPos.y(), cellPos.x()))
+ rect = rect.united(lastCell->rect());
+ }
+ }
+ }
+ }
+ } else {
+ rect = iface->rect();
}
- return iface->text(QAccessible::Description).toNSString();
-}
-- (void)setAccessibilityLabel:(NSString*)label{
- QAccessibleInterface *iface = QAccessible::accessibleInterface(axid);
- if (!iface || !iface->isValid())
- return;
- iface->setText(QAccessible::Description, QString::fromNSString(label));
+ return QCocoaScreen::mapToNative(rect);
}
-- (id) accessibilityMinValue:(QAccessibleInterface*)iface {
- if (QAccessibleValueInterface *val = iface->valueInterface())
- return @(val->minimumValue().toDouble());
+- (NSString*)accessibilityLabel {
+ if (QAccessibleInterface *iface = self.qtInterface)
+ return iface->text(QAccessible::Description).toNSString();
+ qWarning() << "Called accessibilityLabel on invalid object: " << axid;
return nil;
}
-- (id) accessibilityMaxValue:(QAccessibleInterface*)iface {
- if (QAccessibleValueInterface *val = iface->valueInterface())
- return @(val->maximumValue().toDouble());
- return nil;
+- (void)setAccessibilityLabel:(NSString*)label{
+ if (QAccessibleInterface *iface = self.qtInterface)
+ iface->setText(QAccessible::Description, QString::fromNSString(label));
}
- (id) accessibilityValue {
- QAccessibleInterface *iface = QAccessible::accessibleInterface(axid);
- if (!iface || !iface->isValid())
- return nil;
-
- // VoiceOver asks for the value attribute for all elements. Return nil
- // if we don't want the element to have a value attribute.
- if (!QCocoaAccessible::hasValueAttribute(iface))
- return nil;
-
- return QCocoaAccessible::getValueAttribute(iface);
+ if (QAccessibleInterface *iface = self.qtInterface) {
+ // VoiceOver asks for the value attribute for all elements. Return nil
+ // if we don't want the element to have a value attribute.
+ if (QCocoaAccessible::hasValueAttribute(iface))
+ return QCocoaAccessible::getValueAttribute(iface);
+ }
+ return nil;
}
- (NSInteger) accessibilityNumberOfCharacters {
- QAccessibleInterface *iface = QAccessible::accessibleInterface(axid);
- if (!iface || !iface->isValid())
- return 0;
- if (QAccessibleTextInterface *text = iface->textInterface())
- return text->characterCount();
+ if (QAccessibleInterface *iface = self.qtInterface) {
+ if (QAccessibleTextInterface *text = iface->textInterface())
+ return text->characterCount();
+ }
return 0;
}
- (NSString *) accessibilitySelectedText {
- QAccessibleInterface *iface = QAccessible::accessibleInterface(axid);
- if (!iface || !iface->isValid())
- return nil;
- if (QAccessibleTextInterface *text = iface->textInterface()) {
- int start = 0;
- int end = 0;
- text->selection(0, &start, &end);
- return text->text(start, end).toNSString();
+ if (QAccessibleInterface *iface = self.qtInterface) {
+ if (QAccessibleTextInterface *text = iface->textInterface()) {
+ int start = 0;
+ int end = 0;
+ text->selection(0, &start, &end);
+ return text->text(start, end).toNSString();
+ }
}
return nil;
}
- (NSRange) accessibilitySelectedTextRange {
- QAccessibleInterface *iface = QAccessible::accessibleInterface(axid);
- if (!iface || !iface->isValid())
+ QAccessibleInterface *iface = self.qtInterface;
+ if (!iface)
return NSRange();
if (QAccessibleTextInterface *text = iface->textInterface()) {
int start = 0;
@@ -334,8 +684,8 @@ static void convertLineOffset(QAccessibleTextInterface *text, int *line, int *of
}
- (NSInteger)accessibilityLineForIndex:(NSInteger)index {
- QAccessibleInterface *iface = QAccessible::accessibleInterface(axid);
- if (!iface || !iface->isValid())
+ QAccessibleInterface *iface = self.qtInterface;
+ if (!iface)
return 0;
if (QAccessibleTextInterface *text = iface->textInterface()) {
QString textToPos = text->text(0, index);
@@ -345,8 +695,8 @@ static void convertLineOffset(QAccessibleTextInterface *text, int *line, int *of
}
- (NSRange)accessibilityVisibleCharacterRange {
- QAccessibleInterface *iface = QAccessible::accessibleInterface(axid);
- if (!iface || !iface->isValid())
+ QAccessibleInterface *iface = self.qtInterface;
+ if (!iface)
return NSRange();
// FIXME This is not correct and may impact performance for big texts
if (QAccessibleTextInterface *text = iface->textInterface())
@@ -355,8 +705,8 @@ static void convertLineOffset(QAccessibleTextInterface *text, int *line, int *of
}
- (NSInteger) accessibilityInsertionPointLineNumber {
- QAccessibleInterface *iface = QAccessible::accessibleInterface(axid);
- if (!iface || !iface->isValid())
+ QAccessibleInterface *iface = self.qtInterface;
+ if (!iface)
return 0;
if (QAccessibleTextInterface *text = iface->textInterface()) {
int position = text->cursorPosition();
@@ -367,8 +717,8 @@ static void convertLineOffset(QAccessibleTextInterface *text, int *line, int *of
- (NSArray *)accessibilityParameterizedAttributeNames {
- QAccessibleInterface *iface = QAccessible::accessibleInterface(axid);
- if (!iface || !iface->isValid()) {
+ QAccessibleInterface *iface = self.qtInterface;
+ if (!iface) {
qWarning() << "Called attribute on invalid object: " << axid;
return nil;
}
@@ -391,8 +741,8 @@ static void convertLineOffset(QAccessibleTextInterface *text, int *line, int *of
}
- (id)accessibilityAttributeValue:(NSString *)attribute forParameter:(id)parameter {
- QAccessibleInterface *iface = QAccessible::accessibleInterface(axid);
- if (!iface || !iface->isValid()) {
+ QAccessibleInterface *iface = self.qtInterface;
+ if (!iface) {
qWarning() << "Called attribute on invalid object: " << axid;
return nil;
}
@@ -432,7 +782,7 @@ static void convertLineOffset(QAccessibleTextInterface *text, int *line, int *of
QRectF rect;
if (range.length > 0) {
NSUInteger position = range.location + range.length - 1;
- if (position > range.location && iface->textInterface()->text(position, position + 1) == QStringLiteral("\n"))
+ if (position > range.location && iface->textInterface()->text(position, position + 1) == "\n"_L1)
--position;
QRect lastRect = iface->textInterface()->characterRect(position);
rect = firstRect.united(lastRect);
@@ -460,8 +810,8 @@ static void convertLineOffset(QAccessibleTextInterface *text, int *line, int *of
}
- (BOOL)accessibilityIsAttributeSettable:(NSString *)attribute {
- QAccessibleInterface *iface = QAccessible::accessibleInterface(axid);
- if (!iface || !iface->isValid())
+ QAccessibleInterface *iface = self.qtInterface;
+ if (!iface)
return NO;
if ([attribute isEqualToString:NSAccessibilityFocusedAttribute]) {
@@ -479,8 +829,8 @@ static void convertLineOffset(QAccessibleTextInterface *text, int *line, int *of
}
- (void)accessibilitySetValue:(id)value forAttribute:(NSString *)attribute {
- QAccessibleInterface *iface = QAccessible::accessibleInterface(axid);
- if (!iface || !iface->isValid())
+ QAccessibleInterface *iface = self.qtInterface;
+ if (!iface)
return;
if ([attribute isEqualToString:NSAccessibilityFocusedAttribute]) {
if (QAccessibleActionInterface *action = iface->actionInterface())
@@ -508,8 +858,8 @@ static void convertLineOffset(QAccessibleTextInterface *text, int *line, int *of
- (NSArray *)accessibilityActionNames {
NSMutableArray *nsActions = [[NSMutableArray new] autorelease];
- QAccessibleInterface *iface = QAccessible::accessibleInterface(axid);
- if (!iface || !iface->isValid())
+ QAccessibleInterface *iface = self.qtInterface;
+ if (!iface)
return nsActions;
const QStringList &supportedActionNames = QAccessibleBridgeUtils::effectiveActionNames(iface);
@@ -523,8 +873,8 @@ static void convertLineOffset(QAccessibleTextInterface *text, int *line, int *of
}
- (NSString *)accessibilityActionDescription:(NSString *)action {
- QAccessibleInterface *iface = QAccessible::accessibleInterface(axid);
- if (!iface || !iface->isValid())
+ QAccessibleInterface *iface = self.qtInterface;
+ if (!iface)
return nil; // FIXME is that the right return type??
QString qtAction = QCocoaAccessible::translateAction(action, iface);
QString description;
@@ -541,8 +891,7 @@ static void convertLineOffset(QAccessibleTextInterface *text, int *line, int *of
}
- (void)accessibilityPerformAction:(NSString *)action {
- QAccessibleInterface *iface = QAccessible::accessibleInterface(axid);
- if (iface && iface->isValid()) {
+ if (QAccessibleInterface *iface = self.qtInterface) {
const QString qtAction = QCocoaAccessible::translateAction(action, iface);
QAccessibleBridgeUtils::performEffectiveAction(iface, qtAction);
}
@@ -551,15 +900,20 @@ static void convertLineOffset(QAccessibleTextInterface *text, int *line, int *of
// misc
- (BOOL)accessibilityIsIgnored {
- QAccessibleInterface *iface = QAccessible::accessibleInterface(axid);
- if (!iface || !iface->isValid())
- return true;
- return QCocoaAccessible::shouldBeIgnored(iface);
+ // Short-cut for placeholders and synthesized elements. Working around a bug
+ // that corrups lists returned by NSAccessibilityUnignoredChildren, otherwise
+ // we could ignore rows and columns that are outside the table.
+ if (self.isManagedByParent)
+ return false;
+
+ if (QAccessibleInterface *iface = self.qtInterface)
+ return QCocoaAccessible::shouldBeIgnored(iface);
+ return true;
}
- (id)accessibilityHitTest:(NSPoint)point {
- QAccessibleInterface *iface = QAccessible::accessibleInterface(axid);
- if (!iface || !iface->isValid()) {
+ QAccessibleInterface *iface = self.qtInterface;
+ if (!iface) {
// qDebug("Hit test: INVALID");
return NSAccessibilityUnignoredAncestor(self);
}
@@ -578,26 +932,23 @@ static void convertLineOffset(QAccessibleTextInterface *text, int *line, int *of
childInterface = childOfChildInterface;
} while (childOfChildInterface && childOfChildInterface->isValid());
- QAccessible::Id childId = QAccessible::uniqueId(childInterface);
// hit a child, forward to child accessible interface.
- QMacAccessibilityElement *accessibleElement = [QMacAccessibilityElement elementWithId:childId];
+ QMacAccessibilityElement *accessibleElement = [QMacAccessibilityElement elementWithInterface:childInterface];
if (accessibleElement)
return NSAccessibilityUnignoredAncestor(accessibleElement);
return NSAccessibilityUnignoredAncestor(self);
}
- (id)accessibilityFocusedUIElement {
- QAccessibleInterface *iface = QAccessible::accessibleInterface(axid);
-
- if (!iface || !iface->isValid()) {
+ QAccessibleInterface *iface = self.qtInterface;
+ if (!iface) {
qWarning("FocusedUIElement for INVALID");
return nil;
}
QAccessibleInterface *childInterface = iface->focusChild();
if (childInterface && childInterface->isValid()) {
- QAccessible::Id childAxid = QAccessible::uniqueId(childInterface);
- QMacAccessibilityElement *accessibleElement = [QMacAccessibilityElement elementWithId:childAxid];
+ QMacAccessibilityElement *accessibleElement = [QMacAccessibilityElement elementWithInterface:childInterface];
return NSAccessibilityUnignoredAncestor(accessibleElement);
}
@@ -605,8 +956,7 @@ static void convertLineOffset(QAccessibleTextInterface *text, int *line, int *of
}
- (NSString *) accessibilityHelp {
- QAccessibleInterface *iface = QAccessible::accessibleInterface(axid);
- if (iface && iface->isValid()) {
+ if (QAccessibleInterface *iface = self.qtInterface) {
const QString helpText = iface->text(QAccessible::Help);
if (!helpText.isEmpty())
return helpText.toNSString();
@@ -614,6 +964,47 @@ static void convertLineOffset(QAccessibleTextInterface *text, int *line, int *of
return nil;
}
+/*
+ * Support for table
+ */
+- (NSInteger) accessibilityIndex {
+ NSInteger index = 0;
+ if (synthesizedRole == NSAccessibilityCellRole)
+ return m_columnIndex;
+ if (QAccessibleInterface *iface = self.qtInterface) {
+ if (self.isManagedByParent) {
+ // axid matches the parent table axid so that we can easily find the parent table
+ // children of row are cell/any items
+ if (QAccessibleTableInterface *table = iface->tableInterface()) {
+ if (m_rowIndex >= 0)
+ index = NSInteger(m_rowIndex);
+ else if (m_columnIndex >= 0)
+ index = NSInteger(m_columnIndex);
+ }
+ }
+ }
+ return index;
+}
+
+- (NSArray *) accessibilityRows {
+ if (!synthesizedRole && rows) {
+ QAccessibleInterface *iface = self.qtInterface;
+ if (iface && iface->tableInterface())
+ return NSAccessibilityUnignoredChildren(rows);
+ }
+ return nil;
+}
+
+- (NSArray *) accessibilityColumns {
+ if (!synthesizedRole && columns) {
+ QAccessibleInterface *iface = self.qtInterface;
+ if (iface && iface->tableInterface())
+ return NSAccessibilityUnignoredChildren(columns);
+ }
+ return nil;
+}
+
@end
#endif // QT_CONFIG(accessibility)
+
diff --git a/src/plugins/platforms/cocoa/qcocoaapplicationdelegate.mm b/src/plugins/platforms/cocoa/qcocoaapplicationdelegate.mm
index 07ac6023c0..d7f8a1665e 100644
--- a/src/plugins/platforms/cocoa/qcocoaapplicationdelegate.mm
+++ b/src/plugins/platforms/cocoa/qcocoaapplicationdelegate.mm
@@ -148,7 +148,8 @@ QT_USE_NAMESPACE
- (void)applicationWillFinishLaunching:(NSNotification *)notification
{
- Q_UNUSED(notification);
+ if ([reflectionDelegate respondsToSelector:_cmd])
+ [reflectionDelegate applicationWillFinishLaunching:notification];
/*
From the Cocoa documentation: "A good place to install event handlers
@@ -185,15 +186,34 @@ QT_USE_NAMESPACE
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
- Q_UNUSED(aNotification);
+ if ([reflectionDelegate respondsToSelector:_cmd])
+ [reflectionDelegate applicationDidFinishLaunching:aNotification];
+
inLaunch = false;
if (qEnvironmentVariableIsEmpty("QT_MAC_DISABLE_FOREGROUND_APPLICATION_TRANSFORM")) {
- // Move the application window to front to avoid launching behind the terminal.
- // Ignoring other apps is necessary (we must ignore the terminal), but makes
- // Qt apps play slightly less nice with other apps when lanching from Finder
- // (See the activateIgnoringOtherApps docs.)
- [[NSApplication sharedApplication] activateIgnoringOtherApps:YES];
+ auto frontmostApplication = NSWorkspace.sharedWorkspace.frontmostApplication;
+ auto currentApplication = NSRunningApplication.currentApplication;
+ if (frontmostApplication != currentApplication) {
+ // Move the application to front to avoid launching behind the terminal.
+ // Ignoring other apps is necessary (we must ignore the terminal), but makes
+ // Qt apps play slightly less nice with other apps when launching from Finder
+ // (see the activateIgnoringOtherApps docs). FIXME: Try to distinguish between
+ // being non-active here because another application stole activation in the
+ // time it took us to launch from Finder, and being non-active because we were
+ // launched from Terminal or something that doesn't activate us at all.
+ qCDebug(lcQpaApplication) << "Launched with" << frontmostApplication
+ << "as frontmost application. Activating" << currentApplication << "instead.";
+ [NSApplication.sharedApplication activateIgnoringOtherApps:YES];
+ }
+
+ // Qt windows are typically shown in main(), at which point the application
+ // is not active yet. When the application is activated, either externally
+ // or via the override above, it will only bring the main and key windows
+ // forward, which differs from the behavior if these windows had been shown
+ // once the application was already active. To work around this, we explicitly
+ // activate the current application again, bringing all windows to the front.
+ [currentApplication activateWithOptions:NSApplicationActivateAllWindows];
}
QCocoaMenuBar::insertWindowMenu();
@@ -232,6 +252,11 @@ QT_USE_NAMESPACE
- (void)applicationDidBecomeActive:(NSNotification *)notification
{
+ if (QCocoaWindow::s_applicationActivationObserver) {
+ [[[NSWorkspace sharedWorkspace] notificationCenter] removeObserver:QCocoaWindow::s_applicationActivationObserver];
+ QCocoaWindow::s_applicationActivationObserver = nil;
+ }
+
if ([reflectionDelegate respondsToSelector:_cmd])
[reflectionDelegate applicationDidBecomeActive:notification];
@@ -313,14 +338,42 @@ QT_USE_NAMESPACE
{
Q_UNUSED(replyEvent);
NSString *urlString = [[event paramDescriptorForKeyword:keyDirectObject] stringValue];
- QWindowSystemInterface::handleFileOpenEvent(QUrl(QString::fromNSString(urlString)));
+ // The string we get from the requesting application might not necessarily meet
+ // QUrl's requirement for a IDN-compliant host. So if we can't parse into a QUrl,
+ // then we pass the string on to the application as the name of a file (and
+ // QFileOpenEvent::file is not guaranteed to be the path to a local, open'able
+ // file anyway).
+ const QString qurlString = QString::fromNSString(urlString);
+ if (const QUrl url(qurlString); url.isValid())
+ QWindowSystemInterface::handleFileOpenEvent(url);
+ else
+ QWindowSystemInterface::handleFileOpenEvent(qurlString);
}
+
+- (BOOL)applicationSupportsSecureRestorableState:(NSApplication *)application
+{
+ if (@available(macOS 12, *)) {
+ if ([reflectionDelegate respondsToSelector:_cmd])
+ return [reflectionDelegate applicationSupportsSecureRestorableState:application];
+ }
+
+ // We don't support or implement state restorations via the AppKit
+ // state restoration APIs, but if we did, we would/should support
+ // secure state restoration. This is the default for apps linked
+ // against the macOS 14 SDK, but as we target versions below that
+ // as well we need to return YES here explicitly to silence a runtime
+ // warning.
+ return YES;
+}
+
@end
@implementation QCocoaApplicationDelegate (Menus)
- (BOOL)validateMenuItem:(NSMenuItem*)item
{
+ qCDebug(lcQpaMenus) << "Validating" << item << "for" << self;
+
auto *nativeItem = qt_objc_cast<QCocoaNSMenuItem *>(item);
if (!nativeItem)
return item.enabled; // FIXME Test with with Qt as plugin or embedded QWindow.
@@ -342,6 +395,8 @@ QT_USE_NAMESPACE
- (void)qt_itemFired:(QCocoaNSMenuItem *)item
{
+ qCDebug(lcQpaMenus) << "Activating" << item;
+
if (item.hasSubmenu)
return;
@@ -353,7 +408,6 @@ QT_USE_NAMESPACE
if (!platformItem || platformItem->menu())
return;
- QScopedScopeLevelCounter scopeLevelCounter(QGuiApplicationPrivate::instance()->threadData.loadRelaxed());
QGuiApplicationPrivate::modifier_buttons = QAppleKeyMapper::fromCocoaModifiers([NSEvent modifierFlags]);
static QMetaMethod activatedSignal = QMetaMethod::fromSignal(&QCocoaMenuItem::activated);
diff --git a/src/plugins/platforms/cocoa/qcocoabackingstore.h b/src/plugins/platforms/cocoa/qcocoabackingstore.h
index 6db88f923c..71b6015a54 100644
--- a/src/plugins/platforms/cocoa/qcocoabackingstore.h
+++ b/src/plugins/platforms/cocoa/qcocoabackingstore.h
@@ -54,6 +54,7 @@ private:
bool eventFilter(QObject *watched, QEvent *event) override;
QSize m_requestedSize;
+ QRegion m_staticContents;
class GraphicsBuffer : public QIOSurfaceGraphicsBuffer
{
@@ -78,7 +79,8 @@ private:
bool recreateBackBufferIfNeeded();
void finalizeBackBuffer();
- void preserveFromFrontBuffer(const QRegion &region, const QPoint &offset = QPoint());
+ void blitBuffer(GraphicsBuffer *sourceBuffer, const QRegion &sourceRegion,
+ GraphicsBuffer *destinationBuffer, const QPoint &destinationOffset = QPoint());
void backingPropertiesChanged();
QMacNotificationObserver m_backingPropertiesObserver;
diff --git a/src/plugins/platforms/cocoa/qcocoabackingstore.mm b/src/plugins/platforms/cocoa/qcocoabackingstore.mm
index a3a25062c5..b211b5d02d 100644
--- a/src/plugins/platforms/cocoa/qcocoabackingstore.mm
+++ b/src/plugins/platforms/cocoa/qcocoabackingstore.mm
@@ -9,6 +9,7 @@
#include "qcocoahelpers.h"
#include <QtCore/qmath.h>
+#include <QtCore/private/qcore_mac_p.h>
#include <QtGui/qpainter.h>
#include <QuartzCore/CATransaction.h>
@@ -22,8 +23,9 @@ QCocoaBackingStore::QCocoaBackingStore(QWindow *window)
QCFType<CGColorSpaceRef> QCocoaBackingStore::colorSpace() const
{
- NSView *view = static_cast<QCocoaWindow *>(window()->handle())->view();
- return QCFType<CGColorSpaceRef>::constructFromGet(view.window.colorSpace.CGColorSpace);
+ const auto *platformWindow = static_cast<QCocoaWindow *>(window()->handle());
+ const QNSView *view = qnsview_cast(platformWindow->view());
+ return QCFType<CGColorSpaceRef>::constructFromGet(view.colorSpace.CGColorSpace);
}
// ----------------------------------------------------------------------------
@@ -71,12 +73,11 @@ bool QCALayerBackingStore::eventFilter(QObject *watched, QEvent *event)
void QCALayerBackingStore::resize(const QSize &size, const QRegion &staticContents)
{
- qCDebug(lcQpaBackingStore) << "Resize requested to" << size;
-
- if (!staticContents.isNull())
- qCWarning(lcQpaBackingStore) << "QCALayerBackingStore does not support static contents";
+ qCDebug(lcQpaBackingStore) << "Resize requested to" << size
+ << "with static contents" << staticContents;
m_requestedSize = size;
+ m_staticContents = staticContents;
}
void QCALayerBackingStore::beginPaint(const QRegion &region)
@@ -188,11 +189,50 @@ bool QCALayerBackingStore::recreateBackBufferIfNeeded()
}
#endif
- qCInfo(lcQpaBackingStore) << "Creating surface of" << requestedBufferSize
- << "based on requested" << m_requestedSize << "and dpr =" << devicePixelRatio;
+ qCInfo(lcQpaBackingStore)<< "Creating surface of" << requestedBufferSize
+ << "for" << window() << "based on requested" << m_requestedSize
+ << "dpr =" << devicePixelRatio << "and color space" << colorSpace();
static auto pixelFormat = QImage::toPixelFormat(QImage::Format_ARGB32_Premultiplied);
- m_buffers.back().reset(new GraphicsBuffer(requestedBufferSize, devicePixelRatio, pixelFormat, colorSpace()));
+ auto *newBackBuffer = new GraphicsBuffer(requestedBufferSize, devicePixelRatio, pixelFormat, colorSpace());
+
+ if (!m_staticContents.isEmpty() && m_buffers.back()) {
+ // We implicitly support static backingstore content as a result of
+ // finalizing the back buffer on flush, where we copy any non-painted
+ // areas from the front buffer. But there is no guarantee that a resize
+ // will always come after a flush, where we have a pristine front buffer
+ // to copy from. It may come after a few begin/endPaints, where the back
+ // buffer then contains (part of) the latest state. We also have the case
+ // of single-buffered backingstore, where the front and back buffer is
+ // the same, which means we must do the copy from the old back buffer
+ // to the newly resized buffer now, before we replace it below.
+
+ // If the back buffer has been partially filled already, we need to
+ // copy parts of the static content from that. The rest we copy from
+ // the front buffer.
+ const QRegion backBufferRegion = m_staticContents - m_buffers.back()->dirtyRegion;
+ const QRegion frontBufferRegion = m_staticContents - backBufferRegion;
+
+ qCInfo(lcQpaBackingStore) << "Preserving static content" << backBufferRegion
+ << "from back buffer, and" << frontBufferRegion << "from front buffer";
+
+ newBackBuffer->lock(QPlatformGraphicsBuffer::SWWriteAccess);
+ blitBuffer(m_buffers.back().get(), backBufferRegion, newBackBuffer);
+ Q_ASSERT(frontBufferRegion.isEmpty() || m_buffers.front());
+ blitBuffer(m_buffers.front().get(), frontBufferRegion, newBackBuffer);
+ newBackBuffer->unlock();
+
+ // The new back buffer now is valid for the static contents region.
+ // We don't need to maintain the static contents region for resizes
+ // of any other buffers in the swap chain, as these will finalize
+ // their content on flush from the buffer we just filled, and we
+ // don't need to mark them dirty for the area we just filled, as
+ // new buffers are fully dirty when created.
+ newBackBuffer->dirtyRegion -= m_staticContents;
+ m_staticContents = {};
+ }
+
+ m_buffers.back().reset(newBackBuffer);
return true;
}
@@ -255,7 +295,7 @@ bool QCALayerBackingStore::scroll(const QRegion &region, int dx, int dy)
if (!frontBufferRegion.isEmpty()) {
qCDebug(lcQpaBackingStore) << "Scrolling" << frontBufferRegion << "by copying from front buffer";
- preserveFromFrontBuffer(frontBufferRegion, scrollDelta);
+ blitBuffer(m_buffers.front().get(), frontBufferRegion, m_buffers.back().get(), scrollDelta);
}
m_buffers.back()->unlock();
@@ -439,10 +479,11 @@ void QCALayerBackingStore::backingPropertiesChanged()
qCDebug(lcQpaBackingStore) << "Backing properties for" << window() << "did change";
- qCDebug(lcQpaBackingStore) << "Updating color space of existing buffers";
+ const auto newColorSpace = colorSpace();
+ qCDebug(lcQpaBackingStore) << "Updating color space of existing buffers to" << newColorSpace;
for (auto &buffer : m_buffers) {
if (buffer)
- buffer->setColorSpace(colorSpace());
+ buffer->setColorSpace(newColorSpace);
}
}
@@ -474,54 +515,76 @@ void QCALayerBackingStore::finalizeBackBuffer()
if (!m_buffers.back()->isDirty())
return;
- m_buffers.back()->lock(QPlatformGraphicsBuffer::SWWriteAccess);
- preserveFromFrontBuffer(m_buffers.back()->dirtyRegion);
- m_buffers.back()->unlock();
+ qCDebug(lcQpaBackingStore) << "Finalizing back buffer with dirty region" << m_buffers.back()->dirtyRegion;
+
+ if (m_buffers.back() != m_buffers.front()) {
+ m_buffers.back()->lock(QPlatformGraphicsBuffer::SWWriteAccess);
+ blitBuffer(m_buffers.front().get(), m_buffers.back()->dirtyRegion, m_buffers.back().get());
+ m_buffers.back()->unlock();
+ } else {
+ qCDebug(lcQpaBackingStore) << "Front and back buffer is the same. Can not finalize back buffer.";
+ }
// The back buffer is now completely in sync, ready to be presented
m_buffers.back()->dirtyRegion = QRegion();
}
-void QCALayerBackingStore::preserveFromFrontBuffer(const QRegion &region, const QPoint &offset)
+/*
+ \internal
+
+ Blits \a sourceRegion from \a sourceBuffer to \a destinationBuffer,
+ at offset \a destinationOffset.
+
+ The source buffer is automatically locked for read only access
+ during the blit.
+
+ The destination buffer has to be locked for write access by the
+ caller.
+*/
+
+void QCALayerBackingStore::blitBuffer(GraphicsBuffer *sourceBuffer, const QRegion &sourceRegion,
+ GraphicsBuffer *destinationBuffer, const QPoint &destinationOffset)
{
+ Q_ASSERT(sourceBuffer && destinationBuffer);
+ Q_ASSERT(sourceBuffer != destinationBuffer);
- if (m_buffers.front() == m_buffers.back())
- return; // Nothing to preserve from
+ if (sourceRegion.isEmpty())
+ return;
- qCDebug(lcQpaBackingStore) << "Preserving" << region << "of front buffer to"
- << region.translated(offset) << "of back buffer";
+ qCDebug(lcQpaBackingStore) << "Blitting" << sourceRegion << "of" << sourceBuffer
+ << "to" << sourceRegion.translated(destinationOffset) << "of" << destinationBuffer;
- Q_ASSERT(m_buffers.back()->isLocked() == QPlatformGraphicsBuffer::SWWriteAccess);
+ Q_ASSERT(destinationBuffer->isLocked() == QPlatformGraphicsBuffer::SWWriteAccess);
- m_buffers.front()->lock(QPlatformGraphicsBuffer::SWReadAccess);
- const QImage *frontBuffer = m_buffers.front()->asImage();
+ sourceBuffer->lock(QPlatformGraphicsBuffer::SWReadAccess);
+ const QImage *sourceImage = sourceBuffer->asImage();
- const QRect frontSurfaceBounds(QPoint(0, 0), m_buffers.front()->size());
- const qreal sourceDevicePixelRatio = frontBuffer->devicePixelRatio();
+ const QRect sourceBufferBounds(QPoint(0, 0), sourceBuffer->size());
+ const qreal sourceDevicePixelRatio = sourceImage->devicePixelRatio();
- QPainter painter(m_buffers.back()->asImage());
+ QPainter painter(destinationBuffer->asImage());
painter.setCompositionMode(QPainter::CompositionMode_Source);
// Let painter operate in device pixels, to make it easier to compare coordinates
- const qreal targetDevicePixelRatio = painter.device()->devicePixelRatio();
- painter.scale(1.0 / targetDevicePixelRatio, 1.0 / targetDevicePixelRatio);
+ const qreal destinationDevicePixelRatio = painter.device()->devicePixelRatio();
+ painter.scale(1.0 / destinationDevicePixelRatio, 1.0 / destinationDevicePixelRatio);
- for (const QRect &rect : region) {
+ for (const QRect &rect : sourceRegion) {
QRect sourceRect(rect.topLeft() * sourceDevicePixelRatio,
rect.size() * sourceDevicePixelRatio);
- QRect targetRect((rect.topLeft() + offset) * targetDevicePixelRatio,
- rect.size() * targetDevicePixelRatio);
+ QRect destinationRect((rect.topLeft() + destinationOffset) * destinationDevicePixelRatio,
+ rect.size() * destinationDevicePixelRatio);
#ifdef QT_DEBUG
- if (Q_UNLIKELY(!frontSurfaceBounds.contains(sourceRect.bottomRight()))) {
- qCWarning(lcQpaBackingStore) << "Front buffer too small to preserve"
- << QRegion(sourceRect).subtracted(frontSurfaceBounds);
+ if (Q_UNLIKELY(!sourceBufferBounds.contains(sourceRect.bottomRight()))) {
+ qCWarning(lcQpaBackingStore) << "Source buffer of size" << sourceBuffer->size()
+ << "is too small to blit" << sourceRect;
}
#endif
- painter.drawImage(targetRect, *frontBuffer, sourceRect);
+ painter.drawImage(destinationRect, *sourceImage, sourceRect);
}
- m_buffers.front()->unlock();
+ sourceBuffer->unlock();
}
// ----------------------------------------------------------------------------
diff --git a/src/plugins/platforms/cocoa/qcocoaclipboard.mm b/src/plugins/platforms/cocoa/qcocoaclipboard.mm
index 7363b011bf..241faadbec 100644
--- a/src/plugins/platforms/cocoa/qcocoaclipboard.mm
+++ b/src/plugins/platforms/cocoa/qcocoaclipboard.mm
@@ -3,13 +3,15 @@
#include "qcocoaclipboard.h"
+#include <QtGui/qutimimeconverter.h>
+
#ifndef QT_NO_CLIPBOARD
QT_BEGIN_NAMESPACE
QCocoaClipboard::QCocoaClipboard()
- :m_clipboard(new QMacPasteboard(kPasteboardClipboard, QMacInternalPasteboardMime::MIME_CLIP))
- ,m_find(new QMacPasteboard(kPasteboardFind, QMacInternalPasteboardMime::MIME_CLIP))
+ :m_clipboard(new QMacPasteboard(kPasteboardClipboard, QUtiMimeConverter::HandlerScopeFlag::Clipboard))
+ ,m_find(new QMacPasteboard(kPasteboardFind, QUtiMimeConverter::HandlerScopeFlag::Clipboard))
{
connect(qGuiApp, &QGuiApplication::applicationStateChanged, this, &QCocoaClipboard::handleApplicationStateChanged);
}
diff --git a/src/plugins/platforms/cocoa/qcocoacolordialoghelper.mm b/src/plugins/platforms/cocoa/qcocoacolordialoghelper.mm
index 777c3d65b6..b58f58caeb 100644
--- a/src/plugins/platforms/cocoa/qcocoacolordialoghelper.mm
+++ b/src/plugins/platforms/cocoa/qcocoacolordialoghelper.mm
@@ -182,7 +182,17 @@ QT_NAMESPACE_ALIAS_OBJC_CLASS(QNSColorPanelDelegate);
mDialogIsExecuting = false;
mResultSet = false;
mClosingDueToKnownButton = false;
- [mColorPanel makeKeyAndOrderFront:mColorPanel];
+ // Make this an asynchronous call, so the panel is made key only
+ // in the next event loop run. This is to make sure that by
+ // the time the modal loop is run in runModalForWindow below,
+ // which internally also sets the panel to key window,
+ // the panel is not yet key, and the NSApp still has the right
+ // reference to the _previousKeyWindow. Otherwise both NSApp.key
+ // and NSApp._previousKeyWindow would wrongly point to the panel,
+ // loosing any reference to the window that was key before.
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [mColorPanel makeKeyAndOrderFront:mColorPanel];
+ });
}
- (BOOL)runApplicationModalPanel
diff --git a/src/plugins/platforms/cocoa/qcocoadrag.h b/src/plugins/platforms/cocoa/qcocoadrag.h
index 24485ac6ac..dedf8a7fd9 100644
--- a/src/plugins/platforms/cocoa/qcocoadrag.h
+++ b/src/plugins/platforms/cocoa/qcocoadrag.h
@@ -44,7 +44,7 @@ private:
NSEvent *m_lastEvent;
NSView *m_lastView;
Qt::DropAction m_executed_drop_action;
- QEventLoop internalDragLoop;
+ QEventLoop *m_internalDragLoop = nullptr;
bool maybeDragMultipleItems();
diff --git a/src/plugins/platforms/cocoa/qcocoadrag.mm b/src/plugins/platforms/cocoa/qcocoadrag.mm
index 50f094716f..a8404889e9 100644
--- a/src/plugins/platforms/cocoa/qcocoadrag.mm
+++ b/src/plugins/platforms/cocoa/qcocoadrag.mm
@@ -2,12 +2,15 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#include <AppKit/AppKit.h>
+#include <UniformTypeIdentifiers/UTCoreTypes.h>
#include "qcocoadrag.h"
#include "qmacclipboard.h"
#include "qcocoahelpers.h"
#include <QtGui/private/qcoregraphics_p.h>
+#include <QtGui/qutimimeconverter.h>
#include <QtCore/qsysinfo.h>
+#include <QtCore/private/qcore_mac_p.h>
#include <vector>
@@ -96,7 +99,7 @@ Qt::DropAction QCocoaDrag::drag(QDrag *o)
m_drag = o;
m_executed_drop_action = Qt::IgnoreAction;
- QMacPasteboard dragBoard(CFStringRef(NSPasteboardNameDrag), QMacInternalPasteboardMime::MIME_DND);
+ QMacPasteboard dragBoard(CFStringRef(NSPasteboardNameDrag), QUtiMimeConverter::HandlerScopeFlag::DnD);
m_drag->mimeData()->setData("application/x-qt-mime-type-name"_L1, QByteArray("dummy"));
dragBoard.setMimeData(m_drag->mimeData(), QMacPasteboard::LazyRequest);
@@ -159,8 +162,7 @@ bool QCocoaDrag::maybeDragMultipleItems()
for (NSPasteboardItem *item in dragBoard.pasteboardItems) {
bool isUrl = false;
for (NSPasteboardType type in item.types) {
- using NSStringRef = NSString *;
- if ([type isEqualToString:NSStringRef(kUTTypeFileURL)]) {
+ if ([type isEqualToString:UTTypeFileURL.identifier]) {
isUrl = true;
break;
}
@@ -184,6 +186,12 @@ bool QCocoaDrag::maybeDragMultipleItems()
// contains a combined picture for all urls we drag.
auto imageOrNil = dragImage;
for (const auto &qtUrl : qtUrls) {
+ if (!qtUrl.isValid())
+ continue;
+
+ if (qtUrl.isRelative()) // NSPasteboardWriting rejects such items.
+ continue;
+
NSURL *nsUrl = qtUrl.toNSURL();
auto *newItem = [[[NSDraggingItem alloc] initWithPasteboardWriter:nsUrl] autorelease];
const NSRect itemFrame = NSMakeRect(itemLocation.x, itemLocation.y,
@@ -205,7 +213,9 @@ bool QCocoaDrag::maybeDragMultipleItems()
}
[sourceView beginDraggingSessionWithItems:dragItems event:m_lastEvent source:sourceView];
- internalDragLoop.exec();
+ QEventLoop eventLoop;
+ QScopedValueRollback updateGuard(m_internalDragLoop, &eventLoop);
+ eventLoop.exec();
return true;
}
@@ -216,8 +226,10 @@ void QCocoaDrag::setAcceptedAction(Qt::DropAction act)
void QCocoaDrag::exitDragLoop()
{
- if (internalDragLoop.isRunning())
- internalDragLoop.exit();
+ if (m_internalDragLoop) {
+ Q_ASSERT(m_internalDragLoop->isRunning());
+ m_internalDragLoop->exit();
+ }
}
@@ -232,14 +244,14 @@ QPixmap QCocoaDrag::dragPixmap(QDrag *drag, QPoint &hotSpot) const
QFontMetrics fm(f);
if (data->hasImage()) {
- const QImage img = data->imageData().value<QImage>();
+ QImage img = data->imageData().value<QImage>();
if (!img.isNull()) {
- pm = QPixmap::fromImage(img).scaledToWidth(dragImageMaxChars *fm.averageCharWidth());
+ pm = QPixmap::fromImage(std::move(img)).scaledToWidth(dragImageMaxChars *fm.averageCharWidth());
}
}
if (pm.isNull() && (data->hasText() || data->hasUrls()) ) {
- QString s = data->hasText() ? data->text() : data->urls().first().toString();
+ QString s = data->hasText() ? data->text() : data->urls().constFirst().toString();
if (s.length() > dragImageMaxChars)
s = s.left(dragImageMaxChars -3) + QChar(0x2026);
if (!s.isEmpty()) {
@@ -297,11 +309,11 @@ QStringList QCocoaDropData::formats_sys() const
qDebug("DnD: Cannot get PasteBoard!");
return formats;
}
- formats = QMacPasteboard(board, QMacInternalPasteboardMime::MIME_DND).formats();
+ formats = QMacPasteboard(board, QUtiMimeConverter::HandlerScopeFlag::DnD).formats();
return formats;
}
-QVariant QCocoaDropData::retrieveData_sys(const QString &mimeType, QMetaType type) const
+QVariant QCocoaDropData::retrieveData_sys(const QString &mimeType, QMetaType) const
{
QVariant data;
PasteboardRef board;
@@ -309,7 +321,7 @@ QVariant QCocoaDropData::retrieveData_sys(const QString &mimeType, QMetaType typ
qDebug("DnD: Cannot get PasteBoard!");
return data;
}
- data = QMacPasteboard(board, QMacInternalPasteboardMime::MIME_DND).retrieveData(mimeType, type);
+ data = QMacPasteboard(board, QUtiMimeConverter::HandlerScopeFlag::DnD).retrieveData(mimeType);
CFRelease(board);
return data;
}
@@ -322,7 +334,7 @@ bool QCocoaDropData::hasFormat_sys(const QString &mimeType) const
qDebug("DnD: Cannot get PasteBoard!");
return has;
}
- has = QMacPasteboard(board, QMacInternalPasteboardMime::MIME_DND).hasFormat(mimeType);
+ has = QMacPasteboard(board, QUtiMimeConverter::HandlerScopeFlag::DnD).hasFormat(mimeType);
CFRelease(board);
return has;
}
diff --git a/src/plugins/platforms/cocoa/qcocoaeventdispatcher.h b/src/plugins/platforms/cocoa/qcocoaeventdispatcher.h
index 7a291022fe..96eb70dabc 100644
--- a/src/plugins/platforms/cocoa/qcocoaeventdispatcher.h
+++ b/src/plugins/platforms/cocoa/qcocoaeventdispatcher.h
@@ -55,20 +55,26 @@
#include <QtCore/private/qabstracteventdispatcher_p.h>
#include <QtCore/private/qcfsocketnotifier_p.h>
#include <QtCore/private/qtimerinfo_unix_p.h>
+#include <QtCore/qloggingcategory.h>
+#include <QtCore/qpointer.h>
#include <CoreFoundation/CoreFoundation.h>
+Q_FORWARD_DECLARE_OBJC_CLASS(NSWindow);
+
QT_BEGIN_NAMESPACE
+Q_DECLARE_LOGGING_CATEGORY(lcEventDispatcher);
+
typedef struct _NSModalSession *NSModalSession;
typedef struct _QCocoaModalSessionInfo {
QPointer<QWindow> window;
NSModalSession session;
- void *nswindow;
+ NSWindow *nswindow;
} QCocoaModalSessionInfo;
class QCocoaEventDispatcherPrivate;
-class QCocoaEventDispatcher : public QAbstractEventDispatcher
+class QCocoaEventDispatcher : public QAbstractEventDispatcherV2
{
Q_OBJECT
Q_DECLARE_PRIVATE(QCocoaEventDispatcher)
@@ -83,12 +89,12 @@ public:
void registerSocketNotifier(QSocketNotifier *notifier);
void unregisterSocketNotifier(QSocketNotifier *notifier);
- void registerTimer(int timerId, qint64 interval, Qt::TimerType timerType, QObject *object);
- bool unregisterTimer(int timerId);
- bool unregisterTimers(QObject *object);
- QList<TimerInfo> registeredTimers(QObject *object) const;
-
- int remainingTime(int timerId);
+ void registerTimer(Qt::TimerId timerId, Duration interval, Qt::TimerType timerType,
+ QObject *object) final;
+ bool unregisterTimer(Qt::TimerId timerId) final;
+ bool unregisterTimers(QObject *object) final;
+ QList<TimerInfoV2> timersForObject(QObject *object) const final;
+ Duration remainingTime(Qt::TimerId timerId) const final;
void wakeUp();
void interrupt();
@@ -126,6 +132,7 @@ public:
QStack<QCocoaModalSessionInfo> cocoaModalSessionStack;
bool currentExecIsNSAppRun;
bool nsAppRunCalledByQt;
+ bool initializingNSApplication = false;
bool cleanupModalSessionsNeeded;
uint processEventsCalled;
NSModalSession currentModalSessionCached;
@@ -144,7 +151,6 @@ public:
QList<void *> queuedUserInputEvents; // NSEvent *
CFRunLoopSourceRef postedEventsSource;
CFRunLoopObserverRef waitingObserver;
- CFRunLoopObserverRef firstTimeObserver;
QAtomicInt serialNumber;
int lastSerial;
bool interrupt;
@@ -153,7 +159,6 @@ public:
static void postedEventsSourceCallback(void *info);
static void waitingObserverCallback(CFRunLoopObserverRef observer,
CFRunLoopActivity activity, void *info);
- static void firstLoopEntry(CFRunLoopObserverRef ref, CFRunLoopActivity activity, void *info);
bool sendQueuedUserInputEvents();
void processPostedEvents();
};
diff --git a/src/plugins/platforms/cocoa/qcocoaeventdispatcher.mm b/src/plugins/platforms/cocoa/qcocoaeventdispatcher.mm
index 1197706397..739fbda4f5 100644
--- a/src/plugins/platforms/cocoa/qcocoaeventdispatcher.mm
+++ b/src/plugins/platforms/cocoa/qcocoaeventdispatcher.mm
@@ -49,6 +49,7 @@
#include <QtCore/qscopeguard.h>
#include <QtCore/qsocketnotifier.h>
#include <QtCore/private/qthread_p.h>
+#include <QtCore/private/qcore_mac_p.h>
#include <qpa/qplatformwindow.h>
#include <qpa/qplatformnativeinterface.h>
@@ -57,6 +58,8 @@
QT_BEGIN_NAMESPACE
+Q_LOGGING_CATEGORY(lcEventDispatcher, "qt.eventdispatcher");
+
static inline CFRunLoopRef mainRunLoop()
{
return CFRunLoopGetMain();
@@ -86,6 +89,13 @@ void QCocoaEventDispatcherPrivate::runLoopTimerCallback(CFRunLoopTimerRef, void
void QCocoaEventDispatcherPrivate::activateTimersSourceCallback(void *info)
{
QCocoaEventDispatcherPrivate *d = static_cast<QCocoaEventDispatcherPrivate *>(info);
+ if (d->initializingNSApplication) {
+ qCDebug(lcEventDispatcher) << "Deferring" << __FUNCTION__ << "due to NSApp initialization";
+ // We don't want to process any sources during explicit NSApplication
+ // initialization, so defer the source until the actual event processing.
+ CFRunLoopSourceSignal(d->activateTimersSourceRef);
+ return;
+ }
d->processTimers();
d->maybeCancelWaitForMoreEvents();
}
@@ -105,6 +115,7 @@ void QCocoaEventDispatcherPrivate::maybeStartCFRunLoopTimer()
return;
}
+ using DoubleSeconds = std::chrono::duration<double, std::ratio<1>>;
if (!runLoopTimerRef) {
// start the CFRunLoopTimer
CFAbsoluteTime ttf = CFAbsoluteTimeGetCurrent();
@@ -112,10 +123,10 @@ void QCocoaEventDispatcherPrivate::maybeStartCFRunLoopTimer()
CFTimeInterval oneyear = CFTimeInterval(3600. * 24. * 365.);
// Q: when should the CFRunLoopTimer fire for the first time?
- struct timespec tv;
- if (timerInfoList.timerWait(tv)) {
+ if (auto opt = timerInfoList.timerWait()) {
// A: when we have timers to fire, of course
- interval = qMax(tv.tv_sec + tv.tv_nsec / 1000000000., 0.0000001);
+ DoubleSeconds secs{*opt};
+ interval = qMax(secs.count(), 0.0000001);
} else {
// this shouldn't really happen, but in case it does, set the timer to fire a some point in the distant future
interval = oneyear;
@@ -135,10 +146,10 @@ void QCocoaEventDispatcherPrivate::maybeStartCFRunLoopTimer()
CFTimeInterval interval;
// Q: when should the timer first next?
- struct timespec tv;
- if (timerInfoList.timerWait(tv)) {
+ if (auto opt = timerInfoList.timerWait()) {
// A: when we have timers to fire, of course
- interval = qMax(tv.tv_sec + tv.tv_nsec / 1000000000., 0.0000001);
+ DoubleSeconds secs{*opt};
+ interval = qMax(secs.count(), 0.0000001);
} else {
// no timers can fire, but we cannot stop the CFRunLoopTimer, set the timer to fire at some
// point in the distant future (the timer interval is one year)
@@ -160,10 +171,11 @@ void QCocoaEventDispatcherPrivate::maybeStopCFRunLoopTimer()
runLoopTimerRef = nullptr;
}
-void QCocoaEventDispatcher::registerTimer(int timerId, qint64 interval, Qt::TimerType timerType, QObject *obj)
+void QCocoaEventDispatcher::registerTimer(Qt::TimerId timerId, Duration interval,
+ Qt::TimerType timerType, QObject *obj)
{
#ifndef QT_NO_DEBUG
- if (timerId < 1 || interval < 0 || !obj) {
+ if (qToUnderlying(timerId) < 1 || interval.count() < 0 || !obj) {
qWarning("QCocoaEventDispatcher::registerTimer: invalid arguments");
return;
} else if (obj->thread() != thread() || thread() != QThread::currentThread()) {
@@ -177,10 +189,10 @@ void QCocoaEventDispatcher::registerTimer(int timerId, qint64 interval, Qt::Time
d->maybeStartCFRunLoopTimer();
}
-bool QCocoaEventDispatcher::unregisterTimer(int timerId)
+bool QCocoaEventDispatcher::unregisterTimer(Qt::TimerId timerId)
{
#ifndef QT_NO_DEBUG
- if (timerId < 1) {
+ if (qToUnderlying(timerId) < 1) {
qWarning("QCocoaEventDispatcher::unregisterTimer: invalid argument");
return false;
} else if (thread() != QThread::currentThread()) {
@@ -219,13 +231,13 @@ bool QCocoaEventDispatcher::unregisterTimers(QObject *obj)
return returnValue;
}
-QList<QCocoaEventDispatcher::TimerInfo>
-QCocoaEventDispatcher::registeredTimers(QObject *object) const
+QList<QCocoaEventDispatcher::TimerInfoV2>
+QCocoaEventDispatcher::timersForObject(QObject *object) const
{
#ifndef QT_NO_DEBUG
if (!object) {
qWarning("QCocoaEventDispatcher:registeredTimers: invalid argument");
- return QList<TimerInfo>();
+ return {};
}
#endif
@@ -364,6 +376,7 @@ bool QCocoaEventDispatcher::processEvents(QEventLoop::ProcessEventsFlags flags)
// [NSApp run], which is the normal code path for cocoa applications.
if (NSModalSession session = d->currentModalSession()) {
QBoolBlocker execGuard(d->currentExecIsNSAppRun, false);
+ qCDebug(lcEventDispatcher) << "Running modal session" << session;
while ([NSApp runModalSession:session] == NSModalResponseContinue && !d->interrupt) {
qt_mac_waitForMoreEvents(NSModalPanelRunLoopMode);
if (session != d->currentModalSessionCached) {
@@ -407,6 +420,7 @@ bool QCocoaEventDispatcher::processEvents(QEventLoop::ProcessEventsFlags flags)
// to use cocoa's native way of running modal sessions:
if (flags & QEventLoop::WaitForMoreEvents)
qt_mac_waitForMoreEvents(NSModalPanelRunLoopMode);
+ qCDebug(lcEventDispatcher) << "Running modal session" << session;
NSInteger status = [NSApp runModalSession:session];
if (status != NSModalResponseContinue && session == d->currentModalSessionCached) {
// INVARIANT: Someone called [NSApp stopModal:] from outside the event
@@ -527,17 +541,17 @@ bool QCocoaEventDispatcher::processEvents(QEventLoop::ProcessEventsFlags flags)
return retVal;
}
-int QCocoaEventDispatcher::remainingTime(int timerId)
+auto QCocoaEventDispatcher::remainingTime(Qt::TimerId timerId) const -> Duration
{
#ifndef QT_NO_DEBUG
- if (timerId < 1) {
+ if (qToUnderlying(timerId) < 1) {
qWarning("QCocoaEventDispatcher::remainingTime: invalid argument");
- return -1;
+ return Duration::min();
}
#endif
- Q_D(QCocoaEventDispatcher);
- return d->timerInfoList.timerRemainingTime(timerId);
+ Q_D(const QCocoaEventDispatcher);
+ return d->timerInfoList.remainingDuration(timerId);
}
void QCocoaEventDispatcher::wakeUp()
@@ -554,22 +568,42 @@ void QCocoaEventDispatcher::wakeUp()
void QCocoaEventDispatcherPrivate::ensureNSAppInitialized()
{
- // Some elements in Cocoa require NSApplication to be running before
- // they get fully initialized, in particular the menu bar. This
- // function is intended for cases where a dialog is told to execute before
- // QGuiApplication::exec is called, or the application spins the events loop
- // manually rather than calling QGuiApplication:exec.
- // The function makes sure that NSApplication starts running, but stops
- // it again as soon as the send posted events callback is called. That way
- // we let Cocoa finish the initialization it seems to need. We'll only
- // apply this trick at most once for any application, and we avoid doing it
- // for the common case where main just starts QGuiApplication::exec.
+ // Some elements in Cocoa require NSApplication to be initialized before
+ // use, for example the menu bar. Under normal circumstances this happens
+ // as part of [NSApp run], as a result of a call to QGuiApplication:exec(),
+ // but in the cases where a dialog is asked to execute before that happens,
+ // or the application spins the event loop manually via processEvents(),
+ // we need to explicitly ensure NSApplication initialization.
+
+ // We can unfortunately not do this via NSApplicationLoad(), as the function
+ // bails out early if there's already an NSApplication instance, which is
+ // the case if any code has called [NSApplication sharedApplication],
+ // or its short form 'NSApp'.
+
+ // Instead we do an actual [NSApp run], but stop the application as soon
+ // as possible, ensuring that AppKit will do the required initialization,
+ // including calling [NSApplication finishLaunching].
+
+ // We only apply this trick at most once for any application, and we avoid
+ // doing it for the common case where main just starts QGuiApplication::exec.
if (nsAppRunCalledByQt || [NSApp isRunning])
return;
+
+ qCDebug(lcEventDispatcher) << "Ensuring NSApplication is initialized";
nsAppRunCalledByQt = true;
- QBoolBlocker block1(interrupt, true);
- QBoolBlocker block2(currentExecIsNSAppRun, true);
+
+ // Stopping the application will still process runloop sources before
+ // actually stopping, so we need to explicitly guard our sources from
+ // doing anything, deferring their actions until later.
+ QBoolBlocker initializationGuard(initializingNSApplication, true);
+
+ CFRunLoopPerformBlock(mainRunLoop(), kCFRunLoopCommonModes, ^{
+ qCDebug(lcEventDispatcher) << "NSApplication has been initialized; Stopping NSApp";
+ [NSApp stop:NSApp];
+ cancelWaitForMoreEvents(); // Post event that wakes up the runloop
+ });
[NSApp run];
+ qCDebug(lcEventDispatcher) << "Finished ensuring NSApplication is initialized";
}
void QCocoaEventDispatcherPrivate::temporarilyStopAllModalSessions()
@@ -587,6 +621,8 @@ void QCocoaEventDispatcherPrivate::temporarilyStopAllModalSessions()
for (int i=0; i<stackSize; ++i) {
QCocoaModalSessionInfo &info = cocoaModalSessionStack[i];
if (info.session) {
+ qCDebug(lcEventDispatcher) << "Temporarily ending modal session" << info.session
+ << "for" << info.nswindow;
[NSApp endModalSession:info.session];
info.session = nullptr;
[(NSWindow*) info.nswindow release];
@@ -626,6 +662,8 @@ NSModalSession QCocoaEventDispatcherPrivate::currentModalSession()
[(NSWindow*) info.nswindow retain];
QRect rect = cocoaWindow->geometry();
info.session = [NSApp beginModalSessionForWindow:nswindow];
+ qCDebug(lcEventDispatcher) << "Begun modal session" << info.session
+ << "for" << nswindow;
// The call to beginModalSessionForWindow above processes events and may
// have deleted or destroyed the window. Check if it's still valid.
@@ -674,6 +712,8 @@ void QCocoaEventDispatcherPrivate::cleanupModalSessions()
currentModalSessionCached = nullptr;
if (info.session) {
Q_ASSERT(info.nswindow);
+ qCDebug(lcEventDispatcher) << "Ending modal session" << info.session
+ << "for" << info.nswindow;
[NSApp endModalSession:info.session];
[(NSWindow *)info.nswindow release];
}
@@ -686,6 +726,14 @@ void QCocoaEventDispatcherPrivate::cleanupModalSessions()
void QCocoaEventDispatcherPrivate::beginModalSession(QWindow *window)
{
+ qCDebug(lcEventDispatcher) << "Adding modal session for" << window;
+
+ if (std::any_of(cocoaModalSessionStack.constBegin(), cocoaModalSessionStack.constEnd(),
+ [&](const auto &sessionInfo) { return sessionInfo.window == window; })) {
+ qCWarning(lcEventDispatcher) << "Modal session for" << window << "already exists!";
+ return;
+ }
+
// We need to start spinning the modal session. Usually this is done with
// QDialog::exec() for Qt Widgets based applications, but for others that
// just call show(), we need to interrupt().
@@ -706,6 +754,8 @@ void QCocoaEventDispatcherPrivate::beginModalSession(QWindow *window)
void QCocoaEventDispatcherPrivate::endModalSession(QWindow *window)
{
+ qCDebug(lcEventDispatcher) << "Removing modal session for" << window;
+
Q_Q(QCocoaEventDispatcher);
// Mark all sessions attached to window as pending to be stopped. We do this
@@ -752,7 +802,7 @@ void qt_mac_maybeCancelWaitForMoreEventsForwarder(QAbstractEventDispatcher *even
}
QCocoaEventDispatcher::QCocoaEventDispatcher(QObject *parent)
- : QAbstractEventDispatcher(*new QCocoaEventDispatcherPrivate, parent)
+ : QAbstractEventDispatcherV2(*new QCocoaEventDispatcherPrivate, parent)
{
Q_D(QCocoaEventDispatcher);
@@ -789,21 +839,6 @@ QCocoaEventDispatcher::QCocoaEventDispatcher(QObject *parent)
QCocoaEventDispatcherPrivate::waitingObserverCallback,
&observerContext);
CFRunLoopAddObserver(mainRunLoop(), d->waitingObserver, kCFRunLoopCommonModes);
-
- /* The first cycle in the loop adds the source and the events of the source
- are not processed.
- We use an observer to process the posted events for the first
- execution of the loop. */
- CFRunLoopObserverContext firstTimeObserverContext;
- bzero(&firstTimeObserverContext, sizeof(CFRunLoopObserverContext));
- firstTimeObserverContext.info = d;
- d->firstTimeObserver = CFRunLoopObserverCreate(kCFAllocatorDefault,
- kCFRunLoopEntry,
- /* repeats = */ false,
- 0,
- QCocoaEventDispatcherPrivate::firstLoopEntry,
- &firstTimeObserverContext);
- CFRunLoopAddObserver(mainRunLoop(), d->firstTimeObserver, kCFRunLoopCommonModes);
}
void QCocoaEventDispatcherPrivate::waitingObserverCallback(CFRunLoopObserverRef,
@@ -868,18 +903,17 @@ void QCocoaEventDispatcherPrivate::processPostedEvents()
}
}
-void QCocoaEventDispatcherPrivate::firstLoopEntry(CFRunLoopObserverRef ref,
- CFRunLoopActivity activity,
- void *info)
-{
- Q_UNUSED(ref);
- Q_UNUSED(activity);
- static_cast<QCocoaEventDispatcherPrivate *>(info)->processPostedEvents();
-}
-
void QCocoaEventDispatcherPrivate::postedEventsSourceCallback(void *info)
{
QCocoaEventDispatcherPrivate *d = static_cast<QCocoaEventDispatcherPrivate *>(info);
+ if (d->initializingNSApplication) {
+ qCDebug(lcEventDispatcher) << "Deferring" << __FUNCTION__ << "due to NSApp initialization";
+ // We don't want to process any sources during explicit NSApplication
+ // initialization, so defer the source until the actual event processing.
+ CFRunLoopSourceSignal(d->postedEventsSource);
+ return;
+ }
+
if (d->processEventsCalled && (d->processEventsFlags & QEventLoop::EventLoopExec) == 0) {
// processEvents() was called "manually," ignore this source for now
d->maybeCancelWaitForMoreEvents();
@@ -943,7 +977,7 @@ QCocoaEventDispatcher::~QCocoaEventDispatcher()
{
Q_D(QCocoaEventDispatcher);
- qDeleteAll(d->timerInfoList);
+ d->timerInfoList.clearTimers();
d->maybeStopCFRunLoopTimer();
CFRunLoopRemoveSource(mainRunLoop(), d->activateTimersSourceRef, kCFRunLoopCommonModes);
CFRelease(d->activateTimersSourceRef);
@@ -952,6 +986,8 @@ QCocoaEventDispatcher::~QCocoaEventDispatcher()
for (int i = 0; i < d->cocoaModalSessionStack.count(); ++i) {
QCocoaModalSessionInfo &info = d->cocoaModalSessionStack[i];
if (info.session) {
+ qCDebug(lcEventDispatcher) << "Ending modal session" << info.session
+ << "for" << info.nswindow << "during shutdown";
[NSApp endModalSession:info.session];
[(NSWindow *)info.nswindow release];
}
@@ -970,9 +1006,6 @@ QCocoaEventDispatcher::~QCocoaEventDispatcher()
CFRunLoopObserverInvalidate(d->waitingObserver);
CFRelease(d->waitingObserver);
-
- CFRunLoopObserverInvalidate(d->firstTimeObserver);
- CFRelease(d->firstTimeObserver);
}
QtCocoaInterruptDispatcher* QtCocoaInterruptDispatcher::instance = nullptr;
diff --git a/src/plugins/platforms/cocoa/qcocoafiledialoghelper.h b/src/plugins/platforms/cocoa/qcocoafiledialoghelper.h
index 85e317b8ef..3ffccb10fd 100644
--- a/src/plugins/platforms/cocoa/qcocoafiledialoghelper.h
+++ b/src/plugins/platforms/cocoa/qcocoafiledialoghelper.h
@@ -38,6 +38,7 @@ public:
public: // for QNSOpenSavePanelDelegate
void panelClosed(NSInteger result);
+ void panelDirectoryDidChange(NSString *path);
private:
void createNSOpenSavePanelDelegate();
diff --git a/src/plugins/platforms/cocoa/qcocoafiledialoghelper.mm b/src/plugins/platforms/cocoa/qcocoafiledialoghelper.mm
index bb32774d1d..044a282686 100644
--- a/src/plugins/platforms/cocoa/qcocoafiledialoghelper.mm
+++ b/src/plugins/platforms/cocoa/qcocoafiledialoghelper.mm
@@ -18,6 +18,8 @@
#include <QtCore/qoperatingsystemversion.h>
#include <QtCore/qdir.h>
#include <QtCore/qregularexpression.h>
+#include <QtCore/qpointer.h>
+#include <QtCore/private/qcore_mac_p.h>
#include <QtGui/qguiapplication.h>
#include <QtGui/private/qguiapplication_p.h>
@@ -25,6 +27,8 @@
#include <qpa/qplatformtheme.h>
#include <qpa/qplatformnativeinterface.h>
+#include <UniformTypeIdentifiers/UniformTypeIdentifiers.h>
+
QT_USE_NAMESPACE
using namespace Qt::StringLiterals;
@@ -52,13 +56,12 @@ typedef QSharedPointer<QFileDialogOptions> SharedPointerFileDialogOptions;
NSView *m_accessoryView;
NSPopUpButton *m_popupButton;
NSTextField *m_textField;
- QCocoaFileDialogHelper *m_helper;
- NSString *m_currentDirectory;
+ QPointer<QCocoaFileDialogHelper> m_helper;
SharedPointerFileDialogOptions m_options;
- QString *m_currentSelection;
- QStringList *m_nameFilterDropDownList;
- QStringList *m_selectedNameFilter;
+ QString m_currentSelection;
+ QStringList m_nameFilterDropDownList;
+ QStringList m_selectedNameFilter;
}
- (instancetype)initWithAcceptMode:(const QString &)selectFile
@@ -78,26 +81,56 @@ typedef QSharedPointer<QFileDialogOptions> SharedPointerFileDialogOptions;
m_helper = helper;
- m_nameFilterDropDownList = new QStringList(m_options->nameFilters());
+ m_nameFilterDropDownList = m_options->nameFilters();
QString selectedVisualNameFilter = m_options->initiallySelectedNameFilter();
- m_selectedNameFilter = new QStringList([self findStrippedFilterWithVisualFilterName:selectedVisualNameFilter]);
-
- QFileInfo sel(selectFile);
+ m_selectedNameFilter = [self findStrippedFilterWithVisualFilterName:selectedVisualNameFilter];
+
+ m_panel.extensionHidden = [&]{
+ for (const auto &nameFilter : m_nameFilterDropDownList) {
+ const auto extensions = QPlatformFileDialogHelper::cleanFilterList(nameFilter);
+ for (const auto &extension : extensions) {
+ // Explicitly show extensions if we detect a filter
+ // of "all files", as clicking a single file with
+ // extensions hidden will then populate the name
+ // field with only the file name, without any
+ // extension.
+ if (extension == "*"_L1 || extension == "*.*"_L1)
+ return false;
+
+ // Explicitly show extensions if we detect a filter
+ // that has a multi-part extension. This prevents
+ // confusing situations where the user clicks e.g.
+ // 'foo.tar.gz' and 'foo.tar' is populated in the
+ // file name box, but when then clicking save macOS
+ // will warn that the file needs to end in .gz,
+ // due to thinking the user tried to save the file
+ // as a 'tar' file instead. Unfortunately this
+ // property can only be set before the panel is
+ // shown, so we can't toggle it on and off based
+ // on the active filter.
+ if (extension.count('.') > 1)
+ return false;
+ }
+ }
+ return true;
+ }();
+
+ const QFileInfo sel(selectFile);
if (sel.isDir() && !sel.isBundle()){
- m_currentDirectory = [sel.absoluteFilePath().toNSString() retain];
- m_currentSelection = new QString;
+ m_panel.directoryURL = [NSURL fileURLWithPath:sel.absoluteFilePath().toNSString()];
+ m_currentSelection.clear();
} else {
- m_currentDirectory = [sel.absolutePath().toNSString() retain];
- m_currentSelection = new QString(sel.absoluteFilePath());
+ m_panel.directoryURL = [NSURL fileURLWithPath:sel.absolutePath().toNSString()];
+ m_currentSelection = sel.absoluteFilePath();
}
[self createPopUpButton:selectedVisualNameFilter hideDetails:options->testOption(QFileDialogOptions::HideNameFilterDetails)];
[self createTextField];
[self createAccessory];
- m_panel.accessoryView = m_nameFilterDropDownList->size() > 1 ? m_accessoryView : nil;
+ m_panel.accessoryView = m_nameFilterDropDownList.size() > 1 ? m_accessoryView : nil;
// -setAccessoryView: can result in -panel:directoryDidChange:
- // resetting our m_currentDirectory, set the delegate
+ // resetting our current directory. Set the delegate
// here to make sure it gets the correct value.
m_panel.delegate = self;
@@ -111,10 +144,6 @@ typedef QSharedPointer<QFileDialogOptions> SharedPointerFileDialogOptions;
- (void)dealloc
{
- delete m_nameFilterDropDownList;
- delete m_selectedNameFilter;
- delete m_currentSelection;
-
[m_panel orderOut:m_panel];
m_panel.accessoryView = nil;
[m_popupButton release];
@@ -122,19 +151,17 @@ typedef QSharedPointer<QFileDialogOptions> SharedPointerFileDialogOptions;
[m_accessoryView release];
m_panel.delegate = nil;
[m_panel release];
- [m_currentDirectory release];
[super dealloc];
}
- (bool)showPanel:(Qt::WindowModality) windowModality withParent:(QWindow *)parent
{
- QFileInfo info(*m_currentSelection);
+ const QFileInfo info(m_currentSelection);
NSString *filepath = info.filePath().toNSString();
NSURL *url = [NSURL fileURLWithPath:filepath isDirectory:info.isDir()];
bool selectable = (m_options->acceptMode() == QFileDialogOptions::AcceptSave)
|| [self panel:m_panel shouldEnableURL:url];
- m_panel.directoryURL = [NSURL fileURLWithPath:m_currentDirectory];
m_panel.nameFieldStringValue = selectable ? info.fileName().toNSString() : @"";
[self updateProperties];
@@ -159,6 +186,8 @@ typedef QSharedPointer<QFileDialogOptions> SharedPointerFileDialogOptions;
// QEventLoop has been interrupted, and the second-most event loop has not
// yet been reactivated (regardless if [NSApp run] is still on the stack)),
// showing a native modal dialog will fail.
+ if (!m_helper)
+ return;
QMacAutoReleasePool pool;
@@ -180,7 +209,7 @@ typedef QSharedPointer<QFileDialogOptions> SharedPointerFileDialogOptions;
- (void)closePanel
{
- *m_currentSelection = QString::fromNSString(m_panel.URL.path).normalized(QString::NormalizationForm_C);
+ m_currentSelection = QString::fromNSString(m_panel.URL.path).normalized(QString::NormalizationForm_C);
if (m_panel.sheet)
[NSApp endSheet:m_panel];
@@ -190,19 +219,6 @@ typedef QSharedPointer<QFileDialogOptions> SharedPointerFileDialogOptions;
[m_panel close];
}
-- (BOOL)isHiddenFileAtURL:(NSURL *)url
-{
- BOOL hidden = NO;
- if (url) {
- CFBooleanRef isHiddenProperty;
- if (CFURLCopyResourcePropertyForKey((__bridge CFURLRef)url, kCFURLIsHiddenKey, &isHiddenProperty, nullptr)) {
- hidden = CFBooleanGetValue(isHiddenProperty);
- CFRelease(isHiddenProperty);
- }
- }
- return hidden;
-}
-
- (BOOL)panel:(id)sender shouldEnableURL:(NSURL *)url
{
Q_UNUSED(sender);
@@ -211,64 +227,140 @@ typedef QSharedPointer<QFileDialogOptions> SharedPointerFileDialogOptions;
if (!filename.length)
return NO;
- // Always accept directories regardless of their names (unless it is a bundle):
- NSFileManager *fm = NSFileManager.defaultManager;
- NSDictionary *fileAttrs = [fm attributesOfItemAtPath:filename error:nil];
- if (!fileAttrs)
- return NO; // Error accessing the file means 'no'.
- NSString *fileType = fileAttrs.fileType;
- bool isDir = [fileType isEqualToString:NSFileTypeDirectory];
- if (isDir) {
- if (!m_panel.treatsFilePackagesAsDirectories) {
- if ([NSWorkspace.sharedWorkspace isFilePackageAtPath:filename] == NO)
- return YES;
- }
+ const QFileInfo fileInfo(QString::fromNSString(filename));
+
+ // Always accept directories regardless of their names.
+ // This also includes symlinks and aliases to directories.
+ if (fileInfo.isDir()) {
+ // Unless it's a bundle, and we should treat bundles as files.
+ // FIXME: We'd like to use QFileInfo::isBundle() here, but the
+ // detection in QFileInfo goes deeper than NSWorkspace does
+ // (likely a bug), and as a result causes TCC permission
+ // dialogs to pop up when used.
+ bool treatBundlesAsFiles = !m_panel.treatsFilePackagesAsDirectories;
+ if (!(treatBundlesAsFiles && [NSWorkspace.sharedWorkspace isFilePackageAtPath:filename]))
+ return YES;
}
- // Treat symbolic links and aliases to directories like directories
- QFileInfo fileInfo(QString::fromNSString(filename));
- if (fileInfo.isSymLink() && QFileInfo(fileInfo.symLinkTarget()).isDir())
- return YES;
-
- QString qtFileName = fileInfo.fileName();
- // No filter means accept everything
- bool nameMatches = m_selectedNameFilter->isEmpty();
- // Check if the current file name filter accepts the file:
- for (int i = 0; !nameMatches && i < m_selectedNameFilter->size(); ++i) {
- if (QDir::match(m_selectedNameFilter->at(i), qtFileName))
- nameMatches = true;
- }
- if (!nameMatches)
+ if (![self fileInfoMatchesCurrentNameFilter:fileInfo])
return NO;
QDir::Filters filter = m_options->filter();
- if ((!(filter & (QDir::Dirs | QDir::AllDirs)) && isDir)
- || (!(filter & QDir::Files) && [fileType isEqualToString:NSFileTypeRegular])
- || ((filter & QDir::NoSymLinks) && [fileType isEqualToString:NSFileTypeSymbolicLink]))
+ if ((!(filter & (QDir::Dirs | QDir::AllDirs)) && fileInfo.isDir())
+ || (!(filter & QDir::Files) && (fileInfo.isFile() && !fileInfo.isSymLink()))
+ || ((filter & QDir::NoSymLinks) && fileInfo.isSymLink()))
return NO;
bool filterPermissions = ((filter & QDir::PermissionMask)
&& (filter & QDir::PermissionMask) != QDir::PermissionMask);
if (filterPermissions) {
- if ((!(filter & QDir::Readable) && [fm isReadableFileAtPath:filename])
- || (!(filter & QDir::Writable) && [fm isWritableFileAtPath:filename])
- || (!(filter & QDir::Executable) && [fm isExecutableFileAtPath:filename]))
+ if ((!(filter & QDir::Readable) && fileInfo.isReadable())
+ || (!(filter & QDir::Writable) && fileInfo.isWritable())
+ || (!(filter & QDir::Executable) && fileInfo.isExecutable()))
return NO;
}
- if (!(filter & QDir::Hidden)
- && (qtFileName.startsWith(u'.') || [self isHiddenFileAtURL:url]))
+
+ // We control the visibility of hidden files via the showsHiddenFiles
+ // property on the panel, based on QDir::Hidden being set. But the user
+ // can also toggle this via the Command+Shift+. keyboard shortcut,
+ // in which case they have explicitly requested to show hidden files,
+ // and we should enable them even if QDir::Hidden was not set. In
+ // effect, we don't need to filter on QDir::Hidden here.
+
+ return YES;
+}
+
+- (BOOL)panel:(id)sender validateURL:(NSURL *)url error:(NSError * _Nullable *)outError
+{
+ Q_ASSERT(sender == m_panel);
+
+ if (!m_panel.allowedFileTypes && !m_selectedNameFilter.isEmpty()) {
+ // The save panel hasn't done filtering on our behalf,
+ // either because we couldn't represent the filter via
+ // allowedFileTypes, or we opted out due to a multi part
+ // extension, so do the filtering/validation ourselves.
+ QFileInfo fileInfo(QString::fromNSString(url.path).normalized(QString::NormalizationForm_C));
+
+ if ([self fileInfoMatchesCurrentNameFilter:fileInfo])
+ return YES;
+
+ if (fileInfo.suffix().isEmpty()) {
+ // The filter requires a file name with an extension.
+ // We're going to add a default file name in selectedFiles,
+ // to match the native behavior. Check now that we can
+ // overwrite the file, if is already exists.
+ fileInfo = [self applyDefaultSuffixFromCurrentNameFilter:fileInfo];
+
+ if (!fileInfo.exists() || m_options->testOption(QFileDialogOptions::DontConfirmOverwrite))
+ return YES;
+
+ QMacAutoReleasePool pool;
+ auto *alert = [[NSAlert new] autorelease];
+ alert.alertStyle = NSAlertStyleCritical;
+
+ alert.messageText = [NSString stringWithFormat:qt_mac_AppKitString(@"SavePanel",
+ @"\\U201c%@\\U201d already exists. Do you want to replace it?"),
+ fileInfo.fileName().toNSString()];
+ alert.informativeText = [NSString stringWithFormat:qt_mac_AppKitString(@"SavePanel",
+ @"A file or folder with the same name already exists in the folder %@. "
+ "Replacing it will overwrite its current contents."),
+ fileInfo.absoluteDir().dirName().toNSString()];
+
+ auto *replaceButton = [alert addButtonWithTitle:qt_mac_AppKitString(@"SavePanel", @"Replace")];
+ replaceButton.hasDestructiveAction = YES;
+ replaceButton.tag = 1337;
+ [alert addButtonWithTitle:qt_mac_AppKitString(@"Common", @"Cancel")];
+
+ [alert beginSheetModalForWindow:m_panel
+ completionHandler:^(NSModalResponse returnCode) {
+ [NSApp stopModalWithCode:returnCode];
+ }];
+ return [NSApp runModalForWindow:alert.window] == replaceButton.tag;
+ } else {
+ QFileInfo firstFilter(m_selectedNameFilter.first());
+ auto *domain = qGuiApp->organizationDomain().toNSString();
+ *outError = [NSError errorWithDomain:domain code:0 userInfo:@{
+ NSLocalizedDescriptionKey:[NSString stringWithFormat:qt_mac_AppKitString(@"SavePanel",
+ @"You cannot save this document with extension \\U201c.%1$@\\U201d at the end "
+ "of the name. The required extension is \\U201c.%2$@\\U201d."),
+ fileInfo.completeSuffix().toNSString(), firstFilter.completeSuffix().toNSString()]
+ }];
return NO;
+ }
+ }
return YES;
}
+- (QFileInfo)applyDefaultSuffixFromCurrentNameFilter:(const QFileInfo &)fileInfo
+{
+ QFileInfo filterInfo(m_selectedNameFilter.first());
+ return QFileInfo(fileInfo.absolutePath(),
+ fileInfo.baseName() + '.' + filterInfo.completeSuffix());
+}
+
+- (bool)fileInfoMatchesCurrentNameFilter:(const QFileInfo &)fileInfo
+{
+ // No filter means accept everything
+ if (m_selectedNameFilter.isEmpty())
+ return true;
+
+ // Check if the current file name filter accepts the file
+ for (const auto &filter : m_selectedNameFilter) {
+ if (QDir::match(filter, fileInfo.fileName()))
+ return true;
+ }
+
+ return false;
+}
+
- (void)setNameFilters:(const QStringList &)filters hideDetails:(BOOL)hideDetails
{
[m_popupButton removeAllItems];
- *m_nameFilterDropDownList = filters;
+ m_nameFilterDropDownList = filters;
if (filters.size() > 0){
for (int i = 0; i < filters.size(); ++i) {
- QString filter = hideDetails ? [self removeExtensions:filters.at(i)] : filters.at(i);
+ const QString filter = hideDetails ? [self removeExtensions:filters.at(i)] : filters.at(i);
[m_popupButton.menu addItemWithTitle:filter.toNSString() action:nil keyEquivalent:@""];
}
[m_popupButton selectItemAtIndex:0];
@@ -284,8 +376,10 @@ typedef QSharedPointer<QFileDialogOptions> SharedPointerFileDialogOptions;
{
// This m_delegate function is called when the _name_ filter changes.
Q_UNUSED(sender);
- QString selection = m_nameFilterDropDownList->value([m_popupButton indexOfSelectedItem]);
- *m_selectedNameFilter = [self findStrippedFilterWithVisualFilterName:selection];
+ if (!m_helper)
+ return;
+ const QString selection = m_nameFilterDropDownList.value([m_popupButton indexOfSelectedItem]);
+ m_selectedNameFilter = [self findStrippedFilterWithVisualFilterName:selection];
[m_panel validateVisibleColumns];
[self updateProperties];
@@ -304,18 +398,25 @@ typedef QSharedPointer<QFileDialogOptions> SharedPointerFileDialogOptions;
}
return result;
} else {
- QList<QUrl> result;
QString filename = QString::fromNSString(m_panel.URL.path).normalized(QString::NormalizationForm_C);
- const QString defaultSuffix = m_options->defaultSuffix();
- const QFileInfo fileInfo(filename);
+ QFileInfo fileInfo(filename);
+
+ if (fileInfo.suffix().isEmpty() && ![self fileInfoMatchesCurrentNameFilter:fileInfo]) {
+ // We end up in this situation if we accept a file name without extension
+ // in panel:validateURL:error. If so, we match the behavior of the native
+ // save dialog and add the first of the accepted extension from the filter.
+ fileInfo = [self applyDefaultSuffixFromCurrentNameFilter:fileInfo];
+ }
// If neither the user or the NSSavePanel have provided a suffix, use
// the default suffix (if it exists).
- if (fileInfo.suffix().isEmpty() && !defaultSuffix.isEmpty())
- filename.append('.').append(defaultSuffix);
+ const QString defaultSuffix = m_options->defaultSuffix();
+ if (fileInfo.suffix().isEmpty() && !defaultSuffix.isEmpty()) {
+ fileInfo.setFile(fileInfo.absolutePath(),
+ fileInfo.baseName() + '.' + defaultSuffix);
+ }
- result << QUrl::fromLocalFile(filename);
- return result;
+ return { QUrl::fromLocalFile(fileInfo.filePath()) };
}
}
@@ -347,19 +448,25 @@ typedef QSharedPointer<QFileDialogOptions> SharedPointerFileDialogOptions;
m_panel.allowedFileTypes = [self computeAllowedFileTypes];
- // Explicitly show extensions if we detect a filter
- // that has a multi-part extension. This prevents
- // confusing situations where the user clicks e.g.
- // 'foo.tar.gz' and 'foo.tar' is populated in the
- // file name box, but when then clicking save macOS
- // will warn that the file needs to end in .gz,
- // due to thinking the user tried to save the file
- // as a 'tar' file instead. Unfortunately this
- // property can only be set before the panel is
- // shown, so it will not have any effect when
- // switching filters in an already opened dialog.
- if (m_panel.allowedFileTypes.count > 2)
- m_panel.extensionHidden = NO;
+ // Setting allowedFileTypes to nil is not enough to reset any
+ // automatically added extension based on a previous filter.
+ // This is problematic because extensions can in some cases
+ // be hidden from the user, resulting in confusion when the
+ // resulting file name doesn't match the current empty filter.
+ // We work around this by temporarily resetting the allowed
+ // content type to one without an extension, which forces
+ // the save panel to update and remove the extension.
+ const bool nameFieldHasExtension = m_panel.nameFieldStringValue.pathExtension.length > 0;
+ if (!m_panel.allowedFileTypes && !nameFieldHasExtension && !openpanel_cast(m_panel)) {
+ if (!UTTypeDirectory.preferredFilenameExtension) {
+ m_panel.allowedContentTypes = @[ UTTypeDirectory ];
+ m_panel.allowedFileTypes = nil;
+ } else {
+ qWarning() << "UTTypeDirectory unexpectedly reported an extension";
+ }
+ }
+
+ m_panel.showsHiddenFiles = m_options->filter().testFlag(QDir::Hidden);
if (m_panel.visible)
[m_panel validateVisibleColumns];
@@ -368,10 +475,22 @@ typedef QSharedPointer<QFileDialogOptions> SharedPointerFileDialogOptions;
- (void)panelSelectionDidChange:(id)sender
{
Q_UNUSED(sender);
+
+ if (!m_helper)
+ return;
+
+ // Save panels only allow you to select directories, which
+ // means currentChanged will only be emitted when selecting
+ // a directory, and if so, with the latest chosen file name,
+ // which is confusing and inconsistent. We choose to bail
+ // out entirely for save panels, to give consistent behavior.
+ if (!openpanel_cast(m_panel))
+ return;
+
if (m_panel.visible) {
- QString selection = QString::fromNSString(m_panel.URL.path);
- if (selection != *m_currentSelection) {
- *m_currentSelection = selection;
+ const QString selection = QString::fromNSString(m_panel.URL.path);
+ if (selection != m_currentSelection) {
+ m_currentSelection = selection;
emit m_helper->currentChanged(QUrl::fromLocalFile(selection));
}
}
@@ -381,14 +500,10 @@ typedef QSharedPointer<QFileDialogOptions> SharedPointerFileDialogOptions;
{
Q_UNUSED(sender);
- if (!(path && path.length) || [path isEqualToString:m_currentDirectory])
+ if (!m_helper)
return;
- [m_currentDirectory release];
- m_currentDirectory = [path retain];
-
- // ### fixme: priv->setLastVisitedDirectory(newDir);
- emit m_helper->directoryEntered(QUrl::fromLocalFile(QString::fromNSString(m_currentDirectory)));
+ m_helper->panelDirectoryDidChange(path);
}
/*
@@ -396,11 +511,9 @@ typedef QSharedPointer<QFileDialogOptions> SharedPointerFileDialogOptions;
for the current name filter, and updates the save panel.
If a filter do not conform to the format *.xyz or * or *.*,
- all files types are allowed.
-
- Extensions with more than one part (e.g. "tar.gz") are
- reduced to their final part, as NSSavePanel does not deal
- well with multi-part extensions.
+ or contains an extensions with more than one part (e.g. "tar.gz")
+ we treat that as allowing all file types, and do our own
+ validation in panel:validateURL:error.
*/
- (NSArray<NSString*>*)computeAllowedFileTypes
{
@@ -408,7 +521,7 @@ typedef QSharedPointer<QFileDialogOptions> SharedPointerFileDialogOptions;
return nil; // panel:shouldEnableURL: does the file filtering for NSOpenPanel
QStringList fileTypes;
- for (const QString &filter : *m_selectedNameFilter) {
+ for (const QString &filter : std::as_const(m_selectedNameFilter)) {
if (!filter.startsWith("*."_L1))
continue;
@@ -419,6 +532,9 @@ typedef QSharedPointer<QFileDialogOptions> SharedPointerFileDialogOptions;
continue;
auto extensions = filter.split('.', Qt::SkipEmptyParts);
+ if (extensions.count() > 2)
+ return nil;
+
fileTypes += extensions.last();
}
@@ -455,10 +571,10 @@ typedef QSharedPointer<QFileDialogOptions> SharedPointerFileDialogOptions;
m_popupButton.target = self;
m_popupButton.action = @selector(filterChanged:);
- if (m_nameFilterDropDownList->size() > 0) {
+ if (!m_nameFilterDropDownList.isEmpty()) {
int filterToUse = -1;
- for (int i = 0; i < m_nameFilterDropDownList->size(); ++i) {
- QString currentFilter = m_nameFilterDropDownList->at(i);
+ for (int i = 0; i < m_nameFilterDropDownList.size(); ++i) {
+ const QString currentFilter = m_nameFilterDropDownList.at(i);
if (selectedFilter == currentFilter ||
(filterToUse == -1 && currentFilter.startsWith(selectedFilter)))
filterToUse = i;
@@ -472,9 +588,9 @@ typedef QSharedPointer<QFileDialogOptions> SharedPointerFileDialogOptions;
- (QStringList) findStrippedFilterWithVisualFilterName:(QString)name
{
- for (int i = 0; i < m_nameFilterDropDownList->size(); ++i) {
- if (m_nameFilterDropDownList->at(i).startsWith(name))
- return QPlatformFileDialogHelper::cleanFilterList(m_nameFilterDropDownList->at(i));
+ for (const QString &currentFilter : std::as_const(m_nameFilterDropDownList)) {
+ if (currentFilter.startsWith(name))
+ return QPlatformFileDialogHelper::cleanFilterList(currentFilter);
}
return QStringList();
}
@@ -515,21 +631,32 @@ void QCocoaFileDialogHelper::panelClosed(NSInteger result)
void QCocoaFileDialogHelper::setDirectory(const QUrl &directory)
{
+ m_directory = directory;
+
if (m_delegate)
m_delegate->m_panel.directoryURL = [NSURL fileURLWithPath:directory.toLocalFile().toNSString()];
- else
- m_directory = directory;
}
QUrl QCocoaFileDialogHelper::directory() const
{
- if (m_delegate) {
- QString path = QString::fromNSString(m_delegate->m_panel.directoryURL.path).normalized(QString::NormalizationForm_C);
- return QUrl::fromLocalFile(path);
- }
return m_directory;
}
+void QCocoaFileDialogHelper::panelDirectoryDidChange(NSString *path)
+{
+ if (!path || [path isEqual:NSNull.null] || !path.length)
+ return;
+
+ const auto oldDirectory = m_directory;
+ m_directory = QUrl::fromLocalFile(
+ QString::fromNSString(path).normalized(QString::NormalizationForm_C));
+
+ if (m_directory != oldDirectory) {
+ // FIXME: Plumb old directory back to QFileDialog's lastVisitedDir?
+ emit directoryEntered(m_directory);
+ }
+}
+
void QCocoaFileDialogHelper::selectFile(const QUrl &filename)
{
QString filePath = filename.toLocalFile();
diff --git a/src/plugins/platforms/cocoa/qcocoafontdialoghelper.mm b/src/plugins/platforms/cocoa/qcocoafontdialoghelper.mm
index ac5d784b79..5cdf6bf02c 100644
--- a/src/plugins/platforms/cocoa/qcocoafontdialoghelper.mm
+++ b/src/plugins/platforms/cocoa/qcocoafontdialoghelper.mm
@@ -166,7 +166,17 @@ QT_NAMESPACE_ALIAS_OBJC_CLASS(QNSFontPanelDelegate);
{
mDialogIsExecuting = false;
mResultSet = false;
- [mFontPanel makeKeyAndOrderFront:mFontPanel];
+ // Make this an asynchronous call, so the panel is made key only
+ // in the next event loop run. This is to make sure that by
+ // the time the modal loop is run in runModalForWindow below,
+ // which internally also sets the panel to key window,
+ // the panel is not yet key, and the NSApp still has the right
+ // reference to the _previousKeyWindow. Otherwise both NSApp.key
+ // and NSApp._previousKeyWindow would wrongly point to the panel,
+ // loosing any reference to the window that was key before.
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [mFontPanel makeKeyAndOrderFront:mFontPanel];
+ });
}
- (BOOL)runApplicationModalPanel
diff --git a/src/plugins/platforms/cocoa/qcocoaglcontext.mm b/src/plugins/platforms/cocoa/qcocoaglcontext.mm
index 807f502275..a65311175f 100644
--- a/src/plugins/platforms/cocoa/qcocoaglcontext.mm
+++ b/src/plugins/platforms/cocoa/qcocoaglcontext.mm
@@ -8,6 +8,8 @@
#include "qcocoahelpers.h"
#include "qcocoascreen.h"
+#include <QtCore/private/qcore_mac_p.h>
+
#include <qdebug.h>
#include <dlfcn.h>
@@ -415,7 +417,7 @@ bool QCocoaGLContext::setDrawable(QPlatformSurface *surface)
// NSOpenGLContext is not re-entrant. Even when using separate contexts per thread,
// view, and window, calls into the API will still deadlock. For more information
// see https://openradar.appspot.com/37064579
-static QMutex s_reentrancyMutex;
+Q_CONSTINIT static QMutex s_reentrancyMutex;
void QCocoaGLContext::update()
{
diff --git a/src/plugins/platforms/cocoa/qcocoahelpers.h b/src/plugins/platforms/cocoa/qcocoahelpers.h
index 869b245911..c6862a9e65 100644
--- a/src/plugins/platforms/cocoa/qcocoahelpers.h
+++ b/src/plugins/platforms/cocoa/qcocoahelpers.h
@@ -41,6 +41,8 @@ Q_DECLARE_LOGGING_CATEGORY(lcQpaScreen)
Q_DECLARE_LOGGING_CATEGORY(lcQpaApplication)
Q_DECLARE_LOGGING_CATEGORY(lcQpaClipboard)
Q_DECLARE_LOGGING_CATEGORY(lcInputDevices)
+Q_DECLARE_LOGGING_CATEGORY(lcQpaDialogs)
+Q_DECLARE_LOGGING_CATEGORY(lcQpaMenus)
class QPixmap;
class QString;
@@ -54,16 +56,6 @@ NSDragOperation qt_mac_mapDropActions(Qt::DropActions actions);
Qt::DropAction qt_mac_mapNSDragOperation(NSDragOperation nsActions);
Qt::DropActions qt_mac_mapNSDragOperations(NSDragOperation nsActions);
-template <typename T>
-typename std::enable_if<std::is_pointer<T>::value, T>::type
-qt_objc_cast(id object)
-{
- if ([object isKindOfClass:[typename std::remove_pointer<T>::type class]])
- return static_cast<T>(object);
-
- return nil;
-}
-
QT_MANGLE_NAMESPACE(QNSView) *qnsview_cast(NSView *view);
// Misc
@@ -86,6 +78,9 @@ Qt::MouseButtons currentlyPressedMouseButtons();
// accelerators.
QString qt_mac_removeAmpersandEscapes(QString s);
+// Similar to __NXKitString for localized AppKit strings
+NSString *qt_mac_AppKitString(NSString *table, NSString *key);
+
enum {
QtCocoaEventSubTypeWakeup = SHRT_MAX,
QtCocoaEventSubTypePostMessage = SHRT_MAX-1
@@ -313,7 +308,7 @@ QSendSuperHelper<Args...> qt_objcDynamicSuperHelper(id receiver, SEL selector, A
// -------------------------------------------------------------------------
-struct InputMethodQueryResult : public QHash<Qt::InputMethodQuery, QVariant>
+struct InputMethodQueryResult : public QHash<int, QVariant>
{
operator bool() { return !isEmpty(); }
};
diff --git a/src/plugins/platforms/cocoa/qcocoahelpers.mm b/src/plugins/platforms/cocoa/qcocoahelpers.mm
index 0a6a2a7d04..1eba88d5e3 100644
--- a/src/plugins/platforms/cocoa/qcocoahelpers.mm
+++ b/src/plugins/platforms/cocoa/qcocoahelpers.mm
@@ -28,6 +28,8 @@ Q_LOGGING_CATEGORY(lcQpaScreen, "qt.qpa.screen", QtCriticalMsg);
Q_LOGGING_CATEGORY(lcQpaApplication, "qt.qpa.application");
Q_LOGGING_CATEGORY(lcQpaClipboard, "qt.qpa.clipboard")
Q_LOGGING_CATEGORY(lcInputDevices, "qt.qpa.input.devices")
+Q_LOGGING_CATEGORY(lcQpaDialogs, "qt.qpa.dialogs")
+Q_LOGGING_CATEGORY(lcQpaMenus, "qt.qpa.menus")
//
// Conversion Functions
@@ -334,6 +336,15 @@ QString qt_mac_removeAmpersandEscapes(QString s)
return QPlatformTheme::removeMnemonics(s).trimmed();
}
+NSString *qt_mac_AppKitString(NSString *table, NSString *key)
+{
+ static const NSBundle *appKit = [NSBundle bundleForClass:NSApplication.class];
+ if (!appKit)
+ return key;
+
+ return [appKit localizedStringForKey:key value:nil table:table];
+}
+
QT_END_NAMESPACE
/*! \internal
diff --git a/src/plugins/platforms/cocoa/qcocoainputcontext.mm b/src/plugins/platforms/cocoa/qcocoainputcontext.mm
index b242cd69c6..70461376e2 100644
--- a/src/plugins/platforms/cocoa/qcocoainputcontext.mm
+++ b/src/plugins/platforms/cocoa/qcocoainputcontext.mm
@@ -150,11 +150,18 @@ void QCocoaInputContext::updateLocale()
QString language = QString::fromNSString(languages.firstObject);
QLocale locale(language);
- if (m_locale != locale) {
+
+ bool localeUpdated = m_locale != locale;
+ static bool firstUpdate = true;
+
+ m_locale = locale;
+
+ if (localeUpdated && !firstUpdate) {
qCDebug(lcQpaInputMethods) << "Reporting new locale" << locale;
- m_locale = locale;
emitLocaleChanged();
}
+
+ firstUpdate = false;
}
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/cocoa/qcocoaintegration.h b/src/plugins/platforms/cocoa/qcocoaintegration.h
index c6d73e3c06..664700cf51 100644
--- a/src/plugins/platforms/cocoa/qcocoaintegration.h
+++ b/src/plugins/platforms/cocoa/qcocoaintegration.h
@@ -20,7 +20,9 @@
#include <QtCore/QScopedPointer>
#include <qpa/qplatformintegration.h>
#include <QtGui/private/qcoretextfontdatabase_p.h>
-#include <QtGui/private/qopenglcontext_p.h>
+#ifndef QT_NO_OPENGL
+# include <QtGui/private/qopenglcontext_p.h>
+#endif
#include <QtGui/private/qapplekeymapper_p.h>
Q_FORWARD_DECLARE_OBJC_CLASS(NSToolbar);
@@ -82,14 +84,10 @@ public:
QCocoaServices *services() const override;
QVariant styleHint(StyleHint hint) const override;
- Qt::KeyboardModifiers queryKeyboardModifiers() const override;
- QList<int> possibleKeys(const QKeyEvent *event) const override;
-
- void setToolbar(QWindow *window, NSToolbar *toolbar);
- NSToolbar *toolbar(QWindow *window) const;
- void clearToolbars();
+ QPlatformKeyMapper *keyMapper() const override;
void setApplicationIcon(const QIcon &icon) const override;
+ void setApplicationBadge(qint64 number) override;
void beep() const override;
void quit() const override;
@@ -119,7 +117,6 @@ private:
#if QT_CONFIG(vulkan)
mutable QCocoaVulkanInstance *mCocoaVulkanInstance = nullptr;
#endif
- QHash<QWindow *, NSToolbar *> mToolbars;
QCocoaWindowManager m_windowManager;
};
diff --git a/src/plugins/platforms/cocoa/qcocoaintegration.mm b/src/plugins/platforms/cocoa/qcocoaintegration.mm
index 9911263c51..2ce39ff897 100644
--- a/src/plugins/platforms/cocoa/qcocoaintegration.mm
+++ b/src/plugins/platforms/cocoa/qcocoaintegration.mm
@@ -30,12 +30,19 @@
#include <QtCore/qcoreapplication.h>
#include <QtGui/qpointingdevice.h>
+#include <QtCore/private/qcore_mac_p.h>
#include <QtGui/private/qcoregraphics_p.h>
-#include <QtGui/private/qopenglcontext_p.h>
+#include <QtGui/private/qmacmimeregistry_p.h>
+#ifndef QT_NO_OPENGL
+# include <QtGui/private/qopenglcontext_p.h>
+#endif
#include <QtGui/private/qrhibackingstore_p.h>
#include <QtGui/private/qfontengine_coretext_p.h>
#include <IOKit/graphics/IOGraphicsLib.h>
+#include <UniformTypeIdentifiers/UTCoreTypes.h>
+
+#include <inttypes.h>
static void initResources()
{
@@ -118,9 +125,9 @@ QCocoaIntegration::QCocoaIntegration(const QStringList &paramList)
#endif
mFontDb.reset(new QCoreTextFontDatabaseEngineFactory<QCoreTextFontEngine>);
- QString icStr = QPlatformInputContextFactory::requested();
- icStr.isNull() ? mInputContext.reset(new QCocoaInputContext)
- : mInputContext.reset(QPlatformInputContextFactory::create(icStr));
+ auto icStrs = QPlatformInputContextFactory::requested();
+ icStrs.isEmpty() ? mInputContext.reset(new QCocoaInputContext)
+ : mInputContext.reset(QPlatformInputContextFactory::create(icStrs));
initResources();
QMacAutoReleasePool pool;
@@ -135,16 +142,6 @@ QCocoaIntegration::QCocoaIntegration(const QStringList &paramList)
// wants to be foreground applications so change the process type. (But
// see the function implementation for exceptions.)
qt_mac_transformProccessToForegroundApplication();
-
- // Move the application window to front to make it take focus, also when launching
- // from the terminal. On 10.12+ this call has been moved to applicationDidFinishLauching
- // to work around issues with loss of focus at startup.
- if (QOperatingSystemVersion::current() < QOperatingSystemVersion::MacOSSierra) {
- // Ignoring other apps is necessary (we must ignore the terminal), but makes
- // Qt apps play slightly less nice with other apps when lanching from Finder
- // (See the activateIgnoringOtherApps docs.)
- [cocoaApplication activateIgnoringOtherApps : YES];
- }
}
// Qt 4 also does not set the application delegate, so that behavior
@@ -163,7 +160,7 @@ QCocoaIntegration::QCocoaIntegration(const QStringList &paramList)
QCocoaScreen::initializeScreens();
- QMacInternalPasteboardMime::initializeMimeTypes();
+ QMacMimeRegistry::initializeMimeTypes();
QCocoaMimeTypes::initializeMimeTypes();
QWindowSystemInterfacePrivate::TabletEvent::setPlatformSynthesizesMouse(false);
QWindowSystemInterface::registerInputDevice(new QInputDevice(QString("keyboard"), 0,
@@ -188,17 +185,18 @@ QCocoaIntegration::~QCocoaIntegration()
[[NSApplication sharedApplication] setDelegate:nil];
}
+ // Stop global mouse event and app activation monitoring
+ QCocoaWindow::removePopupMonitor();
+
#ifndef QT_NO_CLIPBOARD
// Delete the clipboard integration and destroy mime type converters.
// Deleting the clipboard integration flushes promised pastes using
// the mime converters - the ordering here is important.
delete mCocoaClipboard;
- QMacInternalPasteboardMime::destroyMimeTypes();
+ QMacMimeRegistry::destroyMimeTypes();
#endif
QCocoaScreen::cleanupScreens();
-
- clearToolbars();
}
QCocoaIntegration *QCocoaIntegration::instance()
@@ -238,6 +236,7 @@ bool QCocoaIntegration::hasCapability(QPlatformIntegration::Capability cap) cons
case RasterGLSurface:
case ApplicationState:
case ApplicationIcon:
+ case BackingStoreStaticContents:
return true;
default:
return QPlatformIntegration::hasCapability(cap);
@@ -305,6 +304,18 @@ QPlatformBackingStore *QCocoaIntegration::createPlatformBackingStore(QWindow *wi
return new QCALayerBackingStore(window);
case QSurface::MetalSurface:
case QSurface::OpenGLSurface:
+ case QSurface::VulkanSurface:
+ // If the window is a widget window, we know that the QWidgetRepaintManager
+ // will explicitly use rhiFlush() for the window owning the backingstore,
+ // and any child window with the same surface format. This means we can
+ // safely return a QCALayerBackingStore here, to ensure that any plain
+ // flush() for child windows that don't have a matching surface format
+ // will still work, by setting the layer's contents property.
+ if (window->inherits("QWidgetWindow"))
+ return new QCALayerBackingStore(window);
+
+ // Otherwise we return a QRhiBackingStore, that implements flush() in
+ // terms of rhiFlush().
return new QRhiBackingStore(window);
default:
return nullptr;
@@ -395,38 +406,9 @@ QVariant QCocoaIntegration::styleHint(StyleHint hint) const
return QPlatformIntegration::styleHint(hint);
}
-Qt::KeyboardModifiers QCocoaIntegration::queryKeyboardModifiers() const
+QPlatformKeyMapper *QCocoaIntegration::keyMapper() const
{
- return QAppleKeyMapper::queryKeyboardModifiers();
-}
-
-QList<int> QCocoaIntegration::possibleKeys(const QKeyEvent *event) const
-{
- return mKeyboardMapper->possibleKeys(event);
-}
-
-void QCocoaIntegration::setToolbar(QWindow *window, NSToolbar *toolbar)
-{
- if (NSToolbar *prevToolbar = mToolbars.value(window))
- [prevToolbar release];
-
- [toolbar retain];
- mToolbars.insert(window, toolbar);
-}
-
-NSToolbar *QCocoaIntegration::toolbar(QWindow *window) const
-{
- return mToolbars.value(window);
-}
-
-void QCocoaIntegration::clearToolbars()
-{
- QHash<QWindow *, NSToolbar *>::const_iterator it = mToolbars.constBegin();
- while (it != mToolbars.constEnd()) {
- [it.value() release];
- ++it;
- }
- mToolbars.clear();
+ return mKeyboardMapper.data();
}
void QCocoaIntegration::setApplicationIcon(const QIcon &icon) const
@@ -436,6 +418,11 @@ void QCocoaIntegration::setApplicationIcon(const QIcon &icon) const
NSApp.applicationIconImage = [NSImage imageFromQIcon:icon withSize:fallbackSize];
}
+void QCocoaIntegration::setApplicationBadge(qint64 number)
+{
+ NSApp.dockTile.badgeLabel = number ? [NSString stringWithFormat:@"%" PRId64, number] : nil;
+}
+
void QCocoaIntegration::beep() const
{
NSBeep();
@@ -454,8 +441,8 @@ void QCocoaIntegration::focusWindowChanged(QWindow *focusWindow)
return;
static bool hasDefaultApplicationIcon = [](){
- NSImage *genericApplicationIcon = [[NSWorkspace sharedWorkspace]
- iconForFileType:NSFileTypeForHFSTypeCode(kGenericApplicationIcon)];
+ NSImage *genericApplicationIcon = [NSWorkspace.sharedWorkspace
+ iconForContentType:UTTypeApplicationBundle];
NSImage *applicationIcon = [NSImage imageNamed:NSImageNameApplicationIcon];
NSRect rect = NSMakeRect(0, 0, 32, 32);
diff --git a/src/plugins/platforms/cocoa/qcocoamenu.mm b/src/plugins/platforms/cocoa/qcocoamenu.mm
index 8a3d259b3e..7a6999c2cc 100644
--- a/src/plugins/platforms/cocoa/qcocoamenu.mm
+++ b/src/plugins/platforms/cocoa/qcocoamenu.mm
@@ -18,6 +18,9 @@
#include "qcocoascreen.h"
#include "qcocoaapplicationdelegate.h"
+#include <QtCore/private/qcore_mac_p.h>
+#include <QtCore/qpointer.h>
+
QT_BEGIN_NAMESPACE
QCocoaMenu::QCocoaMenu() :
@@ -35,11 +38,13 @@ QCocoaMenu::QCocoaMenu() :
QCocoaMenu::~QCocoaMenu()
{
- for (auto *item : qAsConst(m_menuItems)) {
+ for (auto *item : std::as_const(m_menuItems)) {
if (item->menuParent() == this)
item->setMenuParent(nullptr);
}
+ if (isOpen())
+ dismiss();
[m_nativeMenu release];
}
@@ -58,7 +63,7 @@ void QCocoaMenu::setMinimumWidth(int width)
void QCocoaMenu::setFont(const QFont &font)
{
if (font.resolveMask()) {
- NSFont *customMenuFont = [NSFont fontWithName:font.families().first().toNSString()
+ NSFont *customMenuFont = [NSFont fontWithName:font.families().constFirst().toNSString()
size:font.pointSize()];
m_nativeMenu.font = customMenuFont;
}
@@ -86,7 +91,7 @@ void QCocoaMenu::insertMenuItem(QPlatformMenuItem *menuItem, QPlatformMenuItem *
int index = m_menuItems.indexOf(beforeItem);
// if a before item is supplied, it should be in the menu
if (index < 0) {
- qWarning("Before menu item not found");
+ qCWarning(lcQpaMenus) << beforeItem << "not in" << m_menuItems;
return;
}
m_menuItems.insert(index, cocoaItem);
@@ -124,13 +129,13 @@ void QCocoaMenu::insertNative(QCocoaMenuItem *item, QCocoaMenuItem *beforeItem)
}
if (nativeItem.menu) {
- qWarning() << "Menu item" << item->text() << "already in menu" << QString::fromNSString(nativeItem.menu.title);
+ qCWarning(lcQpaMenus) << "Menu item" << item->text() << "already in menu" << QString::fromNSString(nativeItem.menu.title);
return;
}
if (beforeItem) {
if (beforeItem->isMerged()) {
- qWarning("No non-merged before menu item found");
+ qCWarning(lcQpaMenus, "No non-merged before menu item found");
return;
}
const NSInteger nativeIndex = [m_nativeMenu indexOfItem:beforeItem->nsItem()];
@@ -166,7 +171,7 @@ void QCocoaMenu::removeMenuItem(QPlatformMenuItem *menuItem)
QMacAutoReleasePool pool;
QCocoaMenuItem *cocoaItem = static_cast<QCocoaMenuItem *>(menuItem);
if (!m_menuItems.contains(cocoaItem)) {
- qWarning("Menu does not contain the item to be removed");
+ qCWarning(lcQpaMenus) << m_menuItems << "does not contain" << cocoaItem;
return;
}
@@ -179,7 +184,7 @@ void QCocoaMenu::removeMenuItem(QPlatformMenuItem *menuItem)
m_menuItems.removeOne(cocoaItem);
if (!cocoaItem->isMerged()) {
if (m_nativeMenu != cocoaItem->nsItem().menu) {
- qWarning("Item to remove does not belong to this menu");
+ qCWarning(lcQpaMenus) << cocoaItem << "does not belong to" << m_nativeMenu;
return;
}
[m_nativeMenu removeItem:cocoaItem->nsItem()];
@@ -219,7 +224,7 @@ void QCocoaMenu::syncMenuItem_helper(QPlatformMenuItem *menuItem, bool menubarUp
QMacAutoReleasePool pool;
QCocoaMenuItem *cocoaItem = static_cast<QCocoaMenuItem *>(menuItem);
if (!m_menuItems.contains(cocoaItem)) {
- qWarning("Item does not belong to this menu");
+ qCWarning(lcQpaMenus) << cocoaItem << "does not belong to" << this;
return;
}
@@ -284,7 +289,7 @@ void QCocoaMenu::syncSeparatorsCollapsible(bool enable)
if (lastVisibleItem && lastVisibleItem.separatorItem)
lastVisibleItem.hidden = YES;
} else {
- for (auto *item : qAsConst(m_menuItems)) {
+ for (auto *item : std::as_const(m_menuItems)) {
if (!item->isSeparator())
continue;
@@ -318,6 +323,8 @@ void QCocoaMenu::showPopup(const QWindow *parentWindow, const QRect &targetRect,
{
QMacAutoReleasePool pool;
+ QPointer<QCocoaMenu> guard = this;
+
QPoint pos = QPoint(targetRect.left(), targetRect.top() + targetRect.height());
QCocoaWindow *cocoaWindow = parentWindow ? static_cast<QCocoaWindow *>(parentWindow->handle()) : nullptr;
NSView *view = cocoaWindow ? cocoaWindow->view() : nil;
@@ -402,6 +409,11 @@ void QCocoaMenu::showPopup(const QWindow *parentWindow, const QRect &targetRect,
}
}
+ if (!guard) {
+ menuParentGuard.dismiss();
+ return;
+ }
+
// The calls above block, and also swallow any mouse release event,
// so we need to clear any mouse button that triggered the menu popup.
if (cocoaWindow && !cocoaWindow->isForeignWindow())
@@ -423,7 +435,7 @@ QPlatformMenuItem *QCocoaMenu::menuItemAt(int position) const
QPlatformMenuItem *QCocoaMenu::menuItemForTag(quintptr tag) const
{
- for (auto *item : qAsConst(m_menuItems)) {
+ for (auto *item : std::as_const(m_menuItems)) {
if (item->tag() == tag)
return item;
}
@@ -439,7 +451,7 @@ QList<QCocoaMenuItem *> QCocoaMenu::items() const
QList<QCocoaMenuItem *> QCocoaMenu::merged() const
{
QList<QCocoaMenuItem *> result;
- for (auto *item : qAsConst(m_menuItems)) {
+ for (auto *item : std::as_const(m_menuItems)) {
if (item->menu()) { // recurse into submenus
result.append(item->menu()->merged());
continue;
@@ -460,7 +472,7 @@ void QCocoaMenu::propagateEnabledState(bool enabled)
if (!m_enabled && enabled) // Some ancestor was enabled, but this menu is not
return;
- for (auto *item : qAsConst(m_menuItems)) {
+ for (auto *item : std::as_const(m_menuItems)) {
if (QCocoaMenu *menu = item->menu())
menu->propagateEnabledState(enabled);
else
@@ -481,6 +493,10 @@ void QCocoaMenu::setAttachedItem(NSMenuItem *item)
if (m_attachedItem)
m_attachedItem.submenu = m_nativeMenu;
+ // NSMenuItems with a submenu and submenuAction: as the item's action
+ // will not take part in NSMenuValidation, so explicitly enable/disable
+ // the item here. See also QCocoaMenuItem::resolveTargetAction()
+ m_attachedItem.enabled = m_attachedItem.hasSubmenu;
}
NSMenuItem *QCocoaMenu::attachedItem() const
diff --git a/src/plugins/platforms/cocoa/qcocoamenubar.h b/src/plugins/platforms/cocoa/qcocoamenubar.h
index b30f8569ab..785de9c0f6 100644
--- a/src/plugins/platforms/cocoa/qcocoamenubar.h
+++ b/src/plugins/platforms/cocoa/qcocoamenubar.h
@@ -9,6 +9,8 @@
#include <qpa/qplatformmenu.h>
#include "qcocoamenu.h"
+#include <QtCore/qpointer.h>
+
QT_BEGIN_NAMESPACE
class QCocoaWindow;
@@ -45,14 +47,12 @@ private:
bool needsImmediateUpdate();
bool shouldDisable(QCocoaWindow *active) const;
- void insertDefaultEditItems(QCocoaMenu *menu);
NSMenuItem *nativeItemForMenu(QCocoaMenu *menu) const;
QList<QPointer<QCocoaMenu> > m_menus;
NSMenu *m_nativeMenu;
QPointer<QCocoaWindow> m_window;
- QList<QPointer<QCocoaMenuItem>> m_defaultEditMenuItems;
};
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/cocoa/qcocoamenubar.mm b/src/plugins/platforms/cocoa/qcocoamenubar.mm
index 23aae466df..2493d90724 100644
--- a/src/plugins/platforms/cocoa/qcocoamenubar.mm
+++ b/src/plugins/platforms/cocoa/qcocoamenubar.mm
@@ -9,10 +9,14 @@
#include "qcocoamenuloader.h"
#include "qcocoaapplication.h" // for custom application category
#include "qcocoaapplicationdelegate.h"
+#include "qcocoahelpers.h"
#include <QtGui/QGuiApplication>
#include <QtCore/QDebug>
+#include <QtCore/private/qcore_mac_p.h>
+#include <QtGui/private/qguiapplication_p.h>
+
QT_BEGIN_NAMESPACE
static QList<QCocoaMenuBar*> static_menubars;
@@ -21,18 +25,19 @@ QCocoaMenuBar::QCocoaMenuBar()
{
static_menubars.append(this);
+ // clicks into the menu bar should close all popup windows
+ static QMacNotificationObserver menuBarClickObserver(nil, NSMenuDidBeginTrackingNotification, ^{
+ QGuiApplicationPrivate::instance()->closeAllPopups();
+ });
+
m_nativeMenu = [[NSMenu alloc] init];
-#ifdef QT_COCOA_ENABLE_MENU_DEBUG
- qDebug() << "Construct QCocoaMenuBar" << this << m_nativeMenu;
-#endif
+ qCDebug(lcQpaMenus) << "Constructed" << this << "with" << m_nativeMenu;
}
QCocoaMenuBar::~QCocoaMenuBar()
{
-#ifdef QT_COCOA_ENABLE_MENU_DEBUG
- qDebug() << "~QCocoaMenuBar" << this;
-#endif
- for (auto menu : qAsConst(m_menus)) {
+ qCDebug(lcQpaMenus) << "Destructing" << this << "with" << m_nativeMenu;
+ for (auto menu : std::as_const(m_menus)) {
if (!menu)
continue;
NSMenuItem *item = nativeItemForMenu(menu);
@@ -85,17 +90,16 @@ void QCocoaMenuBar::insertMenu(QPlatformMenu *platformMenu, QPlatformMenu *befor
{
QCocoaMenu *menu = static_cast<QCocoaMenu *>(platformMenu);
QCocoaMenu *beforeMenu = static_cast<QCocoaMenu *>(before);
-#ifdef QT_COCOA_ENABLE_MENU_DEBUG
- qDebug() << "QCocoaMenuBar" << this << "insertMenu" << menu << "before" << before;
-#endif
+
+ qCDebug(lcQpaMenus) << "Inserting" << menu << "before" << before << "into" << this;
if (m_menus.contains(QPointer<QCocoaMenu>(menu))) {
- qWarning("This menu already belongs to the menubar, remove it first");
+ qCWarning(lcQpaMenus, "This menu already belongs to the menubar, remove it first");
return;
}
if (beforeMenu && !m_menus.contains(QPointer<QCocoaMenu>(beforeMenu))) {
- qWarning("The before menu does not belong to the menubar");
+ qCWarning(lcQpaMenus, "The before menu does not belong to the menubar");
return;
}
@@ -129,7 +133,7 @@ void QCocoaMenuBar::removeMenu(QPlatformMenu *platformMenu)
{
QCocoaMenu *menu = static_cast<QCocoaMenu *>(platformMenu);
if (!m_menus.contains(menu)) {
- qWarning("Trying to remove a menu that does not belong to the menubar");
+ qCWarning(lcQpaMenus) << "Trying to remove" << menu << "that does not belong to" << this;
return;
}
@@ -158,18 +162,6 @@ void QCocoaMenuBar::syncMenu_helper(QPlatformMenu *menu, bool menubarUpdate)
for (QCocoaMenuItem *item : cocoaMenu->items())
cocoaMenu->syncMenuItem_helper(item, menubarUpdate);
- const QString captionNoAmpersand = QString::fromNSString(cocoaMenu->nsMenu().title)
- .remove(u'&');
- if (captionNoAmpersand == QCoreApplication::translate("QCocoaMenu", "Edit")) {
- // prevent recursion from QCocoaMenu::insertMenuItem - when the menu is visible
- // it calls syncMenu again. QCocoaMenu::setVisible just sets the bool, which then
- // gets evaluated in the code after this block.
- const bool wasVisible = cocoaMenu->isVisible();
- cocoaMenu->setVisible(false);
- insertDefaultEditItems(cocoaMenu);
- cocoaMenu->setVisible(wasVisible);
- }
-
BOOL shouldHide = YES;
if (cocoaMenu->isVisible()) {
// If the NSMenu has no visible items, or only separators, we should hide it
@@ -182,10 +174,44 @@ void QCocoaMenuBar::syncMenu_helper(QPlatformMenu *menu, bool menubarUpdate)
}
}
- if (NSMenuItem *attachedItem = cocoaMenu->attachedItem()) {
- // Non-nil attached item means the item's submenu is set
- attachedItem.title = cocoaMenu->nsMenu().title;
- attachedItem.hidden = shouldHide;
+ if (NSMenuItem *menuItem = cocoaMenu->attachedItem()) {
+ // Non-nil menu item means the item's sub menu is set
+
+ NSString *menuTitle = cocoaMenu->nsMenu().title;
+
+ // The NSMenu's title is what's visible to the user, and AppKit uses this
+ // for some of its heuristics of when to add special items to the menus,
+ // such as 'Enter Full Screen' in the View menu, the search bare in the
+ // Help menu, and the "Send App feedback to Apple" in the Help menu.
+ // This relies on the title matching AppKit's localized value from the
+ // MenuCommands table, which in turn depends on the preferredLocalizations
+ // of the AppKit bundle. We don't do any automatic translation of menu
+ // titles visible to the user, so this relies on the application developer
+ // having chosen translated titles that match AppKit's, and that the Qt
+ // preferred UI languages match AppKit's preferredLocalizations.
+
+ // In the case of the Edit menu, AppKit uses the NSMenuItem's title
+ // for its heuristics of when to add the dictation and emoji entries,
+ // and this title is not visible to the user. But like above, the
+ // heuristics are based on the localized title of the menu, so we need
+ // to ensure the title matches AppKit's localization.
+
+ // Unfortunately, the title we have at this point may have gone through
+ // Qt's i18n machinery already, via e.g. tr("Edit") in the application,
+ // in which case we don't know the context of the translation, and can't
+ // do a reverse lookup to go back to the untranslated title to pass to
+ // AppKit. As a workaround we translate the title via a our context,
+ // and document that the user needs to ensure their application matches
+ // this translation.
+ if ([menuTitle isEqual:@"Edit"] || [menuTitle isEqual:tr("Edit").toNSString()]) {
+ menuItem.title = qt_mac_AppKitString(@"InputManager", @"Edit");
+ } else {
+ // The Edit menu is the only case we know of so far, but to be on
+ // the safe side we always sync the menu title.
+ menuItem.title = menuTitle;
+ }
+
+ menuItem.hidden = shouldHide;
}
}
@@ -199,9 +225,7 @@ NSMenuItem *QCocoaMenuBar::nativeItemForMenu(QCocoaMenu *menu) const
void QCocoaMenuBar::handleReparent(QWindow *newParentWindow)
{
-#ifdef QT_COCOA_ENABLE_MENU_DEBUG
- qDebug() << "QCocoaMenuBar" << this << "handleReparent" << newParentWindow;
-#endif
+ qCDebug(lcQpaMenus) << "Reparenting" << this << "to" << newParentWindow;
if (!m_window.isNull())
m_window->setMenubar(nullptr);
@@ -233,7 +257,7 @@ QCocoaWindow *QCocoaMenuBar::findWindowForMenubar()
QCocoaMenuBar *QCocoaMenuBar::findGlobalMenubar()
{
- for (auto *menubar : qAsConst(static_menubars)) {
+ for (auto *menubar : std::as_const(static_menubars)) {
if (menubar->m_window.isNull())
return menubar;
}
@@ -269,12 +293,11 @@ void QCocoaMenuBar::updateMenuBarImmediately()
if (!mb)
return;
-#ifdef QT_COCOA_ENABLE_MENU_DEBUG
- qDebug() << "QCocoaMenuBar" << "updateMenuBarImmediately" << cw;
-#endif
+ qCDebug(lcQpaMenus) << "Updating" << mb << "immediately for" << cw;
+
bool disableForModal = mb->shouldDisable(cw);
- for (auto menu : qAsConst(mb->m_menus)) {
+ for (auto menu : std::as_const(mb->m_menus)) {
if (!menu)
continue;
NSMenuItem *item = mb->nativeItemForMenu(menu);
@@ -303,7 +326,21 @@ void QCocoaMenuBar::updateMenuBarImmediately()
}
[mergedItems release];
- [NSApp setMainMenu:mb->nsMenu()];
+
+ NSMenu *newMainMenu = mb->nsMenu();
+ if (NSApp.mainMenu == newMainMenu) {
+ // NSApplication triggers _customizeMainMenu when the menu
+ // changes, which takes care of adding text input items to
+ // the edit menu e.g., but this doesn't happen if the menu
+ // is the same. In our case we might be re-using an existing
+ // menu, but the menu might have new sub menus that need to
+ // be customized. To ensure NSApplication does the right
+ // thing we reset the main menu first.
+ qCDebug(lcQpaMenus) << "Clearing main menu temporarily";
+ NSApp.mainMenu = nil;
+ }
+ NSApp.mainMenu = newMainMenu;
+
insertWindowMenu();
[loader qtTranslateApplicationMenu];
}
@@ -324,21 +361,39 @@ void QCocoaMenuBar::insertWindowMenu()
winMenuItem.hidden = YES;
winMenuItem.submenu = [[[NSMenu alloc] initWithTitle:@"QtWindowMenu"] autorelease];
+
+ // AppKit has a bug in [NSApplication setWindowsMenu:] where it will resolve
+ // the last item of the window menu's itemArray, but not account for the array
+ // being empty, resulting in a lookup of itemAtIndex:-1. To work around this,
+ // we insert a hidden dummy item into the menu. See FB13369198.
+ auto *dummyItem = [[NSMenuItem alloc] initWithTitle:@"" action:nil keyEquivalent:@""];
+ dummyItem.hidden = YES;
+ [winMenuItem.submenu addItem:[dummyItem autorelease]];
+
[mainMenu insertItem:winMenuItem atIndex:mainMenu.itemArray.count];
app.windowsMenu = winMenuItem.submenu;
- // Windows, created and 'ordered front' before, will not be in this menu:
+ // Windows that have already been ordered in at this point have already been
+ // evaluated by AppKit via _addToWindowsMenuIfNecessary and added to the menu,
+ // but since the menu didn't exist at that point the addition was a noop.
+ // Instead of trying to duplicate the logic AppKit uses for deciding if
+ // a window should be part of the Window menu we toggle one of the settings
+ // that definitely will affect this, which results in AppKit reevaluating the
+ // situation and adding the window to the menu if necessary.
for (NSWindow *win in app.windows) {
- if (win.title && ![win.title isEqualToString:@""])
- [app addWindowsItem:win title:win.title filename:NO];
+ win.excludedFromWindowsMenu = !win.excludedFromWindowsMenu;
+ win.excludedFromWindowsMenu = !win.excludedFromWindowsMenu;
}
}
QList<QCocoaMenuItem*> QCocoaMenuBar::merged() const
{
QList<QCocoaMenuItem*> r;
- for (auto menu : qAsConst(m_menus))
+ for (auto menu : std::as_const(m_menus)) {
+ if (!menu)
+ continue;
r.append(menu->merged());
+ }
return r;
}
@@ -357,10 +412,10 @@ bool QCocoaMenuBar::shouldDisable(QCocoaWindow *active) const
// When there is an application modal window on screen, the entries of
// the menubar should be disabled. The exception in Qt is that if the
// modal window is the only window on screen, then we enable the menu bar.
- for (auto *window : qAsConst(topWindows)) {
+ for (auto *window : std::as_const(topWindows)) {
if (window->isVisible() && window->modality() == Qt::ApplicationModal) {
// check for other visible windows
- for (auto *other : qAsConst(topWindows)) {
+ for (auto *other : std::as_const(topWindows)) {
if ((window != other) && (other->isVisible())) {
// INVARIANT: we found another visible window
// on screen other than our modalWidget. We therefore
@@ -381,8 +436,8 @@ bool QCocoaMenuBar::shouldDisable(QCocoaWindow *active) const
QPlatformMenu *QCocoaMenuBar::menuForTag(quintptr tag) const
{
- for (auto menu : qAsConst(m_menus))
- if (menu->tag() == tag)
+ for (auto menu : std::as_const(m_menus))
+ if (menu && menu->tag() == tag)
return menu;
return nullptr;
@@ -390,10 +445,13 @@ QPlatformMenu *QCocoaMenuBar::menuForTag(quintptr tag) const
NSMenuItem *QCocoaMenuBar::itemForRole(QPlatformMenuItem::MenuRole role)
{
- for (auto menu : qAsConst(m_menus))
- for (auto *item : menu->items())
- if (item->effectiveRole() == role)
- return item->nsItem();
+ for (auto menu : std::as_const(m_menus)) {
+ if (menu) {
+ for (auto *item : menu->items())
+ if (item->effectiveRole() == role)
+ return item->nsItem();
+ }
+ }
return nil;
}
@@ -403,48 +461,6 @@ QCocoaWindow *QCocoaMenuBar::cocoaWindow() const
return m_window.data();
}
-void QCocoaMenuBar::insertDefaultEditItems(QCocoaMenu *menu)
-{
- if (menu->items().isEmpty())
- return;
-
- NSMenu *nsEditMenu = menu->nsMenu();
- if ([nsEditMenu itemAtIndex:nsEditMenu.numberOfItems - 1].action
- == @selector(orderFrontCharacterPalette:)) {
- for (auto defaultEditMenuItem : qAsConst(m_defaultEditMenuItems)) {
- if (menu->items().contains(defaultEditMenuItem))
- menu->removeMenuItem(defaultEditMenuItem);
- }
- qDeleteAll(m_defaultEditMenuItems);
- m_defaultEditMenuItems.clear();
- } else {
- if (m_defaultEditMenuItems.isEmpty()) {
- QCocoaMenuItem *separator = new QCocoaMenuItem;
- separator->setIsSeparator(true);
-
- QCocoaMenuItem *dictationItem = new QCocoaMenuItem;
- dictationItem->setText(QCoreApplication::translate("QCocoaMenuItem", "Start Dictation..."));
- QObject::connect(dictationItem, &QPlatformMenuItem::activated, this, []{
- [NSApplication.sharedApplication performSelector:@selector(startDictation:)];
- });
-
- QCocoaMenuItem *emojiItem = new QCocoaMenuItem;
- emojiItem->setText(QCoreApplication::translate("QCocoaMenuItem", "Emoji && Symbols"));
- emojiItem->setShortcut(QKeyCombination(Qt::MetaModifier|Qt::ControlModifier, Qt::Key_Space));
- QObject::connect(emojiItem, &QPlatformMenuItem::activated, this, []{
- [NSApplication.sharedApplication orderFrontCharacterPalette:nil];
- });
-
- m_defaultEditMenuItems << separator << dictationItem << emojiItem;
- }
- for (auto defaultEditMenuItem : qAsConst(m_defaultEditMenuItems)) {
- if (menu->items().contains(defaultEditMenuItem))
- menu->removeMenuItem(defaultEditMenuItem);
- menu->insertMenuItem(defaultEditMenuItem, nullptr);
- }
- }
-}
-
QT_END_NAMESPACE
#include "moc_qcocoamenubar.cpp"
diff --git a/src/plugins/platforms/cocoa/qcocoamenuitem.h b/src/plugins/platforms/cocoa/qcocoamenuitem.h
index 6e8c17d30c..f677ffb7a7 100644
--- a/src/plugins/platforms/cocoa/qcocoamenuitem.h
+++ b/src/plugins/platforms/cocoa/qcocoamenuitem.h
@@ -8,7 +8,7 @@
#include <qpa/qplatformmenu.h>
#include <QtGui/QImage>
-//#define QT_COCOA_ENABLE_MENU_DEBUG
+#include <QtCore/qpointer.h>
Q_FORWARD_DECLARE_OBJC_CLASS(NSMenuItem);
Q_FORWARD_DECLARE_OBJC_CLASS(NSMenu);
diff --git a/src/plugins/platforms/cocoa/qcocoamenuitem.mm b/src/plugins/platforms/cocoa/qcocoamenuitem.mm
index 688f0504a6..3a0f71bc50 100644
--- a/src/plugins/platforms/cocoa/qcocoamenuitem.mm
+++ b/src/plugins/platforms/cocoa/qcocoamenuitem.mm
@@ -16,6 +16,7 @@
#include "qcocoamenuloader.h"
#include <QtGui/private/qcoregraphics_p.h>
#include <QtCore/qregularexpression.h>
+#include <QtCore/private/qcore_mac_p.h>
#include <QtGui/private/qapplekeymapper_p.h>
#include <QtCore/QDebug>
@@ -179,38 +180,71 @@ void QCocoaMenuItem::setNativeContents(WId item)
m_itemView.needsDisplay = YES;
}
-static QPlatformMenuItem::MenuRole detectMenuRole(const QString &caption)
+static QPlatformMenuItem::MenuRole detectMenuRole(const QString &captionWithPossibleMnemonic)
{
- QString captionNoAmpersand(caption);
- captionNoAmpersand.remove(u'&');
- const QString aboutString = QCoreApplication::translate("QCocoaMenuItem", "About");
- if (captionNoAmpersand.startsWith(aboutString, Qt::CaseInsensitive)
- || captionNoAmpersand.endsWith(aboutString, Qt::CaseInsensitive)) {
+ QString itemCaption(captionWithPossibleMnemonic);
+ itemCaption.remove(u'&');
+
+ static const std::tuple<QPlatformMenuItem::MenuRole, std::vector<std::tuple<Qt::MatchFlags, const char *>>> roleMap[] = {
+ { QPlatformMenuItem::AboutRole, {
+ { Qt::MatchStartsWith | Qt::MatchEndsWith, QT_TRANSLATE_NOOP("QCocoaMenuItem", "About") }
+ }},
+ { QPlatformMenuItem::PreferencesRole, {
+ { Qt::MatchStartsWith, QT_TRANSLATE_NOOP("QCocoaMenuItem", "Config") },
+ { Qt::MatchStartsWith, QT_TRANSLATE_NOOP("QCocoaMenuItem", "Preference") },
+ { Qt::MatchStartsWith, QT_TRANSLATE_NOOP("QCocoaMenuItem", "Options") },
+ { Qt::MatchStartsWith, QT_TRANSLATE_NOOP("QCocoaMenuItem", "Setting") },
+ { Qt::MatchStartsWith, QT_TRANSLATE_NOOP("QCocoaMenuItem", "Setup") },
+ }},
+ { QPlatformMenuItem::QuitRole, {
+ { Qt::MatchStartsWith, QT_TRANSLATE_NOOP("QCocoaMenuItem", "Quit") },
+ { Qt::MatchStartsWith, QT_TRANSLATE_NOOP("QCocoaMenuItem", "Exit") },
+ }},
+ { QPlatformMenuItem::CutRole, {
+ { Qt::MatchExactly, QT_TRANSLATE_NOOP("QCocoaMenuItem", "Cut") }
+ }},
+ { QPlatformMenuItem::CopyRole, {
+ { Qt::MatchExactly, QT_TRANSLATE_NOOP("QCocoaMenuItem", "Copy") }
+ }},
+ { QPlatformMenuItem::PasteRole, {
+ { Qt::MatchExactly, QT_TRANSLATE_NOOP("QCocoaMenuItem", "Paste") }
+ }},
+ { QPlatformMenuItem::SelectAllRole, {
+ { Qt::MatchExactly, QT_TRANSLATE_NOOP("QCocoaMenuItem", "Select All") }
+ }},
+ };
+
+ auto match = [](const QString &caption, const QString &itemCaption, Qt::MatchFlags matchFlags) {
+ if (matchFlags.testFlag(Qt::MatchExactly))
+ return !itemCaption.compare(caption, Qt::CaseInsensitive);
+ if (matchFlags.testFlag(Qt::MatchStartsWith) && itemCaption.startsWith(caption, Qt::CaseInsensitive))
+ return true;
+ if (matchFlags.testFlag(Qt::MatchEndsWith) && itemCaption.endsWith(caption, Qt::CaseInsensitive))
+ return true;
+ return false;
+ };
+
+ QPlatformMenuItem::MenuRole detectedRole = [&]{
+ for (const auto &[role, captions] : roleMap) {
+ for (const auto &[matchFlags, caption] : captions) {
+ // Check for untranslated match
+ if (match(caption, itemCaption, matchFlags))
+ return role;
+ // Then translated with the current Qt translation
+ if (match(QCoreApplication::translate("QCocoaMenuItem", caption), itemCaption, matchFlags))
+ return role;
+ }
+ }
+ return QPlatformMenuItem::NoRole;
+ }();
+
+ if (detectedRole == QPlatformMenuItem::AboutRole) {
static const QRegularExpression qtRegExp("qt$"_L1, QRegularExpression::CaseInsensitiveOption);
- if (captionNoAmpersand.contains(qtRegExp))
- return QPlatformMenuItem::AboutQtRole;
- return QPlatformMenuItem::AboutRole;
- }
- if (captionNoAmpersand.startsWith(QCoreApplication::translate("QCocoaMenuItem", "Config"), Qt::CaseInsensitive)
- || captionNoAmpersand.startsWith(QCoreApplication::translate("QCocoaMenuItem", "Preference"), Qt::CaseInsensitive)
- || captionNoAmpersand.startsWith(QCoreApplication::translate("QCocoaMenuItem", "Options"), Qt::CaseInsensitive)
- || captionNoAmpersand.startsWith(QCoreApplication::translate("QCocoaMenuItem", "Setting"), Qt::CaseInsensitive)
- || captionNoAmpersand.startsWith(QCoreApplication::translate("QCocoaMenuItem", "Setup"), Qt::CaseInsensitive)) {
- return QPlatformMenuItem::PreferencesRole;
+ if (itemCaption.contains(qtRegExp))
+ detectedRole = QPlatformMenuItem::AboutQtRole;
}
- if (captionNoAmpersand.startsWith(QCoreApplication::translate("QCocoaMenuItem", "Quit"), Qt::CaseInsensitive)
- || captionNoAmpersand.startsWith(QCoreApplication::translate("QCocoaMenuItem", "Exit"), Qt::CaseInsensitive)) {
- return QPlatformMenuItem::QuitRole;
- }
- if (!captionNoAmpersand.compare(QCoreApplication::translate("QCocoaMenuItem", "Cut"), Qt::CaseInsensitive))
- return QPlatformMenuItem::CutRole;
- if (!captionNoAmpersand.compare(QCoreApplication::translate("QCocoaMenuItem", "Copy"), Qt::CaseInsensitive))
- return QPlatformMenuItem::CopyRole;
- if (!captionNoAmpersand.compare(QCoreApplication::translate("QCocoaMenuItem", "Paste"), Qt::CaseInsensitive))
- return QPlatformMenuItem::PasteRole;
- if (!captionNoAmpersand.compare(QCoreApplication::translate("QCocoaMenuItem", "Select All"), Qt::CaseInsensitive))
- return QPlatformMenuItem::SelectAllRole;
- return QPlatformMenuItem::NoRole;
+
+ return detectedRole;
}
NSMenuItem *QCocoaMenuItem::sync()
@@ -382,7 +416,7 @@ QKeySequence QCocoaMenuItem::mergeAccel()
void QCocoaMenuItem::syncMerged()
{
if (!m_merged) {
- qWarning("Trying to sync a non-merged item");
+ qCWarning(lcQpaMenus) << "Trying to sync non-merged" << this;
return;
}
@@ -439,7 +473,20 @@ void QCocoaMenuItem::resolveTargetAction()
roleAction = @selector(selectAll:);
break;
default:
- roleAction = @selector(qt_itemFired:);
+ if (m_menu) {
+ // Menu items that represent sub menus should have submenuAction: as their
+ // action, so that clicking the menu item opens the sub menu without closing
+ // the entire menu hierarchy. A menu item with this action and a valid submenu
+ // will disable NSMenuValidation for the item, which is normally not an issue
+ // as NSMenuItems are enabled by default. But in our case, we haven't attached
+ // the submenu yet, which results in AppKit concluding that there's no validator
+ // for the item (the target is nil, and nothing responds to submenuAction:), and
+ // will in response disable the menu item. To work around this we explicitly
+ // enable the menu item in QCocoaMenu::setAttachedItem() once we have a submenu.
+ roleAction = @selector(submenuAction:);
+ } else {
+ roleAction = @selector(qt_itemFired:);
+ }
}
m_native.action = roleAction;
diff --git a/src/plugins/platforms/cocoa/qcocoamenuloader.mm b/src/plugins/platforms/cocoa/qcocoamenuloader.mm
index 6d3c668b87..b5ee3479aa 100644
--- a/src/plugins/platforms/cocoa/qcocoamenuloader.mm
+++ b/src/plugins/platforms/cocoa/qcocoamenuloader.mm
@@ -186,8 +186,8 @@
if (appMenu.supermenu)
unparentAppMenu(appMenu.supermenu);
- NSMenuItem *appMenuItem = [[NSMenuItem alloc] initWithTitle:@"Apple"
- action:nil keyEquivalent:@""];
+ NSMenuItem *appMenuItem = [[[NSMenuItem alloc] initWithTitle:@"Apple"
+ action:nil keyEquivalent:@""] autorelease];
appMenuItem.submenu = appMenu;
[menu insertItem:appMenuItem atIndex:0];
}
diff --git a/src/plugins/platforms/cocoa/qcocoamessagedialog.h b/src/plugins/platforms/cocoa/qcocoamessagedialog.h
new file mode 100644
index 0000000000..b8c273469a
--- /dev/null
+++ b/src/plugins/platforms/cocoa/qcocoamessagedialog.h
@@ -0,0 +1,38 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QCOCOAMESSAGEDIALOG_H
+#define QCOCOAMESSAGEDIALOG_H
+
+#include <qpa/qplatformdialoghelper.h>
+
+Q_FORWARD_DECLARE_OBJC_CLASS(NSAlert);
+typedef long NSInteger;
+typedef NSInteger NSModalResponse;
+
+QT_BEGIN_NAMESPACE
+
+class QEventLoop;
+
+class QCocoaMessageDialog : public QPlatformMessageDialogHelper
+{
+public:
+ QCocoaMessageDialog() = default;
+ ~QCocoaMessageDialog();
+
+ void exec() override;
+ bool show(Qt::WindowFlags windowFlags, Qt::WindowModality windowModality, QWindow *parent) override;
+ void hide() override;
+
+private:
+ Qt::WindowModality modality() const;
+ NSAlert *m_alert = nullptr;
+ QEventLoop *m_eventLoop = nullptr;
+ NSModalResponse runModal() const;
+ void processResponse(NSModalResponse response);
+};
+
+QT_END_NAMESPACE
+
+#endif // QCOCOAMESSAGEDIALOG_H
+
diff --git a/src/plugins/platforms/cocoa/qcocoamessagedialog.mm b/src/plugins/platforms/cocoa/qcocoamessagedialog.mm
new file mode 100644
index 0000000000..84525099c9
--- /dev/null
+++ b/src/plugins/platforms/cocoa/qcocoamessagedialog.mm
@@ -0,0 +1,425 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "qcocoamessagedialog.h"
+
+#include "qcocoawindow.h"
+#include "qcocoahelpers.h"
+#include "qcocoaeventdispatcher.h"
+
+#include <QtCore/qmetaobject.h>
+#include <QtCore/qscopedvaluerollback.h>
+#include <QtCore/qtimer.h>
+
+#include <QtGui/qtextdocument.h>
+#include <QtGui/private/qguiapplication_p.h>
+#include <QtGui/private/qcoregraphics_p.h>
+#include <QtGui/qpa/qplatformtheme.h>
+
+#include <AppKit/NSAlert.h>
+#include <AppKit/NSButton.h>
+
+QT_USE_NAMESPACE
+
+using namespace Qt::StringLiterals;
+
+QT_BEGIN_NAMESPACE
+
+QCocoaMessageDialog::~QCocoaMessageDialog()
+{
+ hide();
+ [m_alert release];
+}
+
+static QString toPlainText(const QString &text)
+{
+ // FIXME: QMessageDialog supports Qt::TextFormat, which
+ // nowadays includes Qt::MarkdownText, but we don't have
+ // the machinery to deal with that yet. We should as a
+ // start plumb the dialog's text format to the platform
+ // via the dialog options.
+
+ if (!Qt::mightBeRichText(text))
+ return text;
+
+ QTextDocument textDocument;
+ textDocument.setHtml(text);
+ return textDocument.toPlainText();
+}
+
+static NSControlStateValue controlStateFor(Qt::CheckState state)
+{
+ switch (state) {
+ case Qt::Checked: return NSControlStateValueOn;
+ case Qt::Unchecked: return NSControlStateValueOff;
+ case Qt::PartiallyChecked: return NSControlStateValueMixed;
+ }
+ Q_UNREACHABLE();
+}
+
+/*
+ Called from QDialogPrivate::setNativeDialogVisible() when the message box
+ is ready to be shown.
+
+ At this point the options() will reflect the specific dialog shown.
+
+ Returns true if the helper could successfully show the dialog, or
+ false if the cross platform fallback dialog should be used instead.
+*/
+bool QCocoaMessageDialog::show(Qt::WindowFlags windowFlags, Qt::WindowModality windowModality, QWindow *parent)
+{
+ Q_UNUSED(windowFlags);
+
+ qCDebug(lcQpaDialogs) << "Asked to show" << windowModality << "dialog with parent" << parent;
+
+ if (m_alert.window.visible) {
+ qCDebug(lcQpaDialogs) << "Dialog already visible, ignoring request to show";
+ return true; // But we don't want to show the fallback dialog instead
+ }
+
+ // We can only do application and window modal dialogs
+ if (windowModality == Qt::NonModal)
+ return false;
+
+ // And only window modal if we have a parent
+ if (windowModality == Qt::WindowModal && (!parent || !parent->handle())) {
+ qCWarning(lcQpaDialogs, "Cannot run window modal dialog without parent window");
+ return false;
+ }
+
+ // And without options we don't know what to show
+ if (!options())
+ return false;
+
+ // NSAlert doesn't have a section for detailed text
+ if (!options()->detailedText().isEmpty()) {
+ qCWarning(lcQpaDialogs, "Message box contains detailed text");
+ return false;
+ }
+
+ if (Qt::mightBeRichText(options()->text()) ||
+ Qt::mightBeRichText(options()->informativeText())) {
+ // Let's fallback to non-native message box,
+ // we only have plain NSString/text in NSAlert.
+ qCDebug(lcQpaDialogs, "Message box contains text in rich text format");
+ return false;
+ }
+
+ Q_ASSERT(!m_alert);
+ m_alert = [NSAlert new];
+ m_alert.window.title = options()->windowTitle().toNSString();
+
+ const QString text = toPlainText(options()->text());
+ m_alert.messageText = text.toNSString();
+ m_alert.informativeText = toPlainText(options()->informativeText()).toNSString();
+
+ switch (options()->standardIcon()) {
+ case QMessageDialogOptions::NoIcon: {
+ // We only reflect the pixmap icon if the standard icon is unset,
+ // as setting a standard icon will also set a corresponding pixmap
+ // icon, which we don't want since it conflicts with the platform.
+ // If the user has set an explicit pixmap icon however, the standard
+ // icon will be NoIcon, so we're good.
+ QPixmap iconPixmap = options()->iconPixmap();
+ if (!iconPixmap.isNull())
+ m_alert.icon = [NSImage imageFromQImage:iconPixmap.toImage()];
+ break;
+ }
+ case QMessageDialogOptions::Information:
+ case QMessageDialogOptions::Question:
+ [m_alert setAlertStyle:NSAlertStyleInformational];
+ break;
+ case QMessageDialogOptions::Warning:
+ [m_alert setAlertStyle:NSAlertStyleWarning];
+ break;
+ case QMessageDialogOptions::Critical:
+ [m_alert setAlertStyle:NSAlertStyleCritical];
+ break;
+ }
+
+ auto defaultButton = options()->defaultButton();
+ auto escapeButton = options()->escapeButton();
+
+ const auto addButton = [&](auto title, auto tag, auto role) {
+ title = QPlatformTheme::removeMnemonics(title);
+ NSButton *button = [m_alert addButtonWithTitle:title.toNSString()];
+
+ // Calling addButtonWithTitle places buttons starting at the right side/top of the alert
+ // and going toward the left/bottom. By default, the first button has a key equivalent of
+ // Return, any button with a title of "Cancel" has a key equivalent of Escape, and any button
+ // with the title "Don't Save" has a key equivalent of Command-D (but only if it's not the first
+ // button). If an explicit default or escape button has been set, we respect these,
+ // and otherwise we fall back to role-based default and escape buttons.
+
+ qCDebug(lcQpaDialogs).verbosity(0) << "Adding button" << title << "with" << role;
+
+ if (!defaultButton && role == AcceptRole)
+ defaultButton = tag;
+
+ if (tag == defaultButton)
+ button.keyEquivalent = @"\r";
+ else if ([button.keyEquivalent isEqualToString:@"\r"])
+ button.keyEquivalent = @"";
+
+ if (!escapeButton && role == RejectRole)
+ escapeButton = tag;
+
+ // Don't override default button with escape button, to match AppKit default
+ if (tag == escapeButton && ![button.keyEquivalent isEqualToString:@"\r"])
+ button.keyEquivalent = @"\e";
+ else if ([button.keyEquivalent isEqualToString:@"\e"])
+ button.keyEquivalent = @"";
+
+ if (@available(macOS 11, *))
+ button.hasDestructiveAction = role == DestructiveRole;
+
+ // The NSModalResponse of showing an NSAlert normally depends on the order of the
+ // button that was clicked, starting from the right with NSAlertFirstButtonReturn (1000),
+ // NSAlertSecondButtonReturn (1001), NSAlertThirdButtonReturn (1002), and after that
+ // NSAlertThirdButtonReturn + n. The response can also be customized per button via its
+ // tag, which, following the above logic, can include any positive value from 1000 and up.
+ // In addition the system reserves the values from -1000 and down for its own modal responses,
+ // such as NSModalResponseStop, NSModalResponseAbort, and NSModalResponseContinue.
+ // Luckily for us, the QPlatformDialogHelper::StandardButton enum values all fall within
+ // the positive range, so we can use the standard button value as the tag directly.
+ // The same applies to the custom button IDs, as these are generated in sequence after
+ // the QPlatformDialogHelper::LastButton.
+ Q_ASSERT(tag >= NSAlertFirstButtonReturn);
+ button.tag = tag;
+ };
+
+ // Resolve all dialog buttons from the options, both standard and custom
+
+ struct Button { QString title; int identifier; ButtonRole role; };
+ std::vector<Button> buttons;
+
+ const auto *platformTheme = QGuiApplicationPrivate::platformTheme();
+ if (auto standardButtons = options()->standardButtons()) {
+ for (int standardButton = FirstButton; standardButton <= LastButton; standardButton <<= 1) {
+ if (standardButtons & standardButton) {
+ auto title = platformTheme->standardButtonText(standardButton);
+ buttons.push_back({
+ title, standardButton, buttonRole(StandardButton(standardButton))
+ });
+ }
+ }
+ }
+ const auto customButtons = options()->customButtons();
+ for (auto customButton : customButtons)
+ buttons.push_back({customButton.label, customButton.id, customButton.role});
+
+ // Sort them according to the QPlatformDialogHelper::ButtonLayout for macOS
+
+ // The ButtonLayout adds one additional role, AlternateRole, which is used
+ // for any AcceptRole beyond the first one, and should be ordered before the
+ // AcceptRole. Set this up by fixing the roles up front.
+ bool seenAccept = false;
+ for (auto &button : buttons) {
+ if (button.role == AcceptRole) {
+ if (!seenAccept)
+ seenAccept = true;
+ else
+ button.role = AlternateRole;
+ }
+ }
+
+ std::vector<Button> orderedButtons;
+ const int *layoutEntry = buttonLayout(Qt::Horizontal, ButtonLayout::MacLayout);
+ while (*layoutEntry != QPlatformDialogHelper::EOL) {
+ const auto role = ButtonRole(*layoutEntry & ~ButtonRole::Reverse);
+ const bool reverse = *layoutEntry & ButtonRole::Reverse;
+
+ auto addButton = [&](const Button &button) {
+ if (button.role == role)
+ orderedButtons.push_back(button);
+ };
+
+ if (reverse)
+ std::for_each(std::crbegin(buttons), std::crend(buttons), addButton);
+ else
+ std::for_each(std::cbegin(buttons), std::cend(buttons), addButton);
+
+ ++layoutEntry;
+ }
+
+ // Add them to the alert in reverse order, since buttons are added right to left
+ for (auto button = orderedButtons.crbegin(); button != orderedButtons.crend(); ++button)
+ addButton(button->title, button->identifier, button->role);
+
+ // If we didn't find a an explicit or implicit default button above
+ // we restore the AppKit behavior of making the first button default.
+ if (!defaultButton)
+ m_alert.buttons.firstObject.keyEquivalent = @"\r";
+
+ if (auto checkBoxLabel = options()->checkBoxLabel(); !checkBoxLabel.isNull()) {
+ checkBoxLabel = QPlatformTheme::removeMnemonics(checkBoxLabel);
+ m_alert.suppressionButton.title = checkBoxLabel.toNSString();
+ auto state = options()->checkBoxState();
+ m_alert.suppressionButton.allowsMixedState = state == Qt::PartiallyChecked;
+ m_alert.suppressionButton.state = controlStateFor(state);
+ m_alert.showsSuppressionButton = YES;
+ }
+
+ qCDebug(lcQpaDialogs) << "Showing" << m_alert;
+
+ if (windowModality == Qt::WindowModal) {
+ auto *cocoaWindow = static_cast<QCocoaWindow*>(parent->handle());
+ [m_alert beginSheetModalForWindow:cocoaWindow->nativeWindow()
+ completionHandler:^(NSModalResponse response) {
+ processResponse(response);
+ }
+ ];
+ } else {
+ // The dialog is application modal, so we need to call runModal,
+ // but we can't call it here as the nativeDialogInUse state of QDialog
+ // depends on the result of show(), and we can't rely on doing it
+ // in exec(), as we can't guarantee that the user will call exec()
+ // after showing the dialog. As a workaround, we call it from exec(),
+ // but also make sure that if the user returns to the main runloop
+ // we'll run the modal dialog from there.
+ QTimer::singleShot(0, this, [this]{
+ if (m_alert && !m_alert.window.visible) {
+ qCDebug(lcQpaDialogs) << "Running deferred modal" << m_alert;
+ QCocoaEventDispatcher::clearCurrentThreadCocoaEventDispatcherInterruptFlag();
+ processResponse(runModal());
+ }
+ });
+ }
+
+ return true;
+}
+
+// We shouldn't get NSModalResponseContinue as a response from NSAlert::runModal,
+// and processResponse must not be called with that value (if we are there, it's
+// too late to do anything about it.
+// However, as QTBUG-114546 shows, there are scenarios where we might get that
+// response anyway. We interpret it to keep the modal loop running, and we only
+// return if we got something else to pass to processResponse.
+NSModalResponse QCocoaMessageDialog::runModal() const
+{
+ NSModalResponse response = NSModalResponseContinue;
+ while (response == NSModalResponseContinue)
+ response = [m_alert runModal];
+ return response;
+}
+
+void QCocoaMessageDialog::exec()
+{
+ Q_ASSERT(m_alert);
+
+ if (modality() == Qt::WindowModal) {
+ qCDebug(lcQpaDialogs) << "Running local event loop for window modal" << m_alert;
+ QEventLoop eventLoop;
+ QScopedValueRollback updateGuard(m_eventLoop, &eventLoop);
+ m_eventLoop->exec(QEventLoop::DialogExec);
+ } else {
+ qCDebug(lcQpaDialogs) << "Running modal" << m_alert;
+ QCocoaEventDispatcher::clearCurrentThreadCocoaEventDispatcherInterruptFlag();
+ processResponse(runModal());
+ }
+}
+
+// Custom modal response code to record that the dialog was hidden by us
+static const NSInteger kModalResponseDialogHidden = NSAlertThirdButtonReturn + 1;
+
+static Qt::CheckState checkStateFor(NSControlStateValue state)
+{
+ switch (state) {
+ case NSControlStateValueOn: return Qt::Checked;
+ case NSControlStateValueOff: return Qt::Unchecked;
+ case NSControlStateValueMixed: return Qt::PartiallyChecked;
+ }
+ Q_UNREACHABLE();
+}
+
+void QCocoaMessageDialog::processResponse(NSModalResponse response)
+{
+ qCDebug(lcQpaDialogs) << "Processing response" << response << "for" << m_alert;
+
+ // We can't re-use the same dialog for the next show() anyways,
+ // since the options may have changed, so get rid of it now,
+ // before we emit anything that might recurse back to hide/show/etc.
+ auto alert = std::exchange(m_alert, nil);
+ [alert autorelease];
+
+ if (alert.showsSuppressionButton)
+ emit checkBoxStateChanged(checkStateFor(alert.suppressionButton.state));
+
+ if (response >= NSAlertFirstButtonReturn) {
+ // Safe range for user-defined modal responses
+ if (response == kModalResponseDialogHidden) {
+ // Dialog was explicitly hidden by us, so nothing to report
+ qCDebug(lcQpaDialogs) << "Dialog was hidden; ignoring response";
+ } else {
+ // Dialog buttons
+ if (response <= StandardButton::LastButton) {
+ Q_ASSERT(response >= StandardButton::FirstButton);
+ auto standardButton = StandardButton(response);
+ emit clicked(standardButton, buttonRole(standardButton));
+ } else {
+ auto *customButton = options()->customButton(response);
+ Q_ASSERT(customButton);
+ emit clicked(StandardButton(customButton->id), customButton->role);
+ }
+ }
+ } else {
+ // We have to consider NSModalResponses beyond the ones specific to
+ // the alert buttons as the alert may be canceled programmatically.
+
+ switch (response) {
+ case NSModalResponseContinue:
+ // Modal session is continuing (returned by runModalSession: only)
+ Q_UNREACHABLE();
+ case NSModalResponseOK:
+ emit accept();
+ break;
+ case NSModalResponseCancel:
+ case NSModalResponseStop: // Modal session was broken with stopModal
+ case NSModalResponseAbort: // Modal session was broken with abortModal
+ emit reject();
+ break;
+ default:
+ qCWarning(lcQpaDialogs) << "Unrecognized modal response" << response;
+ }
+ }
+
+ if (m_eventLoop)
+ m_eventLoop->exit(response);
+}
+
+void QCocoaMessageDialog::hide()
+{
+ if (!m_alert)
+ return;
+
+ if (m_alert.window.visible) {
+ qCDebug(lcQpaDialogs) << "Hiding" << modality() << m_alert;
+
+ // Note: Just hiding or closing the NSAlert's NWindow here is not sufficient,
+ // as the dialog is running a modal event loop as well, which we need to end.
+
+ if (modality() == Qt::WindowModal) {
+ // Will call processResponse() synchronously
+ [m_alert.window.sheetParent endSheet:m_alert.window returnCode:kModalResponseDialogHidden];
+ } else {
+ if (NSApp.modalWindow == m_alert.window) {
+ // Will call processResponse() asynchronously
+ [NSApp stopModalWithCode:kModalResponseDialogHidden];
+ } else {
+ qCWarning(lcQpaDialogs, "Dialog is not top level modal window. Cannot hide.");
+ }
+ }
+ } else {
+ qCDebug(lcQpaDialogs) << "No need to hide already hidden" << m_alert;
+ auto alert = std::exchange(m_alert, nil);
+ [alert autorelease];
+ }
+}
+
+Qt::WindowModality QCocoaMessageDialog::modality() const
+{
+ Q_ASSERT(m_alert && m_alert.window);
+ return m_alert.window.sheetParent ? Qt::WindowModal : Qt::ApplicationModal;
+}
+
+QT_END_NAMESPACE
diff --git a/src/plugins/platforms/cocoa/qcocoamimetypes.mm b/src/plugins/platforms/cocoa/qcocoamimetypes.mm
index 6a8a57d9a1..52e5c64538 100644
--- a/src/plugins/platforms/cocoa/qcocoamimetypes.mm
+++ b/src/plugins/platforms/cocoa/qcocoamimetypes.mm
@@ -4,7 +4,7 @@
#include <AppKit/AppKit.h>
#include "qcocoamimetypes.h"
-#include <QtGui/private/qmacmime_p.h>
+#include <QtGui/qutimimeconverter.h>
#include "qcocoahelpers.h"
#include <QtGui/private/qcoregraphics_p.h>
@@ -12,49 +12,40 @@ QT_BEGIN_NAMESPACE
using namespace Qt::StringLiterals;
-class QMacPasteboardMimeTraditionalMacPlainText : public QMacInternalPasteboardMime {
+class QMacMimeTraditionalMacPlainText : public QUtiMimeConverter {
public:
- QMacPasteboardMimeTraditionalMacPlainText() : QMacInternalPasteboardMime(MIME_ALL) { }
- QString convertorName();
-
- QString flavorFor(const QString &mime);
- QString mimeFor(QString flav);
- bool canConvert(const QString &mime, QString flav);
- QVariant convertToMime(const QString &mime, QList<QByteArray> data, QString flav);
- QList<QByteArray> convertFromMime(const QString &mime, QVariant data, QString flav);
+ QString utiForMime(const QString &mime) const override;
+ QString mimeForUti(const QString &uti) const override;
+ QVariant convertToMime(const QString &mime, const QList<QByteArray> &data,
+ const QString &uti) const override;
+ QList<QByteArray> convertFromMime(const QString &mime, const QVariant &data,
+ const QString &uti) const override;
};
-QString QMacPasteboardMimeTraditionalMacPlainText::convertorName()
-{
- return "PlainText (traditional-mac-plain-text)"_L1;
-}
-
-QString QMacPasteboardMimeTraditionalMacPlainText::flavorFor(const QString &mime)
+QString QMacMimeTraditionalMacPlainText::utiForMime(const QString &mime) const
{
if (mime == "text/plain"_L1)
return "com.apple.traditional-mac-plain-text"_L1;
return QString();
}
-QString QMacPasteboardMimeTraditionalMacPlainText::mimeFor(QString flav)
+QString QMacMimeTraditionalMacPlainText::mimeForUti(const QString &uti) const
{
- if (flav == "com.apple.traditional-mac-plain-text"_L1)
+ if (uti == "com.apple.traditional-mac-plain-text"_L1)
return "text/plain"_L1;
return QString();
}
-bool QMacPasteboardMimeTraditionalMacPlainText::canConvert(const QString &mime, QString flav)
-{
- return flavorFor(mime) == flav;
-}
-
-QVariant QMacPasteboardMimeTraditionalMacPlainText::convertToMime(const QString &mimetype, QList<QByteArray> data, QString flavor)
+QVariant
+QMacMimeTraditionalMacPlainText::convertToMime(const QString &mimetype,
+ const QList<QByteArray> &data,
+ const QString &uti) const
{
if (data.count() > 1)
- qWarning("QMacPasteboardMimeTraditionalMacPlainText: Cannot handle multiple member data");
+ qWarning("QMacMimeTraditionalMacPlainText: Cannot handle multiple member data");
const QByteArray &firstData = data.first();
QVariant ret;
- if (flavor == "com.apple.traditional-mac-plain-text"_L1) {
+ if (uti == "com.apple.traditional-mac-plain-text"_L1) {
return QString(QCFString(CFStringCreateWithBytes(kCFAllocatorDefault,
reinterpret_cast<const UInt8 *>(firstData.constData()),
firstData.size(), CFStringGetSystemEncoding(), false)));
@@ -64,18 +55,21 @@ QVariant QMacPasteboardMimeTraditionalMacPlainText::convertToMime(const QString
return ret;
}
-QList<QByteArray> QMacPasteboardMimeTraditionalMacPlainText::convertFromMime(const QString &, QVariant data, QString flavor)
+QList<QByteArray>
+QMacMimeTraditionalMacPlainText::convertFromMime(const QString &,
+ const QVariant &data,
+ const QString &uti) const
{
QList<QByteArray> ret;
QString string = data.toString();
- if (flavor == "com.apple.traditional-mac-plain-text"_L1)
+ if (uti == "com.apple.traditional-mac-plain-text"_L1)
ret.append(string.toLatin1());
return ret;
}
void QCocoaMimeTypes::initializeMimeTypes()
{
- new QMacPasteboardMimeTraditionalMacPlainText;
+ new QMacMimeTraditionalMacPlainText;
}
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/cocoa/qcocoanativeinterface.h b/src/plugins/platforms/cocoa/qcocoanativeinterface.h
index 344c8523ce..a406cae366 100644
--- a/src/plugins/platforms/cocoa/qcocoanativeinterface.h
+++ b/src/plugins/platforms/cocoa/qcocoanativeinterface.h
@@ -31,12 +31,6 @@ public Q_SLOTS:
void onAppFocusWindowChanged(QWindow *window);
private:
- /*
- Function to return the default background pixmap.
- Needed by QWizard in the Qt widget module.
- */
- Q_INVOKABLE QPixmap defaultBackgroundPixmapForQWizard();
-
Q_INVOKABLE void clearCurrentThreadCocoaEventDispatcherInterruptFlag();
static void registerDraggedTypes(const QStringList &types);
@@ -53,9 +47,6 @@ private:
// deregisters.
static void registerTouchWindow(QWindow *window, bool enable);
- // Set the size of the unified title and toolbar area.
- static void setContentBorderThickness(QWindow *window, int topThickness, int bottomThickness);
-
// Set the size for a unified toolbar content border area.
// Multiple callers can register areas and the platform plugin
// will extend the "unified" area to cover them.
@@ -67,12 +58,6 @@ private:
// Returns true if the given coordinate is inside the current
// content border.
static bool testContentBorderPosition(QWindow *window, int position);
-
- // Sets a NSToolbar instance for the given QWindow. The
- // toolbar will be attached to the native NSWindow when
- // that is created;
- static void setNSToolbar(QWindow *window, void *nsToolbar);
-
};
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/cocoa/qcocoanativeinterface.mm b/src/plugins/platforms/cocoa/qcocoanativeinterface.mm
index 38a6060256..58bda2706a 100644
--- a/src/plugins/platforms/cocoa/qcocoanativeinterface.mm
+++ b/src/plugins/platforms/cocoa/qcocoanativeinterface.mm
@@ -26,6 +26,7 @@
#include <QtGui/qguiapplication.h>
#include <qdebug.h>
+#include <QtGui/private/qmacmimeregistry_p.h>
#include <QtGui/private/qcoregraphics_p.h>
#if QT_CONFIG(vulkan)
@@ -64,49 +65,16 @@ QPlatformNativeInterface::NativeResourceForIntegrationFunction QCocoaNativeInter
return NativeResourceForIntegrationFunction(QCocoaNativeInterface::registerTouchWindow);
if (resource.toLower() == "setembeddedinforeignview")
return NativeResourceForIntegrationFunction(QCocoaNativeInterface::setEmbeddedInForeignView);
- if (resource.toLower() == "setcontentborderthickness")
- return NativeResourceForIntegrationFunction(QCocoaNativeInterface::setContentBorderThickness);
if (resource.toLower() == "registercontentborderarea")
return NativeResourceForIntegrationFunction(QCocoaNativeInterface::registerContentBorderArea);
if (resource.toLower() == "setcontentborderareaenabled")
return NativeResourceForIntegrationFunction(QCocoaNativeInterface::setContentBorderAreaEnabled);
- if (resource.toLower() == "setnstoolbar")
- return NativeResourceForIntegrationFunction(QCocoaNativeInterface::setNSToolbar);
if (resource.toLower() == "testcontentborderposition")
return NativeResourceForIntegrationFunction(QCocoaNativeInterface::testContentBorderPosition);
return nullptr;
}
-QPixmap QCocoaNativeInterface::defaultBackgroundPixmapForQWizard()
-{
- // Note: starting with macOS 10.14, the KeyboardSetupAssistant app bundle no
- // longer contains the "Background.png" image. This function then returns a
- // null pixmap.
- const int ExpectedImageWidth = 242;
- const int ExpectedImageHeight = 414;
- QCFType<CFArrayRef> urls = LSCopyApplicationURLsForBundleIdentifier(
- CFSTR("com.apple.KeyboardSetupAssistant"), nullptr);
- if (urls && CFArrayGetCount(urls) > 0) {
- CFURLRef url = (CFURLRef)CFArrayGetValueAtIndex(urls, 0);
- QCFType<CFBundleRef> bundle = CFBundleCreate(kCFAllocatorDefault, url);
- if (bundle) {
- url = CFBundleCopyResourceURL(bundle, CFSTR("Background"), CFSTR("png"), nullptr);
- if (url) {
- QCFType<CGImageSourceRef> imageSource = CGImageSourceCreateWithURL(url, nullptr);
- QCFType<CGImageRef> image = CGImageSourceCreateImageAtIndex(imageSource, 0, nullptr);
- if (image) {
- int width = CGImageGetWidth(image);
- int height = CGImageGetHeight(image);
- if (width == ExpectedImageWidth && height == ExpectedImageHeight)
- return QPixmap::fromImage(qt_mac_toQImage(image));
- }
- }
- }
- }
- return QPixmap();
-}
-
void QCocoaNativeInterface::clearCurrentThreadCocoaEventDispatcherInterruptFlag()
{
QCocoaEventDispatcher::clearCurrentThreadCocoaEventDispatcherInterruptFlag();
@@ -120,7 +88,7 @@ void QCocoaNativeInterface::onAppFocusWindowChanged(QWindow *window)
void QCocoaNativeInterface::registerDraggedTypes(const QStringList &types)
{
- qt_mac_registerDraggedTypes(types);
+ QMacMimeRegistry::registerDraggedTypes(types);
}
void QCocoaNativeInterface::setEmbeddedInForeignView(QPlatformWindow *window, bool embedded)
@@ -140,16 +108,6 @@ void QCocoaNativeInterface::registerTouchWindow(QWindow *window, bool enable)
cocoaWindow->registerTouch(enable);
}
-void QCocoaNativeInterface::setContentBorderThickness(QWindow *window, int topThickness, int bottomThickness)
-{
- if (!window)
- return;
-
- QCocoaWindow *cocoaWindow = static_cast<QCocoaWindow *>(window->handle());
- if (cocoaWindow)
- cocoaWindow->setContentBorderThickness(topThickness, bottomThickness);
-}
-
void QCocoaNativeInterface::registerContentBorderArea(QWindow *window, quintptr identifier, int upper, int lower)
{
if (!window)
@@ -170,15 +128,6 @@ void QCocoaNativeInterface::setContentBorderAreaEnabled(QWindow *window, quintpt
cocoaWindow->setContentBorderAreaEnabled(identifier, enable);
}
-void QCocoaNativeInterface::setNSToolbar(QWindow *window, void *nsToolbar)
-{
- QCocoaIntegration::instance()->setToolbar(window, static_cast<NSToolbar *>(nsToolbar));
-
- QCocoaWindow *cocoaWindow = static_cast<QCocoaWindow *>(window->handle());
- if (cocoaWindow)
- cocoaWindow->updateNSToolbar();
-}
-
bool QCocoaNativeInterface::testContentBorderPosition(QWindow *window, int position)
{
if (!window)
diff --git a/src/plugins/platforms/cocoa/qcocoansmenu.mm b/src/plugins/platforms/cocoa/qcocoansmenu.mm
index 4bc9b0b5f9..ba222a3ef4 100644
--- a/src/plugins/platforms/cocoa/qcocoansmenu.mm
+++ b/src/plugins/platforms/cocoa/qcocoansmenu.mm
@@ -16,6 +16,8 @@
#include <QtCore/qvarlengtharray.h>
#include <QtGui/private/qapplekeymapper_p.h>
+#include <QtCore/qpointer.h>
+
static NSString *qt_mac_removePrivateUnicode(NSString *string)
{
if (const int len = string.length) {
diff --git a/src/plugins/platforms/cocoa/qcocoascreen.h b/src/plugins/platforms/cocoa/qcocoascreen.h
index 435a6b95d8..7708dc1968 100644
--- a/src/plugins/platforms/cocoa/qcocoascreen.h
+++ b/src/plugins/platforms/cocoa/qcocoascreen.h
@@ -41,13 +41,14 @@ public:
QWindow *topLevelAt(const QPoint &point) const override;
QList<QPlatformScreen *> virtualSiblings() const override;
QPlatformScreen::SubpixelAntialiasingType subpixelAntialiasingTypeHint() const override;
+ Qt::ScreenOrientation orientation() const override;
// ----------------------------------------------------
static NSScreen *nativeScreenForDisplayId(CGDirectDisplayID displayId);
NSScreen *nativeScreen() const;
- void requestUpdate();
+ bool requestUpdate();
void deliverUpdateRequests();
bool isRunningDisplayLink() const;
@@ -90,6 +91,7 @@ private:
QSizeF m_physicalSize;
QCocoaCursor *m_cursor;
qreal m_devicePixelRatio = 0;
+ qreal m_rotation = 0;
CVDisplayLinkRef m_displayLink = nullptr;
dispatch_source_t m_displayLinkSource = nullptr;
diff --git a/src/plugins/platforms/cocoa/qcocoascreen.mm b/src/plugins/platforms/cocoa/qcocoascreen.mm
index a8778ebe1d..be562e5455 100644
--- a/src/plugins/platforms/cocoa/qcocoascreen.mm
+++ b/src/plugins/platforms/cocoa/qcocoascreen.mm
@@ -15,7 +15,9 @@
#include <IOKit/graphics/IOGraphicsLib.h>
#include <QtGui/private/qwindow_p.h>
+#include <QtGui/private/qhighdpiscaling_p.h>
+#include <QtCore/private/qcore_mac_p.h>
#include <QtCore/private/qeventdispatcher_cf_p.h>
QT_BEGIN_NAMESPACE
@@ -198,7 +200,7 @@ QCocoaScreen::~QCocoaScreen()
static QString displayName(CGDirectDisplayID displayID)
{
QIOType<io_iterator_t> iterator;
- if (IOServiceGetMatchingServices(kIOMasterPortDefault,
+ if (IOServiceGetMatchingServices(kIOMainPortDefault,
IOServiceMatching("IODisplayConnect"), &iterator))
return QString();
@@ -246,6 +248,7 @@ void QCocoaScreen::update(CGDirectDisplayID displayId)
const QRect previousGeometry = m_geometry;
const QRect previousAvailableGeometry = m_availableGeometry;
const qreal previousRefreshRate = m_refreshRate;
+ const double previousRotation = m_rotation;
// The reference screen for the geometry is always the primary screen
QRectF primaryScreenGeometry = QRectF::fromCGRect(CGDisplayBounds(CGMainDisplayID()));
@@ -258,7 +261,9 @@ void QCocoaScreen::update(CGDirectDisplayID displayId)
m_depth = NSBitsPerPixelFromDepth(nsScreen.depth);
m_colorSpace = QColorSpace::fromIccProfile(QByteArray::fromNSData(nsScreen.colorSpace.ICCProfileData));
if (!m_colorSpace.isValid()) {
- qWarning() << "macOS generated a color-profile Qt couldn't parse. This shouldn't happen.";
+ qCWarning(lcQpaScreen) << "Failed to parse ICC profile for" << nsScreen.colorSpace
+ << "with ICC data" << nsScreen.colorSpace.ICCProfileData
+ << "- Falling back to sRGB";
m_colorSpace = QColorSpace::SRgb;
}
@@ -268,6 +273,7 @@ void QCocoaScreen::update(CGDirectDisplayID displayId)
QCFType<CGDisplayModeRef> displayMode = CGDisplayCopyDisplayMode(m_displayId);
float refresh = CGDisplayModeGetRefreshRate(displayMode);
m_refreshRate = refresh > 0 ? refresh : 60.0;
+ m_rotation = CGDisplayRotation(displayId);
if (@available(macOS 10.15, *))
m_name = QString::fromNSString(nsScreen.localizedName);
@@ -276,6 +282,9 @@ void QCocoaScreen::update(CGDirectDisplayID displayId)
const bool didChangeGeometry = m_geometry != previousGeometry || m_availableGeometry != previousAvailableGeometry;
+ if (m_rotation != previousRotation)
+ QWindowSystemInterface::handleScreenOrientationChange(screen(), orientation());
+
if (didChangeGeometry)
QWindowSystemInterface::handleScreenGeometryChange(screen(), geometry(), availableGeometry());
if (m_refreshRate != previousRefreshRate)
@@ -286,24 +295,33 @@ void QCocoaScreen::update(CGDirectDisplayID displayId)
Q_LOGGING_CATEGORY(lcQpaScreenUpdates, "qt.qpa.screen.updates", QtCriticalMsg);
-void QCocoaScreen::requestUpdate()
+bool QCocoaScreen::requestUpdate()
{
Q_ASSERT(m_displayId);
if (!isOnline()) {
qCDebug(lcQpaScreenUpdates) << this << "is not online. Ignoring update request";
- return;
+ return false;
}
if (!m_displayLink) {
- CVDisplayLinkCreateWithCGDisplay(m_displayId, &m_displayLink);
+ qCDebug(lcQpaScreenUpdates) << "Creating display link for" << this;
+ if (CVDisplayLinkCreateWithCGDisplay(m_displayId, &m_displayLink) != kCVReturnSuccess) {
+ qCWarning(lcQpaScreenUpdates) << "Failed to create display link for" << this;
+ return false;
+ }
+ if (auto displayId = CVDisplayLinkGetCurrentCGDisplay(m_displayLink); displayId != m_displayId) {
+ qCWarning(lcQpaScreenUpdates) << "Unexpected display" << displayId << "for display link";
+ CVDisplayLinkRelease(m_displayLink);
+ m_displayLink = nullptr;
+ return false;
+ }
CVDisplayLinkSetOutputCallback(m_displayLink, [](CVDisplayLinkRef, const CVTimeStamp*,
const CVTimeStamp*, CVOptionFlags, CVOptionFlags*, void* displayLinkContext) -> int {
// FIXME: It would be nice if update requests would include timing info
static_cast<QCocoaScreen*>(displayLinkContext)->deliverUpdateRequests();
return kCVReturnSuccess;
}, this);
- qCDebug(lcQpaScreenUpdates) << "Display link created for" << this;
// During live window resizing -[NSWindow _resizeWithEvent:] will spin a local event loop
// in event-tracking mode, dequeuing only the mouse drag events needed to update the window's
@@ -358,6 +376,8 @@ void QCocoaScreen::requestUpdate()
qCDebug(lcQpaScreenUpdates) << "Starting display link for" << this;
CVDisplayLinkStart(m_displayLink);
}
+
+ return true;
}
// Helper to allow building up debug output in multiple steps
@@ -451,6 +471,25 @@ void QCocoaScreen::deliverUpdateRequests()
if (!platformWindow->updatesWithDisplayLink())
continue;
+ // QTBUG-107198: Skip updates in a live resize for a better resize experience.
+ if (platformWindow->isContentView() && platformWindow->view().inLiveResize) {
+ const QSurface::SurfaceType surfaceType = window->surfaceType();
+ const bool usesMetalLayer = surfaceType == QWindow::MetalSurface || surfaceType == QWindow::VulkanSurface;
+ const bool usesNonDefaultContentsPlacement = [platformWindow->view() layerContentsPlacement]
+ != NSViewLayerContentsPlacementScaleAxesIndependently;
+ if (usesMetalLayer && usesNonDefaultContentsPlacement) {
+ static bool deliverDisplayLinkUpdatesDuringLiveResize =
+ qEnvironmentVariableIsSet("QT_MAC_DISPLAY_LINK_UPDATE_IN_RESIZE");
+ if (!deliverDisplayLinkUpdatesDuringLiveResize) {
+ // Must keep the link running, we do not know what the event
+ // handlers for UpdateRequest (which is not sent now) would do,
+ // would they trigger a new requestUpdate() or not.
+ pauseUpdates = false;
+ continue;
+ }
+ }
+ }
+
platformWindow->deliverUpdateRequest();
// Another update request was triggered, keep the display link running
@@ -488,41 +527,56 @@ QPlatformScreen::SubpixelAntialiasingType QCocoaScreen::subpixelAntialiasingType
return type;
}
+Qt::ScreenOrientation QCocoaScreen::orientation() const
+{
+ if (m_rotation == 0)
+ return Qt::LandscapeOrientation;
+ if (m_rotation == 90)
+ return Qt::PortraitOrientation;
+ if (m_rotation == 180)
+ return Qt::InvertedLandscapeOrientation;
+ if (m_rotation == 270)
+ return Qt::InvertedPortraitOrientation;
+ return QPlatformScreen::orientation();
+}
+
QWindow *QCocoaScreen::topLevelAt(const QPoint &point) const
{
- NSPoint screenPoint = mapToNative(point);
-
- // Search (hit test) for the top-level window. [NSWidow windowNumberAtPoint:
- // belowWindowWithWindowNumber] may return windows that are not interesting
- // to Qt. The search iterates until a suitable window or no window is found.
- NSInteger topWindowNumber = 0;
- QWindow *window = nullptr;
- do {
- // Get the top-most window, below any previously rejected window.
- topWindowNumber = [NSWindow windowNumberAtPoint:screenPoint
- belowWindowWithWindowNumber:topWindowNumber];
-
- // Continue the search if the window does not belong to this process.
- NSWindow *nsWindow = [NSApp windowWithWindowNumber:topWindowNumber];
- if (!nsWindow)
- continue;
+ __block QWindow *window = nullptr;
+ [NSApp enumerateWindowsWithOptions:NSWindowListOrderedFrontToBack
+ usingBlock:^(NSWindow *nsWindow, BOOL *stop) {
+ if (!nsWindow)
+ return;
- // Continue the search if the window does not belong to Qt.
- if (![nsWindow conformsToProtocol:@protocol(QNSWindowProtocol)])
- continue;
+ // Continue the search if the window does not belong to Qt
+ if (![nsWindow conformsToProtocol:@protocol(QNSWindowProtocol)])
+ return;
- QCocoaWindow *cocoaWindow = qnsview_cast(nsWindow.contentView).platformWindow;
- if (!cocoaWindow)
- continue;
- window = cocoaWindow->window();
+ QCocoaWindow *cocoaWindow = qnsview_cast(nsWindow.contentView).platformWindow;
+ if (!cocoaWindow)
+ return;
+
+ QWindow *w = cocoaWindow->window();
+ if (!w->isVisible())
+ return;
- // Continue the search if the window is not a top-level window.
- if (!window->isTopLevel())
- continue;
+ auto nativeGeometry = QHighDpi::toNativePixels(w->geometry(), w);
+ if (!nativeGeometry.contains(point))
+ return;
- // Stop searching. The current window is the correct window.
- break;
- } while (topWindowNumber > 0);
+ QRegion mask = QHighDpi::toNativeLocalPosition(w->mask(), w);
+ if (!mask.isEmpty() && !mask.contains(point - nativeGeometry.topLeft()))
+ return;
+
+ window = w;
+
+ // Continue the search if the window is not a top-level window
+ if (!window->isTopLevel())
+ return;
+
+ *stop = true;
+ }
+ ];
return window;
}
diff --git a/src/plugins/platforms/cocoa/qcocoaservices.h b/src/plugins/platforms/cocoa/qcocoaservices.h
index 20d9b67760..a0aec6f16b 100644
--- a/src/plugins/platforms/cocoa/qcocoaservices.h
+++ b/src/plugins/platforms/cocoa/qcocoaservices.h
@@ -11,8 +11,12 @@ QT_BEGIN_NAMESPACE
class QCocoaServices : public QPlatformServices
{
public:
+ bool hasCapability(Capability capability) const override;
+
bool openUrl(const QUrl &url) override;
bool openDocument(const QUrl &url) override;
+
+ QPlatformServiceColorPicker *colorPicker(QWindow *parent) override;
};
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/cocoa/qcocoaservices.mm b/src/plugins/platforms/cocoa/qcocoaservices.mm
index 29f8ed96e6..4566cbb8f7 100644
--- a/src/plugins/platforms/cocoa/qcocoaservices.mm
+++ b/src/plugins/platforms/cocoa/qcocoaservices.mm
@@ -4,26 +4,53 @@
#include "qcocoaservices.h"
#include <AppKit/NSWorkspace.h>
+#include <AppKit/NSColorSampler.h>
#include <Foundation/NSURL.h>
#include <QtCore/QUrl>
+#include <QtGui/private/qcoregraphics_p.h>
QT_BEGIN_NAMESPACE
bool QCocoaServices::openUrl(const QUrl &url)
{
- const QString scheme = url.scheme();
- if (scheme.isEmpty())
- return openDocument(url);
return [[NSWorkspace sharedWorkspace] openURL:url.toNSURL()];
}
bool QCocoaServices::openDocument(const QUrl &url)
{
- if (!url.isValid())
- return false;
+ return openUrl(url);
+}
+
+class QCocoaColorPicker : public QPlatformServiceColorPicker
+{
+public:
+ QCocoaColorPicker() : m_colorSampler([NSColorSampler new]) {}
+ ~QCocoaColorPicker() { [m_colorSampler release]; }
+
+ void pickColor() override
+ {
+ [m_colorSampler showSamplerWithSelectionHandler:^(NSColor *selectedColor) {
+ emit colorPicked(qt_mac_toQColor(selectedColor));
+ }];
+ }
+private:
+ NSColorSampler *m_colorSampler = nullptr;
+};
+
+
+QPlatformServiceColorPicker *QCocoaServices::colorPicker(QWindow *parent)
+{
+ Q_UNUSED(parent);
+ return new QCocoaColorPicker;
+}
- return [[NSWorkspace sharedWorkspace] openFile:url.toLocalFile().toNSString()];
+bool QCocoaServices::hasCapability(Capability capability) const
+{
+ switch (capability) {
+ case ColorPicking: return true;
+ default: return false;
+ }
}
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/cocoa/qcocoasystemtrayicon.h b/src/plugins/platforms/cocoa/qcocoasystemtrayicon.h
index 414560e119..75c33cc5a3 100644
--- a/src/plugins/platforms/cocoa/qcocoasystemtrayicon.h
+++ b/src/plugins/platforms/cocoa/qcocoasystemtrayicon.h
@@ -45,12 +45,11 @@ public:
bool isSystemTrayAvailable() const override;
bool supportsMessages() const override;
- void statusItemClicked();
+ void emitActivated();
private:
NSStatusItem *m_statusItem = nullptr;
QStatusItemDelegate *m_delegate = nullptr;
- QCocoaMenu *m_menu = nullptr;
};
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/cocoa/qcocoasystemtrayicon.mm b/src/plugins/platforms/cocoa/qcocoasystemtrayicon.mm
index c004cd69b5..cec8301cf6 100644
--- a/src/plugins/platforms/cocoa/qcocoasystemtrayicon.mm
+++ b/src/plugins/platforms/cocoa/qcocoasystemtrayicon.mm
@@ -56,6 +56,12 @@
#include "qcocoascreen.h"
#include <QtGui/private/qcoregraphics_p.h>
+#warning NSUserNotification was deprecated in macOS 11. \
+We should be using UserNotifications.framework instead. \
+See QTBUG-110998 for more information.
+#define NSUserNotificationCenter QT_IGNORE_DEPRECATIONS(NSUserNotificationCenter)
+#define NSUserNotification QT_IGNORE_DEPRECATIONS(NSUserNotification)
+
QT_BEGIN_NAMESPACE
void QCocoaSystemTrayIcon::init()
@@ -64,6 +70,8 @@ void QCocoaSystemTrayIcon::init()
m_delegate = [[QStatusItemDelegate alloc] initWithSysTray:this];
+ // In case the status item does not have a menu assigned to it
+ // we fall back to the item's button to detect activation.
m_statusItem.button.target = m_delegate;
m_statusItem.button.action = @selector(statusItemClicked);
[m_statusItem.button sendActionOn:NSEventMaskLeftMouseDown | NSEventMaskRightMouseDown | NSEventMaskOtherMouseDown];
@@ -81,8 +89,6 @@ void QCocoaSystemTrayIcon::cleanup()
[m_delegate release];
m_delegate = nil;
-
- m_menu = nullptr;
}
QRect QCocoaSystemTrayIcon::geometry() const
@@ -178,12 +184,31 @@ void QCocoaSystemTrayIcon::updateIcon(const QIcon &icon)
void QCocoaSystemTrayIcon::updateMenu(QPlatformMenu *menu)
{
- // We don't set the menu property of the NSStatusItem here,
- // as that would prevent us from receiving the action for the
- // click, and we wouldn't be able to emit the activated signal.
- // Instead we show the menu manually when the status item is
- // clicked.
- m_menu = static_cast<QCocoaMenu *>(menu);
+ auto *nsMenu = menu ? static_cast<QCocoaMenu *>(menu)->nsMenu() : nil;
+ if (m_statusItem.menu == nsMenu)
+ return;
+
+ if (m_statusItem.menu) {
+ [NSNotificationCenter.defaultCenter removeObserver:m_delegate
+ name:NSMenuDidBeginTrackingNotification
+ object:m_statusItem.menu
+ ];
+ }
+
+ m_statusItem.menu = nsMenu;
+
+ if (m_statusItem.menu) {
+ // When a menu is assigned, NSStatusBarButtonCell will intercept the mouse
+ // down to pop up the menu, and we never see the NSStatusBarButton action.
+ // To ensure we emit the 'activated' signal in both cases we detect when
+ // menu starts tracking, which happens before the menu delegate is sent
+ // the menuWillOpen callback we use to emit aboutToShow for the menu.
+ [NSNotificationCenter.defaultCenter addObserver:m_delegate
+ selector:@selector(statusItemMenuBeganTracking:)
+ name:NSMenuDidBeginTrackingNotification
+ object:m_statusItem.menu
+ ];
+ }
}
void QCocoaSystemTrayIcon::updateToolTip(const QString &toolTip)
@@ -226,7 +251,7 @@ void QCocoaSystemTrayIcon::showMessage(const QString &title, const QString &mess
}
}
-void QCocoaSystemTrayIcon::statusItemClicked()
+void QCocoaSystemTrayIcon::emitActivated()
{
auto *mouseEvent = NSApp.currentEvent;
@@ -245,9 +270,6 @@ void QCocoaSystemTrayIcon::statusItemClicked()
}
emit activated(activationReason);
-
- if (NSMenu *menu = m_menu ? m_menu->nsMenu() : nil)
- QT_IGNORE_DEPRECATIONS([m_statusItem popUpStatusItemMenu:menu]);
}
QT_END_NAMESPACE
@@ -270,7 +292,12 @@ QT_END_NAMESPACE
- (void)statusItemClicked
{
- self.platformSystemTray->statusItemClicked();
+ self.platformSystemTray->emitActivated();
+}
+
+- (void)statusItemMenuBeganTracking:(NSNotification*)notification
+{
+ self.platformSystemTray->emitActivated();
}
- (BOOL)userNotificationCenter:(NSUserNotificationCenter *)center shouldPresentNotification:(NSUserNotification *)notification
diff --git a/src/plugins/platforms/cocoa/qcocoatheme.h b/src/plugins/platforms/cocoa/qcocoatheme.h
index db8ade1fe7..c49d83feae 100644
--- a/src/plugins/platforms/cocoa/qcocoatheme.h
+++ b/src/plugins/platforms/cocoa/qcocoatheme.h
@@ -35,9 +35,10 @@ public:
const QFont *font(Font type = SystemFont) const override;
QPixmap standardPixmap(StandardPixmap sp, const QSizeF &size) const override;
QIcon fileIcon(const QFileInfo &fileInfo, QPlatformTheme::IconOptions options = {}) const override;
+ QIconEngine *createIconEngine(const QString &iconName) const override;
QVariant themeHint(ThemeHint hint) const override;
- Appearance appearance() const override;
+ Qt::ColorScheme colorScheme() const override;
QString standardButtonText(int button) const override;
QKeySequence standardButtonShortcut(int button) const override;
@@ -54,6 +55,9 @@ private:
QMacNotificationObserver m_systemColorObserver;
mutable QHash<QPlatformTheme::Palette, QPalette*> m_palettes;
QMacKeyValueObserver m_appearanceObserver;
+
+ Qt::ColorScheme m_colorScheme = Qt::ColorScheme::Unknown;
+ void updateColorScheme();
};
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/cocoa/qcocoatheme.mm b/src/plugins/platforms/cocoa/qcocoatheme.mm
index 91fc110db5..f4fbfadbe4 100644
--- a/src/plugins/platforms/cocoa/qcocoatheme.mm
+++ b/src/plugins/platforms/cocoa/qcocoatheme.mm
@@ -15,12 +15,14 @@
#include "qcocoahelpers.h"
#include <QtCore/qfileinfo.h>
+#include <QtCore/private/qcore_mac_p.h>
#include <QtGui/private/qfont_p.h>
#include <QtGui/private/qguiapplication_p.h>
#include <QtGui/private/qcoregraphics_p.h>
#include <QtGui/qpainter.h>
#include <QtGui/qtextformat.h>
#include <QtGui/private/qcoretextfontdatabase_p.h>
+#include <QtGui/private/qappleiconengine_p.h>
#include <QtGui/private/qfontengine_coretext_p.h>
#include <QtGui/private/qabstractfileiconengine_p.h>
#include <qpa/qplatformdialoghelper.h>
@@ -30,6 +32,7 @@
#include "qcocoacolordialoghelper.h"
#include "qcocoafiledialoghelper.h"
#include "qcocoafontdialoghelper.h"
+#include "qcocoamessagedialog.h"
#include <CoreServices/CoreServices.h>
@@ -92,6 +95,9 @@ static QPalette *qt_mac_createSystemPalette()
palette->setColor(QPalette::Inactive, QPalette::PlaceholderText, qc);
palette->setColor(QPalette::Disabled, QPalette::PlaceholderText, qc);
+ qc = qt_mac_toQColor([NSColor controlAccentColor]);
+ palette->setColor(QPalette::Accent, qc);
+
return palette;
}
@@ -219,6 +225,8 @@ QCocoaTheme::QCocoaTheme()
NSSystemColorsDidChangeNotification, [this] {
handleSystemThemeChange();
});
+
+ updateColorScheme();
}
QCocoaTheme::~QCocoaTheme()
@@ -237,6 +245,9 @@ void QCocoaTheme::reset()
void QCocoaTheme::handleSystemThemeChange()
{
reset();
+
+ updateColorScheme();
+
m_systemPalette = qt_mac_createSystemPalette();
m_palettes = qt_mac_createRolePalettes();
@@ -250,13 +261,15 @@ void QCocoaTheme::handleSystemThemeChange()
bool QCocoaTheme::usePlatformNativeDialog(DialogType dialogType) const
{
- if (dialogType == QPlatformTheme::FileDialog)
- return true;
- if (dialogType == QPlatformTheme::ColorDialog)
- return true;
- if (dialogType == QPlatformTheme::FontDialog)
+ switch (dialogType) {
+ case QPlatformTheme::FileDialog:
+ case QPlatformTheme::ColorDialog:
+ case QPlatformTheme::FontDialog:
+ case QPlatformTheme::MessageDialog:
return true;
- return false;
+ default:
+ return false;
+ }
}
QPlatformDialogHelper *QCocoaTheme::createPlatformDialogHelper(DialogType dialogType) const
@@ -268,6 +281,8 @@ QPlatformDialogHelper *QCocoaTheme::createPlatformDialogHelper(DialogType dialog
return new QCocoaColorDialogHelper();
case QPlatformTheme::FontDialog:
return new QCocoaFontDialogHelper();
+ case QPlatformTheme::MessageDialog:
+ return new QCocoaMessageDialog;
default:
return nullptr;
}
@@ -379,7 +394,7 @@ QPixmap QCocoaTheme::standardPixmap(StandardPixmap sp, const QSizeF &size) const
if (icon) {
pixmap = qt_mac_convert_iconref(icon, size.width(), size.height());
- ReleaseIconRef(icon);
+ QT_IGNORE_DEPRECATIONS(ReleaseIconRef(icon));
}
return pixmap;
@@ -395,19 +410,8 @@ public:
QPlatformTheme::IconOptions opts) :
QAbstractFileIconEngine(info, opts) {}
- static QList<QSize> availableIconSizes()
- {
- const qreal devicePixelRatio = qGuiApp->devicePixelRatio();
- const int sizes[] = {
- qRound(16 * devicePixelRatio), qRound(32 * devicePixelRatio),
- qRound(64 * devicePixelRatio), qRound(128 * devicePixelRatio),
- qRound(256 * devicePixelRatio)
- };
- return QAbstractFileIconEngine::toSizeList(sizes, sizes + sizeof(sizes) / sizeof(sizes[0]));
- }
-
QList<QSize> availableSizes(QIcon::Mode = QIcon::Normal, QIcon::State = QIcon::Off) override
- { return QCocoaFileIconEngine::availableIconSizes(); }
+ { return QAppleIconEngine::availableIconSizes(); }
protected:
QPixmap filePixmap(const QSize &size, QIcon::Mode, QIcon::State) override
@@ -426,6 +430,11 @@ QIcon QCocoaTheme::fileIcon(const QFileInfo &fileInfo, QPlatformTheme::IconOptio
return QIcon(new QCocoaFileIconEngine(fileInfo, iconOptions));
}
+QIconEngine *QCocoaTheme::createIconEngine(const QString &iconName) const
+{
+ return new QAppleIconEngine(iconName);
+}
+
QVariant QCocoaTheme::themeHint(ThemeHint hint) const
{
switch (hint) {
@@ -439,7 +448,7 @@ QVariant QCocoaTheme::themeHint(ThemeHint hint) const
return QVariant([[NSApplication sharedApplication] isFullKeyboardAccessEnabled] ?
int(Qt::TabFocusAllControls) : int(Qt::TabFocusTextControls | Qt::TabFocusListControls));
case IconPixmapSizes:
- return QVariant::fromValue(QCocoaFileIconEngine::availableIconSizes());
+ return QVariant::fromValue(QAppleIconEngine::availableIconSizes());
case QPlatformTheme::PasswordMaskCharacter:
return QVariant(QChar(0x2022));
case QPlatformTheme::UiEffects:
@@ -452,15 +461,33 @@ QVariant QCocoaTheme::themeHint(ThemeHint hint) const
return !NSScreen.screensHaveSeparateSpaces;
case QPlatformTheme::ShowDirectoriesFirst:
return false;
+ case QPlatformTheme::MouseDoubleClickInterval:
+ return NSEvent.doubleClickInterval * 1000;
+ case QPlatformTheme::KeyboardInputInterval:
+ return NSEvent.keyRepeatDelay * 1000;
+ case QPlatformTheme::KeyboardAutoRepeatRate:
+ return 1.0 / NSEvent.keyRepeatInterval;
default:
break;
}
return QPlatformTheme::themeHint(hint);
}
-QPlatformTheme::Appearance QCocoaTheme::appearance() const
+Qt::ColorScheme QCocoaTheme::colorScheme() const
+{
+ return m_colorScheme;
+}
+
+/*
+ Update the theme's color scheme based on the current appearance.
+
+ We can only reference the appearance on the main thread, but the
+ CoreText font engine needs to know the color scheme, and might be
+ used from secondary threads, so we cache the color scheme.
+*/
+void QCocoaTheme::updateColorScheme()
{
- return qt_mac_applicationIsInDarkMode() ? Appearance::Dark : Appearance::Light;
+ m_colorScheme = qt_mac_applicationIsInDarkMode() ? Qt::ColorScheme::Dark : Qt::ColorScheme::Light;
}
QString QCocoaTheme::standardButtonText(int button) const
@@ -478,12 +505,16 @@ QKeySequence QCocoaTheme::standardButtonShortcut(int button) const
QPlatformMenuItem *QCocoaTheme::createPlatformMenuItem() const
{
- return new QCocoaMenuItem();
+ auto *menuItem = new QCocoaMenuItem();
+ qCDebug(lcQpaMenus) << "Created" << menuItem;
+ return menuItem;
}
QPlatformMenu *QCocoaTheme::createPlatformMenu() const
{
- return new QCocoaMenu();
+ auto *menu = new QCocoaMenu();
+ qCDebug(lcQpaMenus) << "Created" << menu;
+ return menu;
}
QPlatformMenuBar *QCocoaTheme::createPlatformMenuBar() const
@@ -496,7 +527,9 @@ QPlatformMenuBar *QCocoaTheme::createPlatformMenuBar() const
SLOT(onAppFocusWindowChanged(QWindow*)));
}
- return new QCocoaMenuBar();
+ auto *menuBar = new QCocoaMenuBar();
+ qCDebug(lcQpaMenus) << "Created" << menuBar;
+ return menuBar;
}
#ifndef QT_NO_SHORTCUT
diff --git a/src/plugins/platforms/cocoa/qcocoawindow.h b/src/plugins/platforms/cocoa/qcocoawindow.h
index 67fabadb01..ba1bc052fb 100644
--- a/src/plugins/platforms/cocoa/qcocoawindow.h
+++ b/src/plugins/platforms/cocoa/qcocoawindow.h
@@ -39,9 +39,9 @@ class QDebug;
// QCocoaWindow
//
// QCocoaWindow is an NSView (not an NSWindow!) in the sense
-// that it relies on a NSView for all event handling and
-// graphics output and does not require a NSWindow, except for
-// for the window-related functions like setWindowTitle.
+// that it relies on an NSView for all event handling and
+// graphics output and does not require an NSWindow, except for
+// the window-related functions like setWindowTitle.
//
// As a consequence of this it is possible to embed the QCocoaWindow
// in an NSView hierarchy by getting a pointer to the "backing"
@@ -158,18 +158,19 @@ public:
void setWindowCursor(NSCursor *cursor);
void registerTouch(bool enable);
- void setContentBorderThickness(int topThickness, int bottomThickness);
+
void registerContentBorderArea(quintptr identifier, int upper, int lower);
void setContentBorderAreaEnabled(quintptr identifier, bool enable);
void setContentBorderEnabled(bool enable) override;
bool testContentBorderAreaPosition(int position) const;
void applyContentBorderThickness(NSWindow *window = nullptr);
- void updateNSToolbar();
qreal devicePixelRatio() const override;
QWindow *childWindowAt(QPoint windowPoint);
bool shouldRefuseKeyWindowAndFirstResponder();
+ bool windowEvent(QEvent *event) override;
+
QPoint bottomLeftClippedByNSWindowOffset() const override;
void updateNormalGeometry();
@@ -189,7 +190,7 @@ protected:
void recreateWindowIfNeeded();
QCocoaNSWindow *createNSWindow(bool shouldBePanel);
- Qt::WindowState windowState() const;
+ Qt::WindowStates windowState() const;
void applyWindowState(Qt::WindowStates newState);
void toggleMaximized();
void toggleFullScreen();
@@ -215,32 +216,35 @@ public: // for QNSView
void handleWindowStateChanged(HandleFlags flags = NoHandleFlags);
void handleExposeEvent(const QRegion &region);
- NSView *m_view;
- QCocoaNSWindow *m_nsWindow;
+ static void closeAllPopups();
+ static void setupPopupMonitor();
+ static void removePopupMonitor();
+
+ NSView *m_view = nil;
+ QCocoaNSWindow *m_nsWindow = nil;
- Qt::WindowStates m_lastReportedWindowState;
- Qt::WindowModality m_windowModality;
+ Qt::WindowStates m_lastReportedWindowState = Qt::WindowNoState;
+ Qt::WindowModality m_windowModality = Qt::NonModal;
static QPointer<QCocoaWindow> s_windowUnderMouse;
- bool m_initialized;
- bool m_inSetVisible;
- bool m_inSetGeometry;
- bool m_inSetStyleMask;
- QCocoaMenuBar *m_menubar;
+ bool m_initialized = false;
+ bool m_inSetVisible = false;
+ bool m_inSetGeometry = false;
+ bool m_inSetStyleMask = false;
- bool m_frameStrutEventsEnabled;
+ QCocoaMenuBar *m_menubar = nullptr;
+
+ bool m_frameStrutEventsEnabled = false;
QRect m_exposedRect;
QRect m_normalGeometry;
- int m_registerTouchCount;
- bool m_resizableTransientParent;
+ int m_registerTouchCount = 0;
+ bool m_resizableTransientParent = false;
static const int NoAlertRequest;
- NSInteger m_alertRequest;
+ NSInteger m_alertRequest = NoAlertRequest;
- bool m_drawContentBorderGradient;
- int m_topContentBorderThickness;
- int m_bottomContentBorderThickness;
+ bool m_drawContentBorderGradient = false;
struct BorderRange {
BorderRange(quintptr i, int u, int l) : identifier(i), upper(u), lower(l) { }
@@ -254,6 +258,9 @@ public: // for QNSView
QHash<quintptr, BorderRange> m_contentBorderAreas; // identifier -> uppper/lower
QHash<quintptr, bool> m_enabledContentBorderAreas; // identifier -> enabled state (true/false)
+ static inline id s_globalMouseMonitor = 0;
+ static inline id s_applicationActivationObserver = 0;
+
#if QT_CONFIG(vulkan)
VkSurfaceKHR m_vulkanSurface = nullptr;
#endif
diff --git a/src/plugins/platforms/cocoa/qcocoawindow.mm b/src/plugins/platforms/cocoa/qcocoawindow.mm
index ef94b49736..4a245a0f8a 100644
--- a/src/plugins/platforms/cocoa/qcocoawindow.mm
+++ b/src/plugins/platforms/cocoa/qcocoawindow.mm
@@ -98,24 +98,7 @@ Q_CONSTRUCTOR_FUNCTION(qRegisterNotificationCallbacks)
const int QCocoaWindow::NoAlertRequest = -1;
QPointer<QCocoaWindow> QCocoaWindow::s_windowUnderMouse;
-QCocoaWindow::QCocoaWindow(QWindow *win, WId nativeHandle)
- : QPlatformWindow(win)
- , m_view(nil)
- , m_nsWindow(nil)
- , m_lastReportedWindowState(Qt::WindowNoState)
- , m_windowModality(Qt::NonModal)
- , m_initialized(false)
- , m_inSetVisible(false)
- , m_inSetGeometry(false)
- , m_inSetStyleMask(false)
- , m_menubar(nullptr)
- , m_frameStrutEventsEnabled(false)
- , m_registerTouchCount(0)
- , m_resizableTransientParent(false)
- , m_alertRequest(NoAlertRequest)
- , m_drawContentBorderGradient(false)
- , m_topContentBorderThickness(0)
- , m_bottomContentBorderThickness(0)
+QCocoaWindow::QCocoaWindow(QWindow *win, WId nativeHandle) : QPlatformWindow(win)
{
qCDebug(lcQpaWindow) << "QCocoaWindow::QCocoaWindow" << window();
@@ -134,16 +117,24 @@ void QCocoaWindow::initialize()
if (!m_view)
m_view = [[QNSView alloc] initWithCocoaWindow:this];
- // Compute the initial geometry based on the geometry set on the
- // QWindow. This geometry has already been reflected to the
- // QPlatformWindow in the constructor, so to ensure that the
- // resulting setGeometry call does not think the geometry has
- // already been applied, we reset the QPlatformWindow's view
- // of the geometry first.
- auto initialGeometry = QPlatformWindow::initialGeometry(window(),
- windowGeometry(), defaultWindowWidth, defaultWindowHeight);
- QPlatformWindow::d_ptr->rect = QRect();
- setGeometry(initialGeometry);
+ if (!isForeignWindow()) {
+ // Compute the initial geometry based on the geometry set on the
+ // QWindow. This geometry has already been reflected to the
+ // QPlatformWindow in the constructor, so to ensure that the
+ // resulting setGeometry call does not think the geometry has
+ // already been applied, we reset the QPlatformWindow's view
+ // of the geometry first.
+ auto initialGeometry = QPlatformWindow::initialGeometry(window(),
+ windowGeometry(), defaultWindowWidth, defaultWindowHeight);
+ QPlatformWindow::d_ptr->rect = QRect();
+ setGeometry(initialGeometry);
+
+ setMask(QHighDpi::toNativeLocalRegion(window()->mask(), window()));
+
+ } else {
+ // Pick up essential foreign window state
+ QPlatformWindow::setGeometry(QRectF::fromCGRect(m_view.frame).toRect());
+ }
recreateWindowIfNeeded();
@@ -159,7 +150,10 @@ QCocoaWindow::~QCocoaWindow()
QMacAutoReleasePool pool;
[m_nsWindow makeFirstResponder:nil];
[m_nsWindow setContentView:nil];
- if ([m_view superview])
+
+ // Remove from superview only if we have a Qt window parent,
+ // as we don't want to affect window container foreign windows.
+ if (QPlatformWindow::parent())
[m_view removeFromSuperview];
// Make sure to disconnect observer in all case if view is valid
@@ -183,8 +177,7 @@ QCocoaWindow::~QCocoaWindow()
object:m_view];
[m_view release];
- [m_nsWindow close];
- [m_nsWindow release];
+ [m_nsWindow closeAndRelease];
}
QSurfaceFormat QCocoaWindow::format() const
@@ -315,6 +308,31 @@ void QCocoaWindow::setVisible(bool visible)
{
qCDebug(lcQpaWindow) << "QCocoaWindow::setVisible" << window() << visible;
+ // Our implementation of setVisible below is not idempotent, as for
+ // modal windows it calls beginSheet/endSheet or starts/ends modal
+ // sessions. However we can't simply guard for m_view.hidden already
+ // having the right state, as the behavior of this function differs
+ // based on whether the window has been initialized or not, as
+ // handleGeometryChange will bail out if the window is still
+ // initializing. Since we know we'll get a second setVisible
+ // call after creation, we can check for that case specifically,
+ // which means we can then safely guard on m_view.hidden changing.
+
+ if (!m_initialized) {
+ qCDebug(lcQpaWindow) << "Window still initializing, skipping setting visibility";
+ return; // We'll get another setVisible call after create is done
+ }
+
+ if (visible == !m_view.hidden && (!isContentView() || visible == m_view.window.visible)) {
+ qCDebug(lcQpaWindow) << "No change in visible status. Ignoring.";
+ return;
+ }
+
+ if (m_inSetVisible) {
+ qCWarning(lcQpaWindow) << "Already setting window visible!";
+ return;
+ }
+
QScopedValueRollback<bool> rollback(m_inSetVisible, true);
QMacAutoReleasePool pool;
@@ -353,6 +371,9 @@ void QCocoaWindow::setVisible(bool visible)
}
+ // Make the NSView visible first, before showing the NSWindow (in case of top level windows)
+ m_view.hidden = NO;
+
if (isContentView()) {
QWindowSystemInterface::flushWindowSystemEvents(QEventLoop::ExcludeUserInputEvents);
@@ -390,13 +411,6 @@ void QCocoaWindow::setVisible(bool visible)
}
}
}
-
- // In some cases, e.g. QDockWidget, the content view is hidden before moving to its own
- // Cocoa window, and then shown again. Therefore, we test for the view being hidden even
- // if it's attached to an NSWindow.
- if ([m_view isHidden])
- [m_view setHidden:NO];
-
} else {
// Window not visible, hide it
if (isContentView()) {
@@ -425,10 +439,28 @@ void QCocoaWindow::setVisible(bool visible)
if (mainWindow && [mainWindow canBecomeKeyWindow])
[mainWindow makeKeyWindow];
}
- } else {
- [m_view setHidden:YES];
}
+ // AppKit will in some cases set up the key view loop for child views, even if we
+ // don't set autorecalculatesKeyViewLoop, nor call recalculateKeyViewLoop ourselves.
+ // When a child window is promoted to a top level, AppKit will maintain the key view
+ // loop between the views, even if these views now cross NSWindows, even after we
+ // explicitly call recalculateKeyViewLoop. When the top level is then hidden, AppKit
+ // will complain when -[NSView _setHidden:setNeedsDisplay:] tries to transfer first
+ // responder by reading the nextValidKeyView, and it turns out to live in a different
+ // window. We mitigate this by a last second reset of the first responder, which is
+ // what AppKit also falls back to. It's unclear if the original situation of views
+ // having their nextKeyView pointing to views in other windows is kosher or not.
+ if (m_view.window.firstResponder == m_view && m_view.nextValidKeyView
+ && m_view.nextValidKeyView.window != m_view.window) {
+ qCDebug(lcQpaWindow) << "Detected nextValidKeyView" << m_view.nextValidKeyView
+ << "in different window" << m_view.nextValidKeyView.window
+ << "Resetting" << m_view.window << "first responder to nil.";
+ [m_view.window makeFirstResponder:nil];
+ }
+
+ m_view.hidden = YES;
+
if (parentCocoaWindow && window()->type() == Qt::Popup) {
NSWindow *nativeParentWindow = parentCocoaWindow->nativeWindow();
if (m_resizableTransientParent
@@ -526,9 +558,11 @@ NSUInteger QCocoaWindow::windowStyleMask(Qt::WindowFlags flags)
// working (for example minimizing frameless windows, or resizing
// windows that don't have zoom or fullscreen titlebar buttons).
styleMask |= NSWindowStyleMaskClosable
- | NSWindowStyleMaskResizable
| NSWindowStyleMaskMiniaturizable;
+ if (type != Qt::Popup) // We only care about popups exactly.
+ styleMask |= NSWindowStyleMaskResizable;
+
if (type == Qt::Tool)
styleMask |= NSWindowStyleMaskUtilityWindow;
@@ -556,8 +590,6 @@ void QCocoaWindow::updateTitleBarButtons(Qt::WindowFlags windowFlags)
if (!isContentView())
return;
- NSWindow *window = m_view.window;
-
static constexpr std::pair<NSWindowButton, Qt::WindowFlags> buttons[] = {
{ NSWindowCloseButton, Qt::WindowCloseButtonHint },
{ NSWindowMiniaturizeButton, Qt::WindowMinimizeButtonHint},
@@ -566,20 +598,38 @@ void QCocoaWindow::updateTitleBarButtons(Qt::WindowFlags windowFlags)
bool hideButtons = true;
for (const auto &[button, buttonHint] : buttons) {
+ // Set up Qt defaults based on window type
bool enabled = true;
+ if (button == NSWindowMiniaturizeButton)
+ enabled = window()->type() != Qt::Dialog;
+
+ // Let users override via CustomizeWindowHint
if (windowFlags & Qt::CustomizeWindowHint)
enabled = windowFlags & buttonHint;
+ // Then do some final sanitizations
+
if (button == NSWindowZoomButton && isFixedSize())
enabled = false;
- [window standardWindowButton:button].enabled = enabled;
+ // Mimic what macOS natively does for parent windows of modal
+ // sheets, which is to disable the close button, but leave the
+ // other buttons as they were.
+ if (button == NSWindowCloseButton && enabled
+ && QWindowPrivate::get(window())->blockedByModalWindow) {
+ enabled = false;
+ // If we end up having no enabled buttons, our workaround
+ // should not be a reason for hiding all of them.
+ hideButtons = false;
+ }
+
+ [m_view.window standardWindowButton:button].enabled = enabled;
hideButtons &= !enabled;
}
// Hide buttons in case we disabled all of them
for (const auto &[button, buttonHint] : buttons)
- [window standardWindowButton:button].hidden = hideButtons;
+ [m_view.window standardWindowButton:button].hidden = hideButtons;
}
void QCocoaWindow::setWindowFlags(Qt::WindowFlags flags)
@@ -658,7 +708,7 @@ void QCocoaWindow::applyWindowState(Qt::WindowStates requestedState)
if (!isContentView())
return;
- const Qt::WindowState currentState = windowState();
+ const Qt::WindowState currentState = QWindowPrivate::effectiveState(windowState());
const Qt::WindowState newState = QWindowPrivate::effectiveState(requestedState);
if (newState == currentState)
@@ -690,9 +740,10 @@ void QCocoaWindow::applyWindowState(Qt::WindowStates requestedState)
switch (currentState) {
case Qt::WindowMinimized:
[nsWindow deminiaturize:sender];
- Q_ASSERT_X(windowState() != Qt::WindowMinimized, "QCocoaWindow",
- "[NSWindow deminiaturize:] is synchronous");
- break;
+ // Deminiaturizing is not synchronous, so we need to wait for the
+ // NSWindowDidMiniaturizeNotification before continuing to apply
+ // the new state.
+ return;
case Qt::WindowFullScreen: {
toggleFullScreen();
// Exiting fullscreen is not synchronous, so we need to wait for the
@@ -726,23 +777,27 @@ void QCocoaWindow::applyWindowState(Qt::WindowStates requestedState)
}
}
-Qt::WindowState QCocoaWindow::windowState() const
+Qt::WindowStates QCocoaWindow::windowState() const
{
- // FIXME: Support compound states (Qt::WindowStates)
-
+ Qt::WindowStates states = Qt::WindowNoState;
NSWindow *window = m_view.window;
+
if (window.miniaturized)
- return Qt::WindowMinimized;
- if (window.qt_fullScreen)
- return Qt::WindowFullScreen;
- if ((window.zoomed && !isTransitioningToFullScreen())
- || (m_lastReportedWindowState == Qt::WindowMaximized && isTransitioningToFullScreen()))
- return Qt::WindowMaximized;
+ states |= Qt::WindowMinimized;
+
+ // Full screen and maximized are mutually exclusive, as macOS
+ // will report a full screen window as zoomed.
+ if (window.qt_fullScreen) {
+ states |= Qt::WindowFullScreen;
+ } else if ((window.zoomed && !isTransitioningToFullScreen())
+ || (m_lastReportedWindowState == Qt::WindowMaximized && isTransitioningToFullScreen())) {
+ states |= Qt::WindowMaximized;
+ }
// Note: We do not report Qt::WindowActive, even if isActive()
// is true, as QtGui does not expect this window state to be set.
- return Qt::WindowNoState;
+ return states;
}
void QCocoaWindow::toggleMaximized()
@@ -858,12 +913,20 @@ void QCocoaWindow::windowDidDeminiaturize()
if (!isContentView())
return;
+ Qt::WindowState requestedState = window()->windowState();
+
handleWindowStateChanged();
+
+ if (requestedState != windowState() && requestedState != Qt::WindowMinimized) {
+ // We were only going out of minimized as an intermediate step before
+ // progressing into the final step, so re-sync the desired state.
+ applyWindowState(requestedState);
+ }
}
void QCocoaWindow::handleWindowStateChanged(HandleFlags flags)
{
- Qt::WindowState currentState = windowState();
+ Qt::WindowStates currentState = windowState();
if (!(flags & HandleUnconditionally) && currentState == m_lastReportedWindowState)
return;
@@ -1078,28 +1141,14 @@ void QCocoaWindow::setMask(const QRegion &region)
}
}
-bool QCocoaWindow::setKeyboardGrabEnabled(bool grab)
+bool QCocoaWindow::setKeyboardGrabEnabled(bool)
{
- qCDebug(lcQpaWindow) << "QCocoaWindow::setKeyboardGrabEnabled" << window() << grab;
- if (!isContentView())
- return false;
-
- if (grab && ![m_view.window isKeyWindow])
- [m_view.window makeKeyWindow];
-
- return true;
+ return false; // FIXME (QTBUG-106597)
}
-bool QCocoaWindow::setMouseGrabEnabled(bool grab)
+bool QCocoaWindow::setMouseGrabEnabled(bool)
{
- qCDebug(lcQpaWindow) << "QCocoaWindow::setMouseGrabEnabled" << window() << grab;
- if (!isContentView())
- return false;
-
- if (grab && ![m_view.window isKeyWindow])
- [m_view.window makeKeyWindow];
-
- return true;
+ return false; // FIXME (QTBUG-106597)
}
WId QCocoaWindow::winId() const
@@ -1197,36 +1246,37 @@ void QCocoaWindow::windowDidEndLiveResize()
void QCocoaWindow::windowDidBecomeKey()
{
- if (!isContentView())
- return;
-
- if (isForeignWindow())
+ // The NSWindow we're part of become key. Check if we're the first
+ // responder, and if so, deliver focus window change to our window.
+ if (m_view.window.firstResponder != m_view)
return;
- QNSView *firstResponderView = qt_objc_cast<QNSView *>(m_view.window.firstResponder);
- if (!firstResponderView)
- return;
+ qCDebug(lcQpaWindow) << m_view.window << "became key window."
+ << "Updating focus window to" << this << "with view" << m_view;
- const QCocoaWindow *focusCocoaWindow = firstResponderView.platformWindow;
- if (focusCocoaWindow->windowIsPopupType())
+ if (windowIsPopupType()) {
+ qCDebug(lcQpaWindow) << "Window is popup. Skipping focus window change.";
return;
+ }
// See also [QNSView becomeFirstResponder]
- QWindowSystemInterface::handleWindowActivated<QWindowSystemInterface::SynchronousDelivery>(
- focusCocoaWindow->window(), Qt::ActiveWindowFocusReason);
+ QWindowSystemInterface::handleFocusWindowChanged<QWindowSystemInterface::SynchronousDelivery>(
+ window(), Qt::ActiveWindowFocusReason);
}
void QCocoaWindow::windowDidResignKey()
{
- if (!isContentView())
+ // The NSWindow we're part of lost key. Check if we're the first
+ // responder, and if so, deliver window deactivation to our window.
+ if (m_view.window.firstResponder != m_view)
return;
- if (isForeignWindow())
- return;
+ qCDebug(lcQpaWindow) << m_view.window << "resigned key window."
+ << "Clearing focus window" << this << "with view" << m_view;
// Make sure popups are closed before we deliver activation changes, which are
// otherwise ignored by QApplication.
- QGuiApplicationPrivate::instance()->closeAllPopups();
+ closeAllPopups();
// The current key window will be non-nil if another window became key. If that
// window is a Qt window, we delay the window activation event until the didBecomeKey
@@ -1234,12 +1284,14 @@ void QCocoaWindow::windowDidResignKey()
NSWindow *newKeyWindow = [NSApp keyWindow];
if (newKeyWindow && newKeyWindow != m_view.window
&& [newKeyWindow conformsToProtocol:@protocol(QNSWindowProtocol)]) {
+ qCDebug(lcQpaWindow) << "New key window" << newKeyWindow
+ << "is Qt window. Deferring focus window change.";
return;
}
// Lost key window, go ahead and set the active window to zero
if (!windowIsPopupType()) {
- QWindowSystemInterface::handleWindowActivated<QWindowSystemInterface::SynchronousDelivery>(
+ QWindowSystemInterface::handleFocusWindowChanged<QWindowSystemInterface::SynchronousDelivery>(
nullptr, Qt::ActiveWindowFocusReason);
}
}
@@ -1276,8 +1328,14 @@ void QCocoaWindow::windowDidOrderOffScreen()
void QCocoaWindow::windowDidChangeOcclusionState()
{
+ // Note, we don't take the view's hiddenOrHasHiddenAncestor state into
+ // account here, but instead leave that up to handleExposeEvent, just
+ // like all the other signals that could potentially change the exposed
+ // state of the window.
bool visible = m_view.window.occlusionState & NSWindowOcclusionStateVisible;
- qCDebug(lcQpaWindow) << "QCocoaWindow::windowDidChangeOcclusionState" << window() << "is now" << (visible ? "visible" : "occluded");
+ qCDebug(lcQpaWindow) << "Occlusion state of" << m_view.window << "for"
+ << window() << "changed to" << (visible ? "visible" : "occluded");
+
if (visible)
[m_view setNeedsDisplay:YES];
else
@@ -1445,14 +1503,30 @@ void QCocoaWindow::recreateWindowIfNeeded()
QMacAutoReleasePool pool;
QPlatformWindow *parentWindow = QPlatformWindow::parent();
-
- const bool isEmbeddedView = isEmbedded();
- RecreationReasons recreateReason = RecreationNotNeeded;
+ auto *parentCocoaWindow = static_cast<QCocoaWindow *>(parentWindow);
QCocoaWindow *oldParentCocoaWindow = nullptr;
if (QNSView *qnsView = qnsview_cast(m_view.superview))
oldParentCocoaWindow = qnsView.platformWindow;
+ if (isForeignWindow()) {
+ // A foreign window is created as such, and can never move between being
+ // foreign and not, so we don't need to get rid of any existing NSWindows,
+ // nor create new ones, as a foreign window is a single simple NSView.
+ qCDebug(lcQpaWindow) << "Skipping NSWindow management for foreign window" << this;
+
+ // We do however need to manage the parent relationship
+ if (parentCocoaWindow)
+ [parentCocoaWindow->m_view addSubview:m_view];
+ else if (oldParentCocoaWindow)
+ [m_view removeFromSuperview];
+
+ return;
+ }
+
+ const bool isEmbeddedView = isEmbedded();
+ RecreationReasons recreateReason = RecreationNotNeeded;
+
if (parentWindow != oldParentCocoaWindow)
recreateReason |= ParentChanged;
@@ -1483,8 +1557,6 @@ void QCocoaWindow::recreateWindowIfNeeded()
if (recreateReason == RecreationNotNeeded)
return;
- QCocoaWindow *parentCocoaWindow = static_cast<QCocoaWindow *>(parentWindow);
-
// Remove current window (if any)
if ((isContentView() && !shouldBeContentView) || (recreateReason & PanelChanged)) {
if (m_nsWindow) {
@@ -1514,31 +1586,10 @@ void QCocoaWindow::recreateWindowIfNeeded()
}
}
- if (isEmbeddedView) {
- // An embedded window doesn't have its own NSWindow.
- } else if (!parentWindow) {
- // QPlatformWindow subclasses must sync up with QWindow on creation:
- propagateSizeHints();
- setWindowFlags(window()->flags());
- setWindowTitle(window()->title());
- setWindowFilePath(window()->filePath()); // Also sets window icon
- setWindowState(window()->windowState());
- } else {
+ if (parentCocoaWindow) {
// Child windows have no NSWindow, re-parent to superview instead
[parentCocoaWindow->m_view addSubview:m_view];
- [m_view setHidden:!window()->isVisible()];
}
-
- const qreal opacity = qt_window_private(window())->opacity;
- if (!qFuzzyCompare(opacity, qreal(1.0)))
- setOpacity(opacity);
-
- setMask(QHighDpi::toNativeLocalRegion(window()->mask(), window()));
-
- // top-level QWindows may have an attached NSToolBar, call
- // update function which will attach to the NSWindow.
- if (!parentWindow && !isEmbeddedView)
- updateNSToolbar();
}
void QCocoaWindow::requestUpdate()
@@ -1547,7 +1598,10 @@ void QCocoaWindow::requestUpdate()
<< "using" << (updatesWithDisplayLink() ? "display-link" : "timer");
if (updatesWithDisplayLink()) {
- static_cast<QCocoaScreen *>(screen())->requestUpdate();
+ if (!static_cast<QCocoaScreen *>(screen())->requestUpdate()) {
+ qCDebug(lcQpaDrawing) << "Falling back to timer-based update request";
+ QPlatformWindow::requestUpdate();
+ }
} else {
// Fall back to the un-throttled timer-based callback
QPlatformWindow::requestUpdate();
@@ -1573,6 +1627,75 @@ void QCocoaWindow::requestActivateWindow()
[m_view.window makeKeyWindow];
}
+/*
+ Closes all popups, and removes observers and monitors.
+*/
+void QCocoaWindow::closeAllPopups()
+{
+ QGuiApplicationPrivate::instance()->closeAllPopups();
+
+ removePopupMonitor();
+}
+
+void QCocoaWindow::removePopupMonitor()
+{
+ if (s_globalMouseMonitor) {
+ [NSEvent removeMonitor:s_globalMouseMonitor];
+ s_globalMouseMonitor = nil;
+ }
+ if (s_applicationActivationObserver) {
+ [[[NSWorkspace sharedWorkspace] notificationCenter] removeObserver:s_applicationActivationObserver];
+ s_applicationActivationObserver = nil;
+ }
+}
+
+void QCocoaWindow::setupPopupMonitor()
+{
+ // we open a popup window while we are not active. None of our existing event
+ // handlers will get called if the user now clicks anywhere outside the application
+ // or activates another window. Use a global event monitor to watch for mouse
+ // presses, and close popups. We also want mouse tracking in the popup to work, so
+ // also watch for MouseMoved.
+ if (!s_globalMouseMonitor) {
+ // we only get LeftMouseDown events when we also set LeftMouseUp.
+ constexpr NSEventMask mouseButtonMask = NSEventTypeLeftMouseDown | NSEventTypeLeftMouseUp
+ | NSEventMaskRightMouseDown | NSEventMaskOtherMouseDown
+ | NSEventMaskMouseMoved;
+ s_globalMouseMonitor = [NSEvent addGlobalMonitorForEventsMatchingMask:mouseButtonMask
+ handler:^(NSEvent *e){
+ if (!QGuiApplicationPrivate::instance()->popupActive()) {
+ removePopupMonitor();
+ return;
+ }
+ const auto eventType = cocoaEvent2QtMouseEvent(e);
+ if (eventType == QEvent::MouseMove) {
+ if (s_windowUnderMouse) {
+ QWindow *window = s_windowUnderMouse->window();
+ const auto button = cocoaButton2QtButton(e);
+ const auto buttons = currentlyPressedMouseButtons();
+ const auto globalPoint = QCocoaScreen::mapFromNative(NSEvent.mouseLocation);
+ const auto localPoint = window->mapFromGlobal(globalPoint.toPoint());
+ QWindowSystemInterface::handleMouseEvent(window, localPoint, globalPoint,
+ buttons, button, eventType);
+ }
+ } else {
+ closeAllPopups();
+ }
+ }];
+ }
+ // The activation observer also gets called when we become active because the user clicks
+ // into the popup. This should not close the popup, so QCocoaApplicationDelegate's
+ // applicationDidBecomeActive implementation removes this observer.
+ if (!s_applicationActivationObserver) {
+ s_applicationActivationObserver = [[[NSWorkspace sharedWorkspace] notificationCenter]
+ addObserverForName:NSWorkspaceDidActivateApplicationNotification
+ object:nil queue:nil
+ usingBlock:^(NSNotification *){
+ closeAllPopups();
+ }];
+ }
+}
+
QCocoaNSWindow *QCocoaWindow::createNSWindow(bool shouldBePanel)
{
QMacAutoReleasePool pool;
@@ -1672,12 +1795,15 @@ QCocoaNSWindow *QCocoaWindow::createNSWindow(bool shouldBePanel)
// Qt::Tool windows hide on app deactivation, unless Qt::WA_MacAlwaysShowToolWindow is set
nsWindow.hidesOnDeactivate = ((type & Qt::Tool) == Qt::Tool) && !alwaysShowToolWindow();
- // Make popup windows show on the same desktop as the parent full-screen window
- nsWindow.collectionBehavior = NSWindowCollectionBehaviorFullScreenAuxiliary;
+ // Make popup windows show on the same desktop as the parent window
+ nsWindow.collectionBehavior = NSWindowCollectionBehaviorFullScreenAuxiliary
+ | NSWindowCollectionBehaviorMoveToActiveSpace;
if ((type & Qt::Popup) == Qt::Popup) {
nsWindow.hasShadow = YES;
nsWindow.animationBehavior = NSWindowAnimationBehaviorUtilityWindow;
+ if (QGuiApplication::applicationState() != Qt::ApplicationActive)
+ setupPopupMonitor();
}
}
@@ -1686,11 +1812,15 @@ QCocoaNSWindow *QCocoaWindow::createNSWindow(bool shouldBePanel)
applyContentBorderThickness(nsWindow);
- if (QColorSpace colorSpace = format().colorSpace(); colorSpace.isValid()) {
- NSData *iccData = colorSpace.iccProfile().toNSData();
- nsWindow.colorSpace = [[[NSColorSpace alloc] initWithICCProfileData:iccData] autorelease];
- qCDebug(lcQpaDrawing) << "Set" << this << "color space to" << nsWindow.colorSpace;
- }
+ // We propagate the view's color space granulary to both the IOSurfaces
+ // used for QSurface::RasterSurface, as well as the CAMetalLayer used for
+ // QSurface::MetalSurface, but for QSurface::OpenGLSurface we don't have
+ // that option as we use NSOpenGLContext instead of CAOpenGLLayer. As a
+ // workaround we set the NSWindow's color space, which affects GL drawing
+ // with NSOpenGLContext as well. This does not conflict with the granular
+ // modifications we do to each surface for raster or Metal.
+ if (auto *qtView = qnsview_cast(m_view))
+ nsWindow.colorSpace = qtView.colorSpace;
return nsWindow;
}
@@ -1725,22 +1855,32 @@ void QCocoaWindow::setWindowCursor(NSCursor *cursor)
if (isForeignWindow())
return;
+ qCInfo(lcQpaMouse) << "Setting" << this << "cursor to" << cursor;
+
QNSView *view = qnsview_cast(m_view);
if (cursor == view.cursor)
return;
view.cursor = cursor;
+ // We're not using the the legacy cursor rects API to manage our
+ // cursor, but calling this function also invalidates AppKit's
+ // view of whether or not we need a cursorUpdate callback for
+ // our tracking area.
[m_view.window invalidateCursorRectsForView:m_view];
- // There's a bug in AppKit where calling invalidateCursorRectsForView when
- // there's an override cursor active (for example when hovering over the
- // window frame), will not result in a cursorUpdate: callback. To work around
- // this we synthesize a cursor update event and call the callback ourselves,
- // if we detect that the mouse is currently over the view.
+ // We've informed AppKit that we need a cursorUpdate, but cursor
+ // updates for tracking areas are deferred in some cases, such as
+ // when the mouse is down, whereas we want a synchronous update.
+ // To ensure an updated cursor we synthesize a cursor update event
+ // now if the window is otherwise allowed to change the cursor.
auto locationInWindow = m_view.window.mouseLocationOutsideOfEventStream;
auto locationInSuperview = [m_view.superview convertPoint:locationInWindow fromView:nil];
- if ([m_view hitTest:locationInSuperview] == m_view) {
+ bool mouseIsOverView = [m_view hitTest:locationInSuperview] == m_view;
+ auto utilityMask = NSWindowStyleMaskUtilityWindow | NSWindowStyleMaskTitled;
+ bool isUtilityWindow = (m_view.window.styleMask & utilityMask) == utilityMask;
+ if (mouseIsOverView && (m_view.window.keyWindow || isUtilityWindow)) {
+ qCDebug(lcQpaMouse) << "Synthesizing cursor update";
[m_view cursorUpdate:[NSEvent enterExitEventWithType:NSEventTypeCursorUpdate
location:locationInWindow modifierFlags:0 timestamp:0
windowNumber:m_view.window.windowNumber context:nil
@@ -1757,16 +1897,6 @@ void QCocoaWindow::registerTouch(bool enable)
m_view.allowedTouchTypes &= ~NSTouchTypeMaskIndirect;
}
-void QCocoaWindow::setContentBorderThickness(int topThickness, int bottomThickness)
-{
- m_topContentBorderThickness = topThickness;
- m_bottomContentBorderThickness = bottomThickness;
- bool enable = (topThickness > 0 || bottomThickness > 0);
- m_drawContentBorderGradient = enable;
-
- applyContentBorderThickness();
-}
-
void QCocoaWindow::registerContentBorderArea(quintptr identifier, int upper, int lower)
{
m_contentBorderAreas.insert(identifier, BorderRange(identifier, upper, lower));
@@ -1803,7 +1933,7 @@ void QCocoaWindow::applyContentBorderThickness(NSWindow *window)
// Find consecutive registered border areas, starting from the top.
std::vector<BorderRange> ranges(m_contentBorderAreas.cbegin(), m_contentBorderAreas.cend());
std::sort(ranges.begin(), ranges.end());
- int effectiveTopContentBorderThickness = m_topContentBorderThickness;
+ int effectiveTopContentBorderThickness = 0;
for (BorderRange range : ranges) {
// Skip disiabled ranges (typically hidden tool bars)
if (!m_enabledContentBorderAreas.value(range.identifier, false))
@@ -1818,7 +1948,7 @@ void QCocoaWindow::applyContentBorderThickness(NSWindow *window)
break;
}
- int effectiveBottomContentBorderThickness = m_bottomContentBorderThickness;
+ int effectiveBottomContentBorderThickness = 0;
[window setStyleMask:[window styleMask] | NSWindowStyleMaskTexturedBackground];
window.titlebarAppearsTransparent = YES;
@@ -1840,21 +1970,6 @@ void QCocoaWindow::applyContentBorderThickness(NSWindow *window)
[[[window contentView] superview] setNeedsDisplay:YES];
}
-void QCocoaWindow::updateNSToolbar()
-{
- if (!isContentView())
- return;
-
- NSToolbar *toolbar = QCocoaIntegration::instance()->toolbar(window());
- const NSWindow *window = m_view.window;
-
- if (window.toolbar == toolbar)
- return;
-
- window.toolbar = toolbar;
- window.showsToolbarButton = YES;
-}
-
bool QCocoaWindow::testContentBorderAreaPosition(int position) const
{
if (!m_drawContentBorderGradient || !isContentView())
@@ -1874,7 +1989,7 @@ qreal QCocoaWindow::devicePixelRatio() const
{
// The documented way to observe the relationship between device-independent
// and device pixels is to use one for the convertToBacking functions. Other
- // methods such as [NSWindow backingScaleFacor] might not give the correct
+ // methods such as [NSWindow backingScaleFactor] might not give the correct
// result, for example if setWantsBestResolutionOpenGLSurface is not set or
// or ignored by the OpenGL driver.
NSSize backingSize = [m_view convertSizeToBacking:NSMakeSize(1.0, 1.0)];
@@ -1901,6 +2016,22 @@ bool QCocoaWindow::shouldRefuseKeyWindowAndFirstResponder()
if (window()->flags() & (Qt::WindowDoesNotAcceptFocus | Qt::WindowTransparentForInput))
return true;
+ // For application modal windows, as well as direct parent windows
+ // of window modal windows, AppKit takes care of blocking interaction.
+ // The Qt expectation however, is that all transient parents of a
+ // window modal window is blocked, as reflected by QGuiApplication.
+ // We reflect this by returning false from this function for transient
+ // parents blocked by a modal window, but limit it to the cases not
+ // covered by AppKit to avoid potential unwanted side effects.
+ QWindow *modalWindow = nullptr;
+ if (QGuiApplicationPrivate::instance()->isWindowBlocked(window(), &modalWindow)) {
+ if (modalWindow->modality() == Qt::WindowModal && modalWindow->transientParent() != window()) {
+ qCDebug(lcQpaWindow) << "Refusing key window for" << this << "due to being"
+ << "blocked by" << modalWindow;
+ return true;
+ }
+ }
+
if (m_inSetVisible) {
QVariant showWithoutActivating = window()->property("_q_showWithoutActivating");
if (showWithoutActivating.isValid() && showWithoutActivating.toBool())
@@ -1910,6 +2041,20 @@ bool QCocoaWindow::shouldRefuseKeyWindowAndFirstResponder()
return false;
}
+bool QCocoaWindow::windowEvent(QEvent *event)
+{
+ switch (event->type()) {
+ case QEvent::WindowBlocked:
+ case QEvent::WindowUnblocked:
+ updateTitleBarButtons(window()->flags());
+ break;
+ default:
+ break;
+ }
+
+ return QPlatformWindow::windowEvent(event);
+}
+
QPoint QCocoaWindow::bottomLeftClippedByNSWindowOffset() const
{
if (!m_view)
diff --git a/src/plugins/platforms/cocoa/qmacclipboard.h b/src/plugins/platforms/cocoa/qmacclipboard.h
index 658497f743..95267565f2 100644
--- a/src/plugins/platforms/cocoa/qmacclipboard.h
+++ b/src/plugins/platforms/cocoa/qmacclipboard.h
@@ -5,49 +5,57 @@
#define QMACCLIPBOARD_H
#include <QtGui>
-#include <QtGui/private/qmacmime_p.h>
+#include <QtGui/qutimimeconverter.h>
+
+#include <QtCore/qpointer.h>
#include <ApplicationServices/ApplicationServices.h>
QT_BEGIN_NAMESPACE
-class QMacMimeData;
+class QUtiMimeConverter;
+
class QMacPasteboard
{
public:
enum DataRequestType { EagerRequest, LazyRequest };
private:
struct Promise {
- Promise() : itemId(0), convertor(nullptr) { }
+ Promise() : itemId(0), converter(nullptr) { }
- static Promise eagerPromise(int itemId, QMacInternalPasteboardMime *c, QString m, QMacMimeData *d, int o = 0);
- static Promise lazyPromise(int itemId, QMacInternalPasteboardMime *c, QString m, QMacMimeData *d, int o = 0);
- Promise(int itemId, QMacInternalPasteboardMime *c, QString m, QMacMimeData *md, int o, DataRequestType drt);
+ static Promise eagerPromise(int itemId, const QUtiMimeConverter *c, const QString &m, QMimeData *d, int o = 0);
+ static Promise lazyPromise(int itemId, const QUtiMimeConverter *c, const QString &m, QMimeData *d, int o = 0);
+ Promise(int itemId, const QUtiMimeConverter *c, const QString &m, QMimeData *md, int o, DataRequestType drt);
int itemId, offset;
- QMacInternalPasteboardMime *convertor;
+ const QUtiMimeConverter *converter;
QString mime;
- QPointer<QMacMimeData> mimeData;
+ QPointer<QMimeData> mimeData;
QVariant variantData;
DataRequestType dataRequestType;
+ // QMimeData can be set from QVariant, holding
+ // QPixmap. When converting, this triggers
+ // QPixmap's ctor which in turn requires QGuiApplication
+ // to exist and thus will abort the application
+ // abnormally if not.
+ bool isPixmap = false;
};
QList<Promise> promises;
PasteboardRef paste;
- uchar mime_type;
+ const QUtiMimeConverter::HandlerScope scope;
mutable QPointer<QMimeData> mime;
mutable bool mac_mime_source;
bool resolvingBeforeDestruction;
static OSStatus promiseKeeper(PasteboardRef, PasteboardItemID, CFStringRef, void *);
void clear_helper();
public:
- QMacPasteboard(PasteboardRef p, uchar mime_type=0);
- QMacPasteboard(uchar mime_type);
- QMacPasteboard(CFStringRef name=nullptr, uchar mime_type=0);
+ QMacPasteboard(PasteboardRef p, QUtiMimeConverter::HandlerScope scope = QUtiMimeConverter::HandlerScopeFlag::All);
+ QMacPasteboard(QUtiMimeConverter::HandlerScope scope);
+ QMacPasteboard(CFStringRef name=nullptr, QUtiMimeConverter::HandlerScope scope = QUtiMimeConverter::HandlerScopeFlag::All);
~QMacPasteboard();
- bool hasFlavor(QString flavor) const;
- bool hasOSType(int c_flavor) const;
+ bool hasUti(const QString &uti) const;
PasteboardRef pasteBoard() const;
QMimeData *mimeData() const;
@@ -56,7 +64,7 @@ public:
QStringList formats() const;
bool hasFormat(const QString &format) const;
- QVariant retrieveData(const QString &format, QMetaType) const;
+ QVariant retrieveData(const QString &format) const;
void clear();
bool sync() const;
diff --git a/src/plugins/platforms/cocoa/qmacclipboard.mm b/src/plugins/platforms/cocoa/qmacclipboard.mm
index 86dbc9c3d0..edafa3b6a1 100644
--- a/src/plugins/platforms/cocoa/qmacclipboard.mm
+++ b/src/plugins/platforms/cocoa/qmacclipboard.mm
@@ -4,11 +4,15 @@
#include <AppKit/AppKit.h>
#include "qmacclipboard.h"
+#include <QtGui/private/qmacmimeregistry_p.h>
+#include <QtGui/qutimimeconverter.h>
#include <QtGui/qclipboard.h>
#include <QtGui/qguiapplication.h>
#include <QtGui/qbitmap.h>
#include <QtCore/qdatetime.h>
+#include <QtCore/qmetatype.h>
#include <QtCore/qdebug.h>
+#include <QtCore/private/qcore_mac_p.h>
#include <QtGui/qguiapplication.h>
#include <QtGui/qevent.h>
#include <QtCore/qurl.h>
@@ -50,45 +54,47 @@ private:
QMacMimeData();
};
-QMacPasteboard::Promise::Promise(int itemId, QMacInternalPasteboardMime *c, QString m, QMacMimeData *md, int o, DataRequestType drt)
- : itemId(itemId), offset(o), convertor(c), mime(m), dataRequestType(drt)
+QMacPasteboard::Promise::Promise(int itemId, const QUtiMimeConverter *c, const QString &m, QMimeData *md, int o, DataRequestType drt)
+ : itemId(itemId), offset(o), converter(c), mime(m), dataRequestType(drt)
{
// Request the data from the application immediately for eager requests.
if (dataRequestType == QMacPasteboard::EagerRequest) {
- variantData = md->variantData(m);
+ variantData = static_cast<QMacMimeData *>(md)->variantData(m);
+ isPixmap = variantData.metaType().id() == QMetaType::QPixmap;
mimeData = nullptr;
} else {
mimeData = md;
+ if (md->hasImage())
+ isPixmap = md->imageData().metaType().id() == QMetaType::QPixmap;
}
}
-QMacPasteboard::QMacPasteboard(PasteboardRef p, uchar mt)
+QMacPasteboard::QMacPasteboard(PasteboardRef p, QUtiMimeConverter::HandlerScope scope)
+ : scope(scope)
{
mac_mime_source = false;
- mime_type = mt ? mt : uchar(QMacInternalPasteboardMime::MIME_ALL);
paste = p;
CFRetain(paste);
resolvingBeforeDestruction = false;
}
-QMacPasteboard::QMacPasteboard(uchar mt)
+QMacPasteboard::QMacPasteboard(QUtiMimeConverter::HandlerScope scope)
+ : scope(scope)
{
mac_mime_source = false;
- mime_type = mt ? mt : uchar(QMacInternalPasteboardMime::MIME_ALL);
paste = nullptr;
OSStatus err = PasteboardCreate(nullptr, &paste);
- if (err == noErr) {
+ if (err == noErr)
PasteboardSetPromiseKeeper(paste, promiseKeeper, this);
- } else {
+ else
qDebug("PasteBoard: Error creating pasteboard: [%d]", (int)err);
- }
resolvingBeforeDestruction = false;
}
-QMacPasteboard::QMacPasteboard(CFStringRef name, uchar mt)
+QMacPasteboard::QMacPasteboard(CFStringRef name, QUtiMimeConverter::HandlerScope scope)
+ : scope(scope)
{
mac_mime_source = false;
- mime_type = mt ? mt : uchar(QMacInternalPasteboardMime::MIME_ALL);
paste = nullptr;
OSStatus err = PasteboardCreate(name, &paste);
if (err == noErr) {
@@ -105,128 +111,91 @@ QMacPasteboard::~QMacPasteboard()
Commit all promises for paste when shutting down,
unless we are the stack-allocated clipboard used by QCocoaDrag.
*/
- if (mime_type == QMacInternalPasteboardMime::MIME_DND)
+ if (scope == QUtiMimeConverter::HandlerScopeFlag::DnD)
resolvingBeforeDestruction = true;
PasteboardResolvePromises(paste);
if (paste)
CFRelease(paste);
}
-PasteboardRef
-QMacPasteboard::pasteBoard() const
+PasteboardRef QMacPasteboard::pasteBoard() const
{
return paste;
}
-OSStatus QMacPasteboard::promiseKeeper(PasteboardRef paste, PasteboardItemID id, CFStringRef flavor, void *_qpaste)
+OSStatus QMacPasteboard::promiseKeeper(PasteboardRef paste, PasteboardItemID id,
+ CFStringRef uti, void *_qpaste)
{
QMacPasteboard *qpaste = (QMacPasteboard*)_qpaste;
const long promise_id = (long)id;
// Find the kept promise
- QList<QMacInternalPasteboardMime*> availableConverters
- = QMacInternalPasteboardMime::all(QMacInternalPasteboardMime::MIME_ALL);
- const QString flavorAsQString = QString::fromCFString(flavor);
+ const QList<QUtiMimeConverter*> availableConverters = QMacMimeRegistry::all(QUtiMimeConverter::HandlerScopeFlag::All);
+ const QString utiAsQString = QString::fromCFString(uti);
QMacPasteboard::Promise promise;
for (int i = 0; i < qpaste->promises.size(); i++){
- QMacPasteboard::Promise tmp = qpaste->promises[i];
- if (!availableConverters.contains(tmp.convertor)) {
+ const QMacPasteboard::Promise tmp = qpaste->promises[i];
+ if (!availableConverters.contains(tmp.converter)) {
// promise.converter is a pointer initialized by the value found
- // in QMacInternalPasteboardMime's global list of QMacInternalPasteboardMimes.
- // We add pointers to this list in QMacInternalPasteboardMime's ctor;
- // we remove these pointers in QMacInternalPasteboardMime's dtor.
+ // in QUtiMimeConverter's global list of QMacMimes.
+ // We add pointers to this list in QUtiMimeConverter's ctor;
+ // we remove these pointers in QUtiMimeConverter's dtor.
// If tmp.converter was not found in this list, we probably have a
// dangling pointer so let's skip it.
continue;
}
- if (tmp.itemId == promise_id && tmp.convertor->canConvert(tmp.mime, flavorAsQString)){
+ if (tmp.itemId == promise_id && tmp.converter->canConvert(tmp.mime, utiAsQString)) {
promise = tmp;
break;
}
}
- if (!promise.itemId && flavorAsQString == "com.trolltech.qt.MimeTypeName"_L1) {
+ if (!promise.itemId && utiAsQString == "com.trolltech.qt.MimeTypeName"_L1) {
// we have promised this data, but won't be able to convert, so return null data.
// This helps in making the application/x-qt-mime-type-name hidden from normal use.
QByteArray ba;
- QCFType<CFDataRef> data = CFDataCreate(nullptr, (UInt8*)ba.constData(), ba.size());
- PasteboardPutItemFlavor(paste, id, flavor, data, kPasteboardFlavorNoFlags);
+ const QCFType<CFDataRef> data = CFDataCreate(nullptr, (UInt8*)ba.constData(), ba.size());
+ PasteboardPutItemFlavor(paste, id, uti, data, kPasteboardFlavorNoFlags);
return noErr;
}
if (!promise.itemId) {
// There was no promise that could deliver data for the
- // given id and flavor. This should not happen.
- qDebug("Pasteboard: %d: Request for %ld, %s, but no promise found!", __LINE__, promise_id, qPrintable(flavorAsQString));
+ // given id and uti. This should not happen.
+ qDebug("Pasteboard: %d: Request for %ld, %s, but no promise found!", __LINE__, promise_id, qPrintable(utiAsQString));
return cantGetFlavorErr;
}
- qCDebug(lcQpaClipboard, "PasteBoard: Calling in promise for %s[%ld] [%s] (%s) [%d]", qPrintable(promise.mime), promise_id,
- qPrintable(flavorAsQString), qPrintable(promise.convertor->convertorName()), promise.offset);
+ qCDebug(lcQpaClipboard, "PasteBoard: Calling in promise for %s[%ld] [%s] [%d]", qPrintable(promise.mime), promise_id,
+ qPrintable(utiAsQString), promise.offset);
// Get the promise data. If this is a "lazy" promise call variantData()
// to request the data from the application.
QVariant promiseData;
if (promise.dataRequestType == LazyRequest) {
- if (!qpaste->resolvingBeforeDestruction && !promise.mimeData.isNull())
- promiseData = promise.mimeData->variantData(promise.mime);
+ if (!qpaste->resolvingBeforeDestruction && !promise.mimeData.isNull()) {
+ if (promise.isPixmap && !QGuiApplication::instance()) {
+ qCWarning(lcQpaClipboard,
+ "Cannot keep promise, data contains QPixmap and requires livining QGuiApplication");
+ return cantGetFlavorErr;
+ }
+ promiseData = static_cast<QMacMimeData *>(promise.mimeData.data())->variantData(promise.mime);
+ }
} else {
promiseData = promise.variantData;
}
- QList<QByteArray> md = promise.convertor->convertFromMime(promise.mime, promiseData, flavorAsQString);
+ const QList<QByteArray> md = promise.converter->convertFromMime(promise.mime, promiseData, utiAsQString);
if (md.size() <= promise.offset)
return cantGetFlavorErr;
const QByteArray &ba = md[promise.offset];
- QCFType<CFDataRef> data = CFDataCreate(nullptr, (UInt8*)ba.constData(), ba.size());
- PasteboardPutItemFlavor(paste, id, flavor, data, kPasteboardFlavorNoFlags);
+ const QCFType<CFDataRef> data = CFDataCreate(nullptr, (UInt8*)ba.constData(), ba.size());
+ PasteboardPutItemFlavor(paste, id, uti, data, kPasteboardFlavorNoFlags);
return noErr;
}
-bool
-QMacPasteboard::hasOSType(int c_flavor) const
-{
- if (!paste)
- return false;
-
- sync();
-
- ItemCount cnt = 0;
- if (PasteboardGetItemCountSafe(paste, &cnt) || !cnt)
- return false;
-
- qCDebug(lcQpaClipboard, "PasteBoard: hasOSType [%c%c%c%c]", (c_flavor>>24)&0xFF, (c_flavor>>16)&0xFF,
- (c_flavor>>8)&0xFF, (c_flavor>>0)&0xFF);
- for (uint index = 1; index <= cnt; ++index) {
-
- PasteboardItemID id;
- if (PasteboardGetItemIdentifier(paste, index, &id) != noErr)
- return false;
-
- QCFType<CFArrayRef> types;
- if (PasteboardCopyItemFlavors(paste, id, &types ) != noErr)
- return false;
-
- const int type_count = CFArrayGetCount(types);
- for (int i = 0; i < type_count; ++i) {
- CFStringRef flavor = (CFStringRef)CFArrayGetValueAtIndex(types, i);
- CFStringRef preferredTag = UTTypeCopyPreferredTagWithClass(flavor, kUTTagClassOSType);
- const int os_flavor = UTGetOSTypeFromString(preferredTag);
- if (preferredTag)
- CFRelease(preferredTag);
- if (os_flavor == c_flavor) {
- qCDebug(lcQpaClipboard, " - Found!");
- return true;
- }
- }
- }
- qCDebug(lcQpaClipboard, " - NotFound!");
- return false;
-}
-
-bool
-QMacPasteboard::hasFlavor(QString c_flavor) const
+bool QMacPasteboard::hasUti(const QString &uti) const
{
if (!paste)
return false;
@@ -237,7 +206,8 @@ QMacPasteboard::hasFlavor(QString c_flavor) const
if (PasteboardGetItemCountSafe(paste, &cnt) || !cnt)
return false;
- qCDebug(lcQpaClipboard, "PasteBoard: hasFlavor [%s]", qPrintable(c_flavor));
+ qCDebug(lcQpaClipboard, "PasteBoard: hasUti [%s]", qPrintable(uti));
+ const QCFString c_uti(uti);
for (uint index = 1; index <= cnt; ++index) {
PasteboardItemID id;
@@ -245,7 +215,7 @@ QMacPasteboard::hasFlavor(QString c_flavor) const
return false;
PasteboardFlavorFlags flags;
- if (PasteboardGetItemFlavorFlags(paste, id, QCFString(c_flavor), &flags) == noErr) {
+ if (PasteboardGetItemFlavorFlags(paste, id, c_uti, &flags) == noErr) {
qCDebug(lcQpaClipboard, " - Found!");
return true;
}
@@ -254,17 +224,20 @@ QMacPasteboard::hasFlavor(QString c_flavor) const
return false;
}
-class QMacPasteboardMimeSource : public QMimeData {
+class QMacPasteboardMimeSource : public QMimeData
+{
const QMacPasteboard *paste;
public:
QMacPasteboardMimeSource(const QMacPasteboard *p) : QMimeData(), paste(p) { }
~QMacPasteboardMimeSource() { }
- virtual QStringList formats() const { return paste->formats(); }
- virtual QVariant retrieveData(const QString &format, QMetaType type) const { return paste->retrieveData(format, type); }
+ QStringList formats() const override { return paste->formats(); }
+ QVariant retrieveData(const QString &format, QMetaType) const override
+ {
+ return paste->retrieveData(format);
+ }
};
-QMimeData
-*QMacPasteboard::mimeData() const
+QMimeData *QMacPasteboard::mimeData() const
{
if (!mime) {
mac_mime_source = true;
@@ -274,8 +247,7 @@ QMimeData
return mime;
}
-void
-QMacPasteboard::setMimeData(QMimeData *mime_src, DataRequestType dataRequestType)
+void QMacPasteboard::setMimeData(QMimeData *mime_src, DataRequestType dataRequestType)
{
if (!paste)
return;
@@ -286,7 +258,7 @@ QMacPasteboard::setMimeData(QMimeData *mime_src, DataRequestType dataRequestType
delete mime;
mime = mime_src;
- QList<QMacInternalPasteboardMime*> availableConverters = QMacInternalPasteboardMime::all(mime_type);
+ const QList<QUtiMimeConverter*> availableConverters = QMacMimeRegistry::all(scope);
if (mime != nullptr) {
clear_helper();
QStringList formats = mime_src->formats();
@@ -297,35 +269,31 @@ QMacPasteboard::setMimeData(QMimeData *mime_src, DataRequestType dataRequestType
QString dummyMimeType("application/x-qt-mime-type-name"_L1);
if (!formats.contains(dummyMimeType)) {
QByteArray dummyType = mime_src->data(dummyMimeType);
- if (!dummyType.isEmpty()) {
+ if (!dummyType.isEmpty())
formats.append(dummyMimeType);
- }
}
- for (int f = 0; f < formats.size(); ++f) {
- QString mimeType = formats.at(f);
- for (QList<QMacInternalPasteboardMime *>::Iterator it = availableConverters.begin(); it != availableConverters.end(); ++it) {
- QMacInternalPasteboardMime *c = (*it);
+ for (const auto &mimeType : formats) {
+ for (const auto *c : availableConverters) {
// Hack: The Rtf handler converts incoming Rtf to Html. We do
// not want to convert outgoing Html to Rtf but instead keep
// posting it as Html. Skip the Rtf handler here.
- if (c->convertorName() == "Rtf"_L1)
+ if (c->utiForMime("text/html"_L1) == "public.rtf"_L1)
continue;
- QString flavor(c->flavorFor(mimeType));
- if (!flavor.isEmpty()) {
- QMacMimeData *mimeData = static_cast<QMacMimeData*>(mime_src);
+ const QString uti(c->utiForMime(mimeType));
+ if (!uti.isEmpty()) {
- int numItems = c->count(mime_src);
+ const int numItems = c->count(mime_src);
for (int item = 0; item < numItems; ++item) {
- const NSInteger itemID = item+1; //id starts at 1
+ const NSInteger itemID = item + 1; //id starts at 1
//QMacPasteboard::Promise promise = (dataRequestType == QMacPasteboard::EagerRequest) ?
// QMacPasteboard::Promise::eagerPromise(itemID, c, mimeType, mimeData, item) :
// QMacPasteboard::Promise::lazyPromise(itemID, c, mimeType, mimeData, item);
- QMacPasteboard::Promise promise(itemID, c, mimeType, mimeData, item, dataRequestType);
+ const QMacPasteboard::Promise promise(itemID, c, mimeType, mime_src, item, dataRequestType);
promises.append(promise);
- PasteboardPutItemFlavor(paste, reinterpret_cast<PasteboardItemID>(itemID), QCFString(flavor), 0, kPasteboardFlavorNoFlags);
- qCDebug(lcQpaClipboard, " - adding %ld %s [%s] <%s> [%d]",
- itemID, qPrintable(mimeType), qPrintable(flavor), qPrintable(c->convertorName()), item);
+ PasteboardPutItemFlavor(paste, reinterpret_cast<PasteboardItemID>(itemID), QCFString(uti), 0, kPasteboardFlavorNoFlags);
+ qCDebug(lcQpaClipboard, " - adding %ld %s [%s] [%d]",
+ itemID, qPrintable(mimeType), qPrintable(uti), item);
}
}
}
@@ -333,8 +301,7 @@ QMacPasteboard::setMimeData(QMimeData *mime_src, DataRequestType dataRequestType
}
}
-QStringList
-QMacPasteboard::formats() const
+QStringList QMacPasteboard::formats() const
{
if (!paste)
return QStringList();
@@ -359,11 +326,11 @@ QMacPasteboard::formats() const
const int type_count = CFArrayGetCount(types);
for (int i = 0; i < type_count; ++i) {
- const QString flavor = QString::fromCFString((CFStringRef)CFArrayGetValueAtIndex(types, i));
- qCDebug(lcQpaClipboard, " -%s", qPrintable(QString(flavor)));
- QString mimeType = QMacInternalPasteboardMime::flavorToMime(mime_type, flavor);
+ const QString uti = QString::fromCFString((CFStringRef)CFArrayGetValueAtIndex(types, i));
+ qCDebug(lcQpaClipboard, " -%s", qPrintable(QString(uti)));
+ const QString mimeType = QMacMimeRegistry::flavorToMime(scope, uti);
if (!mimeType.isEmpty() && !ret.contains(mimeType)) {
- qCDebug(lcQpaClipboard, " -<%lld> %s [%s]", ret.size(), qPrintable(mimeType), qPrintable(QString(flavor)));
+ qCDebug(lcQpaClipboard, " -<%lld> %s [%s]", ret.size(), qPrintable(mimeType), qPrintable(QString(uti)));
ret << mimeType;
}
}
@@ -371,8 +338,7 @@ QMacPasteboard::formats() const
return ret;
}
-bool
-QMacPasteboard::hasFormat(const QString &format) const
+bool QMacPasteboard::hasFormat(const QString &format) const
{
if (!paste)
return false;
@@ -396,9 +362,9 @@ QMacPasteboard::hasFormat(const QString &format) const
const int type_count = CFArrayGetCount(types);
for (int i = 0; i < type_count; ++i) {
- const QString flavor = QString::fromCFString((CFStringRef)CFArrayGetValueAtIndex(types, i));
- qCDebug(lcQpaClipboard, " -%s [0x%x]", qPrintable(QString(flavor)), mime_type);
- QString mimeType = QMacInternalPasteboardMime::flavorToMime(mime_type, flavor);
+ const QString uti = QString::fromCFString((CFStringRef)CFArrayGetValueAtIndex(types, i));
+ qCDebug(lcQpaClipboard, " -%s [0x%x]", qPrintable(uti), uchar(scope));
+ QString mimeType = QMacMimeRegistry::flavorToMime(scope, uti);
if (!mimeType.isEmpty())
qCDebug(lcQpaClipboard, " - %s", qPrintable(mimeType));
if (mimeType == format)
@@ -408,8 +374,7 @@ QMacPasteboard::hasFormat(const QString &format) const
return false;
}
-QVariant
-QMacPasteboard::retrieveData(const QString &format, QMetaType) const
+QVariant QMacPasteboard::retrieveData(const QString &format) const
{
if (!paste)
return QVariant();
@@ -421,18 +386,17 @@ QMacPasteboard::retrieveData(const QString &format, QMetaType) const
return QByteArray();
qCDebug(lcQpaClipboard, "Pasteboard: retrieveData [%s]", qPrintable(format));
- const QList<QMacInternalPasteboardMime *> mimes = QMacInternalPasteboardMime::all(mime_type);
- for (int mime = 0; mime < mimes.size(); ++mime) {
- QMacInternalPasteboardMime *c = mimes.at(mime);
- QString c_flavor = c->flavorFor(format);
- if (!c_flavor.isEmpty()) {
+ const QList<QUtiMimeConverter *> availableConverters = QMacMimeRegistry::all(scope);
+ for (const auto *c : availableConverters) {
+ const QString c_uti = c->utiForMime(format);
+ if (!c_uti.isEmpty()) {
// Converting via PasteboardCopyItemFlavorData below will for some UITs result
// in newlines mapping to '\r' instead of '\n'. To work around this we shortcut
// the conversion via NSPasteboard's NSStringPboardType if possible.
- if (c_flavor == "com.apple.traditional-mac-plain-text"_L1
- || c_flavor == "public.utf8-plain-text"_L1
- || c_flavor == "public.utf16-plain-text"_L1) {
- QString str = qt_mac_get_pasteboardString(paste);
+ if (c_uti == "com.apple.traditional-mac-plain-text"_L1
+ || c_uti == "public.utf8-plain-text"_L1
+ || c_uti == "public.utf16-plain-text"_L1) {
+ const QString str = qt_mac_get_pasteboardString(paste);
if (!str.isEmpty())
return str;
}
@@ -450,26 +414,29 @@ QMacPasteboard::retrieveData(const QString &format, QMetaType) const
const int type_count = CFArrayGetCount(types);
for (int i = 0; i < type_count; ++i) {
- CFStringRef flavor = static_cast<CFStringRef>(CFArrayGetValueAtIndex(types, i));
- if (c_flavor == QString::fromCFString(flavor)) {
+ const CFStringRef uti = static_cast<CFStringRef>(CFArrayGetValueAtIndex(types, i));
+ if (c_uti == QString::fromCFString(uti)) {
QCFType<CFDataRef> macBuffer;
- if (PasteboardCopyItemFlavorData(paste, id, flavor, &macBuffer) == noErr) {
- QByteArray buffer((const char *)CFDataGetBytePtr(macBuffer), CFDataGetLength(macBuffer));
+ if (PasteboardCopyItemFlavorData(paste, id, uti, &macBuffer) == noErr) {
+ QByteArray buffer((const char *)CFDataGetBytePtr(macBuffer),
+ CFDataGetLength(macBuffer));
if (!buffer.isEmpty()) {
- qCDebug(lcQpaClipboard, " - %s [%s] (%s)", qPrintable(format), qPrintable(c_flavor), qPrintable(c->convertorName()));
+ qCDebug(lcQpaClipboard, " - %s [%s]", qPrintable(format),
+ qPrintable(c_uti));
buffer.detach(); //detach since we release the macBuffer
retList.append(buffer);
break; //skip to next element
}
}
} else {
- qCDebug(lcQpaClipboard, " - NoMatch %s [%s] (%s)", qPrintable(c_flavor), qPrintable(QString::fromCFString(flavor)), qPrintable(c->convertorName()));
+ qCDebug(lcQpaClipboard, " - NoMatch %s [%s]", qPrintable(c_uti),
+ qPrintable(QString::fromCFString(uti)));
}
}
}
if (!retList.isEmpty()) {
- ret = c->convertToMime(format, retList, c_flavor);
+ ret = c->convertToMime(format, retList, c_uti);
return ret;
}
}
@@ -484,15 +451,13 @@ void QMacPasteboard::clear_helper()
promises.clear();
}
-void
-QMacPasteboard::clear()
+void QMacPasteboard::clear()
{
qCDebug(lcQpaClipboard, "PasteBoard: clear!");
clear_helper();
}
-bool
-QMacPasteboard::sync() const
+bool QMacPasteboard::sync() const
{
if (!paste)
return false;
diff --git a/src/plugins/platforms/cocoa/qmultitouch_mac.mm b/src/plugins/platforms/cocoa/qmultitouch_mac.mm
index 73d103c5e3..f818b04acd 100644
--- a/src/plugins/platforms/cocoa/qmultitouch_mac.mm
+++ b/src/plugins/platforms/cocoa/qmultitouch_mac.mm
@@ -12,10 +12,10 @@ QT_BEGIN_NAMESPACE
using namespace Qt::StringLiterals;
-QHash<qint64, QCocoaTouch*> QCocoaTouch::_currentTouches;
-QHash<quint64, QPointingDevice*> QCocoaTouch::_touchDevices;
-QPointF QCocoaTouch::_screenReferencePos;
-QPointF QCocoaTouch::_trackpadReferencePos;
+Q_CONSTINIT QHash<qint64, QCocoaTouch*> QCocoaTouch::_currentTouches;
+Q_CONSTINIT QHash<quint64, QPointingDevice*> QCocoaTouch::_touchDevices;
+Q_CONSTINIT QPointF QCocoaTouch::_screenReferencePos;
+Q_CONSTINIT QPointF QCocoaTouch::_trackpadReferencePos;
int QCocoaTouch::_idAssignmentCount = 0;
int QCocoaTouch::_touchCount = 0;
bool QCocoaTouch::_updateInternalStateOnly = true;
diff --git a/src/plugins/platforms/cocoa/qnsview.h b/src/plugins/platforms/cocoa/qnsview.h
index 1858c2660d..7f845a5c3b 100644
--- a/src/plugins/platforms/cocoa/qnsview.h
+++ b/src/plugins/platforms/cocoa/qnsview.h
@@ -4,6 +4,8 @@
#ifndef QNSVIEW_H
#define QNSVIEW_H
+#include <AppKit/NSView.h>
+
#include <QtCore/private/qcore_mac_p.h>
QT_BEGIN_NAMESPACE
@@ -30,6 +32,12 @@ QT_DECLARE_NAMESPACED_OBJC_INTERFACE(QNSView, NSView
- (void)cancelComposingText;
@end
+Q_FORWARD_DECLARE_OBJC_CLASS(NSColorSpace);
+
+@interface QNSView (DrawingAPI)
+@property (nonatomic, readonly) NSColorSpace *colorSpace;
+@end
+
@interface QNSView (QtExtras)
@property (nonatomic, readonly) QCocoaWindow *platformWindow;
@end
diff --git a/src/plugins/platforms/cocoa/qnsview.mm b/src/plugins/platforms/cocoa/qnsview.mm
index dd0f1d6511..48ffa5c1cc 100644
--- a/src/plugins/platforms/cocoa/qnsview.mm
+++ b/src/plugins/platforms/cocoa/qnsview.mm
@@ -5,6 +5,7 @@
#include <AppKit/AppKit.h>
#include <MetalKit/MetalKit.h>
+#include <UniformTypeIdentifiers/UTCoreTypes.h>
#include "qnsview.h"
#include "qcocoawindow.h"
@@ -21,6 +22,7 @@
#include <QtCore/QPointer>
#include <QtCore/QSet>
#include <QtCore/qsysinfo.h>
+#include <QtCore/private/qcore_mac_p.h>
#include <QtGui/QAccessible>
#include <QtGui/QImage>
#include <private/qguiapplication_p.h>
@@ -33,13 +35,7 @@
#include "qcocoaglcontext.h"
#endif
#include "qcocoaintegration.h"
-
-// Private interface
-@interface QNSView ()
-- (BOOL)isTransparentForUserInput;
-@property (assign) NSView* previousSuperview;
-@property (assign) NSWindow* previousWindow;
-@end
+#include <QtGui/private/qmacmimeregistry_p.h>
@interface QNSView (Drawing) <CALayerDelegate>
- (void)initDrawing;
@@ -81,6 +77,21 @@ QT_NAMESPACE_ALIAS_OBJC_CLASS(QNSViewMouseMoveHelper);
@end
@interface QNSView (ComplexText) <NSTextInputClient>
+@property (readonly) QObject* focusObject;
+@end
+
+@interface QT_MANGLE_NAMESPACE(QNSViewMenuHelper) : NSObject
+- (instancetype)initWithView:(QNSView *)theView;
+@end
+QT_NAMESPACE_ALIAS_OBJC_CLASS(QNSViewMenuHelper);
+
+// Private interface
+@interface QNSView ()
+- (BOOL)isTransparentForUserInput;
+@property (assign) NSView* previousSuperview;
+@property (assign) NSWindow* previousWindow;
+@property (retain) QNSViewMenuHelper* menuHelper;
+@property (nonatomic, retain) NSColorSpace *colorSpace;
@end
@implementation QNSView {
@@ -100,19 +111,30 @@ QT_NAMESPACE_ALIAS_OBJC_CLASS(QNSViewMouseMoveHelper);
// Keys
bool m_lastKeyDead;
bool m_sendKeyEvent;
+ bool m_sendKeyEventWithoutText;
NSEvent *m_currentlyInterpretedKeyEvent;
QSet<quint32> m_acceptedKeyDowns;
// Text
QString m_composingText;
QPointer<QObject> m_composingFocusObject;
+ NSDraggingContext m_lastSeenContext;
}
+@synthesize colorSpace = m_colorSpace;
+
- (instancetype)initWithCocoaWindow:(QCocoaWindow *)platformWindow
{
if ((self = [super initWithFrame:NSZeroRect])) {
m_platformWindow = platformWindow;
+ // NSViews are by default visible, but QWindows are not.
+ // We should ideally pick up the actual QWindow state here,
+ // but QWindowPrivate::setVisible() expects to control the
+ // order of events tightly, so we need to wait for a call
+ // to QCocoaWindow::setVisible().
+ self.hidden = YES;
+
self.focusRingType = NSFocusRingTypeNone;
self.previousSuperview = nil;
@@ -127,6 +149,9 @@ QT_NAMESPACE_ALIAS_OBJC_CLASS(QNSViewMouseMoveHelper);
m_lastKeyDead = false;
m_sendKeyEvent = false;
m_currentlyInterpretedKeyEvent = nil;
+ m_lastSeenContext = NSDraggingContextWithinApplication;
+
+ self.menuHelper = [[[QNSViewMenuHelper alloc] initWithView:self] autorelease];
}
return self;
}
@@ -252,15 +277,29 @@ QT_NAMESPACE_ALIAS_OBJC_CLASS(QNSViewMouseMoveHelper);
return focusWindow;
}
+/*
+ Invoked when the view is hidden, either directly,
+ or in response to an ancestor being hidden.
+*/
- (void)viewDidHide
{
+ qCDebug(lcQpaWindow) << "Did hide" << self;
+
if (!m_platformWindow->isExposed())
return;
m_platformWindow->handleExposeEvent(QRegion());
+}
+
+/*
+ Invoked when the view is unhidden, either directly,
+ or in response to an ancestor being unhidden.
+*/
+- (void)viewDidUnhide
+{
+ qCDebug(lcQpaWindow) << "Did unhide" << self;
- // Note: setNeedsDisplay is automatically called for
- // viewDidUnhide so no reason to override it here.
+ [self setNeedsDisplay:YES];
}
- (BOOL)isTransparentForUserInput
@@ -293,7 +332,7 @@ QT_NAMESPACE_ALIAS_OBJC_CLASS(QNSViewMouseMoveHelper);
// QWindow activation from QCocoaWindow::windowDidBecomeKey instead. The only
// exception is if the window can never become key, in which case we naturally
// cannot wait for that to happen.
- QWindowSystemInterface::handleWindowActivated<QWindowSystemInterface::SynchronousDelivery>(
+ QWindowSystemInterface::handleFocusWindowChanged<QWindowSystemInterface::SynchronousDelivery>(
[self topLevelWindow], Qt::ActiveWindowFocusReason);
}
diff --git a/src/plugins/platforms/cocoa/qnsview_accessibility.mm b/src/plugins/platforms/cocoa/qnsview_accessibility.mm
index 3f3898fd18..e781f21a6c 100644
--- a/src/plugins/platforms/cocoa/qnsview_accessibility.mm
+++ b/src/plugins/platforms/cocoa/qnsview_accessibility.mm
@@ -13,6 +13,15 @@
@implementation QNSView (Accessibility)
+- (void)activateQtAccessibility
+{
+ // Activate the Qt accessibility machinery for all entry points
+ // below that may be triggered by system accessibility queries,
+ // as otherwise Qt is not aware that the system needs to know
+ // about all accessibility state changes in Qt.
+ QCocoaIntegration::instance()->accessibility()->setActive(true);
+}
+
- (id)childAccessibleElement
{
QCocoaWindow *platformWindow = self.platformWindow;
@@ -32,8 +41,7 @@
- (id)accessibilityAttributeValue:(NSString *)attribute
{
- // activate accessibility updates
- QCocoaIntegration::instance()->accessibility()->setActive(true);
+ [self activateQtAccessibility];
if ([attribute isEqualToString:NSAccessibilityChildrenAttribute])
return NSAccessibilityUnignoredChildrenForOnlyChild([self childAccessibleElement]);
@@ -43,11 +51,13 @@
- (id)accessibilityHitTest:(NSPoint)point
{
+ [self activateQtAccessibility];
return [[self childAccessibleElement] accessibilityHitTest:point];
}
- (id)accessibilityFocusedUIElement
{
+ [self activateQtAccessibility];
return [[self childAccessibleElement] accessibilityFocusedUIElement];
}
diff --git a/src/plugins/platforms/cocoa/qnsview_complextext.mm b/src/plugins/platforms/cocoa/qnsview_complextext.mm
index ad5f5a0827..d7f8f4baf0 100644
--- a/src/plugins/platforms/cocoa/qnsview_complextext.mm
+++ b/src/plugins/platforms/cocoa/qnsview_complextext.mm
@@ -7,6 +7,17 @@
// ------------- Text insertion -------------
+- (QObject*)focusObject
+{
+ // The text input system may still hold a reference to our QNSView,
+ // even after QCocoaWindow has been destructed, delivering text input
+ // events to us, so we need to guard for this situation explicitly.
+ if (!m_platformWindow)
+ return nullptr;
+
+ return m_platformWindow->window()->focusObject();
+}
+
/*
Inserts the given text, potentially replacing existing text.
@@ -52,8 +63,7 @@
}
}
- QObject *focusObject = m_platformWindow->window()->focusObject();
- if (queryInputMethod(focusObject)) {
+ if (queryInputMethod(self.focusObject)) {
QInputMethodEvent inputMethodEvent;
const bool isAttributedString = [text isKindOfClass:NSAttributedString.class];
@@ -75,7 +85,7 @@
inputMethodEvent.setCommitString(commitString, replaceFrom, replaceLength);
}
- QCoreApplication::sendEvent(focusObject, &inputMethodEvent);
+ QCoreApplication::sendEvent(self.focusObject, &inputMethodEvent);
}
m_composingText.clear();
@@ -86,6 +96,9 @@
{
Q_UNUSED(sender);
+ if (!m_platformWindow)
+ return;
+
// Depending on the input method, pressing enter may
// result in simply dismissing the input method editor,
// without confirming the composition. In other cases
@@ -117,8 +130,8 @@
newlineEvent.key = isEnter ? Qt::Key_Enter : Qt::Key_Return;
newlineEvent.text = isEnter ? QLatin1Char(kEnterCharCode)
: QLatin1Char(kReturnCharCode);
- newlineEvent.nativeVirtualKey = isEnter ? kVK_ANSI_KeypadEnter
- : kVK_Return;
+ newlineEvent.nativeVirtualKey = isEnter ? quint32(kVK_ANSI_KeypadEnter)
+ : quint32(kVK_Return);
qCDebug(lcQpaKeys) << "Inserting newline via" << newlineEvent;
newlineEvent.sendWindowSystemEvent(m_platformWindow->window());
@@ -242,7 +255,7 @@
// Update the composition, now that we've computed the replacement range
m_composingText = preeditString;
- if (QObject *focusObject = m_platformWindow->window()->focusObject()) {
+ if (QObject *focusObject = self.focusObject) {
m_composingFocusObject = focusObject;
if (queryInputMethod(focusObject)) {
QInputMethodEvent event(preeditString, preeditAttributes);
@@ -284,8 +297,7 @@
*/
- (NSRange)markedRange
{
- QObject *focusObject = m_platformWindow->window()->focusObject();
- if (auto queryResult = queryInputMethod(focusObject, Qt::ImAbsolutePosition)) {
+ if (auto queryResult = queryInputMethod(self.focusObject, Qt::ImAbsolutePosition)) {
int absoluteCursorPosition = queryResult.value(Qt::ImAbsolutePosition).toInt();
// The cursor position as reflected by Qt::ImAbsolutePosition is not
@@ -320,7 +332,7 @@
<< "for focus object" << m_composingFocusObject;
if (!m_composingText.isEmpty()) {
- QObject *focusObject = m_platformWindow->window()->focusObject();
+ QObject *focusObject = self.focusObject;
if (queryInputMethod(focusObject)) {
QInputMethodEvent e;
e.setCommitString(m_composingText);
@@ -367,8 +379,20 @@
// pass the originating key event up the responder chain if applicable.
qCDebug(lcQpaKeys) << "Trying to perform command" << selector;
- if (![self tryToPerform:selector with:self])
+ if (![self tryToPerform:selector with:self]) {
m_sendKeyEvent = true;
+
+ if (![NSStringFromSelector(selector) hasPrefix:@"insert"]) {
+ // The text input system determined that the key event was not
+ // meant for text insertion, and instead asked us to treat it
+ // as a (possibly noop) command. This typically happens for key
+ // events with either ⌘ or ⌃, function keys such as F1-F35,
+ // arrow keys, etc. We reflect that when sending the key event
+ // later on, by removing the text from the event, so that the
+ // event does not result in text insertion on the client side.
+ m_sendKeyEventWithoutText = true;
+ }
+ }
}
// ------------- Various text properties -------------
@@ -381,8 +405,7 @@
*/
- (NSRange)selectedRange
{
- QObject *focusObject = m_platformWindow->window()->focusObject();
- if (auto queryResult = queryInputMethod(focusObject,
+ if (auto queryResult = queryInputMethod(self.focusObject,
Qt::ImCursorPosition | Qt::ImAbsolutePosition | Qt::ImAnchorPosition)) {
// Unfortunately the Qt::InputMethodQuery values are all relative
@@ -429,8 +452,7 @@
*/
- (NSAttributedString *)attributedSubstringForProposedRange:(NSRange)range actualRange:(NSRangePointer)actualRange
{
- QObject *focusObject = m_platformWindow->window()->focusObject();
- if (auto queryResult = queryInputMethod(focusObject,
+ if (auto queryResult = queryInputMethod(self.focusObject,
Qt::ImAbsolutePosition | Qt::ImTextBeforeCursor | Qt::ImTextAfterCursor)) {
const int absoluteCursorPosition = queryResult.value(Qt::ImAbsolutePosition).toInt();
const QString textBeforeCursor = queryResult.value(Qt::ImTextBeforeCursor).toString();
@@ -466,8 +488,8 @@
Q_UNUSED(range);
Q_UNUSED(actualRange);
- QWindow *window = m_platformWindow->window();
- if (queryInputMethod(window->focusObject())) {
+ QWindow *window = m_platformWindow ? m_platformWindow->window() : nullptr;
+ if (window && queryInputMethod(window->focusObject())) {
QRect cursorRect = qApp->inputMethod()->cursorRectangle().toRect();
cursorRect.moveBottomLeft(window->mapToGlobal(cursorRect.bottomLeft()));
return QCocoaScreen::mapToNative(cursorRect);
@@ -483,6 +505,32 @@
return NSNotFound;
}
+/*
+ Returns the window level of the text input.
+
+ This allows the input method to place its input panel
+ above the text input.
+*/
+- (NSInteger)windowLevel
+{
+ // The default level assumed by input methods is NSFloatingWindowLevel,
+ // but our NSWindow level could be higher than that for many reasons,
+ // including being set via QWindow::setFlags() or directly on the
+ // NSWindow, or because we're embedded into a native view hierarchy.
+ // Return the actual window level to account for this.
+ auto level = m_platformWindow ? m_platformWindow->nativeWindow().level
+ : NSNormalWindowLevel;
+
+ // The logic above only covers our own window though. In some cases,
+ // such as when a completer is active, the text input has a lower
+ // window level than another window that's also visible, and we don't
+ // want the input panel to be sandwiched between these two windows.
+ // Account for this by explicitly using NSPopUpMenuWindowLevel as
+ // the minimum window level, which corresponds to the highest level
+ // one can get via QWindow::setFlags(), except for Qt::ToolTip.
+ return qMax(level, NSPopUpMenuWindowLevel);
+}
+
// ------------- Helper functions -------------
/*
diff --git a/src/plugins/platforms/cocoa/qnsview_dragging.mm b/src/plugins/platforms/cocoa/qnsview_dragging.mm
index c88e5d502f..4f7d35a0d6 100644
--- a/src/plugins/platforms/cocoa/qnsview_dragging.mm
+++ b/src/plugins/platforms/cocoa/qnsview_dragging.mm
@@ -16,12 +16,12 @@
NSPasteboardTypeRTF, NSPasteboardTypeTabularText, NSPasteboardTypeFont,
NSPasteboardTypeRuler, NSFileContentsPboardType,
NSPasteboardTypeRTFD , NSPasteboardTypeHTML,
- NSPasteboardTypeURL, NSPasteboardTypePDF, (NSString *)kUTTypeVCard,
- (NSString *)kPasteboardTypeFileURLPromise, (NSString *)kUTTypeInkText,
+ NSPasteboardTypeURL, NSPasteboardTypePDF, UTTypeVCard.identifier,
+ (NSString *)kPasteboardTypeFileURLPromise,
NSPasteboardTypeMultipleTextSelection, mimeTypeGeneric]];
// Add custom types supported by the application
- for (const QString &customType : qt_mac_enabledDraggedTypes())
+ for (const QString &customType : QMacMimeRegistry::enabledDraggedTypes())
[supportedTypes addObject:customType.toNSString()];
[self registerForDraggedTypes:supportedTypes];
@@ -45,8 +45,8 @@ static QPoint mapWindowCoordinates(QWindow *source, QWindow *target, QPoint poin
- (NSDragOperation)draggingSession:(NSDraggingSession *)session sourceOperationMaskForDraggingContext:(NSDraggingContext)context
{
Q_UNUSED(session);
- Q_UNUSED(context);
+ m_lastSeenContext = context;
QCocoaDrag* nativeDrag = QCocoaIntegration::instance()->drag();
return qt_mac_mapDropActions(nativeDrag->currentDrag()->supportedActions());
}
@@ -61,8 +61,11 @@ static QPoint mapWindowCoordinates(QWindow *source, QWindow *target, QPoint poin
//
// Since Qt already takes care of tracking the keyboard modifiers, we
// don't need (or want) Cocoa to filter anything. Instead, we'll let
- // the application do the actual filtering.
- return YES;
+ // the application do the actual filtering. But only while dragging
+ // within application, otherwise ignored modifiers may end up in a
+ // wrong drop operation executed.
+
+ return m_lastSeenContext == NSDraggingContextWithinApplication;
}
- (BOOL)wantsPeriodicDraggingUpdates
@@ -250,6 +253,8 @@ static QPoint mapWindowCoordinates(QWindow *source, QWindow *target, QPoint poin
Q_UNUSED(screenPoint);
Q_UNUSED(operation);
+ m_lastSeenContext = NSDraggingContextWithinApplication;
+
if (!m_platformWindow)
return;
diff --git a/src/plugins/platforms/cocoa/qnsview_drawing.mm b/src/plugins/platforms/cocoa/qnsview_drawing.mm
index 472a5291e7..bf102e43f8 100644
--- a/src/plugins/platforms/cocoa/qnsview_drawing.mm
+++ b/src/plugins/platforms/cocoa/qnsview_drawing.mm
@@ -13,6 +13,14 @@
<< " QT_MAC_WANTS_LAYER/_q_mac_wantsLayer has no effect.";
}
+ // Pick up and persist requested color space from surface format
+ const QSurfaceFormat surfaceFormat = m_platformWindow->format();
+ if (QColorSpace colorSpace = surfaceFormat.colorSpace(); colorSpace.isValid()) {
+ NSData *iccData = colorSpace.iccProfile().toNSData();
+ self.colorSpace = [[[NSColorSpace alloc] initWithICCProfileData:iccData] autorelease];
+ }
+
+ // Trigger creation of the layer
self.wantsLayer = YES;
}
@@ -28,6 +36,12 @@
return YES;
}
+- (NSColorSpace*)colorSpace
+{
+ // If no explicit color space was set, use the NSWindow's color space
+ return m_colorSpace ? m_colorSpace : self.window.colorSpace;
+}
+
// ----------------------- Layer setup -----------------------
- (BOOL)shouldUseMetalLayer
@@ -93,12 +107,7 @@
[super setLayer:layer];
- // When adding a view to a view hierarchy the backing properties will change
- // which results in updating the contents scale, but in case of switching the
- // layer on a view that's already in a view hierarchy we need to manually ensure
- // the scale is up to date.
- if (self.superview)
- [self updateLayerContentsScale];
+ [self propagateBackingProperties];
if (self.opaque && lcQpaDrawing().isDebugEnabled()) {
// If the view claims to be opaque we expect it to fill the entire
@@ -131,8 +140,7 @@
{
qCDebug(lcQpaDrawing) << "Backing properties changed for" << self;
- if (self.layer)
- [self updateLayerContentsScale];
+ [self propagateBackingProperties];
// Ideally we would plumb this situation through QPA in a way that lets
// clients invalidate their own caches, recreate QBackingStore, etc.
@@ -141,8 +149,11 @@
[self setNeedsDisplay:YES];
}
-- (void)updateLayerContentsScale
+- (void)propagateBackingProperties
{
+ if (!self.layer)
+ return;
+
// We expect clients to fill the layer with retina aware content,
// based on the devicePixelRatio of the QWindow, so we set the
// layer's content scale to match that. By going via devicePixelRatio
@@ -153,6 +164,12 @@
auto devicePixelRatio = m_platformWindow->devicePixelRatio();
qCDebug(lcQpaDrawing) << "Updating" << self.layer << "content scale to" << devicePixelRatio;
self.layer.contentsScale = devicePixelRatio;
+
+ if ([self.layer isKindOfClass:CAMetalLayer.class]) {
+ CAMetalLayer *metalLayer = static_cast<CAMetalLayer *>(self.layer);
+ metalLayer.colorspace = self.colorSpace.CGColorSpace;
+ qCDebug(lcQpaDrawing) << "Set" << metalLayer << "color space to" << metalLayer.colorspace;
+ }
}
/*
@@ -178,8 +195,10 @@
- (void)drawRect:(NSRect)dirtyBoundingRect
{
Q_UNUSED(dirtyBoundingRect);
- Q_ASSERT_X(!self.layer, "QNSView",
- "The drawRect code path should not be hit when we are layer backed");
+ // As we are layer backed we shouldn't really end up here, but AppKit will
+ // in some cases call this method just because we implement it.
+ // FIXME: Remove drawRect and switch from displayLayer to updateLayer
+ qCWarning(lcQpaDrawing) << "[QNSView drawRect] called for layer backed view";
}
/*
diff --git a/src/plugins/platforms/cocoa/qnsview_keys.mm b/src/plugins/platforms/cocoa/qnsview_keys.mm
index dc669d6f3b..abee622e65 100644
--- a/src/plugins/platforms/cocoa/qnsview_keys.mm
+++ b/src/plugins/platforms/cocoa/qnsview_keys.mm
@@ -3,8 +3,63 @@
// This file is included from qnsview.mm, and only used to organize the code
+/*
+ Determines if the text represents one of the "special keys" on macOS
+
+ As a legacy from OpenStep, macOS reserves the range 0xF700-0xF8FF of the
+ Unicode private use area for representing function keys on the keyboard:
+
+ http://www.unicode.org/Public/MAPPINGS/VENDORS/APPLE/CORPCHAR.TXT
+
+ https://developer.apple.com/documentation/appkit/nsevent/specialkey
+
+ These code points are not supposed to have any glyphs associated with them,
+ but since we can't guarantee that the system doesn't have a font that does
+ provide glyphs for this range (Arial Unicode MS e.g.) we need to filter
+ the text of our key events up front.
+*/
+static bool isSpecialKey(const QString &text)
+{
+ if (text.length() != 1)
+ return false;
+
+ const char16_t unicode = text.at(0).unicode();
+ if (unicode >= 0xF700 && unicode <= 0xF8FF)
+ return true;
+
+ return false;
+}
+
+static bool sendAsShortcut(const KeyEvent &keyEvent, QWindow *window)
+{
+ KeyEvent shortcutEvent = keyEvent;
+ shortcutEvent.type = QEvent::Shortcut;
+ qCDebug(lcQpaKeys) << "Trying potential shortcuts in" << window
+ << "for" << shortcutEvent;
+
+ if (shortcutEvent.sendWindowSystemEvent(window)) {
+ qCDebug(lcQpaKeys) << "Found matching shortcut; will not send as key event";
+ return true;
+ }
+ qCDebug(lcQpaKeys) << "No matching shortcuts; continuing with key event delivery";
+ return false;
+}
+
@implementation QNSView (Keys)
+- (bool)performKeyEquivalent:(NSEvent *)nsevent
+{
+ // Implemented to handle shortcuts for modified Tab keys, which are
+ // handled by Cocoa and not delivered to your keyDown implementation.
+ if (nsevent.type == NSEventTypeKeyDown && m_composingText.isEmpty()) {
+ const bool ctrlDown = [nsevent modifierFlags] & NSEventModifierFlagControl;
+ const bool isTabKey = nsevent.keyCode == kVK_Tab;
+ if (ctrlDown && isTabKey && sendAsShortcut(KeyEvent(nsevent), [self topLevelWindow]))
+ return YES;
+ }
+ return NO;
+}
+
- (bool)handleKeyEvent:(NSEvent *)nsevent
{
qCDebug(lcQpaKeys) << "Handling" << nsevent;
@@ -16,20 +71,17 @@
// We will send a key event unless the input method handles it
QBoolBlocker sendKeyEventGuard(m_sendKeyEvent, true);
+ // Assume we should send key events with text, unless told
+ // otherwise by doCommandBySelector.
+ m_sendKeyEventWithoutText = false;
+
+ bool didInterpretKeyEvent = false;
+
if (keyEvent.type == QEvent::KeyPress) {
if (m_composingText.isEmpty()) {
- KeyEvent shortcutEvent = keyEvent;
- shortcutEvent.type = QEvent::Shortcut;
- qCDebug(lcQpaKeys) << "Trying potential shortcuts in" << window
- << "for" << shortcutEvent;
-
- if (shortcutEvent.sendWindowSystemEvent(window)) {
- qCDebug(lcQpaKeys) << "Found matching shortcut; will not send as key event";
+ if (sendAsShortcut(keyEvent, window))
return true;
- } else {
- qCDebug(lcQpaKeys) << "No matching shortcuts; continuing with key event delivery";
- }
}
QObject *focusObject = m_platformWindow ? m_platformWindow->window()->focusObject() : nullptr;
@@ -61,8 +113,12 @@
qCDebug(lcQpaKeys) << "Interpreting key event for focus object" << focusObject;
m_currentlyInterpretedKeyEvent = nsevent;
- [self interpretKeyEvents:@[nsevent]];
+ if (![self.inputContext handleEvent:nsevent]) {
+ qCDebug(lcQpaKeys) << "Input context did not consume event";
+ m_sendKeyEvent = true;
+ }
m_currentlyInterpretedKeyEvent = 0;
+ didInterpretKeyEvent = true;
// If the last key we sent was dead, then pass the next
// key to the IM as well to complete composition.
@@ -75,7 +131,10 @@
bool accepted = true;
if (m_sendKeyEvent && m_composingText.isEmpty()) {
- KeyEvent keyEvent(nsevent);
+ // Trust text input system on whether to send the event with text or not,
+ // or otherwise apply heuristics to filter out private use symbols.
+ if (didInterpretKeyEvent ? m_sendKeyEventWithoutText : isSpecialKey(keyEvent.text))
+ keyEvent.text = {};
qCDebug(lcQpaKeys) << "Sending as" << keyEvent;
accepted = keyEvent.sendWindowSystemEvent(window);
}
@@ -207,9 +266,16 @@ KeyEvent::KeyEvent(NSEvent *nsevent)
default: break; // Must be manually set
}
- if (nsevent.type == NSEventTypeKeyDown || nsevent.type == NSEventTypeKeyUp) {
+ switch (nsevent.type) {
+ case NSEventTypeKeyDown:
+ case NSEventTypeKeyUp:
+ case NSEventTypeFlagsChanged:
nativeVirtualKey = nsevent.keyCode;
+ default:
+ break;
+ }
+ if (nsevent.type == NSEventTypeKeyDown || nsevent.type == NSEventTypeKeyUp) {
NSString *charactersIgnoringModifiers = nsevent.charactersIgnoringModifiers;
NSString *characters = nsevent.characters;
@@ -229,11 +295,7 @@ KeyEvent::KeyEvent(NSEvent *nsevent)
key = QAppleKeyMapper::fromCocoaKey(character);
}
- // Ignore text for the U+F700-U+F8FF range. This is used by Cocoa when
- // delivering function keys (e.g. arrow keys, backspace, F1-F35, etc.)
- if (!(modifiers & (Qt::ControlModifier | Qt::MetaModifier))
- && (character.unicode() < 0xf700 || character.unicode() > 0xf8ff))
- text = QString::fromNSString(characters);
+ text = QString::fromNSString(characters);
isRepeat = nsevent.ARepeat;
}
@@ -250,10 +312,9 @@ bool KeyEvent::sendWindowSystemEvent(QWindow *window) const
case QEvent::KeyPress:
case QEvent::KeyRelease: {
static const int count = 1;
- static const bool tryShortcutOverride = false;
QWindowSystemInterface::handleExtendedKeyEvent(window, timestamp,
type, key, modifiers, nativeScanCode, nativeVirtualKey, nativeModifiers,
- text, isRepeat, count, tryShortcutOverride);
+ text, isRepeat, count);
// FIXME: Make handleExtendedKeyEvent synchronous
return QWindowSystemInterface::flushWindowSystemEvents();
}
diff --git a/src/plugins/platforms/cocoa/qnsview_menus.mm b/src/plugins/platforms/cocoa/qnsview_menus.mm
index 7f2b3d387c..2840936975 100644
--- a/src/plugins/platforms/cocoa/qnsview_menus.mm
+++ b/src/plugins/platforms/cocoa/qnsview_menus.mm
@@ -9,22 +9,56 @@
#include "qcocoamenu.h"
#include "qcocoamenubar.h"
-static bool selectorIsCutCopyPaste(SEL selector)
+@implementation QNSView (Menus)
+
+// Qt does not (yet) have a mechanism for propagating generic actions,
+// so we can only support actions that originate from a QCocoaNSMenuItem,
+// where we can forward the action by emitting QPlatformMenuItem::activated().
+// But waiting for forwardInvocation to check that the sender is a
+// QCocoaNSMenuItem is too late, as AppKit has at that point chosen
+// our view as the target for the action, and if we can't handle it
+// the action will not propagate up the responder chain as it should.
+// Instead, we hook in early in the process of determining the target
+// via the supplementalTargetForAction API, and if we can support the
+// action we forward it to a helper. The helper must be tied to the
+// view, as the menu validation logic depends on the view's state.
+
+- (id)supplementalTargetForAction:(SEL)action sender:(id)sender
{
- return (selector == @selector(cut:)
- || selector == @selector(copy:)
- || selector == @selector(paste:)
- || selector == @selector(selectAll:));
+ qCDebug(lcQpaMenus) << "Resolving action target for" << action << "from" << sender << "via" << self;
+
+ if (qt_objc_cast<QCocoaNSMenuItem *>(sender)) {
+ // The supplemental target must support the selector, but we
+ // determine so dynamically, so check here before continuing.
+ if ([self.menuHelper respondsToSelector:action])
+ return self.menuHelper;
+ } else {
+ qCDebug(lcQpaMenus) << "Ignoring action for menu item we didn't create";
+ }
+
+ return [super supplementalTargetForAction:action sender:sender];
}
-@interface QNSView (Menus)
-- (void)qt_itemFired:(QCocoaNSMenuItem *)item;
@end
-@implementation QNSView (Menus)
+@interface QNSViewMenuHelper ()
+@property (assign) QNSView* view;
+@end
+
+@implementation QNSViewMenuHelper
+
+- (instancetype)initWithView:(QNSView *)theView
+{
+ if ((self = [super init]))
+ self.view = theView;
+
+ return self;
+}
- (BOOL)validateMenuItem:(NSMenuItem*)item
{
+ qCDebug(lcQpaMenus) << "Validating" << item << "for" << self.view;
+
auto *nativeItem = qt_objc_cast<QCocoaNSMenuItem *>(item);
if (!nativeItem)
return item.enabled; // FIXME Test with with Qt as plugin or embedded QWindow.
@@ -51,7 +85,7 @@ static bool selectorIsCutCopyPaste(SEL selector)
}
if ((!menuWindow || menuWindow->window() != QGuiApplication::modalWindow())
- && (!menubar || menubar->cocoaWindow() != self.platformWindow))
+ && (!menubar || menubar->cocoaWindow() != self.view.platformWindow))
return NO;
}
@@ -60,41 +94,42 @@ static bool selectorIsCutCopyPaste(SEL selector)
- (BOOL)respondsToSelector:(SEL)selector
{
- // Not exactly true. Both copy: and selectAll: can work on non key views.
- if (selectorIsCutCopyPaste(selector))
- return ([NSApp keyWindow] == self.window) && (self.window.firstResponder == self);
+ // See QCocoaMenuItem::resolveTargetAction()
+
+ if (selector == @selector(cut:)
+ || selector == @selector(copy:)
+ || selector == @selector(paste:)
+ || selector == @selector(selectAll:)) {
+ // Not exactly true. Both copy: and selectAll: can work on non key views.
+ return NSApp.keyWindow == self.view.window
+ && self.view.window.firstResponder == self.view;
+ }
- return [super respondsToSelector:selector];
-}
+ if (selector == @selector(qt_itemFired:))
+ return YES;
-- (void)qt_itemFired:(QCocoaNSMenuItem *)item
-{
- auto *appDelegate = [QCocoaApplicationDelegate sharedDelegate];
- [appDelegate qt_itemFired:item];
+ return [super respondsToSelector:selector];
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector
{
- if (selectorIsCutCopyPaste(selector)) {
- NSMethodSignature *itemFiredSign = [super methodSignatureForSelector:@selector(qt_itemFired:)];
- return itemFiredSign;
- }
+ // Double check, in case something has cached that we respond
+ // to the selector, but the result has changed since then.
+ if (![self respondsToSelector:selector])
+ return nil;
- return [super methodSignatureForSelector:selector];
+ auto *appDelegate = [QCocoaApplicationDelegate sharedDelegate];
+ return [appDelegate methodSignatureForSelector:@selector(qt_itemFired:)];
}
- (void)forwardInvocation:(NSInvocation *)invocation
{
- if (selectorIsCutCopyPaste(invocation.selector)) {
- NSObject *sender;
- [invocation getArgument:&sender atIndex:2];
- if (auto *nativeItem = qt_objc_cast<QCocoaNSMenuItem *>(sender)) {
- [self qt_itemFired:nativeItem];
- return;
- }
- }
-
- [super forwardInvocation:invocation];
+ NSObject *sender;
+ [invocation getArgument:&sender atIndex:2];
+ qCDebug(lcQpaMenus) << "Forwarding" << invocation.selector << "from" << sender;
+ Q_ASSERT(qt_objc_cast<QCocoaNSMenuItem *>(sender));
+ invocation.selector = @selector(qt_itemFired:);
+ [invocation invokeWithTarget:[QCocoaApplicationDelegate sharedDelegate]];
}
@end
diff --git a/src/plugins/platforms/cocoa/qnsview_mouse.mm b/src/plugins/platforms/cocoa/qnsview_mouse.mm
index 89125ca91f..2fd57fe68e 100644
--- a/src/plugins/platforms/cocoa/qnsview_mouse.mm
+++ b/src/plugins/platforms/cocoa/qnsview_mouse.mm
@@ -209,20 +209,19 @@ static const QPointingDevice *pointingDeviceFor(qint64 deviceID)
case NSEventTypeOtherMouseUp:
return QEvent::NonClientAreaMouseButtonRelease;
+ case NSEventTypeMouseMoved:
case NSEventTypeLeftMouseDragged:
case NSEventTypeRightMouseDragged:
case NSEventTypeOtherMouseDragged:
return QEvent::NonClientAreaMouseMove;
default:
- break;
+ Q_UNREACHABLE();
}
-
- return QEvent::None;
}();
qCInfo(lcQpaMouse) << eventType << "at" << qtWindowPoint << "with" << m_frameStrutButtons << "in" << self.window;
- QWindowSystemInterface::handleFrameStrutMouseEvent(m_platformWindow->window(),
+ QWindowSystemInterface::handleMouseEvent(m_platformWindow->window(),
timestamp, qtWindowPoint, qtScreenPoint, m_frameStrutButtons, button, eventType);
}
@end
@@ -484,10 +483,6 @@ static const QPointingDevice *pointingDeviceFor(qint64 deviceID)
- (void)cursorUpdate:(NSEvent *)theEvent
{
- // Note: We do not get this callback when moving from a subview that
- // uses the legacy cursorRect API, so the cursor is reset to the arrow
- // cursor. See rdar://34183708
-
if (!NSApp.active)
return;
@@ -548,6 +543,30 @@ static const QPointingDevice *pointingDeviceFor(qint64 deviceID)
[self handleMouseEvent: theEvent];
}
+- (BOOL)shouldPropagateMouseEnterExit
+{
+ Q_ASSERT(m_platformWindow);
+
+ // We send out enter and leave events mainly from mouse move events (mouseMovedImpl),
+ // but in some case (see mouseEnteredImpl:) we also want to propagate enter/leave
+ // events from the platform. We only do this for windows that themselves are not
+ // handled by another parent QWindow.
+
+ if (m_platformWindow->isContentView())
+ return true;
+
+ // Windows manually embedded into a native view does not have a QWindow parent
+ if (m_platformWindow->isEmbedded())
+ return true;
+
+ // Windows embedded via fromWinId do, but the parent isn't a QNSView
+ QPlatformWindow *parentWindow = m_platformWindow->QPlatformWindow::parent();
+ if (parentWindow && parentWindow->isForeignWindow())
+ return true;
+
+ return false;
+}
+
- (void)mouseEnteredImpl:(NSEvent *)theEvent
{
Q_UNUSED(theEvent);
@@ -571,8 +590,7 @@ static const QPointingDevice *pointingDeviceFor(qint64 deviceID)
// in time (s_windowUnderMouse). The latter is also used to also send out enter/leave
// events when the application is activated/deactivated.
- // Root (top level or embedded) windows generate enter events for sub-windows
- if (!m_platformWindow->isContentView() && !m_platformWindow->isEmbedded())
+ if (![self shouldPropagateMouseEnterExit])
return;
QPointF windowPoint;
@@ -598,8 +616,7 @@ static const QPointingDevice *pointingDeviceFor(qint64 deviceID)
if (!m_platformWindow)
return;
- // Root (top level or embedded) windows generate enter events for sub-windows
- if (!m_platformWindow->isContentView() && !m_platformWindow->isEmbedded())
+ if (![self shouldPropagateMouseEnterExit])
return;
QCocoaWindow *windowToLeave = QCocoaWindow::s_windowUnderMouse;
diff --git a/src/plugins/platforms/cocoa/qnsview_touch.mm b/src/plugins/platforms/cocoa/qnsview_touch.mm
index 6a147701fc..97ed5b7624 100644
--- a/src/plugins/platforms/cocoa/qnsview_touch.mm
+++ b/src/plugins/platforms/cocoa/qnsview_touch.mm
@@ -25,7 +25,10 @@ Q_LOGGING_CATEGORY(lcQpaTouch, "qt.qpa.input.touch")
const NSTimeInterval timestamp = [event timestamp];
const QList<QWindowSystemInterface::TouchPoint> points = QCocoaTouch::getCurrentTouchPointList(event, [self shouldSendSingleTouch]);
qCDebug(lcQpaTouch) << "touchesBeganWithEvent" << points << "from device" << Qt::hex << [event deviceID];
- QWindowSystemInterface::handleTouchEvent(m_platformWindow->window(), timestamp * 1000, QCocoaTouch::getTouchDevice(QInputDevice::DeviceType::TouchPad, [event deviceID]), points);
+ QWindowSystemInterface::handleTouchEvent<QWindowSystemInterface::SynchronousDelivery>(
+ m_platformWindow->window(), timestamp * 1000,
+ QCocoaTouch::getTouchDevice(QInputDevice::DeviceType::TouchPad, [event deviceID]),
+ points);
}
- (void)touchesMovedWithEvent:(NSEvent *)event
@@ -36,7 +39,10 @@ Q_LOGGING_CATEGORY(lcQpaTouch, "qt.qpa.input.touch")
const NSTimeInterval timestamp = [event timestamp];
const QList<QWindowSystemInterface::TouchPoint> points = QCocoaTouch::getCurrentTouchPointList(event, [self shouldSendSingleTouch]);
qCDebug(lcQpaTouch) << "touchesMovedWithEvent" << points << "from device" << Qt::hex << [event deviceID];
- QWindowSystemInterface::handleTouchEvent(m_platformWindow->window(), timestamp * 1000, QCocoaTouch::getTouchDevice(QInputDevice::DeviceType::TouchPad, [event deviceID]), points);
+ QWindowSystemInterface::handleTouchEvent<QWindowSystemInterface::SynchronousDelivery>(
+ m_platformWindow->window(), timestamp * 1000,
+ QCocoaTouch::getTouchDevice(QInputDevice::DeviceType::TouchPad, [event deviceID]),
+ points);
}
- (void)touchesEndedWithEvent:(NSEvent *)event
@@ -47,7 +53,10 @@ Q_LOGGING_CATEGORY(lcQpaTouch, "qt.qpa.input.touch")
const NSTimeInterval timestamp = [event timestamp];
const QList<QWindowSystemInterface::TouchPoint> points = QCocoaTouch::getCurrentTouchPointList(event, [self shouldSendSingleTouch]);
qCDebug(lcQpaTouch) << "touchesEndedWithEvent" << points << "from device" << Qt::hex << [event deviceID];
- QWindowSystemInterface::handleTouchEvent(m_platformWindow->window(), timestamp * 1000, QCocoaTouch::getTouchDevice(QInputDevice::DeviceType::TouchPad, [event deviceID]), points);
+ QWindowSystemInterface::handleTouchEvent<QWindowSystemInterface::SynchronousDelivery>(
+ m_platformWindow->window(), timestamp * 1000,
+ QCocoaTouch::getTouchDevice(QInputDevice::DeviceType::TouchPad, [event deviceID]),
+ points);
}
- (void)touchesCancelledWithEvent:(NSEvent *)event
@@ -58,7 +67,10 @@ Q_LOGGING_CATEGORY(lcQpaTouch, "qt.qpa.input.touch")
const NSTimeInterval timestamp = [event timestamp];
const QList<QWindowSystemInterface::TouchPoint> points = QCocoaTouch::getCurrentTouchPointList(event, [self shouldSendSingleTouch]);
qCDebug(lcQpaTouch) << "touchesCancelledWithEvent" << points << "from device" << Qt::hex << [event deviceID];
- QWindowSystemInterface::handleTouchEvent(m_platformWindow->window(), timestamp * 1000, QCocoaTouch::getTouchDevice(QInputDevice::DeviceType::TouchPad, [event deviceID]), points);
+ QWindowSystemInterface::handleTouchEvent<QWindowSystemInterface::SynchronousDelivery>(
+ m_platformWindow->window(), timestamp * 1000,
+ QCocoaTouch::getTouchDevice(QInputDevice::DeviceType::TouchPad, [event deviceID]),
+ points);
}
@end
diff --git a/src/plugins/platforms/cocoa/qnswindow.h b/src/plugins/platforms/cocoa/qnswindow.h
index ae43f28e8e..8f842eba85 100644
--- a/src/plugins/platforms/cocoa/qnswindow.h
+++ b/src/plugins/platforms/cocoa/qnswindow.h
@@ -8,6 +8,9 @@
#include <QPointer>
#include <QtCore/private/qcore_mac_p.h>
+#include <AppKit/NSWindow.h>
+#include <AppKit/NSPanel.h>
+
QT_FORWARD_DECLARE_CLASS(QCocoaWindow)
#if defined(__OBJC__)
@@ -33,6 +36,8 @@ QT_FORWARD_DECLARE_CLASS(QCocoaWindow)
typedef NSWindow<QNSWindowProtocol> QCocoaNSWindow;
+QCocoaNSWindow *qnswindow_cast(NSWindow *window);
+
#else
class QCocoaNSWindow;
#endif // __OBJC__
diff --git a/src/plugins/platforms/cocoa/qnswindow.mm b/src/plugins/platforms/cocoa/qnswindow.mm
index cce76e4b20..74ba6f65ac 100644
--- a/src/plugins/platforms/cocoa/qnswindow.mm
+++ b/src/plugins/platforms/cocoa/qnswindow.mm
@@ -58,6 +58,15 @@ static bool isMouseEvent(NSEvent *ev)
}
@end
+
+NSWindow<QNSWindowProtocol> *qnswindow_cast(NSWindow *window)
+{
+ if ([window conformsToProtocol:@protocol(QNSWindowProtocol)])
+ return static_cast<QCocoaNSWindow *>(window);
+ else
+ return nil;
+}
+
@implementation QNSWindow
#define QNSWINDOW_PROTOCOL_IMPLMENTATION 1
#include "qnswindow.mm"
@@ -98,9 +107,10 @@ static bool isMouseEvent(NSEvent *ev)
continue;
if ([window conformsToProtocol:@protocol(QNSWindowProtocol)]) {
- QCocoaWindow *cocoaWindow = static_cast<QCocoaNSWindow *>(window).platformWindow;
- window.level = notification.name == NSApplicationWillResignActiveNotification ?
- NSNormalWindowLevel : cocoaWindow->windowLevel(cocoaWindow->window()->flags());
+ if (QCocoaWindow *cocoaWindow = static_cast<QCocoaNSWindow *>(window).platformWindow) {
+ window.level = notification.name == NSApplicationWillResignActiveNotification ?
+ NSNormalWindowLevel : cocoaWindow->windowLevel(cocoaWindow->window()->flags());
+ }
}
// The documentation says that "when a window enters a new level, it’s ordered
@@ -167,45 +177,6 @@ static bool isMouseEvent(NSEvent *ev)
}
@end
-#if !defined(QT_APPLE_NO_PRIVATE_APIS)
-// When creating an NSWindow the worksWhenModal function is queried,
-// and the resulting state is used to set the corresponding window tag,
-// which the window server uses to determine whether or not the window
-// should be allowed to activate via mouse clicks in the title-bar.
-// Unfortunately, prior to macOS 10.15, this window tag was never
-// updated after the initial assignment in [NSWindow _commonAwake],
-// which meant that windows that dynamically change their worksWhenModal
-// state will behave as if they were never allowed to work when modal.
-// We work around this by manually updating the window tag when needed.
-
-typedef uint32_t CGSConnectionID;
-typedef uint32_t CGSWindowID;
-
-extern "C" {
-CGSConnectionID CGSMainConnectionID() __attribute__((weak_import));
-OSStatus CGSSetWindowTags(const CGSConnectionID, const CGSWindowID, int *, int) __attribute__((weak_import));
-OSStatus CGSClearWindowTags(const CGSConnectionID, const CGSWindowID, int *, int) __attribute__((weak_import));
-}
-
-@interface QNSPanel (WorksWhenModalWindowTagWorkaround) @end
-@implementation QNSPanel (WorksWhenModalWindowTagWorkaround)
-- (void)setWorksWhenModal:(BOOL)worksWhenModal
-{
- [super setWorksWhenModal:worksWhenModal];
-
- if (QOperatingSystemVersion::current() < QOperatingSystemVersion::MacOSCatalina) {
- if (CGSMainConnectionID && CGSSetWindowTags && CGSClearWindowTags) {
- static int kWorksWhenModalWindowTag = 0x40;
- auto *function = worksWhenModal ? CGSSetWindowTags : CGSClearWindowTags;
- function(CGSMainConnectionID(), self.windowNumber, &kWorksWhenModalWindowTag, 64);
- } else {
- qWarning() << "Missing APIs for window tag handling, can not update worksWhenModal state";
- }
- }
-}
-@end
-#endif // QT_APPLE_NO_PRIVATE_APIS
-
#else // QNSWINDOW_PROTOCOL_IMPLMENTATION
// The following content is mixed in to the QNSWindow and QNSPanel classes via includes
@@ -235,6 +206,29 @@ OSStatus CGSClearWindowTags(const CGSConnectionID, const CGSWindowID, int *, int
return m_platformWindow;
}
+- (void)setContentView:(NSView*)view
+{
+ [super setContentView:view];
+
+ if (!qnsview_cast(self.contentView))
+ return;
+
+ // Now that we're the content view, we can apply the properties of
+ // the QWindow. We do this here, instead of in init, so that we can
+ // use the same code paths for setting these properties during
+ // NSWindow initialization as we do when setting them later on.
+ const QWindow *window = m_platformWindow->window();
+ qCDebug(lcQpaWindow) << "Reflecting" << window << "state to" << self;
+
+ m_platformWindow->propagateSizeHints();
+ m_platformWindow->setWindowFlags(window->flags());
+ m_platformWindow->setWindowTitle(window->title());
+ m_platformWindow->setWindowFilePath(window->filePath()); // Also sets window icon
+ m_platformWindow->setWindowState(window->windowState());
+ m_platformWindow->setOpacity(window->opacity());
+ m_platformWindow->setVisible(window->isVisible());
+}
+
- (NSString *)description
{
NSMutableString *description = [NSMutableString stringWithString:[super description]];
@@ -296,8 +290,13 @@ OSStatus CGSClearWindowTags(const CGSConnectionID, const CGSWindowID, int *, int
// we assume that if you have translucent content, without a
// frame then you intend to do all background drawing yourself.
const QWindow *window = m_platformWindow ? m_platformWindow->window() : nullptr;
- if (!self.opaque && window && window->flags().testFlag(Qt::FramelessWindowHint))
- return [NSColor clearColor];
+ if (!self.opaque && window) {
+ // Qt::Popup also requires clearColor - in qmacstyle
+ // we fill background using a special path with rounded corners.
+ if (window->flags().testFlag(Qt::FramelessWindowHint)
+ || (window->flags() & Qt::WindowType_Mask) == Qt::Popup)
+ return [NSColor clearColor];
+ }
// This still allows you to have translucent content with a frame,
// where the system background (or color set via NSWindow) will
diff --git a/src/plugins/platforms/cocoa/qnswindowdelegate.mm b/src/plugins/platforms/cocoa/qnswindowdelegate.mm
index 2d90fbf544..1db7772771 100644
--- a/src/plugins/platforms/cocoa/qnswindowdelegate.mm
+++ b/src/plugins/platforms/cocoa/qnswindowdelegate.mm
@@ -23,9 +23,7 @@ static inline bool isWhiteSpace(const QString &s)
static QCocoaWindow *toPlatformWindow(NSWindow *window)
{
- if ([window conformsToProtocol:@protocol(QNSWindowProtocol)])
- return static_cast<QCocoaNSWindow *>(window).platformWindow;
- return nullptr;
+ return qnswindow_cast(window).platformWindow;
}
@implementation QNSWindowDelegate
diff --git a/src/plugins/platforms/direct2d/CMakeLists.txt b/src/plugins/platforms/direct2d/CMakeLists.txt
index 41fe0bde5b..271104b71e 100644
--- a/src/plugins/platforms/direct2d/CMakeLists.txt
+++ b/src/plugins/platforms/direct2d/CMakeLists.txt
@@ -1,7 +1,5 @@
# Copyright (C) 2022 The Qt Company Ltd.
-# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-
-# Generated from direct2d.pro.
+# SPDX-License-Identifier: BSD-3-Clause
#####################################################################
## QWindowsDirect2DIntegrationPlugin Plugin:
@@ -19,12 +17,13 @@ qt_internal_add_plugin(QWindowsDirect2DIntegrationPlugin
../windows/qwindowscursor.cpp ../windows/qwindowscursor.h
../windows/qwindowsdialoghelpers.cpp ../windows/qwindowsdialoghelpers.h
../windows/qwindowsdropdataobject.cpp ../windows/qwindowsdropdataobject.h
+ ../windows/qwindowsiconengine.cpp ../windows/qwindowsiconengine.h
../windows/qwindowsinputcontext.cpp ../windows/qwindowsinputcontext.h
../windows/qwindowsintegration.cpp ../windows/qwindowsintegration.h
../windows/qwindowsinternalmimedata.cpp ../windows/qwindowsinternalmimedata.h
../windows/qwindowskeymapper.cpp ../windows/qwindowskeymapper.h
../windows/qwindowsmenu.cpp ../windows/qwindowsmenu.h
- ../windows/qwindowsmime.cpp ../windows/qwindowsmime.h
+ ../windows/qwindowsmimeregistry.cpp ../windows/qwindowsmimeregistry.h
../windows/qwindowsmousehandler.cpp ../windows/qwindowsmousehandler.h
../windows/qwindowsnativeinterface.cpp ../windows/qwindowsnativeinterface.h
../windows/qwindowsole.cpp ../windows/qwindowsole.h
@@ -47,6 +46,8 @@ qt_internal_add_plugin(QWindowsDirect2DIntegrationPlugin
qwindowsdirect2dplatformpixmap.cpp qwindowsdirect2dplatformpixmap.h
qwindowsdirect2dplatformplugin.cpp
qwindowsdirect2dwindow.cpp qwindowsdirect2dwindow.h
+ NO_UNITY_BUILD_SOURCES
+ ../windows/qwindowspointerhandler.cpp
DEFINES
QT_NO_CAST_FROM_ASCII
QT_NO_FOREACH
@@ -58,16 +59,17 @@ qt_internal_add_plugin(QWindowsDirect2DIntegrationPlugin
Qt::Gui
Qt::GuiPrivate
advapi32
- d2d1 # special case
+ d2d1
d3d11
dwmapi
- dwrite # special case
+ dwrite
dxgi
dxguid
gdi32
imm32
ole32
oleaut32
+ setupapi
shell32
shlwapi
user32
@@ -96,15 +98,9 @@ qt_internal_add_resource(QWindowsDirect2DIntegrationPlugin "openglblacklists"
${openglblacklists_resource_files}
)
-#### Keys ignored in scope 1:.:.:direct2d.pro:<TRUE>:
-# OTHER_FILES = "direct2d.json"
-
## Scopes:
#####################################################################
-#### Keys ignored in scope 2:.:.:direct2d.pro:NOT TARGET___equals____ss_QT_DEFAULT_QPA_PLUGIN:
-# PLUGIN_EXTENDS = "-"
-
qt_internal_extend_target(QWindowsDirect2DIntegrationPlugin CONDITION QT_FEATURE_opengl AND NOT QT_FEATURE_dynamicgl
LIBRARIES
opengl32
@@ -113,6 +109,8 @@ qt_internal_extend_target(QWindowsDirect2DIntegrationPlugin CONDITION QT_FEATURE
qt_internal_extend_target(QWindowsDirect2DIntegrationPlugin CONDITION MINGW
LIBRARIES
uuid
+ NO_PCH_SOURCES
+ ../windows/qwindowspointerhandler.cpp
)
qt_internal_extend_target(QWindowsDirect2DIntegrationPlugin CONDITION QT_FEATURE_opengl
@@ -191,6 +189,7 @@ endif()
qt_internal_extend_target(QWindowsDirect2DIntegrationPlugin CONDITION QT_FEATURE_accessibility
SOURCES
+ ../windows/uiautomation/qwindowsuiautomation.cpp ../windows/uiautomation/qwindowsuiautomation.h
../windows/uiautomation/qwindowsuiaaccessibility.cpp ../windows/uiautomation/qwindowsuiaaccessibility.h
../windows/uiautomation/qwindowsuiabaseprovider.cpp ../windows/uiautomation/qwindowsuiabaseprovider.h
../windows/uiautomation/qwindowsuiaexpandcollapseprovider.cpp ../windows/uiautomation/qwindowsuiaexpandcollapseprovider.h
@@ -212,13 +211,18 @@ qt_internal_extend_target(QWindowsDirect2DIntegrationPlugin CONDITION QT_FEATURE
../windows/uiautomation/qwindowsuiawindowprovider.cpp ../windows/uiautomation/qwindowsuiawindowprovider.h
)
+if(QT_FEATURE_accessibility)
+ find_library(UI_AUTOMATION_LIBRARY uiautomationcore)
+ if(UI_AUTOMATION_LIBRARY)
+ qt_internal_extend_target(QWindowsDirect2DIntegrationPlugin
+ LIBRARIES
+ ${UI_AUTOMATION_LIBRARY}
+ )
+ endif()
+endif()
+
qt_internal_extend_target(QWindowsDirect2DIntegrationPlugin CONDITION MINGW AND QT_FEATURE_accessibility
LIBRARIES
uuid
)
-# begin special case
-if (MINGW)
- set_source_files_properties(../windows/qwindowspointerhandler.cpp PROPERTIES SKIP_PRECOMPILE_HEADERS ON)
-endif()
-# end special case
diff --git a/src/plugins/platforms/direct2d/qwindowsdirect2dpaintengine.cpp b/src/plugins/platforms/direct2d/qwindowsdirect2dpaintengine.cpp
index bed06dc62d..bc304f78be 100644
--- a/src/plugins/platforms/direct2d/qwindowsdirect2dpaintengine.cpp
+++ b/src/plugins/platforms/direct2d/qwindowsdirect2dpaintengine.cpp
@@ -1651,7 +1651,7 @@ void QWindowsDirect2DPaintEngine::rasterFill(const QVectorPath &path, const QBru
p.setPen(state()->pen);
auto *extended = static_cast<QPaintEngineEx *>(engine);
- for (const QPainterClipInfo &info : qAsConst(state()->clipInfo)) {
+ for (const QPainterClipInfo &info : std::as_const(state()->clipInfo)) {
extended->state()->matrix = info.matrix;
extended->transformChanged();
diff --git a/src/plugins/platforms/directfb/CMakeLists.txt b/src/plugins/platforms/directfb/CMakeLists.txt
index 5d123696bf..7222168a37 100644
--- a/src/plugins/platforms/directfb/CMakeLists.txt
+++ b/src/plugins/platforms/directfb/CMakeLists.txt
@@ -1,12 +1,8 @@
# Copyright (C) 2022 The Qt Company Ltd.
-# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+# SPDX-License-Identifier: BSD-3-Clause
-# Generated from directfb.pro.
-
-# begin special case:
qt_find_package(DirectFB)
qt_find_package(EGL)
-# end special case:
#####################################################################
## QDirectFbIntegrationPlugin Plugin:
@@ -28,16 +24,13 @@ qt_internal_add_plugin(QDirectFbIntegrationPlugin
qdirectfbwindow.cpp qdirectfbwindow.h
LIBRARIES
PkgConfig::DirectFB
- EGL::EGL # special case
+ EGL::EGL
Qt::Core
Qt::CorePrivate
Qt::Gui
Qt::GuiPrivate
)
-#### Keys ignored in scope 1:.:.:directfb.pro:<TRUE>:
-# OTHER_FILES = "directfb.json"
-
## Scopes:
#####################################################################
@@ -53,13 +46,7 @@ qt_internal_extend_target(QDirectFbIntegrationPlugin CONDITION NOT DIRECTFB_PLAT
DIRECTFB_PLATFORM_HOOKS
)
-#### Keys ignored in scope 3:.:.:directfb.pro:NOT DIRECTFB_PLATFORM_HOOKS_SOURCES_ISEMPTY:
-# QMAKE_LIBDIR = "$$DIRECTFB_PLATFORM_HOOKS_LIBDIR"
-
qt_internal_extend_target(QDirectFbIntegrationPlugin CONDITION DIRECTFB_PLATFORM_HOOKS_SOURCES_ISEMPTY
SOURCES
qdirectfbeglhooks_stub.cpp
)
-
-#### Keys ignored in scope 5:.:.:directfb.pro:NOT TARGET___equals____ss_QT_DEFAULT_QPA_PLUGIN:
-# PLUGIN_EXTENDS = "-"
diff --git a/src/plugins/platforms/directfb/main.cpp b/src/plugins/platforms/directfb/main.cpp
index 02cceeb487..4618696154 100644
--- a/src/plugins/platforms/directfb/main.cpp
+++ b/src/plugins/platforms/directfb/main.cpp
@@ -24,7 +24,7 @@ class QDirectFbIntegrationPlugin : public QPlatformIntegrationPlugin
Q_OBJECT
Q_PLUGIN_METADATA(IID QPlatformIntegrationFactoryInterface_iid FILE "directfb.json")
public:
- QPlatformIntegration *create(const QString&, const QStringList&);
+ QPlatformIntegration *create(const QString&, const QStringList&) override;
};
QPlatformIntegration * QDirectFbIntegrationPlugin::create(const QString& system, const QStringList& paramList)
diff --git a/src/plugins/platforms/directfb/qdirectfbbackingstore.h b/src/plugins/platforms/directfb/qdirectfbbackingstore.h
index c51f29271d..625b672a40 100644
--- a/src/plugins/platforms/directfb/qdirectfbbackingstore.h
+++ b/src/plugins/platforms/directfb/qdirectfbbackingstore.h
@@ -18,10 +18,10 @@ class QDirectFbBackingStore : public QPlatformBackingStore
public:
QDirectFbBackingStore(QWindow *window);
- QPaintDevice *paintDevice();
- void flush(QWindow *window, const QRegion &region, const QPoint &offset);
- void resize (const QSize &size, const QRegion &staticContents);
- bool scroll(const QRegion &area, int dx, int dy);
+ QPaintDevice *paintDevice() override;
+ void flush(QWindow *window, const QRegion &region, const QPoint &offset) override;
+ void resize (const QSize &size, const QRegion &staticContents) override;
+ bool scroll(const QRegion &area, int dx, int dy) override;
QImage toImage() const override;
diff --git a/src/plugins/platforms/directfb/qdirectfbblitter.cpp b/src/plugins/platforms/directfb/qdirectfbblitter.cpp
index 0a110da798..c93e48ead7 100644
--- a/src/plugins/platforms/directfb/qdirectfbblitter.cpp
+++ b/src/plugins/platforms/directfb/qdirectfbblitter.cpp
@@ -216,7 +216,7 @@ bool QDirectFbBlitter::drawCachedGlyphs(const QPaintEngineState *state, QFontEng
for (int i=0; i<numGlyphs; ++i) {
QFixed subPixelPosition = fontEngine->subPixelPositionForX(positions[i].x);
- QTextureGlyphCache::GlyphAndSubPixelPosition glyph(glyphs[i], subPixelPosition);
+ QTextureGlyphCache::GlyphAndSubPixelPosition glyph(glyphs[i], QFixedPoint(subPixelPosition, 0));
const QTextureGlyphCache::Coord &c = cache->coords[glyph];
if (c.isNull())
continue;
@@ -424,7 +424,7 @@ void QDirectFbBlitter::drawDebugRect(const QRect &rect, const QColor &color)
void QDirectFbTextureGlyphCache::resizeTextureData(int width, int height)
{
- m_surface.reset();;
+ m_surface.reset();
QImageTextureGlyphCache::resizeTextureData(width, height);
}
diff --git a/src/plugins/platforms/directfb/qdirectfbblitter.h b/src/plugins/platforms/directfb/qdirectfbblitter.h
index 591021061c..6183859613 100644
--- a/src/plugins/platforms/directfb/qdirectfbblitter.h
+++ b/src/plugins/platforms/directfb/qdirectfbblitter.h
@@ -19,11 +19,20 @@ public:
QDirectFbBlitter(const QSize &size, bool alpha);
virtual ~QDirectFbBlitter();
- virtual void fillRect(const QRectF &rect, const QColor &color);
- virtual void drawPixmap(const QRectF &rect, const QPixmap &pixmap, const QRectF &subrect);
- void alphaFillRect(const QRectF &rect, const QColor &color, QPainter::CompositionMode cmode);
- void drawPixmapOpacity(const QRectF &rect, const QPixmap &pixmap, const QRectF &subrect, QPainter::CompositionMode cmode, qreal opacity);
- virtual bool drawCachedGlyphs(const QPaintEngineState *state, QFontEngine::GlyphFormat glyphFormat, int numGlyphs, const glyph_t *glyphs, const QFixedPoint *positions, QFontEngine *fontEngine);
+ void fillRect(const QRectF &rect, const QColor &color) override;
+ void drawPixmap(const QRectF &rect, const QPixmap &pixmap, const QRectF &subrect) override;
+ void alphaFillRect(const QRectF &rect, const QColor &color, QPainter::CompositionMode cmode) override;
+ void drawPixmapOpacity(const QRectF &rect,
+ const QPixmap &pixmap,
+ const QRectF &subrect,
+ QPainter::CompositionMode cmode,
+ qreal opacity) override;
+ bool drawCachedGlyphs(const QPaintEngineState *state,
+ QFontEngine::GlyphFormat glyphFormat,
+ int numGlyphs,
+ const glyph_t *glyphs,
+ const QFixedPoint *positions,
+ QFontEngine *fontEngine) override;
IDirectFBSurface *dfbSurface() const;
@@ -32,8 +41,8 @@ public:
static DFBSurfacePixelFormat selectPixmapFormat(bool withAlpha);
protected:
- virtual QImage *doLock();
- virtual void doUnlock();
+ QImage *doLock() override;
+ void doUnlock() override;
QDirectFBPointer<IDirectFBSurface> m_surface;
QImage m_image;
@@ -50,12 +59,12 @@ private:
class QDirectFbBlitterPlatformPixmap : public QBlittablePlatformPixmap
{
public:
- QBlittable *createBlittable(const QSize &size, bool alpha) const;
+ QBlittable *createBlittable(const QSize &size, bool alpha) const override;
QDirectFbBlitter *dfbBlitter() const;
- virtual bool fromFile(const QString &filename, const char *format,
- Qt::ImageConversionFlags flags);
+ bool fromFile(const QString &filename, const char *format,
+ Qt::ImageConversionFlags flags) override;
private:
bool fromDataBufferDescription(const DFBDataBufferDescription &);
@@ -83,7 +92,7 @@ public:
: QImageTextureGlyphCache(format, matrix)
{}
- virtual void resizeTextureData(int width, int height);
+ void resizeTextureData(int width, int height) override;
IDirectFBSurface *sourceSurface();
diff --git a/src/plugins/platforms/directfb/qdirectfbconvenience.h b/src/plugins/platforms/directfb/qdirectfbconvenience.h
index c013988fe2..dc657f384e 100644
--- a/src/plugins/platforms/directfb/qdirectfbconvenience.h
+++ b/src/plugins/platforms/directfb/qdirectfbconvenience.h
@@ -62,7 +62,7 @@ template <typename T>
class QDirectFBPointer : public QScopedPointer<T, QDirectFBInterfaceCleanupHandler<T> >
{
public:
- QDirectFBPointer(T *t = nullptr)
+ Q_NODISCARD_CTOR QDirectFBPointer(T *t = nullptr)
: QScopedPointer<T, QDirectFBInterfaceCleanupHandler<T> >(t)
{}
diff --git a/src/plugins/platforms/directfb/qdirectfbcursor.h b/src/plugins/platforms/directfb/qdirectfbcursor.h
index 9a480fc8f8..21a8f4353d 100644
--- a/src/plugins/platforms/directfb/qdirectfbcursor.h
+++ b/src/plugins/platforms/directfb/qdirectfbcursor.h
@@ -19,7 +19,7 @@ class QDirectFBCursor : public QPlatformCursor
public:
QDirectFBCursor(QPlatformScreen *screen);
#ifndef QT_NO_CURSOR
- void changeCursor(QCursor *cursor, QWindow *window);
+ void changeCursor(QCursor *cursor, QWindow *window) override;
#endif
private:
diff --git a/src/plugins/platforms/directfb/qdirectfbinput.cpp b/src/plugins/platforms/directfb/qdirectfbinput.cpp
index a62ff9882c..c5aacad6cc 100644
--- a/src/plugins/platforms/directfb/qdirectfbinput.cpp
+++ b/src/plugins/platforms/directfb/qdirectfbinput.cpp
@@ -126,7 +126,7 @@ void QDirectFbInput::handleMouseEvents(const DFBEvent &event)
long timestamp = (event.window.timestamp.tv_sec*1000) + (event.window.timestamp.tv_usec/1000);
QWindow *tlw = m_tlwMap.value(event.window.window_id);
- QWindowSystemInterface::handleMouseEvent(tlw, timestamp, p, globalPos, buttons);
+ QWindowSystemInterface::handleMouseEvent(tlw, timestamp, p, globalPos, buttons, Qt::NoButton, QEvent::None);
}
void QDirectFbInput::handleWheelEvent(const DFBEvent &event)
@@ -135,9 +135,12 @@ void QDirectFbInput::handleWheelEvent(const DFBEvent &event)
QPoint globalPos(event.window.cx, event.window.cy);
long timestamp = (event.window.timestamp.tv_sec*1000) + (event.window.timestamp.tv_usec/1000);
QWindow *tlw = m_tlwMap.value(event.window.window_id);
- QWindowSystemInterface::handleWheelEvent(tlw, timestamp, p, globalPos,
- event.window.step*120,
- Qt::Vertical);
+ QWindowSystemInterface::handleWheelEvent(tlw,
+ timestamp,
+ p,
+ globalPos,
+ QPoint(),
+ QPoint(0, event.window.step*120));
}
void QDirectFbInput::handleKeyEvents(const DFBEvent &event)
@@ -173,7 +176,7 @@ void QDirectFbInput::handleEnterLeaveEvents(const DFBEvent &event)
void QDirectFbInput::handleGotFocusEvent(const DFBEvent &event)
{
QWindow *tlw = m_tlwMap.value(event.window.window_id);
- QWindowSystemInterface::handleWindowActivated(tlw, Qt::ActiveWindowFocusReason);
+ QWindowSystemInterface::handleFocusWindowChanged(tlw, Qt::ActiveWindowFocusReason);
}
void QDirectFbInput::handleCloseEvent(const DFBEvent &event)
diff --git a/src/plugins/platforms/directfb/qdirectfbinput.h b/src/plugins/platforms/directfb/qdirectfbinput.h
index 5496f537a2..02175abc7b 100644
--- a/src/plugins/platforms/directfb/qdirectfbinput.h
+++ b/src/plugins/platforms/directfb/qdirectfbinput.h
@@ -26,7 +26,7 @@ public:
void stopInputEventLoop();
protected:
- void run();
+ void run() override;
private:
void handleEvents();
diff --git a/src/plugins/platforms/directfb/qdirectfbintegration.h b/src/plugins/platforms/directfb/qdirectfbintegration.h
index aa369d7c05..8dd2a4516a 100644
--- a/src/plugins/platforms/directfb/qdirectfbintegration.h
+++ b/src/plugins/platforms/directfb/qdirectfbintegration.h
@@ -25,16 +25,16 @@ public:
void connectToDirectFb();
- bool hasCapability(Capability cap) const;
- QPlatformPixmap *createPlatformPixmap(QPlatformPixmap::PixelType type) const;
- QPlatformWindow *createPlatformWindow(QWindow *window) const;
- QPlatformBackingStore *createPlatformBackingStore(QWindow *window) const;
- QAbstractEventDispatcher *createEventDispatcher() const;
-
- QPlatformFontDatabase *fontDatabase() const;
- QPlatformServices *services() const;
- QPlatformInputContext *inputContext() const { return m_inputContext; }
- QPlatformNativeInterface *nativeInterface() const;
+ bool hasCapability(Capability cap) const override;
+ QPlatformPixmap *createPlatformPixmap(QPlatformPixmap::PixelType type) const override;
+ QPlatformWindow *createPlatformWindow(QWindow *window) const override;
+ QPlatformBackingStore *createPlatformBackingStore(QWindow *window) const override;
+ QAbstractEventDispatcher *createEventDispatcher() const override;
+
+ QPlatformFontDatabase *fontDatabase() const override;
+ QPlatformServices *services() const override;
+ QPlatformInputContext *inputContext() const override { return m_inputContext; }
+ QPlatformNativeInterface *nativeInterface() const override;
protected:
virtual void initializeDirectFB();
diff --git a/src/plugins/platforms/directfb/qdirectfbscreen.h b/src/plugins/platforms/directfb/qdirectfbscreen.h
index a4f2e9adb1..cbcb6a55da 100644
--- a/src/plugins/platforms/directfb/qdirectfbscreen.h
+++ b/src/plugins/platforms/directfb/qdirectfbscreen.h
@@ -19,11 +19,11 @@ class QDirectFbScreen : public QPlatformScreen
public:
QDirectFbScreen(int display);
- QRect geometry() const { return m_geometry; }
- int depth() const { return m_depth; }
- QImage::Format format() const { return m_format; }
- QSizeF physicalSize() const { return m_physicalSize; }
- QPlatformCursor *cursor() const { return m_cursor.data(); }
+ QRect geometry() const override { return m_geometry; }
+ int depth() const override { return m_depth; }
+ QImage::Format format() const override { return m_format; }
+ QSizeF physicalSize() const override { return m_physicalSize; }
+ QPlatformCursor *cursor() const override { return m_cursor.data(); }
// DirectFb helpers
IDirectFBDisplayLayer *dfbLayer() const;
diff --git a/src/plugins/platforms/directfb/qdirectfbwindow.h b/src/plugins/platforms/directfb/qdirectfbwindow.h
index 6413d91860..f05038d8ca 100644
--- a/src/plugins/platforms/directfb/qdirectfbwindow.h
+++ b/src/plugins/platforms/directfb/qdirectfbwindow.h
@@ -15,19 +15,19 @@ class QDirectFbWindow : public QPlatformWindow
{
public:
QDirectFbWindow(QWindow *tlw, QDirectFbInput *inputhandler);
- ~QDirectFbWindow();
+ ~QDirectFbWindow() override;
- void setGeometry(const QRect &rect);
- void setOpacity(qreal level);
+ void setGeometry(const QRect &rect) override;
+ void setOpacity(qreal level) override;
- void setVisible(bool visible);
+ void setVisible(bool visible) override;
- void setWindowFlags(Qt::WindowFlags flags);
- bool setKeyboardGrabEnabled(bool grab);
- bool setMouseGrabEnabled(bool grab);
- void raise();
- void lower();
- WId winId() const;
+ void setWindowFlags(Qt::WindowFlags flags) override;
+ bool setKeyboardGrabEnabled(bool grab) override;
+ bool setMouseGrabEnabled(bool grab) override;
+ void raise() override;
+ void lower() override;
+ WId winId() const override;
virtual void createDirectFBWindow();
IDirectFBWindow *dfbWindow() const;
diff --git a/src/plugins/platforms/eglfs/CMakeLists.txt b/src/plugins/platforms/eglfs/CMakeLists.txt
index 8df6c98c60..a0a6116a45 100644
--- a/src/plugins/platforms/eglfs/CMakeLists.txt
+++ b/src/plugins/platforms/eglfs/CMakeLists.txt
@@ -1,8 +1,6 @@
# Copyright (C) 2022 The Qt Company Ltd.
-# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+# SPDX-License-Identifier: BSD-3-Clause
-# Generated from eglfs.pro.
-# special case begin
qt_find_package(EGL)
if(QT_FEATURE_eglfs_gbm)
@@ -18,7 +16,6 @@ elseif(QT_FEATURE_eglfs_openwfd)
endif()
set(QT_QPA_DEFAULT_EGLFS_INTEGRATION "${_device_integration}" CACHE STRING "Default EGLFS device integration plugin")
-# special case end
#####################################################################
## EglFSDeviceIntegrationPrivate Module:
@@ -37,7 +34,8 @@ qt_internal_add_module(EglFSDeviceIntegrationPrivate
DEFINES
QT_BUILD_EGL_DEVICE_LIB
QT_EGL_NO_X11
- EGLFS_PREFERRED_PLUGIN=${QT_QPA_DEFAULT_EGLFS_INTEGRATION} # special case
+ QT_NO_FOREACH
+ EGLFS_PREFERRED_PLUGIN=${QT_QPA_DEFAULT_EGLFS_INTEGRATION}
INCLUDE_DIRECTORIES
api
PUBLIC_LIBRARIES
@@ -45,12 +43,12 @@ qt_internal_add_module(EglFSDeviceIntegrationPrivate
Qt::DeviceDiscoverySupportPrivate
Qt::FbSupportPrivate
Qt::GuiPrivate
- EGL::EGL # special case
+ EGL::EGL
+ HEADER_SYNC_SOURCE_DIRECTORY
+ "${CMAKE_CURRENT_SOURCE_DIR}/api"
+ NO_GENERATE_CPP_EXPORTS
)
-#### Keys ignored in scope 2:.:.:eglfsdeviceintegration.pro:<TRUE>:
-# MODULE = "eglfsdeviceintegration"
-
## Scopes:
#####################################################################
@@ -72,19 +70,6 @@ qt_internal_extend_target(EglFSDeviceIntegrationPrivate CONDITION QT_FEATURE_ope
Qt::OpenGLPrivate
)
-# special case begin
-# comment out
-#qt_internal_extend_target(EglFSDeviceIntegrationPrivate CONDITION NOT EGLFS_PLATFORM_HOOKS_SOURCES_ISEMPTY
- #DEFINES
- #EGLFS_PLATFORM_HOOKS
-#)
-
-#qt_internal_extend_target(EglFSDeviceIntegrationPrivate CONDITION NOT EGLFS_DEVICE_INTEGRATION_ISEMPTY
- #DEFINES
- #EGLFS_PREFERRED_PLUGIN=
-#)
-# special case end
-
if(QT_FEATURE_cursor)
# Resources:
set(cursor_resource_files
@@ -107,23 +92,15 @@ endif()
qt_internal_add_plugin(QEglFSIntegrationPlugin
OUTPUT_NAME qeglfs
PLUGIN_TYPE platforms
- DEFAULT_IF ${QT_QPA_DEFAULT_PLATFORM} MATCHES eglfs # special case
+ DEFAULT_IF ${QT_QPA_DEFAULT_PLATFORM} MATCHES eglfs
SOURCES
qeglfsmain.cpp
DEFINES
QT_EGL_NO_X11
LIBRARIES
- Qt::CorePrivate # special case
+ Qt::CorePrivate
Qt::EglFSDeviceIntegrationPrivate
- EGL::EGL # special case
+ EGL::EGL
)
-#### Keys ignored in scope 12:.:.:eglfs-plugin.pro:<TRUE>:
-# OTHER_FILES = "$$PWD/eglfs.json"
-
-## Scopes:
-#####################################################################
-
-#### Keys ignored in scope 13:.:.:eglfs-plugin.pro:NOT TARGET___equals____ss_QT_DEFAULT_QPA_PLUGIN:
-# PLUGIN_EXTENDS = "-"
add_subdirectory(deviceintegration)
diff --git a/src/plugins/platforms/eglfs/api/qeglfscontext.cpp b/src/plugins/platforms/eglfs/api/qeglfscontext.cpp
index a17b567b24..9c10c1a998 100644
--- a/src/plugins/platforms/eglfs/api/qeglfscontext.cpp
+++ b/src/plugins/platforms/eglfs/api/qeglfscontext.cpp
@@ -22,10 +22,19 @@ QEglFSContext::QEglFSContext(const QSurfaceFormat &format, QPlatformOpenGLContex
EGLSurface QEglFSContext::eglSurfaceForPlatformSurface(QPlatformSurface *surface)
{
- if (surface->surface()->surfaceClass() == QSurface::Window)
- return static_cast<QEglFSWindow *>(surface)->surface();
- else
+ if (surface->surface()->surfaceClass() == QSurface::Window) {
+
+ QEglFSWindow *w = static_cast<QEglFSWindow *>(surface);
+ EGLSurface s = w->surface();
+ if (s == EGL_NO_SURFACE) {
+ w->resetSurface();
+ s = w->surface();
+ }
+ return s;
+
+ } else {
return static_cast<QEGLPbuffer *>(surface)->pbuffer();
+ }
}
EGLSurface QEglFSContext::createTemporaryOffscreenSurface()
diff --git a/src/plugins/platforms/eglfs/api/qeglfscursor.cpp b/src/plugins/platforms/eglfs/api/qeglfscursor.cpp
index dadde5905e..1e1a7d8269 100644
--- a/src/plugins/platforms/eglfs/api/qeglfscursor.cpp
+++ b/src/plugins/platforms/eglfs/api/qeglfscursor.cpp
@@ -8,6 +8,7 @@
#include <qpa/qwindowsysteminterface.h>
#include <QtGui/QOpenGLContext>
+#include <QtGui/QOpenGLFunctions>
#include <QtCore/QFile>
#include <QtCore/QJsonDocument>
#include <QtCore/QJsonArray>
@@ -117,16 +118,18 @@ void QEglFSCursor::createShaderPrograms()
void QEglFSCursor::createCursorTexture(uint *texture, const QImage &image)
{
+ Q_ASSERT(QOpenGLContext::currentContext());
+ QOpenGLFunctions *f = QOpenGLContext::currentContext()->functions();
if (!*texture)
- glGenTextures(1, texture);
- glBindTexture(GL_TEXTURE_2D, *texture);
- glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
- glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
- glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
- glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
-
- glTexImage2D(GL_TEXTURE_2D, 0 /* level */, GL_RGBA, image.width(), image.height(), 0 /* border */,
- GL_RGBA, GL_UNSIGNED_BYTE, image.constBits());
+ f->glGenTextures(1, texture);
+ f->glBindTexture(GL_TEXTURE_2D, *texture);
+ f->glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+ f->glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+ f->glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ f->glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+
+ f->glTexImage2D(GL_TEXTURE_2D, 0 /* level */, GL_RGBA, image.width(), image.height(), 0 /* border */,
+ GL_RGBA, GL_UNSIGNED_BYTE, image.constBits());
}
void QEglFSCursor::initCursorAtlas()
@@ -338,8 +341,8 @@ void QEglFSCursor::paintOnScreen()
// to deal with the changes we make.
struct StateSaver
{
- StateSaver() {
- f = QOpenGLContext::currentContext()->functions();
+ StateSaver(QOpenGLFunctions* func) {
+ f = func;
vaoHelper = QOpenGLVertexArrayObjectHelper::vertexArrayObjectHelperForContext(QOpenGLContext::currentContext());
static bool windowsChecked = false;
@@ -365,6 +368,8 @@ struct StateSaver
f->glGetIntegerv(GL_BLEND_SRC_ALPHA, blendFunc + 1);
f->glGetIntegerv(GL_BLEND_DST_RGB, blendFunc + 2);
f->glGetIntegerv(GL_BLEND_DST_ALPHA, blendFunc + 3);
+ scissor = f->glIsEnabled(GL_SCISSOR_TEST);
+ stencil = f->glIsEnabled(GL_STENCIL_TEST);
f->glGetIntegerv(GL_ARRAY_BUFFER_BINDING, &arrayBuf);
if (vaoHelper->isValid())
f->glGetIntegerv(GL_VERTEX_ARRAY_BINDING, &vao);
@@ -388,17 +393,15 @@ struct StateSaver
f->glFrontFace(frontFace);
if (cull)
f->glEnable(GL_CULL_FACE);
- else
- f->glDisable(GL_CULL_FACE);
if (depthTest)
f->glEnable(GL_DEPTH_TEST);
- else
- f->glDisable(GL_DEPTH_TEST);
- if (blend)
- f->glEnable(GL_BLEND);
- else
+ if (!blend)
f->glDisable(GL_BLEND);
f->glBlendFuncSeparate(blendFunc[0], blendFunc[1], blendFunc[2], blendFunc[3]);
+ if (scissor)
+ f->glEnable(GL_SCISSOR_TEST);
+ if (stencil)
+ f->glEnable(GL_STENCIL_TEST);
f->glBindBuffer(GL_ARRAY_BUFFER, arrayBuf);
if (vaoHelper->isValid())
vaoHelper->glBindVertexArray(vao);
@@ -423,6 +426,8 @@ struct StateSaver
bool depthTest;
bool blend;
GLint blendFunc[4];
+ bool scissor;
+ bool stencil;
GLint vao;
GLint arrayBuf;
struct { GLint enabled, type, size, normalized, stride, buffer; GLvoid *pointer; } va[2];
@@ -430,11 +435,9 @@ struct StateSaver
void QEglFSCursor::draw(const QRectF &r)
{
- StateSaver stateSaver;
-
- // one time initialization
- if (!QOpenGLFunctions::d_ptr)
- initializeOpenGLFunctions();
+ Q_ASSERT(QOpenGLContext::currentContext());
+ QOpenGLFunctions *f = QOpenGLContext::currentContext()->functions();
+ StateSaver stateSaver(f);
QEglFSCursorData &gfx = static_cast<QEglFSContext*>(QOpenGLContext::currentContext()->handle())->cursorData;
if (!gfx.program) {
@@ -483,13 +486,13 @@ void QEglFSCursor::draw(const QRectF &r)
s2, t1
};
- glActiveTexture(GL_TEXTURE0);
- glBindTexture(GL_TEXTURE_2D, cursorTexture);
+ f->glActiveTexture(GL_TEXTURE0);
+ f->glBindTexture(GL_TEXTURE_2D, cursorTexture);
if (stateSaver.vaoHelper->isValid())
stateSaver.vaoHelper->glBindVertexArray(0);
- glBindBuffer(GL_ARRAY_BUFFER, 0);
+ f->glBindBuffer(GL_ARRAY_BUFFER, 0);
gfx.program->enableAttributeArray(0);
gfx.program->enableAttributeArray(1);
@@ -499,13 +502,15 @@ void QEglFSCursor::draw(const QRectF &r)
gfx.program->setUniformValue(gfx.textureEntry, 0);
gfx.program->setUniformValue(gfx.matEntry, m_rotationMatrix);
- glDisable(GL_CULL_FACE);
- glFrontFace(GL_CCW);
- glEnable(GL_BLEND);
- glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
- glDisable(GL_DEPTH_TEST); // disable depth testing to make sure cursor is always on top
+ f->glDisable(GL_CULL_FACE);
+ f->glFrontFace(GL_CCW);
+ f->glEnable(GL_BLEND);
+ f->glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
+ f->glDisable(GL_DEPTH_TEST); // disable depth testing to make sure cursor is always on top
+ f->glDisable(GL_SCISSOR_TEST);
+ f->glDisable(GL_STENCIL_TEST);
- glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
+ f->glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
gfx.program->disableAttributeArray(0);
gfx.program->disableAttributeArray(1);
diff --git a/src/plugins/platforms/eglfs/api/qeglfscursor_p.h b/src/plugins/platforms/eglfs/api/qeglfscursor_p.h
index 294426067c..88d4ce695a 100644
--- a/src/plugins/platforms/eglfs/api/qeglfscursor_p.h
+++ b/src/plugins/platforms/eglfs/api/qeglfscursor_p.h
@@ -20,7 +20,6 @@
#include <qpa/qplatformscreen.h>
#include <QtOpenGL/QOpenGLShaderProgram>
#include <QtGui/QMatrix4x4>
-#include <QtGui/QOpenGLFunctions>
#include <QtGui/private/qinputdevicemanager_p.h>
#include <QtCore/qlist.h>
@@ -58,7 +57,6 @@ struct QEglFSCursorData {
};
class Q_EGLFS_EXPORT QEglFSCursor : public QPlatformCursor
- , protected QOpenGLFunctions
{
Q_OBJECT
public:
diff --git a/src/plugins/platforms/eglfs/api/qeglfsdeviceintegration.cpp b/src/plugins/platforms/eglfs/api/qeglfsdeviceintegration.cpp
index 268b9aa093..56fda45e90 100644
--- a/src/plugins/platforms/eglfs/api/qeglfsdeviceintegration.cpp
+++ b/src/plugins/platforms/eglfs/api/qeglfsdeviceintegration.cpp
@@ -65,7 +65,7 @@ static int framebuffer = -1;
QByteArray QEglFSDeviceIntegration::fbDeviceName() const
{
-#ifdef Q_OS_LINUX
+#if defined(Q_OS_LINUX) || defined(Q_OS_VXWORKS)
QByteArray fbDev = qgetenv("QT_QPA_EGLFS_FB");
if (fbDev.isEmpty())
fbDev = QByteArrayLiteral("/dev/fb0");
@@ -95,7 +95,7 @@ int QEglFSDeviceIntegration::framebufferIndex() const
void QEglFSDeviceIntegration::platformInit()
{
-#ifdef Q_OS_LINUX
+#if defined(Q_OS_LINUX) || defined(Q_OS_VXWORKS)
QByteArray fbDev = fbDeviceName();
framebuffer = qt_safe_open(fbDev, O_RDONLY);
@@ -113,7 +113,7 @@ void QEglFSDeviceIntegration::platformInit()
void QEglFSDeviceIntegration::platformDestroy()
{
-#ifdef Q_OS_LINUX
+#if defined(Q_OS_LINUX) || defined(Q_OS_VXWORKS)
if (framebuffer != -1)
close(framebuffer);
#endif
diff --git a/src/plugins/platforms/eglfs/api/qeglfsintegration.cpp b/src/plugins/platforms/eglfs/api/qeglfsintegration.cpp
index 42cbdf127c..f0b64c475c 100644
--- a/src/plugins/platforms/eglfs/api/qeglfsintegration.cpp
+++ b/src/plugins/platforms/eglfs/api/qeglfsintegration.cpp
@@ -110,7 +110,8 @@ void QEglFSIntegration::initialize()
void QEglFSIntegration::destroy()
{
- foreach (QWindow *w, qGuiApp->topLevelWindows())
+ const auto toplevels = qGuiApp->topLevelWindows();
+ for (QWindow *w : toplevels)
w->destroy();
qt_egl_device_integration()->screenDestroy();
@@ -386,6 +387,14 @@ QFunctionPointer QEglFSIntegration::platformFunction(const QByteArray &function)
return qt_egl_device_integration()->platformFunction(function);
}
+QVariant QEglFSIntegration::styleHint(QPlatformIntegration::StyleHint hint) const
+{
+ if (hint == QPlatformIntegration::ShowIsFullScreen)
+ return true;
+
+ return QPlatformIntegration::styleHint(hint);
+}
+
#if QT_CONFIG(evdev)
void QEglFSIntegration::loadKeymap(const QString &filename)
{
diff --git a/src/plugins/platforms/eglfs/api/qeglfsintegration_p.h b/src/plugins/platforms/eglfs/api/qeglfsintegration_p.h
index 3e4c57197e..8007167ec7 100644
--- a/src/plugins/platforms/eglfs/api/qeglfsintegration_p.h
+++ b/src/plugins/platforms/eglfs/api/qeglfsintegration_p.h
@@ -76,6 +76,8 @@ public:
QFunctionPointer platformFunction(const QByteArray &function) const override;
+ QVariant styleHint(QPlatformIntegration::StyleHint hint) const override;
+
QFbVtHandler *vtHandler() { return m_vtHandler.data(); }
QPointer<QWindow> pointerWindow() { return m_pointerWindow; }
diff --git a/src/plugins/platforms/eglfs/api/qeglfsscreen.cpp b/src/plugins/platforms/eglfs/api/qeglfsscreen.cpp
index a6345f693d..c5108be04a 100644
--- a/src/plugins/platforms/eglfs/api/qeglfsscreen.cpp
+++ b/src/plugins/platforms/eglfs/api/qeglfsscreen.cpp
@@ -121,7 +121,7 @@ void QEglFSScreen::handleCursorMove(const QPoint &pos)
return;
// First window is always fullscreen.
- if (windows.count() == 1) {
+ if (windows.size() == 1) {
QWindow *window = windows[0]->sourceWindow();
if (platformIntegration->pointerWindow() != window) {
platformIntegration->setPointerWindow(window);
@@ -131,7 +131,7 @@ void QEglFSScreen::handleCursorMove(const QPoint &pos)
}
QWindow *enter = nullptr, *leave = nullptr;
- for (int i = windows.count() - 1; i >= 0; --i) {
+ for (int i = windows.size() - 1; i >= 0; --i) {
QWindow *window = windows[i]->sourceWindow();
const QRect geom = window->geometry();
if (geom.contains(pos)) {
@@ -189,7 +189,7 @@ QPixmap QEglFSScreen::grabWindow(WId wid, int x, int y, int width, int height) c
return QPixmap::fromImage(img).copy(x, y, width, height);
}
- foreach (QOpenGLCompositorWindow *w, windows) {
+ for (QOpenGLCompositorWindow *w : windows) {
const QWindow *window = w->sourceWindow();
if (window->winId() == wid) {
const QRect geom = window->geometry();
diff --git a/src/plugins/platforms/eglfs/api/qeglfswindow.cpp b/src/plugins/platforms/eglfs/api/qeglfswindow.cpp
index 39c5b3d4bf..861dd9c5da 100644
--- a/src/plugins/platforms/eglfs/api/qeglfswindow.cpp
+++ b/src/plugins/platforms/eglfs/api/qeglfswindow.cpp
@@ -22,6 +22,8 @@
QT_BEGIN_NAMESPACE
+Q_DECLARE_LOGGING_CATEGORY(qLcEglDevDebug)
+
QEglFSWindow::QEglFSWindow(QWindow *w)
: QPlatformWindow(w),
#ifndef QT_NO_OPENGL
@@ -150,20 +152,44 @@ void QEglFSWindow::destroy()
#ifndef QT_NO_OPENGL
QOpenGLCompositor::destroy();
- if (qt_gl_global_share_context() == m_rasterCompositingContext)
- qt_gl_set_global_share_context(nullptr);
- delete m_rasterCompositingContext;
#endif
}
+#ifndef QT_NO_OPENGL
+ if (qt_gl_global_share_context() == m_rasterCompositingContext)
+ qt_gl_set_global_share_context(nullptr);
+ delete m_rasterCompositingContext;
+#endif
+
m_flags = { };
}
void QEglFSWindow::invalidateSurface()
{
if (m_surface != EGL_NO_SURFACE) {
- eglDestroySurface(screen()->display(), m_surface);
+ qCDebug(qLcEglDevDebug) << Q_FUNC_INFO << " about to destroy EGLSurface: " << m_surface;
+
+ bool ok = eglDestroySurface(screen()->display(), m_surface);
+
+ if (!ok) {
+ qCWarning(qLcEglDevDebug, "QEglFSWindow::invalidateSurface() eglDestroySurface failed!"
+ " Follow-up errors or memory leaks are possible."
+ " eglGetError(): %x", eglGetError());
+ }
+
+ if (eglGetCurrentSurface(EGL_READ) == m_surface ||
+ eglGetCurrentSurface(EGL_DRAW) == m_surface) {
+ bool ok = eglMakeCurrent(eglGetCurrentDisplay(), EGL_NO_DISPLAY, EGL_NO_DISPLAY, EGL_NO_CONTEXT);
+ qCDebug(qLcEglDevDebug) << Q_FUNC_INFO << " due to eglDestroySurface on *currently* bound surface"
+ << "we just called eglMakeCurrent(..,0,0,0)! It returned: " << ok;
+ }
+
+ if (screen()->primarySurface() == m_surface)
+ screen()->setPrimarySurface(EGL_NO_SURFACE);
+
+
m_surface = EGL_NO_SURFACE;
+ m_flags = m_flags & ~Created;
}
qt_egl_device_integration()->destroyNativeWindow(m_window);
m_window = 0;
@@ -240,7 +266,7 @@ void QEglFSWindow::requestActivateWindow()
QOpenGLCompositor::instance()->moveToTop(this);
#endif
QWindow *wnd = window();
- QWindowSystemInterface::handleWindowActivated(wnd, Qt::ActiveWindowFocusReason);
+ QWindowSystemInterface::handleFocusWindowChanged(wnd, Qt::ActiveWindowFocusReason);
QWindowSystemInterface::handleExposeEvent(wnd, QRect(QPoint(0, 0), wnd->geometry().size()));
}
@@ -260,7 +286,7 @@ void QEglFSWindow::lower()
#ifndef QT_NO_OPENGL
QOpenGLCompositor *compositor = QOpenGLCompositor::instance();
QList<QOpenGLCompositorWindow *> windows = compositor->windows();
- if (window()->type() != Qt::Desktop && windows.count() > 1) {
+ if (window()->type() != Qt::Desktop && windows.size() > 1) {
int idx = windows.indexOf(this);
if (idx > 0) {
compositor->changeWindowIndex(this, idx - 1);
diff --git a/src/plugins/platforms/eglfs/api/qeglfswindow_p.h b/src/plugins/platforms/eglfs/api/qeglfswindow_p.h
index d111042040..e51cd69f3d 100644
--- a/src/plugins/platforms/eglfs/api/qeglfswindow_p.h
+++ b/src/plugins/platforms/eglfs/api/qeglfswindow_p.h
@@ -70,8 +70,8 @@ public:
bool isRaster() const;
#ifndef QT_NO_OPENGL
- QOpenGLCompositorBackingStore *backingStore() { return m_backingStore; }
- void setBackingStore(QOpenGLCompositorBackingStore *backingStore);
+ QOpenGLCompositorBackingStore *backingStore() const override { return m_backingStore; }
+ void setBackingStore(QOpenGLCompositorBackingStore *backingStore) override;
QWindow *sourceWindow() const override;
const QPlatformTextureList *textures() const override;
void endCompositing() override;
diff --git a/src/plugins/platforms/eglfs/deviceintegration/CMakeLists.txt b/src/plugins/platforms/eglfs/deviceintegration/CMakeLists.txt
index 8c8389c316..bb50216f23 100644
--- a/src/plugins/platforms/eglfs/deviceintegration/CMakeLists.txt
+++ b/src/plugins/platforms/eglfs/deviceintegration/CMakeLists.txt
@@ -1,7 +1,5 @@
# Copyright (C) 2022 The Qt Company Ltd.
-# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-
-# Generated from deviceintegration.pro.
+# SPDX-License-Identifier: BSD-3-Clause
if(QT_FEATURE_eglfs_x11)
add_subdirectory(eglfs_x11)
@@ -16,10 +14,10 @@ if(QT_FEATURE_eglfs_egldevice)
add_subdirectory(eglfs_kms_egldevice)
endif()
if(QT_FEATURE_eglfs_vsp2)
- # add_subdirectory(eglfs_kms_vsp2) # special case TODO
+ # add_subdirectory(eglfs_kms_vsp2) # TODO: QTBUG-112769
endif()
if(QT_FEATURE_eglfs_brcm)
- # add_subdirectory(eglfs_brcm) # special case TODO
+ # add_subdirectory(eglfs_brcm) # TODO: QTBUG-112769
endif()
if(QT_FEATURE_eglfs_mali)
add_subdirectory(eglfs_mali)
@@ -28,7 +26,7 @@ if(QT_FEATURE_eglfs_viv)
add_subdirectory(eglfs_viv)
endif()
if(QT_FEATURE_eglfs_rcar)
- # add_subdirectory(eglfs_rcar) # special case TODO
+ # add_subdirectory(eglfs_rcar) # TODO: QTBUG-112769
endif()
if(QT_FEATURE_eglfs_viv_wl)
add_subdirectory(eglfs_viv_wl)
diff --git a/src/plugins/platforms/eglfs/deviceintegration/eglfs_emu/CMakeLists.txt b/src/plugins/platforms/eglfs/deviceintegration/eglfs_emu/CMakeLists.txt
index 57d1533a3f..fa744be033 100644
--- a/src/plugins/platforms/eglfs/deviceintegration/eglfs_emu/CMakeLists.txt
+++ b/src/plugins/platforms/eglfs/deviceintegration/eglfs_emu/CMakeLists.txt
@@ -1,7 +1,5 @@
# Copyright (C) 2022 The Qt Company Ltd.
-# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-
-# Generated from eglfs_emu.pro.
+# SPDX-License-Identifier: BSD-3-Clause
#####################################################################
## QEglFSEmulatorIntegrationPlugin Plugin:
@@ -25,7 +23,3 @@ qt_internal_add_plugin(QEglFSEmulatorIntegrationPlugin
Qt::Gui
Qt::GuiPrivate
)
-
-#### Keys ignored in scope 1:.:.:eglfs_emu.pro:<TRUE>:
-# DISTFILES = "eglfs_emu.json"
-# OTHER_FILES = "$$PWD/eglfs_emu.json"
diff --git a/src/plugins/platforms/eglfs/deviceintegration/eglfs_emu/qeglfsemulatorintegration.cpp b/src/plugins/platforms/eglfs/deviceintegration/eglfs_emu/qeglfsemulatorintegration.cpp
index ed55420c57..a63aafa242 100644
--- a/src/plugins/platforms/eglfs/deviceintegration/eglfs_emu/qeglfsemulatorintegration.cpp
+++ b/src/plugins/platforms/eglfs/deviceintegration/eglfs_emu/qeglfsemulatorintegration.cpp
@@ -93,7 +93,7 @@ EGLNativeWindowType QEglFSEmulatorIntegration::createNativeWindow(QPlatformWindo
// Let the emulator know which screen the window surface is attached to
setDisplay(screen->id());
}
- static QBasicAtomicInt uniqueWindowId = Q_BASIC_ATOMIC_INITIALIZER(0);
+ Q_CONSTINIT static QBasicAtomicInt uniqueWindowId = Q_BASIC_ATOMIC_INITIALIZER(0);
return EGLNativeWindowType(qintptr(1 + uniqueWindowId.fetchAndAddRelaxed(1)));
}
diff --git a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/CMakeLists.txt b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/CMakeLists.txt
index a04c9bdb0f..9f9d315202 100644
--- a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/CMakeLists.txt
+++ b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/CMakeLists.txt
@@ -1,7 +1,5 @@
# Copyright (C) 2022 The Qt Company Ltd.
-# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-
-# Generated from eglfs_kms.pro.
+# SPDX-License-Identifier: BSD-3-Clause
#####################################################################
## EglFsKmsGbmSupportPrivate Module:
@@ -26,6 +24,7 @@ qt_internal_add_module(EglFsKmsGbmSupportPrivate
Qt::GuiPrivate
Qt::KmsSupportPrivate
gbm::gbm
+ NO_GENERATE_CPP_EXPORTS
)
#####################################################################
## QEglFSKmsGbmIntegrationPlugin Plugin:
@@ -48,6 +47,3 @@ qt_internal_add_plugin(QEglFSKmsGbmIntegrationPlugin
Qt::KmsSupportPrivate
gbm::gbm
)
-
-#### Keys ignored in scope 3:.:.:eglfs_kms-plugin.pro:<TRUE>:
-# OTHER_FILES = "$$PWD/eglfs_kms.json"
diff --git a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmdevice.cpp b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmdevice.cpp
index 6d903a3f1e..89479fc250 100644
--- a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmdevice.cpp
+++ b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmdevice.cpp
@@ -47,7 +47,13 @@ bool QEglFSKmsGbmDevice::open()
setFd(fd);
- m_eventReader.create(this);
+ if (usesEventReader()) {
+ qCDebug(qLcEglfsKmsDebug, "Using dedicated drm event reading thread");
+ m_eventReader.create(this);
+ } else {
+ qCDebug(qLcEglfsKmsDebug, "Not using dedicated drm event reading thread; "
+ "threaded multi-screen setups may experience problems");
+ }
return true;
}
@@ -56,7 +62,8 @@ void QEglFSKmsGbmDevice::close()
{
// Note: screens are gone at this stage.
- m_eventReader.destroy();
+ if (usesEventReader())
+ m_eventReader.destroy();
if (m_gbm_device) {
gbm_device_destroy(m_gbm_device);
@@ -138,4 +145,10 @@ void QEglFSKmsGbmDevice::registerScreen(QPlatformScreen *screen,
m_globalCursor->reevaluateVisibilityForScreens();
}
+bool QEglFSKmsGbmDevice::usesEventReader() const
+{
+ static const bool eventReaderThreadDisabled = qEnvironmentVariableIntValue("QT_QPA_EGLFS_KMS_NO_EVENT_READER_THREAD");
+ return !eventReaderThreadDisabled;
+}
+
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmdevice_p.h b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmdevice_p.h
index 832b3a2873..e00992ed29 100644
--- a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmdevice_p.h
+++ b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmdevice_p.h
@@ -19,6 +19,7 @@
#include "qeglfskmsgbmcursor_p.h"
#include <private/qeglfskmsdevice_p.h>
+#include <private/qeglfskmseventreader_p.h>
#include <gbm.h>
@@ -51,11 +52,14 @@ public:
const QPoint &virtualPos,
const QList<QPlatformScreen *> &virtualSiblings) override;
+ bool usesEventReader() const;
+ QEglFSKmsEventReader *eventReader() { return &m_eventReader; }
+
private:
Q_DISABLE_COPY(QEglFSKmsGbmDevice)
gbm_device *m_gbm_device;
-
+ QEglFSKmsEventReader m_eventReader;
QEglFSKmsGbmCursor *m_globalCursor;
};
diff --git a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmscreen.cpp b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmscreen.cpp
index c7b0208188..073ba3bdcc 100644
--- a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmscreen.cpp
+++ b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmscreen.cpp
@@ -20,6 +20,8 @@ QT_BEGIN_NAMESPACE
Q_DECLARE_LOGGING_CATEGORY(qLcEglfsKmsDebug)
+QMutex QEglFSKmsGbmScreen::m_nonThreadedFlipMutex;
+
static inline uint32_t drmFormatToGbmFormat(uint32_t drmFormat)
{
Q_ASSERT(DRM_FORMAT_XRGB8888 == GBM_FORMAT_XRGB8888);
@@ -140,11 +142,12 @@ gbm_surface *QEglFSKmsGbmScreen::createSurface(EGLConfig eglConfig)
}
}
+ const uint32_t gbmFormat = drmFormatToGbmFormat(m_output.drm_format);
+
// Fallback for older drivers, and when "format" is explicitly specified
// in the output config. (not guaranteed that the requested format works
// of course, but do what we are told to)
if (!m_gbm_surface) {
- uint32_t gbmFormat = drmFormatToGbmFormat(m_output.drm_format);
if (queryFromEgl)
qCDebug(qLcEglfsKmsDebug, "Could not create surface with EGL_NATIVE_VISUAL_ID, falling back to format %x", gbmFormat);
m_gbm_surface = gbm_surface_create(gbmDevice,
@@ -153,13 +156,39 @@ gbm_surface *QEglFSKmsGbmScreen::createSurface(EGLConfig eglConfig)
gbmFormat,
gbmFlags());
}
+
+ // Fallback for some drivers, its required to request with modifiers
+ if (!m_gbm_surface) {
+ uint64_t modifier = DRM_FORMAT_MOD_LINEAR;
+
+ m_gbm_surface = gbm_surface_create_with_modifiers(gbmDevice,
+ rawGeometry().width(),
+ rawGeometry().height(),
+ gbmFormat,
+ &modifier, 1);
+ }
+ // Fail here, as it would fail with the next usage of the GBM surface, which is very unexpected
+ if (!m_gbm_surface)
+ qFatal("Could not create GBM surface!");
}
return m_gbm_surface; // not owned, gets destroyed in QEglFSKmsGbmIntegration::destroyNativeWindow() via QEglFSKmsGbmWindow::invalidateSurface()
}
void QEglFSKmsGbmScreen::resetSurface()
{
+ m_flipPending = false; // not necessarily true but enough to keep bo_next
+ m_gbm_bo_current = nullptr;
m_gbm_surface = nullptr;
+
+ // Leave m_gbm_bo_next untouched. waitForFlip() should
+ // still do its work, when called. Otherwise we end up
+ // in device-is-busy errors if there is a new QWindow
+ // created afterwards. (QTBUG-122663)
+
+ // If not using atomic, will need a new drmModeSetCrtc if a new window
+ // gets created later on (and so there's a new fb).
+ if (!device()->hasAtomicSupport())
+ needsNewModeSetForNextFb = true;
}
void QEglFSKmsGbmScreen::initCloning(QPlatformScreen *screenThisScreenClones,
@@ -171,8 +200,10 @@ void QEglFSKmsGbmScreen::initCloning(QPlatformScreen *screenThisScreenClones,
qWarning("QEglFSKmsGbmScreen %s cannot be clone source and destination at the same time", qPrintable(name()));
return;
}
- if (clonesAnother)
+ if (clonesAnother) {
m_cloneSource = static_cast<QEglFSKmsGbmScreen *>(screenThisScreenClones);
+ qCDebug(qLcEglfsKmsDebug, "Screen %s clones %s", qPrintable(name()), qPrintable(m_cloneSource->name()));
+ }
// clone sources need to know their additional destinations
for (QPlatformScreen *s : screensCloningThisScreen) {
@@ -187,8 +218,9 @@ void QEglFSKmsGbmScreen::ensureModeSet(uint32_t fb)
QKmsOutput &op(output());
const int fd = device()->fd();
- if (!op.mode_set) {
+ if (!op.mode_set || needsNewModeSetForNextFb) {
op.mode_set = true;
+ needsNewModeSetForNextFb = false;
bool doModeSet = true;
drmModeCrtcPtr currentMode = drmModeGetCrtc(fd, op.crtc_id);
@@ -227,6 +259,29 @@ void QEglFSKmsGbmScreen::ensureModeSet(uint32_t fb)
}
}
+void QEglFSKmsGbmScreen::nonThreadedPageFlipHandler(int fd,
+ unsigned int sequence,
+ unsigned int tv_sec,
+ unsigned int tv_usec,
+ void *user_data)
+{
+ // note that with cloning involved this callback is called also for screens that clone another one
+ Q_UNUSED(fd);
+ QEglFSKmsGbmScreen *screen = static_cast<QEglFSKmsGbmScreen *>(user_data);
+ screen->flipFinished();
+ screen->pageFlipped(sequence, tv_sec, tv_usec);
+}
+
+void QEglFSKmsGbmScreen::waitForFlipWithEventReader(QEglFSKmsGbmScreen *screen)
+{
+ m_flipMutex.lock();
+ QEglFSKmsGbmDevice *dev = static_cast<QEglFSKmsGbmDevice *>(device());
+ dev->eventReader()->startWaitFlip(screen, &m_flipMutex, &m_flipCond);
+ m_flipCond.wait(&m_flipMutex);
+ m_flipMutex.unlock();
+ screen->flipFinished();
+}
+
void QEglFSKmsGbmScreen::waitForFlip()
{
if (m_headless || m_cloneSource)
@@ -236,18 +291,69 @@ void QEglFSKmsGbmScreen::waitForFlip()
if (!m_gbm_bo_next)
return;
- m_flipMutex.lock();
- device()->eventReader()->startWaitFlip(this, &m_flipMutex, &m_flipCond);
- m_flipCond.wait(&m_flipMutex);
- m_flipMutex.unlock();
-
- flipFinished();
+ QEglFSKmsGbmDevice *dev = static_cast<QEglFSKmsGbmDevice *>(device());
+ if (dev->usesEventReader()) {
+ waitForFlipWithEventReader(this);
+ // Now, unlike on the other code path, we need to ensure the
+ // flips have completed for the screens that just scan out
+ // this one's content, because the eventReader's wait is
+ // per-output.
+ for (CloneDestination &d : m_cloneDests) {
+ if (d.screen != this)
+ waitForFlipWithEventReader(d.screen);
+ }
+ } else {
+ QMutexLocker lock(&m_nonThreadedFlipMutex);
+ while (m_gbm_bo_next) {
+ drmEventContext drmEvent;
+ memset(&drmEvent, 0, sizeof(drmEvent));
+ drmEvent.version = 2;
+ drmEvent.vblank_handler = nullptr;
+ drmEvent.page_flip_handler = nonThreadedPageFlipHandler;
+ drmHandleEvent(device()->fd(), &drmEvent);
+ }
+ }
#if QT_CONFIG(drm_atomic)
device()->threadLocalAtomicReset();
#endif
}
+#if QT_CONFIG(drm_atomic)
+static void addAtomicFlip(drmModeAtomicReq *request, const QKmsOutput &output, uint32_t fb)
+{
+ drmModeAtomicAddProperty(request, output.eglfs_plane->id,
+ output.eglfs_plane->framebufferPropertyId, fb);
+
+ drmModeAtomicAddProperty(request, output.eglfs_plane->id,
+ output.eglfs_plane->crtcPropertyId, output.crtc_id);
+
+ drmModeAtomicAddProperty(request, output.eglfs_plane->id,
+ output.eglfs_plane->srcwidthPropertyId, output.size.width() << 16);
+
+ drmModeAtomicAddProperty(request, output.eglfs_plane->id,
+ output.eglfs_plane->srcXPropertyId, 0);
+
+ drmModeAtomicAddProperty(request, output.eglfs_plane->id,
+ output.eglfs_plane->srcYPropertyId, 0);
+
+ drmModeAtomicAddProperty(request, output.eglfs_plane->id,
+ output.eglfs_plane->srcheightPropertyId, output.size.height() << 16);
+
+ drmModeAtomicAddProperty(request, output.eglfs_plane->id,
+ output.eglfs_plane->crtcXPropertyId, 0);
+
+ drmModeAtomicAddProperty(request, output.eglfs_plane->id,
+ output.eglfs_plane->crtcYPropertyId, 0);
+
+ drmModeAtomicAddProperty(request, output.eglfs_plane->id,
+ output.eglfs_plane->crtcwidthPropertyId, output.modes[output.mode].hdisplay);
+
+ drmModeAtomicAddProperty(request, output.eglfs_plane->id,
+ output.eglfs_plane->crtcheightPropertyId, output.modes[output.mode].vdisplay);
+}
+#endif
+
void QEglFSKmsGbmScreen::flip()
{
// For headless screen just return silently. It is not necessarily an error
@@ -267,14 +373,24 @@ void QEglFSKmsGbmScreen::flip()
m_gbm_bo_next = gbm_surface_lock_front_buffer(m_gbm_surface);
if (!m_gbm_bo_next) {
- qWarning("Could not lock GBM surface front buffer!");
+ qWarning("Could not lock GBM surface front buffer for screen %s", qPrintable(name()));
return;
}
+ auto gbmRelease = qScopeGuard([this]{
+ m_flipPending = false;
+ gbm_surface_release_buffer(m_gbm_surface, m_gbm_bo_next);
+ m_gbm_bo_next = nullptr;
+ });
+
FrameBuffer *fb = framebufferForBufferObject(m_gbm_bo_next);
+ if (!fb) {
+ qWarning("FrameBuffer not available. Cannot flip");
+ return;
+ }
ensureModeSet(fb->fb);
- QKmsOutput &op(output());
+ const QKmsOutput &thisOutput(output());
const int fd = device()->fd();
m_flipPending = true;
@@ -282,40 +398,27 @@ void QEglFSKmsGbmScreen::flip()
#if QT_CONFIG(drm_atomic)
drmModeAtomicReq *request = device()->threadLocalAtomicRequest();
if (request) {
- drmModeAtomicAddProperty(request, op.eglfs_plane->id, op.eglfs_plane->framebufferPropertyId, fb->fb);
- drmModeAtomicAddProperty(request, op.eglfs_plane->id, op.eglfs_plane->crtcPropertyId, op.crtc_id);
- drmModeAtomicAddProperty(request, op.eglfs_plane->id, op.eglfs_plane->srcwidthPropertyId,
- op.size.width() << 16);
- drmModeAtomicAddProperty(request, op.eglfs_plane->id, op.eglfs_plane->srcXPropertyId, 0);
- drmModeAtomicAddProperty(request, op.eglfs_plane->id, op.eglfs_plane->srcYPropertyId, 0);
- drmModeAtomicAddProperty(request, op.eglfs_plane->id, op.eglfs_plane->srcheightPropertyId,
- op.size.height() << 16);
- drmModeAtomicAddProperty(request, op.eglfs_plane->id, op.eglfs_plane->crtcXPropertyId, 0);
- drmModeAtomicAddProperty(request, op.eglfs_plane->id, op.eglfs_plane->crtcYPropertyId, 0);
- drmModeAtomicAddProperty(request, op.eglfs_plane->id, op.eglfs_plane->crtcwidthPropertyId,
- m_output.modes[m_output.mode].hdisplay);
- drmModeAtomicAddProperty(request, op.eglfs_plane->id, op.eglfs_plane->crtcheightPropertyId,
- m_output.modes[m_output.mode].vdisplay);
-
+ addAtomicFlip(request, thisOutput, fb->fb);
static int zpos = qEnvironmentVariableIntValue("QT_QPA_EGLFS_KMS_ZPOS");
- if (zpos)
- drmModeAtomicAddProperty(request, op.eglfs_plane->id, op.eglfs_plane->zposPropertyId, zpos);
+ if (zpos) {
+ drmModeAtomicAddProperty(request, thisOutput.eglfs_plane->id,
+ thisOutput.eglfs_plane->zposPropertyId, zpos);
+ }
static uint blendOp = uint(qEnvironmentVariableIntValue("QT_QPA_EGLFS_KMS_BLEND_OP"));
- if (blendOp)
- drmModeAtomicAddProperty(request, op.eglfs_plane->id, op.eglfs_plane->blendOpPropertyId, blendOp);
+ if (blendOp) {
+ drmModeAtomicAddProperty(request, thisOutput.eglfs_plane->id,
+ thisOutput.eglfs_plane->blendOpPropertyId, blendOp);
+ }
}
#endif
} else {
int ret = drmModePageFlip(fd,
- op.crtc_id,
- fb->fb,
- DRM_MODE_PAGE_FLIP_EVENT,
- this);
+ thisOutput.crtc_id,
+ fb->fb,
+ DRM_MODE_PAGE_FLIP_EVENT,
+ this);
if (ret) {
qErrnoWarning("Could not queue DRM page flip on screen %s", qPrintable(name()));
- m_flipPending = false;
- gbm_surface_release_buffer(m_gbm_surface, m_gbm_bo_next);
- m_gbm_bo_next = nullptr;
return;
}
}
@@ -324,17 +427,20 @@ void QEglFSKmsGbmScreen::flip()
if (d.screen != this) {
d.screen->ensureModeSet(fb->fb);
d.cloneFlipPending = true;
- QKmsOutput &destOutput(d.screen->output());
+ const QKmsOutput &destOutput(d.screen->output());
if (device()->hasAtomicSupport()) {
#if QT_CONFIG(drm_atomic)
drmModeAtomicReq *request = device()->threadLocalAtomicRequest();
- if (request) {
- drmModeAtomicAddProperty(request, destOutput.eglfs_plane->id,
- destOutput.eglfs_plane->framebufferPropertyId, fb->fb);
- drmModeAtomicAddProperty(request, destOutput.eglfs_plane->id,
- destOutput.eglfs_plane->crtcPropertyId, destOutput.crtc_id);
- }
+ if (request)
+ addAtomicFlip(request, destOutput, fb->fb);
+
+ // ### This path is broken. On the other branch we can easily
+ // pass in d.screen as the user_data for drmModePageFlip, but
+ // using one atomic request breaks down here since we get events
+ // with the same user_data passed to drmModeAtomicCommit. Until
+ // this gets reworked (multiple requests?) screen cloning is not
+ // compatible with atomic.
#endif
} else {
int ret = drmModePageFlip(fd,
@@ -343,16 +449,24 @@ void QEglFSKmsGbmScreen::flip()
DRM_MODE_PAGE_FLIP_EVENT,
d.screen);
if (ret) {
- qErrnoWarning("Could not queue DRM page flip for clone screen %s", qPrintable(name()));
+ qErrnoWarning("Could not queue DRM page flip for screen %s (clones screen %s)",
+ qPrintable(d.screen->name()),
+ qPrintable(name()));
d.cloneFlipPending = false;
}
}
}
}
+ if (device()->hasAtomicSupport()) {
#if QT_CONFIG(drm_atomic)
- device()->threadLocalAtomicCommit(this);
+ if (!device()->threadLocalAtomicCommit(this)) {
+ return;
+ }
#endif
+ }
+
+ gbmRelease.dismiss();
}
void QEglFSKmsGbmScreen::flipFinished()
@@ -379,19 +493,23 @@ void QEglFSKmsGbmScreen::cloneDestFlipFinished(QEglFSKmsGbmScreen *cloneDestScre
void QEglFSKmsGbmScreen::updateFlipStatus()
{
- Q_ASSERT(!m_cloneSource);
+ // only for 'real' outputs that own the color buffer, i.e. that are not cloning another one
+ if (m_cloneSource)
+ return;
+ // proceed only if flips for both this and all others that clone this have finished
if (m_flipPending)
return;
- for (const CloneDestination &d : qAsConst(m_cloneDests)) {
+ for (const CloneDestination &d : std::as_const(m_cloneDests)) {
if (d.cloneFlipPending)
return;
}
- if (m_gbm_bo_current)
+ if (m_gbm_bo_current) {
gbm_surface_release_buffer(m_gbm_surface,
m_gbm_bo_current);
+ }
m_gbm_bo_current = m_gbm_bo_next;
m_gbm_bo_next = nullptr;
diff --git a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmscreen_p.h b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmscreen_p.h
index d2882038bd..aca34fcae2 100644
--- a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmscreen_p.h
+++ b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms/qeglfskmsgbmscreen_p.h
@@ -52,6 +52,12 @@ protected:
void flipFinished();
void ensureModeSet(uint32_t fb);
void cloneDestFlipFinished(QEglFSKmsGbmScreen *cloneDestScreen);
+ void waitForFlipWithEventReader(QEglFSKmsGbmScreen *screen);
+ static void nonThreadedPageFlipHandler(int fd,
+ unsigned int sequence,
+ unsigned int tv_sec,
+ unsigned int tv_usec,
+ void *user_data);
gbm_surface *m_gbm_surface;
@@ -61,6 +67,7 @@ protected:
QMutex m_flipMutex;
QWaitCondition m_flipCond;
+ static QMutex m_nonThreadedFlipMutex;
QScopedPointer<QEglFSKmsGbmCursor> m_cursor;
@@ -76,6 +83,8 @@ protected:
bool cloneFlipPending = false;
};
QList<CloneDestination> m_cloneDests;
+
+ bool needsNewModeSetForNextFb = false;
};
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_egldevice/CMakeLists.txt b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_egldevice/CMakeLists.txt
index 5567b14e77..3c3e5a8a6c 100644
--- a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_egldevice/CMakeLists.txt
+++ b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_egldevice/CMakeLists.txt
@@ -1,7 +1,5 @@
# Copyright (C) 2022 The Qt Company Ltd.
-# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-
-# Generated from eglfs_kms_egldevice.pro.
+# SPDX-License-Identifier: BSD-3-Clause
#####################################################################
## QEglFSKmsEglDeviceIntegrationPlugin Plugin:
@@ -27,6 +25,3 @@ qt_internal_add_plugin(QEglFSKmsEglDeviceIntegrationPlugin
Qt::GuiPrivate
Qt::KmsSupportPrivate
)
-
-#### Keys ignored in scope 1:.:.:eglfs_kms_egldevice.pro:<TRUE>:
-# OTHER_FILES = "$$PWD/eglfs_kms_egldevice.json"
diff --git a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_egldevice/qeglfskmsegldeviceintegration.cpp b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_egldevice/qeglfskmsegldeviceintegration.cpp
index 3a79ebb7b4..a213bc9bba 100644
--- a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_egldevice/qeglfskmsegldeviceintegration.cpp
+++ b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_egldevice/qeglfskmsegldeviceintegration.cpp
@@ -38,8 +38,11 @@ EGLDisplay QEglFSKmsEglDeviceIntegration::createDisplay(EGLNativeDisplayType nat
EGLDisplay display;
+ EGLint egldevice_fd = device()->fd();
+
+ const EGLint attribs[] = { EGL_DRM_MASTER_FD_EXT, egldevice_fd, EGL_NONE };
if (m_funcs->has_egl_platform_device) {
- display = m_funcs->get_platform_display(EGL_PLATFORM_DEVICE_EXT, nativeDisplay, nullptr);
+ display = m_funcs->get_platform_display(EGL_PLATFORM_DEVICE_EXT, nativeDisplay, attribs);
} else {
qWarning("EGL_EXT_platform_device not available, falling back to legacy path!");
display = eglGetDisplay(nativeDisplay);
@@ -168,7 +171,7 @@ void QEglFSKmsEglDeviceWindow::resetSurface()
QByteArray reqLayerIndex = qgetenv("QT_QPA_EGLFS_LAYER_INDEX");
if (!reqLayerIndex.isEmpty()) {
int idx = reqLayerIndex.toInt();
- if (idx >= 0 && idx < layers.count()) {
+ if (idx >= 0 && idx < layers.size()) {
qCDebug(qLcEglfsKmsDebug, "EGLOutput layer index override = %d", idx);
layer = layers[idx];
}
diff --git a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_egldevice/qeglfskmsegldevicescreen.cpp b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_egldevice/qeglfskmsegldevicescreen.cpp
index eadb079f70..3fc46cd224 100644
--- a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_egldevice/qeglfskmsegldevicescreen.cpp
+++ b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_egldevice/qeglfskmsegldevicescreen.cpp
@@ -13,12 +13,63 @@ Q_DECLARE_LOGGING_CATEGORY(qLcEglfsKmsDebug)
QEglFSKmsEglDeviceScreen::QEglFSKmsEglDeviceScreen(QEglFSKmsDevice *device, const QKmsOutput &output)
: QEglFSKmsScreen(device, output)
+ , m_default_fb_handle(uint32_t(-1))
+ , m_default_fb_id(uint32_t(-1))
{
+ const int fd = device->fd();
+
+ struct drm_mode_create_dumb createRequest;
+ createRequest.width = output.size.width();
+ createRequest.height = output.size.height();
+ createRequest.bpp = 32;
+ createRequest.flags = 0;
+
+ qCDebug(qLcEglfsKmsDebug, "Creating dumb fb %dx%d", createRequest.width, createRequest.height);
+
+ int ret = drmIoctl(fd, DRM_IOCTL_MODE_CREATE_DUMB, &createRequest);
+ if (ret < 0)
+ qFatal("Unable to create dumb buffer.\n");
+
+ m_default_fb_handle = createRequest.handle;
+
+ uint32_t handles[4] = { 0, 0, 0, 0 };
+ uint32_t pitches[4] = { 0, 0, 0, 0 };
+ uint32_t offsets[4] = { 0, 0, 0, 0 };
+
+ handles[0] = createRequest.handle;
+ pitches[0] = createRequest.pitch;
+ offsets[0] = 0;
+
+ ret = drmModeAddFB2(fd, createRequest.width, createRequest.height, DRM_FORMAT_ARGB8888, handles,
+ pitches, offsets, &m_default_fb_id, 0);
+ if (ret)
+ qFatal("Unable to add fb\n");
+
+ qCDebug(qLcEglfsKmsDebug, "Added dumb fb %dx%d handle:%u pitch:%d id:%u", createRequest.width, createRequest.height,
+ createRequest.handle, createRequest.pitch, m_default_fb_id);
}
QEglFSKmsEglDeviceScreen::~QEglFSKmsEglDeviceScreen()
{
- const int remainingScreenCount = qGuiApp->screens().count();
+ int ret;
+ const int fd = device()->fd();
+
+ if (m_default_fb_id != uint32_t(-1)) {
+ ret = drmModeRmFB(fd, m_default_fb_id);
+ if (ret)
+ qErrnoWarning("drmModeRmFB failed");
+ }
+
+ if (m_default_fb_handle != uint32_t(-1)) {
+ struct drm_mode_destroy_dumb destroyRequest;
+ destroyRequest.handle = m_default_fb_handle;
+
+ ret = drmIoctl(fd, DRM_IOCTL_MODE_DESTROY_DUMB, &destroyRequest);
+ if (ret)
+ qErrnoWarning("DRM_IOCTL_MODE_DESTROY_DUMB failed");
+ }
+
+ const int remainingScreenCount = qGuiApp->screens().size();
qCDebug(qLcEglfsKmsDebug, "Screen dtor. Remaining screens: %d", remainingScreenCount);
if (!remainingScreenCount && !device()->screenConfig()->separateScreens())
static_cast<QEglFSKmsEglDevice *>(device())->destroyGlobalCursor();
@@ -53,8 +104,11 @@ void QEglFSKmsEglDeviceScreen::waitForFlip()
if (alreadySet) {
// Maybe detecting the DPMS mode could help here, but there are no properties
// exposed on the connector apparently. So rely on an env var for now.
- static bool alwaysDoSet = qEnvironmentVariableIntValue("QT_QPA_EGLFS_ALWAYS_SET_MODE");
- if (!alwaysDoSet) {
+ // Note that typically, we need to set crtc with the default fb even if the
+ // mode did not change, unless QT_QPA_EGLFS_ALWAYS_SET_MODE is explicitly specified.
+ static bool envVarSet = false;
+ static bool alwaysDoSet = qEnvironmentVariableIntValue("QT_QPA_EGLFS_ALWAYS_SET_MODE", &envVarSet);
+ if (envVarSet && !alwaysDoSet) {
qCDebug(qLcEglfsKmsDebug, "Mode already set");
return;
}
@@ -62,7 +116,7 @@ void QEglFSKmsEglDeviceScreen::waitForFlip()
qCDebug(qLcEglfsKmsDebug, "Setting mode");
int ret = drmModeSetCrtc(fd, op.crtc_id,
- uint32_t(-1), 0, 0,
+ m_default_fb_id, 0, 0,
&op.connector_id, 1,
&op.modes[op.mode]);
if (ret)
@@ -74,7 +128,7 @@ void QEglFSKmsEglDeviceScreen::waitForFlip()
if (op.wants_forced_plane) {
qCDebug(qLcEglfsKmsDebug, "Setting plane %u", op.forced_plane_id);
- int ret = drmModeSetPlane(fd, op.forced_plane_id, op.crtc_id, uint32_t(-1), 0,
+ int ret = drmModeSetPlane(fd, op.forced_plane_id, op.crtc_id, m_default_fb_id, 0,
0, 0, w, h,
0 << 16, 0 << 16, op.size.width() << 16, op.size.height() << 16);
if (ret == -1)
diff --git a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_egldevice/qeglfskmsegldevicescreen.h b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_egldevice/qeglfskmsegldevicescreen.h
index 884a70093a..8779499bef 100644
--- a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_egldevice/qeglfskmsegldevicescreen.h
+++ b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_egldevice/qeglfskmsegldevicescreen.h
@@ -17,6 +17,9 @@ public:
QPlatformCursor *cursor() const override;
void waitForFlip() override;
+private:
+ uint32_t m_default_fb_handle;
+ uint32_t m_default_fb_id;
};
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_support/CMakeLists.txt b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_support/CMakeLists.txt
index d342d41121..5fcc0e1b18 100644
--- a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_support/CMakeLists.txt
+++ b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_support/CMakeLists.txt
@@ -1,7 +1,5 @@
# Copyright (C) 2022 The Qt Company Ltd.
-# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-
-# Generated from eglfs_kms_support.pro.
+# SPDX-License-Identifier: BSD-3-Clause
#####################################################################
## EglFsKmsSupportPrivate Module:
@@ -26,4 +24,5 @@ qt_internal_add_module(EglFsKmsSupportPrivate
Qt::Gui
Qt::GuiPrivate
Qt::KmsSupportPrivate
+ NO_GENERATE_CPP_EXPORTS
)
diff --git a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_support/qeglfskmsdevice_p.h b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_support/qeglfskmsdevice_p.h
index 7dc5235d33..6e11953a69 100644
--- a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_support/qeglfskmsdevice_p.h
+++ b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_support/qeglfskmsdevice_p.h
@@ -17,7 +17,6 @@
//
#include "private/qeglfsglobal_p.h"
-#include "qeglfskmseventreader_p.h"
#include <QtKmsSupport/private/qkmsdevice_p.h>
QT_BEGIN_NAMESPACE
@@ -31,11 +30,6 @@ public:
bool isPrimary,
const QPoint &virtualPos,
const QList<QPlatformScreen *> &virtualSiblings) override;
-
- QEglFSKmsEventReader *eventReader() { return &m_eventReader; }
-
-protected:
- QEglFSKmsEventReader m_eventReader;
};
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_support/qeglfskmsscreen.cpp b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_support/qeglfskmsscreen.cpp
index cc52d371e3..369356b761 100644
--- a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_support/qeglfskmsscreen.cpp
+++ b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_support/qeglfskmsscreen.cpp
@@ -185,7 +185,7 @@ QList<QPlatformScreen::Mode> QEglFSKmsScreen::modes() const
QList<QPlatformScreen::Mode> list;
list.reserve(m_output.modes.size());
- for (const drmModeModeInfo &info : qAsConst(m_output.modes))
+ for (const drmModeModeInfo &info : std::as_const(m_output.modes))
list.append({QSize(info.hdisplay, info.vdisplay),
qreal(info.vrefresh > 0 ? info.vrefresh : 60)});
diff --git a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_vsp2/qlinuxmediadevice.cpp b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_vsp2/qlinuxmediadevice.cpp
index 44555b85b5..5d2900097e 100644
--- a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_vsp2/qlinuxmediadevice.cpp
+++ b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_vsp2/qlinuxmediadevice.cpp
@@ -200,7 +200,7 @@ bool QLinuxMediaDevice::resetLinks()
struct media_link *QLinuxMediaDevice::parseLink(const QString &link)
{
- char *endp = nullptr;;
+ char *endp = nullptr;
struct media_link *mediaLink = media_parse_link(m_mediaDevice, link.toStdString().c_str(), &endp);
if (!mediaLink)
diff --git a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_vsp2/qvsp2blendingdevice.cpp b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_vsp2/qvsp2blendingdevice.cpp
index 40cdd78e56..e7e3c76190 100644
--- a/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_vsp2/qvsp2blendingdevice.cpp
+++ b/src/plugins/platforms/eglfs/deviceintegration/eglfs_kms_vsp2/qvsp2blendingdevice.cpp
@@ -205,7 +205,7 @@ bool QVsp2BlendingDevice::blend(int outputDmabufFd)
if (queueingFailed) {
qWarning() << "Vsp2: Trying to clean up queued buffers";
- for (auto &input : qAsConst(m_inputs)) {
+ for (auto &input : std::as_const(m_inputs)) {
if (input.enabled) {
if (!input.rpfInput->clearBuffers())
qWarning() << "Vsp2: Failed to remove buffers after an aborted blend";
@@ -248,7 +248,7 @@ int QVsp2BlendingDevice::numInputs() const
bool QVsp2BlendingDevice::hasContinuousLayers() const
{
bool seenDisabled = false;
- for (auto &input : qAsConst(m_inputs)) {
+ for (auto &input : std::as_const(m_inputs)) {
if (seenDisabled && input.enabled)
return false;
seenDisabled |= !input.enabled;
diff --git a/src/plugins/platforms/eglfs/deviceintegration/eglfs_mali/CMakeLists.txt b/src/plugins/platforms/eglfs/deviceintegration/eglfs_mali/CMakeLists.txt
index c919b655fa..38981f87b9 100644
--- a/src/plugins/platforms/eglfs/deviceintegration/eglfs_mali/CMakeLists.txt
+++ b/src/plugins/platforms/eglfs/deviceintegration/eglfs_mali/CMakeLists.txt
@@ -1,7 +1,5 @@
# Copyright (C) 2022 The Qt Company Ltd.
-# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-
-# Generated from eglfs_mali.pro.
+# SPDX-License-Identifier: BSD-3-Clause
#####################################################################
## QEglFSMaliIntegrationPlugin Plugin:
@@ -24,6 +22,3 @@ qt_internal_add_plugin(QEglFSMaliIntegrationPlugin
Qt::Gui
Qt::GuiPrivate
)
-
-#### Keys ignored in scope 1:.:.:eglfs_mali.pro:<TRUE>:
-# OTHER_FILES = "$$PWD/eglfs_mali.json"
diff --git a/src/plugins/platforms/eglfs/deviceintegration/eglfs_openwfd/CMakeLists.txt b/src/plugins/platforms/eglfs/deviceintegration/eglfs_openwfd/CMakeLists.txt
index 385aaaef25..7e2362e627 100644
--- a/src/plugins/platforms/eglfs/deviceintegration/eglfs_openwfd/CMakeLists.txt
+++ b/src/plugins/platforms/eglfs/deviceintegration/eglfs_openwfd/CMakeLists.txt
@@ -1,7 +1,5 @@
# Copyright (C) 2022 The Qt Company Ltd.
-# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-
-# Generated from openwfd.pro.
+# SPDX-License-Identifier: BSD-3-Clause
#####################################################################
## QEglFSOpenWFDIntegrationPlugin Plugin:
@@ -22,6 +20,3 @@ qt_internal_add_plugin(QEglFSOpenWFDIntegrationPlugin
Qt::Gui
Qt::GuiPrivate
)
-
-#### Keys ignored in scope 1:.:.:openwfd.pro:<TRUE>:
-# OTHER_FILES = "$$PWD/eglfs_openwfd.json"
diff --git a/src/plugins/platforms/eglfs/deviceintegration/eglfs_viv/CMakeLists.txt b/src/plugins/platforms/eglfs/deviceintegration/eglfs_viv/CMakeLists.txt
index 836461f9a4..01defc9242 100644
--- a/src/plugins/platforms/eglfs/deviceintegration/eglfs_viv/CMakeLists.txt
+++ b/src/plugins/platforms/eglfs/deviceintegration/eglfs_viv/CMakeLists.txt
@@ -1,7 +1,5 @@
# Copyright (C) 2022 The Qt Company Ltd.
-# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-
-# Generated from eglfs_viv.pro.
+# SPDX-License-Identifier: BSD-3-Clause
#####################################################################
## QEglFSVivIntegrationPlugin Plugin:
@@ -25,6 +23,3 @@ qt_internal_add_plugin(QEglFSVivIntegrationPlugin
Qt::Gui
Qt::GuiPrivate
)
-
-#### Keys ignored in scope 1:.:.:eglfs_viv.pro:<TRUE>:
-# OTHER_FILES = "$$PWD/eglfs_viv.json"
diff --git a/src/plugins/platforms/eglfs/deviceintegration/eglfs_viv_wl/CMakeLists.txt b/src/plugins/platforms/eglfs/deviceintegration/eglfs_viv_wl/CMakeLists.txt
index f7e4f7a164..6052e98ab8 100644
--- a/src/plugins/platforms/eglfs/deviceintegration/eglfs_viv_wl/CMakeLists.txt
+++ b/src/plugins/platforms/eglfs/deviceintegration/eglfs_viv_wl/CMakeLists.txt
@@ -1,7 +1,5 @@
# Copyright (C) 2022 The Qt Company Ltd.
-# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-
-# Generated from eglfs_viv_wl.pro.
+# SPDX-License-Identifier: BSD-3-Clause
#####################################################################
## QEglFSVivWaylandIntegrationPlugin Plugin:
@@ -26,6 +24,3 @@ qt_internal_add_plugin(QEglFSVivWaylandIntegrationPlugin
Qt::Gui
Qt::GuiPrivate
)
-
-#### Keys ignored in scope 1:.:.:eglfs_viv_wl.pro:<TRUE>:
-# OTHER_FILES = "$$PWD/eglfs_viv_wl.json"
diff --git a/src/plugins/platforms/eglfs/deviceintegration/eglfs_x11/CMakeLists.txt b/src/plugins/platforms/eglfs/deviceintegration/eglfs_x11/CMakeLists.txt
index b4bdf770ff..9dde3090c8 100644
--- a/src/plugins/platforms/eglfs/deviceintegration/eglfs_x11/CMakeLists.txt
+++ b/src/plugins/platforms/eglfs/deviceintegration/eglfs_x11/CMakeLists.txt
@@ -1,7 +1,5 @@
# Copyright (C) 2022 The Qt Company Ltd.
-# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-
-# Generated from eglfs_x11.pro.
+# SPDX-License-Identifier: BSD-3-Clause
#####################################################################
## QEglFSX11IntegrationPlugin Plugin:
@@ -26,7 +24,5 @@ qt_internal_add_plugin(QEglFSX11IntegrationPlugin
X11::X11
X11::XCB
XCB::XCB
+ NO_UNITY_BUILD # X11 define clashes
)
-
-#### Keys ignored in scope 1:.:.:eglfs_x11.pro:<TRUE>:
-# OTHER_FILES = "$$PWD/eglfs_x11.json"
diff --git a/src/plugins/platforms/eglfs/deviceintegration/eglfs_x11/qeglfsx11integration.cpp b/src/plugins/platforms/eglfs/deviceintegration/eglfs_x11/qeglfsx11integration.cpp
index 39ec35df9e..8f491274d5 100644
--- a/src/plugins/platforms/eglfs/deviceintegration/eglfs_x11/qeglfsx11integration.cpp
+++ b/src/plugins/platforms/eglfs/deviceintegration/eglfs_x11/qeglfsx11integration.cpp
@@ -25,7 +25,7 @@ private:
QEglFSX11Integration *m_integration;
};
-static QBasicAtomicInt running;
+Q_CONSTINIT static QBasicAtomicInt running = Q_BASIC_ATOMIC_INITIALIZER(0);
void EventReader::run()
{
@@ -115,7 +115,7 @@ QSize QEglFSX11Integration::screenSize() const
{
if (m_screenSize.isEmpty()) {
QList<QByteArray> env = qgetenv("EGLFS_X11_SIZE").split('x');
- if (env.length() == 2) {
+ if (env.size() == 2) {
m_screenSize = QSize(env.at(0).toInt(), env.at(1).toInt());
} else {
XWindowAttributes a;
diff --git a/src/plugins/platforms/haiku/qhaikuintegration.cpp b/src/plugins/platforms/haiku/qhaikuintegration.cpp
index 7a4ecbfcbf..2b0672e363 100644
--- a/src/plugins/platforms/haiku/qhaikuintegration.cpp
+++ b/src/plugins/platforms/haiku/qhaikuintegration.cpp
@@ -20,6 +20,8 @@
QT_BEGIN_NAMESPACE
+using namespace Qt::Literals::StringLiterals;
+
static long int startApplicationThread(void *data)
{
QHaikuApplication *app = static_cast<QHaikuApplication*>(data);
@@ -32,7 +34,7 @@ QHaikuIntegration::QHaikuIntegration(const QStringList &parameters)
{
Q_UNUSED(parameters);
- const QString signature = QStringLiteral("application/x-vnd.Qt.%1").arg(QFileInfo(QCoreApplication::applicationFilePath()).fileName());
+ const QString signature = "application/x-vnd.Qt.%1"_L1.arg(QFileInfo(QCoreApplication::applicationFilePath()).fileName());
QHaikuApplication *app = new QHaikuApplication(signature.toLocal8Bit());
be_app = app;
diff --git a/src/plugins/platforms/haiku/qhaikuwindow.cpp b/src/plugins/platforms/haiku/qhaikuwindow.cpp
index 21bcad1749..3f2c6a889a 100644
--- a/src/plugins/platforms/haiku/qhaikuwindow.cpp
+++ b/src/plugins/platforms/haiku/qhaikuwindow.cpp
@@ -294,7 +294,7 @@ void QHaikuWindow::haikuWindowResized(const QSize &size, bool zoomInProgress)
void QHaikuWindow::haikuWindowActivated(bool activated)
{
- QWindowSystemInterface::handleWindowActivated(activated ? window() : nullptr);
+ QWindowSystemInterface::handleFocusWindowChanged(activated ? window() : nullptr);
}
void QHaikuWindow::haikuWindowMinimized(bool minimize)
diff --git a/src/plugins/platforms/ios/CMakeLists.txt b/src/plugins/platforms/ios/CMakeLists.txt
index d67239fee5..1c99103a49 100644
--- a/src/plugins/platforms/ios/CMakeLists.txt
+++ b/src/plugins/platforms/ios/CMakeLists.txt
@@ -1,7 +1,5 @@
# Copyright (C) 2022 The Qt Company Ltd.
-# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-
-# Generated from ios.pro.
+# SPDX-License-Identifier: BSD-3-Clause
#####################################################################
## QIOSIntegrationPlugin Plugin:
@@ -9,13 +7,13 @@
qt_internal_add_plugin(QIOSIntegrationPlugin
OUTPUT_NAME qios
- DEFAULT_IF ${QT_QPA_DEFAULT_PLATFORM} MATCHES ios # special case
+ STATIC # Force static, even in shared builds
+ DEFAULT_IF ${QT_QPA_DEFAULT_PLATFORM} MATCHES ios
PLUGIN_TYPE platforms
SOURCES
plugin.mm
qiosapplicationdelegate.h qiosapplicationdelegate.mm
qiosapplicationstate.h qiosapplicationstate.mm
- qioscontext.h qioscontext.mm
qioseventdispatcher.h qioseventdispatcher.mm
qiosglobal.h qiosglobal.mm
qiosinputcontext.h qiosinputcontext.mm
@@ -30,26 +28,31 @@ qt_internal_add_plugin(QIOSIntegrationPlugin
quiaccessibilityelement.h quiaccessibilityelement.mm
quiview.h quiview.mm
uistrings_p.h uistrings.cpp
+ NO_PCH_SOURCES
+ qioscontext.mm # undef QT_NO_FOREACH
+ qiosintegration.mm # undef QT_NO_FOREACH
+ qiosplatformaccessibility.mm # undef QT_NO_FOREACH
+ qiosscreen.mm # undef QT_NO_FOREACH
LIBRARIES
${FWAudioToolbox}
${FWFoundation}
${FWMetal}
${FWQuartzCore}
${FWUIKit}
+ ${FWCoreGraphics}
Qt::CorePrivate
Qt::GuiPrivate
)
-# special case begin
qt_disable_apple_app_extension_api_only(QIOSIntegrationPlugin)
-# special case end
-#### Keys ignored in scope 2:.:.:kernel.pro:<TRUE>:
-# OTHER_FILES = "quiview_textinput.mm" "quiview_accessibility.mm"
## Scopes:
#####################################################################
+qt_internal_find_apple_system_framework(FWUniformTypeIdentifiers UniformTypeIdentifiers)
qt_internal_extend_target(QIOSIntegrationPlugin CONDITION QT_FEATURE_opengl
+ SOURCES
+ qioscontext.h qioscontext.mm
LIBRARIES
Qt::OpenGLPrivate
)
@@ -59,13 +62,15 @@ qt_internal_extend_target(QIOSIntegrationPlugin CONDITION NOT TVOS
qiosclipboard.h qiosclipboard.mm
qiosdocumentpickercontroller.h qiosdocumentpickercontroller.mm
qiosfiledialog.h qiosfiledialog.mm
+ qioscolordialog.h qioscolordialog.mm
+ qiosfontdialog.h qiosfontdialog.mm
qiosmenu.h qiosmenu.mm
qiosmessagedialog.h qiosmessagedialog.mm
qiostextinputoverlay.h qiostextinputoverlay.mm
LIBRARIES
${FWAssetsLibrary}
+ ${FWUniformTypeIdentifiers}
+ ${FWPhotos}
)
-#### Keys ignored in scope 6:.:.:kernel.pro:NOT TARGET___equals____ss_QT_DEFAULT_QPA_PLUGIN:
-# PLUGIN_EXTENDS = "-"
add_subdirectory(optional)
diff --git a/src/plugins/platforms/ios/optional/CMakeLists.txt b/src/plugins/platforms/ios/optional/CMakeLists.txt
index 72f61c7473..a01d7a6441 100644
--- a/src/plugins/platforms/ios/optional/CMakeLists.txt
+++ b/src/plugins/platforms/ios/optional/CMakeLists.txt
@@ -1,7 +1,5 @@
# Copyright (C) 2022 The Qt Company Ltd.
-# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-
-# Generated from optional.pro.
+# SPDX-License-Identifier: BSD-3-Clause
if(IOS)
add_subdirectory(nsphotolibrarysupport)
diff --git a/src/plugins/platforms/ios/optional/nsphotolibrarysupport/CMakeLists.txt b/src/plugins/platforms/ios/optional/nsphotolibrarysupport/CMakeLists.txt
index 45f6e588d5..663878bde7 100644
--- a/src/plugins/platforms/ios/optional/nsphotolibrarysupport/CMakeLists.txt
+++ b/src/plugins/platforms/ios/optional/nsphotolibrarysupport/CMakeLists.txt
@@ -1,7 +1,5 @@
# Copyright (C) 2022 The Qt Company Ltd.
-# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-
-# Generated from nsphotolibrarysupport.pro.
+# SPDX-License-Identifier: BSD-3-Clause
#####################################################################
## QIosOptionalPlugin_NSPhotoLibrary Plugin:
@@ -9,6 +7,7 @@
qt_internal_add_plugin(QIosOptionalPlugin_NSPhotoLibraryPlugin
OUTPUT_NAME qiosnsphotolibrarysupport
+ STATIC # Force static, even in shared builds
PLUGIN_TYPE platforms/darwin
CLASS_NAME QIosOptionalPlugin_NSPhotoLibrary
DEFAULT_IF FALSE
@@ -26,17 +25,7 @@ qt_internal_add_plugin(QIosOptionalPlugin_NSPhotoLibraryPlugin
Qt::GuiPrivate
)
-# special case begin
set_target_properties(QIosOptionalPlugin_NSPhotoLibraryPlugin
PROPERTIES
DISABLE_PRECOMPILE_HEADERS ON
)
-# special case end
-
-
-#### Keys ignored in scope 1:.:.:nsphotolibrarysupport.pro:<TRUE>:
-# OTHER_FILES = "plugin.json"
-# PLUGIN_EXTENDS = "-"
-
-## Scopes:
-#####################################################################
diff --git a/src/plugins/platforms/ios/optional/nsphotolibrarysupport/qiosfileengineassetslibrary.h b/src/plugins/platforms/ios/optional/nsphotolibrarysupport/qiosfileengineassetslibrary.h
index 90b62af56d..0ad54a9e11 100644
--- a/src/plugins/platforms/ios/optional/nsphotolibrarysupport/qiosfileengineassetslibrary.h
+++ b/src/plugins/platforms/ios/optional/nsphotolibrarysupport/qiosfileengineassetslibrary.h
@@ -29,8 +29,8 @@ public:
void setFileName(const QString &file) override;
#ifndef QT_NO_FILESYSTEMITERATOR
- Iterator *beginEntryList(QDir::Filters filters, const QStringList &filterNames) override;
- Iterator *endEntryList() override;
+ IteratorUniquePtr beginEntryList(const QString &path, QDir::Filters filters,
+ const QStringList &filterNames) override;
#endif
void setError(QFile::FileError error, const QString &str) { QAbstractFileEngine::setError(error, str); }
diff --git a/src/plugins/platforms/ios/optional/nsphotolibrarysupport/qiosfileengineassetslibrary.mm b/src/plugins/platforms/ios/optional/nsphotolibrarysupport/qiosfileengineassetslibrary.mm
index 0ca911f68b..f7e112ab81 100644
--- a/src/plugins/platforms/ios/optional/nsphotolibrarysupport/qiosfileengineassetslibrary.mm
+++ b/src/plugins/platforms/ios/optional/nsphotolibrarysupport/qiosfileengineassetslibrary.mm
@@ -11,6 +11,8 @@
#include <QtCore/qurl.h>
#include <QtCore/qset.h>
#include <QtCore/qthreadstorage.h>
+#include <QtCore/qfileselector.h>
+#include <QtCore/qpointer.h>
QT_BEGIN_NAMESPACE
@@ -256,8 +258,8 @@ public:
QIOSAssetEnumerator *m_enumerator;
QIOSFileEngineIteratorAssetsLibrary(
- QDir::Filters filters, const QStringList &nameFilters)
- : QAbstractFileEngineIterator(filters, nameFilters)
+ const QString &path, QDir::Filters filters, const QStringList &nameFilters)
+ : QAbstractFileEngineIterator(path, filters, nameFilters)
, m_enumerator(new QIOSAssetEnumerator([[[ALAssetsLibrary alloc] init] autorelease], ALAssetsGroupAll))
{
}
@@ -268,8 +270,11 @@ public:
g_iteratorCurrentUrl.setLocalData(QString());
}
- QString next() override
+ bool advance() override
{
+ if (!m_enumerator->hasNext())
+ return false;
+
// Cache the URL that we are about to return, since QDir will immediately create a
// new file engine on the file and ask if it exists. Unless we do this, we end up
// creating a new ALAsset just to verify its existence, which will be especially
@@ -277,12 +282,7 @@ public:
ALAsset *asset = m_enumerator->next();
QString url = QUrl::fromNSURL([asset valueForProperty:ALAssetPropertyAssetURL]).toString();
g_iteratorCurrentUrl.setLocalData(url);
- return url;
- }
-
- bool hasNext() const override
- {
- return m_enumerator->hasNext();
+ return true;
}
QString currentFileName() const override
@@ -344,6 +344,17 @@ QAbstractFileEngine::FileFlags QIOSFileEngineAssetsLibrary::fileFlags(QAbstractF
{
QAbstractFileEngine::FileFlags flags;
const bool isDir = (m_assetUrl == "assets-library://"_L1);
+ if (!isDir) {
+ static const QFileSelector fileSelector;
+ static const auto selectors = fileSelector.allSelectors();
+ if (m_assetUrl.startsWith("assets-library://"_L1)) {
+ for (const auto &selector : selectors) {
+ if (m_assetUrl.endsWith(selector))
+ return flags;
+ }
+ }
+ }
+
const bool exists = isDir || m_assetUrl == g_iteratorCurrentUrl.localData() || loadAsset();
if (!exists)
@@ -427,15 +438,11 @@ void QIOSFileEngineAssetsLibrary::setFileName(const QString &file)
#ifndef QT_NO_FILESYSTEMITERATOR
-QAbstractFileEngine::Iterator *QIOSFileEngineAssetsLibrary::beginEntryList(
- QDir::Filters filters, const QStringList &filterNames)
-{
- return new QIOSFileEngineIteratorAssetsLibrary(filters, filterNames);
-}
-
-QAbstractFileEngine::Iterator *QIOSFileEngineAssetsLibrary::endEntryList()
+QAbstractFileEngine::IteratorUniquePtr
+QIOSFileEngineAssetsLibrary::beginEntryList(
+ const QString &path, QDir::Filters filters, const QStringList &filterNames)
{
- return 0;
+ return std::make_unique<QIOSFileEngineIteratorAssetsLibrary>(path, filters, filterNames);
}
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/ios/optional/nsphotolibrarysupport/qiosfileenginefactory.h b/src/plugins/platforms/ios/optional/nsphotolibrarysupport/qiosfileenginefactory.h
index caa852df9f..136716f792 100644
--- a/src/plugins/platforms/ios/optional/nsphotolibrarysupport/qiosfileenginefactory.h
+++ b/src/plugins/platforms/ios/optional/nsphotolibrarysupport/qiosfileenginefactory.h
@@ -13,18 +13,18 @@ QT_BEGIN_NAMESPACE
class QIOSFileEngineFactory : public QAbstractFileEngineHandler
{
public:
- QAbstractFileEngine* create(const QString &fileName) const
+ std::unique_ptr<QAbstractFileEngine> create(const QString &fileName) const
{
- static QLatin1StringView assetsScheme("assets-library:");
+ Q_CONSTINIT static QLatin1StringView assetsScheme("assets-library:");
#ifndef Q_OS_TVOS
if (fileName.toLower().startsWith(assetsScheme))
- return new QIOSFileEngineAssetsLibrary(fileName);
+ return std::make_unique<QIOSFileEngineAssetsLibrary>(fileName);
#else
Q_UNUSED(fileName);
#endif
- return 0;
+ return {};
}
};
diff --git a/src/plugins/platforms/ios/qiosclipboard.mm b/src/plugins/platforms/ios/qiosclipboard.mm
index 554c3ca118..de8ab69dff 100644
--- a/src/plugins/platforms/ios/qiosclipboard.mm
+++ b/src/plugins/platforms/ios/qiosclipboard.mm
@@ -6,22 +6,11 @@
#ifndef QT_NO_CLIPBOARD
#include <QtCore/qurl.h>
-#include <QtGui/private/qmacmime_p.h>
+#include <QtGui/private/qmacmimeregistry_p.h>
+#include <QtGui/qutimimeconverter.h>
#include <QtCore/QMimeData>
#include <QtGui/QGuiApplication>
-@interface UIPasteboard (QUIPasteboard)
-+ (instancetype)pasteboardWithQClipboardMode:(QClipboard::Mode)mode;
-@end
-
-@implementation UIPasteboard (QUIPasteboard)
-+ (instancetype)pasteboardWithQClipboardMode:(QClipboard::Mode)mode
-{
- NSString *name = (mode == QClipboard::Clipboard) ? UIPasteboardNameGeneral : UIPasteboardNameFind;
- return [UIPasteboard pasteboardWithName:name create:NO];
-}
-@end
-
// --------------------------------------------------------------------
@interface QUIClipboard : NSObject
@@ -30,7 +19,6 @@
@implementation QUIClipboard {
QIOSClipboard *m_qiosClipboard;
NSInteger m_changeCountClipboard;
- NSInteger m_changeCountFindBuffer;
}
- (instancetype)initWithQIOSClipboard:(QIOSClipboard *)qiosClipboard
@@ -38,8 +26,7 @@
self = [super init];
if (self) {
m_qiosClipboard = qiosClipboard;
- m_changeCountClipboard = [UIPasteboard pasteboardWithQClipboardMode:QClipboard::Clipboard].changeCount;
- m_changeCountFindBuffer = [UIPasteboard pasteboardWithQClipboardMode:QClipboard::FindBuffer].changeCount;
+ m_changeCountClipboard = UIPasteboard.generalPasteboard.changeCount;
[[NSNotificationCenter defaultCenter]
addObserver:self
@@ -76,18 +63,12 @@
- (void)updatePasteboardChanged:(NSNotification *)notification
{
Q_UNUSED(notification);
- NSInteger changeCountClipboard = [UIPasteboard pasteboardWithQClipboardMode:QClipboard::Clipboard].changeCount;
- NSInteger changeCountFindBuffer = [UIPasteboard pasteboardWithQClipboardMode:QClipboard::FindBuffer].changeCount;
+ NSInteger changeCountClipboard = UIPasteboard.generalPasteboard.changeCount;
if (m_changeCountClipboard != changeCountClipboard) {
m_changeCountClipboard = changeCountClipboard;
m_qiosClipboard->emitChanged(QClipboard::Clipboard);
}
-
- if (m_changeCountFindBuffer != changeCountFindBuffer) {
- m_changeCountFindBuffer = changeCountFindBuffer;
- m_qiosClipboard->emitChanged(QClipboard::FindBuffer);
- }
}
@end
@@ -98,25 +79,22 @@ QT_BEGIN_NAMESPACE
class QIOSMimeData : public QMimeData {
public:
- QIOSMimeData(QClipboard::Mode mode) : QMimeData(), m_mode(mode) { }
+ QIOSMimeData() : QMimeData() { }
~QIOSMimeData() { }
QStringList formats() const override;
QVariant retrieveData(const QString &mimeType, QMetaType type) const override;
-
-private:
- const QClipboard::Mode m_mode;
};
QStringList QIOSMimeData::formats() const
{
QStringList foundMimeTypes;
- UIPasteboard *pb = [UIPasteboard pasteboardWithQClipboardMode:m_mode];
+ UIPasteboard *pb = UIPasteboard.generalPasteboard;
NSArray<NSString *> *pasteboardTypes = [pb pasteboardTypes];
for (NSUInteger i = 0; i < [pasteboardTypes count]; ++i) {
- QString uti = QString::fromNSString([pasteboardTypes objectAtIndex:i]);
- QString mimeType = QMacInternalPasteboardMime::flavorToMime(QMacInternalPasteboardMime::MIME_ALL, uti);
+ const QString uti = QString::fromNSString([pasteboardTypes objectAtIndex:i]);
+ const QString mimeType = QMacMimeRegistry::flavorToMime(QUtiMimeConverter::HandlerScopeFlag::All, uti);
if (!mimeType.isEmpty() && !foundMimeTypes.contains(mimeType))
foundMimeTypes << mimeType;
}
@@ -126,17 +104,14 @@ QStringList QIOSMimeData::formats() const
QVariant QIOSMimeData::retrieveData(const QString &mimeType, QMetaType) const
{
- UIPasteboard *pb = [UIPasteboard pasteboardWithQClipboardMode:m_mode];
+ UIPasteboard *pb = UIPasteboard.generalPasteboard;
NSArray<NSString *> *pasteboardTypes = [pb pasteboardTypes];
- foreach (QMacInternalPasteboardMime *converter,
- QMacInternalPasteboardMime::all(QMacInternalPasteboardMime::MIME_ALL)) {
- if (!converter->canConvert(mimeType, converter->flavorFor(mimeType)))
- continue;
-
+ const auto converters = QMacMimeRegistry::all(QUtiMimeConverter::HandlerScopeFlag::All);
+ for (QUtiMimeConverter *converter : converters) {
for (NSUInteger i = 0; i < [pasteboardTypes count]; ++i) {
NSString *availableUtiNSString = [pasteboardTypes objectAtIndex:i];
- QString availableUti = QString::fromNSString(availableUtiNSString);
+ const QString availableUti = QString::fromNSString(availableUtiNSString);
if (!converter->canConvert(mimeType, availableUti))
continue;
@@ -166,7 +141,7 @@ QMimeData *QIOSClipboard::mimeData(QClipboard::Mode mode)
{
Q_ASSERT(supportsMode(mode));
if (!m_mimeData.contains(mode))
- return *m_mimeData.insert(mode, new QIOSMimeData(mode));
+ return *m_mimeData.insert(mode, new QIOSMimeData);
return m_mimeData[mode];
}
@@ -174,7 +149,7 @@ void QIOSClipboard::setMimeData(QMimeData *mimeData, QClipboard::Mode mode)
{
Q_ASSERT(supportsMode(mode));
- UIPasteboard *pb = [UIPasteboard pasteboardWithQClipboardMode:mode];
+ UIPasteboard *pb = UIPasteboard.generalPasteboard;
if (!mimeData) {
pb.items = [NSArray<NSDictionary<NSString *, id> *> array];
return;
@@ -183,26 +158,29 @@ void QIOSClipboard::setMimeData(QMimeData *mimeData, QClipboard::Mode mode)
mimeData->deleteLater();
NSMutableDictionary<NSString *, id> *pbItem = [NSMutableDictionary<NSString *, id> dictionaryWithCapacity:mimeData->formats().size()];
- foreach (const QString &mimeType, mimeData->formats()) {
- foreach (QMacInternalPasteboardMime *converter,
- QMacInternalPasteboardMime::all(QMacInternalPasteboardMime::MIME_ALL)) {
- QString uti = converter->flavorFor(mimeType);
- if (uti.isEmpty() || !converter->canConvert(mimeType, uti))
+ const auto formats = mimeData->formats();
+ for (const QString &mimeType : formats) {
+ const auto converters = QMacMimeRegistry::all(QUtiMimeConverter::HandlerScopeFlag::All);
+ for (const QUtiMimeConverter *converter : converters) {
+ const QString uti = converter->utiForMime(mimeType);
+ if (uti.isEmpty())
continue;
QVariant mimeDataAsVariant;
if (mimeData->hasImage()) {
mimeDataAsVariant = mimeData->imageData();
} else if (mimeData->hasUrls()) {
+ const auto urls = mimeData->urls();
QVariantList urlList;
- for (QUrl url : mimeData->urls())
+ urlList.reserve(urls.size());
+ for (const QUrl& url : urls)
urlList << url;
mimeDataAsVariant = QVariant(urlList);
} else {
mimeDataAsVariant = QVariant(mimeData->data(mimeType));
}
- QByteArray byteArray = converter->convertFromMime(mimeType, mimeDataAsVariant, uti).first();
+ QByteArray byteArray = converter->convertFromMime(mimeType, mimeDataAsVariant, uti).constFirst();
NSData *nsData = [NSData dataWithBytes:byteArray.constData() length:byteArray.size()];
[pbItem setValue:nsData forKey:uti.toNSString()];
break;
@@ -214,7 +192,7 @@ void QIOSClipboard::setMimeData(QMimeData *mimeData, QClipboard::Mode mode)
bool QIOSClipboard::supportsMode(QClipboard::Mode mode) const
{
- return (mode == QClipboard::Clipboard || mode == QClipboard::FindBuffer);
+ return mode == QClipboard::Clipboard;
}
bool QIOSClipboard::ownsMode(QClipboard::Mode mode) const
diff --git a/src/plugins/platforms/ios/qioscolordialog.h b/src/plugins/platforms/ios/qioscolordialog.h
new file mode 100644
index 0000000000..1af718949b
--- /dev/null
+++ b/src/plugins/platforms/ios/qioscolordialog.h
@@ -0,0 +1,38 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QIOSCOLORDIALOG_H
+#define QIOSCOLORDIALOG_H
+
+#include <QtCore/qeventloop.h>
+#include <qpa/qplatformdialoghelper.h>
+
+Q_FORWARD_DECLARE_OBJC_CLASS(QIOSColorDialogController);
+
+QT_BEGIN_NAMESPACE
+
+class QIOSColorDialog : public QPlatformColorDialogHelper
+{
+public:
+ QIOSColorDialog();
+ ~QIOSColorDialog();
+
+ void exec() override;
+ bool show(Qt::WindowFlags windowFlags, Qt::WindowModality windowModality, QWindow *parent) override;
+ void hide() override;
+
+ void setCurrentColor(const QColor&) override;
+ QColor currentColor() const override;
+
+ void updateColor(const QColor&);
+
+private:
+ QEventLoop m_eventLoop;
+ QIOSColorDialogController *m_viewController;
+ QColor m_currentColor;
+};
+
+QT_END_NAMESPACE
+
+#endif // QIOSCOLORDIALOG_H
+
diff --git a/src/plugins/platforms/ios/qioscolordialog.mm b/src/plugins/platforms/ios/qioscolordialog.mm
new file mode 100644
index 0000000000..92c0f3e46c
--- /dev/null
+++ b/src/plugins/platforms/ios/qioscolordialog.mm
@@ -0,0 +1,159 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#import <UIKit/UIKit.h>
+
+#include <QtGui/qwindow.h>
+#include <QDebug>
+
+#include <QtCore/private/qcore_mac_p.h>
+
+#include "qioscolordialog.h"
+#include "qiosintegration.h"
+
+@interface QIOSColorDialogController : UIColorPickerViewController <UIColorPickerViewControllerDelegate,
+ UIAdaptivePresentationControllerDelegate>
+@end
+
+@implementation QIOSColorDialogController {
+ QIOSColorDialog *m_colorDialog;
+}
+
+- (instancetype)initWithQIOSColorDialog:(QIOSColorDialog *)dialog
+{
+ if (self = [super init]) {
+ m_colorDialog = dialog;
+ self.delegate = self;
+ self.presentationController.delegate = self;
+ self.supportsAlpha = dialog->options()->testOption(QColorDialogOptions::ShowAlphaChannel);
+ }
+ return self;
+}
+
+- (void)setQColor:(const QColor &)qColor
+{
+ UIColor *uiColor;
+ const QColor::Spec spec = qColor.spec();
+ if (spec == QColor::Hsv) {
+ uiColor = [UIColor colorWithHue:qColor.hsvHueF()
+ saturation:qColor.hsvSaturationF()
+ brightness:qColor.valueF()
+ alpha:qColor.alphaF()];
+ } else {
+ uiColor = [UIColor colorWithRed:qColor.redF()
+ green:qColor.greenF()
+ blue:qColor.blueF()
+ alpha:qColor.alphaF()];
+ }
+ self.selectedColor = uiColor;
+}
+
+- (void)updateQColor
+{
+ UIColor *color = self.selectedColor;
+ CGFloat red = 0, green = 0, blue = 0, alpha = 0;
+
+ QColor newColor;
+ if ([color getRed:&red green:&green blue:&blue alpha:&alpha])
+ newColor.setRgbF(red, green, blue, alpha);
+ else
+ qWarning() << "Incompatible color space";
+
+
+ if (m_colorDialog) {
+ m_colorDialog->updateColor(newColor);
+ emit m_colorDialog->currentColorChanged(newColor);
+ }
+}
+
+// ----------------------UIColorPickerViewControllerDelegate--------------------------
+- (void)colorPickerViewControllerDidSelectColor:(UIColorPickerViewController *)viewController
+{
+ Q_UNUSED(viewController);
+ [self updateQColor];
+}
+
+- (void)colorPickerViewControllerDidFinish:(UIColorPickerViewController *)viewController
+{
+ Q_UNUSED(viewController);
+ [self updateQColor];
+ emit m_colorDialog->accept();
+}
+
+// ----------------------UIAdaptivePresentationControllerDelegate--------------------------
+- (void)presentationControllerDidDismiss:(UIPresentationController *)presentationController
+{
+ Q_UNUSED(presentationController);
+ emit m_colorDialog->reject();
+}
+
+@end
+
+QIOSColorDialog::QIOSColorDialog()
+ : m_viewController(nullptr)
+{
+}
+
+QIOSColorDialog::~QIOSColorDialog()
+{
+ hide();
+}
+
+void QIOSColorDialog::exec()
+{
+ m_eventLoop.exec(QEventLoop::DialogExec);
+}
+
+bool QIOSColorDialog::show(Qt::WindowFlags windowFlags, Qt::WindowModality windowModality, QWindow *parent)
+{
+ Q_UNUSED(windowFlags);
+
+ if (!m_viewController) {
+ m_viewController = [[QIOSColorDialogController alloc] initWithQIOSColorDialog:this];
+ if (m_currentColor.isValid())
+ [m_viewController setQColor:m_currentColor];
+ }
+
+ if (windowModality == Qt::ApplicationModal || windowModality == Qt::WindowModal)
+ m_viewController.modalInPresentation = YES;
+
+ UIWindow *window = parent ? reinterpret_cast<UIView *>(parent->winId()).window
+ : qt_apple_sharedApplication().keyWindow;
+ if (!window)
+ return false;
+
+ // We can't present from view controller if already presenting
+ if (window.rootViewController.presentedViewController)
+ return false;
+
+ [window.rootViewController presentViewController:m_viewController animated:YES completion:nil];
+
+ return true;
+}
+
+void QIOSColorDialog::hide()
+{
+ [m_viewController dismissViewControllerAnimated:YES completion:nil];
+ [m_viewController release];
+ m_viewController = nullptr;
+ m_eventLoop.exit();
+}
+
+void QIOSColorDialog::setCurrentColor(const QColor &color)
+{
+ updateColor(color);
+ if (m_viewController)
+ [m_viewController setQColor:color];
+}
+
+QColor QIOSColorDialog::currentColor() const
+{
+ return m_currentColor;
+}
+
+void QIOSColorDialog::updateColor(const QColor &color)
+{
+ m_currentColor = color;
+}
+
+
diff --git a/src/plugins/platforms/ios/qioscontext.mm b/src/plugins/platforms/ios/qioscontext.mm
index 1649089841..499adea0fe 100644
--- a/src/plugins/platforms/ios/qioscontext.mm
+++ b/src/plugins/platforms/ios/qioscontext.mm
@@ -1,6 +1,8 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+#undef QT_NO_FOREACH // this file contains unported legacy Q_FOREACH uses
+
#include "qioscontext.h"
#include "qiosintegration.h"
diff --git a/src/plugins/platforms/ios/qiosdocumentpickercontroller.h b/src/plugins/platforms/ios/qiosdocumentpickercontroller.h
index 9182a699b3..f0b7472539 100644
--- a/src/plugins/platforms/ios/qiosdocumentpickercontroller.h
+++ b/src/plugins/platforms/ios/qiosdocumentpickercontroller.h
@@ -2,6 +2,7 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#import <UIKit/UIKit.h>
+#import <UniformTypeIdentifiers/UniformTypeIdentifiers.h>
#include "qiosfiledialog.h"
diff --git a/src/plugins/platforms/ios/qiosdocumentpickercontroller.mm b/src/plugins/platforms/ios/qiosdocumentpickercontroller.mm
index 09934dda0e..fca0432426 100644
--- a/src/plugins/platforms/ios/qiosdocumentpickercontroller.mm
+++ b/src/plugins/platforms/ios/qiosdocumentpickercontroller.mm
@@ -12,27 +12,37 @@
- (instancetype)initWithQIOSFileDialog:(QIOSFileDialog *)fileDialog
{
- NSMutableArray <NSString *> *docTypes = [[[NSMutableArray alloc] init] autorelease];
- UIDocumentPickerMode importMode;
- switch (fileDialog->options()->fileMode()) {
- case QFileDialogOptions::AnyFile:
- case QFileDialogOptions::ExistingFile:
- case QFileDialogOptions::ExistingFiles:
- [docTypes addObject:(__bridge NSString *)kUTTypeContent];
- [docTypes addObject:(__bridge NSString *)kUTTypeItem];
- [docTypes addObject:(__bridge NSString *)kUTTypeData];
- importMode = UIDocumentPickerModeImport;
- break;
- case QFileDialogOptions::Directory:
- case QFileDialogOptions::DirectoryOnly:
- // Directory picking is not supported because it requires
- // special handling not possible with the current QFilePicker
- // implementation.
-
- Q_UNREACHABLE();
+ NSMutableArray <UTType *> *docTypes = [[[NSMutableArray alloc] init] autorelease];
+
+ QStringList nameFilters = fileDialog->options()->nameFilters();
+ if (!nameFilters.isEmpty() && (fileDialog->options()->fileMode() != QFileDialogOptions::Directory
+ || fileDialog->options()->fileMode() != QFileDialogOptions::DirectoryOnly))
+ {
+ QStringList results;
+ for (const QString &filter : nameFilters)
+ results.append(QPlatformFileDialogHelper::cleanFilterList(filter));
+
+ docTypes = [self computeAllowedFileTypes:results];
+ }
+
+ if (!docTypes.count) {
+ switch (fileDialog->options()->fileMode()) {
+ case QFileDialogOptions::AnyFile:
+ case QFileDialogOptions::ExistingFile:
+ case QFileDialogOptions::ExistingFiles:
+ [docTypes addObject:[UTType typeWithIdentifier:(__bridge NSString *)UTTypeContent]];
+ [docTypes addObject:[UTType typeWithIdentifier:(__bridge NSString *)UTTypeItem]];
+ [docTypes addObject:[UTType typeWithIdentifier:(__bridge NSString *)UTTypeData]];
+ break;
+ // Showing files is not supported in Directory mode in iOS
+ case QFileDialogOptions::Directory:
+ case QFileDialogOptions::DirectoryOnly:
+ [docTypes addObject:[UTType typeWithIdentifier:(__bridge NSString *)UTTypeFolder]];
+ break;
+ }
}
- if (self = [super initWithDocumentTypes:docTypes inMode:importMode]) {
+ if (self = [super initForOpeningContentTypes:docTypes]) {
m_fileDialog = fileDialog;
self.modalPresentationStyle = UIModalPresentationFormSheet;
self.delegate = self;
@@ -78,4 +88,28 @@
emit m_fileDialog->reject();
}
+- (NSMutableArray<UTType*>*)computeAllowedFileTypes:(QStringList)filters
+{
+ QStringList fileTypes;
+ for (const QString &filter : filters) {
+ if (filter == (QLatin1String("*")))
+ continue;
+
+ if (filter.contains(u'?'))
+ continue;
+
+ if (filter.count(u'*') != 1)
+ continue;
+
+ auto extensions = filter.split('.', Qt::SkipEmptyParts);
+ fileTypes += extensions.last();
+ }
+
+ NSMutableArray<UTType *> *result = [NSMutableArray<UTType *> arrayWithCapacity:fileTypes.size()];
+ for (const QString &string : fileTypes)
+ [result addObject:[UTType typeWithFilenameExtension:string.toNSString()]];
+
+ return result;
+}
+
@end
diff --git a/src/plugins/platforms/ios/qiosfiledialog.h b/src/plugins/platforms/ios/qiosfiledialog.h
index 12a3af7181..f00c154c03 100644
--- a/src/plugins/platforms/ios/qiosfiledialog.h
+++ b/src/plugins/platforms/ios/qiosfiledialog.h
@@ -39,6 +39,7 @@ private:
bool showImagePickerDialog(QWindow *parent);
bool showNativeDocumentPickerDialog(QWindow *parent);
+ void showImagePickerDialog_helper(QWindow *parent);
};
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/ios/qiosfiledialog.mm b/src/plugins/platforms/ios/qiosfiledialog.mm
index 64e9445c43..63d180220b 100644
--- a/src/plugins/platforms/ios/qiosfiledialog.mm
+++ b/src/plugins/platforms/ios/qiosfiledialog.mm
@@ -3,6 +3,8 @@
#import <UIKit/UIKit.h>
+#import <Photos/Photos.h>
+
#include <QtCore/qstandardpaths.h>
#include <QtGui/qwindow.h>
#include <QDebug>
@@ -14,6 +16,8 @@
#include "qiosoptionalplugininterface.h"
#include "qiosdocumentpickercontroller.h"
+#include <QtCore/qpointer.h>
+
using namespace Qt::StringLiterals;
QIOSFileDialog::QIOSFileDialog()
@@ -36,11 +40,15 @@ bool QIOSFileDialog::show(Qt::WindowFlags windowFlags, Qt::WindowModality window
Q_UNUSED(windowFlags);
Q_UNUSED(windowModality);
- bool acceptOpen = options()->acceptMode() == QFileDialogOptions::AcceptOpen;
- QString directory = options()->initialDirectory().toLocalFile();
+ const bool acceptOpen = options()->acceptMode() == QFileDialogOptions::AcceptOpen;
+ const auto initialDir = options()->initialDirectory();
+ const QString directory = initialDir.toLocalFile();
+ // We manually add assets-library:// to the list of paths,
+ // when converted to QUrl, it becames a scheme.
+ const QString scheme = initialDir.scheme();
if (acceptOpen) {
- if (directory.startsWith("assets-library:"_L1))
+ if (directory.startsWith("assets-library:"_L1) || scheme == "assets-library"_L1)
return showImagePickerDialog(parent);
else
return showNativeDocumentPickerDialog(parent);
@@ -49,6 +57,13 @@ bool QIOSFileDialog::show(Qt::WindowFlags windowFlags, Qt::WindowModality window
return false;
}
+void QIOSFileDialog::showImagePickerDialog_helper(QWindow *parent)
+{
+ UIWindow *window = parent ? reinterpret_cast<UIView *>(parent->winId()).window
+ : qt_apple_sharedApplication().keyWindow;
+ [window.rootViewController presentViewController:m_viewController animated:YES completion:nil];
+}
+
bool QIOSFileDialog::showImagePickerDialog(QWindow *parent)
{
if (!m_viewController) {
@@ -67,9 +82,38 @@ bool QIOSFileDialog::showImagePickerDialog(QWindow *parent)
return false;
}
- UIWindow *window = parent ? reinterpret_cast<UIView *>(parent->winId()).window
- : qt_apple_sharedApplication().keyWindow;
- [window.rootViewController presentViewController:m_viewController animated:YES completion:nil];
+ // "Old style" authorization (deprecated, but we have to work with AssetsLibrary anyway).
+ //
+ // From the documentation:
+ // "The authorizationStatus and requestAuthorization: methods aren’t compatible with the
+ // limited library and return PHAuthorizationStatusAuthorized when the user authorizes your
+ // app for limited access only."
+ //
+ // This is good enough for us.
+
+ const auto authStatus = [PHPhotoLibrary authorizationStatus];
+ if (authStatus == PHAuthorizationStatusAuthorized) {
+ showImagePickerDialog_helper(parent);
+ } else if (authStatus == PHAuthorizationStatusNotDetermined) {
+ QPointer<QWindow> winGuard(parent);
+ QPointer<QIOSFileDialog> thisGuard(this);
+ [PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus status) {
+ dispatch_async(dispatch_get_main_queue(), ^{
+ if (status == PHAuthorizationStatusAuthorized) {
+ if (thisGuard && winGuard)
+ thisGuard->showImagePickerDialog_helper(winGuard);
+
+ } else if (thisGuard) {
+ emit thisGuard->reject();
+ }
+ });
+ }];
+ } else {
+ // Treat 'Limited' (we don't know how to deal with anyway) and 'Denied' as errors.
+ // FIXME: logging category?
+ qWarning() << "QIOSFileDialog: insufficient permission, cannot pick images";
+ return false;
+ }
return true;
}
@@ -77,10 +121,6 @@ bool QIOSFileDialog::showImagePickerDialog(QWindow *parent)
bool QIOSFileDialog::showNativeDocumentPickerDialog(QWindow *parent)
{
#ifndef Q_OS_TVOS
- if (options()->fileMode() == QFileDialogOptions::Directory ||
- options()->fileMode() == QFileDialogOptions::DirectoryOnly)
- return false;
-
m_viewController = [[QIOSDocumentPickerController alloc] initWithQIOSFileDialog:this];
UIWindow *window = parent ? reinterpret_cast<UIView *>(parent->winId()).window
diff --git a/src/plugins/platforms/ios/qiosfontdialog.h b/src/plugins/platforms/ios/qiosfontdialog.h
new file mode 100644
index 0000000000..f0a92d0d6f
--- /dev/null
+++ b/src/plugins/platforms/ios/qiosfontdialog.h
@@ -0,0 +1,41 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QIOSFONTDIALOG_H
+#define QIOSFONTDIALOG_H
+
+#include <QtCore/qeventloop.h>
+#include <qpa/qplatformdialoghelper.h>
+
+@interface QIOSFontDialogController : UIFontPickerViewController <UIFontPickerViewControllerDelegate,
+ UIAdaptivePresentationControllerDelegate>
+@end
+
+QT_BEGIN_NAMESPACE
+
+class QIOSFontDialog : public QPlatformFontDialogHelper
+{
+public:
+ QIOSFontDialog();
+ ~QIOSFontDialog();
+
+ void exec() override;
+
+ bool show(Qt::WindowFlags windowFlags, Qt::WindowModality windowModality, QWindow *parent) override;
+ void hide() override;
+
+ void setCurrentFont(const QFont &) override;
+ QFont currentFont() const override;
+
+ void updateCurrentFont(const QFont &);
+
+private:
+ QEventLoop m_eventLoop;
+ QIOSFontDialogController *m_viewController;
+ QFont m_currentFont;
+
+};
+
+QT_END_NAMESPACE
+
+#endif // QIOSFONTDIALOG_H
diff --git a/src/plugins/platforms/ios/qiosfontdialog.mm b/src/plugins/platforms/ios/qiosfontdialog.mm
new file mode 100644
index 0000000000..4cea1cb558
--- /dev/null
+++ b/src/plugins/platforms/ios/qiosfontdialog.mm
@@ -0,0 +1,190 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#import <UIKit/UIKit.h>
+
+#include <QtGui/qwindow.h>
+#include <QtGui/qfontdatabase.h>
+#include <QDebug>
+
+#include <QtCore/private/qcore_mac_p.h>
+#include <QtGui/private/qfont_p.h>
+#include <QtGui/private/qfontengine_p.h>
+
+#include "qiosfontdialog.h"
+#include "qiosintegration.h"
+
+@implementation QIOSFontDialogController {
+ QIOSFontDialog *m_fontDialog;
+}
+
+- (instancetype)initWithQIOSFontDialog:(QIOSFontDialog *)dialog
+{
+ UIFontPickerViewControllerConfiguration *configuration = [[UIFontPickerViewControllerConfiguration alloc] init];
+ if (dialog->options()->testOption(QFontDialogOptions::MonospacedFonts)) {
+ UIFontDescriptorSymbolicTraits traits = {};
+ traits |= UIFontDescriptorTraitMonoSpace;
+ configuration.filteredTraits = traits;
+ }
+ configuration.includeFaces = YES;
+ if (self = [super initWithConfiguration:configuration]) {
+ m_fontDialog = dialog;
+ self.delegate = self;
+ self.presentationController.delegate = self;
+ }
+ [configuration release];
+ return self;
+}
+
+- (void)setQFont:(const QFont &)font
+{
+ QFontInfo fontInfo(font);
+ auto family = fontInfo.family().toNSString();
+ auto size = fontInfo.pointSize();
+
+ NSDictionary *dictionary = @{
+ static_cast<NSString *>(UIFontDescriptorFamilyAttribute): family,
+ static_cast<NSString *>(UIFontDescriptorSizeAttribute): [NSNumber numberWithInt:size]
+ };
+ UIFontDescriptor *fd = [UIFontDescriptor fontDescriptorWithFontAttributes:dictionary];
+
+ UIFontDescriptorSymbolicTraits traits = 0;
+ if (font.style() == QFont::StyleItalic)
+ traits |= UIFontDescriptorTraitItalic;
+ if (font.weight() == QFont::Bold)
+ traits |= UIFontDescriptorTraitBold;
+ fd = [fd fontDescriptorWithSymbolicTraits:traits];
+
+ self.selectedFontDescriptor = fd;
+}
+
+- (void)updateQFont
+{
+ UIFontDescriptor *font = self.selectedFontDescriptor;
+ if (!font)
+ return;
+
+ NSDictionary *attributes = font.fontAttributes;
+ UIFontDescriptorSymbolicTraits traits = font.symbolicTraits;
+
+ QFont newFont;
+ int size = qRound(font.pointSize);
+ QString family = QString::fromNSString([attributes objectForKey:UIFontDescriptorFamilyAttribute]);
+ if (family.isEmpty()) {
+ // If includeFaces is true, then the font descriptor won't
+ // have the UIFontDescriptorFamilyAttribute key set so we
+ // need to create a UIFont to get the font family
+ UIFont *f = [UIFont fontWithDescriptor:font size:size];
+ family = QString::fromNSString(f.familyName);
+ }
+
+ QString style;
+ if ((traits & (UIFontDescriptorTraitItalic | UIFontDescriptorTraitBold)) == (UIFontDescriptorTraitItalic | UIFontDescriptorTraitBold))
+ style = "Bold Italic";
+ else if (traits & UIFontDescriptorTraitItalic)
+ style = "Italic";
+ else if (traits & UIFontDescriptorTraitBold)
+ style = "Bold";
+
+ newFont = QFontDatabase::font(family, style, size);
+
+ if (m_fontDialog) {
+ m_fontDialog->updateCurrentFont(newFont);
+ emit m_fontDialog->currentFontChanged(newFont);
+ }
+}
+
+// ----------------------UIFontPickerViewControllerDelegate--------------------------
+- (void)fontPickerViewControllerDidPickFont:(UIFontPickerViewController *)viewController
+{
+ [self updateQFont];
+ emit m_fontDialog->accept();
+}
+
+- (void)fontPickerViewControllerDidCancel:(UIFontPickerViewController *)viewController
+{
+ Q_UNUSED(viewController);
+ emit m_fontDialog->reject();
+}
+
+// ----------------------UIAdaptivePresentationControllerDelegate--------------------------
+- (void)presentationControllerDidDismiss:(UIPresentationController *)presentationController
+{
+ Q_UNUSED(presentationController);
+ emit m_fontDialog->reject();
+}
+
+@end
+
+QIOSFontDialog::QIOSFontDialog()
+ : m_viewController(nullptr)
+{
+}
+
+QIOSFontDialog::~QIOSFontDialog()
+{
+ hide();
+}
+
+void QIOSFontDialog::exec()
+{
+ m_eventLoop.exec(QEventLoop::DialogExec);
+}
+
+bool QIOSFontDialog::show(Qt::WindowFlags windowFlags, Qt::WindowModality windowModality, QWindow *parent)
+{
+ Q_UNUSED(windowFlags);
+ Q_UNUSED(windowModality);
+
+ if (!m_viewController) {
+ m_viewController = [[QIOSFontDialogController alloc] initWithQIOSFontDialog:this];
+ [m_viewController setQFont:m_currentFont];
+ }
+
+ if (windowModality == Qt::ApplicationModal || windowModality == Qt::WindowModal)
+ m_viewController.modalInPresentation = YES;
+
+ UIWindow *window = parent ? reinterpret_cast<UIView *>(parent->winId()).window
+ : qt_apple_sharedApplication().keyWindow;
+ if (!window)
+ return false;
+
+ // We can't present from view controller if already presenting
+ if (window.rootViewController.presentedViewController)
+ return false;
+
+ [window.rootViewController presentViewController:m_viewController animated:YES completion:nil];
+
+ return true;
+}
+
+void QIOSFontDialog::hide()
+{
+ [m_viewController dismissViewControllerAnimated:YES completion:nil];
+ [m_viewController release];
+ m_viewController = nullptr;
+ if (m_eventLoop.isRunning())
+ m_eventLoop.exit();
+}
+
+void QIOSFontDialog::setCurrentFont(const QFont &font)
+{
+ if (m_currentFont == font)
+ return;
+
+ m_currentFont = font;
+ if (m_viewController)
+ [m_viewController setQFont:font];
+}
+
+QFont QIOSFontDialog::currentFont() const
+{
+ return m_currentFont;
+}
+
+void QIOSFontDialog::updateCurrentFont(const QFont &font)
+{
+ m_currentFont = font;
+}
+
+
diff --git a/src/plugins/platforms/ios/qiosglobal.h b/src/plugins/platforms/ios/qiosglobal.h
index 022fa88c24..a5139a3fde 100644
--- a/src/plugins/platforms/ios/qiosglobal.h
+++ b/src/plugins/platforms/ios/qiosglobal.h
@@ -37,7 +37,7 @@ int infoPlistValue(NSString* key, int defaultValue);
QT_END_NAMESPACE
@interface UIResponder (QtFirstResponder)
-+ (id)currentFirstResponder;
++ (id)qt_currentFirstResponder;
@end
QT_BEGIN_NAMESPACE
diff --git a/src/plugins/platforms/ios/qiosglobal.mm b/src/plugins/platforms/ios/qiosglobal.mm
index 0a6bc5bb62..c0faccaf71 100644
--- a/src/plugins/platforms/ios/qiosglobal.mm
+++ b/src/plugins/platforms/ios/qiosglobal.mm
@@ -119,7 +119,7 @@ QT_END_NAMESPACE
@implementation UIResponder (QtFirstResponder)
-+ (id)currentFirstResponder
++ (id)qt_currentFirstResponder
{
if (qt_apple_isApplicationExtension()) {
qWarning() << "can't get first responder in application extensions!";
diff --git a/src/plugins/platforms/ios/qiosinputcontext.mm b/src/plugins/platforms/ios/qiosinputcontext.mm
index 4c4b213ba2..56322a0f65 100644
--- a/src/plugins/platforms/ios/qiosinputcontext.mm
+++ b/src/plugins/platforms/ios/qiosinputcontext.mm
@@ -18,6 +18,8 @@
#include <QGuiApplication>
#include <QtGui/private/qwindow_p.h>
+#include <QtCore/qpointer.h>
+
// -------------------------------------------------------------------------
static QUIView *focusView()
@@ -119,12 +121,12 @@ static QUIView *focusView()
{
[self keyboardWillOrDidChange:notification];
- UIResponder *firstResponder = [UIResponder currentFirstResponder];
+ UIResponder *firstResponder = [UIResponder qt_currentFirstResponder];
if (![firstResponder isKindOfClass:[QIOSTextInputResponder class]])
return;
// Enable hide-keyboard gesture
- self.enabled = YES;
+ self.enabled = m_context->isInputPanelVisible();
m_context->scrollToCursor();
}
@@ -174,7 +176,11 @@ static QUIView *focusView()
{
[super touchesBegan:touches withEvent:event];
- Q_ASSERT(m_context->isInputPanelVisible());
+ if (!m_context->isInputPanelVisible()) {
+ qImDebug("keyboard was hidden by sliding it down, disabling hide-keyboard gesture");
+ self.enabled = NO;
+ return;
+ }
if ([touches count] != 1)
self.state = UIGestureRecognizerStateFailed;
@@ -228,7 +234,7 @@ static QUIView *focusView()
if (self.state == UIGestureRecognizerStateBegan) {
qImDebug("hide keyboard gesture was triggered");
- UIResponder *firstResponder = [UIResponder currentFirstResponder];
+ UIResponder *firstResponder = [UIResponder qt_currentFirstResponder];
Q_ASSERT([firstResponder isKindOfClass:[QIOSTextInputResponder class]]);
[firstResponder resignFirstResponder];
}
@@ -366,7 +372,7 @@ void QIOSInputContext::updateKeyboardState(NSNotification *notification)
// The isInputPanelVisible() property is based on whether or not the virtual keyboard
// is visible on screen, and does not follow the logic of the iOS WillShow and WillHide
// notifications which are not emitted for undocked keyboards, and are buggy when dealing
- // with input-accesosory-views. The reason for using frameEnd here (the future state),
+ // with input-accessory-views. The reason for using frameEnd here (the future state),
// instead of the current state reflected in frameBegin, is that QInputMethod::isVisible()
// is documented to reflect the future state in the case of animated transitions.
m_keyboardState.keyboardVisible = CGRectIntersectsRect(frameEnd, [UIScreen mainScreen].bounds);
diff --git a/src/plugins/platforms/ios/qiosintegration.h b/src/plugins/platforms/ios/qiosintegration.h
index 6830a5b455..a57a707c7f 100644
--- a/src/plugins/platforms/ios/qiosintegration.h
+++ b/src/plugins/platforms/ios/qiosintegration.h
@@ -31,9 +31,13 @@ public:
bool hasCapability(Capability cap) const override;
QPlatformWindow *createPlatformWindow(QWindow *window) const override;
+ QPlatformWindow *createForeignWindow(QWindow *window, WId nativeHandle) const override;
QPlatformBackingStore *createPlatformBackingStore(QWindow *window) const override;
+#if QT_CONFIG(opengl)
QPlatformOpenGLContext *createPlatformOpenGLContext(QOpenGLContext *context) const override;
+#endif
+
QPlatformOffscreenSurface *createPlatformOffscreenSurface(QOffscreenSurface *surface) const override;
QPlatformFontDatabase *fontDatabase() const override;
@@ -58,6 +62,8 @@ public:
void beep() const override;
+ void setApplicationBadge(qint64 number) override;
+
static QIOSIntegration *instance();
// -- QPlatformNativeInterface --
diff --git a/src/plugins/platforms/ios/qiosintegration.mm b/src/plugins/platforms/ios/qiosintegration.mm
index 84caccc267..c646042eb2 100644
--- a/src/plugins/platforms/ios/qiosintegration.mm
+++ b/src/plugins/platforms/ios/qiosintegration.mm
@@ -1,13 +1,14 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+#undef QT_NO_FOREACH // this file contains unported legacy Q_FOREACH uses
+
#include "qiosintegration.h"
#include "qioseventdispatcher.h"
#include "qiosglobal.h"
#include "qioswindow.h"
#include "qiosscreen.h"
#include "qiosplatformaccessibility.h"
-#include "qioscontext.h"
#ifndef Q_OS_TVOS
#include "qiosclipboard.h"
#endif
@@ -24,10 +25,15 @@
#include <qpa/qplatformoffscreensurface.h>
#include <QtGui/private/qcoretextfontdatabase_p.h>
-#include <QtGui/private/qmacmime_p.h>
+#include <QtGui/private/qmacmimeregistry_p.h>
+#include <QtGui/qutimimeconverter.h>
#include <QDir>
#include <QOperatingSystemVersion>
+#if QT_CONFIG(opengl)
+#include "qioscontext.h"
+#endif
+
#import <AudioToolbox/AudioServices.h>
#include <QtDebug>
@@ -89,7 +95,7 @@ void QIOSIntegration::initialize()
#if QT_CONFIG(tabletevent)
QWindowSystemInterfacePrivate::TabletEvent::setPlatformSynthesizesMouse(false);
#endif
- QMacInternalPasteboardMime::initializeMimeTypes();
+ QMacMimeRegistry::initializeMimeTypes();
qsizetype size = QList<QPluginParsedMetaData>(m_optionalPlugins->metaData()).size();
for (qsizetype i = 0; i < size; ++i)
@@ -105,7 +111,8 @@ QIOSIntegration::~QIOSIntegration()
delete m_clipboard;
m_clipboard = 0;
#endif
- QMacInternalPasteboardMime::destroyMimeTypes();
+
+ QMacMimeRegistry::destroyMimeTypes();
delete m_inputContext;
m_inputContext = 0;
@@ -126,11 +133,15 @@ QIOSIntegration::~QIOSIntegration()
bool QIOSIntegration::hasCapability(Capability cap) const
{
switch (cap) {
+#if QT_CONFIG(opengl)
case BufferQueueingOpenGL:
return true;
case OpenGL:
case ThreadedOpenGL:
return true;
+ case RasterGLSurface:
+ return true;
+#endif
case ThreadedPixmaps:
return true;
case MultipleWindows:
@@ -139,7 +150,7 @@ bool QIOSIntegration::hasCapability(Capability cap) const
return false;
case ApplicationState:
return true;
- case RasterGLSurface:
+ case ForeignWindows:
return true;
default:
return QPlatformIntegration::hasCapability(cap);
@@ -151,16 +162,23 @@ QPlatformWindow *QIOSIntegration::createPlatformWindow(QWindow *window) const
return new QIOSWindow(window);
}
+QPlatformWindow *QIOSIntegration::createForeignWindow(QWindow *window, WId nativeHandle) const
+{
+ return new QIOSWindow(window, nativeHandle);
+}
+
QPlatformBackingStore *QIOSIntegration::createPlatformBackingStore(QWindow *window) const
{
return new QRhiBackingStore(window);
}
+#if QT_CONFIG(opengl)
// Used when the QWindow's surface type is set by the client to QSurface::OpenGLSurface
QPlatformOpenGLContext *QIOSIntegration::createPlatformOpenGLContext(QOpenGLContext *context) const
{
return new QIOSContext(context);
}
+#endif
class QIOSOffscreenSurface : public QPlatformOffscreenSurface
{
@@ -268,6 +286,11 @@ void QIOSIntegration::beep() const
#endif
}
+void QIOSIntegration::setApplicationBadge(qint64 number)
+{
+ UIApplication.sharedApplication.applicationIconBadgeNumber = number;
+}
+
// ---------------------------------------------------------
void *QIOSIntegration::nativeResourceForWindow(const QByteArray &resource, QWindow *window)
diff --git a/src/plugins/platforms/ios/qiosmenu.h b/src/plugins/platforms/ios/qiosmenu.h
index 1822c08b1a..b0c8e7e10c 100644
--- a/src/plugins/platforms/ios/qiosmenu.h
+++ b/src/plugins/platforms/ios/qiosmenu.h
@@ -11,6 +11,8 @@
#import "quiview.h"
+#include <QtCore/qpointer.h>
+
class QIOSMenu;
@class QUIMenuController;
@class QUIPickerView;
diff --git a/src/plugins/platforms/ios/qiosmenu.mm b/src/plugins/platforms/ios/qiosmenu.mm
index e547cb0641..227ad2c7f5 100644
--- a/src/plugins/platforms/ios/qiosmenu.mm
+++ b/src/plugins/platforms/ios/qiosmenu.mm
@@ -491,7 +491,7 @@ QIOSMenuItemList QIOSMenu::filterFirstResponderActions(const QIOSMenuItemList &m
// In case of QIOSTextResponder, edit actions will be converted to key events that ends up
// triggering the shortcuts of the filtered menu items.
QIOSMenuItemList filteredMenuItems;
- UIResponder *responder = [UIResponder currentFirstResponder];
+ UIResponder *responder = [UIResponder qt_currentFirstResponder];
for (int i = 0; i < menuItems.count(); ++i) {
QIOSMenuItem *menuItem = menuItems.at(i);
@@ -502,8 +502,8 @@ QIOSMenuItemList QIOSMenu::filterFirstResponderActions(const QIOSMenuItemList &m
|| (shortcut == QKeySequence::Paste && [responder canPerformAction:@selector(paste:) withSender:nil])
|| (shortcut == QKeySequence::Delete && [responder canPerformAction:@selector(delete:) withSender:nil])
|| (shortcut == QKeySequence::SelectAll && [responder canPerformAction:@selector(selectAll:) withSender:nil])
- || (shortcut == QKeySequence::Undo && [responder canPerformAction:@selector(undo:) withSender:nil])
- || (shortcut == QKeySequence::Redo && [responder canPerformAction:@selector(redo:) withSender:nil])
+ || (shortcut == QKeySequence::Undo && [responder canPerformAction:@selector(undo) withSender:nil])
+ || (shortcut == QKeySequence::Redo && [responder canPerformAction:@selector(redo) withSender:nil])
|| (shortcut == QKeySequence::Bold && [responder canPerformAction:@selector(toggleBoldface:) withSender:nil])
|| (shortcut == QKeySequence::Italic && [responder canPerformAction:@selector(toggleItalics:) withSender:nil])
|| (shortcut == QKeySequence::Underline && [responder canPerformAction:@selector(toggleUnderline:) withSender:nil])) {
diff --git a/src/plugins/platforms/ios/qiosmessagedialog.mm b/src/plugins/platforms/ios/qiosmessagedialog.mm
index fd14e699e0..34421bbbd1 100644
--- a/src/plugins/platforms/ios/qiosmessagedialog.mm
+++ b/src/plugins/platforms/ios/qiosmessagedialog.mm
@@ -11,6 +11,7 @@
#include "qiosglobal.h"
#include "quiview.h"
+#include "qiosscreen.h"
#include "qiosmessagedialog.h"
using namespace Qt::StringLiterals;
@@ -29,7 +30,7 @@ inline QString QIOSMessageDialog::messageTextPlain()
{
// Concatenate text fragments, and remove HTML tags
const QSharedPointer<QMessageDialogOptions> &opt = options();
- const QString &lineShift = QStringLiteral("\n\n");
+ constexpr auto lineShift = "\n\n"_L1;
const QString &informativeText = opt->informativeText();
const QString &detailedText = opt->detailedText();
@@ -39,7 +40,7 @@ inline QString QIOSMessageDialog::messageTextPlain()
if (!detailedText.isEmpty())
text += lineShift + detailedText;
- text.replace("<p>"_L1, QStringLiteral("\n"), Qt::CaseInsensitive);
+ text.replace("<p>"_L1, "\n"_L1, Qt::CaseInsensitive);
text.remove(QRegularExpression(QStringLiteral("<[^>]*>")));
return text;
@@ -91,6 +92,9 @@ bool QIOSMessageDialog::show(Qt::WindowFlags windowFlags, Qt::WindowModality win
|| windowModality == Qt::NonModal) // We can only do modal dialogs
return false;
+ if (!options()->checkBoxLabel().isNull())
+ return false; // Can't support
+
m_alertController = [[UIAlertController
alertControllerWithTitle:options()->windowTitle().toNSString()
message:messageTextPlain().toNSString()
@@ -113,6 +117,25 @@ bool QIOSMessageDialog::show(Qt::WindowFlags windowFlags, Qt::WindowModality win
}
UIWindow *window = parent ? reinterpret_cast<UIView *>(parent->winId()).window : qt_apple_sharedApplication().keyWindow;
+ if (!window) {
+ qCDebug(lcQpaWindow, "Attempting to exec a dialog without any window/widget visible.");
+
+ auto *primaryScreen = static_cast<QIOSScreen*>(QGuiApplication::primaryScreen()->handle());
+ Q_ASSERT(primaryScreen);
+
+ window = primaryScreen->uiWindow();
+ if (window.hidden) {
+ // With a window hidden, an attempt to present view controller
+ // below fails with a warning, that a view "is not a part of
+ // any view hierarchy". The UIWindow is initially hidden,
+ // as unhiding it is what hides the splash screen.
+ window.hidden = NO;
+ }
+ }
+
+ if (!window)
+ return false;
+
[window.rootViewController presentViewController:m_alertController animated:YES completion:nil];
return true;
}
diff --git a/src/plugins/platforms/ios/qiosplatformaccessibility.mm b/src/plugins/platforms/ios/qiosplatformaccessibility.mm
index d54b7db57a..eb18ee637e 100644
--- a/src/plugins/platforms/ios/qiosplatformaccessibility.mm
+++ b/src/plugins/platforms/ios/qiosplatformaccessibility.mm
@@ -1,12 +1,15 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+#undef QT_NO_FOREACH // this file contains unported legacy Q_FOREACH uses
+
#include "qiosplatformaccessibility.h"
#if QT_CONFIG(accessibility)
#include <QtGui/QtGui>
#include "qioswindow.h"
+#include "quiaccessibilityelement.h"
QIOSPlatformAccessibility::QIOSPlatformAccessibility()
{}
@@ -25,8 +28,6 @@ void invalidateCache(QAccessibleInterface *iface)
// This will invalidate everything regardless of what window the
// interface belonged to. We might want to revisit this strategy later.
// (Therefore this function still takes the interface as argument)
- // It is also responsible for the bug that focus gets temporary lost
- // when items get added or removed from the screen
foreach (QWindow *win, QGuiApplication::topLevelWindows()) {
if (win && win->handle()) {
QT_PREPEND_NAMESPACE(QIOSWindow) *window = static_cast<QT_PREPEND_NAMESPACE(QIOSWindow) *>(win->handle());
@@ -38,14 +39,35 @@ void invalidateCache(QAccessibleInterface *iface)
void QIOSPlatformAccessibility::notifyAccessibilityUpdate(QAccessibleEvent *event)
{
- if (!isActive() || !event->accessibleInterface())
+ auto *accessibleInterface = event->accessibleInterface();
+ if (!isActive() || !accessibleInterface)
return;
switch (event->type()) {
+ case QAccessible::Focus: {
+ auto *element = [QMacAccessibilityElement elementWithId:event->uniqueId()];
+ Q_ASSERT(element);
+ // There's no NSAccessibilityFocusedUIElementChangedNotification, like we have on
+ // macOS. Instead, the documentation for UIAccessibilityLayoutChangedNotification
+ // specifies that the optional argument to UIAccessibilityPostNotification is the
+ // accessibility element for VoiceOver to move to after processing the notification.
+ UIAccessibilityPostNotification(UIAccessibilityLayoutChangedNotification, element);
+ break;
+ }
case QAccessible::ObjectCreated:
case QAccessible::ObjectShow:
case QAccessible::ObjectHide:
case QAccessible::ObjectDestroyed:
- invalidateCache(event->accessibleInterface());
+ invalidateCache(accessibleInterface);
+ switch (accessibleInterface->role()) {
+ case QAccessible::Window:
+ case QAccessible::Dialog:
+ // Bigger changes to the UI require a full reset of VoiceOver
+ UIAccessibilityPostNotification(UIAccessibilityScreenChangedNotification, nil);
+ break;
+ default:
+ // While smaller changes can be handled by re-reading the layout
+ UIAccessibilityPostNotification(UIAccessibilityLayoutChangedNotification, nil);
+ }
break;
default:
break;
diff --git a/src/plugins/platforms/ios/qiosscreen.mm b/src/plugins/platforms/ios/qiosscreen.mm
index ce0dedc88d..e48592aa24 100644
--- a/src/plugins/platforms/ios/qiosscreen.mm
+++ b/src/plugins/platforms/ios/qiosscreen.mm
@@ -1,6 +1,8 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+#undef QT_NO_FOREACH // this file contains unported legacy Q_FOREACH uses
+
#include "qiosglobal.h"
#include "qiosintegration.h"
#include "qiosscreen.h"
@@ -15,6 +17,7 @@
#include <QtGui/qpointingdevice.h>
#include <QtGui/private/qwindow_p.h>
+#include <QtGui/private/qguiapplication_p.h>
#include <private/qcoregraphics_p.h>
#include <qpa/qwindowsysteminterface.h>
@@ -72,14 +75,17 @@ static QIOSScreen* qtPlatformScreenFor(UIScreen *uiScreen)
+ (void)screenConnected:(NSNotification*)notification
{
- Q_ASSERT_X(QIOSIntegration::instance(), Q_FUNC_INFO,
- "Screen connected before QIOSIntegration creation");
+ if (!QIOSIntegration::instance())
+ return; // Will be added when QIOSIntegration is created
QWindowSystemInterface::handleScreenAdded(new QIOSScreen([notification object]));
}
+ (void)screenDisconnected:(NSNotification*)notification
{
+ if (!QIOSIntegration::instance())
+ return;
+
QIOSScreen *screen = qtPlatformScreenFor([notification object]);
Q_ASSERT_X(screen, Q_FUNC_INFO, "Screen disconnected that we didn't know about");
@@ -88,6 +94,9 @@ static QIOSScreen* qtPlatformScreenFor(UIScreen *uiScreen)
+ (void)screenModeChanged:(NSNotification*)notification
{
+ if (!QIOSIntegration::instance())
+ return;
+
QIOSScreen *screen = qtPlatformScreenFor([notification object]);
Q_ASSERT_X(screen, Q_FUNC_INFO, "Screen changed that we didn't know about");
@@ -140,21 +149,6 @@ static QIOSScreen* qtPlatformScreenFor(UIScreen *uiScreen)
@end
-@interface UIScreen (Compatibility)
-@property (nonatomic, readonly) CGRect qt_applicationFrame;
-@end
-
-@implementation UIScreen (Compatibility)
-- (CGRect)qt_applicationFrame
-{
-#ifdef Q_OS_IOS
- return self.applicationFrame;
-#else
- return self.bounds;
-#endif
-}
-@end
-
// -------------------------------------------------------------------------
@implementation QUIWindow
@@ -177,8 +171,19 @@ static QIOSScreen* qtPlatformScreenFor(UIScreen *uiScreen)
{
[super traitCollectionDidChange:previousTraitCollection];
+ if (!qGuiApp)
+ return;
+
+ Qt::ColorScheme colorScheme = self.traitCollection.userInterfaceStyle
+ == UIUserInterfaceStyleDark
+ ? Qt::ColorScheme::Dark
+ : Qt::ColorScheme::Light;
+
if (self.screen == UIScreen.mainScreen) {
- if (previousTraitCollection.userInterfaceStyle != self.traitCollection.userInterfaceStyle) {
+ // Check if the current userInterfaceStyle reports a different appearance than
+ // the platformTheme's appearance. We might have set that one based on the UIScreen
+ if (previousTraitCollection.userInterfaceStyle != self.traitCollection.userInterfaceStyle
+ || QGuiApplicationPrivate::platformTheme()->colorScheme() != colorScheme) {
QIOSTheme::initializeSystemPalette();
QWindowSystemInterface::handleThemeChange<QWindowSystemInterface::SynchronousDelivery>();
}
@@ -254,7 +259,7 @@ QIOSScreen::QIOSScreen(UIScreen *screen)
if (!qt_apple_isApplicationExtension()) {
for (UIWindow *existingWindow in qt_apple_sharedApplication().windows) {
if (existingWindow.screen == m_uiScreen) {
- m_uiWindow = [m_uiWindow retain];
+ m_uiWindow = [existingWindow retain];
break;
}
}
@@ -291,6 +296,15 @@ QString QIOSScreen::name() const
return "External display"_L1;
}
+static bool isRunningOnVisionOS()
+{
+ static bool result = []{
+ // This class is documented to only be available on visionOS
+ return NSClassFromString(@"UIWindowSceneGeometryPreferencesVision");
+ }();
+ return result;
+}
+
void QIOSScreen::updateProperties()
{
QRect previousGeometry = m_geometry;
@@ -298,14 +312,22 @@ void QIOSScreen::updateProperties()
m_geometry = QRectF::fromCGRect(m_uiScreen.bounds).toRect();
- // The application frame doesn't take safe area insets into account, and
- // the safe area insets are not available before the UIWindow is shown,
- // and do not take split-view constraints into account, so we have to
- // combine the two to get the correct available geometry.
- QRect applicationFrame = QRectF::fromCGRect(m_uiScreen.qt_applicationFrame).toRect();
- UIEdgeInsets safeAreaInsets = m_uiWindow.qt_safeAreaInsets;
- m_availableGeometry = m_geometry.adjusted(safeAreaInsets.left, safeAreaInsets.top,
- -safeAreaInsets.right, -safeAreaInsets.bottom).intersected(applicationFrame);
+ m_availableGeometry = m_geometry;
+
+ // For convenience, we reflect the safe area margins of the screen's UIWindow
+ // by reducing the available geometry of the screen. But we only do this if
+ // the UIWindow bounds is representative of the UIScreen.
+ if (isRunningOnVisionOS()) {
+ // On visionOS there is no concept of a screen, and hence no concept of
+ // screen-relative system UI that we should keep top level windows away
+ // from, so don't apply the UIWindow safe area insets to the screen.
+ } else {
+ UIEdgeInsets safeAreaInsets = m_uiWindow.safeAreaInsets;
+ if (m_uiWindow.bounds.size.width == m_uiScreen.bounds.size.width)
+ m_availableGeometry.adjust(safeAreaInsets.left, 0, -safeAreaInsets.right, 0);
+ if (m_uiWindow.bounds.size.height == m_uiScreen.bounds.size.height)
+ m_availableGeometry.adjust(0, safeAreaInsets.top, 0, -safeAreaInsets.bottom);
+ }
#ifndef Q_OS_TVOS
if (m_uiScreen == [UIScreen mainScreen]) {
diff --git a/src/plugins/platforms/ios/qiostextinputoverlay.mm b/src/plugins/platforms/ios/qiostextinputoverlay.mm
index 512ab77bd2..01046334a1 100644
--- a/src/plugins/platforms/ios/qiostextinputoverlay.mm
+++ b/src/plugins/platforms/ios/qiostextinputoverlay.mm
@@ -947,7 +947,17 @@ static void executeBlockWithoutAnimation(Block block)
int cursorPosOnRelease = QPlatformInputContext::queryFocusObject(Qt::ImCursorPosition, touchPos).toInt();
if (cursorPosOnRelease == _cursorPosOnPress) {
+ // We've recognized a gesture to open the menu, but we don't know
+ // whether the user tapped a control that was overlaid our input
+ // area, since we don't do any granular hit-testing in touchesBegan.
+ // To ensure that the gesture doesn't eat touch events that should
+ // have reached another UI control we report the gesture as failed
+ // here, and then manually show the menu at the next runloop pass.
_menuShouldBeVisible = true;
+ self.state = UIGestureRecognizerStateFailed;
+ dispatch_async(dispatch_get_main_queue(), ^{
+ QIOSTextInputOverlay::s_editMenu.visible = _menuShouldBeVisible;
+ });
} else {
// The menu is hidden, and the cursor will change position once
// Qt receive the touch release. We therefore fail so that we
@@ -996,19 +1006,26 @@ QIOSTextInputOverlay::~QIOSTextInputOverlay()
void QIOSTextInputOverlay::updateFocusObject()
{
+ // Destroy old recognizers since they were created with
+ // dependencies to the old focus object (focus view).
if (m_cursorRecognizer) {
- // Destroy old recognizers since they were created with
- // dependencies to the old focus object (focus view).
m_cursorRecognizer.enabled = NO;
- m_selectionRecognizer.enabled = NO;
- m_openMenuOnTapRecognizer.enabled = NO;
[m_cursorRecognizer release];
- [m_selectionRecognizer release];
- [m_openMenuOnTapRecognizer release];
- [s_editMenu release];
m_cursorRecognizer = nullptr;
+ }
+ if (m_selectionRecognizer) {
+ m_selectionRecognizer.enabled = NO;
+ [m_selectionRecognizer release];
m_selectionRecognizer = nullptr;
+ }
+ if (m_openMenuOnTapRecognizer) {
+ m_openMenuOnTapRecognizer.enabled = NO;
+ [m_openMenuOnTapRecognizer release];
m_openMenuOnTapRecognizer = nullptr;
+ }
+
+ if (s_editMenu) {
+ [s_editMenu release];
s_editMenu = nullptr;
}
diff --git a/src/plugins/platforms/ios/qiostextresponder.mm b/src/plugins/platforms/ios/qiostextresponder.mm
index 8069ddb601..b7377cc5e7 100644
--- a/src/plugins/platforms/ios/qiostextresponder.mm
+++ b/src/plugins/platforms/ios/qiostextresponder.mm
@@ -164,7 +164,7 @@
{
FirstResponderCandidate firstResponderCandidate(self);
- qImDebug() << "self:" << self << "first:" << [UIResponder currentFirstResponder];
+ qImDebug() << "self:" << self << "first:" << [UIResponder qt_currentFirstResponder];
if (![super becomeFirstResponder]) {
qImDebug() << self << "was not allowed to become first responder";
@@ -178,7 +178,7 @@
- (BOOL)resignFirstResponder
{
- qImDebug() << "self:" << self << "first:" << [UIResponder currentFirstResponder];
+ qImDebug() << "self:" << self << "first:" << [UIResponder qt_currentFirstResponder];
// Don't allow activation events of the window that we're doing text on behalf on
// to steal responder.
@@ -196,11 +196,11 @@
// a regular responder transfer to another window. In the former case, iOS
// will set the new first-responder to our next-responder, and in the latter
// case we'll have an active responder candidate.
- if (![UIResponder currentFirstResponder] && !FirstResponderCandidate::currentCandidate()) {
+ if (![UIResponder qt_currentFirstResponder] && !FirstResponderCandidate::currentCandidate()) {
// No first responder set anymore, sync this with Qt by clearing the
// focus object.
m_inputContext->clearCurrentFocusObject();
- } else if ([UIResponder currentFirstResponder] == [self nextResponder]) {
+ } else if ([UIResponder qt_currentFirstResponder] == [self nextResponder]) {
// We have resigned the keyboard, and transferred first responder back to the parent view
Q_ASSERT(!FirstResponderCandidate::currentCandidate());
if ([self currentImeState:Qt::ImEnabled].toBool()) {
@@ -222,8 +222,12 @@
- (UIResponder*)nextResponder
{
- return qApp->focusWindow() ?
- reinterpret_cast<QUIView *>(qApp->focusWindow()->handle()->winId()) : 0;
+ // Make sure we have a handle/platform window before getting the winId().
+ // In the dtor of QIOSWindow the platform window is set to null before calling
+ // removeFromSuperview which will end up calling nextResponder. That means it's
+ // possible that we can get here while the window is being torn down.
+ return (qApp->focusWindow() && qApp->focusWindow()->handle()) ?
+ reinterpret_cast<QUIView *>(qApp->focusWindow()->handle()->winId()) : 0;
}
// -------------------------------------------------------------------------
@@ -898,7 +902,7 @@
QInputMethodEvent e(m_markedText, attrs);
[self sendEventToFocusObject:e];
}
- QRectF startRect = QPlatformInputContext::cursorRectangle();;
+ QRectF startRect = QPlatformInputContext::cursorRectangle();
attrs = QList<QInputMethodEvent::Attribute>();
attrs << QInputMethodEvent::Attribute(QInputMethodEvent::Selection, r.location + r.length, 0, 0);
@@ -906,7 +910,7 @@
QInputMethodEvent e(m_markedText, attrs);
[self sendEventToFocusObject:e];
}
- QRectF endRect = QPlatformInputContext::cursorRectangle();;
+ QRectF endRect = QPlatformInputContext::cursorRectangle();
if (cursorPos != int(r.location + r.length) || cursorPos != anchorPos) {
attrs = QList<QInputMethodEvent::Attribute>();
@@ -944,20 +948,20 @@
[self sendEventToFocusObject:e];
}
-- (void)setBaseWritingDirection:(UITextWritingDirection)writingDirection forRange:(UITextRange *)range
+- (void)setBaseWritingDirection:(NSWritingDirection)writingDirection forRange:(UITextRange *)range
{
Q_UNUSED(writingDirection);
Q_UNUSED(range);
// Writing direction is handled by QLocale
}
-- (UITextWritingDirection)baseWritingDirectionForPosition:(UITextPosition *)position inDirection:(UITextStorageDirection)direction
+- (NSWritingDirection)baseWritingDirectionForPosition:(UITextPosition *)position inDirection:(UITextStorageDirection)direction
{
Q_UNUSED(position);
Q_UNUSED(direction);
if (QLocale::system().textDirection() == Qt::RightToLeft)
- return UITextWritingDirectionRightToLeft;
- return UITextWritingDirectionLeftToRight;
+ return NSWritingDirectionRightToLeft;
+ return NSWritingDirectionLeftToRight;
}
- (UITextRange *)characterRangeByExtendingPosition:(UITextPosition *)position inDirection:(UITextLayoutDirection)direction
diff --git a/src/plugins/platforms/ios/qiostheme.h b/src/plugins/platforms/ios/qiostheme.h
index ea5169800b..0f12ce099c 100644
--- a/src/plugins/platforms/ios/qiostheme.h
+++ b/src/plugins/platforms/ios/qiostheme.h
@@ -21,7 +21,7 @@ public:
const QPalette *palette(Palette type = SystemPalette) const override;
QVariant themeHint(ThemeHint hint) const override;
- Appearance appearance() const override;
+ Qt::ColorScheme colorScheme() const override;
QPlatformMenuItem* createPlatformMenuItem() const override;
QPlatformMenu* createPlatformMenu() const override;
@@ -30,6 +30,7 @@ public:
QPlatformDialogHelper *createPlatformDialogHelper(DialogType type) const override;
const QFont *font(Font type = SystemFont) const override;
+ QIconEngine *createIconEngine(const QString &iconName) const override;
static const char *name;
diff --git a/src/plugins/platforms/ios/qiostheme.mm b/src/plugins/platforms/ios/qiostheme.mm
index 390ca2e351..3d6d60f971 100644
--- a/src/plugins/platforms/ios/qiostheme.mm
+++ b/src/plugins/platforms/ios/qiostheme.mm
@@ -11,6 +11,7 @@
#include <QtGui/private/qcoregraphics_p.h>
#include <QtGui/private/qcoretextfontdatabase_p.h>
+#include <QtGui/private/qappleiconengine_p.h>
#include <QtGui/private/qguiapplication_p.h>
#include <qpa/qplatformintegration.h>
@@ -21,6 +22,9 @@
#include "qiosmenu.h"
#include "qiosfiledialog.h"
#include "qiosmessagedialog.h"
+#include "qioscolordialog.h"
+#include "qiosfontdialog.h"
+#include "qiosscreen.h"
#endif
QT_BEGIN_NAMESPACE
@@ -34,7 +38,7 @@ QIOSTheme::QIOSTheme()
m_contentSizeCategoryObserver = QMacNotificationObserver(nil,
UIContentSizeCategoryDidChangeNotification, [] {
qCDebug(lcQpaFonts) << "Contents size category changed to" << UIApplication.sharedApplication.preferredContentSizeCategory;
- QPlatformFontDatabase::handleAvailableFontsChanged();
+ QPlatformFontDatabase::repopulateFontDatabase();
});
}
@@ -66,6 +70,9 @@ void QIOSTheme::initializeSystemPalette()
s_systemPalette.setBrush(QPalette::Highlight, QColor(11, 70, 150, 60));
s_systemPalette.setBrush(QPalette::HighlightedText, qt_mac_toQBrush(UIColor.labelColor.CGColor));
+
+ if (@available(ios 15.0, *))
+ s_systemPalette.setBrush(QPalette::Accent, qt_mac_toQBrush(UIColor.tintColor.CGColor));
}
const QPalette *QIOSTheme::palette(QPlatformTheme::Palette type) const
@@ -98,6 +105,8 @@ bool QIOSTheme::usePlatformNativeDialog(QPlatformTheme::DialogType type) const
switch (type) {
case FileDialog:
case MessageDialog:
+ case ColorDialog:
+ case FontDialog:
return !qt_apple_isApplicationExtension();
default:
return false;
@@ -114,6 +123,12 @@ QPlatformDialogHelper *QIOSTheme::createPlatformDialogHelper(QPlatformTheme::Dia
case MessageDialog:
return new QIOSMessageDialog();
break;
+ case ColorDialog:
+ return new QIOSColorDialog();
+ break;
+ case FontDialog:
+ return new QIOSFontDialog();
+ break;
#endif
default:
return 0;
@@ -132,15 +147,22 @@ QVariant QIOSTheme::themeHint(ThemeHint hint) const
}
}
-QPlatformTheme::Appearance QIOSTheme::appearance() const
+Qt::ColorScheme QIOSTheme::colorScheme() const
{
- if (UIWindow *window = qt_apple_sharedApplication().windows.lastObject) {
- return window.rootViewController.traitCollection.userInterfaceStyle
- == UIUserInterfaceStyleDark
- ? QPlatformTheme::Appearance::Dark
- : QPlatformTheme::Appearance::Light;
+ // Set the appearance based on the QUIWindow
+ // Fallback to the UIScreen if no window is created yet
+ UIUserInterfaceStyle appearance = UIScreen.mainScreen.traitCollection.userInterfaceStyle;
+ NSArray<UIWindow *> *windows = qt_apple_sharedApplication().windows;
+ for (UIWindow *window in windows) {
+ if ([window isKindOfClass:[QUIWindow class]]) {
+ appearance = static_cast<QUIWindow*>(window).traitCollection.userInterfaceStyle;
+ break;
+ }
}
- return QPlatformTheme::Appearance::Unknown;
+
+ return appearance == UIUserInterfaceStyleDark
+ ? Qt::ColorScheme::Dark
+ : Qt::ColorScheme::Light;
}
const QFont *QIOSTheme::font(Font type) const
@@ -150,4 +172,9 @@ const QFont *QIOSTheme::font(Font type) const
return coreTextFontDatabase->themeFont(type);
}
+QIconEngine *QIOSTheme::createIconEngine(const QString &iconName) const
+{
+ return new QAppleIconEngine(iconName);
+}
+
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/ios/qiosviewcontroller.mm b/src/plugins/platforms/ios/qiosviewcontroller.mm
index c4e8968232..01a8a0d9b2 100644
--- a/src/plugins/platforms/ios/qiosviewcontroller.mm
+++ b/src/plugins/platforms/ios/qiosviewcontroller.mm
@@ -21,6 +21,8 @@
#include "qioswindow.h"
#include "quiview.h"
+#include <QtCore/qpointer.h>
+
// -------------------------------------------------------------------------
@interface QIOSViewController ()
@@ -107,8 +109,11 @@
{
Q_UNUSED(subview);
- Q_ASSERT(self.window);
UIWindow *uiWindow = self.window;
+ // uiWindow can be null when closing from the ios "app manager" and the app is
+ // showing a native window like UIDocumentBrowserViewController
+ if (!uiWindow)
+ return;
if (uiWindow.screen != [UIScreen mainScreen] && self.subviews.count == 1) {
// We're about to remove the last view of an external screen, so go back
diff --git a/src/plugins/platforms/ios/qioswindow.h b/src/plugins/platforms/ios/qioswindow.h
index 988269a855..02b2161202 100644
--- a/src/plugins/platforms/ios/qioswindow.h
+++ b/src/plugins/platforms/ios/qioswindow.h
@@ -21,7 +21,7 @@ class QIOSWindow : public QObject, public QPlatformWindow
Q_OBJECT
public:
- explicit QIOSWindow(QWindow *window);
+ explicit QIOSWindow(QWindow *window, WId nativeHandle = 0);
~QIOSWindow();
void setGeometry(const QRect &rect) override;
@@ -56,13 +56,20 @@ public:
void requestUpdate() override;
+ void setMask(const QRegion &region) override;
+
+#if QT_CONFIG(opengl)
CAEAGLLayer *eaglLayer() const;
+#endif
+
+ bool isForeignWindow() const override;
+ UIView *view() const;
private:
void applicationStateChanged(Qt::ApplicationState state);
void applyGeometry(const QRect &rect);
- QUIView *m_view;
+ UIView *m_view;
QRect m_normalGeometry;
int m_windowLevel;
@@ -78,6 +85,8 @@ private:
QDebug operator<<(QDebug debug, const QIOSWindow *window);
#endif
+QT_MANGLE_NAMESPACE(QUIView) *quiview_cast(UIView *view);
+
QT_END_NAMESPACE
#endif // QIOSWINDOW_H
diff --git a/src/plugins/platforms/ios/qioswindow.mm b/src/plugins/platforms/ios/qioswindow.mm
index e88f5ab3d0..3631d6be70 100644
--- a/src/plugins/platforms/ios/qioswindow.mm
+++ b/src/plugins/platforms/ios/qioswindow.mm
@@ -4,7 +4,6 @@
#include "qioswindow.h"
#include "qiosapplicationdelegate.h"
-#include "qioscontext.h"
#include "qiosglobal.h"
#include "qiosintegration.h"
#include "qiosscreen.h"
@@ -12,10 +11,16 @@
#include "quiview.h"
#include "qiosinputcontext.h"
+#include <QtCore/private/qcore_mac_p.h>
+
#include <QtGui/private/qwindow_p.h>
+#include <QtGui/private/qhighdpiscaling_p.h>
#include <qpa/qplatformintegration.h>
+#if QT_CONFIG(opengl)
#import <QuartzCore/CAEAGLLayer.h>
+#endif
+
#ifdef Q_OS_IOS
#import <QuartzCore/CAMetalLayer.h>
#endif
@@ -24,34 +29,47 @@
QT_BEGIN_NAMESPACE
-QIOSWindow::QIOSWindow(QWindow *window)
+enum {
+ defaultWindowWidth = 160,
+ defaultWindowHeight = 160
+};
+
+QIOSWindow::QIOSWindow(QWindow *window, WId nativeHandle)
: QPlatformWindow(window)
, m_windowLevel(0)
{
+ if (nativeHandle) {
+ m_view = reinterpret_cast<UIView *>(nativeHandle);
+ [m_view retain];
+ } else {
#ifdef Q_OS_IOS
- if (window->surfaceType() == QSurface::RasterSurface)
- window->setSurfaceType(QSurface::MetalSurface);
+ if (window->surfaceType() == QSurface::RasterSurface)
+ window->setSurfaceType(QSurface::MetalSurface);
- if (window->surfaceType() == QSurface::MetalSurface)
- m_view = [[QUIMetalView alloc] initWithQIOSWindow:this];
- else
+ if (window->surfaceType() == QSurface::MetalSurface)
+ m_view = [[QUIMetalView alloc] initWithQIOSWindow:this];
+ else
#endif
- m_view = [[QUIView alloc] initWithQIOSWindow:this];
+ m_view = [[QUIView alloc] initWithQIOSWindow:this];
+ }
connect(qGuiApp, &QGuiApplication::applicationStateChanged, this, &QIOSWindow::applicationStateChanged);
setParent(QPlatformWindow::parent());
- // Resolve default window geometry in case it was not set before creating the
- // platform window. This picks up eg. minimum-size if set, and defaults to
- // the "maxmized" geometry (even though we're not in that window state).
- // FIXME: Detect if we apply a maximized geometry and send a window state
- // change event in that case.
- m_normalGeometry = initialGeometry(window, QPlatformWindow::geometry(),
- screen()->availableGeometry().width(), screen()->availableGeometry().height());
+ if (!isForeignWindow()) {
+ // Resolve default window geometry in case it was not set before creating the
+ // platform window. This picks up eg. minimum-size if set.
+ m_normalGeometry = initialGeometry(window, QPlatformWindow::geometry(),
+ defaultWindowWidth, defaultWindowHeight);
- setWindowState(window->windowStates());
- setOpacity(window->opacity());
+ setWindowState(window->windowStates());
+ setOpacity(window->opacity());
+ setMask(QHighDpi::toNativeLocalRegion(window->mask(), window));
+ } else {
+ // Pick up essential foreign window state
+ QPlatformWindow::setGeometry(QRectF::fromCGRect(m_view.frame).toRect());
+ }
Qt::ScreenOrientation initialOrientation = window->contentOrientation();
if (initialOrientation != Qt::PrimaryOrientation) {
@@ -73,8 +91,15 @@ QIOSWindow::~QIOSWindow()
[m_view touchesCancelled:[NSSet set] withEvent:0];
clearAccessibleCache();
- m_view.platformWindow = 0;
- [m_view removeFromSuperview];
+
+ quiview_cast(m_view).platformWindow = nullptr;
+
+ // Remove from superview, unless we're a foreign window without a
+ // Qt window parent, in which case the foreign window is used as
+ // a window container for a Qt UI hierarchy inside a native UI.
+ if (!(isForeignWindow() && !QPlatformWindow::parent()))
+ [m_view removeFromSuperview];
+
[m_view release];
}
@@ -113,7 +138,7 @@ void QIOSWindow::setVisible(bool visible)
if (visible && shouldAutoActivateWindow()) {
if (!window()->property("_q_showWithoutActivating").toBool())
requestActivateWindow();
- } else if (!visible && [m_view isActiveWindow]) {
+ } else if (!visible && [quiview_cast(m_view) isActiveWindow]) {
// Our window was active/focus window but now hidden, so relinquish
// focus to the next possible window in the stack.
NSArray<UIView *> *subviews = m_view.viewController.view.subviews;
@@ -202,7 +227,7 @@ void QIOSWindow::applyGeometry(const QRect &rect)
QMargins QIOSWindow::safeAreaMargins() const
{
- UIEdgeInsets safeAreaInsets = m_view.qt_safeAreaInsets;
+ UIEdgeInsets safeAreaInsets = m_view.safeAreaInsets;
return QMargins(safeAreaInsets.left, safeAreaInsets.top,
safeAreaInsets.right, safeAreaInsets.bottom);
}
@@ -226,22 +251,32 @@ void QIOSWindow::setWindowState(Qt::WindowStates state)
if (state & Qt::WindowMinimized) {
applyGeometry(QRect());
} else if (state & (Qt::WindowFullScreen | Qt::WindowMaximized)) {
- // When an application is in split-view mode, the UIScreen still has the
- // same geometry, but the UIWindow is resized to the area reserved for the
- // application. We use this to constrain the geometry used when applying the
- // fullscreen or maximized window states. Note that we do not do this
- // in applyGeometry(), as we don't want to artificially limit window
- // placement "outside" of the screen bounds if that's what the user wants.
-
QRect uiWindowBounds = QRectF::fromCGRect(m_view.window.bounds).toRect();
- QRect fullscreenGeometry = screen()->geometry().intersected(uiWindowBounds);
- QRect maximizedGeometry = window()->flags() & Qt::MaximizeUsingFullscreenGeometryHint ?
- fullscreenGeometry : screen()->availableGeometry().intersected(uiWindowBounds);
-
- if (state & Qt::WindowFullScreen)
- applyGeometry(fullscreenGeometry);
- else
- applyGeometry(maximizedGeometry);
+ if (NSProcessInfo.processInfo.iOSAppOnMac) {
+ // iOS apps running as "Designed for iPad" on macOS do not match
+ // our current window management implementation where a single
+ // UIWindow is tied to a single screen. And even if we're on the
+ // right screen, the UIScreen does not account for the 77% scale
+ // of the UIUserInterfaceIdiomPad environment, so we can't use
+ // it to clamp the window geometry. Instead just use the UIWindow
+ // directly, which represents our "screen".
+ applyGeometry(uiWindowBounds);
+ } else {
+ // When an application is in split-view mode, the UIScreen still has the
+ // same geometry, but the UIWindow is resized to the area reserved for the
+ // application. We use this to constrain the geometry used when applying the
+ // fullscreen or maximized window states. Note that we do not do this
+ // in applyGeometry(), as we don't want to artificially limit window
+ // placement "outside" of the screen bounds if that's what the user wants.
+ QRect fullscreenGeometry = screen()->geometry().intersected(uiWindowBounds);
+ QRect maximizedGeometry = window()->flags() & Qt::MaximizeUsingFullscreenGeometryHint ?
+ fullscreenGeometry : screen()->availableGeometry().intersected(uiWindowBounds);
+
+ if (state & Qt::WindowFullScreen)
+ applyGeometry(fullscreenGeometry);
+ else
+ applyGeometry(maximizedGeometry);
+ }
} else {
applyGeometry(m_normalGeometry);
}
@@ -249,10 +284,16 @@ void QIOSWindow::setWindowState(Qt::WindowStates state)
void QIOSWindow::setParent(const QPlatformWindow *parentWindow)
{
- UIView *parentView = parentWindow ? reinterpret_cast<UIView *>(parentWindow->winId())
- : isQtApplication() ? static_cast<QIOSScreen *>(screen())->uiWindow().rootViewController.view : 0;
-
- [parentView addSubview:m_view];
+ UIView *parentView = parentWindow ?
+ reinterpret_cast<UIView *>(parentWindow->winId())
+ : isQtApplication() && !isForeignWindow() ?
+ static_cast<QIOSScreen *>(screen())->uiWindow().rootViewController.view
+ : nullptr;
+
+ if (parentView)
+ [parentView addSubview:m_view];
+ else if (quiview_cast(m_view.superview))
+ [m_view removeFromSuperview];
}
void QIOSWindow::requestActivateWindow()
@@ -273,8 +314,6 @@ void QIOSWindow::requestActivateWindow()
void QIOSWindow::raiseOrLower(bool raise)
{
- // Re-insert m_view at the correct index among its sibling views
- // (QWindows) according to their current m_windowLevel:
if (!isQtApplication())
return;
@@ -282,17 +321,27 @@ void QIOSWindow::raiseOrLower(bool raise)
if (subviews.count == 1)
return;
- for (int i = int(subviews.count) - 1; i >= 0; --i) {
- UIView *view = static_cast<UIView *>([subviews objectAtIndex:i]);
- if (view.hidden || view == m_view || !view.qwindow)
- continue;
- int level = static_cast<QIOSWindow *>(view.qwindow->handle())->m_windowLevel;
- if (m_windowLevel > level || (raise && m_windowLevel == level)) {
- [m_view.superview insertSubview:m_view aboveSubview:view];
- return;
+ if (m_view.superview == m_view.qtViewController.view) {
+ // We're a top level window, so we need to take window
+ // levels into account.
+ for (int i = int(subviews.count) - 1; i >= 0; --i) {
+ UIView *view = static_cast<UIView *>([subviews objectAtIndex:i]);
+ if (view.hidden || view == m_view || !view.qwindow)
+ continue;
+ int level = static_cast<QIOSWindow *>(view.qwindow->handle())->m_windowLevel;
+ if (m_windowLevel > level || (raise && m_windowLevel == level)) {
+ [m_view.superview insertSubview:m_view aboveSubview:view];
+ return;
+ }
}
+ [m_view.superview insertSubview:m_view atIndex:0];
+ } else {
+ // Child window, or embedded into a non-Qt view controller
+ if (raise)
+ [m_view.superview bringSubviewToFront:m_view];
+ else
+ [m_view.superview sendSubviewToBack:m_view];
}
- [m_view.superview insertSubview:m_view atIndex:0];
}
void QIOSWindow::updateWindowLevel()
@@ -333,8 +382,11 @@ void QIOSWindow::handleContentOrientationChange(Qt::ScreenOrientation orientatio
void QIOSWindow::applicationStateChanged(Qt::ApplicationState)
{
+ if (isForeignWindow())
+ return;
+
if (window()->isExposed() != isExposed())
- [m_view sendUpdatedExposeEvent];
+ [quiview_cast(m_view) sendUpdatedExposeEvent];
}
qreal QIOSWindow::devicePixelRatio() const
@@ -344,7 +396,10 @@ qreal QIOSWindow::devicePixelRatio() const
void QIOSWindow::clearAccessibleCache()
{
- [m_view clearAccessibleCache];
+ if (isForeignWindow())
+ return;
+
+ [quiview_cast(m_view) clearAccessibleCache];
}
void QIOSWindow::requestUpdate()
@@ -352,11 +407,27 @@ void QIOSWindow::requestUpdate()
static_cast<QIOSScreen *>(screen())->setUpdatesPaused(false);
}
+void QIOSWindow::setMask(const QRegion &region)
+{
+ if (!region.isEmpty()) {
+ QCFType<CGMutablePathRef> maskPath = CGPathCreateMutable();
+ for (const QRect &r : region)
+ CGPathAddRect(maskPath, nullptr, r.toCGRect());
+ CAShapeLayer *maskLayer = [CAShapeLayer layer];
+ maskLayer.path = maskPath;
+ m_view.layer.mask = maskLayer;
+ } else {
+ m_view.layer.mask = nil;
+ }
+}
+
+#if QT_CONFIG(opengl)
CAEAGLLayer *QIOSWindow::eaglLayer() const
{
Q_ASSERT([m_view.layer isKindOfClass:[CAEAGLLayer class]]);
return static_cast<CAEAGLLayer *>(m_view.layer);
}
+#endif
#ifndef QT_NO_DEBUG_STREAM
QDebug operator<<(QDebug debug, const QIOSWindow *window)
@@ -371,6 +442,37 @@ QDebug operator<<(QDebug debug, const QIOSWindow *window)
}
#endif // !QT_NO_DEBUG_STREAM
+/*!
+ Returns the view cast to a QUIview if possible.
+
+ If the view is not a QUIview, nil is returned, which is safe to
+ send messages to, effectively making [quiview_cast(view) message]
+ a no-op.
+
+ For extra verbosity and clearer code, please consider checking
+ that the platform window is not a foreign window before using
+ this cast, via QPlatformWindow::isForeignWindow().
+
+ Do not use this method solely to check for foreign windows, as
+ that will make the code harder to read for people not working
+ primarily on iOS, who do not know the difference between the
+ UIView and QUIView cases.
+*/
+QUIView *quiview_cast(UIView *view)
+{
+ return qt_objc_cast<QUIView *>(view);
+}
+
+bool QIOSWindow::isForeignWindow() const
+{
+ return ![m_view isKindOfClass:QUIView.class];
+}
+
+UIView *QIOSWindow::view() const
+{
+ return m_view;
+}
+
QT_END_NAMESPACE
#include "moc_qioswindow.cpp"
diff --git a/src/plugins/platforms/ios/quiaccessibilityelement.h b/src/plugins/platforms/ios/quiaccessibilityelement.h
index e78fef6d30..8580325436 100644
--- a/src/plugins/platforms/ios/quiaccessibilityelement.h
+++ b/src/plugins/platforms/ios/quiaccessibilityelement.h
@@ -14,7 +14,7 @@
@property (readonly) QAccessible::Id axid;
- (instancetype)initWithId:(QAccessible::Id)anId withAccessibilityContainer:(id)view;
-+ (instancetype)elementWithId:(QAccessible::Id)anId withAccessibilityContainer:(id)view;
++ (instancetype)elementWithId:(QAccessible::Id)anId;
@end
diff --git a/src/plugins/platforms/ios/quiaccessibilityelement.mm b/src/plugins/platforms/ios/quiaccessibilityelement.mm
index 08e366f32b..39b2cb8a50 100644
--- a/src/plugins/platforms/ios/quiaccessibilityelement.mm
+++ b/src/plugins/platforms/ios/quiaccessibilityelement.mm
@@ -8,6 +8,7 @@
#include "private/qaccessiblecache_p.h"
#include "private/qcore_mac_p.h"
#include "uistrings_p.h"
+#include "qioswindow.h"
QT_NAMESPACE_ALIAS_OBJC_CLASS(QMacAccessibilityElement);
@@ -23,7 +24,7 @@ QT_NAMESPACE_ALIAS_OBJC_CLASS(QMacAccessibilityElement);
return self;
}
-+ (instancetype)elementWithId:(QAccessible::Id)anId withAccessibilityContainer:(id)view
++ (instancetype)elementWithId:(QAccessible::Id)anId
{
Q_ASSERT(anId);
if (!anId)
@@ -33,9 +34,17 @@ QT_NAMESPACE_ALIAS_OBJC_CLASS(QMacAccessibilityElement);
QMacAccessibilityElement *element = cache->elementForId(anId);
if (!element) {
- Q_ASSERT(QAccessible::accessibleInterface(anId));
- element = [[self alloc] initWithId:anId withAccessibilityContainer:view];
- cache->insertElement(anId, element);
+ auto *a11yInterface = QAccessible::accessibleInterface(anId);
+ Q_ASSERT(a11yInterface);
+ auto *window = a11yInterface->window();
+ if (window && window->handle()) {
+ auto *platformWindow = static_cast<QIOSWindow*>(window->handle());
+ element = [[self alloc] initWithId:anId withAccessibilityContainer:platformWindow->view()];
+ cache->insertElement(anId, element);
+ } else {
+ qWarning() << "Could not create a11y element for" << window
+ << "with platform window" << (window ? window->handle() : nullptr);
+ }
}
return element;
}
diff --git a/src/plugins/platforms/ios/quiview.h b/src/plugins/platforms/ios/quiview.h
index 6d1d19fd3d..5172585172 100644
--- a/src/plugins/platforms/ios/quiview.h
+++ b/src/plugins/platforms/ios/quiview.h
@@ -32,7 +32,6 @@ QT_END_NAMESPACE
- (QWindow *)qwindow;
- (UIViewController *)viewController;
- (QIOSViewController*)qtViewController;
-@property (nonatomic, readonly) UIEdgeInsets qt_safeAreaInsets;
@end
#ifdef Q_OS_IOS
diff --git a/src/plugins/platforms/ios/quiview.mm b/src/plugins/platforms/ios/quiview.mm
index 85f27f8450..f1103dae9c 100644
--- a/src/plugins/platforms/ios/quiview.mm
+++ b/src/plugins/platforms/ios/quiview.mm
@@ -24,10 +24,35 @@
Q_LOGGING_CATEGORY(lcQpaTablet, "qt.qpa.input.tablet")
Q_LOGGING_CATEGORY(lcQpaInputEvents, "qt.qpa.input.events")
+namespace {
+inline ulong getTimeStamp(UIEvent *event)
+{
+#if TARGET_OS_SIMULATOR == 1
+ // We currently build Qt for simulator using X86_64, even on ARM based macs.
+ // This results in the simulator running on ARM, while the app is running
+ // inside it using Rosetta. And with this combination, the event.timestamp, which is
+ // documented to be in seconds, looks to be something else, and is not progressing
+ // in sync with a normal clock.
+ // Sending out mouse events with a timestamp that doesn't follow normal clock time
+ // will cause problems for mouse-, and pointer handlers that uses them to e.g calculate
+ // the time between a press and release, and to decide if the user is performing a tap
+ // or a drag.
+ // For that reason, we choose to ignore UIEvent.timestamp under the mentioned condition, and
+ // instead rely on NSProcessInfo. Note that if we force the whole simulator to use Rosetta
+ // (and not only the Qt app), the timestamps will progress normally.
+#if defined(Q_PROCESSOR_ARM)
+ #warning The timestamp work-around for x86_64 can (probably) be removed when building for ARM
+#endif
+ return ulong(NSProcessInfo.processInfo.systemUptime * 1000);
+#endif
+
+ return ulong(event.timestamp * 1000);
+}
+}
+
@implementation QUIView {
QHash<NSUInteger, QWindowSystemInterface::TouchPoint> m_activeTouches;
UITouch *m_activePencilTouch;
- int m_nextTouchId;
NSMutableArray<UIAccessibilityElement *> *m_accessibleElements;
UIPanGestureRecognizer *m_scrollGestureRecognizer;
CGPoint m_lastScrollCursorPos;
@@ -56,7 +81,10 @@ Q_LOGGING_CATEGORY(lcQpaInputEvents, "qt.qpa.input.events")
+ (Class)layerClass
{
+#if QT_CONFIG(opengl)
return [CAEAGLLayer class];
+#endif
+ return [super layerClass];
}
- (instancetype)initWithQIOSWindow:(QT_PREPEND_NAMESPACE(QIOSWindow) *)window
@@ -79,6 +107,17 @@ Q_LOGGING_CATEGORY(lcQpaInputEvents, "qt.qpa.input.events")
m_lastScrollDelta = CGPointZero;
m_lastScrollCursorPos = CGPointZero;
[self addGestureRecognizer:m_scrollGestureRecognizer];
+
+ if ([self.layer isKindOfClass:CAMetalLayer.class]) {
+ QWindow *window = self.platformWindow->window();
+ if (QColorSpace colorSpace = window->format().colorSpace(); colorSpace.isValid()) {
+ QCFType<CFDataRef> iccData = colorSpace.iccProfile().toCFData();
+ QCFType<CGColorSpaceRef> cgColorSpace = CGColorSpaceCreateWithICCData(iccData);
+ CAMetalLayer *metalLayer = static_cast<CAMetalLayer *>(self.layer);
+ metalLayer.colorspace = cgColorSpace;
+ qCDebug(lcQpaWindow) << "Set" << self << "color space to" << metalLayer.colorspace;
+ }
+ }
}
return self;
@@ -87,6 +126,7 @@ Q_LOGGING_CATEGORY(lcQpaInputEvents, "qt.qpa.input.events")
- (instancetype)initWithFrame:(CGRect)frame
{
if ((self = [super initWithFrame:frame])) {
+#if QT_CONFIG(opengl)
if ([self.layer isKindOfClass:[CAEAGLLayer class]]) {
// Set up EAGL layer
CAEAGLLayer *eaglLayer = static_cast<CAEAGLLayer *>(self.layer);
@@ -96,6 +136,7 @@ Q_LOGGING_CATEGORY(lcQpaInputEvents, "qt.qpa.input.events")
kEAGLDrawablePropertyColorFormat: kEAGLColorFormatRGBA8
};
}
+#endif
if (isQtApplication())
self.hidden = YES;
@@ -222,6 +263,9 @@ Q_LOGGING_CATEGORY(lcQpaInputEvents, "qt.qpa.input.events")
Q_UNUSED(layer);
Q_ASSERT(layer == self.layer);
+ if (!self.platformWindow)
+ return;
+
[self sendUpdatedExposeEvent];
}
@@ -263,7 +307,7 @@ Q_LOGGING_CATEGORY(lcQpaInputEvents, "qt.qpa.input.events")
// blocked by this guard.
FirstResponderCandidate firstResponderCandidate(self);
- qImDebug() << "self:" << self << "first:" << [UIResponder currentFirstResponder];
+ qImDebug() << "self:" << self << "first:" << [UIResponder qt_currentFirstResponder];
if (![super becomeFirstResponder]) {
qImDebug() << self << "was not allowed to become first responder";
@@ -274,7 +318,7 @@ Q_LOGGING_CATEGORY(lcQpaInputEvents, "qt.qpa.input.events")
}
if (qGuiApp->focusWindow() != self.platformWindow->window())
- QWindowSystemInterface::handleWindowActivated(self.platformWindow->window(), Qt::ActiveWindowFocusReason);
+ QWindowSystemInterface::handleFocusWindowChanged(self.platformWindow->window(), Qt::ActiveWindowFocusReason);
else
qImDebug() << self.platformWindow->window() << "already active, not sending window activation";
@@ -302,7 +346,7 @@ Q_LOGGING_CATEGORY(lcQpaInputEvents, "qt.qpa.input.events")
- (BOOL)resignFirstResponder
{
- qImDebug() << "self:" << self << "first:" << [UIResponder currentFirstResponder];
+ qImDebug() << "self:" << self << "first:" << [UIResponder qt_currentFirstResponder];
if (![super resignFirstResponder])
return NO;
@@ -311,7 +355,7 @@ Q_LOGGING_CATEGORY(lcQpaInputEvents, "qt.qpa.input.events")
UIResponder *newResponder = FirstResponderCandidate::currentCandidate();
if ([self responderShouldTriggerWindowDeactivation:newResponder])
- QWindowSystemInterface::handleWindowActivated(nullptr, Qt::ActiveWindowFocusReason);
+ QWindowSystemInterface::handleFocusWindowChanged(nullptr, Qt::ActiveWindowFocusReason);
return YES;
}
@@ -325,7 +369,7 @@ Q_LOGGING_CATEGORY(lcQpaInputEvents, "qt.qpa.input.events")
if ([self isFirstResponder])
return YES;
- UIResponder *firstResponder = [UIResponder currentFirstResponder];
+ UIResponder *firstResponder = [UIResponder qt_currentFirstResponder];
if ([firstResponder isKindOfClass:[QIOSTextInputResponder class]]
&& [firstResponder nextResponder] == self)
return YES;
@@ -476,7 +520,10 @@ Q_LOGGING_CATEGORY(lcQpaInputEvents, "qt.qpa.input.events")
{
Q_ASSERT(!m_activeTouches.contains(touch.hash));
#endif
- m_activeTouches[touch.hash].id = m_nextTouchId++;
+ // Use window-independent touch identifiers, so that
+ // multi-touch works across windows.
+ static quint16 nextTouchId = 0;
+ m_activeTouches[touch.hash].id = nextTouchId++;
#if QT_CONFIG(tabletevent)
}
#endif
@@ -490,17 +537,17 @@ Q_LOGGING_CATEGORY(lcQpaInputEvents, "qt.qpa.input.events")
topLevel->requestActivateWindow();
}
- [self handleTouches:touches withEvent:event withState:QEventPoint::State::Pressed withTimestamp:ulong(event.timestamp * 1000)];
+ [self handleTouches:touches withEvent:event withState:QEventPoint::State::Pressed withTimestamp:getTimeStamp(event)];
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
- [self handleTouches:touches withEvent:event withState:QEventPoint::State::Updated withTimestamp:ulong(event.timestamp * 1000)];
+ [self handleTouches:touches withEvent:event withState:QEventPoint::State::Updated withTimestamp:getTimeStamp(event)];
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
- [self handleTouches:touches withEvent:event withState:QEventPoint::State::Released withTimestamp:ulong(event.timestamp * 1000)];
+ [self handleTouches:touches withEvent:event withState:QEventPoint::State::Released withTimestamp:getTimeStamp(event)];
// Remove ended touch points from the active set:
#ifndef Q_OS_TVOS
@@ -518,9 +565,6 @@ Q_LOGGING_CATEGORY(lcQpaInputEvents, "qt.qpa.input.events")
// tvOS only supports single touch
m_activeTouches.clear();
#endif
-
- if (m_activeTouches.isEmpty() && !m_activePencilTouch)
- m_nextTouchId = 0;
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
@@ -553,10 +597,9 @@ Q_LOGGING_CATEGORY(lcQpaInputEvents, "qt.qpa.input.events")
qWarning("Subset of active touches cancelled by UIKit");
m_activeTouches.clear();
- m_nextTouchId = 0;
m_activePencilTouch = nil;
- NSTimeInterval timestamp = event ? event.timestamp : [[NSProcessInfo processInfo] systemUptime];
+ ulong timestamp = event ? getTimeStamp(event) : ([[NSProcessInfo processInfo] systemUptime] * 1000);
QIOSIntegration *iosIntegration = static_cast<QIOSIntegration *>(QGuiApplicationPrivate::platformIntegration());
@@ -564,7 +607,7 @@ Q_LOGGING_CATEGORY(lcQpaInputEvents, "qt.qpa.input.events")
// event loop in response to the touch event (a dialog e.g.), which will deadlock
// the UIKit event delivery system (QTBUG-98651).
QWindowSystemInterface::handleTouchCancelEvent<QWindowSystemInterface::AsynchronousDelivery>(
- self.platformWindow->window(), ulong(timestamp * 1000), iosIntegration->touchDevice());
+ self.platformWindow->window(), timestamp, iosIntegration->touchDevice());
}
- (int)mapPressTypeToKey:(UIPress*)press withModifiers:(Qt::KeyboardModifiers)qtModifiers text:(QString &)text
@@ -589,13 +632,33 @@ Q_LOGGING_CATEGORY(lcQpaInputEvents, "qt.qpa.input.events")
return Qt::Key_unknown;
}
-- (bool)processPresses:(NSSet *)presses withType:(QEvent::Type)type {
+- (bool)isControlKey:(Qt::Key)key
+{
+ switch (key) {
+ case Qt::Key_Up:
+ case Qt::Key_Down:
+ case Qt::Key_Left:
+ case Qt::Key_Right:
+ return true;
+ default:
+ break;
+ }
+
+ return false;
+}
+
+- (bool)handlePresses:(NSSet<UIPress *> *)presses eventType:(QEvent::Type)type
+{
// Presses on Menu button will generate a Menu key event. By default, not handling
// this event will cause the application to return to Headboard (tvOS launcher).
// When handling the event (for example, as a back button), both press and
// release events must be handled accordingly.
+ if (!qApp->focusWindow())
+ return false;
+
+ bool eventHandled = false;
+ const bool imEnabled = QIOSInputContext::instance()->inputMethodAccepted();
- bool handled = false;
for (UIPress* press in presses) {
Qt::KeyboardModifiers qtModifiers = Qt::NoModifier;
if (@available(ios 13.4, *))
@@ -604,26 +667,15 @@ Q_LOGGING_CATEGORY(lcQpaInputEvents, "qt.qpa.input.events")
int key = [self mapPressTypeToKey:press withModifiers:qtModifiers text:text];
if (key == Qt::Key_unknown)
continue;
- if (QWindowSystemInterface::handleKeyEvent(self.platformWindow->window(), type, key,
- qtModifiers, text)) {
- handled = true;
- }
- }
-
- return handled;
-}
+ if (imEnabled && ![self isControlKey:Qt::Key(key)])
+ continue;
-- (BOOL)handlePresses:(NSSet<UIPress *> *)presses eventType:(QEvent::Type)type
-{
- bool handlePress = false;
- if (qApp->focusWindow()) {
- QInputMethodQueryEvent queryEvent(Qt::ImEnabled);
- if (qApp->focusObject() && QCoreApplication::sendEvent(qApp->focusObject(), &queryEvent))
- handlePress = queryEvent.value(Qt::ImEnabled).toBool();
- if (!handlePress && [self processPresses:presses withType:type])
- return true;
+ bool keyHandled = QWindowSystemInterface::handleKeyEvent(
+ self.platformWindow->window(), type, key, qtModifiers, text);
+ eventHandled = eventHandled || keyHandled;
}
- return false;
+
+ return eventHandled;
}
- (void)pressesBegan:(NSSet<UIPress *> *)presses withEvent:(UIPressesEvent *)event
@@ -635,14 +687,14 @@ Q_LOGGING_CATEGORY(lcQpaInputEvents, "qt.qpa.input.events")
- (void)pressesChanged:(NSSet<UIPress *> *)presses withEvent:(UIPressesEvent *)event
{
if (![self handlePresses:presses eventType:QEvent::KeyPress])
- [super pressesBegan:presses withEvent:event];
+ [super pressesChanged:presses withEvent:event];
[super pressesChanged:presses withEvent:event];
}
- (void)pressesEnded:(NSSet<UIPress *> *)presses withEvent:(UIPressesEvent *)event
{
if (![self handlePresses:presses eventType:QEvent::KeyRelease])
- [super pressesBegan:presses withEvent:event];
+ [super pressesEnded:presses withEvent:event];
[super pressesEnded:presses withEvent:event];
}
@@ -777,11 +829,6 @@ Q_LOGGING_CATEGORY(lcQpaInputEvents, "qt.qpa.input.events")
return nil;
}
-- (UIEdgeInsets)qt_safeAreaInsets
-{
- return self.safeAreaInsets;
-}
-
@end
#ifdef Q_OS_IOS
diff --git a/src/plugins/platforms/ios/quiview_accessibility.mm b/src/plugins/platforms/ios/quiview_accessibility.mm
index bb27d832d1..04e1f8cfb3 100644
--- a/src/plugins/platforms/ios/quiview_accessibility.mm
+++ b/src/plugins/platforms/ios/quiview_accessibility.mm
@@ -23,9 +23,11 @@
if (!iface || iface->state().invisible)
return;
- [self createAccessibleElement: iface];
for (int i = 0; i < iface->childCount(); ++i)
[self createAccessibleContainer: iface->child(i)];
+
+ // The container element must go last, so that it underlays all its children
+ [self createAccessibleElement:iface];
}
- (void)initAccessibility
@@ -52,7 +54,6 @@
- (void)clearAccessibleCache
{
[m_accessibleElements removeAllObjects];
- UIAccessibilityPostNotification(UIAccessibilityScreenChangedNotification, @"");
}
// this is a container, returning yes here means the functions below will never be called
diff --git a/src/plugins/platforms/linuxfb/CMakeLists.txt b/src/plugins/platforms/linuxfb/CMakeLists.txt
index 9162997fc8..9f75f53828 100644
--- a/src/plugins/platforms/linuxfb/CMakeLists.txt
+++ b/src/plugins/platforms/linuxfb/CMakeLists.txt
@@ -1,7 +1,5 @@
# Copyright (C) 2022 The Qt Company Ltd.
-# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-
-# Generated from linuxfb.pro.
+# SPDX-License-Identifier: BSD-3-Clause
#####################################################################
## QLinuxFbIntegrationPlugin Plugin:
@@ -10,7 +8,7 @@
qt_internal_add_plugin(QLinuxFbIntegrationPlugin
OUTPUT_NAME qlinuxfb
PLUGIN_TYPE platforms
- DEFAULT_IF ${QT_QPA_DEFAULT_PLATFORM} MATCHES linuxfb # special case
+ DEFAULT_IF ${QT_QPA_DEFAULT_PLATFORM} MATCHES linuxfb
SOURCES
main.cpp
qlinuxfbintegration.cpp qlinuxfbintegration.h
@@ -25,9 +23,6 @@ qt_internal_add_plugin(QLinuxFbIntegrationPlugin
Qt::GuiPrivate
)
-#### Keys ignored in scope 1:.:.:linuxfb.pro:<TRUE>:
-# OTHER_FILES = "linuxfb.json"
-
## Scopes:
#####################################################################
@@ -42,6 +37,3 @@ qt_internal_extend_target(QLinuxFbIntegrationPlugin CONDITION TARGET Qt::KmsSupp
LIBRARIES
Qt::KmsSupportPrivate
)
-
-#### Keys ignored in scope 4:.:.:linuxfb.pro:NOT TARGET___equals____ss_QT_DEFAULT_QPA_PLUGIN:
-# PLUGIN_EXTENDS = "-"
diff --git a/src/plugins/platforms/linuxfb/main.cpp b/src/plugins/platforms/linuxfb/main.cpp
index 9d05e64466..12aaafc21a 100644
--- a/src/plugins/platforms/linuxfb/main.cpp
+++ b/src/plugins/platforms/linuxfb/main.cpp
@@ -22,7 +22,7 @@ QPlatformIntegration* QLinuxFbIntegrationPlugin::create(const QString& system, c
if (!system.compare("linuxfb"_L1, Qt::CaseInsensitive))
return new QLinuxFbIntegration(paramList);
- return 0;
+ return nullptr;
}
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/linuxfb/qlinuxfbdrmscreen.cpp b/src/plugins/platforms/linuxfb/qlinuxfbdrmscreen.cpp
index beb8349635..56b28ba48c 100644
--- a/src/plugins/platforms/linuxfb/qlinuxfbdrmscreen.cpp
+++ b/src/plugins/platforms/linuxfb/qlinuxfbdrmscreen.cpp
@@ -62,7 +62,7 @@ public:
void swapBuffers(Output *output);
- int outputCount() const { return m_outputs.count(); }
+ int outputCount() const { return m_outputs.size(); }
Output *output(int idx) { return &m_outputs[idx]; }
private:
@@ -125,8 +125,7 @@ void QLinuxFbDevice::close()
void *QLinuxFbDevice::nativeDisplay() const
{
- Q_UNREACHABLE();
- return nullptr;
+ Q_UNREACHABLE_RETURN(nullptr);
}
QPlatformScreen *QLinuxFbDevice::createScreen(const QKmsOutput &output)
@@ -243,7 +242,7 @@ bool QLinuxFbDevice::createFramebuffer(QLinuxFbDevice::Output *output, int buffe
qErrnoWarning(errno, "Failed to map dumb buffer");
return false;
}
- fb.p = mmap(0, fb.size, PROT_READ | PROT_WRITE, MAP_SHARED, fd(), mreq.offset);
+ fb.p = mmap(nullptr, fb.size, PROT_READ | PROT_WRITE, MAP_SHARED, fd(), mreq.offset);
if (fb.p == MAP_FAILED) {
qErrnoWarning(errno, "Failed to mmap dumb buffer");
return false;
@@ -408,7 +407,7 @@ QRegion QLinuxFbDrmScreen::doRedraw()
// Image has alpha but no need for blending at this stage.
// Do not waste time with the default SourceOver.
pntr.setCompositionMode(QPainter::CompositionMode_Source);
- for (const QRect &rect : qAsConst(output->dirty[output->backFb]))
+ for (const QRect &rect : std::as_const(output->dirty[output->backFb]))
pntr.drawImage(rect, mScreenImage, rect);
pntr.end();
diff --git a/src/plugins/platforms/linuxfb/qlinuxfbintegration.cpp b/src/plugins/platforms/linuxfb/qlinuxfbintegration.cpp
index a9ab8463f4..f87766cf1b 100644
--- a/src/plugins/platforms/linuxfb/qlinuxfbintegration.cpp
+++ b/src/plugins/platforms/linuxfb/qlinuxfbintegration.cpp
@@ -42,7 +42,7 @@ QLinuxFbIntegration::QLinuxFbIntegration(const QStringList &paramList)
: m_primaryScreen(nullptr),
m_fontDb(new QGenericUnixFontDatabase),
m_services(new QGenericUnixServices),
- m_kbdMgr(0)
+ m_kbdMgr(nullptr)
{
#if QT_CONFIG(kms)
if (qEnvironmentVariableIntValue("QT_QPA_FB_DRM") != 0)
@@ -147,7 +147,7 @@ QPlatformNativeInterface *QLinuxFbIntegration::nativeInterface() const
QFunctionPointer QLinuxFbIntegration::platformFunction(const QByteArray &function) const
{
Q_UNUSED(function);
- return 0;
+ return nullptr;
}
#if QT_CONFIG(evdev)
diff --git a/src/plugins/platforms/linuxfb/qlinuxfbscreen.cpp b/src/plugins/platforms/linuxfb/qlinuxfbscreen.cpp
index 1fedb546eb..e2826f3946 100644
--- a/src/plugins/platforms/linuxfb/qlinuxfbscreen.cpp
+++ b/src/plugins/platforms/linuxfb/qlinuxfbscreen.cpp
@@ -213,7 +213,7 @@ static QImage::Format determineFormat(const fb_var_screeninfo &info, int depth)
static int openTtyDevice(const QString &device)
{
- const char *const devs[] = { "/dev/tty0", "/dev/tty", "/dev/console", 0 };
+ const char *const devs[] = { "/dev/tty0", "/dev/tty", "/dev/console", nullptr };
int fd = -1;
if (device.isEmpty()) {
@@ -253,9 +253,9 @@ static void blankScreen(int fd, bool on)
}
QLinuxFbScreen::QLinuxFbScreen(const QStringList &args)
- : mArgs(args), mFbFd(-1), mTtyFd(-1), mBlitter(0)
+ : mArgs(args), mFbFd(-1), mTtyFd(-1), mBlitter(nullptr)
{
- mMmap.data = 0;
+ mMmap.data = nullptr;
}
QLinuxFbScreen::~QLinuxFbScreen()
@@ -286,7 +286,7 @@ bool QLinuxFbScreen::initialize()
bool doSwitchToGraphicsMode = true;
// Parse arguments
- for (const QString &arg : qAsConst(mArgs)) {
+ for (const QString &arg : std::as_const(mArgs)) {
QRegularExpressionMatch match;
if (arg == "nographicsmodeswitch"_L1)
doSwitchToGraphicsMode = false;
@@ -344,7 +344,7 @@ bool QLinuxFbScreen::initialize()
// mmap the framebuffer
mMmap.size = finfo.smem_len;
- uchar *data = (unsigned char *)mmap(0, mMmap.size, PROT_READ | PROT_WRITE, MAP_SHARED, mFbFd, 0);
+ uchar *data = (unsigned char *)mmap(nullptr, mMmap.size, PROT_READ | PROT_WRITE, MAP_SHARED, mFbFd, 0);
if ((long)data == -1) {
qErrnoWarning(errno, "Failed to mmap framebuffer");
return false;
diff --git a/src/plugins/platforms/minimal/CMakeLists.txt b/src/plugins/platforms/minimal/CMakeLists.txt
index 3ce3ce2b79..f3683deccf 100644
--- a/src/plugins/platforms/minimal/CMakeLists.txt
+++ b/src/plugins/platforms/minimal/CMakeLists.txt
@@ -1,18 +1,16 @@
# Copyright (C) 2022 The Qt Company Ltd.
-# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-
-# Generated from minimal.pro.
+# SPDX-License-Identifier: BSD-3-Clause
#####################################################################
## QMinimalIntegrationPlugin Plugin:
#####################################################################
-qt_find_package(WrapFreetype PROVIDED_TARGETS WrapFreetype::WrapFreetype) # special case
+qt_find_package(WrapFreetype PROVIDED_TARGETS WrapFreetype::WrapFreetype)
qt_internal_add_plugin(QMinimalIntegrationPlugin
OUTPUT_NAME qminimal
PLUGIN_TYPE platforms
- DEFAULT_IF ${QT_QPA_DEFAULT_PLATFORM} MATCHES minimal # special case
+ DEFAULT_IF ${QT_QPA_DEFAULT_PLATFORM} MATCHES minimal
SOURCES
main.cpp
qminimalbackingstore.cpp qminimalbackingstore.h
@@ -26,9 +24,6 @@ qt_internal_add_plugin(QMinimalIntegrationPlugin
Qt::GuiPrivate
)
-#### Keys ignored in scope 1:.:.:minimal.pro:<TRUE>:
-# OTHER_FILES = "minimal.json"
-
## Scopes:
#####################################################################
@@ -36,6 +31,3 @@ qt_internal_extend_target(QMinimalIntegrationPlugin CONDITION QT_FEATURE_freetyp
LIBRARIES
WrapFreetype::WrapFreetype
)
-
-#### Keys ignored in scope 3:.:.:minimal.pro:NOT TARGET___equals____ss_QT_DEFAULT_QPA_PLUGIN:
-# PLUGIN_EXTENDS = "-"
diff --git a/src/plugins/platforms/minimal/main.cpp b/src/plugins/platforms/minimal/main.cpp
index 426ca2ad11..03cb3ca588 100644
--- a/src/plugins/platforms/minimal/main.cpp
+++ b/src/plugins/platforms/minimal/main.cpp
@@ -22,7 +22,7 @@ QPlatformIntegration *QMinimalIntegrationPlugin::create(const QString& system, c
if (!system.compare("minimal"_L1, Qt::CaseInsensitive))
return new QMinimalIntegration(paramList);
- return 0;
+ return nullptr;
}
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/minimal/qminimalintegration.cpp b/src/plugins/platforms/minimal/qminimalintegration.cpp
index 7523a2d966..4679967431 100644
--- a/src/plugins/platforms/minimal/qminimalintegration.cpp
+++ b/src/plugins/platforms/minimal/qminimalintegration.cpp
@@ -6,10 +6,11 @@
#include <QtGui/private/qpixmap_raster_p.h>
#include <QtGui/private/qguiapplication_p.h>
+#include <qpa/qplatformfontdatabase.h>
+#include <qpa/qplatformnativeinterface.h>
#include <qpa/qplatformwindow.h>
#include <qpa/qwindowsysteminterface.h>
-#include <QtGui/private/qfreetypefontdatabase_p.h>
#if defined(Q_OS_WIN)
# include <QtGui/private/qwindowsfontdatabase_p.h>
# if QT_CONFIG(freetype)
@@ -21,11 +22,11 @@
#if QT_CONFIG(fontconfig)
# include <QtGui/private/qgenericunixfontdatabase_p.h>
-# include <qpa/qplatformfontdatabase.h>
#endif
#if QT_CONFIG(freetype)
#include <QtGui/private/qfontengine_ft_p.h>
+#include <QtGui/private/qfreetypefontdatabase_p.h>
#endif
#if !defined(Q_OS_WIN)
@@ -57,7 +58,7 @@ static inline unsigned parseOptions(const QStringList &paramList)
}
QMinimalIntegration::QMinimalIntegration(const QStringList &parameters)
- : m_fontDatabase(0)
+ : m_fontDatabase(nullptr)
, m_options(parseOptions(parameters))
{
if (qEnvironmentVariableIsSet(debugBackingStoreEnvironmentVariable)
@@ -157,6 +158,13 @@ QAbstractEventDispatcher *QMinimalIntegration::createEventDispatcher() const
#endif
}
+QPlatformNativeInterface *QMinimalIntegration::nativeInterface() const
+{
+ if (!m_nativeInterface)
+ m_nativeInterface.reset(new QPlatformNativeInterface);
+ return m_nativeInterface.get();
+}
+
QMinimalIntegration *QMinimalIntegration::instance()
{
return static_cast<QMinimalIntegration *>(QGuiApplicationPrivate::platformIntegration());
diff --git a/src/plugins/platforms/minimal/qminimalintegration.h b/src/plugins/platforms/minimal/qminimalintegration.h
index 914f26bf25..6070972b1b 100644
--- a/src/plugins/platforms/minimal/qminimalintegration.h
+++ b/src/plugins/platforms/minimal/qminimalintegration.h
@@ -7,6 +7,8 @@
#include <qpa/qplatformintegration.h>
#include <qpa/qplatformscreen.h>
+#include <qscopedpointer.h>
+
QT_BEGIN_NAMESPACE
class QMinimalScreen : public QPlatformScreen
@@ -46,12 +48,15 @@ public:
QPlatformBackingStore *createPlatformBackingStore(QWindow *window) const override;
QAbstractEventDispatcher *createEventDispatcher() const override;
+ QPlatformNativeInterface *nativeInterface() const override;
+
unsigned options() const { return m_options; }
static QMinimalIntegration *instance();
private:
mutable QPlatformFontDatabase *m_fontDatabase;
+ mutable QScopedPointer<QPlatformNativeInterface> m_nativeInterface;
QMinimalScreen *m_primaryScreen;
unsigned m_options;
};
diff --git a/src/plugins/platforms/minimalegl/CMakeLists.txt b/src/plugins/platforms/minimalegl/CMakeLists.txt
index 286e8aa5af..a6ec8be781 100644
--- a/src/plugins/platforms/minimalegl/CMakeLists.txt
+++ b/src/plugins/platforms/minimalegl/CMakeLists.txt
@@ -1,8 +1,7 @@
# Copyright (C) 2022 The Qt Company Ltd.
-# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+# SPDX-License-Identifier: BSD-3-Clause
-# Generated from minimalegl.pro.
-qt_find_package(EGL) # special case
+qt_find_package(EGL)
#####################################################################
## QMinimalEglIntegrationPlugin Plugin:
@@ -11,7 +10,7 @@ qt_find_package(EGL) # special case
qt_internal_add_plugin(QMinimalEglIntegrationPlugin
OUTPUT_NAME qminimalegl
PLUGIN_TYPE platforms
- DEFAULT_IF ${QT_QPA_DEFAULT_PLATFORM} MATCHES minimalegl # special case
+ DEFAULT_IF ${QT_QPA_DEFAULT_PLATFORM} MATCHES minimalegl
SOURCES
main.cpp
qminimaleglintegration.cpp qminimaleglintegration.h
@@ -24,12 +23,9 @@ qt_internal_add_plugin(QMinimalEglIntegrationPlugin
Qt::CorePrivate
Qt::Gui
Qt::GuiPrivate
- EGL::EGL # special case
+ EGL::EGL
)
-#### Keys ignored in scope 1:.:.:minimalegl.pro:<TRUE>:
-# OTHER_FILES = "minimalegl.json"
-
## Scopes:
#####################################################################
@@ -39,6 +35,3 @@ qt_internal_extend_target(QMinimalEglIntegrationPlugin CONDITION QT_FEATURE_open
LIBRARIES
Qt::OpenGL
)
-
-#### Keys ignored in scope 3:.:.:minimalegl.pro:NOT TARGET___equals____ss_QT_DEFAULT_QPA_PLUGIN:
-# PLUGIN_EXTENDS = "-"
diff --git a/src/plugins/platforms/minimalegl/main.cpp b/src/plugins/platforms/minimalegl/main.cpp
index 8f7d91dce9..3586535277 100644
--- a/src/plugins/platforms/minimalegl/main.cpp
+++ b/src/plugins/platforms/minimalegl/main.cpp
@@ -22,7 +22,7 @@ QPlatformIntegration* QMinimalEglIntegrationPlugin::create(const QString& system
if (!system.compare("minimalegl"_L1, Qt::CaseInsensitive))
return new QMinimalEglIntegration;
- return 0;
+ return nullptr;
}
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/minimalegl/qminimaleglbackingstore.cpp b/src/plugins/platforms/minimalegl/qminimaleglbackingstore.cpp
index 5b18ffda2c..88131f4f99 100644
--- a/src/plugins/platforms/minimalegl/qminimaleglbackingstore.cpp
+++ b/src/plugins/platforms/minimalegl/qminimaleglbackingstore.cpp
@@ -11,7 +11,7 @@ QT_BEGIN_NAMESPACE
QMinimalEglBackingStore::QMinimalEglBackingStore(QWindow *window)
: QPlatformBackingStore(window)
, m_context(new QOpenGLContext)
- , m_device(0)
+ , m_device(nullptr)
{
m_context->setFormat(window->requestedFormat());
m_context->setScreen(window->screen());
diff --git a/src/plugins/platforms/minimalegl/qminimaleglscreen.cpp b/src/plugins/platforms/minimalegl/qminimaleglscreen.cpp
index 64123f0e8f..716caea067 100644
--- a/src/plugins/platforms/minimalegl/qminimaleglscreen.cpp
+++ b/src/plugins/platforms/minimalegl/qminimaleglscreen.cpp
@@ -41,8 +41,8 @@ public:
QMinimalEglScreen::QMinimalEglScreen(EGLNativeDisplayType display)
: m_depth(32)
, m_format(QImage::Format_Invalid)
- , m_platformContext(0)
- , m_surface(0)
+ , m_platformContext(nullptr)
+ , m_surface(nullptr)
{
#ifdef QEGL_EXTRA_DEBUG
qWarning("QEglScreen %p\n", this);
@@ -123,7 +123,7 @@ void QMinimalEglScreen::createAndSetPlatformContext()
q_printEglConfig(m_dpy, config);
#endif
- m_surface = eglCreateWindowSurface(m_dpy, config, eglWindow, NULL);
+ m_surface = eglCreateWindowSurface(m_dpy, config, eglWindow, nullptr);
if (Q_UNLIKELY(m_surface == EGL_NO_SURFACE)) {
qWarning("Could not create the egl surface: error = 0x%x\n", eglGetError());
eglTerminate(m_dpy);
@@ -132,7 +132,7 @@ void QMinimalEglScreen::createAndSetPlatformContext()
// qWarning("Created surface %dx%d\n", w, h);
#ifndef QT_NO_OPENGL
- QEGLPlatformContext *platformContext = new QMinimalEglContext(platformFormat, 0, m_dpy);
+ QEGLPlatformContext *platformContext = new QMinimalEglContext(platformFormat, nullptr, m_dpy);
m_platformContext = platformContext;
#endif
EGLint w,h; // screen size detection
diff --git a/src/plugins/platforms/offscreen/CMakeLists.txt b/src/plugins/platforms/offscreen/CMakeLists.txt
index 0a6d923f0c..09ad9a384d 100644
--- a/src/plugins/platforms/offscreen/CMakeLists.txt
+++ b/src/plugins/platforms/offscreen/CMakeLists.txt
@@ -1,7 +1,5 @@
# Copyright (C) 2022 The Qt Company Ltd.
-# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-
-# Generated from offscreen.pro.
+# SPDX-License-Identifier: BSD-3-Clause
#####################################################################
## QOffscreenIntegrationPlugin Plugin:
@@ -10,7 +8,7 @@
qt_internal_add_plugin(QOffscreenIntegrationPlugin
OUTPUT_NAME qoffscreen
PLUGIN_TYPE platforms
- DEFAULT_IF ${QT_QPA_DEFAULT_PLATFORM} MATCHES offscreen # special case
+ DEFAULT_IF ${QT_QPA_DEFAULT_PLATFORM} MATCHES offscreen
SOURCES
main.cpp
qoffscreencommon.cpp qoffscreencommon.h
@@ -25,9 +23,6 @@ qt_internal_add_plugin(QOffscreenIntegrationPlugin
Qt::GuiPrivate
)
-#### Keys ignored in scope 1:.:.:offscreen.pro:<TRUE>:
-# OTHER_FILES = "offscreen.json"
-
## Scopes:
#####################################################################
@@ -37,6 +32,3 @@ qt_internal_extend_target(QOffscreenIntegrationPlugin CONDITION QT_FEATURE_openg
LIBRARIES
X11::X11
)
-
-#### Keys ignored in scope 3:.:.:offscreen.pro:NOT TARGET___equals____ss_QT_DEFAULT_QPA_PLUGIN:
-# PLUGIN_EXTENDS = "-"
diff --git a/src/plugins/platforms/offscreen/qoffscreencommon.cpp b/src/plugins/platforms/offscreen/qoffscreencommon.cpp
index 6e063a2b56..923fffb29c 100644
--- a/src/plugins/platforms/offscreen/qoffscreencommon.cpp
+++ b/src/plugins/platforms/offscreen/qoffscreencommon.cpp
@@ -189,13 +189,13 @@ QPixmap QOffscreenBackingStore::grabWindow(WId window, const QRect &rect) const
QOffscreenBackingStore *QOffscreenBackingStore::backingStoreForWinId(WId id)
{
- return m_backingStoreForWinIdHash.value(id, 0);
+ return m_backingStoreForWinIdHash.value(id, nullptr);
}
void QOffscreenBackingStore::clearHash()
{
for (auto it = m_windowAreaHash.cbegin(), end = m_windowAreaHash.cend(); it != end; ++it) {
- const auto it2 = qAsConst(m_backingStoreForWinIdHash).find(it.key());
+ const auto it2 = std::as_const(m_backingStoreForWinIdHash).find(it.key());
if (it2.value() == this)
m_backingStoreForWinIdHash.erase(it2);
}
diff --git a/src/plugins/platforms/offscreen/qoffscreenintegration.cpp b/src/plugins/platforms/offscreen/qoffscreenintegration.cpp
index cb02205601..ea8042928a 100644
--- a/src/plugins/platforms/offscreen/qoffscreenintegration.cpp
+++ b/src/plugins/platforms/offscreen/qoffscreenintegration.cpp
@@ -82,8 +82,8 @@ QOffscreenIntegration::QOffscreenIntegration(const QStringList& paramList)
QOffscreenIntegration::~QOffscreenIntegration()
{
- for (auto screen : std::as_const(m_screens))
- QWindowSystemInterface::handleScreenRemoved(screen);
+ while (!m_screens.isEmpty())
+ QWindowSystemInterface::handleScreenRemoved(m_screens.takeLast());
}
/*
@@ -145,7 +145,7 @@ std::optional<QJsonObject> QOffscreenIntegration::resolveConfigFileConfiguration
QString configPrefix("configfile="_L1);
if (param.startsWith(configPrefix)) {
hasConfigFile = true;
- configFilePath = param.mid(configPrefix.length());
+ configFilePath = param.mid(configPrefix.size());
}
}
if (!hasConfigFile)
diff --git a/src/plugins/platforms/offscreen/qoffscreenwindow.cpp b/src/plugins/platforms/offscreen/qoffscreenwindow.cpp
index b009722e56..1a1471c687 100644
--- a/src/plugins/platforms/offscreen/qoffscreenwindow.cpp
+++ b/src/plugins/platforms/offscreen/qoffscreenwindow.cpp
@@ -86,7 +86,7 @@ void QOffscreenWindow::setVisible(bool visible)
if (visible) {
if (window()->type() != Qt::ToolTip)
- QWindowSystemInterface::handleWindowActivated(window(), Qt::ActiveWindowFocusReason);
+ QWindowSystemInterface::handleFocusWindowChanged(window(), Qt::ActiveWindowFocusReason);
if (m_pendingGeometryChangeOnShow) {
m_pendingGeometryChangeOnShow = false;
@@ -122,7 +122,7 @@ void QOffscreenWindow::setVisible(bool visible)
void QOffscreenWindow::requestActivateWindow()
{
if (m_visible)
- QWindowSystemInterface::handleWindowActivated(window(), Qt::ActiveWindowFocusReason);
+ QWindowSystemInterface::handleFocusWindowChanged(window(), Qt::ActiveWindowFocusReason);
}
WId QOffscreenWindow::winId() const
@@ -165,9 +165,9 @@ void QOffscreenWindow::setWindowState(Qt::WindowStates state)
QOffscreenWindow *QOffscreenWindow::windowForWinId(WId id)
{
- return m_windowForWinIdHash.value(id, 0);
+ return m_windowForWinIdHash.value(id, nullptr);
}
-QHash<WId, QOffscreenWindow *> QOffscreenWindow::m_windowForWinIdHash;
+Q_CONSTINIT QHash<WId, QOffscreenWindow *> QOffscreenWindow::m_windowForWinIdHash;
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/offscreen/qoffscreenwindow.h b/src/plugins/platforms/offscreen/qoffscreenwindow.h
index f1a437c64c..d525f2c657 100644
--- a/src/plugins/platforms/offscreen/qoffscreenwindow.h
+++ b/src/plugins/platforms/offscreen/qoffscreenwindow.h
@@ -41,7 +41,7 @@ private:
bool m_frameMarginsRequested;
WId m_winId;
- static QHash<WId, QOffscreenWindow *> m_windowForWinIdHash;
+ Q_CONSTINIT static QHash<WId, QOffscreenWindow *> m_windowForWinIdHash;
};
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/qnx/CMakeLists.txt b/src/plugins/platforms/qnx/CMakeLists.txt
index ad7719a09e..9fb412d8a4 100644
--- a/src/plugins/platforms/qnx/CMakeLists.txt
+++ b/src/plugins/platforms/qnx/CMakeLists.txt
@@ -1,7 +1,5 @@
# Copyright (C) 2022 The Qt Company Ltd.
-# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-
-# Generated from qnx.pro.
+# SPDX-License-Identifier: BSD-3-Clause
#####################################################################
## QQnxIntegrationPlugin Plugin:
@@ -10,7 +8,7 @@
qt_internal_add_plugin(QQnxIntegrationPlugin
OUTPUT_NAME qqnx
PLUGIN_TYPE platforms
- DEFAULT_IF ${QT_QPA_DEFAULT_PLATFORM} MATCHES qnx # special case
+ DEFAULT_IF ${QT_QPA_DEFAULT_PLATFORM} MATCHES qnx
SOURCES
main.cpp main.h
qqnxabstractcover.h
@@ -33,6 +31,12 @@ qt_internal_add_plugin(QQnxIntegrationPlugin
qqnxscreeneventthread.cpp qqnxscreeneventthread.h
qqnxservices.cpp qqnxservices.h
qqnxwindow.cpp qqnxwindow.h
+ NO_PCH_SOURCES
+ qqnxclipboard.cpp # undef QT_NO_FOREACH
+ qqnxintegration.cpp # undef QT_NO_FOREACH
+ qqnxscreen.cpp # undef QT_NO_FOREACH
+ qqnxscreeneventhandler.cpp # undef QT_NO_FOREACH
+ qqnxwindow.cpp # undef QT_NO_FOREACH
LIBRARIES
Qt::Core
Qt::CorePrivate
@@ -41,9 +45,6 @@ qt_internal_add_plugin(QQnxIntegrationPlugin
screen
)
-#### Keys ignored in scope 1:.:.:qnx.pro:<TRUE>:
-# OTHER_FILES = "qnx.json"
-
## Scopes:
#####################################################################
@@ -90,6 +91,3 @@ qt_internal_extend_target(QQnxIntegrationPlugin CONDITION lgmon
LIBRARIES
lgmon
)
-
-#### Keys ignored in scope 8:.:.:qnx.pro:NOT TARGET___equals____ss_QT_DEFAULT_QPA_PLUGIN:
-# PLUGIN_EXTENDS = "-"
diff --git a/src/plugins/platforms/qnx/qqnxbuttoneventnotifier.cpp b/src/plugins/platforms/qnx/qqnxbuttoneventnotifier.cpp
index bf66d54bba..6497367579 100644
--- a/src/plugins/platforms/qnx/qqnxbuttoneventnotifier.cpp
+++ b/src/plugins/platforms/qnx/qqnxbuttoneventnotifier.cpp
@@ -21,8 +21,8 @@
QT_BEGIN_NAMESPACE
-static const char *ppsPath = "/pps/system/buttons/status";
-static const int ppsBufferSize = 256;
+const char *QQnxButtonEventNotifier::ppsPath = "/pps/system/buttons/status";
+const size_t QQnxButtonEventNotifier::ppsBufferSize = 256;
QQnxButtonEventNotifier::QQnxButtonEventNotifier(QObject *parent)
: QObject(parent),
diff --git a/src/plugins/platforms/qnx/qqnxbuttoneventnotifier.h b/src/plugins/platforms/qnx/qqnxbuttoneventnotifier.h
index 987055f903..81ccf64415 100644
--- a/src/plugins/platforms/qnx/qqnxbuttoneventnotifier.h
+++ b/src/plugins/platforms/qnx/qqnxbuttoneventnotifier.h
@@ -45,6 +45,9 @@ private:
QSocketNotifier *m_readNotifier;
ButtonState m_state[ButtonCount];
QList<QByteArray> m_buttonKeys;
+
+ static const char *ppsPath;
+ static const size_t ppsBufferSize;
};
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/qnx/qqnxclipboard.cpp b/src/plugins/platforms/qnx/qqnxclipboard.cpp
index 80f9d73a3b..8e42148c12 100644
--- a/src/plugins/platforms/qnx/qqnxclipboard.cpp
+++ b/src/plugins/platforms/qnx/qqnxclipboard.cpp
@@ -1,6 +1,8 @@
// Copyright (C) 2011 - 2012 Research In Motion
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+#undef QT_NO_FOREACH // this file contains unported legacy Q_FOREACH uses
+
#if !defined(QT_NO_CLIPBOARD)
#include "qqnxclipboard.h"
diff --git a/src/plugins/platforms/qnx/qqnxintegration.cpp b/src/plugins/platforms/qnx/qqnxintegration.cpp
index ed9ad6ccbc..310d5d4c8a 100644
--- a/src/plugins/platforms/qnx/qqnxintegration.cpp
+++ b/src/plugins/platforms/qnx/qqnxintegration.cpp
@@ -1,6 +1,8 @@
// Copyright (C) 2013 BlackBerry Limited. All rights reserved.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+#undef QT_NO_FOREACH // this file contains unported legacy Q_FOREACH uses
+
#include "qqnxglobal.h"
#include "qqnxintegration.h"
@@ -98,7 +100,7 @@ static inline QQnxIntegration::Options parseOptions(const QStringList &paramList
static inline int getContextCapabilities(const QStringList &paramList)
{
- QString contextCapabilitiesPrefix = QStringLiteral("screen-context-capabilities=");
+ constexpr auto contextCapabilitiesPrefix = "screen-context-capabilities="_L1;
int contextCapabilities = SCREEN_APPLICATION_CONTEXT;
for (const QString &param : paramList) {
if (param.startsWith(contextCapabilitiesPrefix)) {
@@ -571,7 +573,7 @@ QList<screen_display_t *> QQnxIntegration::sortDisplays(screen_display_t *availa
// Go through all the requested displays IDs
QList<screen_display_t *> orderedDisplays;
- for (const QJsonValue &value : qAsConst(requestedDisplays)) {
+ for (const QJsonValue &value : std::as_const(requestedDisplays)) {
int requestedValue = value.toInt();
// Move all displays with matching ID from the intermediate list
diff --git a/src/plugins/platforms/qnx/qqnxintegration.h b/src/plugins/platforms/qnx/qqnxintegration.h
index 590b9450c2..f126300aed 100644
--- a/src/plugins/platforms/qnx/qqnxintegration.h
+++ b/src/plugins/platforms/qnx/qqnxintegration.h
@@ -6,6 +6,7 @@
#include <qpa/qplatformintegration.h>
#include <private/qtguiglobal_p.h>
+#include <QtCore/qhash.h>
#include <QtCore/qmutex.h>
#include <screen/screen.h>
@@ -39,8 +40,7 @@ class QQnxButtonEventNotifier;
class QQnxClipboard;
#endif
-template<class K, class V> class QHash;
-typedef QHash<screen_window_t, QWindow *> QQnxWindowMapper;
+using QQnxWindowMapper = QHash<screen_window_t, QWindow *>;
class QQnxIntegration : public QPlatformIntegration
{
diff --git a/src/plugins/platforms/qnx/qqnxnavigatoreventnotifier.cpp b/src/plugins/platforms/qnx/qqnxnavigatoreventnotifier.cpp
index 5d099b7e46..8024214e69 100644
--- a/src/plugins/platforms/qnx/qqnxnavigatoreventnotifier.cpp
+++ b/src/plugins/platforms/qnx/qqnxnavigatoreventnotifier.cpp
@@ -23,11 +23,11 @@
#define qNavigatorEventNotifierDebug QT_NO_QDEBUG_MACRO
#endif
-static const char *navigatorControlPath = "/pps/services/navigator/control";
-static const int ppsBufferSize = 4096;
-
QT_BEGIN_NAMESPACE
+const char *QQnxNavigatorEventNotifier::navigatorControlPath = "/pps/services/navigator/control";
+const size_t QQnxNavigatorEventNotifier::ppsBufferSize = 4096;
+
QQnxNavigatorEventNotifier::QQnxNavigatorEventNotifier(QQnxNavigatorEventHandler *eventHandler, QObject *parent)
: QObject(parent),
m_fd(-1),
diff --git a/src/plugins/platforms/qnx/qqnxnavigatoreventnotifier.h b/src/plugins/platforms/qnx/qqnxnavigatoreventnotifier.h
index 6ecf776f36..66100ece3f 100644
--- a/src/plugins/platforms/qnx/qqnxnavigatoreventnotifier.h
+++ b/src/plugins/platforms/qnx/qqnxnavigatoreventnotifier.h
@@ -32,6 +32,9 @@ private:
int m_fd;
QSocketNotifier *m_readNotifier;
QQnxNavigatorEventHandler *m_eventHandler;
+
+ static const char *navigatorControlPath;
+ static const size_t ppsBufferSize;
};
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/qnx/qqnxnavigatorpps.cpp b/src/plugins/platforms/qnx/qqnxnavigatorpps.cpp
index 9ca402822d..c945f3e98a 100644
--- a/src/plugins/platforms/qnx/qqnxnavigatorpps.cpp
+++ b/src/plugins/platforms/qnx/qqnxnavigatorpps.cpp
@@ -14,11 +14,11 @@
#define qNavigatorDebug QT_NO_QDEBUG_MACRO
#endif
-static const char *navigatorControlPath = "/pps/services/navigator/control";
-static const int ppsBufferSize = 4096;
-
QT_BEGIN_NAMESPACE
+const char *QQnxNavigatorPps::navigatorControlPath = "/pps/services/navigator/control";
+const size_t QQnxNavigatorPps::ppsBufferSize = 4096;
+
QQnxNavigatorPps::QQnxNavigatorPps(QObject *parent)
: QQnxAbstractNavigator(parent)
, m_fd(-1)
diff --git a/src/plugins/platforms/qnx/qqnxnavigatorpps.h b/src/plugins/platforms/qnx/qqnxnavigatorpps.h
index 7f23097bc9..cbe0f99621 100644
--- a/src/plugins/platforms/qnx/qqnxnavigatorpps.h
+++ b/src/plugins/platforms/qnx/qqnxnavigatorpps.h
@@ -26,8 +26,9 @@ private:
bool sendPpsMessage(const QByteArray &message, const QByteArray &data);
void parsePPS(const QByteArray &ppsData, QHash<QByteArray, QByteArray> &messageFields);
-private:
int m_fd;
+ static const char *navigatorControlPath;
+ static const size_t ppsBufferSize;
};
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/qnx/qqnxscreen.cpp b/src/plugins/platforms/qnx/qqnxscreen.cpp
index 66f8bfae01..9d689da991 100644
--- a/src/plugins/platforms/qnx/qqnxscreen.cpp
+++ b/src/plugins/platforms/qnx/qqnxscreen.cpp
@@ -1,6 +1,8 @@
// Copyright (C) 2011 - 2013 BlackBerry Limited. All rights reserved.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+#undef QT_NO_FOREACH // this file contains unported legacy Q_FOREACH uses
+
#include "qqnxglobal.h"
#include "qqnxscreen.h"
diff --git a/src/plugins/platforms/qnx/qqnxscreeneventhandler.cpp b/src/plugins/platforms/qnx/qqnxscreeneventhandler.cpp
index 525b22242c..e93a38f62c 100644
--- a/src/plugins/platforms/qnx/qqnxscreeneventhandler.cpp
+++ b/src/plugins/platforms/qnx/qqnxscreeneventhandler.cpp
@@ -1,6 +1,8 @@
// Copyright (C) 2013 BlackBerry Limited. All rights reserved.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+#undef QT_NO_FOREACH // this file contains unported legacy Q_FOREACH uses
+
#include "qqnxglobal.h"
#include "qqnxscreeneventhandler.h"
@@ -708,7 +710,7 @@ void QQnxScreenEventHandler::handleKeyboardFocusPropertyEvent(screen_window_t wi
}
if (focus && focusWindow != QGuiApplication::focusWindow())
- QWindowSystemInterface::handleWindowActivated(focusWindow, Qt::ActiveWindowFocusReason);
+ QWindowSystemInterface::handleFocusWindowChanged(focusWindow, Qt::ActiveWindowFocusReason);
else if (!focus && focusWindow == QGuiApplication::focusWindow())
m_focusLostTimer = startTimer(50);
}
diff --git a/src/plugins/platforms/qnx/qqnxwindow.cpp b/src/plugins/platforms/qnx/qqnxwindow.cpp
index 5310ff3c65..eed45227f6 100644
--- a/src/plugins/platforms/qnx/qqnxwindow.cpp
+++ b/src/plugins/platforms/qnx/qqnxwindow.cpp
@@ -1,6 +1,8 @@
// Copyright (C) 2011 - 2013 BlackBerry Limited. All rights reserved.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+#undef QT_NO_FOREACH // this file contains unported legacy Q_FOREACH uses
+
#include "qqnxglobal.h"
#include "qqnxwindow.h"
diff --git a/src/plugins/platforms/vkkhrdisplay/CMakeLists.txt b/src/plugins/platforms/vkkhrdisplay/CMakeLists.txt
index 1d32b3b808..719e5c45e6 100644
--- a/src/plugins/platforms/vkkhrdisplay/CMakeLists.txt
+++ b/src/plugins/platforms/vkkhrdisplay/CMakeLists.txt
@@ -1,5 +1,5 @@
# Copyright (C) 2022 The Qt Company Ltd.
-# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+# SPDX-License-Identifier: BSD-3-Clause
qt_find_package(WrapFreetype PROVIDED_TARGETS WrapFreetype::WrapFreetype)
diff --git a/src/plugins/platforms/vkkhrdisplay/main.cpp b/src/plugins/platforms/vkkhrdisplay/main.cpp
index c11305607b..aa2dc3abf5 100644
--- a/src/plugins/platforms/vkkhrdisplay/main.cpp
+++ b/src/plugins/platforms/vkkhrdisplay/main.cpp
@@ -21,7 +21,7 @@ QPlatformIntegration *QVkKhrDisplayIntegrationPlugin::create(const QString &syst
if (!system.compare("vkkhrdisplay"_L1, Qt::CaseInsensitive))
return new QVkKhrDisplayIntegration(paramList);
- return 0;
+ return nullptr;
}
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/vkkhrdisplay/qvkkhrdisplayintegration.cpp b/src/plugins/platforms/vkkhrdisplay/qvkkhrdisplayintegration.cpp
index 411cda4af8..502c2518f2 100644
--- a/src/plugins/platforms/vkkhrdisplay/qvkkhrdisplayintegration.cpp
+++ b/src/plugins/platforms/vkkhrdisplay/qvkkhrdisplayintegration.cpp
@@ -219,7 +219,11 @@ QPlatformWindow *QVkKhrDisplayIntegration::createPlatformWindow(QWindow *window)
{
if (window->surfaceType() != QSurface::VulkanSurface) {
qWarning("vkkhrdisplay platform plugin only supports QWindow with surfaceType == VulkanSurface");
- return nullptr;
+ // Assume VulkanSurface, better than crashing. Consider e.g. an autotest
+ // creating a default QWindow just to have something to be used with
+ // QRhi's Null backend. Continuing to set up a Vulkan window (even
+ // though the request was Raster or something) is better than failing to
+ // create a platform window, and may even be sufficient in some cases.
}
QVkKhrDisplayWindow *w = new QVkKhrDisplayWindow(window);
diff --git a/src/plugins/platforms/vkkhrdisplay/qvkkhrdisplayvulkaninstance.cpp b/src/plugins/platforms/vkkhrdisplay/qvkkhrdisplayvulkaninstance.cpp
index 96c7fbfbdd..2e8d60209e 100644
--- a/src/plugins/platforms/vkkhrdisplay/qvkkhrdisplayvulkaninstance.cpp
+++ b/src/plugins/platforms/vkkhrdisplay/qvkkhrdisplayvulkaninstance.cpp
@@ -46,6 +46,9 @@ void QVkKhrDisplayVulkanInstance::createOrAdoptInstance()
m_enumeratePhysicalDevices = (PFN_vkEnumeratePhysicalDevices)
m_vkGetInstanceProcAddr(m_vkInst, "vkEnumeratePhysicalDevices");
+ m_getPhysicalDeviceSurfaceSupportKHR = reinterpret_cast<PFN_vkGetPhysicalDeviceSurfaceSupportKHR>(
+ m_vkGetInstanceProcAddr(m_vkInst, "vkGetPhysicalDeviceSurfaceSupportKHR"));
+
// Use for first physical device, unless overridden by QT_VK_PHYSICAL_DEVICE_INDEX.
// This behavior matches what the Vulkan backend of QRhi would do.
@@ -81,10 +84,14 @@ bool QVkKhrDisplayVulkanInstance::supportsPresent(VkPhysicalDevice physicalDevic
uint32_t queueFamilyIndex,
QWindow *window)
{
- Q_UNUSED(physicalDevice);
- Q_UNUSED(queueFamilyIndex);
- Q_UNUSED(window);
- return true;
+ if (!m_getPhysicalDeviceSurfaceSupportKHR)
+ return true;
+
+ VkSurfaceKHR surface = QVulkanInstance::surfaceForWindow(window);
+ VkBool32 supported = false;
+ m_getPhysicalDeviceSurfaceSupportKHR(physicalDevice, queueFamilyIndex, surface, &supported);
+
+ return supported;
}
bool QVkKhrDisplayVulkanInstance::chooseDisplay()
@@ -136,7 +143,7 @@ bool QVkKhrDisplayVulkanInstance::chooseDisplay()
j, (void *) mode.displayMode,
mode.parameters.visibleRegion.width, mode.parameters.visibleRegion.height,
mode.parameters.refreshRate);
- if (j == wantedModeIndex) {
+ if (j == wantedModeIndex && i == wantedDisplayIndex) {
m_displayMode = mode.displayMode;
m_width = mode.parameters.visibleRegion.width;
m_height = mode.parameters.visibleRegion.height;
diff --git a/src/plugins/platforms/vkkhrdisplay/qvkkhrdisplayvulkaninstance.h b/src/plugins/platforms/vkkhrdisplay/qvkkhrdisplayvulkaninstance.h
index a47878e344..bf99dc037f 100644
--- a/src/plugins/platforms/vkkhrdisplay/qvkkhrdisplayvulkaninstance.h
+++ b/src/plugins/platforms/vkkhrdisplay/qvkkhrdisplayvulkaninstance.h
@@ -45,6 +45,7 @@ private:
QVulkanInstance *m_instance;
VkPhysicalDevice m_physDev = VK_NULL_HANDLE;
PFN_vkEnumeratePhysicalDevices m_enumeratePhysicalDevices = nullptr;
+ PFN_vkGetPhysicalDeviceSurfaceSupportKHR m_getPhysicalDeviceSurfaceSupportKHR = nullptr;
#if VK_KHR_display
PFN_vkGetPhysicalDeviceDisplayPropertiesKHR m_getPhysicalDeviceDisplayPropertiesKHR = nullptr;
PFN_vkGetDisplayModePropertiesKHR m_getDisplayModePropertiesKHR = nullptr;
diff --git a/src/plugins/platforms/vnc/CMakeLists.txt b/src/plugins/platforms/vnc/CMakeLists.txt
index a9086bdf48..25cb399bd0 100644
--- a/src/plugins/platforms/vnc/CMakeLists.txt
+++ b/src/plugins/platforms/vnc/CMakeLists.txt
@@ -1,7 +1,5 @@
# Copyright (C) 2022 The Qt Company Ltd.
-# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-
-# Generated from vnc.pro.
+# SPDX-License-Identifier: BSD-3-Clause
#####################################################################
## QVncIntegrationPlugin Plugin:
@@ -10,7 +8,7 @@
qt_internal_add_plugin(QVncIntegrationPlugin
OUTPUT_NAME qvnc
PLUGIN_TYPE platforms
- DEFAULT_IF ${QT_QPA_DEFAULT_PLATFORM} MATCHES vnc # special case
+ DEFAULT_IF ${QT_QPA_DEFAULT_PLATFORM} MATCHES vnc
SOURCES
main.cpp
qvnc.cpp qvnc_p.h
@@ -28,9 +26,6 @@ qt_internal_add_plugin(QVncIntegrationPlugin
Qt::Network
)
-#### Keys ignored in scope 1:.:.:vnc.pro:<TRUE>:
-# OTHER_FILES = "vnc.json"
-
## Scopes:
#####################################################################
@@ -38,6 +33,3 @@ qt_internal_extend_target(QVncIntegrationPlugin CONDITION TARGET Qt::InputSuppor
LIBRARIES
Qt::InputSupportPrivate
)
-
-#### Keys ignored in scope 3:.:.:vnc.pro:NOT TARGET___equals____ss_QT_DEFAULT_QPA_PLUGIN:
-# PLUGIN_EXTENDS = "-"
diff --git a/src/plugins/platforms/vnc/qvnc.cpp b/src/plugins/platforms/vnc/qvnc.cpp
index d09c6789b7..99e80e5801 100644
--- a/src/plugins/platforms/vnc/qvnc.cpp
+++ b/src/plugins/platforms/vnc/qvnc.cpp
@@ -567,7 +567,7 @@ void QVncClientCursor::changeCursor(QCursor *widgetCursor, QWindow *window)
cursor = *platformImage.image();
hotspot = platformImage.hotspot();
}
- for (auto client : qAsConst(clients))
+ for (auto client : std::as_const(clients))
client->setDirtyCursor();
}
@@ -583,7 +583,7 @@ void QVncClientCursor::addClient(QVncClient *client)
uint QVncClientCursor::removeClient(QVncClient *client)
{
clients.removeOne(client);
- return clients.count();
+ return clients.size();
}
#endif // QT_CONFIG(cursor)
@@ -613,7 +613,7 @@ QVncServer::~QVncServer()
void QVncServer::setDirty()
{
- for (auto client : qAsConst(clients))
+ for (auto client : std::as_const(clients))
client->setDirty(qvnc_screen->dirtyRegion);
qvnc_screen->clearDirty();
@@ -635,11 +635,10 @@ void QVncServer::newConnection()
void QVncServer::discardClient(QVncClient *client)
{
clients.removeOne(client);
+ qvnc_screen->disableClientCursor(client);
client->deleteLater();
- if (clients.isEmpty()) {
- qvnc_screen->disableClientCursor(client);
+ if (clients.isEmpty())
qvnc_screen->setPowerState(QPlatformScreen::PowerStateOff);
- }
}
inline QImage QVncServer::screenImage() const
diff --git a/src/plugins/platforms/vnc/qvncscreen.cpp b/src/plugins/platforms/vnc/qvncscreen.cpp
index eb8241138e..c87df20ef6 100644
--- a/src/plugins/platforms/vnc/qvncscreen.cpp
+++ b/src/plugins/platforms/vnc/qvncscreen.cpp
@@ -41,7 +41,7 @@ bool QVncScreen::initialize()
mDepth = 32;
mPhysicalSize = QSizeF(mGeometry.width()/96.*25.4, mGeometry.height()/96.*25.4);
- for (const QString &arg : qAsConst(mArgs)) {
+ for (const QString &arg : std::as_const(mArgs)) {
QRegularExpressionMatch match;
if (arg.contains(mmSizeRx, &match)) {
mPhysicalSize = QSizeF(match.captured("width").toDouble(), match.captured("height").toDouble());
@@ -113,9 +113,10 @@ void QVncScreen::disableClientCursor(QVncClient *client)
if (clientCount == 0) {
delete clientCursor;
clientCursor = nullptr;
- }
- mCursor = new QFbCursor(this);
+ if (mCursor == nullptr)
+ mCursor = new QFbCursor(this);
+ }
#else
Q_UNUSED(client);
#endif
diff --git a/src/plugins/platforms/wasm/CMakeLists.txt b/src/plugins/platforms/wasm/CMakeLists.txt
index 94a26da371..185b921a4f 100644
--- a/src/plugins/platforms/wasm/CMakeLists.txt
+++ b/src/plugins/platforms/wasm/CMakeLists.txt
@@ -1,7 +1,5 @@
# Copyright (C) 2022 The Qt Company Ltd.
-# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-
-# Generated from wasm.pro.
+# SPDX-License-Identifier: BSD-3-Clause
#####################################################################
## QWasmIntegrationPlugin Plugin:
@@ -9,30 +7,35 @@
qt_internal_add_plugin(QWasmIntegrationPlugin
OUTPUT_NAME qwasm
- DEFAULT_IF ${QT_QPA_DEFAULT_PLATFORM} MATCHES wasm # special case
+ DEFAULT_IF ${QT_QPA_DEFAULT_PLATFORM} MATCHES wasm
PLUGIN_TYPE platforms
STATIC
SOURCES
main.cpp
qwasmaccessibility.cpp qwasmaccessibility.h
+ qwasmbase64iconstore.cpp qwasmbase64iconstore.h
qwasmclipboard.cpp qwasmclipboard.h
qwasmcompositor.cpp qwasmcompositor.h
+ qwasmcssstyle.cpp qwasmcssstyle.h
qwasmcursor.cpp qwasmcursor.h
+ qwasmdom.cpp qwasmdom.h
qwasmevent.cpp qwasmevent.h
qwasmeventdispatcher.cpp qwasmeventdispatcher.h
- qwasmeventtranslator.cpp qwasmeventtranslator.h
qwasmfontdatabase.cpp qwasmfontdatabase.h
qwasmintegration.cpp qwasmintegration.h
+ qwasmkeytranslator.cpp qwasmkeytranslator.h
qwasmoffscreensurface.cpp qwasmoffscreensurface.h
qwasmopenglcontext.cpp qwasmopenglcontext.h
qwasmplatform.cpp qwasmplatform.h
qwasmscreen.cpp qwasmscreen.h
qwasmservices.cpp qwasmservices.h
- qwasmstring.cpp qwasmstring.h
- qwasmstylepixmaps_p.h
qwasmtheme.cpp qwasmtheme.h
qwasmwindow.cpp qwasmwindow.h
+ qwasmwindowclientarea.cpp qwasmwindowclientarea.h
+ qwasmwindowtreenode.cpp qwasmwindowtreenode.h
+ qwasmwindownonclientarea.cpp qwasmwindownonclientarea.h
qwasminputcontext.cpp qwasminputcontext.h
+ qwasmwindowstack.cpp qwasmwindowstack.h
qwasmdrag.cpp qwasmdrag.h
DEFINES
QT_EGL_NO_X11
@@ -46,7 +49,6 @@ qt_internal_add_plugin(QWasmIntegrationPlugin
# Resources:
set(wasmfonts_resource_files
- "${QtBase_SOURCE_DIR}/src/3rdparty/wasm/Vera.ttf"
"${QtBase_SOURCE_DIR}/src/3rdparty/wasm/DejaVuSans.ttf"
"${QtBase_SOURCE_DIR}/src/3rdparty/wasm/DejaVuSansMono.ttf"
)
@@ -59,6 +61,7 @@ qt_internal_add_resource(QWasmIntegrationPlugin "wasmfonts"
FILES
${wasmfonts_resource_files}
)
+
qt_internal_extend_target(QWasmIntegrationPlugin CONDITION QT_FEATURE_opengl
SOURCES
qwasmbackingstore.cpp qwasmbackingstore.h
@@ -67,13 +70,28 @@ qt_internal_extend_target(QWasmIntegrationPlugin CONDITION QT_FEATURE_opengl
Qt::OpenGLPrivate
)
-#### Keys ignored in scope 4:.:.:wasm.pro:NOT TARGET___equals____ss_QT_DEFAULT_QPA_PLUGIN:
# PLUGIN_EXTENDS = "-"
set(wasm_support_files
wasm_shell.html
qtloader.js
- qtlogo.svg
+ resources/qtlogo.svg
+)
+
+set(wasmwindow_resource_files
+ "resources/maximize.svg"
+ "resources/qtlogo.svg"
+ "resources/restore.svg"
+ "resources/x.svg"
+)
+
+qt_internal_add_resource(QWasmIntegrationPlugin "wasmwindow"
+ PREFIX
+ "/wasm-window"
+ BASE
+ "resources"
+ FILES
+ ${wasmwindow_resource_files}
)
qt_path_join(destination ${QT_INSTALL_DIR} "plugins/platforms")
diff --git a/src/plugins/platforms/wasm/main.cpp b/src/plugins/platforms/wasm/main.cpp
index 1b430829ad..f32ef5aab8 100644
--- a/src/plugins/platforms/wasm/main.cpp
+++ b/src/plugins/platforms/wasm/main.cpp
@@ -6,6 +6,8 @@
QT_BEGIN_NAMESPACE
+using namespace Qt::Literals::StringLiterals;
+
class QWasmIntegrationPlugin : public QPlatformIntegrationPlugin
{
Q_OBJECT
@@ -17,7 +19,7 @@ public:
QPlatformIntegration *QWasmIntegrationPlugin::create(const QString& system, const QStringList& paramList)
{
Q_UNUSED(paramList);
- if (!system.compare(QStringLiteral("wasm"), Qt::CaseInsensitive))
+ if (!system.compare("wasm"_L1, Qt::CaseInsensitive))
return new QWasmIntegration;
return nullptr;
diff --git a/src/plugins/platforms/wasm/qtloader.js b/src/plugins/platforms/wasm/qtloader.js
index 2de0ac95e8..bbc0ac68ab 100644
--- a/src/plugins/platforms/wasm/qtloader.js
+++ b/src/plugins/platforms/wasm/qtloader.js
@@ -1,581 +1,295 @@
-// Copyright (C) 2018 The Qt Company Ltd.
+// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
-// QtLoader provides javascript API for managing Qt application modules.
-//
-// QtLoader provides API on top of Emscripten which supports common lifecycle
-// tasks such as displaying placeholder content while the module downloads,
-// handing application exits, and checking for browser wasm support.
-//
-// There are two usage modes:
-// * Managed: QtLoader owns and manages the HTML display elements like
-// the loader and canvas.
-// * External: The embedding HTML page owns the display elements. QtLoader
-// provides event callbacks which the page reacts to.
-//
-// Managed mode usage:
-//
-// var config = {
-// containerElements : [$("container-id")];
-// }
-// var qtLoader = QtLoader(config);
-// qtLoader.loadEmscriptenModule("applicationName");
-//
-// External mode.usage:
-//
-// var config = {
-// canvasElements : [$("canvas-id")],
-// showLoader: function() {
-// loader.style.display = 'block'
-// canvas.style.display = 'hidden'
-// },
-// showCanvas: function() {
-// loader.style.display = 'hidden'
-// canvas.style.display = 'block'
-// return canvas;
-// }
-// }
-// var qtLoader = QtLoader(config);
-// qtLoader.loadEmscriptenModule("applicationName");
-//
-// Config keys
-//
-// containerElements : [container-element, ...]
-// One or more HTML elements. QtLoader will display loader elements
-// on these while loading the application, and replace the loader with a
-// canvas on load complete.
-// canvasElements : [canvas-element, ...]
-// One or more canvas elements.
-// showLoader : function(status, containerElement)
-// Optional loading element constructor function. Implement to create
-// a custom loading screen. This function may be called multiple times,
-// while preparing the application binary. "status" is a string
-// containing the loading sub-status, and may be either "Downloading",
-// or "Compiling". The browser may be using streaming compilation, in
-// which case the wasm module is compiled during downloading and the
-// there is no separate compile step.
-// showCanvas : function(containerElement)
-// Optional canvas constructor function. Implement to create custom
-// canvas elements.
-// showExit : function(crashed, exitCode, containerElement)
-// Optional exited element constructor function.
-// showError : function(crashed, exitCode, containerElement)
-// Optional error element constructor function.
-// statusChanged : function(newStatus)
-// Optional callback called when the status of the app has changed
-//
-// path : <string>
-// Prefix path for wasm file, realative to the loading HMTL file.
-// restartMode : "DoNotRestart", "RestartOnExit", "RestartOnCrash"
-// Controls whether the application should be reloaded on exits. The default is "DoNotRestart"
-// restartType : "RestartModule", "ReloadPage"
-// restartLimit : <int>
-// Restart attempts limit. The default is 10.
-// stdoutEnabled : <bool>
-// stderrEnabled : <bool>
-// environment : <object>
-// key-value environment variable pairs.
-//
-// QtLoader object API
-//
-// webAssemblySupported : bool
-// webGLSupported : bool
-// canLoadQt : bool
-// Reports if WebAssembly and WebGL are supported. These are requirements for
-// running Qt applications.
-// loadEmscriptenModule(applicationName)
-// Loads the application from the given emscripten javascript module file and wasm file
-// status
-// One of "Created", "Loading", "Running", "Exited".
-// crashed
-// Set to true if there was an unclean exit.
-// exitCode
-// main()/emscripten_force_exit() return code. Valid on status change to
-// "Exited", iff crashed is false.
-// exitText
-// Abort/exit message.
-// addCanvasElement
-// Add canvas at run-time. Adds a corresponding QScreen,
-// removeCanvasElement
-// Remove canvas at run-time. Removes the corresponding QScreen.
-// resizeCanvasElement
-// Signals to the application that a canvas has been resized.
-// setFontDpi
-// Sets the logical font dpi for the application.
-// module
-// Returns the Emscripten module object, or undefined if the module
-// has not been created yet. Note that the module object becomes available
-// at the very end of the loading sequence, _after_ the transition from
-// Loading to Running occurs.
-
-
-function QtLoader(config)
+/**
+ * Loads the instance of a WASM module.
+ *
+ * @param config May contain any key normally accepted by emscripten and the 'qt' extra key, with
+ * the following sub-keys:
+ * - environment: { [name:string] : string }
+ * environment variables set on the instance
+ * - onExit: (exitStatus: { text: string, code?: number, crashed: bool }) => void
+ * called when the application has exited for any reason. There are two cases:
+ * aborted: crashed is true, text contains an error message.
+ * exited: crashed is false, code contians the exit code.
+ *
+ * Note that by default Emscripten does not exit when main() returns. This behavior
+ * is controlled by the EXIT_RUNTIME linker flag; set "-s EXIT_RUNTIME=1" to make
+ * Emscripten tear down the runtime and exit when main() returns.
+ *
+ * - containerElements: HTMLDivElement[]
+ * Array of host elements for Qt screens. Each of these elements is mapped to a QScreen on
+ * launch.
+ * - fontDpi: number
+ * Specifies font DPI for the instance
+ * - onLoaded: () => void
+ * Called when the module has loaded.
+ * - entryFunction: (emscriptenConfig: object) => Promise<EmscriptenModule>
+ * Qt always uses emscripten's MODULARIZE option. This is the MODULARIZE entry function.
+ * - module: Promise<WebAssembly.Module>
+ * The module to create the instance from (optional). Specifying the module allows optimizing
+ * use cases where several instances are created from a single WebAssembly source.
+ * - qtdir: string
+ * Path to Qt installation. This path will be used for loading Qt shared libraries and plugins.
+ * The path is set to 'qt' by default, and is relative to the path of the web page's html file.
+ * This property is not in use when static linking is used, since this build mode includes all
+ * libraries and plugins in the wasm file.
+ * - preload: [string]: Array of file paths to json-encoded files which specifying which files to preload.
+ * The preloaded files will be downloaded at application startup and copied to the in-memory file
+ * system provided by Emscripten.
+ *
+ * Each json file must contain an array of source, destination objects:
+ * [
+ * {
+ * "source": "path/to/source",
+ * "destination": "/path/to/destination"
+ * },
+ * ...
+ * ]
+ * The source path is relative to the html file path. The destination path must be
+ * an absolute path.
+ *
+ * $QTDIR may be used as a placeholder for the "qtdir" configuration property (see @qtdir), for instance:
+ * "source": "$QTDIR/plugins/imageformats/libqjpeg.so"
+ * - localFonts.requestPermission: bool
+ * Whether Qt should request for local fonts access permission on startup (default false).
+ * - localFonts.familiesCollection string
+ * Specifies a collection of local fonts to load. Possible values are:
+ * "NoFontFamilies" : Don't load any font families
+ * "DefaultFontFamilies" : A subset of available font families; currently the "web-safe" fonts (default).
+ * "AllFontFamilies" : All local font families (not reccomended)
+ * - localFonts.extraFamilies: [string]
+ * Adds additional font families to be loaded at startup.
+ *
+ * @return Promise<instance: EmscriptenModule>
+ * The promise is resolved when the module has been instantiated and its main function has been
+ * called.
+ *
+ * @see https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/emscripten for
+ * EmscriptenModule
+ */
+async function qtLoad(config)
{
- // The Emscripten module and module configuration object. The module
- // object is created in completeLoadEmscriptenModule().
- self.module = undefined;
- self.moduleConfig = {};
-
- // Qt properties. These are propagated to the Emscripten module after
- // it has been created.
- self.qtContainerElements = undefined;
- self.qtFontDpi = 96;
-
- function webAssemblySupported() {
- return typeof WebAssembly !== "undefined"
- }
-
- function webGLSupported() {
- // We expect that WebGL is supported if WebAssembly is; however
- // the GPU may be blacklisted.
- try {
- var canvas = document.createElement("canvas");
- return !!(window.WebGLRenderingContext && (canvas.getContext("webgl") || canvas.getContext("experimental-webgl")));
- } catch (e) {
- return false;
+ const throwIfEnvUsedButNotExported = (instance, config) =>
+ {
+ const environment = config.environment;
+ if (!environment || Object.keys(environment).length === 0)
+ return;
+ const isEnvExported = typeof instance.ENV === 'object';
+ if (!isEnvExported)
+ throw new Error('ENV must be exported if environment variables are passed');
+ };
+
+ if (typeof config !== 'object')
+ throw new Error('config is required, expected an object');
+ if (typeof config.qt !== 'object')
+ throw new Error('config.qt is required, expected an object');
+ if (typeof config.qt.entryFunction !== 'function')
+ throw new Error('config.qt.entryFunction is required, expected a function');
+
+ config.qt.qtdir ??= 'qt';
+ config.qt.preload ??= [];
+
+ config.qtContainerElements = config.qt.containerElements;
+ delete config.qt.containerElements;
+ config.qtFontDpi = config.qt.fontDpi;
+ delete config.qt.fontDpi;
+
+ // Make Emscripten not call main(); this gives us more control over
+ // the startup sequence.
+ const originalNoInitialRun = config.noInitialRun;
+ const originalArguments = config.arguments;
+ config.noInitialRun = true;
+
+ // Used for rejecting a failed load's promise where emscripten itself does not allow it,
+ // like in instantiateWasm below. This allows us to throw in case of a load error instead of
+ // hanging on a promise to entry function, which emscripten unfortunately does.
+ let circuitBreakerReject;
+ const circuitBreaker = new Promise((_, reject) => { circuitBreakerReject = reject; });
+
+ // If module async getter is present, use it so that module reuse is possible.
+ if (config.qt.module) {
+ config.instantiateWasm = async (imports, successCallback) =>
+ {
+ try {
+ const module = await config.qt.module;
+ successCallback(
+ await WebAssembly.instantiate(module, imports), module);
+ } catch (e) {
+ circuitBreakerReject(e);
+ }
}
}
-
- function canLoadQt() {
- // The current Qt implementation requires WebAssembly (asm.js is not in use),
- // and also WebGL (there is no raster fallback).
- return webAssemblySupported() && webGLSupported();
- }
-
- function removeChildren(element) {
- while (element.firstChild) element.removeChild(element.firstChild);
- }
-
- function createCanvas() {
- var canvas = document.createElement("canvas");
- canvas.className = "QtCanvas";
- canvas.style.height = "100%";
- canvas.style.width = "100%";
-
- // Set contentEditable in order to enable clipboard events; hide the resulting focus frame.
- canvas.contentEditable = true;
- canvas.style.outline = "0px solid transparent";
- canvas.style.caretColor = "transparent";
- canvas.style.cursor = "default";
-
- return canvas;
- }
-
- // Set default state handler functions and create canvases if needed
- if (config.containerElements !== undefined) {
-
- config.canvasElements = config.containerElements.map(createCanvas);
-
- config.showError = config.showError || function(errorText, container) {
- removeChildren(container);
- var errorTextElement = document.createElement("text");
- errorTextElement.className = "QtError"
- errorTextElement.innerHTML = errorText;
- return errorTextElement;
+ const fetchJsonHelper = async path => (await fetch(path)).json();
+ const filesToPreload = (await Promise.all(config.qt.preload.map(fetchJsonHelper))).flat();
+ const qtPreRun = (instance) => {
+ // Copy qt.environment to instance.ENV
+ throwIfEnvUsedButNotExported(instance, config);
+ for (const [name, value] of Object.entries(config.qt.environment ?? {}))
+ instance.ENV[name] = value;
+
+ // Preload files from qt.preload
+ const makeDirs = (FS, filePath) => {
+ const parts = filePath.split("/");
+ let path = "/";
+ for (let i = 0; i < parts.length - 1; ++i) {
+ const part = parts[i];
+ if (part == "")
+ continue;
+ path += part + "/";
+ try {
+ FS.mkdir(path);
+ } catch (error) {
+ const EEXIST = 20;
+ if (error.errno != EEXIST)
+ throw error;
+ }
+ }
}
- config.showLoader = config.showLoader || function(loadingState, container) {
- removeChildren(container);
- var loadingText = document.createElement("text");
- loadingText.className = "QtLoading"
- loadingText.innerHTML = '<p><center> ${loadingState}...</center><p>';
- return loadingText;
- };
-
- config.showCanvas = config.showCanvas || function(canvas, container) {
- removeChildren(container);
+ const extractFilenameAndDir = (path) => {
+ const parts = path.split('/');
+ const filename = parts.pop();
+ const dir = parts.join('/');
+ return {
+ filename: filename,
+ dir: dir
+ };
}
-
- config.showExit = config.showExit || function(crashed, exitCode, container) {
- if (!crashed)
- return undefined;
-
- removeChildren(container);
- var fontSize = 54;
- var crashSymbols = ["\u{1F615}", "\u{1F614}", "\u{1F644}", "\u{1F928}", "\u{1F62C}",
- "\u{1F915}", "\u{2639}", "\u{1F62E}", "\u{1F61E}", "\u{1F633}"];
- var symbolIndex = Math.floor(Math.random() * crashSymbols.length);
- var errorHtml = `<font size='${fontSize}'> ${crashSymbols[symbolIndex]} </font>`
- var errorElement = document.createElement("text");
- errorElement.className = "QtExit"
- errorElement.innerHTML = errorHtml;
- return errorElement;
+ const preloadFile = (file) => {
+ makeDirs(instance.FS, file.destination);
+ const source = file.source.replace('$QTDIR', config.qt.qtdir);
+ const filenameAndDir = extractFilenameAndDir(file.destination);
+ instance.FS.createPreloadedFile(filenameAndDir.dir, filenameAndDir.filename, source, true, true);
}
+ const isFsExported = typeof instance.FS === 'object';
+ if (!isFsExported)
+ throw new Error('FS must be exported if preload is used');
+ filesToPreload.forEach(preloadFile);
}
- config.restartMode = config.restartMode || "DoNotRestart";
- config.restartLimit = config.restartLimit || 10;
-
- if (config.stdoutEnabled === undefined) config.stdoutEnabled = true;
- if (config.stderrEnabled === undefined) config.stderrEnabled = true;
-
- // Make sure config.path is defined and ends with "/" if needed
- if (config.path === undefined)
- config.path = "";
- if (config.path.length > 0 && !config.path.endsWith("/"))
- config.path = config.path.concat("/");
-
- if (config.environment === undefined)
- config.environment = {};
-
- var publicAPI = {};
- publicAPI.webAssemblySupported = webAssemblySupported();
- publicAPI.webGLSupported = webGLSupported();
- publicAPI.canLoadQt = canLoadQt();
- publicAPI.canLoadApplication = canLoadQt();
- publicAPI.status = undefined;
- publicAPI.loadEmscriptenModule = loadEmscriptenModule;
- publicAPI.addCanvasElement = addCanvasElement;
- publicAPI.removeCanvasElement = removeCanvasElement;
- publicAPI.resizeCanvasElement = resizeCanvasElement;
- publicAPI.setFontDpi = setFontDpi;
- publicAPI.fontDpi = fontDpi;
- publicAPI.module = module;
-
- self.restartCount = 0;
-
- function handleError(error) {
- self.error = error;
- setStatus("Error");
- console.error(error);
- }
-
- function fetchResource(filePath) {
- var fullPath = config.path + filePath;
- return fetch(fullPath).then(function(response) {
- if (!response.ok) {
- let err = response.status + " " + response.statusText + " " + response.url;
- handleError(err);
- return Promise.reject(err)
- } else {
- return response;
- }
- });
- }
-
- function fetchText(filePath) {
- return fetchResource(filePath).then(function(response) {
- return response.text();
- });
- }
-
- function fetchThenCompileWasm(response) {
- return response.arrayBuffer().then(function(data) {
- self.loaderSubState = "Compiling";
- setStatus("Loading") // trigger loaderSubState update
- return WebAssembly.compile(data);
- });
- }
-
- function fetchCompileWasm(filePath) {
- return fetchResource(filePath).then(function(response) {
- if (typeof WebAssembly.compileStreaming !== "undefined") {
- self.loaderSubState = "Downloading/Compiling";
- setStatus("Loading");
- return WebAssembly.compileStreaming(response).catch(function(error) {
- // compileStreaming may/will fail if the server does not set the correct
- // mime type (application/wasm) for the wasm file. Fall back to fetch,
- // then compile in this case.
- return fetchThenCompileWasm(response);
- });
- } else {
- // Fall back to fetch, then compile if compileStreaming is not supported
- return fetchThenCompileWasm(response);
- }
- });
+ if (!config.preRun)
+ config.preRun = [];
+ config.preRun.push(qtPreRun);
+
+ const originalLocateFile = config.locateFile;
+ config.locateFile = filename =>
+ {
+ const originalLocatedFilename = originalLocateFile ? originalLocateFile(filename) : filename;
+ if (originalLocatedFilename.startsWith('libQt6'))
+ return `${config.qt.qtdir}/lib/${originalLocatedFilename}`;
+ return originalLocatedFilename;
}
- function loadEmscriptenModule(applicationName) {
+ let onExitCalled = false;
+ const originalOnExit = config.onExit;
+ config.onExit = code => {
+ originalOnExit?.();
- // Loading in qtloader.js goes through four steps:
- // 1) Check prerequisites
- // 2) Download resources
- // 3) Configure the emscripten Module object
- // 4) Start the emcripten runtime, after which emscripten takes over
-
- // Check for Wasm & WebGL support; set error and return before downloading resources if missing
- if (!webAssemblySupported()) {
- handleError("Error: WebAssembly is not supported");
- return;
- }
- if (!webGLSupported()) {
- handleError("Error: WebGL is not supported");
- return;
- }
-
- // Continue waiting if loadEmscriptenModule() is called again
- if (publicAPI.status == "Loading")
- return;
- self.loaderSubState = "Downloading";
- setStatus("Loading");
-
- // Fetch emscripten generated javascript runtime
- var emscriptenModuleSource = undefined
- var emscriptenModuleSourcePromise = fetchText(applicationName + ".js").then(function(source) {
- emscriptenModuleSource = source
- });
-
- // Fetch and compile wasm module
- var wasmModule = undefined;
- var wasmModulePromise = fetchCompileWasm(applicationName + ".wasm").then(function (module) {
- wasmModule = module;
- });
-
- // Wait for all resources ready
- Promise.all([emscriptenModuleSourcePromise, wasmModulePromise]).then(function(){
- completeLoadEmscriptenModule(applicationName, emscriptenModuleSource, wasmModule);
- }).catch(function(error) {
- handleError(error);
- // An error here is fatal, abort
- self.moduleConfig.onAbort(error)
- });
- }
-
- function completeLoadEmscriptenModule(applicationName, emscriptenModuleSource, wasmModule) {
-
- // The wasm binary has been compiled into a module during resource download,
- // and is ready to be instantiated. Define the instantiateWasm callback which
- // emscripten will call to create the instance.
- self.moduleConfig.instantiateWasm = function(imports, successCallback) {
- WebAssembly.instantiate(wasmModule, imports).then(function(instance) {
- successCallback(instance, wasmModule);
- }, function(error) {
- handleError(error)
+ if (!onExitCalled) {
+ onExitCalled = true;
+ config.qt.onExit?.({
+ code,
+ crashed: false
});
- return {};
- };
-
- self.moduleConfig.locateFile = self.moduleConfig.locateFile || function(filename) {
- return config.path + filename;
- };
-
- // Attach status callbacks
- self.moduleConfig.setStatus = self.moduleConfig.setStatus || function(text) {
- // Currently the only usable status update from this function
- // is "Running..."
- if (text.startsWith("Running"))
- setStatus("Running");
- };
- self.moduleConfig.monitorRunDependencies = self.moduleConfig.monitorRunDependencies || function(left) {
- // console.log("monitorRunDependencies " + left)
- };
-
- // Attach standard out/err callbacks.
- self.moduleConfig.print = self.moduleConfig.print || function(text) {
- if (config.stdoutEnabled)
- console.log(text)
- };
- self.moduleConfig.printErr = self.moduleConfig.printErr || function(text) {
- if (config.stderrEnabled)
- console.warn(text)
- };
-
- // Error handling: set status to "Exited", update crashed and
- // exitCode according to exit type.
- // Emscripten will typically call printErr with the error text
- // as well. Note that emscripten may also throw exceptions from
- // async callbacks. These should be handled in window.onerror by user code.
- self.moduleConfig.onAbort = self.moduleConfig.onAbort || function(text) {
- publicAPI.crashed = true;
- publicAPI.exitText = text;
- setStatus("Exited");
- };
- self.moduleConfig.quit = self.moduleConfig.quit || function(code, exception) {
-
- // Emscripten (and Qt) supports exiting from main() while keeping the app
- // running. Don't transition into the "Exited" state for clean exits.
- if (code == 0)
- return;
-
- if (exception.name == "ExitStatus") {
- // Clean exit with code
- publicAPI.exitText = undefined
- publicAPI.exitCode = code;
- } else {
- publicAPI.exitText = exception.toString();
- publicAPI.crashed = true;
- }
- setStatus("Exited");
- };
-
- self.moduleConfig.preRun = self.moduleConfig.preRun || []
- self.moduleConfig.preRun.push(function(module) {
- // Set environment variables
- for (var [key, value] of Object.entries(config.environment)) {
- module.ENV[key.toUpperCase()] = value;
- }
- // Propagate Qt module properties
- module.qtContainerElements = self.qtContainerElements;
- module.qtFontDpi = self.qtFontDpi;
- });
-
- self.moduleConfig.mainScriptUrlOrBlob = new Blob([emscriptenModuleSource], {type: 'text/javascript'});
-
- self.qtContainerElements = config.canvasElements;
-
- config.restart = function() {
-
- // Restart by reloading the page. This will wipe all state which means
- // reload loops can't be prevented.
- if (config.restartType == "ReloadPage") {
- location.reload();
- }
-
- // Restart by readling the emscripten app module.
- ++self.restartCount;
- if (self.restartCount > config.restartLimit) {
- handleError("Error: This application has crashed too many times and has been disabled. Reload the page to try again.");
- return;
- }
- loadEmscriptenModule(applicationName);
- };
-
- publicAPI.exitCode = undefined;
- publicAPI.exitText = undefined;
- publicAPI.crashed = false;
-
- // Load the Emscripten application module. This is done by eval()'ing the
- // javascript runtime generated by Emscripten, and then calling
- // createQtAppInstance(), which was added to the global scope.
- eval(emscriptenModuleSource);
- createQtAppInstance(self.moduleConfig).then(function(module) {
- self.module = module;
- });
- }
-
- function setErrorContent() {
- if (config.containerElements === undefined) {
- if (config.showError !== undefined)
- config.showError(self.error);
- return;
- }
-
- for (container of config.containerElements) {
- var errorElement = config.showError(self.error, container);
- container.appendChild(errorElement);
}
}
- function setLoaderContent() {
- if (config.containerElements === undefined) {
- if (config.showLoader !== undefined)
- config.showLoader(self.loaderSubState);
- return;
- }
-
- for (container of config.containerElements) {
- var loaderElement = config.showLoader(self.loaderSubState, container);
- container.appendChild(loaderElement);
+ const originalOnAbort = config.onAbort;
+ config.onAbort = text =>
+ {
+ originalOnAbort?.();
+
+ if (!onExitCalled) {
+ onExitCalled = true;
+ config.qt.onExit?.({
+ text,
+ crashed: true
+ });
}
- }
-
- function setCanvasContent() {
- if (config.containerElements === undefined) {
- if (config.showCanvas !== undefined)
- config.showCanvas();
+ };
+
+ // Call app/emscripten module entry function. It may either come from the emscripten
+ // runtime script or be customized as needed.
+ let instance;
+ try {
+ instance = await Promise.race(
+ [circuitBreaker, config.qt.entryFunction(config)]);
+
+ // Call main after creating the instance. We've opted into manually
+ // calling main() by setting noInitialRun in the config. Thie Works around
+ // issue where Emscripten suppresses all exceptions thrown during main.
+ if (!originalNoInitialRun)
+ instance.callMain(originalArguments);
+ } catch (e) {
+ // If this is the exception thrown by app.exec() then that is a normal
+ // case and we suppress it.
+ if (e == "unwind") // not much to go on
return;
- }
- for (var i = 0; i < config.containerElements.length; ++i) {
- var container = config.containerElements[i];
- var canvas = config.canvasElements[i];
- config.showCanvas(canvas, container);
- container.appendChild(canvas);
+ if (!onExitCalled) {
+ onExitCalled = true;
+ config.qt.onExit?.({
+ text: e.message,
+ crashed: true
+ });
}
+ throw e;
}
- function setExitContent() {
-
- // publicAPI.crashed = true;
-
- if (publicAPI.status != "Exited")
- return;
-
- if (config.containerElements === undefined) {
- if (config.showExit !== undefined)
- config.showExit(publicAPI.crashed, publicAPI.exitCode);
- return;
- }
-
- if (!publicAPI.crashed)
- return;
+ return instance;
+}
- for (container of config.containerElements) {
- var loaderElement = config.showExit(publicAPI.crashed, publicAPI.exitCode, container);
- if (loaderElement !== undefined)
- container.appendChild(loaderElement);
- }
+// Compatibility API. This API is deprecated,
+// and will be removed in a future version of Qt.
+function QtLoader(qtConfig) {
+
+ const warning = 'Warning: The QtLoader API is deprecated and will be removed in ' +
+ 'a future version of Qt. Please port to the new qtLoad() API.';
+ console.warn(warning);
+
+ let emscriptenConfig = qtConfig.moduleConfig || {}
+ qtConfig.moduleConfig = undefined;
+ const showLoader = qtConfig.showLoader;
+ qtConfig.showLoader = undefined;
+ const showError = qtConfig.showError;
+ qtConfig.showError = undefined;
+ const showExit = qtConfig.showExit;
+ qtConfig.showExit = undefined;
+ const showCanvas = qtConfig.showCanvas;
+ qtConfig.showCanvas = undefined;
+ if (qtConfig.canvasElements) {
+ qtConfig.containerElements = qtConfig.canvasElements
+ qtConfig.canvasElements = undefined;
+ } else {
+ qtConfig.containerElements = qtConfig.containerElements;
+ qtConfig.containerElements = undefined;
}
-
- var committedStatus = undefined;
- function handleStatusChange() {
- if (publicAPI.status != "Loading" && committedStatus == publicAPI.status)
- return;
- committedStatus = publicAPI.status;
-
- if (publicAPI.status == "Error") {
- setErrorContent();
- } else if (publicAPI.status == "Loading") {
- setLoaderContent();
- } else if (publicAPI.status == "Running") {
- setCanvasContent();
- } else if (publicAPI.status == "Exited") {
- if (config.restartMode == "RestartOnExit" ||
- config.restartMode == "RestartOnCrash" && publicAPI.crashed) {
- committedStatus = undefined;
- config.restart();
- } else {
- setExitContent();
+ emscriptenConfig.qt = qtConfig;
+
+ let qtloader = {
+ exitCode: undefined,
+ exitText: "",
+ loadEmscriptenModule: _name => {
+ try {
+ qtLoad(emscriptenConfig);
+ } catch (e) {
+ showError?.(e.message);
}
}
-
- // Send status change notification
- if (config.statusChanged)
- config.statusChanged(publicAPI.status);
}
- function setStatus(status) {
- if (status != "Loading" && publicAPI.status == status)
- return;
- publicAPI.status = status;
-
- window.setTimeout(function() { handleStatusChange(); }, 0);
- }
-
- function addCanvasElement(element) {
- if (publicAPI.status == "Running")
- self.module.qtAddContainerElement(element);
- else
- console.log("Error: addCanvasElement can only be called in the Running state");
+ qtConfig.onLoaded = () => {
+ showCanvas?.();
}
- function removeCanvasElement(element) {
- if (publicAPI.status == "Running")
- self.module.qtRemoveContainerElement(element);
- else
- console.log("Error: removeCanvasElement can only be called in the Running state");
+ qtConfig.onExit = exit => {
+ qtloader.exitCode = exit.code
+ qtloader.exitText = exit.text;
+ showExit?.();
}
- function resizeCanvasElement(element) {
- if (publicAPI.status == "Running")
- self.module.qtResizeContainerElement(element);
- }
-
- function setFontDpi(dpi) {
- self.qtFontDpi = dpi;
- if (publicAPI.status == "Running")
- self.qtUpdateDpi();
- }
+ showLoader?.("Loading");
- function fontDpi() {
- return self.qtFontDpi;
- }
-
- function module() {
- return self.module;
- }
-
- setStatus("Created");
-
- return publicAPI;
-}
+ return qtloader;
+};
diff --git a/src/plugins/platforms/wasm/qtlogo.svg b/src/plugins/platforms/wasm/qtlogo.svg
deleted file mode 100644
index ad7c7776bf..0000000000
--- a/src/plugins/platforms/wasm/qtlogo.svg
+++ /dev/null
@@ -1,40 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<svg
- xmlns:dc="http://purl.org/dc/elements/1.1/"
- xmlns:cc="http://creativecommons.org/ns#"
- xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
- xmlns:svg="http://www.w3.org/2000/svg"
- xmlns="http://www.w3.org/2000/svg"
- width="462pt"
- height="339pt"
- viewBox="0 0 462 339"
- version="1.1">
- <metadata
- id="metadata20">
- <rdf:RDF>
- <cc:Work
- rdf:about="">
- <dc:format>image/svg+xml</dc:format>
- <dc:type
- rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
- <dc:title></dc:title>
- </cc:Work>
- </rdf:RDF>
- </metadata>
- <path
- fill="#41cd52"
- d=" M 63.50 0.00 L 462.00 0.00 L 462.00 274.79 C 440.60 296.26 419.13 317.66 397.61 339.00 L 0.00 339.00 L 0.00 63.39 C 21.08 42.18 42.34 21.13 63.50 0.00 Z"
- id="path6" />
- <path
- d=" M 122.37 71.33 C 137.50 61.32 156.21 58.79 174.00 58.95 C 190.94 59.16 208.72 62.13 222.76 72.24 C 232.96 79.41 239.59 90.48 244.01 101.93 C 251.16 120.73 253.26 141.03 253.50 161.01 C 253.53 181.13 252.62 201.69 245.96 220.86 C 241.50 233.90 233.01 245.48 221.81 253.52 C 229.87 266.58 238.09 279.54 246.15 292.60 C 236.02 297.27 225.92 301.97 215.78 306.62 C 207.15 292.38 198.56 278.11 189.90 263.89 C 178.19 265.81 166.21 265.66 154.44 264.36 C 140.34 262.67 125.97 258.37 115.09 248.88 C 106.73 241.64 101.48 231.51 97.89 221.21 C 92.01 203.79 90.43 185.25 90.16 166.97 C 90.02 147.21 91.28 127.14 97.24 108.18 C 101.85 93.92 109.48 79.69 122.37 71.33 Z"
- id="path8"
- fill="#ffffff" />
- <path
- d=" M 294.13 70.69 C 304.73 70.68 315.33 70.68 325.93 70.69 C 325.96 84.71 325.92 98.72 325.95 112.74 C 339.50 112.76 353.05 112.74 366.60 112.75 C 366.37 121.85 366.12 130.95 365.86 140.05 C 352.32 140.08 338.79 140.04 325.25 140.07 C 325.28 163.05 325.18 186.03 325.30 209.01 C 325.56 215.30 325.42 221.94 328.19 227.75 C 330.21 232.23 335.65 233.38 340.08 233.53 C 348.43 233.50 356.77 233.01 365.12 232.86 C 365.63 241.22 366.12 249.59 366.60 257.95 C 349.99 260.74 332.56 264.08 316.06 258.86 C 309.11 256.80 302.63 252.19 299.81 245.32 C 294.76 233.63 294.35 220.62 294.13 208.07 C 294.11 185.40 294.13 162.74 294.12 140.07 C 286.73 140.05 279.34 140.08 271.95 140.05 C 271.93 130.96 271.93 121.86 271.95 112.76 C 279.34 112.73 286.72 112.77 294.11 112.74 C 294.14 98.72 294.10 84.71 294.13 70.69 Z"
- id="path10"
- fill="#ffffff" />
- <path
- fill="#41cd52"
- d=" M 160.51 87.70 C 170.80 86.36 181.60 86.72 191.34 90.61 C 199.23 93.73 205.93 99.84 209.47 107.58 C 214.90 119.31 216.98 132.26 218.03 145.05 C 219.17 162.07 219.01 179.25 216.66 196.17 C 215.01 206.24 212.66 216.85 205.84 224.79 C 198.92 232.76 188.25 236.18 178.01 236.98 C 167.21 237.77 155.82 236.98 146.07 231.87 C 140.38 228.84 135.55 224.09 132.73 218.27 C 129.31 211.30 127.43 203.69 126.11 196.07 C 122.13 171.91 121.17 146.91 126.61 122.89 C 128.85 113.83 132.11 104.53 138.73 97.70 C 144.49 91.85 152.51 88.83 160.51 87.70 Z"
- id="path12" />
-</svg>
diff --git a/src/plugins/platforms/wasm/qwasmaccessibility.cpp b/src/plugins/platforms/wasm/qwasmaccessibility.cpp
index b96a3799e6..4c3cb46ba3 100644
--- a/src/plugins/platforms/wasm/qwasmaccessibility.cpp
+++ b/src/plugins/platforms/wasm/qwasmaccessibility.cpp
@@ -3,6 +3,15 @@
#include "qwasmaccessibility.h"
#include "qwasmscreen.h"
+#include "qwasmwindow.h"
+#include "qwasmintegration.h"
+#include <QtGui/qwindow.h>
+
+#if QT_CONFIG(accessibility)
+
+#include <QtGui/private/qaccessiblebridgeutils_p.h>
+
+Q_LOGGING_CATEGORY(lcQpaAccessibility, "qt.qpa.accessibility")
// Qt WebAssembly a11y backend
//
@@ -14,33 +23,107 @@
// events. In addition or alternatively, we could also walk the accessibility tree
// from setRootObject().
-
QWasmAccessibility::QWasmAccessibility()
{
+ s_instance = this;
}
QWasmAccessibility::~QWasmAccessibility()
{
+ s_instance = nullptr;
+}
+
+QWasmAccessibility *QWasmAccessibility::s_instance = nullptr;
+QWasmAccessibility* QWasmAccessibility::get()
+{
+ return s_instance;
+}
+
+void QWasmAccessibility::addAccessibilityEnableButton(QWindow *window)
+{
+ get()->addAccessibilityEnableButtonImpl(window);
+}
+
+void QWasmAccessibility::removeAccessibilityEnableButton(QWindow *window)
+{
+ get()->removeAccessibilityEnableButtonImpl(window);
+}
+
+void QWasmAccessibility::addAccessibilityEnableButtonImpl(QWindow *window)
+{
+ if (m_accessibilityEnabled)
+ return;
+
+ emscripten::val container = getContainer(window);
+ emscripten::val document = getDocument(container);
+ emscripten::val button = document.call<emscripten::val>("createElement", std::string("button"));
+ button.set("innerText", std::string("Enable Screen Reader"));
+ button["classList"].call<void>("add", emscripten::val("hidden-visually-read-by-screen-reader"));
+ container.call<void>("appendChild", button);
+
+ auto enableContext = std::make_tuple(button, std::make_unique<qstdweb::EventCallback>
+ (button, std::string("click"), [this](emscripten::val) { enableAccessibility(); }));
+ m_enableButtons.insert(std::make_pair(window, std::move(enableContext)));
+}
+
+void QWasmAccessibility::removeAccessibilityEnableButtonImpl(QWindow *window)
+{
+ auto it = m_enableButtons.find(window);
+ if (it == m_enableButtons.end())
+ return;
+
+ // Remove button
+ auto [element, callback] = it->second;
+ Q_UNUSED(callback);
+ element["parentElement"].call<void>("removeChild", element);
+ m_enableButtons.erase(it);
+}
+
+void QWasmAccessibility::enableAccessibility()
+{
+ // Enable accessibility globally for the applicaton. Remove all "enable"
+ // buttons and populate the accessibility tree, starting from the root object.
+
+ Q_ASSERT(!m_accessibilityEnabled);
+ m_accessibilityEnabled = true;
+ for (const auto& [key, value] : m_enableButtons) {
+ const auto &[element, callback] = value;
+ Q_UNUSED(key);
+ Q_UNUSED(callback);
+ element["parentElement"].call<void>("removeChild", element);
+ }
+ m_enableButtons.clear();
+ populateAccessibilityTree(QAccessible::queryAccessibleInterface(m_rootObject));
+}
+
+emscripten::val QWasmAccessibility::getContainer(QWindow *window)
+{
+ return window ? static_cast<QWasmWindow *>(window->handle())->a11yContainer()
+ : emscripten::val::undefined();
}
emscripten::val QWasmAccessibility::getContainer(QAccessibleInterface *iface)
{
- // Get to QWasmScreen::container(), return undefined element if unable to
- QWindow *window = iface->window();
- if (!window)
+ if (!iface)
return emscripten::val::undefined();
- QWasmScreen *screen = QWasmScreen::get(window->screen());
- if (!screen)
- return emscripten::val::undefined();
- return screen->container();
+ return getContainer(getWindow(iface));
+}
+
+QWindow *QWasmAccessibility::getWindow(QAccessibleInterface *iface)
+{
+ QWindow *window = iface->window();
+ // this is needed to add tabs as the window is not available
+ if (!window && iface->parent())
+ window = iface->parent()->window();
+ return window;
}
emscripten::val QWasmAccessibility::getDocument(const emscripten::val &container)
{
if (container.isUndefined())
- return emscripten::val::undefined();
+ return emscripten::val::global("document");
return container["ownerDocument"];
}
@@ -58,12 +141,12 @@ emscripten::val QWasmAccessibility::createHtmlElement(QAccessibleInterface *ifac
// Get the correct html document for the container, or fall back
// to the global document. TODO: Does using the correct document actually matter?
- emscripten::val document = container.isUndefined() ? emscripten::val::global("document") : getDocument(container);
+ emscripten::val document = getDocument(container);
// Translate the Qt a11y elemen role into html element type + ARIA role.
// Here we can either create <div> elements with a spesific ARIA role,
// or create e.g. <button> elements which should have built-in accessibility.
- emscripten::val element = [iface, document] {
+ emscripten::val element = [this, iface, document] {
emscripten::val element = emscripten::val::undefined();
@@ -71,17 +154,134 @@ emscripten::val QWasmAccessibility::createHtmlElement(QAccessibleInterface *ifac
case QAccessible::Button: {
element = document.call<emscripten::val>("createElement", std::string("button"));
+ element.call<void>("addEventListener", emscripten::val("click"),
+ emscripten::val::module_property("qtEventReceived"), true);
} break;
-
case QAccessible::CheckBox: {
element = document.call<emscripten::val>("createElement", std::string("input"));
element.call<void>("setAttribute", std::string("type"), std::string("checkbox"));
+ if (iface->state().checked) {
+ element.call<void>("setAttribute", std::string("checked"), std::string("true"));
+ }
+ element.call<void>("addEventListener", emscripten::val("change"),
+ emscripten::val::module_property("qtEventReceived"), true);
+
+ } break;
+
+ case QAccessible::RadioButton: {
+ element = document.call<emscripten::val>("createElement", std::string("input"));
+ element.call<void>("setAttribute", std::string("type"), std::string("radio"));
+ if (iface->state().checked) {
+ element.call<void>("setAttribute", std::string("checked"), std::string("true"));
+ }
+ element.set(std::string("name"), std::string("buttonGroup"));
+ element.call<void>("addEventListener", emscripten::val("change"),
+ emscripten::val::module_property("qtEventReceived"), true);
+ } break;
+
+ case QAccessible::SpinBox: {
+ element = document.call<emscripten::val>("createElement", std::string("input"));
+ element.call<void>("setAttribute", std::string("type"), std::string("number"));
+ std::string valueString = iface->valueInterface()->currentValue().toString().toStdString();
+ element.call<void>("setAttribute", std::string("value"), valueString);
+ element.call<void>("addEventListener", emscripten::val("change"),
+ emscripten::val::module_property("qtEventReceived"), true);
+ } break;
+
+ case QAccessible::Slider: {
+ element = document.call<emscripten::val>("createElement", std::string("input"));
+ element.call<void>("setAttribute", std::string("type"), std::string("range"));
+ std::string valueString = iface->valueInterface()->currentValue().toString().toStdString();
+ element.call<void>("setAttribute", std::string("value"), valueString);
+ element.call<void>("addEventListener", emscripten::val("change"),
+ emscripten::val::module_property("qtEventReceived"), true);
+ } break;
+
+ case QAccessible::PageTabList:{
+ element = document.call<emscripten::val>("createElement", std::string("div"));
+ element.call<void>("setAttribute", std::string("role"), std::string("tablist"));
+ QString idName = iface->text(QAccessible::Name).replace(" ", "_");
+ idName += "_tabList";
+ element.call<void>("setAttribute", std::string("id"), idName.toStdString());
+
+ for (int i = 0; i < iface->childCount(); ++i) {
+ if (iface->child(i)->role() == QAccessible::PageTab){
+ emscripten::val elementTab = emscripten::val::undefined();
+ elementTab = ensureHtmlElement(iface->child(i));
+ elementTab.call<void>("setAttribute", std::string("aria-owns"), idName.toStdString());
+ setHtmlElementGeometry(iface->child(i));
+ }
+ }
+ } break;
+
+ case QAccessible::PageTab:{
+ element = document.call<emscripten::val>("createElement", std::string("button"));
+ element.call<void>("setAttribute", std::string("role"), std::string("tab"));
+ QString text = iface->text(QAccessible::Name);
+ element.call<void>("setAttribute", std::string("title"), text.toStdString());
+ element.call<void>("addEventListener", emscripten::val("click"),
+ emscripten::val::module_property("qtEventReceived"), true);
+ } break;
+
+ case QAccessible::ScrollBar: {
+ element = document.call<emscripten::val>("createElement", std::string("div"));
+ element.call<void>("setAttribute", std::string("role"), std::string("scrollbar"));
+ std::string valueString = iface->valueInterface()->currentValue().toString().toStdString();
+ element.call<void>("setAttribute", std::string("aria-valuenow"), valueString);
+ element.call<void>("addEventListener", emscripten::val("change"),
+ emscripten::val::module_property("qtEventReceived"), true);
} break;
+ case QAccessible::StaticText: {
+ element = document.call<emscripten::val>("createElement", std::string("textarea"));
+ element.call<void>("setAttribute", std::string("readonly"), std::string("true"));
+
+ } break;
+ case QAccessible::Dialog: {
+ element = document.call<emscripten::val>("createElement", std::string("dialog"));
+ }break;
+ case QAccessible::ToolBar:{
+ element = document.call<emscripten::val>("createElement", std::string("div"));
+ QString text = iface->text(QAccessible::Name);
+
+ element.call<void>("setAttribute", std::string("role"), std::string("toolbar"));
+ element.call<void>("setAttribute", std::string("title"), text.toStdString());
+ element.call<void>("addEventListener", emscripten::val("click"),
+ emscripten::val::module_property("qtEventReceived"), true);
+ }break;
+ case QAccessible::MenuItem:
+ case QAccessible::ButtonMenu: {
+ element = document.call<emscripten::val>("createElement", std::string("button"));
+ QString text = iface->text(QAccessible::Name);
+
+ element.call<void>("setAttribute", std::string("role"), std::string("menuitem"));
+ element.call<void>("setAttribute", std::string("title"), text.toStdString());
+ element.call<void>("addEventListener", emscripten::val("click"),
+ emscripten::val::module_property("qtEventReceived"), true);
+ }break;
+ case QAccessible::MenuBar:
+ case QAccessible::PopupMenu: {
+ element = document.call<emscripten::val>("createElement",std::string("div"));
+ QString text = iface->text(QAccessible::Name);
+ element.call<void>("setAttribute", std::string("role"), std::string("menubar"));
+ element.call<void>("setAttribute", std::string("title"), text.toStdString());
+ for (int i = 0; i < iface->childCount(); ++i) {
+ emscripten::val childElement = emscripten::val::undefined();
+ childElement= ensureHtmlElement(iface->child(i));
+ childElement.call<void>("setAttribute", std::string("aria-owns"), text.toStdString());
+ setHtmlElementTextName(iface->child(i));
+ setHtmlElementGeometry(iface->child(i));
+ }
+ }break;
+ case QAccessible::EditableText: {
+ element = document.call<emscripten::val>("createElement", std::string("input"));
+ element.call<void>("setAttribute", std::string("type"),std::string("text"));
+ element.call<void>("addEventListener", emscripten::val("input"),
+ emscripten::val::module_property("qtEventReceived"), true);
+ } break;
default:
- qDebug() << "TODO: createHtmlElement() handle" << iface->role();
+ qCDebug(lcQpaAccessibility) << "TODO: createHtmlElement() handle" << iface->role();
element = document.call<emscripten::val>("createElement", std::string("div"));
- //element.set("AriaRole", "foo");
}
return element;
@@ -99,7 +299,7 @@ emscripten::val QWasmAccessibility::createHtmlElement(QAccessibleInterface *ifac
void QWasmAccessibility::destroyHtmlElement(QAccessibleInterface *iface)
{
Q_UNUSED(iface);
- qDebug() << "TODO destroyHtmlElement";
+ qCDebug(lcQpaAccessibility) << "TODO destroyHtmlElement";
}
emscripten::val QWasmAccessibility::ensureHtmlElement(QAccessibleInterface *iface)
@@ -120,7 +320,7 @@ void QWasmAccessibility::setHtmlElementVisibility(QAccessibleInterface *iface, b
emscripten::val container = getContainer(iface);
if (container.isUndefined()) {
- qDebug() << "TODO: setHtmlElementVisibility: unable to find html container for element" << iface;
+ qCDebug(lcQpaAccessibility) << "TODO: setHtmlElementVisibility: unable to find html container for element" << iface;
return;
}
@@ -132,17 +332,27 @@ void QWasmAccessibility::setHtmlElementVisibility(QAccessibleInterface *iface, b
void QWasmAccessibility::setHtmlElementGeometry(QAccessibleInterface *iface)
{
emscripten::val element = ensureHtmlElement(iface);
- setHtmlElementGeometry(iface, element);
+
+ // QAccessibleInterface gives us the geometry in global (screen) coordinates. Translate that
+ // to window geometry in order to position elements relative to window origin.
+ QWindow *window = getWindow(iface);
+ if (!window)
+ qCWarning(lcQpaAccessibility) << "Unable to find window for" << iface << "setting null geometry";
+ QRect screenGeometry = iface->rect();
+ QPoint windowPos = window ? window->mapFromGlobal(screenGeometry.topLeft()) : QPoint();
+ QRect windowGeometry(windowPos, screenGeometry.size());
+
+ setHtmlElementGeometry(element, windowGeometry);
}
-void QWasmAccessibility::setHtmlElementGeometry(QAccessibleInterface *iface, emscripten::val element)
+void QWasmAccessibility::setHtmlElementGeometry(emscripten::val element, QRect geometry)
{
// Position the element using "position: absolute" in order to place
- // it under the corresponding Qt element on the canvas.
- QRect geometry = iface->rect();
+ // it under the corresponding Qt element in the screen.
emscripten::val style = element["style"];
style.set("position", std::string("absolute"));
- style.set("z-index", std::string("-1")); // FIXME: "0" should be sufficient to order beheind the canvas, but isn't
+ style.set("z-index", std::string("-1")); // FIXME: "0" should be sufficient to order beheind the
+ // screen element, but isn't
style.set("left", std::to_string(geometry.x()) + "px");
style.set("top", std::to_string(geometry.y()) + "px");
style.set("width", std::to_string(geometry.width()) + "px");
@@ -156,37 +366,325 @@ void QWasmAccessibility::setHtmlElementTextName(QAccessibleInterface *iface)
element.set("innerHTML", text.toStdString()); // FIXME: use something else than innerHTML
}
+void QWasmAccessibility::setHtmlElementTextNameLE(QAccessibleInterface *iface) {
+ emscripten::val element = ensureHtmlElement(iface);
+ QString text = iface->text(QAccessible::Name);
+ element.call<void>("setAttribute", std::string("name"), text.toStdString());
+ QString value = iface->text(QAccessible::Value);
+ element.set("innerHTML", value.toStdString());
+}
+
+void QWasmAccessibility::setHtmlElementDescription(QAccessibleInterface *iface) {
+ emscripten::val element = ensureHtmlElement(iface);
+ QString desc = iface->text(QAccessible::Description);
+ element.call<void>("setAttribute", std::string("aria-description"), desc.toStdString());
+}
+
void QWasmAccessibility::handleStaticTextUpdate(QAccessibleEvent *event)
{
switch (event->type()) {
case QAccessible::NameChanged: {
setHtmlElementTextName(event->accessibleInterface());
} break;
+ case QAccessible::DescriptionChanged: {
+ setHtmlElementDescription(event->accessibleInterface());
+ } break;
default:
- qDebug() << "TODO: implement handleStaticTextUpdate for event" << event->type();
+ qCDebug(lcQpaAccessibility) << "TODO: implement handleStaticTextUpdate for event" << event->type();
break;
}
}
+void QWasmAccessibility::handleLineEditUpdate(QAccessibleEvent *event) {
+
+ switch (event->type()) {
+ case QAccessible::NameChanged: {
+ setHtmlElementTextName(event->accessibleInterface());
+ } break;
+ case QAccessible::Focus:
+ case QAccessible::TextRemoved:
+ case QAccessible::TextInserted:
+ case QAccessible::TextCaretMoved: {
+ setHtmlElementTextNameLE(event->accessibleInterface());
+ } break;
+ case QAccessible::DescriptionChanged: {
+ setHtmlElementDescription(event->accessibleInterface());
+ } break;
+ default:
+ qCDebug(lcQpaAccessibility) << "TODO: implement handleLineEditUpdate for event" << event->type();
+ break;
+ }
+}
+
+void QWasmAccessibility::handleEventFromHtmlElement(const emscripten::val event)
+{
+
+ QAccessibleInterface *iface = m_elements.key(event["target"]);
+ if (iface == nullptr) {
+ return;
+ } else {
+ QString eventType = QString::fromStdString(event["type"].as<std::string>());
+ const auto& actionNames = QAccessibleBridgeUtils::effectiveActionNames(iface);
+ if (actionNames.contains(QAccessibleActionInterface::pressAction())) {
+
+ iface->actionInterface()->doAction(QAccessibleActionInterface::pressAction());
+
+ } else if (actionNames.contains(QAccessibleActionInterface::toggleAction())) {
+
+ iface->actionInterface()->doAction(QAccessibleActionInterface::toggleAction());
+
+ } else if (actionNames.contains(QAccessibleActionInterface::increaseAction()) ||
+ actionNames.contains(QAccessibleActionInterface::decreaseAction())) {
+
+ QString val = QString::fromStdString(event["target"]["value"].as<std::string>());
+
+ iface->valueInterface()->setCurrentValue(val.toInt());
+
+ } else if (eventType == "input") {
+
+ // as EditableTextInterface is not implemented in qml accessibility
+ // so we need to check the role for text to update in the textbox during accessibility
+
+ if (iface->editableTextInterface() || iface->role() == QAccessible::EditableText) {
+ std::string insertText = event["target"]["value"].as<std::string>();
+ iface->setText(QAccessible::Value, QString::fromStdString(insertText));
+ }
+ }
+ }
+}
+
void QWasmAccessibility::handleButtonUpdate(QAccessibleEvent *event)
{
- qDebug() << "TODO: implement handleButtonUpdate for event" << event->type();
+ qCDebug(lcQpaAccessibility) << "TODO: implement handleButtonUpdate for event" << event->type();
}
void QWasmAccessibility::handleCheckBoxUpdate(QAccessibleEvent *event)
{
switch (event->type()) {
+ case QAccessible::Focus:
+ case QAccessible::NameChanged: {
+ setHtmlElementTextName(event->accessibleInterface());
+ } break;
+ case QAccessible::StateChanged: {
+ QAccessibleInterface *accessible = event->accessibleInterface();
+ emscripten::val element = ensureHtmlElement(accessible);
+ bool checkedString = accessible->state().checked ? true : false;
+ element.call<void>("setAttribute", std::string("checked"), checkedString);
+ } break;
+ case QAccessible::DescriptionChanged: {
+ setHtmlElementDescription(event->accessibleInterface());
+ } break;
+ default:
+ qCDebug(lcQpaAccessibility) << "TODO: implement handleCheckBoxUpdate for event" << event->type();
+ break;
+ }
+}
+void QWasmAccessibility::handleToolUpdate(QAccessibleEvent *event)
+{
+ QAccessibleInterface *iface = event->accessibleInterface();
+ QString text = iface->text(QAccessible::Name);
+ QString desc = iface->text(QAccessible::Description);
+ switch (event->type()) {
+ case QAccessible::NameChanged:
+ case QAccessible::StateChanged:{
+ emscripten::val element = ensureHtmlElement(iface);
+ element.call<void>("setAttribute", std::string("title"), text.toStdString());
+ } break;
+ case QAccessible::DescriptionChanged: {
+ setHtmlElementDescription(event->accessibleInterface());
+ } break;
+ default:
+ qCDebug(lcQpaAccessibility) << "TODO: implement handleToolUpdate for event" << event->type();
+ break;
+ }
+}
+void QWasmAccessibility::handleMenuUpdate(QAccessibleEvent *event)
+{
+ QAccessibleInterface *iface = event->accessibleInterface();
+ QString text = iface->text(QAccessible::Name);
+ QString desc = iface->text(QAccessible::Description);
+ switch (event->type()) {
+ case QAccessible::Focus:
+ case QAccessible::NameChanged:
+ case QAccessible::MenuStart ://"TODO: To implement later
+ case QAccessible::PopupMenuStart://"TODO: To implement later
+ case QAccessible::StateChanged:{
+ emscripten::val element = ensureHtmlElement(iface);
+ element.call<void>("setAttribute", std::string("title"), text.toStdString());
+ } break;
+ case QAccessible::DescriptionChanged: {
+ setHtmlElementDescription(event->accessibleInterface());
+ } break;
+ default:
+ qCDebug(lcQpaAccessibility) << "TODO: implement handleMenuUpdate for event" << event->type();
+ break;
+ }
+}
+void QWasmAccessibility::handleDialogUpdate(QAccessibleEvent *event) {
+
+ switch (event->type()) {
+ case QAccessible::NameChanged:
+ case QAccessible::Focus:
+ case QAccessible::DialogStart:
+ case QAccessible::StateChanged: {
+ setHtmlElementTextName(event->accessibleInterface());
+ } break;
+ case QAccessible::DescriptionChanged: {
+ setHtmlElementDescription(event->accessibleInterface());
+ } break;
+ default:
+ qCDebug(lcQpaAccessibility) << "TODO: implement handleLineEditUpdate for event" << event->type();
+ break;
+ }
+}
+
+void QWasmAccessibility::populateAccessibilityTree(QAccessibleInterface *iface)
+{
+ if (!iface)
+ return;
+
+ // Create html element for the interface, sync up properties.
+ ensureHtmlElement(iface);
+ const bool visible = !iface->state().invisible;
+ setHtmlElementVisibility(iface, visible);
+ setHtmlElementGeometry(iface);
+ setHtmlElementTextName(iface);
+ setHtmlElementDescription(iface);
+
+ for (int i = 0; i < iface->childCount(); ++i)
+ populateAccessibilityTree(iface->child(i));
+}
+
+void QWasmAccessibility::handleRadioButtonUpdate(QAccessibleEvent *event)
+{
+ switch (event->type()) {
+ case QAccessible::Focus:
+ case QAccessible::NameChanged: {
+ setHtmlElementTextName(event->accessibleInterface());
+ } break;
+ case QAccessible::StateChanged: {
+ QAccessibleInterface *accessible = event->accessibleInterface();
+ emscripten::val element = ensureHtmlElement(accessible);
+ std::string checkedString = accessible->state().checked ? "true" : "false";
+ element.call<void>("setAttribute", std::string("checked"), checkedString);
+ } break;
+ case QAccessible::DescriptionChanged: {
+ setHtmlElementDescription(event->accessibleInterface());
+ } break;
+ default:
+ qDebug() << "TODO: implement handleRadioButtonUpdate for event" << event->type();
+ break;
+ }
+}
+
+void QWasmAccessibility::handleSpinBoxUpdate(QAccessibleEvent *event)
+{
+ switch (event->type()) {
+ case QAccessible::Focus:
+ case QAccessible::NameChanged: {
+ setHtmlElementTextName(event->accessibleInterface());
+ } break;
+ case QAccessible::ValueChanged: {
+ QAccessibleInterface *accessible = event->accessibleInterface();
+ emscripten::val element = ensureHtmlElement(accessible);
+ std::string valueString = accessible->valueInterface()->currentValue().toString().toStdString();
+ element.call<void>("setAttribute", std::string("value"), valueString);
+ } break;
+ case QAccessible::DescriptionChanged: {
+ setHtmlElementDescription(event->accessibleInterface());
+ } break;
+ default:
+ qDebug() << "TODO: implement handleSpinBoxUpdate for event" << event->type();
+ break;
+ }
+}
+
+void QWasmAccessibility::handleSliderUpdate(QAccessibleEvent *event)
+{
+ switch (event->type()) {
+ case QAccessible::Focus:
+ case QAccessible::NameChanged: {
+ setHtmlElementTextName(event->accessibleInterface());
+ } break;
+ case QAccessible::ValueChanged: {
+ QAccessibleInterface *accessible = event->accessibleInterface();
+ emscripten::val element = ensureHtmlElement(accessible);
+ std::string valueString = accessible->valueInterface()->currentValue().toString().toStdString();
+ element.call<void>("setAttribute", std::string("value"), valueString);
+ } break;
+ case QAccessible::DescriptionChanged: {
+ setHtmlElementDescription(event->accessibleInterface());
+ } break;
+ default:
+ qDebug() << "TODO: implement handleSliderUpdate for event" << event->type();
+ break;
+ }
+}
+
+void QWasmAccessibility::handleScrollBarUpdate(QAccessibleEvent *event)
+{
+ switch (event->type()) {
+ case QAccessible::Focus:
case QAccessible::NameChanged: {
setHtmlElementTextName(event->accessibleInterface());
} break;
+ case QAccessible::ValueChanged: {
+ QAccessibleInterface *accessible = event->accessibleInterface();
+ emscripten::val element = ensureHtmlElement(accessible);
+ std::string valueString = accessible->valueInterface()->currentValue().toString().toStdString();
+ element.call<void>("setAttribute", std::string("aria-valuenow"), valueString);
+ } break;
+ case QAccessible::DescriptionChanged: {
+ setHtmlElementDescription(event->accessibleInterface());
+ } break;
default:
- qDebug() << "TODO: implement handleCheckBoxUpdate for event" << event->type();
+ qDebug() << "TODO: implement handleSliderUpdate for event" << event->type();
+ break;
+ }
+
+}
+
+void QWasmAccessibility::handlePageTabUpdate(QAccessibleEvent *event)
+{
+ switch (event->type()) {
+ case QAccessible::NameChanged: {
+ setHtmlElementTextName(event->accessibleInterface());
+ } break;
+ case QAccessible::Focus: {
+ setHtmlElementTextName(event->accessibleInterface());
+ } break;
+ case QAccessible::DescriptionChanged: {
+ setHtmlElementDescription(event->accessibleInterface());
+ } break;
+ default:
+ qDebug() << "TODO: implement handlePageTabUpdate for event" << event->type();
+ break;
+ }
+}
+
+void QWasmAccessibility::handlePageTabListUpdate(QAccessibleEvent *event)
+{
+ switch (event->type()) {
+ case QAccessible::NameChanged: {
+ setHtmlElementTextName(event->accessibleInterface());
+ } break;
+ case QAccessible::Focus: {
+ setHtmlElementTextName(event->accessibleInterface());
+ } break;
+ case QAccessible::DescriptionChanged: {
+ setHtmlElementDescription(event->accessibleInterface());
+ } break;
+ default:
+ qDebug() << "TODO: implement handlePageTabUpdate for event" << event->type();
break;
}
}
void QWasmAccessibility::notifyAccessibilityUpdate(QAccessibleEvent *event)
{
+ if (!m_accessibilityEnabled)
+ return;
+
QAccessibleInterface *iface = event->accessibleInterface();
if (!iface) {
qWarning() << "notifyAccessibilityUpdate with null a11y interface" ;
@@ -197,12 +695,12 @@ void QWasmAccessibility::notifyAccessibilityUpdate(QAccessibleEvent *event)
// https://doc.qt.io/qt-5/qaccessible.html#Event-enum
switch (event->type()) {
case QAccessible::ObjectShow:
-
setHtmlElementVisibility(iface, true);
// Sync up properties on show;
setHtmlElementGeometry(iface);
setHtmlElementTextName(iface);
+ setHtmlElementDescription(iface);
return;
break;
@@ -227,16 +725,46 @@ void QWasmAccessibility::notifyAccessibilityUpdate(QAccessibleEvent *event)
case QAccessible::CheckBox:
handleCheckBoxUpdate(event);
break;
+ case QAccessible::EditableText:
+ handleLineEditUpdate(event);
+ break;
+ case QAccessible::Dialog:
+ handleDialogUpdate(event);
+ break;
+ case QAccessible::MenuItem:
+ case QAccessible::MenuBar:
+ case QAccessible::PopupMenu:
+ handleMenuUpdate(event);
+ break;
+ case QAccessible::ToolBar:
+ case QAccessible::ButtonMenu:
+ handleToolUpdate(event);
+ case QAccessible::RadioButton:
+ handleRadioButtonUpdate(event);
+ break;
+ case QAccessible::SpinBox:
+ handleSpinBoxUpdate(event);
+ break;
+ case QAccessible::Slider:
+ handleSliderUpdate(event);
+ break;
+ case QAccessible::PageTab:
+ handlePageTabUpdate(event);
+ break;
+ case QAccessible::PageTabList:
+ handlePageTabListUpdate(event);
+ break;
+ case QAccessible::ScrollBar:
+ handleScrollBarUpdate(event);
+ break;
default:
- qDebug() << "TODO: implement notifyAccessibilityUpdate for role" << iface->role();
+ qCDebug(lcQpaAccessibility) << "TODO: implement notifyAccessibilityUpdate for role" << iface->role();
};
}
-void QWasmAccessibility::setRootObject(QObject *o)
+void QWasmAccessibility::setRootObject(QObject *root)
{
- qDebug() << "setRootObject" << o;
- QAccessibleInterface *iface = QAccessible::queryAccessibleInterface(o);
- Q_UNUSED(iface)
+ m_rootObject = root;
}
void QWasmAccessibility::initialize()
@@ -248,3 +776,14 @@ void QWasmAccessibility::cleanup()
{
}
+
+void QWasmAccessibility::onHtmlEventReceived(emscripten::val event)
+{
+ static_cast<QWasmAccessibility *>(QWasmIntegration::get()->accessibility())->handleEventFromHtmlElement(event);
+}
+
+EMSCRIPTEN_BINDINGS(qtButtonEvent) {
+ function("qtEventReceived", &QWasmAccessibility::onHtmlEventReceived);
+}
+
+#endif // QT_CONFIG(accessibility)
diff --git a/src/plugins/platforms/wasm/qwasmaccessibility.h b/src/plugins/platforms/wasm/qwasmaccessibility.h
index 4f34b96c2e..c4be7f0d72 100644
--- a/src/plugins/platforms/wasm/qwasmaccessibility.h
+++ b/src/plugins/platforms/wasm/qwasmaccessibility.h
@@ -4,10 +4,22 @@
#ifndef QWASMACCESIBILITY_H
#define QWASMACCESIBILITY_H
+#include <QtCore/qtconfigmacros.h>
+#include <QtGui/qtguiglobal.h>
+
+#if QT_CONFIG(accessibility)
+
#include <QtCore/qhash.h>
+#include <private/qstdweb_p.h>
#include <qpa/qplatformaccessibility.h>
#include <emscripten/val.h>
+#include <QLoggingCategory>
+
+#include <map>
+#include <emscripten/bind.h>
+
+Q_DECLARE_LOGGING_CATEGORY(lcQpaAccessibility)
class QWasmAccessibility : public QPlatformAccessibility
{
@@ -15,30 +27,66 @@ public:
QWasmAccessibility();
~QWasmAccessibility();
+ static QWasmAccessibility* get();
+
+ static void addAccessibilityEnableButton(QWindow *window);
+ static void removeAccessibilityEnableButton(QWindow *window);
+
+private:
+ void addAccessibilityEnableButtonImpl(QWindow *window);
+ void removeAccessibilityEnableButtonImpl(QWindow *window);
+ void enableAccessibility();
+
+ static emscripten::val getContainer(QWindow *window);
static emscripten::val getContainer(QAccessibleInterface *iface);
static emscripten::val getDocument(const emscripten::val &container);
static emscripten::val getDocument(QAccessibleInterface *iface);
+ static QWindow *getWindow(QAccessibleInterface *iface);
emscripten::val createHtmlElement(QAccessibleInterface *iface);
void destroyHtmlElement(QAccessibleInterface *iface);
emscripten::val ensureHtmlElement(QAccessibleInterface *iface);
void setHtmlElementVisibility(QAccessibleInterface *iface, bool visible);
void setHtmlElementGeometry(QAccessibleInterface *iface);
- void setHtmlElementGeometry(QAccessibleInterface *iface, emscripten::val element);
+ void setHtmlElementGeometry(emscripten::val element, QRect geometry);
void setHtmlElementTextName(QAccessibleInterface *iface);
+ void setHtmlElementTextNameLE(QAccessibleInterface *iface);
+ void setHtmlElementDescription(QAccessibleInterface *iface);
void handleStaticTextUpdate(QAccessibleEvent *event);
void handleButtonUpdate(QAccessibleEvent *event);
void handleCheckBoxUpdate(QAccessibleEvent *event);
+ void handleDialogUpdate(QAccessibleEvent *event);
+ void handleMenuUpdate(QAccessibleEvent *event);
+ void handleToolUpdate(QAccessibleEvent *event);
+ void handleLineEditUpdate(QAccessibleEvent *event);
+ void handleRadioButtonUpdate(QAccessibleEvent *event);
+ void handleSpinBoxUpdate(QAccessibleEvent *event);
+ void handlePageTabUpdate(QAccessibleEvent *event);
+ void handleSliderUpdate(QAccessibleEvent *event);
+ void handleScrollBarUpdate(QAccessibleEvent *event);
+ void handlePageTabListUpdate(QAccessibleEvent *event);
+
+ void handleEventFromHtmlElement(const emscripten::val event);
+ void populateAccessibilityTree(QAccessibleInterface *iface);
void notifyAccessibilityUpdate(QAccessibleEvent *event) override;
void setRootObject(QObject *o) override;
void initialize() override;
void cleanup() override;
+public: // public for EMSCRIPTEN_BINDINGS
+ static void onHtmlEventReceived(emscripten::val event);
+
private:
+ static QWasmAccessibility *s_instance;
+ QObject *m_rootObject = nullptr;
+ bool m_accessibilityEnabled = false;
+ std::map<QWindow *, std::tuple<emscripten::val, std::shared_ptr<qstdweb::EventCallback>>> m_enableButtons;
QHash<QAccessibleInterface *, emscripten::val> m_elements;
};
+#endif // QT_CONFIG(accessibility)
+
#endif
diff --git a/src/plugins/platforms/wasm/qwasmbackingstore.cpp b/src/plugins/platforms/wasm/qwasmbackingstore.cpp
index 594dcaa812..a3c1ae8a50 100644
--- a/src/plugins/platforms/wasm/qwasmbackingstore.cpp
+++ b/src/plugins/platforms/wasm/qwasmbackingstore.cpp
@@ -4,21 +4,18 @@
#include "qwasmbackingstore.h"
#include "qwasmwindow.h"
#include "qwasmcompositor.h"
+#include "qwasmdom.h"
-#include <QtOpenGL/qopengltexture.h>
-#include <QtGui/qmatrix4x4.h>
#include <QtGui/qpainter.h>
-#include <private/qguiapplication_p.h>
-#include <qpa/qplatformscreen.h>
-#include <QtGui/qoffscreensurface.h>
#include <QtGui/qbackingstore.h>
+#include <emscripten.h>
+#include <emscripten/wire.h>
+
QT_BEGIN_NAMESPACE
QWasmBackingStore::QWasmBackingStore(QWasmCompositor *compositor, QWindow *window)
- : QPlatformBackingStore(window)
- , m_compositor(compositor)
- , m_texture(new QOpenGLTexture(QOpenGLTexture::Target2D))
+ : QPlatformBackingStore(window), m_compositor(compositor)
{
QWasmWindow *wasmWindow = static_cast<QWasmWindow *>(window->handle());
if (wasmWindow)
@@ -29,29 +26,11 @@ QWasmBackingStore::~QWasmBackingStore()
{
auto window = this->window();
QWasmIntegration::get()->removeBackingStore(window);
- destroy();
QWasmWindow *wasmWindow = static_cast<QWasmWindow *>(window->handle());
if (wasmWindow)
wasmWindow->setBackingStore(nullptr);
}
-void QWasmBackingStore::destroy()
-{
- if (m_texture->isCreated()) {
- auto context = m_compositor->context();
- auto currentContext = QOpenGLContext::currentContext();
- if (!currentContext || !QOpenGLContext::areSharing(context, currentContext)) {
- QOffscreenSurface offScreenSurface(m_compositor->screen()->screen());
- offScreenSurface.setFormat(context->format());
- offScreenSurface.create();
- context->makeCurrent(&offScreenSurface);
- m_texture->destroy();
- } else {
- m_texture->destroy();
- }
- }
-}
-
QPaintDevice *QWasmBackingStore::paintDevice()
{
return &m_image;
@@ -64,33 +43,24 @@ void QWasmBackingStore::flush(QWindow *window, const QRegion &region, const QPoi
Q_UNUSED(offset);
m_dirty |= region;
- m_compositor->handleBackingStoreFlush();
+ m_compositor->handleBackingStoreFlush(window);
}
-void QWasmBackingStore::updateTexture()
+void QWasmBackingStore::updateTexture(QWasmWindow *window)
{
if (m_dirty.isNull())
return;
- if (m_recreateTexture) {
- m_recreateTexture = false;
- destroy();
+ if (m_webImageDataArray.isUndefined()) {
+ m_webImageDataArray = window->context2d().call<emscripten::val>(
+ "createImageData", emscripten::val(m_image.width()),
+ emscripten::val(m_image.height()));
}
- if (!m_texture->isCreated()) {
- m_texture->setMinificationFilter(QOpenGLTexture::Nearest);
- m_texture->setMagnificationFilter(QOpenGLTexture::Nearest);
- m_texture->setWrapMode(QOpenGLTexture::ClampToEdge);
- m_texture->setData(m_image, QOpenGLTexture::DontGenerateMipMaps);
- m_texture->create();
- }
- m_texture->bind();
-
- QRegion fixed;
+ QRegion clippedDpiScaledRegion;
QRect imageRect = m_image.rect();
for (const QRect &rect : m_dirty) {
-
// Convert device-independent dirty region to device region
qreal dpr = m_image.devicePixelRatio();
QRect deviceRect = QRect(rect.topLeft() * dpr, rect.size() * dpr);
@@ -103,21 +73,11 @@ void QWasmBackingStore::updateTexture()
r.setWidth(imageRect.width());
}
- fixed |= r;
+ clippedDpiScaledRegion |= r;
}
- for (const QRect &rect : fixed) {
- // if the sub-rect is full-width we can pass the image data directly to
- // OpenGL instead of copying, since there is no gap between scanlines
- if (rect.width() == imageRect.width()) {
- glTexSubImage2D(GL_TEXTURE_2D, 0, 0, rect.y(), rect.width(), rect.height(), GL_RGBA, GL_UNSIGNED_BYTE,
- m_image.constScanLine(rect.y()));
- } else {
- glTexSubImage2D(GL_TEXTURE_2D, 0, rect.x(), rect.y(), rect.width(), rect.height(), GL_RGBA, GL_UNSIGNED_BYTE,
- m_image.copy(rect).constBits());
- }
- }
- /* End of code taken from QEGLPlatformBackingStore */
+ for (const QRect &dirtyRect : clippedDpiScaledRegion)
+ dom::drawImageToWebImageDataArray(m_image, m_webImageDataArray, dirtyRect);
m_dirty = QRegion();
}
@@ -126,7 +86,7 @@ void QWasmBackingStore::beginPaint(const QRegion &region)
{
m_dirty |= region;
// Keep backing store device pixel ratio in sync with window
- if (m_image.devicePixelRatio() != window()->devicePixelRatio())
+ if (m_image.devicePixelRatio() != window()->handle()->devicePixelRatio())
resize(backingStore()->size(), backingStore()->staticContents());
QPainter painter(&m_image);
@@ -143,11 +103,11 @@ void QWasmBackingStore::resize(const QSize &size, const QRegion &staticContents)
{
Q_UNUSED(staticContents);
- QImage::Format format = window()->format().hasAlpha() ?
- QImage::Format_ARGB32_Premultiplied : QImage::Format_RGB32;
- m_image = QImage(size * window()->devicePixelRatio(), format);
- m_image.setDevicePixelRatio(window()->devicePixelRatio());
- m_recreateTexture = true;
+ QImage::Format format = QImage::Format_RGBA8888;
+ const auto platformScreenDPR = window()->handle()->devicePixelRatio();
+ m_image = QImage(size * platformScreenDPR, format);
+ m_image.setDevicePixelRatio(platformScreenDPR);
+ m_webImageDataArray = emscripten::val::undefined();
}
QImage QWasmBackingStore::toImage() const
@@ -161,10 +121,10 @@ const QImage &QWasmBackingStore::getImageRef() const
return m_image;
}
-const QOpenGLTexture *QWasmBackingStore::getUpdatedTexture()
+emscripten::val QWasmBackingStore::getUpdatedWebImage(QWasmWindow *window)
{
- updateTexture();
- return m_texture.data();
+ updateTexture(window);
+ return m_webImageDataArray;
}
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/wasm/qwasmbackingstore.h b/src/plugins/platforms/wasm/qwasmbackingstore.h
index b5c5e2e406..54e9fe4cb3 100644
--- a/src/plugins/platforms/wasm/qwasmbackingstore.h
+++ b/src/plugins/platforms/wasm/qwasmbackingstore.h
@@ -7,18 +7,20 @@
#include <qpa/qplatformbackingstore.h>
#include <QtGui/qimage.h>
+#include <emscripten/val.h>
+
QT_BEGIN_NAMESPACE
class QOpenGLTexture;
class QRegion;
class QWasmCompositor;
+class QWasmWindow;
class QWasmBackingStore : public QPlatformBackingStore
{
public:
QWasmBackingStore(QWasmCompositor *compositor, QWindow *window);
~QWasmBackingStore();
- void destroy();
QPaintDevice *paintDevice() override;
@@ -28,17 +30,16 @@ public:
QImage toImage() const override;
const QImage &getImageRef() const;
- const QOpenGLTexture *getUpdatedTexture();
+ emscripten::val getUpdatedWebImage(QWasmWindow *window);
protected:
- void updateTexture();
+ void updateTexture(QWasmWindow *window);
private:
QWasmCompositor *m_compositor;
QImage m_image;
- QScopedPointer<QOpenGLTexture> m_texture;
QRegion m_dirty;
- bool m_recreateTexture = false;
+ emscripten::val m_webImageDataArray = emscripten::val::undefined();
};
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/wasm/qwasmbase64iconstore.cpp b/src/plugins/platforms/wasm/qwasmbase64iconstore.cpp
new file mode 100644
index 0000000000..8f05f082ea
--- /dev/null
+++ b/src/plugins/platforms/wasm/qwasmbase64iconstore.cpp
@@ -0,0 +1,40 @@
+// Copyright (C) 2018 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "qwasmbase64iconstore.h"
+
+#include <QtCore/qfile.h>
+
+QT_BEGIN_NAMESPACE
+
+Q_GLOBAL_STATIC(Base64IconStore, globalWasmWindowIconStore);
+
+Base64IconStore::Base64IconStore()
+{
+ QString iconSources[static_cast<size_t>(IconType::Size)] = {
+ QStringLiteral(":/wasm-window/maximize.svg"), QStringLiteral(":/wasm-window/qtlogo.svg"),
+ QStringLiteral(":/wasm-window/restore.svg"), QStringLiteral(":/wasm-window/x.svg")
+ };
+
+ for (size_t iconType = static_cast<size_t>(IconType::First);
+ iconType < static_cast<size_t>(IconType::Size); ++iconType) {
+ QFile svgFile(iconSources[static_cast<size_t>(iconType)]);
+ if (!svgFile.open(QIODevice::ReadOnly))
+ Q_ASSERT(false); // A resource should always be opened.
+ m_storage[static_cast<size_t>(iconType)] = svgFile.readAll().toBase64();
+ }
+}
+
+Base64IconStore::~Base64IconStore() = default;
+
+Base64IconStore *Base64IconStore::get()
+{
+ return globalWasmWindowIconStore();
+}
+
+std::string_view Base64IconStore::getIcon(IconType type) const
+{
+ return m_storage[static_cast<size_t>(type)];
+}
+
+QT_END_NAMESPACE
diff --git a/src/plugins/platforms/wasm/qwasmbase64iconstore.h b/src/plugins/platforms/wasm/qwasmbase64iconstore.h
new file mode 100644
index 0000000000..89704f2d2c
--- /dev/null
+++ b/src/plugins/platforms/wasm/qwasmbase64iconstore.h
@@ -0,0 +1,37 @@
+// Copyright (C) 2018 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef QWASMBASE64IMAGESTORE_H
+#define QWASMBASE64IMAGESTORE_H
+
+#include <string>
+#include <string_view>
+
+#include <QtCore/qtconfigmacros.h>
+
+QT_BEGIN_NAMESPACE
+class Base64IconStore
+{
+public:
+ enum class IconType {
+ Maximize,
+ First = Maximize,
+ QtLogo,
+ Restore,
+ X,
+ Size,
+ };
+
+ Base64IconStore();
+ ~Base64IconStore();
+
+ static Base64IconStore *get();
+
+ std::string_view getIcon(IconType type) const;
+
+private:
+ std::string m_storage[static_cast<size_t>(IconType::Size)];
+};
+
+QT_END_NAMESPACE
+#endif // QWASMBASE64IMAGESTORE_H
diff --git a/src/plugins/platforms/wasm/qwasmclipboard.cpp b/src/plugins/platforms/wasm/qwasmclipboard.cpp
index 99f3e61155..1aa3ffa5b3 100644
--- a/src/plugins/platforms/wasm/qwasmclipboard.cpp
+++ b/src/plugins/platforms/wasm/qwasmclipboard.cpp
@@ -2,20 +2,19 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#include "qwasmclipboard.h"
+#include "qwasmdom.h"
+#include "qwasmevent.h"
#include "qwasmwindow.h"
-#include "qwasmstring.h"
-#include <private/qstdweb_p.h>
-#include <emscripten.h>
-#include <emscripten/html5.h>
-#include <emscripten/bind.h>
-#include <emscripten/val.h>
+#include <private/qstdweb_p.h>
#include <QCoreApplication>
#include <qpa/qwindowsysteminterface.h>
#include <QBuffer>
#include <QString>
+#include <emscripten/val.h>
+
QT_BEGIN_NAMESPACE
using namespace emscripten;
@@ -27,12 +26,11 @@ static void commonCopyEvent(val event)
// doing it this way seems to sanitize the text better that calling data() like down below
if (_mimes->hasText()) {
- event["clipboardData"].call<void>("setData", val("text/plain")
- , QWasmString::fromQString(_mimes->text()));
+ event["clipboardData"].call<void>("setData", val("text/plain"),
+ _mimes->text().toEcmaString());
}
if (_mimes->hasHtml()) {
- event["clipboardData"].call<void>("setData", val("text/html")
- , QWasmString::fromQString(_mimes->html()));
+ event["clipboardData"].call<void>("setData", val("text/html"), _mimes->html().toEcmaString());
}
for (auto mimetype : _mimes->formats()) {
@@ -40,21 +38,19 @@ static void commonCopyEvent(val event)
continue;
QByteArray ba = _mimes->data(mimetype);
if (!ba.isEmpty())
- event["clipboardData"].call<void>("setData", QWasmString::fromQString(mimetype)
- , val(ba.constData()));
+ event["clipboardData"].call<void>("setData", mimetype.toEcmaString(),
+ val(ba.constData()));
}
event.call<void>("preventDefault");
- QWasmIntegration::get()->getWasmClipboard()->m_isListener = false;
}
static void qClipboardCutTo(val event)
{
- QWasmIntegration::get()->getWasmClipboard()->m_isListener = true;
- if (!QWasmIntegration::get()->getWasmClipboard()->hasClipboardApi) {
+ if (!QWasmIntegration::get()->getWasmClipboard()->hasClipboardApi()) {
// Send synthetic Ctrl+X to make the app cut data to Qt's clipboard
- QWindowSystemInterface::handleKeyEvent<QWindowSystemInterface::SynchronousDelivery>(
- 0, QEvent::KeyPress, Qt::Key_C, Qt::ControlModifier, "X");
+ QWindowSystemInterface::handleKeyEvent(
+ 0, QEvent::KeyPress, Qt::Key_X, Qt::ControlModifier, "X");
}
commonCopyEvent(event);
@@ -62,80 +58,19 @@ static void qClipboardCutTo(val event)
static void qClipboardCopyTo(val event)
{
- QWasmIntegration::get()->getWasmClipboard()->m_isListener = true;
-
- if (!QWasmIntegration::get()->getWasmClipboard()->hasClipboardApi) {
+ if (!QWasmIntegration::get()->getWasmClipboard()->hasClipboardApi()) {
// Send synthetic Ctrl+C to make the app copy data to Qt's clipboard
- QWindowSystemInterface::handleKeyEvent<QWindowSystemInterface::SynchronousDelivery>(
+ QWindowSystemInterface::handleKeyEvent(
0, QEvent::KeyPress, Qt::Key_C, Qt::ControlModifier, "C");
}
commonCopyEvent(event);
}
-static void qClipboardPasteTo(val dataTransfer)
+static void qClipboardPasteTo(val event)
{
- QWasmIntegration::get()->getWasmClipboard()->m_isListener = true;
- val clipboardData = dataTransfer["clipboardData"];
- val types = clipboardData["types"];
- int typesCount = types["length"].as<int>();
- std::string stdMimeFormat;
- QMimeData *mMimeData = new QMimeData;
- for (int i = 0; i < typesCount; i++) {
- stdMimeFormat = types[i].as<std::string>();
- QString mimeFormat = QString::fromStdString(stdMimeFormat);
- if (mimeFormat.contains("STRING", Qt::CaseSensitive) || mimeFormat.contains("TEXT", Qt::CaseSensitive))
- continue;
-
- if (mimeFormat.contains("text")) {
-// also "text/plain;charset=utf-8"
-// "UTF8_STRING" "MULTIPLE"
- val mimeData = clipboardData.call<val>("getData", val(stdMimeFormat)); // as DataTransfer
-
- const QString qstr = QWasmString::toQString(mimeData);
+ event.call<void>("preventDefault"); // prevent browser from handling drop event
- if (qstr.length() > 0) {
- if (mimeFormat.contains("text/html")) {
- mMimeData->setHtml(qstr);
- } else if (mimeFormat.isEmpty() || mimeFormat.contains("text/plain")) {
- mMimeData->setText(qstr); // the type can be empty
- } else {
- mMimeData->setData(mimeFormat, qstr.toLocal8Bit());}
- }
- } else {
- val items = clipboardData["items"];
-
- int itemsCount = items["length"].as<int>();
- // handle data
- for (int i = 0; i < itemsCount; i++) {
- val item = items[i];
- val clipboardFile = item.call<emscripten::val>("getAsFile"); // string kind is handled above
- if (clipboardFile.isUndefined() || item["kind"].as<std::string>() == "string" ) {
- continue;
- }
- qstdweb::File file(clipboardFile);
-
- mimeFormat = QString::fromStdString(file.type());
- QByteArray fileContent;
- fileContent.resize(file.size());
-
- file.stream(fileContent.data(), [=]() {
- if (!fileContent.isEmpty()) {
-
- if (mimeFormat.contains("image")) {
- QImage image;
- image.loadFromData(fileContent, nullptr);
- mMimeData->setImageData(image);
- } else {
- mMimeData->setData(mimeFormat,fileContent.data());
- }
- QWasmClipboard::qWasmClipboardPaste(mMimeData);
- }
- });
- } // next item
- }
- }
- QWasmClipboard::qWasmClipboardPaste(mMimeData);
- QWasmIntegration::get()->getWasmClipboard()->m_isListener = false;
+ QWasmIntegration::get()->getWasmClipboard()->sendClipboardData(event);
}
EMSCRIPTEN_BINDINGS(qtClipboardModule) {
@@ -144,26 +79,14 @@ EMSCRIPTEN_BINDINGS(qtClipboardModule) {
function("qtClipboardPasteTo", &qClipboardPasteTo);
}
-QWasmClipboard::QWasmClipboard() :
- isPaste(false),
- m_isListener(false)
+QWasmClipboard::QWasmClipboard()
{
val clipboard = val::global("navigator")["clipboard"];
- val permissions = val::global("navigator")["permissions"];
- val hasInstallTrigger = val::global("window")["InstallTrigger"];
-
- hasPermissionsApi = !permissions.isUndefined();
- hasClipboardApi = (!clipboard.isUndefined() && !clipboard["readText"].isUndefined());
- bool isFirefox = !hasInstallTrigger.isUndefined();
- isSafari = !emscripten::val::global("window")["safari"].isUndefined();
-
- // firefox has clipboard API if user sets these config tweaks:
- // dom.events.asyncClipboard.clipboardItem true
- // dom.events.asyncClipboard.read true
- // dom.events.testing.asyncClipboard
- // and permissions API, but does not currently support
- // the clipboardRead and clipboardWrite permissions
- if (hasClipboardApi && hasPermissionsApi && !isFirefox)
+
+ const bool hasPermissionsApi = !val::global("navigator")["permissions"].isUndefined();
+ m_hasClipboardApi = !clipboard.isUndefined() && !clipboard["readText"].isUndefined();
+
+ if (m_hasClipboardApi && hasPermissionsApi)
initClipboardPermissions();
}
@@ -181,16 +104,27 @@ QMimeData *QWasmClipboard::mimeData(QClipboard::Mode mode)
void QWasmClipboard::setMimeData(QMimeData *mimeData, QClipboard::Mode mode)
{
- QPlatformClipboard::setMimeData(mimeData, mode);
// handle setText/ setData programmatically
- if (!isPaste) {
- if (hasClipboardApi) {
- writeToClipboardApi();
- } else if (!m_isListener) {
- writeToClipboard(mimeData);
- }
- }
- isPaste = false;
+ QPlatformClipboard::setMimeData(mimeData, mode);
+ if (m_hasClipboardApi)
+ writeToClipboardApi();
+ else
+ writeToClipboard();
+}
+
+QWasmClipboard::ProcessKeyboardResult QWasmClipboard::processKeyboard(const KeyEvent &event)
+{
+ if (event.type != EventType::KeyDown || !event.modifiers.testFlag(Qt::ControlModifier))
+ return ProcessKeyboardResult::Ignored;
+
+ if (event.key != Qt::Key_C && event.key != Qt::Key_V && event.key != Qt::Key_X)
+ return ProcessKeyboardResult::Ignored;
+
+ const bool isPaste = event.key == Qt::Key_V;
+
+ return m_hasClipboardApi && !isPaste
+ ? ProcessKeyboardResult::NativeClipboardEventAndCopiedDataNeeded
+ : ProcessKeyboardResult::NativeClipboardEventNeeded;
}
bool QWasmClipboard::supportsMode(QClipboard::Mode mode) const
@@ -204,38 +138,31 @@ bool QWasmClipboard::ownsMode(QClipboard::Mode mode) const
return false;
}
-void QWasmClipboard::qWasmClipboardPaste(QMimeData *mData)
-{
- QWasmIntegration::get()->clipboard()->setMimeData(mData, QClipboard::Clipboard);
-
- QWindowSystemInterface::handleKeyEvent<QWindowSystemInterface::SynchronousDelivery>(
- 0, QEvent::KeyPress, Qt::Key_V, Qt::ControlModifier, "V");
-}
-
void QWasmClipboard::initClipboardPermissions()
{
- if (!hasClipboardApi)
- return;
-
val permissions = val::global("navigator")["permissions"];
- val readPermissionsMap = val::object();
- readPermissionsMap.set("name", val("clipboard-read"));
- permissions.call<val>("query", readPermissionsMap);
- val writePermissionsMap = val::object();
- writePermissionsMap.set("name", val("clipboard-write"));
- permissions.call<val>("query", writePermissionsMap);
+ qstdweb::Promise::make(permissions, "query", { .catchFunc = [](emscripten::val) {} }, ([]() {
+ val readPermissionsMap = val::object();
+ readPermissionsMap.set("name", val("clipboard-read"));
+ return readPermissionsMap;
+ })());
+ qstdweb::Promise::make(permissions, "query", { .catchFunc = [](emscripten::val) {} }, ([]() {
+ val readPermissionsMap = val::object();
+ readPermissionsMap.set("name", val("clipboard-write"));
+ return readPermissionsMap;
+ })());
}
-void QWasmClipboard::installEventHandlers(const emscripten::val &canvas)
+void QWasmClipboard::installEventHandlers(const emscripten::val &target)
{
emscripten::val cContext = val::undefined();
emscripten::val isChromium = val::global("window")["chrome"];
- if (!isChromium.isUndefined()) {
+ if (!isChromium.isUndefined()) {
cContext = val::global("document");
- } else {
- cContext = canvas;
- }
+ } else {
+ cContext = target;
+ }
// Fallback path for browsers which do not support direct clipboard access
cContext.call<void>("addEventListener", val("cut"),
val::module_property("qtClipboardCutTo"), true);
@@ -245,16 +172,20 @@ void QWasmClipboard::installEventHandlers(const emscripten::val &canvas)
val::module_property("qtClipboardPasteTo"), true);
}
+bool QWasmClipboard::hasClipboardApi()
+{
+ return m_hasClipboardApi;
+}
+
void QWasmClipboard::writeToClipboardApi()
{
- if (!QWasmIntegration::get()->getWasmClipboard()->hasClipboardApi)
- return;
+ Q_ASSERT(m_hasClipboardApi);
// copy event
// browser event handler detected ctrl c if clipboard API
// or Qt call from keyboard event handler
- QMimeData *_mimes = QWasmIntegration::get()->getWasmClipboard()->mimeData(QClipboard::Clipboard);
+ QMimeData *_mimes = mimeData(QClipboard::Clipboard);
if (!_mimes)
return;
@@ -312,12 +243,12 @@ void QWasmClipboard::writeToClipboardApi()
// we have a blob, now create a ClipboardItem
emscripten::val type = emscripten::val::array();
- type.set("type", val(QWasmString::fromQString(mimetype)));
+ type.set("type", mimetype.toEcmaString());
emscripten::val contentBlob = emscripten::val::global("Blob").new_(contentArray, type);
emscripten::val clipboardItemObject = emscripten::val::object();
- clipboardItemObject.set(val(QWasmString::fromQString(mimetype)), contentBlob);
+ clipboardItemObject.set(mimetype.toEcmaString(), contentBlob);
val clipboardItemData = val::global("ClipboardItem").new_(clipboardItemObject);
@@ -342,9 +273,8 @@ void QWasmClipboard::writeToClipboardApi()
clipboardWriteArray);
}
-void QWasmClipboard::writeToClipboard(const QMimeData *data)
+void QWasmClipboard::writeToClipboard()
{
- Q_UNUSED(data)
// this works for firefox, chrome by generating
// copy event, but not safari
// execCommand has been deemed deprecated in the docs, but browsers do not seem
@@ -352,4 +282,23 @@ void QWasmClipboard::writeToClipboard(const QMimeData *data)
val document = val::global("document");
document.call<val>("execCommand", val("copy"));
}
+
+void QWasmClipboard::sendClipboardData(emscripten::val event)
+{
+ qDebug() << "sendClipboardData";
+
+ dom::DataTransfer *transfer = new dom::DataTransfer(event["clipboardData"]);
+ const auto mimeCallback = std::function([transfer](QMimeData *data) {
+
+ // Persist clipboard data so that the app can read it when handling the CTRL+V
+ QWasmIntegration::get()->clipboard()->QPlatformClipboard::setMimeData(data, QClipboard::Clipboard);
+ QWindowSystemInterface::handleKeyEvent(0, QEvent::KeyPress, Qt::Key_V,
+ Qt::ControlModifier, "V");
+ QWindowSystemInterface::handleKeyEvent(0, QEvent::KeyRelease, Qt::Key_V,
+ Qt::ControlModifier, "V");
+ delete transfer;
+ });
+
+ transfer->toMimeDataWithFile(mimeCallback);
+}
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/wasm/qwasmclipboard.h b/src/plugins/platforms/wasm/qwasmclipboard.h
index ef11fd2e49..86618dd560 100644
--- a/src/plugins/platforms/wasm/qwasmclipboard.h
+++ b/src/plugins/platforms/wasm/qwasmclipboard.h
@@ -7,6 +7,7 @@
#include <QObject>
#include <qpa/qplatformclipboard.h>
+#include <private/qstdweb_p.h>
#include <QMimeData>
#include <emscripten/bind.h>
@@ -14,9 +15,17 @@
QT_BEGIN_NAMESPACE
+struct KeyEvent;
+
class QWasmClipboard : public QObject, public QPlatformClipboard
{
public:
+ enum class ProcessKeyboardResult {
+ Ignored,
+ NativeClipboardEventNeeded,
+ NativeClipboardEventAndCopiedDataNeeded,
+ };
+
QWasmClipboard();
virtual ~QWasmClipboard();
@@ -26,16 +35,17 @@ public:
bool supportsMode(QClipboard::Mode mode) const override;
bool ownsMode(QClipboard::Mode mode) const override;
- static void qWasmClipboardPaste(QMimeData *mData);
+ ProcessKeyboardResult processKeyboard(const KeyEvent &event);
+ static void installEventHandlers(const emscripten::val &target);
+ bool hasClipboardApi();
+ void sendClipboardData(emscripten::val event);
+
+private:
void initClipboardPermissions();
- void installEventHandlers(const emscripten::val &canvas);
- bool hasClipboardApi;
- bool hasPermissionsApi;
void writeToClipboardApi();
- void writeToClipboard(const QMimeData *data);
- bool isPaste;
- bool m_isListener;
- bool isSafari;
+ void writeToClipboard();
+
+ bool m_hasClipboardApi = false;
};
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/wasm/qwasmcompositor.cpp b/src/plugins/platforms/wasm/qwasmcompositor.cpp
index e93098cf79..ef460f666f 100644
--- a/src/plugins/platforms/wasm/qwasmcompositor.cpp
+++ b/src/plugins/platforms/wasm/qwasmcompositor.cpp
@@ -2,165 +2,42 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#include "qwasmcompositor.h"
-#include "qwasmstylepixmaps_p.h"
#include "qwasmwindow.h"
-#include "qwasmeventtranslator.h"
-#include "qwasmeventdispatcher.h"
-#include "qwasmclipboard.h"
-#include "qwasmevent.h"
-#include <QtOpenGL/qopengltexture.h>
-
-#include <QtGui/private/qwindow_p.h>
-#include <QtGui/qopenglcontext.h>
-#include <QtGui/qopenglfunctions.h>
-#include <QtGui/qoffscreensurface.h>
-#include <QtGui/qpainter.h>
-#include <private/qpixmapcache_p.h>
-
-#include <private/qguiapplication_p.h>
+#include <private/qeventdispatcher_wasm_p.h>
#include <qpa/qwindowsysteminterface.h>
-#include <QtCore/qcoreapplication.h>
-#include <QtGui/qguiapplication.h>
-
-#include <emscripten/bind.h>
-namespace {
-QWasmWindow *AsWasmWindow(QWindow *window) {
- return static_cast<QWasmWindow*>(window->handle());
-}
-} // namespace
+#include <emscripten/html5.h>
using namespace emscripten;
-Q_GUI_EXPORT int qt_defaultDpiX();
-
-bool g_scrollingInvertedFromDevice = false;
-
-static void mouseWheelEvent(emscripten::val event)
-{
- emscripten::val wheelInverted = event["webkitDirectionInvertedFromDevice"];
- if (wheelInverted.as<bool>())
- g_scrollingInvertedFromDevice = true;
-}
-
-EMSCRIPTEN_BINDINGS(qtMouseModule) {
- function("qtMouseWheelEvent", &mouseWheelEvent);
-}
+bool QWasmCompositor::m_requestUpdateHoldEnabled = true;
-QWasmCompositor::QWasmCompositor(QWasmScreen *screen)
- : QObject(screen)
- , m_windowManipulation(screen)
- , m_blitter(new QOpenGLTextureBlitter)
- , m_eventTranslator(std::make_unique<QWasmEventTranslator>())
+QWasmCompositor::QWasmCompositor(QWasmScreen *screen) : QObject(screen)
{
- m_touchDevice = std::make_unique<QPointingDevice>(
- "touchscreen", 1, QInputDevice::DeviceType::TouchScreen,
- QPointingDevice::PointerType::Finger,
- QPointingDevice::Capability::Position | QPointingDevice::Capability::Area
- | QPointingDevice::Capability::NormalizedPosition,
- 10, 0);
- QWindowSystemInterface::registerInputDevice(m_touchDevice.get());
+ QWindowSystemInterface::setSynchronousWindowSystemEvents(true);
}
QWasmCompositor::~QWasmCompositor()
{
- m_windowUnderMouse.clear();
-
if (m_requestAnimationFrameId != -1)
emscripten_cancel_animation_frame(m_requestAnimationFrameId);
- deregisterEventHandlers();
- destroy();
-}
-
-void QWasmCompositor::deregisterEventHandlers()
-{
- QByteArray canvasSelector = screen()->canvasTargetId().toUtf8();
- emscripten_set_keydown_callback(canvasSelector.constData(), 0, 0, NULL);
- emscripten_set_keyup_callback(canvasSelector.constData(), 0, 0, NULL);
-
- emscripten_set_focus_callback(canvasSelector.constData(), 0, 0, NULL);
-
- emscripten_set_wheel_callback(canvasSelector.constData(), 0, 0, NULL);
-
- emscripten_set_touchstart_callback(canvasSelector.constData(), 0, 0, NULL);
- emscripten_set_touchend_callback(canvasSelector.constData(), 0, 0, NULL);
- emscripten_set_touchmove_callback(canvasSelector.constData(), 0, 0, NULL);
- emscripten_set_touchcancel_callback(canvasSelector.constData(), 0, 0, NULL);
-
- val canvas = screen()->canvas();
- canvas.call<void>("removeEventListener",
- std::string("drop"),
- val::module_property("qtDrop"), val(true));
-}
-
-void QWasmCompositor::destroy()
-{
- // Destroy OpenGL resources. This is done here in a separate function
- // which can be called while screen() still returns a valid screen
- // (which it might not, during destruction). A valid QScreen is
- // a requirement for QOffscreenSurface on Wasm since the native
- // context is tied to a single canvas.
- if (m_context) {
- QOffscreenSurface offScreenSurface(screen()->screen());
- offScreenSurface.setFormat(m_context->format());
- offScreenSurface.create();
- m_context->makeCurrent(&offScreenSurface);
- for (QWasmWindow *window : m_windowStack)
- window->destroy();
- m_blitter.reset(nullptr);
- m_context.reset(nullptr);
- }
-
+ // TODO(mikolaj.boc): Investigate if m_isEnabled is needed at all. It seems like a frame should
+ // not be generated after this instead.
m_isEnabled = false; // prevent frame() from creating a new m_context
}
-void QWasmCompositor::initEventHandlers()
+void QWasmCompositor::onWindowTreeChanged(QWasmWindowTreeNodeChangeType changeType,
+ QWasmWindow *window)
{
- QByteArray canvasSelector = screen()->canvasTargetId().toUtf8();
-
- if (platform() == Platform::MacOS) {
- if (!emscripten::val::global("window")["safari"].isUndefined()) {
- val canvas = screen()->canvas();
- canvas.call<void>("addEventListener",
- val("wheel"),
- val::module_property("qtMouseWheelEvent"));
- }
- }
-
- constexpr EM_BOOL UseCapture = 1;
-
- emscripten_set_keydown_callback(canvasSelector.constData(), (void *)this, UseCapture, &keyboard_cb);
- emscripten_set_keyup_callback(canvasSelector.constData(), (void *)this, UseCapture, &keyboard_cb);
-
- val canvas = screen()->canvas();
- const auto callback = std::function([this](emscripten::val event) {
- if (processPointer(*PointerEvent::fromWeb(event)))
- event.call<void>("preventDefault");
- });
-
- m_pointerDownCallback = std::make_unique<qstdweb::EventCallback>(canvas, "pointerdown", callback);
- m_pointerMoveCallback = std::make_unique<qstdweb::EventCallback>(canvas, "pointermove", callback);
- m_pointerUpCallback = std::make_unique<qstdweb::EventCallback>(canvas, "pointerup", callback);
- m_pointerEnterCallback = std::make_unique<qstdweb::EventCallback>(canvas, "pointerenter", callback);
- m_pointerLeaveCallback = std::make_unique<qstdweb::EventCallback>(canvas, "pointerleave", callback);
-
- emscripten_set_focus_callback(canvasSelector.constData(), (void *)this, UseCapture, &focus_cb);
-
- emscripten_set_wheel_callback(canvasSelector.constData(), (void *)this, UseCapture, &wheel_cb);
-
- emscripten_set_touchstart_callback(canvasSelector.constData(), (void *)this, UseCapture, &touchCallback);
- emscripten_set_touchend_callback(canvasSelector.constData(), (void *)this, UseCapture, &touchCallback);
- emscripten_set_touchmove_callback(canvasSelector.constData(), (void *)this, UseCapture, &touchCallback);
- emscripten_set_touchcancel_callback(canvasSelector.constData(), (void *)this, UseCapture, &touchCallback);
-
- canvas.call<void>("addEventListener",
- std::string("drop"),
- val::module_property("qtDrop"), val(true));
- canvas.set("data-qtdropcontext", // ? unique
- emscripten::val(quintptr(reinterpret_cast<void *>(screen()))));
+ auto allWindows = screen()->allWindows();
+ setEnabled(std::any_of(allWindows.begin(), allWindows.end(), [](QWasmWindow *element) {
+ return !element->context2d().isUndefined();
+ }));
+ if (changeType == QWasmWindowTreeNodeChangeType::NodeRemoval)
+ m_requestUpdateWindows.remove(window);
}
void QWasmCompositor::setEnabled(bool enabled)
@@ -168,199 +45,14 @@ void QWasmCompositor::setEnabled(bool enabled)
m_isEnabled = enabled;
}
-void QWasmCompositor::addWindow(QWasmWindow *window)
-{
- m_windowVisibility.insert(window, false);
-
- m_windowStack.append(window);
-
- notifyTopWindowChanged(window);
-}
-
-void QWasmCompositor::removeWindow(QWasmWindow *window)
-{
- m_windowStack.removeAll(window);
- m_windowVisibility.remove(window);
- m_requestUpdateWindows.remove(window);
-
- if (!m_windowStack.isEmpty() && !QGuiApplication::focusWindow()) {
- auto m_lastMouseTargetWindow = m_windowStack.last();
- m_lastMouseTargetWindow->requestActivateWindow();
- notifyTopWindowChanged(m_lastMouseTargetWindow);
- }
-}
-
-void QWasmCompositor::setVisible(QWasmWindow *window, bool visible)
-{
- const bool wasVisible = m_windowVisibility[window];
- if (wasVisible == visible)
- return;
-
- m_windowVisibility[window] = visible;
- if (!visible)
- m_globalDamage = window->window()->geometry(); // repaint previously covered area.
-
- requestUpdateWindow(window, QWasmCompositor::ExposeEventDelivery);
-}
-
-void QWasmCompositor::raise(QWasmWindow *window)
-{
- if (m_windowStack.size() <= 1)
- return;
-
- m_windowStack.removeAll(window);
- m_windowStack.append(window);
-
- notifyTopWindowChanged(window);
-}
-
-void QWasmCompositor::lower(QWasmWindow *window)
-{
- if (m_windowStack.size() <= 1)
- return;
-
- m_windowStack.removeAll(window);
- m_windowStack.prepend(window);
- m_globalDamage = window->window()->geometry(); // repaint previously covered area.
-
- notifyTopWindowChanged(window);
-}
-
-int QWasmCompositor::windowCount() const
-{
- return m_windowStack.count();
-}
-
-QWindow *QWasmCompositor::windowAt(QPoint targetPointInScreenCoords, int padding) const
-{
- const auto found = std::find_if(m_windowStack.rbegin(), m_windowStack.rend(),
- [this, padding, &targetPointInScreenCoords](const QWasmWindow* window) {
- const QRect geometry = window->windowFrameGeometry()
- .adjusted(-padding, -padding, padding, padding);
-
- return m_windowVisibility[window] && geometry.contains(targetPointInScreenCoords);
- });
- return found != m_windowStack.rend() ? (*found)->window() : nullptr;
-}
-
-QWindow *QWasmCompositor::keyWindow() const
-{
- return m_windowStack.at(m_windowStack.count() - 1)->window();
-}
-
-void QWasmCompositor::blit(QOpenGLTextureBlitter *blitter, QWasmScreen *screen, const QOpenGLTexture *texture, QRect targetGeometry)
-{
- QMatrix4x4 m;
- m.translate(-1.0f, -1.0f);
-
- m.scale(2.0f / (float)screen->geometry().width(),
- 2.0f / (float)screen->geometry().height());
-
- m.translate((float)targetGeometry.width() / 2.0f,
- (float)-targetGeometry.height() / 2.0f);
-
- m.translate(targetGeometry.x(), screen->geometry().height() - targetGeometry.y());
-
- m.scale(0.5f * (float)targetGeometry.width(),
- 0.5f * (float)targetGeometry.height());
-
- blitter->blit(texture->textureId(), m, QOpenGLTextureBlitter::OriginTopLeft);
-}
-
-void QWasmCompositor::drawWindowContent(QOpenGLTextureBlitter *blitter, QWasmScreen *screen, QWasmWindow *window)
-{
- QWasmBackingStore *backingStore = window->backingStore();
- if (!backingStore)
- return;
-
- QOpenGLTexture const *texture = backingStore->getUpdatedTexture();
- QRect windowCanvasGeometry = window->geometry().translated(-screen->geometry().topLeft());
- blit(blitter, screen, texture, windowCanvasGeometry);
-}
-
-QPalette QWasmCompositor::makeWindowPalette()
-{
- QPalette palette;
- palette.setColor(QPalette::Active, QPalette::Highlight,
- palette.color(QPalette::Active, QPalette::Highlight));
- palette.setColor(QPalette::Active, QPalette::Base,
- palette.color(QPalette::Active, QPalette::Highlight));
- palette.setColor(QPalette::Inactive, QPalette::Highlight,
- palette.color(QPalette::Inactive, QPalette::Dark));
- palette.setColor(QPalette::Inactive, QPalette::Base,
- palette.color(QPalette::Inactive, QPalette::Dark));
- palette.setColor(QPalette::Inactive, QPalette::HighlightedText,
- palette.color(QPalette::Inactive, QPalette::Window));
-
- return palette;
-}
-
-QRect QWasmCompositor::titlebarRect(QWasmTitleBarOptions tb, QWasmCompositor::SubControls subcontrol)
-{
- QRect ret;
- const int controlMargin = 2;
- const int controlHeight = tb.rect.height() - controlMargin *2;
- const int delta = controlHeight + controlMargin;
- int offset = 0;
-
- bool isMinimized = tb.state & Qt::WindowMinimized;
- bool isMaximized = tb.state & Qt::WindowMaximized;
-
- ret = tb.rect;
- switch (subcontrol) {
- case SC_TitleBarLabel:
- if (tb.flags & Qt::WindowSystemMenuHint)
- ret.adjust(delta, 0, -delta, 0);
- break;
- case SC_TitleBarCloseButton:
- if (tb.flags & Qt::WindowSystemMenuHint) {
- ret.adjust(0, 0, -delta, 0);
- offset += delta;
- }
- break;
- case SC_TitleBarMaxButton:
- if (!isMaximized && tb.flags & Qt::WindowMaximizeButtonHint) {
- ret.adjust(0, 0, -delta*2, 0);
- offset += (delta +delta);
- }
- break;
- case SC_TitleBarNormalButton:
- if (isMinimized && (tb.flags & Qt::WindowMinimizeButtonHint)) {
- offset += delta;
- } else if (isMaximized && (tb.flags & Qt::WindowMaximizeButtonHint)) {
- ret.adjust(0, 0, -delta*2, 0);
- offset += (delta +delta);
- }
- break;
- case SC_TitleBarSysMenu:
- if (tb.flags & Qt::WindowSystemMenuHint) {
- ret.setRect(tb.rect.left() + controlMargin, tb.rect.top() + controlMargin,
- controlHeight, controlHeight);
- }
- break;
- default:
- break;
- };
-
- if (subcontrol != SC_TitleBarLabel && subcontrol != SC_TitleBarSysMenu) {
- ret.setRect(tb.rect.right() - offset, tb.rect.top() + controlMargin,
- controlHeight, controlHeight);
- }
-
- if (qApp->layoutDirection() == Qt::LeftToRight)
- return ret;
-
- QRect rect = ret;
- rect.translate(2 * (tb.rect.right() - ret.right()) +
- ret.width() - tb.rect.width(), 0);
-
- return rect;
-}
-
-void QWasmCompositor::requestUpdateAllWindows()
+// requestUpdate delivery is initially disabled at startup, while Qt completes
+// startup tasks such as font loading. This function enables requestUpdate delivery
+// again.
+bool QWasmCompositor::releaseRequestUpdateHold()
{
- m_requestUpdateAllWindows = true;
- requestUpdate();
+ const bool wasEnabled = m_requestUpdateHoldEnabled;
+ m_requestUpdateHoldEnabled = false;
+ return wasEnabled;
}
void QWasmCompositor::requestUpdateWindow(QWasmWindow *window, UpdateRequestDeliveryType updateType)
@@ -384,11 +76,17 @@ void QWasmCompositor::requestUpdate()
if (m_requestAnimationFrameId != -1)
return;
+ if (m_requestUpdateHoldEnabled)
+ return;
+
static auto frame = [](double frameTime, void *context) -> int {
Q_UNUSED(frameTime);
+
QWasmCompositor *compositor = reinterpret_cast<QWasmCompositor *>(context);
+
compositor->m_requestAnimationFrameId = -1;
compositor->deliverUpdateRequests();
+
return 0;
};
m_requestAnimationFrameId = emscripten_request_animation_frame(frame, this);
@@ -401,905 +99,61 @@ void QWasmCompositor::deliverUpdateRequests()
// update set.
auto requestUpdateWindows = m_requestUpdateWindows;
m_requestUpdateWindows.clear();
- bool requestUpdateAllWindows = m_requestUpdateAllWindows;
- m_requestUpdateAllWindows = false;
- // Update window content, either all windows or a spesific set of windows. Use the correct update
- // type: QWindow subclasses expect that requested and delivered updateRequests matches exactly.
+ // Update window content, either all windows or a spesific set of windows. Use the correct
+ // update type: QWindow subclasses expect that requested and delivered updateRequests matches
+ // exactly.
m_inDeliverUpdateRequest = true;
- if (requestUpdateAllWindows) {
- for (QWasmWindow *window : m_windowStack) {
- auto it = requestUpdateWindows.find(window);
- UpdateRequestDeliveryType updateType =
- (it == m_requestUpdateWindows.end() ? ExposeEventDelivery : it.value());
- deliverUpdateRequest(window, updateType);
- }
- } else {
- for (auto it = requestUpdateWindows.constBegin(); it != requestUpdateWindows.constEnd(); ++it) {
- auto *window = it.key();
- UpdateRequestDeliveryType updateType = it.value();
- deliverUpdateRequest(window, updateType);
- }
+ for (auto it = requestUpdateWindows.constBegin(); it != requestUpdateWindows.constEnd(); ++it) {
+ auto *window = it.key();
+ UpdateRequestDeliveryType updateType = it.value();
+ deliverUpdateRequest(window, updateType);
}
- m_inDeliverUpdateRequest = false;
- // Compose window content
- frame();
+ m_inDeliverUpdateRequest = false;
+ frame(requestUpdateWindows.keys());
}
void QWasmCompositor::deliverUpdateRequest(QWasmWindow *window, UpdateRequestDeliveryType updateType)
{
- // update by deliverUpdateRequest and expose event accordingly.
+ QWindow *qwindow = window->window();
+
+ // Make sure the DPR value for the window is up to date on expose/repaint.
+ // FIXME: listen to native DPR change events instead, if/when available.
+ QWindowSystemInterface::handleWindowDevicePixelRatioChanged(qwindow);
+
+ // Update by deliverUpdateRequest and expose event according to requested update
+ // type. If the window has not yet been exposed then we must expose it first regardless
+ // of update type. The deliverUpdateRequest must still be sent in this case in order
+ // to maintain correct window update state.
+ QRect updateRect(QPoint(0, 0), qwindow->geometry().size());
if (updateType == UpdateRequestDelivery) {
- window->QPlatformWindow::deliverUpdateRequest();
+ if (qwindow->isExposed() == false)
+ QWindowSystemInterface::handleExposeEvent(qwindow, updateRect);
+ window->deliverUpdateRequest();
} else {
- QWindow *qwindow = window->window();
- QWindowSystemInterface::handleExposeEvent<QWindowSystemInterface::SynchronousDelivery>(
- qwindow, QRect(QPoint(0, 0), qwindow->geometry().size()));
+ QWindowSystemInterface::handleExposeEvent(qwindow, updateRect);
}
}
-void QWasmCompositor::handleBackingStoreFlush()
+void QWasmCompositor::handleBackingStoreFlush(QWindow *window)
{
- // Request update to flush the updated backing store content,
- // unless we are currently processing an update, in which case
- // the new content will flushed as a part of that update.
+ // Request update to flush the updated backing store content, unless we are currently
+ // processing an update, in which case the new content will flushed as a part of that update.
if (!m_inDeliverUpdateRequest)
- requestUpdate();
-}
-
-int dpiScaled(qreal value)
-{
- return value * (qreal(qt_defaultDpiX()) / 96.0);
-}
-
-QWasmCompositor::QWasmTitleBarOptions QWasmCompositor::makeTitleBarOptions(const QWasmWindow *window)
-{
- int width = window->windowFrameGeometry().width();
- int border = window->borderWidth();
-
- QWasmTitleBarOptions titleBarOptions;
-
- titleBarOptions.rect = QRect(border, border, width - 2 * border, window->titleHeight());
- titleBarOptions.flags = window->window()->flags();
- titleBarOptions.state = window->window()->windowState();
-
- bool isMaximized = titleBarOptions.state & Qt::WindowMaximized; // this gets reset when maximized
-
- if (titleBarOptions.flags & (Qt::WindowTitleHint))
- titleBarOptions.subControls |= SC_TitleBarLabel;
- if (titleBarOptions.flags & Qt::WindowMaximizeButtonHint) {
- if (isMaximized)
- titleBarOptions.subControls |= SC_TitleBarNormalButton;
- else
- titleBarOptions.subControls |= SC_TitleBarMaxButton;
- }
- if (titleBarOptions.flags & Qt::WindowSystemMenuHint) {
- titleBarOptions.subControls |= SC_TitleBarCloseButton;
- titleBarOptions.subControls |= SC_TitleBarSysMenu;
- }
-
-
- titleBarOptions.palette = QWasmCompositor::makeWindowPalette();
-
- if (window->window()->isActive())
- titleBarOptions.palette.setCurrentColorGroup(QPalette::Active);
- else
- titleBarOptions.palette.setCurrentColorGroup(QPalette::Inactive);
-
- if (window->activeSubControl() != QWasmCompositor::SC_None)
- titleBarOptions.subControls = window->activeSubControl();
-
- if (!window->window()->title().isEmpty())
- titleBarOptions.titleBarOptionsString = window->window()->title();
-
- titleBarOptions.windowIcon = window->window()->icon();
-
- return titleBarOptions;
-}
-
-void QWasmCompositor::drawWindowDecorations(QOpenGLTextureBlitter *blitter, QWasmScreen *screen, QWasmWindow *window)
-{
- int width = window->windowFrameGeometry().width();
- int height = window->windowFrameGeometry().height();
- qreal dpr = window->devicePixelRatio();
-
- QImage image(QSize(width * dpr, height * dpr), QImage::Format_RGB32);
- image.setDevicePixelRatio(dpr);
- QPainter painter(&image);
- painter.fillRect(QRect(0, 0, width, height), painter.background());
-
- QWasmTitleBarOptions titleBarOptions = makeTitleBarOptions(window);
-
- drawTitlebarWindow(titleBarOptions, &painter);
-
- QWasmFrameOptions frameOptions;
- frameOptions.rect = QRect(0, 0, width, height);
- frameOptions.lineWidth = dpiScaled(4.);
-
- drawFrameWindow(frameOptions, &painter);
-
- painter.end();
-
- QOpenGLTexture texture(QOpenGLTexture::Target2D);
- texture.setMinificationFilter(QOpenGLTexture::Nearest);
- texture.setMagnificationFilter(QOpenGLTexture::Nearest);
- texture.setWrapMode(QOpenGLTexture::ClampToEdge);
- texture.setData(image, QOpenGLTexture::DontGenerateMipMaps);
- texture.create();
- texture.bind();
-
- glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, image.width(), image.height(), GL_RGBA, GL_UNSIGNED_BYTE,
- image.constScanLine(0));
-
- QRect windowCanvasGeometry = window->windowFrameGeometry().translated(-screen->geometry().topLeft());
- blit(blitter, screen, &texture, windowCanvasGeometry);
-}
-
-void QWasmCompositor::drawFrameWindow(QWasmFrameOptions options, QPainter *painter)
-{
- int x = options.rect.x();
- int y = options.rect.y();
- int w = options.rect.width();
- int h = options.rect.height();
- const QColor &c1 = options.palette.light().color();
- const QColor &c2 = options.palette.shadow().color();
- const QColor &c3 = options.palette.midlight().color();
- const QColor &c4 = options.palette.dark().color();
- const QBrush *fill = nullptr;
-
- const qreal devicePixelRatio = painter->device()->devicePixelRatio();
- if (!qFuzzyCompare(devicePixelRatio, qreal(1))) {
- const qreal inverseScale = qreal(1) / devicePixelRatio;
- painter->scale(inverseScale, inverseScale);
- x = qRound(devicePixelRatio * x);
- y = qRound(devicePixelRatio * y);
- w = qRound(devicePixelRatio * w);
- h = qRound(devicePixelRatio * h);
- }
-
- QPen oldPen = painter->pen();
- QPoint a[3] = { QPoint(x, y+h-2), QPoint(x, y), QPoint(x+w-2, y) };
- painter->setPen(c1);
- painter->drawPolyline(a, 3);
- QPoint b[3] = { QPoint(x, y+h-1), QPoint(x+w-1, y+h-1), QPoint(x+w-1, y) };
- painter->setPen(c2);
- painter->drawPolyline(b, 3);
- if (w > 4 && h > 4) {
- QPoint c[3] = { QPoint(x+1, y+h-3), QPoint(x+1, y+1), QPoint(x+w-3, y+1) };
- painter->setPen(c3);
- painter->drawPolyline(c, 3);
- QPoint d[3] = { QPoint(x+1, y+h-2), QPoint(x+w-2, y+h-2), QPoint(x+w-2, y+1) };
- painter->setPen(c4);
- painter->drawPolyline(d, 3);
- if (fill)
- painter->fillRect(QRect(x+2, y+2, w-4, h-4), *fill);
- }
- painter->setPen(oldPen);
-}
-
-//from commonstyle.cpp
-static QPixmap cachedPixmapFromXPM(const char * const *xpm)
-{
- QPixmap result;
- const QString tag = QString::asprintf("xpm:0x%p", static_cast<const void*>(xpm));
- if (!QPixmapCache::find(tag, &result)) {
- result = QPixmap(xpm);
- QPixmapCache::insert(tag, result);
- }
- return result;
-}
-
-void QWasmCompositor::drawItemPixmap(QPainter *painter, const QRect &rect, int alignment,
- const QPixmap &pixmap) const
-{
- qreal scale = pixmap.devicePixelRatio();
- QSize size = pixmap.size() / scale;
- int x = rect.x();
- int y = rect.y();
- int w = size.width();
- int h = size.height();
- if ((alignment & Qt::AlignVCenter) == Qt::AlignVCenter)
- y += rect.size().height()/2 - h/2;
- else if ((alignment & Qt::AlignBottom) == Qt::AlignBottom)
- y += rect.size().height() - h;
- if ((alignment & Qt::AlignRight) == Qt::AlignRight)
- x += rect.size().width() - w;
- else if ((alignment & Qt::AlignHCenter) == Qt::AlignHCenter)
- x += rect.size().width()/2 - w/2;
-
- QRect aligned = QRect(x, y, w, h);
- QRect inter = aligned.intersected(rect);
-
- painter->drawPixmap(inter.x(), inter.y(), pixmap, inter.x() - aligned.x(),
- inter.y() - aligned.y(), inter.width() * scale, inter.height() *scale);
-}
-
-
-void QWasmCompositor::drawTitlebarWindow(QWasmTitleBarOptions tb, QPainter *painter)
-{
- QRect ir;
- if (tb.subControls.testFlag(SC_TitleBarLabel)) {
- QColor left = tb.palette.highlight().color();
- QColor right = tb.palette.base().color();
-
- QBrush fillBrush(left);
- if (left != right) {
- QPoint p1(tb.rect.x(), tb.rect.top() + tb.rect.height()/2);
- QPoint p2(tb.rect.right(), tb.rect.top() + tb.rect.height()/2);
- QLinearGradient lg(p1, p2);
- lg.setColorAt(0, left);
- lg.setColorAt(1, right);
- fillBrush = lg;
- }
-
- painter->fillRect(tb.rect, fillBrush);
- ir = titlebarRect(tb, SC_TitleBarLabel);
- painter->setPen(tb.palette.highlightedText().color());
- painter->drawText(ir.x() + 2, ir.y(), ir.width() - 2, ir.height(),
- Qt::AlignLeft | Qt::AlignVCenter | Qt::TextSingleLine, tb.titleBarOptionsString);
- } // SC_TitleBarLabel
-
- QPixmap pixmap;
-
- if (tb.subControls.testFlag(SC_TitleBarCloseButton)
- && tb.flags & Qt::WindowSystemMenuHint) {
- ir = titlebarRect(tb, SC_TitleBarCloseButton);
- pixmap = cachedPixmapFromXPM(qt_close_xpm).scaled(QSize(10, 10));
- drawItemPixmap(painter, ir, Qt::AlignCenter, pixmap);
- } //SC_TitleBarCloseButton
-
- if (tb.subControls.testFlag(SC_TitleBarMaxButton)
- && tb.flags & Qt::WindowMaximizeButtonHint
- && !(tb.state & Qt::WindowMaximized)) {
- ir = titlebarRect(tb, SC_TitleBarMaxButton);
- pixmap = cachedPixmapFromXPM(qt_maximize_xpm).scaled(QSize(10, 10));
- drawItemPixmap(painter, ir, Qt::AlignCenter, pixmap);
- } //SC_TitleBarMaxButton
-
- bool drawNormalButton = (tb.subControls & SC_TitleBarNormalButton)
- && (((tb.flags & Qt::WindowMinimizeButtonHint)
- && (tb.flags & Qt::WindowMinimized))
- || ((tb.flags & Qt::WindowMaximizeButtonHint)
- && (tb.flags & Qt::WindowMaximized)));
-
- if (drawNormalButton) {
- ir = titlebarRect(tb, SC_TitleBarNormalButton);
- pixmap = cachedPixmapFromXPM(qt_normalizeup_xpm).scaled( QSize(10, 10));
-
- drawItemPixmap(painter, ir, Qt::AlignCenter, pixmap);
- } // SC_TitleBarNormalButton
-
- if (tb.subControls & SC_TitleBarSysMenu && tb.flags & Qt::WindowSystemMenuHint) {
- ir = titlebarRect(tb, SC_TitleBarSysMenu);
- if (!tb.windowIcon.isNull()) {
- tb.windowIcon.paint(painter, ir, Qt::AlignCenter);
- } else {
- pixmap = cachedPixmapFromXPM(qt_menu_xpm).scaled(QSize(10, 10));
- drawItemPixmap(painter, ir, Qt::AlignCenter, pixmap);
- }
- }
-}
-
-void QWasmCompositor::drawShadePanel(QWasmTitleBarOptions options, QPainter *painter)
-{
- int lineWidth = 1;
- QPalette palette = options.palette;
- const QBrush *fill = &options.palette.brush(QPalette::Button);
-
- int x = options.rect.x();
- int y = options.rect.y();
- int w = options.rect.width();
- int h = options.rect.height();
-
- const qreal devicePixelRatio = painter->device()->devicePixelRatio();
- if (!qFuzzyCompare(devicePixelRatio, qreal(1))) {
- const qreal inverseScale = qreal(1) / devicePixelRatio;
- painter->scale(inverseScale, inverseScale);
-
- x = qRound(devicePixelRatio * x);
- y = qRound(devicePixelRatio * y);
- w = qRound(devicePixelRatio * w);
- h = qRound(devicePixelRatio * h);
- lineWidth = qRound(devicePixelRatio * lineWidth);
- }
-
- QColor shade = palette.dark().color();
- QColor light = palette.light().color();
-
- if (fill) {
- if (fill->color() == shade)
- shade = palette.shadow().color();
- if (fill->color() == light)
- light = palette.midlight().color();
- }
- QPen oldPen = painter->pen();
- QList<QLineF> lines;
- lines.reserve(2*lineWidth);
-
- painter->setPen(light);
- int x1, y1, x2, y2;
- int i;
- x1 = x;
- y1 = y2 = y;
- x2 = x + w - 2;
- for (i = 0; i < lineWidth; i++) // top shadow
- lines << QLineF(x1, y1++, x2--, y2++);
-
- x2 = x1;
- y1 = y + h - 2;
- for (i = 0; i < lineWidth; i++) // left shado
- lines << QLineF(x1++, y1, x2++, y2--);
-
- painter->drawLines(lines);
- lines.clear();
- painter->setPen(shade);
- x1 = x;
- y1 = y2 = y+h-1;
- x2 = x+w-1;
- for (i=0; i<lineWidth; i++) { // bottom shadow
- lines << QLineF(x1++, y1--, x2, y2--);
- }
- x1 = x2;
- y1 = y;
- y2 = y + h - lineWidth - 1;
- for (i = 0; i < lineWidth; i++) // right shadow
- lines << QLineF(x1--, y1++, x2--, y2);
-
- painter->drawLines(lines);
- if (fill) // fill with fill color
- painter->fillRect(x+lineWidth, y+lineWidth, w-lineWidth*2, h-lineWidth*2, *fill);
- painter->setPen(oldPen); // restore pen
-
-}
-
-void QWasmCompositor::drawWindow(QOpenGLTextureBlitter *blitter, QWasmScreen *screen, QWasmWindow *window)
-{
- if (window->window()->type() != Qt::Popup && !(window->m_windowState & Qt::WindowFullScreen))
- drawWindowDecorations(blitter, screen, window);
- drawWindowContent(blitter, screen, window);
-}
-
-void QWasmCompositor::frame()
-{
- if (!m_isEnabled || m_windowStack.empty() || !screen())
- return;
-
- QWasmWindow *someWindow = nullptr;
-
- for (QWasmWindow *window : qAsConst(m_windowStack)) {
- if (window->window()->surfaceClass() == QSurface::Window
- && qt_window_private(static_cast<QWindow *>(window->window()))->receivedExpose) {
- someWindow = window;
- break;
- }
- }
-
- if (!someWindow)
- return;
-
- if (m_context.isNull()) {
- m_context.reset(new QOpenGLContext());
- m_context->setFormat(someWindow->window()->requestedFormat());
- m_context->setScreen(screen()->screen());
- m_context->create();
- }
-
- bool ok = m_context->makeCurrent(someWindow->window());
- if (!ok)
- return;
-
- if (!m_blitter->isCreated())
- m_blitter->create();
-
- qreal dpr = screen()->devicePixelRatio();
- glViewport(0, 0, screen()->geometry().width() * dpr, screen()->geometry().height() * dpr);
-
- m_context->functions()->glClearColor(0.2, 0.2, 0.2, 1.0);
- m_context->functions()->glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
-
- m_blitter->bind();
- m_blitter->setRedBlueSwizzle(true);
-
- for (QWasmWindow *window : qAsConst(m_windowStack)) {
- if (m_windowVisibility[window])
- drawWindow(m_blitter.data(), screen(), window);
- }
-
- m_blitter->release();
-
- if (someWindow && someWindow->window()->surfaceType() == QSurface::OpenGLSurface)
- m_context->swapBuffers(someWindow->window());
+ requestUpdateWindow(static_cast<QWasmWindow *>(window->handle()));
}
-void QWasmCompositor::WindowManipulation::resizeWindow(const QPoint& amount)
+void QWasmCompositor::frame(const QList<QWasmWindow *> &windows)
{
- const auto& minShrink = std::get<ResizeState>(m_state->operationSpecific).m_minShrink;
- const auto& maxGrow = std::get<ResizeState>(m_state->operationSpecific).m_maxGrow;
- const auto& resizeMode = std::get<ResizeState>(m_state->operationSpecific).m_resizeMode;
-
- const QPoint cappedGrowVector(
- std::min(maxGrow.x(), std::max(minShrink.x(),
- (resizeMode & Left) ? -amount.x() : (resizeMode & Right) ? amount.x() : 0)),
- std::min(maxGrow.y(), std::max(minShrink.y(),
- (resizeMode & Top) ? -amount.y() : (resizeMode & Bottom) ? amount.y() : 0)));
-
- const auto& initialBounds =
- std::get<ResizeState>(m_state->operationSpecific).m_initialWindowBounds;
- m_state->window->setGeometry(
- initialBounds.adjusted(
- (resizeMode & Left) ? -cappedGrowVector.x() : 0,
- (resizeMode & Top) ? -cappedGrowVector.y() : 0,
- (resizeMode & Right) ? cappedGrowVector.x() : 0,
- (resizeMode & Bottom) ? cappedGrowVector.y() : 0
- ));
-}
-
-void QWasmCompositor::notifyTopWindowChanged(QWasmWindow *window)
-{
- QWindow *modalWindow;
- bool isTargetWindowBlocked = QGuiApplicationPrivate::instance()->isWindowBlocked(window->window(), &modalWindow);
-
- if (isTargetWindowBlocked) {
- modalWindow->requestActivate();
- raise(AsWasmWindow(modalWindow));
+ if (!m_isEnabled || !screen())
return;
- }
- requestUpdate();
+ for (QWasmWindow *window : windows)
+ window->paint();
}
QWasmScreen *QWasmCompositor::screen()
{
return static_cast<QWasmScreen *>(parent());
}
-
-QOpenGLContext *QWasmCompositor::context()
-{
- return m_context.data();
-}
-
-int QWasmCompositor::keyboard_cb(int eventType, const EmscriptenKeyboardEvent *keyEvent, void *userData)
-{
- QWasmCompositor *wasmCompositor = reinterpret_cast<QWasmCompositor *>(userData);
- return static_cast<int>(wasmCompositor->processKeyboard(eventType, keyEvent));
-}
-
-int QWasmCompositor::focus_cb(int eventType, const EmscriptenFocusEvent *focusEvent, void *userData)
-{
- Q_UNUSED(eventType)
- Q_UNUSED(focusEvent)
- Q_UNUSED(userData)
-
- return 0;
-}
-
-int QWasmCompositor::wheel_cb(int eventType, const EmscriptenWheelEvent *wheelEvent, void *userData)
-{
- QWasmCompositor *compositor = (QWasmCompositor *) userData;
- return static_cast<int>(compositor->processWheel(eventType, wheelEvent));
-}
-
-int QWasmCompositor::touchCallback(int eventType, const EmscriptenTouchEvent *touchEvent, void *userData)
-{
- auto compositor = reinterpret_cast<QWasmCompositor*>(userData);
- return static_cast<int>(compositor->handleTouch(eventType, touchEvent));
-}
-
-bool QWasmCompositor::processPointer(const PointerEvent& event)
-{
- if (event.pointerType != PointerType::Mouse)
- return false;
-
- const QPoint targetPointInScreenCoords = screen()->geometry().topLeft() + event.point;
-
- QWindow *const targetWindow = ([this, &targetPointInScreenCoords]() -> QWindow * {
- auto *targetWindow =
- m_windowManipulation.operation() == WindowManipulation::Operation::None ?
- screen()->compositor()->windowAt(targetPointInScreenCoords, 5) : nullptr;
-
- return targetWindow ? targetWindow : m_lastMouseTargetWindow.get();
- })();
- if (targetWindow)
- m_lastMouseTargetWindow = targetWindow;
-
- const QPoint pointInTargetWindowCoords = targetWindow->mapFromGlobal(targetPointInScreenCoords);
- const bool pointerIsWithinTargetWindowBounds = targetWindow->geometry().contains(targetPointInScreenCoords);
- const bool isTargetWindowBlocked = QGuiApplicationPrivate::instance()->isWindowBlocked(targetWindow);
-
- if (m_mouseInCanvas && m_windowUnderMouse != targetWindow && pointerIsWithinTargetWindowBounds) {
- // delayed mouse enter
- enterWindow(targetWindow, pointInTargetWindowCoords, targetPointInScreenCoords);
- m_windowUnderMouse = targetWindow;
- }
-
- QWasmWindow *wasmTargetWindow = AsWasmWindow(targetWindow);
- Qt::WindowStates windowState = targetWindow->windowState();
- const bool isTargetWindowResizable = !windowState.testFlag(Qt::WindowMaximized) && !windowState.testFlag(Qt::WindowFullScreen);
-
- switch (event.type) {
- case EventType::PointerDown:
- {
- if (targetWindow)
- targetWindow->requestActivate();
-
- m_pressedWindow = targetWindow;
-
- m_windowManipulation.onPointerDown(event, targetWindow);
-
- wasmTargetWindow->injectMousePressed(pointInTargetWindowCoords, targetPointInScreenCoords, event.mouseButton, event.modifiers);
- break;
- }
- case EventType::PointerUp:
- {
- m_windowManipulation.onPointerUp(event);
-
- if (m_pressedWindow) {
- // Always deliver the released event to the same window that was pressed
- AsWasmWindow(m_pressedWindow)->injectMouseReleased(pointInTargetWindowCoords, targetPointInScreenCoords, event.mouseButton, event.modifiers);
- if (event.mouseButton == Qt::MouseButton::LeftButton)
- m_pressedWindow = nullptr;
- } else {
- wasmTargetWindow->injectMouseReleased(pointInTargetWindowCoords, targetPointInScreenCoords, event.mouseButton, event.modifiers);
- }
- break;
- }
- case EventType::PointerMove:
- {
- if (wasmTargetWindow && event.mouseButtons.testFlag(Qt::NoButton)) {
- const bool isOnResizeRegion = wasmTargetWindow->isPointOnResizeRegion(targetPointInScreenCoords);
-
- if (isTargetWindowResizable && isOnResizeRegion && !isTargetWindowBlocked) {
- const QCursor resizingCursor = QWasmEventTranslator::cursorForMode(
- wasmTargetWindow->resizeModeAtPoint(targetPointInScreenCoords));
-
- if (resizingCursor != targetWindow->cursor()) {
- m_isResizeCursorDisplayed = true;
- QWasmCursor::setOverrideWasmCursor(resizingCursor, targetWindow->screen());
- }
- } else if (m_isResizeCursorDisplayed) { // off resizing area
- m_isResizeCursorDisplayed = false;
- QWasmCursor::clearOverrideWasmCursor(targetWindow->screen());
- }
- }
-
- m_windowManipulation.onPointerMove(event);
- break;
- }
- case EventType::PointerEnter:
- processMouseEnter(nullptr);
- break;
- case EventType::PointerLeave:
- processMouseLeave();
- break;
- default:
- break;
- };
-
- if (!pointerIsWithinTargetWindowBounds && event.mouseButtons.testFlag(Qt::NoButton)) {
- leaveWindow(m_lastMouseTargetWindow);
- }
-
- const bool eventAccepted = deliverEventToTarget(event, targetWindow);
- if (!eventAccepted && event.type == EventType::PointerDown)
- QGuiApplicationPrivate::instance()->closeAllPopups();
- return eventAccepted;
-}
-
-bool QWasmCompositor::deliverEventToTarget(const PointerEvent &event, QWindow *eventTarget)
-{
- const QPoint pointInScreenCoords = screen()->geometry().topLeft() + event.point;
- const QPoint targetPointClippedToScreen(
- std::max(screen()->geometry().left(),
- std::min(screen()->geometry().right(), pointInScreenCoords.x())),
- std::max(screen()->geometry().top(),
- std::min(screen()->geometry().bottom(), pointInScreenCoords.y())));
-
- bool deliveringToPreviouslyClickedWindow = false;
-
- if (!eventTarget) {
- if (event.type != EventType::PointerUp || !m_lastMouseTargetWindow)
- return false;
-
- eventTarget = m_lastMouseTargetWindow;
- m_lastMouseTargetWindow = nullptr;
- deliveringToPreviouslyClickedWindow = true;
- }
-
- WindowArea windowArea = WindowArea::Client;
- if (!eventTarget->geometry().contains(targetPointClippedToScreen)
- && !deliveringToPreviouslyClickedWindow) {
- if (!eventTarget->frameGeometry().contains(targetPointClippedToScreen))
- return false;
- windowArea = WindowArea::NonClient;
- }
-
- const QEvent::Type eventType =
- MouseEvent::mouseEventTypeFromEventType(event.type, windowArea);
-
- return eventType != QEvent::None &&
- QWindowSystemInterface::handleMouseEvent<QWindowSystemInterface::SynchronousDelivery>(
- eventTarget, QWasmIntegration::getTimestamp(),
- eventTarget->mapFromGlobal(targetPointClippedToScreen),
- targetPointClippedToScreen, event.mouseButtons, event.mouseButton,
- eventType, event.modifiers);
-}
-
-QWasmCompositor::WindowManipulation::WindowManipulation(QWasmScreen *screen)
- : m_screen(screen)
-{
- Q_ASSERT(!!screen);
-}
-
-QWasmCompositor::WindowManipulation::Operation QWasmCompositor::WindowManipulation::operation() const
-{
- if (!m_state)
- return Operation::None;
-
- return std::holds_alternative<MoveState>(m_state->operationSpecific)
- ? Operation::Move : Operation::Resize;
-}
-
-void QWasmCompositor::WindowManipulation::onPointerDown(
- const PointerEvent& event, QWindow* windowAtPoint)
-{
- // Only one operation at a time.
- if (operation() != Operation::None)
- return;
-
- if (event.mouseButton != Qt::MouseButton::LeftButton)
- return;
-
- const bool isTargetWindowResizable =
- !windowAtPoint->windowStates().testFlag(Qt::WindowMaximized) &&
- !windowAtPoint->windowStates().testFlag(Qt::WindowFullScreen);
- if (!isTargetWindowResizable)
- return;
-
- const bool isTargetWindowBlocked =
- QGuiApplicationPrivate::instance()->isWindowBlocked(windowAtPoint);
- if (isTargetWindowBlocked)
- return;
-
- const auto pointInScreenCoords = m_screen->geometry().topLeft() + event.point;
-
- std::unique_ptr<std::variant<ResizeState, MoveState>> operationSpecific;
- if (AsWasmWindow(windowAtPoint)->isPointOnTitle(pointInScreenCoords)) {
- operationSpecific = std::make_unique<std::variant<ResizeState, MoveState>>(MoveState {
- .m_lastPointInScreenCoords = pointInScreenCoords
- });
- } else if (AsWasmWindow(windowAtPoint)->isPointOnResizeRegion(pointInScreenCoords)) {
- operationSpecific = std::make_unique<std::variant<ResizeState, MoveState>>(ResizeState {
- .m_resizeMode = AsWasmWindow(windowAtPoint)->resizeModeAtPoint(pointInScreenCoords),
- .m_originInScreenCoords = pointInScreenCoords,
- .m_initialWindowBounds = windowAtPoint->geometry(),
- .m_minShrink = QPoint(windowAtPoint->minimumWidth() - windowAtPoint->geometry().width(),
- windowAtPoint->minimumHeight() - windowAtPoint->geometry().height()),
- .m_maxGrow = QPoint(
- windowAtPoint->maximumWidth() - windowAtPoint->geometry().width(),
- windowAtPoint->maximumHeight() - windowAtPoint->geometry().height()),
- });
- } else {
- return;
- }
-
- m_state.reset(new OperationState{
- .pointerId = event.pointerId,
- .window = windowAtPoint,
- .operationSpecific = std::move(*operationSpecific),
- });
-
- m_screen->canvas().call<void>("setPointerCapture", event.pointerId);
-}
-
-void QWasmCompositor::WindowManipulation::onPointerMove(
- const PointerEvent& event)
-{
- if (operation() == Operation::None || event.pointerId != m_state->pointerId)
- return;
-
- switch (operation()) {
- case Operation::Move: {
- const QPoint targetPointClippedToScreen =
- m_screen->translateAndClipGlobalPoint(event.point);
- const QPoint difference = targetPointClippedToScreen -
- std::get<MoveState>(m_state->operationSpecific).m_lastPointInScreenCoords;
-
- std::get<MoveState>(m_state->operationSpecific).m_lastPointInScreenCoords = targetPointClippedToScreen;
-
- m_state->window->setPosition(m_state->window->position() + difference);
- break;
- }
- case Operation::Resize: {
- const auto pointInScreenCoords = m_screen->geometry().topLeft() + event.point;
- resizeWindow(pointInScreenCoords -
- std::get<ResizeState>(m_state->operationSpecific).m_originInScreenCoords);
- break;
- }
- case Operation::None:
- Q_ASSERT(0);
- break;
- }
-}
-
-void QWasmCompositor::WindowManipulation::onPointerUp(const PointerEvent& event)
-{
- if (operation() == Operation::None || event.mouseButtons != 0 || event.pointerId != m_state->pointerId)
- return;
-
- m_state.reset();
- m_screen->canvas().call<void>("releasePointerCapture", event.pointerId);
-}
-
-bool QWasmCompositor::processKeyboard(int eventType, const EmscriptenKeyboardEvent *emKeyEvent)
-{
- Q_ASSERT(eventType == EMSCRIPTEN_EVENT_KEYDOWN || eventType == EMSCRIPTEN_EVENT_KEYUP);
-
- auto translatedEvent = m_eventTranslator->translateKeyEvent(eventType, emKeyEvent);
-
- const QFlags<Qt::KeyboardModifier> modifiers = KeyboardModifier::getForEvent(*emKeyEvent);
-
- // Clipboard path: cut/copy/paste are handled by clipboard event or direct clipboard access.
- if (translatedEvent.type == QEvent::KeyPress && modifiers.testFlag(Qt::ControlModifier)
- && (translatedEvent.key == Qt::Key_X || translatedEvent.key == Qt::Key_V
- || translatedEvent.key == Qt::Key_C)) {
- QWasmIntegration::get()->getWasmClipboard()->isPaste = translatedEvent.key == Qt::Key_V;
- return false; // continue on to event
- }
-
- if (translatedEvent.text.isEmpty())
- translatedEvent.text = QString(emKeyEvent->key);
- if (translatedEvent.text.size() > 1)
- translatedEvent.text.clear();
- return QWindowSystemInterface::handleKeyEvent<QWindowSystemInterface::SynchronousDelivery>(
- 0, translatedEvent.type, translatedEvent.key, modifiers, translatedEvent.text);
-}
-
-bool QWasmCompositor::processWheel(int eventType, const EmscriptenWheelEvent *wheelEvent)
-{
- Q_UNUSED(eventType);
-
- const EmscriptenMouseEvent* mouseEvent = &wheelEvent->mouse;
-
- int scrollFactor = 0;
- switch (wheelEvent->deltaMode) {
- case DOM_DELTA_PIXEL:
- scrollFactor = 1;
- break;
- case DOM_DELTA_LINE:
- scrollFactor = 12;
- break;
- case DOM_DELTA_PAGE:
- scrollFactor = 20;
- break;
- };
-
- scrollFactor = -scrollFactor; // Web scroll deltas are inverted from Qt deltas.
-
- Qt::KeyboardModifiers modifiers = KeyboardModifier::getForEvent(*mouseEvent);
- QPoint targetPointInCanvasCoords(mouseEvent->targetX, mouseEvent->targetY);
- QPoint targetPointInScreenCoords = screen()->geometry().topLeft() + targetPointInCanvasCoords;
-
- QWindow *targetWindow = screen()->compositor()->windowAt(targetPointInScreenCoords, 5);
- if (!targetWindow)
- return 0;
- QPoint pointInTargetWindowCoords = targetWindow->mapFromGlobal(targetPointInScreenCoords);
-
- QPoint pixelDelta;
-
- if (wheelEvent->deltaY != 0) pixelDelta.setY(wheelEvent->deltaY * scrollFactor);
- if (wheelEvent->deltaX != 0) pixelDelta.setX(wheelEvent->deltaX * scrollFactor);
-
- QPoint angleDelta = pixelDelta; // FIXME: convert from pixels?
-
- bool accepted = QWindowSystemInterface::handleWheelEvent(
- targetWindow, QWasmIntegration::getTimestamp(), pointInTargetWindowCoords,
- targetPointInScreenCoords, pixelDelta, angleDelta, modifiers,
- Qt::NoScrollPhase, Qt::MouseEventNotSynthesized,
- g_scrollingInvertedFromDevice);
- return accepted;
-}
-
-int QWasmCompositor::handleTouch(int eventType, const EmscriptenTouchEvent *touchEvent)
-{
- QList<QWindowSystemInterface::TouchPoint> touchPointList;
- touchPointList.reserve(touchEvent->numTouches);
- QWindow *targetWindow;
-
- for (int i = 0; i < touchEvent->numTouches; i++) {
-
- const EmscriptenTouchPoint *touches = &touchEvent->touches[i];
-
- QPoint targetPointInCanvasCoords(touches->targetX, touches->targetY);
- QPoint targetPointInScreenCoords = screen()->geometry().topLeft() + targetPointInCanvasCoords;
-
- targetWindow = screen()->compositor()->windowAt(targetPointInScreenCoords, 5);
- if (targetWindow == nullptr)
- continue;
-
- QWindowSystemInterface::TouchPoint touchPoint;
-
- touchPoint.area = QRect(0, 0, 8, 8);
- touchPoint.id = touches->identifier;
- touchPoint.pressure = 1.0;
-
- touchPoint.area.moveCenter(targetPointInScreenCoords);
-
- const auto tp = m_pressedTouchIds.constFind(touchPoint.id);
- if (tp != m_pressedTouchIds.constEnd())
- touchPoint.normalPosition = tp.value();
-
- QPointF pointInTargetWindowCoords = QPointF(targetWindow->mapFromGlobal(targetPointInScreenCoords));
- QPointF normalPosition(pointInTargetWindowCoords.x() / targetWindow->width(),
- pointInTargetWindowCoords.y() / targetWindow->height());
-
- const bool stationaryTouchPoint = (normalPosition == touchPoint.normalPosition);
- touchPoint.normalPosition = normalPosition;
-
- switch (eventType) {
- case EMSCRIPTEN_EVENT_TOUCHSTART:
- if (tp != m_pressedTouchIds.constEnd()) {
- touchPoint.state = (stationaryTouchPoint
- ? QEventPoint::State::Stationary
- : QEventPoint::State::Updated);
- } else {
- touchPoint.state = QEventPoint::State::Pressed;
- }
- m_pressedTouchIds.insert(touchPoint.id, touchPoint.normalPosition);
-
- break;
- case EMSCRIPTEN_EVENT_TOUCHEND:
- touchPoint.state = QEventPoint::State::Released;
- m_pressedTouchIds.remove(touchPoint.id);
- break;
- case EMSCRIPTEN_EVENT_TOUCHMOVE:
- touchPoint.state = (stationaryTouchPoint
- ? QEventPoint::State::Stationary
- : QEventPoint::State::Updated);
-
- m_pressedTouchIds.insert(touchPoint.id, touchPoint.normalPosition);
- break;
- default:
- break;
- }
-
- touchPointList.append(touchPoint);
- }
-
- QFlags<Qt::KeyboardModifier> keyModifier = KeyboardModifier::getForEvent(*touchEvent);
-
- bool accepted = false;
-
- if (eventType == EMSCRIPTEN_EVENT_TOUCHCANCEL)
- accepted = QWindowSystemInterface::handleTouchCancelEvent(targetWindow, QWasmIntegration::getTimestamp(), m_touchDevice.get(), keyModifier);
- else
- accepted = QWindowSystemInterface::handleTouchEvent<QWindowSystemInterface::SynchronousDelivery>(
- targetWindow, QWasmIntegration::getTimestamp(), m_touchDevice.get(), touchPointList, keyModifier);
-
- return static_cast<int>(accepted);
-}
-
-void QWasmCompositor::leaveWindow(QWindow *window)
-{
- m_windowUnderMouse = nullptr;
- QWindowSystemInterface::handleLeaveEvent<QWindowSystemInterface::SynchronousDelivery>(window);
-}
-
-void QWasmCompositor::enterWindow(QWindow *window, const QPoint &pointInTargetWindowCoords, const QPoint &targetPointInScreenCoords)
-{
- QWindowSystemInterface::handleEnterEvent<QWindowSystemInterface::SynchronousDelivery>(window, pointInTargetWindowCoords, targetPointInScreenCoords);
-}
-
-bool QWasmCompositor::processMouseEnter(const EmscriptenMouseEvent *mouseEvent)
-{
- Q_UNUSED(mouseEvent)
- // mouse has entered the canvas area
- m_mouseInCanvas = true;
- return true;
-}
-
-bool QWasmCompositor::processMouseLeave()
-{
- m_mouseInCanvas = false;
- return true;
-}
diff --git a/src/plugins/platforms/wasm/qwasmcompositor.h b/src/plugins/platforms/wasm/qwasmcompositor.h
index c883fc310d..4953d65233 100644
--- a/src/plugins/platforms/wasm/qwasmcompositor.h
+++ b/src/plugins/platforms/wasm/qwasmcompositor.h
@@ -4,239 +4,56 @@
#ifndef QWASMCOMPOSITOR_H
#define QWASMCOMPOSITOR_H
-#include <QtGui/qregion.h>
-#include <qpa/qplatformwindow.h>
-
-#include <QtOpenGL/qopengltextureblitter.h>
-#include <QtGui/qpalette.h>
-#include <QtGui/qpainter.h>
-#include <QtGui/qinputdevice.h>
-#include <QtCore/private/qstdweb_p.h>
+#include "qwasmwindowstack.h"
-#include <QPointer>
-#include <QPointingDevice>
+#include <qpa/qplatformwindow.h>
-#include <emscripten/html5.h>
-#include <emscripten/emscripten.h>
-#include <emscripten/bind.h>
+#include <QMap>
QT_BEGIN_NAMESPACE
-struct PointerEvent;
class QWasmWindow;
class QWasmScreen;
-class QOpenGLContext;
-class QOpenGLTexture;
-class QWasmEventTranslator;
-class QWasmCompositor : public QObject
+enum class QWasmWindowTreeNodeChangeType;
+
+class QWasmCompositor final : public QObject
{
Q_OBJECT
public:
QWasmCompositor(QWasmScreen *screen);
- ~QWasmCompositor();
- void initEventHandlers();
- void deregisterEventHandlers();
- void destroy();
-
- enum QWasmSubControl {
- SC_None = 0x00000000,
- SC_TitleBarSysMenu = 0x00000001,
- SC_TitleBarMinButton = 0x00000002,
- SC_TitleBarMaxButton = 0x00000004,
- SC_TitleBarCloseButton = 0x00000008,
- SC_TitleBarNormalButton = 0x00000010,
- SC_TitleBarLabel = 0x00000100
- };
- Q_DECLARE_FLAGS(SubControls, QWasmSubControl)
-
- enum QWasmStateFlag {
- State_None = 0x00000000,
- State_Enabled = 0x00000001,
- State_Raised = 0x00000002,
- State_Sunken = 0x00000004
- };
- Q_DECLARE_FLAGS(StateFlags, QWasmStateFlag)
-
- enum ResizeDimension {
- Left = 1,
- Right = 1 << 1,
- Top = 1 << 2,
- Bottom = 1 << 3
- };
-
- enum ResizeMode {
- ResizeNone,
- ResizeTopLeft = Top | Left,
- ResizeTop = Top,
- ResizeTopRight = Top | Right,
- ResizeRight = Right,
- ResizeBottomRight = Bottom | Right,
- ResizeBottom = Bottom,
- ResizeBottomLeft = Bottom | Left,
- ResizeLeft = Left
- };
-
- struct QWasmTitleBarOptions {
- QRect rect;
- Qt::WindowFlags flags;
- int state;
- QPalette palette;
- QString titleBarOptionsString;
- QWasmCompositor::SubControls subControls;
- QIcon windowIcon;
- };
-
- struct QWasmFrameOptions {
- QRect rect;
- int lineWidth;
- QPalette palette;
- };
-
- void setEnabled(bool enabled);
-
- void addWindow(QWasmWindow *window);
- void removeWindow(QWasmWindow *window);
+ ~QWasmCompositor() final;
void setVisible(QWasmWindow *window, bool visible);
- void raise(QWasmWindow *window);
- void lower(QWasmWindow *window);
- int windowCount() const;
-
- QWindow *windowAt(QPoint globalPoint, int padding = 0) const;
- QWindow *keyWindow() const;
-
- static QWasmTitleBarOptions makeTitleBarOptions(const QWasmWindow *window);
- static QRect titlebarRect(QWasmTitleBarOptions tb, QWasmCompositor::SubControls subcontrol);
+ void onScreenDeleting();
QWasmScreen *screen();
- QOpenGLContext *context();
+ void setEnabled(bool enabled);
+
+ static bool releaseRequestUpdateHold();
+ void requestUpdate();
enum UpdateRequestDeliveryType { ExposeEventDelivery, UpdateRequestDelivery };
- void requestUpdateAllWindows();
void requestUpdateWindow(QWasmWindow *window, UpdateRequestDeliveryType updateType = ExposeEventDelivery);
- void requestUpdate();
- void deliverUpdateRequests();
- void deliverUpdateRequest(QWasmWindow *window, UpdateRequestDeliveryType updateType);
- void handleBackingStoreFlush();
- bool processKeyboard(int eventType, const EmscriptenKeyboardEvent *keyEvent);
- bool processWheel(int eventType, const EmscriptenWheelEvent *wheelEvent);
- int handleTouch(int eventType, const EmscriptenTouchEvent *touchEvent);
-
- bool processMouseEnter(const EmscriptenMouseEvent *mouseEvent);
- bool processMouseLeave();
- void enterWindow(QWindow* window, const QPoint &localPoint, const QPoint &globalPoint);
- void leaveWindow(QWindow* window);
-private slots:
- void frame();
+ void handleBackingStoreFlush(QWindow *window);
+ void onWindowTreeChanged(QWasmWindowTreeNodeChangeType changeType, QWasmWindow *window);
private:
- class WindowManipulation {
- public:
- enum class Operation {
- None,
- Move,
- Resize,
- };
-
- WindowManipulation(QWasmScreen* screen);
-
- void onPointerDown(const PointerEvent& event, QWindow* windowAtPoint);
- void onPointerMove(const PointerEvent& event);
- void onPointerUp(const PointerEvent& event);
-
- Operation operation() const;
-
- private:
- struct ResizeState {
- ResizeMode m_resizeMode;
- QPoint m_originInScreenCoords;
- QRect m_initialWindowBounds;
- const QPoint m_minShrink;
- const QPoint m_maxGrow;
- };
- struct MoveState {
- QPoint m_lastPointInScreenCoords;
- };
- struct OperationState
- {
- int pointerId;
- QPointer<QWindow> window;
- std::variant<ResizeState, MoveState> operationSpecific;
- };
-
- void resizeWindow(const QPoint& amount);
-
- QWasmScreen *m_screen;
-
- std::unique_ptr<OperationState> m_state;
- };
-
- void notifyTopWindowChanged(QWasmWindow *window);
- void drawWindow(QOpenGLTextureBlitter *blitter, QWasmScreen *screen, QWasmWindow *window);
- void drawWindowContent(QOpenGLTextureBlitter *blitter, QWasmScreen *screen, QWasmWindow *window);
- void blit(QOpenGLTextureBlitter *blitter, QWasmScreen *screen, const QOpenGLTexture *texture, QRect targetGeometry);
-
- void drawWindowDecorations(QOpenGLTextureBlitter *blitter, QWasmScreen *screen, QWasmWindow *window);
-
- static QPalette makeWindowPalette();
-
- void drawFrameWindow(QWasmFrameOptions options, QPainter *painter);
- void drawTitlebarWindow(QWasmTitleBarOptions options, QPainter *painter);
- void drawShadePanel(QWasmTitleBarOptions options, QPainter *painter);
- void drawItemPixmap(QPainter *painter, const QRect &rect,
- int alignment, const QPixmap &pixmap) const;
-
- static int keyboard_cb(int eventType, const EmscriptenKeyboardEvent *keyEvent, void *userData);
- static int focus_cb(int eventType, const EmscriptenFocusEvent *focusEvent, void *userData);
- static int wheel_cb(int eventType, const EmscriptenWheelEvent *wheelEvent, void *userData);
-
- bool processPointer(const PointerEvent& event);
- bool deliverEventToTarget(const PointerEvent& event, QWindow *eventTarget);
-
- static int touchCallback(int eventType, const EmscriptenTouchEvent *ev, void *userData);
-
- WindowManipulation m_windowManipulation;
-
- QScopedPointer<QOpenGLContext> m_context;
- QScopedPointer<QOpenGLTextureBlitter> m_blitter;
-
- QHash<const QWasmWindow *, bool> m_windowVisibility;
- QList<QWasmWindow *> m_windowStack;
- QRegion m_globalDamage; // damage caused by expose, window close, etc.
- bool m_needComposit = false;
- bool m_inFlush = false;
+ void frame(const QList<QWasmWindow *> &windows);
+
+ void deregisterEventHandlers();
+
+ void deliverUpdateRequests();
+ void deliverUpdateRequest(QWasmWindow *window, UpdateRequestDeliveryType updateType);
+
bool m_isEnabled = true;
- QSize m_targetSize;
- qreal m_targetDevicePixelRatio = 1;
QMap<QWasmWindow *, UpdateRequestDeliveryType> m_requestUpdateWindows;
- bool m_requestUpdateAllWindows = false;
int m_requestAnimationFrameId = -1;
bool m_inDeliverUpdateRequest = false;
-
- QPointer<QWindow> m_pressedWindow;
- QPointer<QWindow> m_lastMouseTargetWindow;
-
- std::unique_ptr<qstdweb::EventCallback> m_pointerDownCallback;
- std::unique_ptr<qstdweb::EventCallback> m_pointerMoveCallback;
- std::unique_ptr<qstdweb::EventCallback> m_pointerUpCallback;
- std::unique_ptr<qstdweb::EventCallback> m_pointerLeaveCallback;
- std::unique_ptr<qstdweb::EventCallback> m_pointerEnterCallback;
-
- std::unique_ptr<QPointingDevice> m_touchDevice;
-
- QMap <int, QPointF> m_pressedTouchIds;
-
- bool m_isResizeCursorDisplayed = false;
-
- std::unique_ptr<QWasmEventTranslator> m_eventTranslator;
-
- bool m_mouseInCanvas = false;
- QPointer<QWindow> m_windowUnderMouse;
+ static bool m_requestUpdateHoldEnabled;
};
-Q_DECLARE_OPERATORS_FOR_FLAGS(QWasmCompositor::SubControls)
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/wasm/qwasmcssstyle.cpp b/src/plugins/platforms/wasm/qwasmcssstyle.cpp
new file mode 100644
index 0000000000..e0e1a99f48
--- /dev/null
+++ b/src/plugins/platforms/wasm/qwasmcssstyle.cpp
@@ -0,0 +1,248 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "qwasmcssstyle.h"
+
+#include "qwasmbase64iconstore.h"
+
+#include <QtCore/qstring.h>
+#include <QtCore/qfile.h>
+
+QT_BEGIN_NAMESPACE
+
+namespace {
+const char *Style = R"css(
+.qt-screen {
+ --border-width: 4px;
+ --resize-outline-width: 8px;
+ --resize-outline-half-width: var(--resize-outline-width) / 2;
+
+ position: relative;
+ border: none;
+ caret-color: transparent;
+ cursor: default;
+ width: 100%;
+ height: 100%;
+ overflow: hidden;
+}
+
+.qt-screen div {
+ touch-action: none;
+}
+
+.qt-window {
+ position: absolute;
+ background-color: lightgray;
+}
+
+.qt-window-contents {
+ overflow: hidden;
+ position: relative;
+}
+
+.qt-window.transparent-for-input {
+ pointer-events: none;
+}
+
+.qt-window.has-shadow {
+ box-shadow: rgb(0 0 0 / 20%) 0px 10px 16px 0px, rgb(0 0 0 / 19%) 0px 6px 20px 0px;
+}
+
+.qt-window.has-border {
+ border: var(--border-width) solid lightgray;
+ caret-color: transparent;
+}
+
+.qt-window.frameless {
+ background-color: transparent;
+}
+
+.resize-outline {
+ position: absolute;
+ display: none;
+}
+
+.qt-window.no-resize > .resize-outline { display: none; }
+
+.qt-window.has-border:not(.maximized):not(.no-resize) .resize-outline {
+ display: block;
+}
+
+.resize-outline.nw {
+ left: calc(-1 * var(--resize-outline-half-width) - var(--border-width));
+ top: calc(-1 * var(--resize-outline-half-width) - var(--border-width));
+ width: var(--resize-outline-width);
+ height: var(--resize-outline-width);
+ cursor: nwse-resize;
+}
+
+.resize-outline.n {
+ left: var(--resize-outline-half-width);
+ top: calc(-1 * var(--resize-outline-half-width) - var(--border-width));
+ height: var(--resize-outline-width);
+ width: calc(100% + 2 * var(--border-width) - var(--resize-outline-width));
+ cursor: ns-resize;
+}
+
+.resize-outline.ne {
+ left: calc(100% + var(--border-width) - var(--resize-outline-half-width));
+ top: calc(-1 * var(--resize-outline-half-width) - var(--border-width));
+ width: var(--resize-outline-width);
+ height: var(--resize-outline-width);
+ cursor: nesw-resize;
+}
+
+.resize-outline.w {
+ left: calc(-1 * var(--resize-outline-half-width) - var(--border-width));
+ top: 0;
+ height: calc(100% + 2 * var(--border-width) - var(--resize-outline-width));
+ width: var(--resize-outline-width);
+ cursor: ew-resize;
+}
+
+.resize-outline.e {
+ left: calc(100% + var(--border-width) - var(--resize-outline-half-width));
+ top: 0;
+ height: calc(100% + 2 * var(--border-width) - var(--resize-outline-width));
+ width: var(--resize-outline-width);
+ cursor: ew-resize;
+}
+
+.resize-outline.sw {
+ left: calc(-1 * var(--resize-outline-half-width) - var(--border-width));
+ top: calc(100% + var(--border-width) - var(--resize-outline-half-width));
+ width: var(--resize-outline-width);
+ height: var(--resize-outline-width);
+ cursor: nesw-resize;
+}
+
+.resize-outline.s {
+ left: var(--resize-outline-half-width);
+ top: calc(100% + var(--border-width) - var(--resize-outline-half-width));
+ height: var(--resize-outline-width);
+ width: calc(100% + 2 * var(--border-width) - var(--resize-outline-width));
+ cursor: ns-resize;
+}
+
+.resize-outline.se {
+ left: calc(100% + var(--border-width) - var(--resize-outline-half-width));
+ top: calc(100% + var(--border-width) - var(--resize-outline-half-width));
+ width: var(--resize-outline-width);
+ height: var(--resize-outline-width);
+ cursor: nwse-resize;
+}
+
+.title-bar {
+ display: none;
+ align-items: center;
+ overflow: hidden;
+ height: 18px;
+ padding-bottom: 4px;
+}
+
+.qt-window.has-border > .title-bar {
+ display: flex;
+}
+
+.title-bar .window-name {
+ display: none;
+ font-family: 'Lucida Grande';
+ white-space: nowrap;
+ user-select: none;
+ overflow: hidden;
+}
+
+
+.qt-window.has-title .title-bar .window-name {
+ display: block;
+}
+
+.title-bar .spacer {
+ flex-grow: 1
+}
+
+.qt-window.inactive .title-bar {
+ opacity: 0.35;
+}
+
+.qt-window-canvas-container {
+ display: flex;
+ pointer-events: none;
+}
+
+.title-bar div {
+ pointer-events: none;
+}
+
+.qt-window-a11y-container {
+ position: absolute;
+ z-index: -1;
+}
+
+.title-bar .image-button {
+ width: 18px;
+ height: 18px;
+ display: flex;
+ justify-content: center;
+ user-select: none;
+ align-items: center;
+}
+
+.title-bar .image-button img {
+ width: 10px;
+ height: 10px;
+ user-select: none;
+ pointer-events: none;
+ -webkit-user-drag: none;
+ background-size: 10px 10px;
+}
+
+.title-bar .action-button {
+ pointer-events: all;
+}
+
+.qt-window.blocked div {
+ pointer-events: none;
+}
+
+.title-bar .action-button img {
+ transition: filter 0.08s ease-out;
+}
+
+.title-bar .action-button:hover img {
+ filter: invert(0.45);
+}
+
+.title-bar .action-button:active img {
+ filter: invert(0.6);
+}
+
+/* This will clip the content within 50% frame in 1x1 pixel area, preventing it
+ from being rendered on the page, but it should still be read by modern
+ screen readers */
+.hidden-visually-read-by-screen-reader {
+ visibility: visible;
+ clip: rect(1px, 1px, 1px, 1px);
+ clip-path: inset(50%);
+ height: 1px;
+ width: 1px;
+ margin: -1px;
+ overflow: hidden;
+ padding: 0;
+ position: absolute;
+}
+
+)css";
+
+} // namespace
+
+emscripten::val QWasmCSSStyle::createStyleElement(emscripten::val parent)
+{
+ auto document = parent["ownerDocument"];
+ auto screenStyle = document.call<emscripten::val>("createElement", emscripten::val("style"));
+
+ screenStyle.set("textContent", std::string(Style));
+ return screenStyle;
+}
+
+QT_END_NAMESPACE
diff --git a/src/plugins/platforms/wasm/qwasmcssstyle.h b/src/plugins/platforms/wasm/qwasmcssstyle.h
new file mode 100644
index 0000000000..fc4cc2d54c
--- /dev/null
+++ b/src/plugins/platforms/wasm/qwasmcssstyle.h
@@ -0,0 +1,18 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef QWASMCSSSTYLE_H
+#define QWASMCSSSTYLE_H
+
+#include <QtCore/qglobal.h>
+
+#include <emscripten/val.h>
+
+QT_BEGIN_NAMESPACE
+
+namespace QWasmCSSStyle {
+emscripten::val createStyleElement(emscripten::val parent);
+}
+
+QT_END_NAMESPACE
+#endif // QWASMINLINESTYLEREGISTRY_H
diff --git a/src/plugins/platforms/wasm/qwasmcursor.cpp b/src/plugins/platforms/wasm/qwasmcursor.cpp
index f954e005ce..c258befa77 100644
--- a/src/plugins/platforms/wasm/qwasmcursor.cpp
+++ b/src/plugins/platforms/wasm/qwasmcursor.cpp
@@ -3,9 +3,11 @@
#include "qwasmcursor.h"
#include "qwasmscreen.h"
-#include "qwasmstring.h"
+#include "qwasmwindow.h"
+#include <QtCore/qbuffer.h>
#include <QtCore/qdebug.h>
+#include <QtCore/qstring.h>
#include <QtGui/qwindow.h>
#include <emscripten/emscripten.h>
@@ -14,125 +16,86 @@
QT_BEGIN_NAMESPACE
using namespace emscripten;
-void QWasmCursor::changeCursor(QCursor *windowCursor, QWindow *window)
-{
- if (!window)
- return;
- QScreen *screen = window->screen();
- if (!screen)
- return;
-
- if (windowCursor) {
-
- // Bitmap and custom cursors are not implemented (will fall back to "auto")
- if (windowCursor->shape() == Qt::BitmapCursor || windowCursor->shape() >= Qt::CustomCursor)
- qWarning() << "QWasmCursor: bitmap and custom cursors are not supported";
-
-
- htmlCursorName = cursorShapeToHtml(windowCursor->shape());
- }
- if (htmlCursorName.isEmpty())
- htmlCursorName = "default";
-
- setWasmCursor(screen, htmlCursorName);
-}
-
-QByteArray QWasmCursor::cursorShapeToHtml(Qt::CursorShape shape)
+namespace {
+QByteArray cursorToCss(const QCursor *cursor)
{
- QByteArray cursorName;
-
+ auto shape = cursor->shape();
switch (shape) {
case Qt::ArrowCursor:
- cursorName = "default";
- break;
+ return "default";
case Qt::UpArrowCursor:
- cursorName = "n-resize";
- break;
+ return "n-resize";
case Qt::CrossCursor:
- cursorName = "crosshair";
- break;
+ return "crosshair";
case Qt::WaitCursor:
- cursorName = "wait";
- break;
+ return "wait";
case Qt::IBeamCursor:
- cursorName = "text";
- break;
+ return "text";
case Qt::SizeVerCursor:
- cursorName = "ns-resize";
- break;
+ return "ns-resize";
case Qt::SizeHorCursor:
- cursorName = "ew-resize";
- break;
+ return "ew-resize";
case Qt::SizeBDiagCursor:
- cursorName = "nesw-resize";
- break;
+ return "nesw-resize";
case Qt::SizeFDiagCursor:
- cursorName = "nwse-resize";
- break;
+ return "nwse-resize";
case Qt::SizeAllCursor:
- cursorName = "move";
- break;
+ return "move";
case Qt::BlankCursor:
- cursorName = "none";
- break;
+ return "none";
case Qt::SplitVCursor:
- cursorName = "row-resize";
- break;
+ return "row-resize";
case Qt::SplitHCursor:
- cursorName = "col-resize";
- break;
+ return "col-resize";
case Qt::PointingHandCursor:
- cursorName = "pointer";
- break;
+ return "pointer";
case Qt::ForbiddenCursor:
- cursorName = "not-allowed";
- break;
+ return "not-allowed";
case Qt::WhatsThisCursor:
- cursorName = "help";
- break;
+ return "help";
case Qt::BusyCursor:
- cursorName = "progress";
- break;
+ return "progress";
case Qt::OpenHandCursor:
- cursorName = "grab";
- break;
+ return "grab";
case Qt::ClosedHandCursor:
- cursorName = "grabbing";
- break;
+ return "grabbing";
case Qt::DragCopyCursor:
- cursorName = "copy";
- break;
+ return "copy";
case Qt::DragMoveCursor:
- cursorName = "default";
- break;
+ return "default";
case Qt::DragLinkCursor:
- cursorName = "alias";
- break;
+ return "alias";
+ case Qt::BitmapCursor: {
+ auto pixmap = cursor->pixmap();
+ QByteArray cursorAsPng;
+ QBuffer buffer(&cursorAsPng);
+ buffer.open(QBuffer::WriteOnly);
+ pixmap.save(&buffer, "PNG");
+ buffer.close();
+ auto cursorAsBase64 = cursorAsPng.toBase64();
+ auto hotSpot = cursor->hotSpot();
+ auto encodedCursor =
+ QString("url(data:image/png;base64,%1) %2 %3, auto")
+ .arg(QString::fromUtf8(cursorAsBase64),
+ QString::number(hotSpot.x()),
+ QString::number(hotSpot.y()));
+ return encodedCursor.toUtf8();
+ }
default:
- break;
+ static_assert(Qt::CustomCursor == 25,
+ "New cursor type added, handle it");
+ qWarning() << "QWasmCursor: " << shape << " unsupported";
+ return "default";
}
-
- return cursorName;
-}
-
-void QWasmCursor::setWasmCursor(QScreen *screen, const QByteArray &name)
-{
- // Set cursor on the canvas
- val canvas = QWasmScreen::get(screen)->canvas();
- val canvasStyle = canvas["style"];
- canvasStyle.set("cursor", val(name.constData()));
}
+} // namespace
-void QWasmCursor::setOverrideWasmCursor(const QCursor &windowCursor, QScreen *screen)
-{
- QWasmCursor *wCursor = static_cast<QWasmCursor *>(QWasmScreen::get(screen)->cursor());
- wCursor->setWasmCursor(screen, wCursor->cursorShapeToHtml(windowCursor.shape()));
-}
-
-void QWasmCursor::clearOverrideWasmCursor(QScreen *screen)
+void QWasmCursor::changeCursor(QCursor *windowCursor, QWindow *window)
{
- QWasmCursor *wCursor = static_cast<QWasmCursor *>(QWasmScreen::get(screen)->cursor());
- wCursor->setWasmCursor(screen, wCursor->htmlCursorName);
+ if (!window)
+ return;
+ if (QWasmWindow *wasmWindow = static_cast<QWasmWindow *>(window->handle()))
+ wasmWindow->setWindowCursor(windowCursor ? cursorToCss(windowCursor) : "default");
}
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/wasm/qwasmcursor.h b/src/plugins/platforms/wasm/qwasmcursor.h
index 4a5cb23bf4..6873602caf 100644
--- a/src/plugins/platforms/wasm/qwasmcursor.h
+++ b/src/plugins/platforms/wasm/qwasmcursor.h
@@ -5,19 +5,13 @@
#define QWASMCURSOR_H
#include <qpa/qplatformcursor.h>
+
QT_BEGIN_NAMESPACE
class QWasmCursor : public QPlatformCursor
{
public:
void changeCursor(QCursor *windowCursor, QWindow *window) override;
-
- QByteArray cursorShapeToHtml(Qt::CursorShape shape);
- static void setOverrideWasmCursor(const QCursor &windowCursor, QScreen *screen);
- static void clearOverrideWasmCursor(QScreen *screen);
-private:
- QByteArray htmlCursorName = "default";
- void setWasmCursor(QScreen *screen, const QByteArray &name);
};
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/wasm/qwasmdom.cpp b/src/plugins/platforms/wasm/qwasmdom.cpp
new file mode 100644
index 0000000000..6b2b3d0933
--- /dev/null
+++ b/src/plugins/platforms/wasm/qwasmdom.cpp
@@ -0,0 +1,303 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "qwasmdom.h"
+
+#include <QtCore/qdir.h>
+#include <QtCore/qfile.h>
+#include <QtCore/qpoint.h>
+#include <QtCore/qrect.h>
+#include <QtGui/qimage.h>
+#include <private/qstdweb_p.h>
+#include <QtCore/qurl.h>
+
+#include <utility>
+#include <emscripten/wire.h>
+
+QT_BEGIN_NAMESPACE
+
+namespace dom {
+namespace {
+std::string dropActionToDropEffect(Qt::DropAction action)
+{
+ switch (action) {
+ case Qt::DropAction::CopyAction:
+ return "copy";
+ case Qt::DropAction::IgnoreAction:
+ return "none";
+ case Qt::DropAction::LinkAction:
+ return "link";
+ case Qt::DropAction::MoveAction:
+ case Qt::DropAction::TargetMoveAction:
+ return "move";
+ case Qt::DropAction::ActionMask:
+ Q_ASSERT(false);
+ return "";
+ }
+}
+} // namespace
+
+DataTransfer::DataTransfer(emscripten::val webDataTransfer)
+ : webDataTransfer(webDataTransfer) {
+}
+
+DataTransfer::~DataTransfer() = default;
+
+DataTransfer::DataTransfer(const DataTransfer &other) = default;
+
+DataTransfer::DataTransfer(DataTransfer &&other) = default;
+
+DataTransfer &DataTransfer::operator=(const DataTransfer &other) = default;
+
+DataTransfer &DataTransfer::operator=(DataTransfer &&other) = default;
+
+void DataTransfer::setDragImage(emscripten::val element, const QPoint &hotspot)
+{
+ webDataTransfer.call<void>("setDragImage", element, emscripten::val(hotspot.x()),
+ emscripten::val(hotspot.y()));
+}
+
+void DataTransfer::setData(std::string format, std::string data)
+{
+ webDataTransfer.call<void>("setData", emscripten::val(std::move(format)),
+ emscripten::val(std::move(data)));
+}
+
+void DataTransfer::setDropAction(Qt::DropAction action)
+{
+ webDataTransfer.set("dropEffect", emscripten::val(dropActionToDropEffect(action)));
+}
+
+void DataTransfer::setDataFromMimeData(const QMimeData &mimeData)
+{
+ for (const auto &format : mimeData.formats()) {
+ auto data = mimeData.data(format);
+
+ auto encoded = format.startsWith("text/")
+ ? QString::fromLocal8Bit(data).toStdString()
+ : "QB64" + QString::fromLocal8Bit(data.toBase64()).toStdString();
+
+ setData(format.toStdString(), std::move(encoded));
+ }
+}
+
+// Converts a DataTransfer instance to a QMimeData instance. Invokes the
+// given callback when the conversion is complete. The callback takes ownership
+// of the QMimeData.
+void DataTransfer::toMimeDataWithFile(std::function<void(QMimeData *)> callback)
+{
+ enum class ItemKind {
+ File,
+ String,
+ };
+
+ class MimeContext {
+
+ public:
+ MimeContext(int itemCount, std::function<void(QMimeData *)> callback)
+ :m_remainingItemCount(itemCount), m_callback(callback)
+ {
+
+ }
+
+ void deref() {
+ if (--m_remainingItemCount > 0)
+ return;
+
+ mimeData->setUrls(fileUrls);
+
+ m_callback(mimeData);
+
+ // Delete files; we expect that the user callback reads/copies
+ // file content before returning.
+ // Fixme: tie file lifetime to lifetime of the QMimeData?
+ for (QUrl fileUrl: fileUrls)
+ QFile(fileUrl.toLocalFile()).remove();
+
+ delete this;
+ }
+
+ QMimeData *mimeData = new QMimeData();
+ QList<QUrl> fileUrls;
+
+ private:
+ int m_remainingItemCount;
+ std::function<void(QMimeData *)> m_callback;
+ };
+
+ const auto items = webDataTransfer["items"];
+ const int itemCount = items["length"].as<int>();
+ const int fileCount = webDataTransfer["files"]["length"].as<int>();
+ MimeContext *mimeContext = new MimeContext(itemCount, callback);
+
+ for (int i = 0; i < itemCount; ++i) {
+ const auto item = items[i];
+ const auto itemKind =
+ item["kind"].as<std::string>() == "string" ? ItemKind::String : ItemKind::File;
+ const auto itemMimeType = QString::fromStdString(item["type"].as<std::string>());
+
+ switch (itemKind) {
+ case ItemKind::File: {
+ qstdweb::File webfile(item.call<emscripten::val>("getAsFile"));
+
+ if (webfile.size() > 1e+9) { // limit file size to 1 GB
+ qWarning() << "File is too large (> 1GB) and will be skipped. File size is" << webfile.size();
+ mimeContext->deref();
+ continue;
+ }
+
+ QString mimeFormat = QString::fromStdString(webfile.type());
+ QString fileName = QString::fromStdString(webfile.name());
+
+ // there's a file, now read it
+ QByteArray fileContent(webfile.size(), Qt::Uninitialized);
+ webfile.stream(fileContent.data(), [=]() {
+
+ // If we get a single file, and that file is an image, then
+ // try to decode the image data. This handles the case where
+ // image data (i.e. not an image file) is pasted. The browsers
+ // will then create a fake "image.png" file which has the image
+ // data. As a side effect Qt will also decode the image for
+ // single-image-file drops, since there is no way to differentiate
+ // the fake "image.png" from a real one.
+ if (fileCount == 1 && mimeFormat.contains("image/")) {
+ QImage image;
+ if (image.loadFromData(fileContent))
+ mimeContext->mimeData->setImageData(image);
+ }
+
+ QDir qtTmpDir("/qt/tmp/"); // "tmp": indicate that these files won't stay around
+ qtTmpDir.mkpath(qtTmpDir.path());
+
+ QUrl fileUrl = QUrl::fromLocalFile(qtTmpDir.filePath(QString::fromStdString(webfile.name())));
+ mimeContext->fileUrls.append(fileUrl);
+
+ QFile file(fileUrl.toLocalFile());
+ if (!file.open(QFile::WriteOnly)) {
+ qWarning() << "File was not opened";
+ mimeContext->deref();
+ return;
+ }
+ if (file.write(fileContent) < 0) {
+ qWarning() << "Write failed";
+ file.close();
+ }
+ mimeContext->deref();
+ });
+ break;
+ }
+ case ItemKind::String:
+ if (itemMimeType.contains("STRING", Qt::CaseSensitive)
+ || itemMimeType.contains("TEXT", Qt::CaseSensitive)) {
+ mimeContext->deref();
+ break;
+ }
+ QString a;
+ QString data = QString::fromEcmaString(webDataTransfer.call<emscripten::val>(
+ "getData", emscripten::val(itemMimeType.toStdString())));
+
+ if (!data.isEmpty()) {
+ if (itemMimeType == "text/html")
+ mimeContext->mimeData->setHtml(data);
+ else if (itemMimeType.isEmpty() || itemMimeType == "text/plain")
+ mimeContext->mimeData->setText(data); // the type can be empty
+ else {
+ // TODO improve encoding
+ if (data.startsWith("QB64")) {
+ data.remove(0, 4);
+ mimeContext->mimeData->setData(itemMimeType,
+ QByteArray::fromBase64(QByteArray::fromStdString(
+ data.toStdString())));
+ } else {
+ mimeContext->mimeData->setData(itemMimeType, data.toLocal8Bit());
+ }
+ }
+ }
+ mimeContext->deref();
+ break;
+ }
+ } // for items
+}
+
+QMimeData *DataTransfer::toMimeDataPreview()
+{
+ auto data = new QMimeData();
+
+ QList<QUrl> uriList;
+ for (int i = 0; i < webDataTransfer["items"]["length"].as<int>(); ++i) {
+ const auto item = webDataTransfer["items"][i];
+ if (item["kind"].as<std::string>() == "file") {
+ uriList.append(QUrl("blob://placeholder"));
+ } else {
+ const auto itemMimeType = QString::fromStdString(item["type"].as<std::string>());
+ data->setData(itemMimeType, QByteArray());
+ }
+ }
+ data->setUrls(uriList);
+ return data;
+}
+
+void syncCSSClassWith(emscripten::val element, std::string cssClassName, bool flag)
+{
+ if (flag) {
+ element["classList"].call<void>("add", emscripten::val(std::move(cssClassName)));
+ return;
+ }
+
+ element["classList"].call<void>("remove", emscripten::val(std::move(cssClassName)));
+}
+
+QPointF mapPoint(emscripten::val source, emscripten::val target, const QPointF &point)
+{
+ const auto sourceBoundingRect =
+ QRectF::fromDOMRect(source.call<emscripten::val>("getBoundingClientRect"));
+ const auto targetBoundingRect =
+ QRectF::fromDOMRect(target.call<emscripten::val>("getBoundingClientRect"));
+
+ const auto offset = sourceBoundingRect.topLeft() - targetBoundingRect.topLeft();
+ return point + offset;
+}
+
+void drawImageToWebImageDataArray(const QImage &sourceImage, emscripten::val destinationImageData,
+ const QRect &sourceRect)
+{
+ Q_ASSERT_X(destinationImageData["constructor"]["name"].as<std::string>() == "ImageData",
+ Q_FUNC_INFO, "The destination should be an ImageData instance");
+
+ constexpr int BytesPerColor = 4;
+ if (sourceRect.width() == sourceImage.width()) {
+ // Copy a contiguous chunk of memory
+ // ...............
+ // OOOOOOOOOOOOOOO
+ // OOOOOOOOOOOOOOO -> image data
+ // OOOOOOOOOOOOOOO
+ // ...............
+ auto imageMemory = emscripten::typed_memory_view(sourceRect.width() * sourceRect.height()
+ * BytesPerColor,
+ sourceImage.constScanLine(sourceRect.y()));
+ destinationImageData["data"].call<void>(
+ "set", imageMemory, sourceRect.y() * sourceImage.width() * BytesPerColor);
+ } else {
+ // Go through the scanlines manually to set the individual lines in bulk. This is
+ // marginally less performant than the above.
+ // ...............
+ // ...OOOOOOOOO... r = 0 -> image data
+ // ...OOOOOOOOO... r = 1 -> image data
+ // ...OOOOOOOOO... r = 2 -> image data
+ // ...............
+ for (int row = 0; row < sourceRect.height(); ++row) {
+ auto scanlineMemory =
+ emscripten::typed_memory_view(sourceRect.width() * BytesPerColor,
+ sourceImage.constScanLine(row + sourceRect.y())
+ + BytesPerColor * sourceRect.x());
+ destinationImageData["data"].call<void>("set", scanlineMemory,
+ (sourceRect.y() + row) * sourceImage.width()
+ * BytesPerColor
+ + sourceRect.x() * BytesPerColor);
+ }
+ }
+}
+
+} // namespace dom
+
+QT_END_NAMESPACE
diff --git a/src/plugins/platforms/wasm/qwasmdom.h b/src/plugins/platforms/wasm/qwasmdom.h
new file mode 100644
index 0000000000..0a520815a3
--- /dev/null
+++ b/src/plugins/platforms/wasm/qwasmdom.h
@@ -0,0 +1,63 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef QWASMDOM_H
+#define QWASMDOM_H
+
+#include <QtCore/qtconfigmacros.h>
+#include <QtCore/QPointF>
+#include <private/qstdweb_p.h>
+#include <QtCore/qnamespace.h>
+
+#include <emscripten/val.h>
+
+#include <functional>
+#include <memory>
+#include <string>
+
+#include <QMimeData>
+QT_BEGIN_NAMESPACE
+
+namespace qstdweb {
+ struct CancellationFlag;
+}
+
+
+class QPoint;
+class QRect;
+
+namespace dom {
+struct DataTransfer
+{
+ explicit DataTransfer(emscripten::val webDataTransfer);
+ ~DataTransfer();
+ DataTransfer(const DataTransfer &other);
+ DataTransfer(DataTransfer &&other);
+ DataTransfer &operator=(const DataTransfer &other);
+ DataTransfer &operator=(DataTransfer &&other);
+
+ void toMimeDataWithFile(std::function<void(QMimeData *)> callback);
+ QMimeData *toMimeDataPreview();
+ void setDragImage(emscripten::val element, const QPoint &hotspot);
+ void setData(std::string format, std::string data);
+ void setDropAction(Qt::DropAction dropAction);
+ void setDataFromMimeData(const QMimeData &mimeData);
+
+ emscripten::val webDataTransfer;
+};
+
+inline emscripten::val document()
+{
+ return emscripten::val::global("document");
+}
+
+void syncCSSClassWith(emscripten::val element, std::string cssClassName, bool flag);
+
+QPointF mapPoint(emscripten::val source, emscripten::val target, const QPointF &point);
+
+void drawImageToWebImageDataArray(const QImage &source, emscripten::val destinationImageData,
+ const QRect &sourceRect);
+} // namespace dom
+
+QT_END_NAMESPACE
+#endif // QWASMDOM_H
diff --git a/src/plugins/platforms/wasm/qwasmdrag.cpp b/src/plugins/platforms/wasm/qwasmdrag.cpp
index c2b50e4356..d07a46618f 100644
--- a/src/plugins/platforms/wasm/qwasmdrag.cpp
+++ b/src/plugins/platforms/wasm/qwasmdrag.cpp
@@ -1,209 +1,291 @@
-// Copyright (C) 2022 The Qt Company Ltd.
+// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#include "qwasmdrag.h"
-#include <iostream>
-#include <QMimeDatabase>
+#include "qwasmbase64iconstore.h"
+#include "qwasmdom.h"
+#include "qwasmevent.h"
+#include "qwasmintegration.h"
-#include <emscripten.h>
-#include <emscripten/html5.h>
-#include <emscripten/val.h>
-#include <emscripten/bind.h>
-#include <private/qstdweb_p.h>
#include <qpa/qwindowsysteminterface.h>
-#include <private/qsimpledrag_p.h>
-#include "qwasmcompositor.h"
-#include "qwasmeventtranslator.h"
-#include <QtCore/QEventLoop>
-#include <QMimeData>
-#include <private/qshapedpixmapdndwindow_p.h>
+
+#include <QtCore/private/qstdweb_p.h>
+#include <QtCore/qeventloop.h>
+#include <QtCore/qmimedata.h>
+#include <QtCore/qtimer.h>
+#include <QFile>
+
+#include <functional>
+#include <string>
+#include <utility>
QT_BEGIN_NAMESPACE
-using namespace emscripten;
+namespace {
+
+QWindow *windowForDrag(QDrag *drag)
+{
+ QWindow *window = qobject_cast<QWindow *>(drag->source());
+ if (window)
+ return window;
+ if (drag->source()->metaObject()->indexOfMethod("_q_closestWindowHandle()") == -1)
+ return nullptr;
+
+ QMetaObject::invokeMethod(drag->source(), "_q_closestWindowHandle",
+ Q_RETURN_ARG(QWindow *, window));
+ return window;
+}
+
+} // namespace
-static void getTextPlainCallback(val m_string)
+struct QWasmDrag::DragState
+{
+ class DragImage
+ {
+ public:
+ DragImage(const QPixmap &pixmap, const QMimeData *mimeData, QWindow *window);
+ ~DragImage();
+
+ emscripten::val htmlElement();
+
+ private:
+ emscripten::val generateDragImage(const QPixmap &pixmap, const QMimeData *mimeData);
+ emscripten::val generateDragImageFromText(const QMimeData *mimeData);
+ emscripten::val generateDefaultDragImage();
+ emscripten::val generateDragImageFromPixmap(const QPixmap &pixmap);
+
+ emscripten::val m_imageDomElement;
+ emscripten::val m_temporaryImageElementParent;
+ };
+
+ DragState(QDrag *drag, QWindow *window, std::function<void()> quitEventLoopClosure);
+ ~DragState();
+ DragState(const QWasmDrag &other) = delete;
+ DragState(QWasmDrag &&other) = delete;
+ DragState &operator=(const QWasmDrag &other) = delete;
+ DragState &operator=(QWasmDrag &&other) = delete;
+
+ QDrag *drag;
+ QWindow *window;
+ std::function<void()> quitEventLoopClosure;
+ std::unique_ptr<DragImage> dragImage;
+ Qt::DropAction dropAction = Qt::DropAction::IgnoreAction;
+};
+
+QWasmDrag::QWasmDrag() = default;
+
+QWasmDrag::~QWasmDrag() = default;
+
+QWasmDrag *QWasmDrag::instance()
{
- QWasmDrag *thisDrag = static_cast<QWasmDrag*>(QWasmIntegration::get()->drag());
- thisDrag->m_mimeData->setText(QString::fromStdString(m_string.as<std::string>()));
- thisDrag->qWasmDrop();
+ return static_cast<QWasmDrag *>(QWasmIntegration::get()->drag());
}
-static void getTextUrlCallback(val m_string)
+Qt::DropAction QWasmDrag::drag(QDrag *drag)
{
- QWasmDrag *thisDrag = static_cast<QWasmDrag*>(QWasmIntegration::get()->drag());
- thisDrag->m_mimeData->setData(QStringLiteral("text/uri-list"),
- QByteArray::fromStdString(m_string.as<std::string>()));
+ Q_ASSERT_X(!m_dragState, Q_FUNC_INFO, "Drag already in progress");
+
+ QWindow *window = windowForDrag(drag);
+ if (!window)
+ return Qt::IgnoreAction;
+
+ Qt::DropAction dragResult = Qt::IgnoreAction;
+ if (qstdweb::haveJspi()) {
+ QEventLoop loop;
+ m_dragState = std::make_unique<DragState>(drag, window, [&loop]() { loop.quit(); });
+ loop.exec();
+ dragResult = m_dragState->dropAction;
+ m_dragState.reset();
+ }
- thisDrag->qWasmDrop();
+ if (dragResult == Qt::IgnoreAction)
+ dragResult = QBasicDrag::drag(drag);
+
+ return dragResult;
}
-static void getTextHtmlCallback(val m_string)
+void QWasmDrag::onNativeDragStarted(DragEvent *event)
{
- QWasmDrag *thisDrag = static_cast<QWasmDrag*>(QWasmIntegration::get()->drag());
- thisDrag->m_mimeData->setHtml(QString::fromStdString(m_string.as<std::string>()));
+ Q_ASSERT_X(event->type == EventType::DragStart, Q_FUNC_INFO,
+ "The event is not a DragStart event");
+ // It is possible for a drag start event to arrive from another window.
+ if (!m_dragState || m_dragState->window != event->targetWindow) {
+ event->cancelDragStart();
+ return;
+ }
- thisDrag->qWasmDrop();
+ m_dragState->dragImage = std::make_unique<DragState::DragImage>(
+ m_dragState->drag->pixmap(), m_dragState->drag->mimeData(), event->targetWindow);
+ event->dataTransfer.setDragImage(m_dragState->dragImage->htmlElement(),
+ m_dragState->drag->hotSpot());
+ event->dataTransfer.setDataFromMimeData(*m_dragState->drag->mimeData());
}
-static void dropEvent(val event)
-{
- // someone dropped a file into the browser window
- // event is dataTransfer object
- // if drop event from outside browser, we do not get any mouse release, maybe mouse move
- // after the drop event
-
- // data-context thing was not working here :(
- QWasmDrag *wasmDrag = static_cast<QWasmDrag*>(QWasmIntegration::get()->drag());
-
- wasmDrag->m_wasmScreen =
- reinterpret_cast<QWasmScreen*>(event["target"]["data-qtdropcontext"].as<quintptr>());
-
- wasmDrag->m_mouseDropPoint = QPoint(event["x"].as<int>(), event["y"].as<int>());
- if (wasmDrag->m_mimeData)
- delete wasmDrag->m_mimeData;
- wasmDrag->m_mimeData = new QMimeData;
- wasmDrag->m_qButton = MouseEvent::buttonFromWeb(event["button"].as<int>());
-
- wasmDrag->m_keyModifiers = Qt::NoModifier;
- if (event["altKey"].as<bool>())
- wasmDrag->m_keyModifiers |= Qt::AltModifier;
- if (event["ctrlKey"].as<bool>())
- wasmDrag->m_keyModifiers |= Qt::ControlModifier;
- if (event["metaKey"].as<bool>())
- wasmDrag->m_keyModifiers |= Qt::MetaModifier;
-
- event.call<void>("preventDefault"); // prevent browser from handling drop event
-
- std::string dEffect = event["dataTransfer"]["dropEffect"].as<std::string>();
-
- wasmDrag->m_dropActions = Qt::IgnoreAction;
- if (dEffect == "copy")
- wasmDrag->m_dropActions = Qt::CopyAction;
- if (dEffect == "move")
- wasmDrag->m_dropActions = Qt::MoveAction;
- if (dEffect == "link")
- wasmDrag->m_dropActions = Qt::LinkAction;
-
- val dt = event["dataTransfer"]["items"]["length"];
-
- val typesCount = event["dataTransfer"]["types"]["length"];
-
- // handle mimedata
- int count = dt.as<int>();
- wasmDrag->m_mimeTypesCount = count;
- // kind is file type: file or string
- for (int i=0; i < count; i++) {
- val item = event["dataTransfer"]["items"][i];
- val kind = item["kind"];
- val fileType = item["type"];
-
- if (kind.as<std::string>() == "file") {
- val m_file = item.call<val>("getAsFile");
- if (m_file.isUndefined()) {
- continue;
- }
-
- qstdweb::File file(m_file);
-
- QString mimeFormat = QString::fromStdString(file.type());
- QByteArray fileContent;
- fileContent.resize(file.size());
-
- file.stream(fileContent.data(), [=]() {
- if (!fileContent.isEmpty()) {
-
- if (mimeFormat.contains("image")) {
- QImage image;
- image.loadFromData(fileContent, nullptr);
- wasmDrag->m_mimeData->setImageData(image);
- } else {
- wasmDrag->m_mimeData->setData(mimeFormat, fileContent.data());
- }
- wasmDrag->qWasmDrop();
- }
- });
-
- } else { // string
-
- if (fileType.as<std::string>() == "text/uri-list"
- || fileType.as<std::string>() == "text/x-moz-url") {
- item.call<val>("getAsString", val::module_property("qtgetTextUrl"));
- } else if (fileType.as<std::string>() == "text/html") {
- item.call<val>("getAsString", val::module_property("qtgetTextHtml"));
- } else { // handle everything else here as plain text
- item.call<val>("getAsString", val::module_property("qtgetTextPlain"));
- }
- }
+void QWasmDrag::onNativeDragOver(DragEvent *event)
+{
+ auto mimeDataPreview = event->dataTransfer.toMimeDataPreview();
+
+ const Qt::DropActions actions = m_dragState
+ ? m_dragState->drag->supportedActions()
+ : (Qt::DropAction::CopyAction | Qt::DropAction::MoveAction
+ | Qt::DropAction::LinkAction);
+
+ const auto dragResponse = QWindowSystemInterface::handleDrag(
+ event->targetWindow, &*mimeDataPreview, event->pointInPage.toPoint(), actions,
+ event->mouseButton, event->modifiers);
+ event->acceptDragOver();
+ if (dragResponse.isAccepted()) {
+ event->dataTransfer.setDropAction(dragResponse.acceptedAction());
+ } else {
+ event->dataTransfer.setDropAction(Qt::DropAction::IgnoreAction);
}
}
-EMSCRIPTEN_BINDINGS(drop_module) {
- function("qtDrop", &dropEvent);
- function("qtgetTextPlain", &getTextPlainCallback);
- function("qtgetTextUrl", &getTextUrlCallback);
- function("qtgetTextHtml", &getTextHtmlCallback);
+void QWasmDrag::onNativeDrop(DragEvent *event)
+{
+ QWasmWindow *wasmWindow = QWasmWindow::fromWindow(event->targetWindow);
+
+ const auto screenElementPos = dom::mapPoint(
+ event->target(), wasmWindow->platformScreen()->element(), event->localPoint);
+ const auto screenPos =
+ wasmWindow->platformScreen()->mapFromLocal(screenElementPos);
+ const QPoint targetWindowPos = event->targetWindow->mapFromGlobal(screenPos).toPoint();
+
+ const Qt::DropActions actions = m_dragState
+ ? m_dragState->drag->supportedActions()
+ : (Qt::DropAction::CopyAction | Qt::DropAction::MoveAction
+ | Qt::DropAction::LinkAction);
+ Qt::MouseButton mouseButton = event->mouseButton;
+ QFlags<Qt::KeyboardModifier> modifiers = event->modifiers;
+
+ // Accept the native drop event: We are going to async read any dropped
+ // files, but the browser expects that accepted state is set before any
+ // async calls.
+ event->acceptDrop();
+
+ const auto dropCallback = [&m_dragState = m_dragState, wasmWindow, targetWindowPos,
+ actions, mouseButton, modifiers](QMimeData *mimeData) {
+
+ auto dropResponse = std::make_shared<QPlatformDropQtResponse>(true, Qt::DropAction::CopyAction);
+ *dropResponse = QWindowSystemInterface::handleDrop(wasmWindow->window(), mimeData,
+ targetWindowPos, actions,
+ mouseButton, modifiers);
+
+ if (dropResponse->isAccepted())
+ m_dragState->dropAction = dropResponse->acceptedAction();
+
+ delete mimeData;
+ };
+
+ event->dataTransfer.toMimeDataWithFile(dropCallback);
}
+void QWasmDrag::onNativeDragFinished(DragEvent *event)
+{
+ m_dragState->dropAction = event->dropAction;
+ m_dragState->quitEventLoopClosure();
+}
+
+QWasmDrag::DragState::DragImage::DragImage(const QPixmap &pixmap, const QMimeData *mimeData,
+ QWindow *window)
+ : m_temporaryImageElementParent(QWasmWindow::fromWindow(window)->containerElement())
+{
+ m_imageDomElement = generateDragImage(pixmap, mimeData);
+
+ m_imageDomElement.set("className", "hidden-drag-image");
+ m_temporaryImageElementParent.call<void>("appendChild", m_imageDomElement);
+}
+
+QWasmDrag::DragState::DragImage::~DragImage()
+{
+ m_temporaryImageElementParent.call<void>("removeChild", m_imageDomElement);
+}
-QWasmDrag::QWasmDrag()
+emscripten::val QWasmDrag::DragState::DragImage::generateDragImage(const QPixmap &pixmap,
+ const QMimeData *mimeData)
{
- init();
+ if (!pixmap.isNull())
+ return generateDragImageFromPixmap(pixmap);
+ if (mimeData->hasFormat("text/plain"))
+ return generateDragImageFromText(mimeData);
+ return generateDefaultDragImage();
}
-QWasmDrag::~QWasmDrag()
+emscripten::val
+QWasmDrag::DragState::DragImage::generateDragImageFromText(const QMimeData *mimeData)
{
- if (m_mimeData)
- delete m_mimeData;
+ emscripten::val dragImageElement =
+ emscripten::val::global("document")
+ .call<emscripten::val>("createElement", emscripten::val("span"));
+
+ constexpr qsizetype MaxCharactersInDragImage = 100;
+
+ const auto text = QString::fromUtf8(mimeData->data("text/plain"));
+ dragImageElement.set(
+ "innerText",
+ text.first(qMin(qsizetype(MaxCharactersInDragImage), text.length())).toStdString());
+ return dragImageElement;
}
-void QWasmDrag::init()
+emscripten::val QWasmDrag::DragState::DragImage::generateDefaultDragImage()
{
+ emscripten::val dragImageElement =
+ emscripten::val::global("document")
+ .call<emscripten::val>("createElement", emscripten::val("div"));
+
+ auto innerImgElement = emscripten::val::global("document")
+ .call<emscripten::val>("createElement", emscripten::val("img"));
+ innerImgElement.set("src",
+ "data:image/" + std::string("svg+xml") + ";base64,"
+ + std::string(Base64IconStore::get()->getIcon(
+ Base64IconStore::IconType::QtLogo)));
+
+ constexpr char DragImageSize[] = "50px";
+
+ dragImageElement["style"].set("width", DragImageSize);
+ innerImgElement["style"].set("width", DragImageSize);
+ dragImageElement["style"].set("display", "flex");
+
+ dragImageElement.call<void>("appendChild", innerImgElement);
+ return dragImageElement;
}
-void QWasmDrag::drop(const QPoint &globalPos, Qt::MouseButtons b, Qt::KeyboardModifiers mods)
+emscripten::val QWasmDrag::DragState::DragImage::generateDragImageFromPixmap(const QPixmap &pixmap)
{
- QSimpleDrag::drop(globalPos, b, mods);
+ emscripten::val dragImageElement =
+ emscripten::val::global("document")
+ .call<emscripten::val>("createElement", emscripten::val("canvas"));
+ dragImageElement.set("width", pixmap.width());
+ dragImageElement.set("height", pixmap.height());
+
+ dragImageElement["style"].set(
+ "width", std::to_string(pixmap.width() / pixmap.devicePixelRatio()) + "px");
+ dragImageElement["style"].set(
+ "height", std::to_string(pixmap.height() / pixmap.devicePixelRatio()) + "px");
+
+ auto context2d = dragImageElement.call<emscripten::val>("getContext", emscripten::val("2d"));
+ auto imageData = context2d.call<emscripten::val>(
+ "createImageData", emscripten::val(pixmap.width()), emscripten::val(pixmap.height()));
+
+ dom::drawImageToWebImageDataArray(pixmap.toImage().convertedTo(QImage::Format::Format_RGBA8888),
+ imageData, QRect(0, 0, pixmap.width(), pixmap.height()));
+ context2d.call<void>("putImageData", imageData, emscripten::val(0), emscripten::val(0));
+
+ return dragImageElement;
}
-void QWasmDrag::move(const QPoint &globalPos, Qt::MouseButtons b, Qt::KeyboardModifiers mods)
+emscripten::val QWasmDrag::DragState::DragImage::htmlElement()
{
- QSimpleDrag::move(globalPos, b, mods);
+ return m_imageDomElement;
}
-void QWasmDrag::qWasmDrop()
-{
- // collect mime
- QWasmDrag *thisDrag = static_cast<QWasmDrag*>(QWasmIntegration::get()->drag());
-
- if (thisDrag->m_mimeTypesCount != thisDrag->m_mimeData->formats().size())
- return; // keep collecting mimetypes
-
- // start drag enter
- QWindowSystemInterface::handleDrag(thisDrag->m_wasmScreen->topLevelAt(thisDrag->m_mouseDropPoint),
- thisDrag->m_mimeData,
- thisDrag->m_mouseDropPoint,
- thisDrag->m_dropActions,
- thisDrag->m_qButton,
- thisDrag->m_keyModifiers);
-
- // drag drop
- QWindowSystemInterface::handleDrop(thisDrag->m_wasmScreen->topLevelAt(thisDrag->m_mouseDropPoint),
- thisDrag->m_mimeData,
- thisDrag->m_mouseDropPoint,
- thisDrag->m_dropActions,
- thisDrag->m_qButton,
- thisDrag->m_keyModifiers);
-
- // drag leave
- QWindowSystemInterface::handleDrag(thisDrag->m_wasmScreen->topLevelAt(thisDrag->m_mouseDropPoint),
- nullptr,
- QPoint(),
- Qt::IgnoreAction, { }, { });
-
- thisDrag->m_mimeData->clear();
- thisDrag->m_mimeTypesCount = 0;
+QWasmDrag::DragState::DragState(QDrag *drag, QWindow *window,
+ std::function<void()> quitEventLoopClosure)
+ : drag(drag), window(window), quitEventLoopClosure(std::move(quitEventLoopClosure))
+{
}
+QWasmDrag::DragState::~DragState() = default;
+
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/wasm/qwasmdrag.h b/src/plugins/platforms/wasm/qwasmdrag.h
index 6358b415c3..146a69ebe8 100644
--- a/src/plugins/platforms/wasm/qwasmdrag.h
+++ b/src/plugins/platforms/wasm/qwasmdrag.h
@@ -1,43 +1,47 @@
-// Copyright (C) 2022 The Qt Company Ltd.
+// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
-#ifndef QWASMDRAG_H
-#define QWASMDRAG_H
+#ifndef QWINDOWSDRAG_H
+#define QWINDOWSDRAG_H
-#include <qpa/qplatformdrag.h>
-#include <private/qsimpledrag_p.h>
#include <private/qstdweb_p.h>
-#include <QDrag>
-#include "qwasmscreen.h"
+#include <private/qsimpledrag_p.h>
+
+#include <qpa/qplatformdrag.h>
+#include <QtGui/qdrag.h>
-QT_REQUIRE_CONFIG(draganddrop);
+#include <memory>
QT_BEGIN_NAMESPACE
-class QWasmDrag : public QSimpleDrag
+struct DragEvent;
+
+class QWasmDrag final : public QSimpleDrag
{
public:
-
QWasmDrag();
- ~QWasmDrag();
+ ~QWasmDrag() override;
+ QWasmDrag(const QWasmDrag &other) = delete;
+ QWasmDrag(QWasmDrag &&other) = delete;
+ QWasmDrag &operator=(const QWasmDrag &other) = delete;
+ QWasmDrag &operator=(QWasmDrag &&other) = delete;
+
+ static QWasmDrag *instance();
- void drop(const QPoint &globalPos, Qt::MouseButtons b, Qt::KeyboardModifiers mods) override;
- void move(const QPoint &globalPos, Qt::MouseButtons b, Qt::KeyboardModifiers mods) override;
+ void onNativeDragOver(DragEvent *event);
+ void onNativeDrop(DragEvent *event);
+ void onNativeDragStarted(DragEvent *event);
+ void onNativeDragFinished(DragEvent *event);
- Qt::MouseButton m_qButton;
- QPoint m_mouseDropPoint;
- QFlags<Qt::KeyboardModifier> m_keyModifiers;
- Qt::DropActions m_dropActions;
- QWasmScreen *m_wasmScreen = nullptr;
- int m_mimeTypesCount = 0;
- QMimeData *m_mimeData = nullptr;
- void qWasmDrop();
+ // QPlatformDrag:
+ Qt::DropAction drag(QDrag *drag) final;
private:
- void init();
-};
+ struct DragState;
+ std::unique_ptr<DragState> m_dragState;
+};
QT_END_NAMESPACE
-#endif // QWASMDRAG_H
+#endif // QWINDOWSDRAG_H
diff --git a/src/plugins/platforms/wasm/qwasmevent.cpp b/src/plugins/platforms/wasm/qwasmevent.cpp
index b094dbfe14..c1d6ce3a2a 100644
--- a/src/plugins/platforms/wasm/qwasmevent.cpp
+++ b/src/plugins/platforms/wasm/qwasmevent.cpp
@@ -3,8 +3,78 @@
#include "qwasmevent.h"
+#include "qwasmkeytranslator.h"
+
+#include <QtCore/private/qmakearray_p.h>
+#include <QtCore/private/qstringiterator_p.h>
+#include <QtCore/qregularexpression.h>
+
QT_BEGIN_NAMESPACE
+namespace {
+constexpr std::string_view WebDeadKeyValue = "Dead";
+
+bool isDeadKeyEvent(const char *key)
+{
+ return qstrncmp(key, WebDeadKeyValue.data(), WebDeadKeyValue.size()) == 0;
+}
+
+Qt::Key getKeyFromCode(const std::string &code)
+{
+ if (auto mapping = QWasmKeyTranslator::mapWebKeyTextToQtKey(code.c_str()))
+ return *mapping;
+
+ static QRegularExpression regex(QString(QStringLiteral(R"re((?:Key|Digit)(\w))re")));
+ const auto codeQString = QString::fromStdString(code);
+ const auto match = regex.match(codeQString);
+
+ if (!match.hasMatch())
+ return Qt::Key_unknown;
+
+ constexpr size_t CharacterIndex = 1;
+ return static_cast<Qt::Key>(match.capturedView(CharacterIndex).at(0).toLatin1());
+}
+
+Qt::Key webKeyToQtKey(const std::string &code, const std::string &key, bool isDeadKey,
+ QFlags<Qt::KeyboardModifier> modifiers)
+{
+ if (isDeadKey) {
+ auto mapped = getKeyFromCode(code);
+ switch (mapped) {
+ case Qt::Key_U:
+ return Qt::Key_Dead_Diaeresis;
+ case Qt::Key_E:
+ return Qt::Key_Dead_Acute;
+ case Qt::Key_I:
+ return Qt::Key_Dead_Circumflex;
+ case Qt::Key_N:
+ return Qt::Key_Dead_Tilde;
+ case Qt::Key_QuoteLeft:
+ return modifiers.testFlag(Qt::ShiftModifier) ? Qt::Key_Dead_Tilde : Qt::Key_Dead_Grave;
+ case Qt::Key_6:
+ return Qt::Key_Dead_Circumflex;
+ case Qt::Key_Apostrophe:
+ return modifiers.testFlag(Qt::ShiftModifier) ? Qt::Key_Dead_Diaeresis
+ : Qt::Key_Dead_Acute;
+ case Qt::Key_AsciiTilde:
+ return Qt::Key_Dead_Tilde;
+ default:
+ return Qt::Key_unknown;
+ }
+ } else if (auto mapping = QWasmKeyTranslator::mapWebKeyTextToQtKey(key.c_str())) {
+ return *mapping;
+ }
+
+ // cast to unicode key
+ QString str = QString::fromUtf8(key.c_str()).toUpper();
+ if (str.length() > 1)
+ return Qt::Key_unknown;
+
+ QStringIterator i(str);
+ return static_cast<Qt::Key>(i.next(0));
+}
+} // namespace
+
namespace KeyboardModifier
{
template <>
@@ -16,10 +86,129 @@ QFlags<Qt::KeyboardModifier> getForEvent<EmscriptenKeyboardEvent>(
}
} // namespace KeyboardModifier
-std::optional<PointerEvent> PointerEvent::fromWeb(emscripten::val event)
+Event::Event(EventType type, emscripten::val webEvent)
+ : webEvent(webEvent), type(type)
+{
+}
+
+Event::~Event() = default;
+
+Event::Event(const Event &other) = default;
+
+Event::Event(Event &&other) = default;
+
+Event &Event::operator=(const Event &other) = default;
+
+Event &Event::operator=(Event &&other) = default;
+
+KeyEvent::KeyEvent(EventType type, emscripten::val event) : Event(type, event)
{
- PointerEvent ret;
+ const auto code = event["code"].as<std::string>();
+ const auto webKey = event["key"].as<std::string>();
+ deadKey = isDeadKeyEvent(webKey.c_str());
+
+ modifiers = KeyboardModifier::getForEvent(event);
+ key = webKeyToQtKey(code, webKey, deadKey, modifiers);
+
+ text = QString::fromUtf8(webKey);
+ if (text.size() > 1)
+ text.clear();
+
+ if (key == Qt::Key_Tab)
+ text = "\t";
+}
+
+KeyEvent::~KeyEvent() = default;
+
+KeyEvent::KeyEvent(const KeyEvent &other) = default;
+
+KeyEvent::KeyEvent(KeyEvent &&other) = default;
+KeyEvent &KeyEvent::operator=(const KeyEvent &other) = default;
+
+KeyEvent &KeyEvent::operator=(KeyEvent &&other) = default;
+
+std::optional<KeyEvent> KeyEvent::fromWebWithDeadKeyTranslation(emscripten::val event,
+ QWasmDeadKeySupport *deadKeySupport)
+{
+ const auto eventType = ([&event]() -> std::optional<EventType> {
+ const auto eventTypeString = event["type"].as<std::string>();
+
+ if (eventTypeString == "keydown")
+ return EventType::KeyDown;
+ else if (eventTypeString == "keyup")
+ return EventType::KeyUp;
+ return std::nullopt;
+ })();
+ if (!eventType)
+ return std::nullopt;
+
+ auto result = KeyEvent(*eventType, event);
+ deadKeySupport->applyDeadKeyTranslations(&result);
+
+ return result;
+}
+
+MouseEvent::MouseEvent(EventType type, emscripten::val event) : Event(type, event)
+{
+ mouseButton = MouseEvent::buttonFromWeb(event["button"].as<int>());
+ mouseButtons = MouseEvent::buttonsFromWeb(event["buttons"].as<unsigned short>());
+ // The current button state (event.buttons) may be out of sync for some PointerDown
+ // events where the "down" state is very brief, for example taps on Apple trackpads.
+ // Qt expects that the current button state is in sync with the event, so we sync
+ // it up here.
+ if (type == EventType::PointerDown)
+ mouseButtons |= mouseButton;
+ localPoint = QPointF(event["offsetX"].as<qreal>(), event["offsetY"].as<qreal>());
+ pointInPage = QPointF(event["pageX"].as<qreal>(), event["pageY"].as<qreal>());
+ pointInViewport = QPointF(event["clientX"].as<qreal>(), event["clientY"].as<qreal>());
+ modifiers = KeyboardModifier::getForEvent(event);
+}
+
+MouseEvent::~MouseEvent() = default;
+
+MouseEvent::MouseEvent(const MouseEvent &other) = default;
+
+MouseEvent::MouseEvent(MouseEvent &&other) = default;
+
+MouseEvent &MouseEvent::operator=(const MouseEvent &other) = default;
+
+MouseEvent &MouseEvent::operator=(MouseEvent &&other) = default;
+
+PointerEvent::PointerEvent(EventType type, emscripten::val event) : MouseEvent(type, event)
+{
+ pointerId = event["pointerId"].as<int>();
+ pointerType = ([type = event["pointerType"].as<std::string>()]() {
+ if (type == "mouse")
+ return PointerType::Mouse;
+ if (type == "touch")
+ return PointerType::Touch;
+ if (type == "pen")
+ return PointerType::Pen;
+ return PointerType::Other;
+ })();
+ width = event["width"].as<qreal>();
+ height = event["height"].as<qreal>();
+ pressure = event["pressure"].as<qreal>();
+ tiltX = event["tiltX"].as<qreal>();
+ tiltY = event["tiltY"].as<qreal>();
+ tangentialPressure = event["tangentialPressure"].as<qreal>();
+ twist = event["twist"].as<qreal>();
+ isPrimary = event["isPrimary"].as<bool>();
+}
+
+PointerEvent::~PointerEvent() = default;
+
+PointerEvent::PointerEvent(const PointerEvent &other) = default;
+
+PointerEvent::PointerEvent(PointerEvent &&other) = default;
+
+PointerEvent &PointerEvent::operator=(const PointerEvent &other) = default;
+
+PointerEvent &PointerEvent::operator=(PointerEvent &&other) = default;
+
+std::optional<PointerEvent> PointerEvent::fromWeb(emscripten::val event)
+{
const auto eventType = ([&event]() -> std::optional<EventType> {
const auto eventTypeString = event["type"].as<std::string>();
@@ -38,16 +227,112 @@ std::optional<PointerEvent> PointerEvent::fromWeb(emscripten::val event)
if (!eventType)
return std::nullopt;
- ret.type = *eventType;
- ret.pointerType = event["pointerType"].as<std::string>() == "mouse" ?
- PointerType::Mouse : PointerType::Other;
- ret.mouseButton = MouseEvent::buttonFromWeb(event["button"].as<int>());
- ret.mouseButtons = MouseEvent::buttonsFromWeb(event["buttons"].as<unsigned short>());
- ret.point = QPoint(event["x"].as<int>(), event["y"].as<int>());
- ret.pointerId = event["pointerId"].as<int>();
- ret.modifiers = KeyboardModifier::getForEvent(event);
+ return PointerEvent(*eventType, event);
+}
+
+DragEvent::DragEvent(EventType type, emscripten::val event, QWindow *window)
+ : MouseEvent(type, event), dataTransfer(event["dataTransfer"]), targetWindow(window)
+{
+ dropAction = ([event]() {
+ const std::string effect = event["dataTransfer"]["dropEffect"].as<std::string>();
+
+ if (effect == "copy")
+ return Qt::CopyAction;
+ else if (effect == "move")
+ return Qt::MoveAction;
+ else if (effect == "link")
+ return Qt::LinkAction;
+ return Qt::IgnoreAction;
+ })();
+}
+
+DragEvent::~DragEvent() = default;
+
+DragEvent::DragEvent(const DragEvent &other) = default;
+
+DragEvent::DragEvent(DragEvent &&other) = default;
+
+DragEvent &DragEvent::operator=(const DragEvent &other) = default;
+
+DragEvent &DragEvent::operator=(DragEvent &&other) = default;
+
+std::optional<DragEvent> DragEvent::fromWeb(emscripten::val event, QWindow *targetWindow)
+{
+ const auto eventType = ([&event]() -> std::optional<EventType> {
+ const auto eventTypeString = event["type"].as<std::string>();
+
+ if (eventTypeString == "dragend")
+ return EventType::DragEnd;
+ if (eventTypeString == "dragover")
+ return EventType::DragOver;
+ if (eventTypeString == "dragstart")
+ return EventType::DragStart;
+ if (eventTypeString == "drop")
+ return EventType::Drop;
+ return std::nullopt;
+ })();
+ if (!eventType)
+ return std::nullopt;
+ return DragEvent(*eventType, event, targetWindow);
+}
- return ret;
+void DragEvent::cancelDragStart()
+{
+ Q_ASSERT_X(type == EventType::DragStart, Q_FUNC_INFO, "Only supported for DragStart");
+ webEvent.call<void>("preventDefault");
+}
+
+void DragEvent::acceptDragOver()
+{
+ Q_ASSERT_X(type == EventType::DragOver, Q_FUNC_INFO, "Only supported for DragOver");
+ webEvent.call<void>("preventDefault");
+}
+
+void DragEvent::acceptDrop()
+{
+ Q_ASSERT_X(type == EventType::Drop, Q_FUNC_INFO, "Only supported for Drop");
+ webEvent.call<void>("preventDefault");
+}
+
+WheelEvent::WheelEvent(EventType type, emscripten::val event) : MouseEvent(type, event)
+{
+ deltaMode = ([event]() {
+ const int deltaMode = event["deltaMode"].as<int>();
+ const auto jsWheelEventType = emscripten::val::global("WheelEvent");
+ if (deltaMode == jsWheelEventType["DOM_DELTA_PIXEL"].as<int>())
+ return DeltaMode::Pixel;
+ else if (deltaMode == jsWheelEventType["DOM_DELTA_LINE"].as<int>())
+ return DeltaMode::Line;
+ return DeltaMode::Page;
+ })();
+
+ delta = QPointF(event["deltaX"].as<qreal>(), event["deltaY"].as<qreal>());
+
+ webkitDirectionInvertedFromDevice = event["webkitDirectionInvertedFromDevice"].as<bool>();
+}
+
+WheelEvent::~WheelEvent() = default;
+
+WheelEvent::WheelEvent(const WheelEvent &other) = default;
+
+WheelEvent::WheelEvent(WheelEvent &&other) = default;
+
+WheelEvent &WheelEvent::operator=(const WheelEvent &other) = default;
+
+WheelEvent &WheelEvent::operator=(WheelEvent &&other) = default;
+
+std::optional<WheelEvent> WheelEvent::fromWeb(emscripten::val event)
+{
+ const auto eventType = ([&event]() -> std::optional<EventType> {
+ const auto eventTypeString = event["type"].as<std::string>();
+
+ if (eventTypeString == "wheel")
+ return EventType::Wheel;
+ return std::nullopt;
+ })();
+ if (!eventType)
+ return std::nullopt;
+ return WheelEvent(*eventType, event);
}
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/wasm/qwasmevent.h b/src/plugins/platforms/wasm/qwasmevent.h
index df9276b746..6ada5393e3 100644
--- a/src/plugins/platforms/wasm/qwasmevent.h
+++ b/src/plugins/platforms/wasm/qwasmevent.h
@@ -5,11 +5,12 @@
#define QWASMEVENT_H
#include "qwasmplatform.h"
+#include "qwasmdom.h"
#include <QtCore/qglobal.h>
#include <QtCore/qnamespace.h>
#include <QtGui/qevent.h>
-
+#include <private/qstdweb_p.h>
#include <QPoint>
#include <emscripten/html5.h>
@@ -17,16 +18,29 @@
QT_BEGIN_NAMESPACE
+class QWasmDeadKeySupport;
+class QWindow;
+
enum class EventType {
+ DragEnd,
+ DragOver,
+ DragStart,
+ Drop,
+ KeyDown,
+ KeyUp,
PointerDown,
PointerMove,
PointerUp,
PointerEnter,
PointerLeave,
+ PointerCancel,
+ Wheel,
};
enum class PointerType {
Mouse,
+ Touch,
+ Pen,
Other,
};
@@ -35,6 +49,8 @@ enum class WindowArea {
Client,
};
+enum class DeltaMode { Pixel, Line, Page };
+
namespace KeyboardModifier {
namespace internal
{
@@ -107,17 +123,46 @@ QFlags<Qt::KeyboardModifier> getForEvent<EmscriptenKeyboardEvent>(
} // namespace KeyboardModifier
-struct Q_CORE_EXPORT Event
+struct Event
{
+ Event(EventType type, emscripten::val webEvent);
+ ~Event();
+ Event(const Event &other);
+ Event(Event &&other);
+ Event &operator=(const Event &other);
+ Event &operator=(Event &&other);
+
+ emscripten::val webEvent;
EventType type;
+ emscripten::val target() const { return webEvent["target"]; }
};
-struct Q_CORE_EXPORT MouseEvent : public Event
+struct KeyEvent : public Event
{
- QPoint point;
- Qt::MouseButton mouseButton;
- Qt::MouseButtons mouseButtons;
+ static std::optional<KeyEvent>
+ fromWebWithDeadKeyTranslation(emscripten::val webEvent, QWasmDeadKeySupport *deadKeySupport);
+
+ KeyEvent(EventType type, emscripten::val webEvent);
+ ~KeyEvent();
+ KeyEvent(const KeyEvent &other);
+ KeyEvent(KeyEvent &&other);
+ KeyEvent &operator=(const KeyEvent &other);
+ KeyEvent &operator=(KeyEvent &&other);
+
+ Qt::Key key;
QFlags<Qt::KeyboardModifier> modifiers;
+ bool deadKey;
+ QString text;
+};
+
+struct MouseEvent : public Event
+{
+ MouseEvent(EventType type, emscripten::val webEvent);
+ ~MouseEvent();
+ MouseEvent(const MouseEvent &other);
+ MouseEvent(MouseEvent &&other);
+ MouseEvent &operator=(const MouseEvent &other);
+ MouseEvent &operator=(MouseEvent &&other);
static constexpr Qt::MouseButton buttonFromWeb(int webButton) {
switch (webButton) {
@@ -153,14 +198,72 @@ struct Q_CORE_EXPORT MouseEvent : public Event
return QEvent::None;
}
}
+
+ QPointF localPoint;
+ QPointF pointInPage;
+ QPointF pointInViewport;
+ Qt::MouseButton mouseButton;
+ Qt::MouseButtons mouseButtons;
+ QFlags<Qt::KeyboardModifier> modifiers;
};
-struct Q_CORE_EXPORT PointerEvent : public MouseEvent
+struct PointerEvent : public MouseEvent
{
static std::optional<PointerEvent> fromWeb(emscripten::val webEvent);
+ PointerEvent(EventType type, emscripten::val webEvent);
+ ~PointerEvent();
+ PointerEvent(const PointerEvent &other);
+ PointerEvent(PointerEvent &&other);
+ PointerEvent &operator=(const PointerEvent &other);
+ PointerEvent &operator=(PointerEvent &&other);
+
PointerType pointerType;
int pointerId;
+ qreal pressure;
+ qreal tiltX;
+ qreal tiltY;
+ qreal tangentialPressure;
+ qreal twist;
+ qreal width;
+ qreal height;
+ bool isPrimary;
+};
+
+struct DragEvent : public MouseEvent
+{
+ static std::optional<DragEvent> fromWeb(emscripten::val webEvent, QWindow *targetQWindow);
+
+ DragEvent(EventType type, emscripten::val webEvent, QWindow *targetQWindow);
+ ~DragEvent();
+ DragEvent(const DragEvent &other);
+ DragEvent(DragEvent &&other);
+ DragEvent &operator=(const DragEvent &other);
+ DragEvent &operator=(DragEvent &&other);
+
+ void cancelDragStart();
+ void acceptDragOver();
+ void acceptDrop();
+
+ Qt::DropAction dropAction;
+ dom::DataTransfer dataTransfer;
+ QWindow *targetWindow;
+};
+
+struct WheelEvent : public MouseEvent
+{
+ static std::optional<WheelEvent> fromWeb(emscripten::val webEvent);
+
+ WheelEvent(EventType type, emscripten::val webEvent);
+ ~WheelEvent();
+ WheelEvent(const WheelEvent &other);
+ WheelEvent(WheelEvent &&other);
+ WheelEvent &operator=(const WheelEvent &other);
+ WheelEvent &operator=(WheelEvent &&other);
+
+ DeltaMode deltaMode;
+ bool webkitDirectionInvertedFromDevice;
+ QPointF delta;
};
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/wasm/qwasmeventdispatcher.cpp b/src/plugins/platforms/wasm/qwasmeventdispatcher.cpp
index 2fd1a30401..1f2d3095d6 100644
--- a/src/plugins/platforms/wasm/qwasmeventdispatcher.cpp
+++ b/src/plugins/platforms/wasm/qwasmeventdispatcher.cpp
@@ -2,16 +2,34 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#include "qwasmeventdispatcher.h"
+#include "qwasmintegration.h"
#include <QtGui/qpa/qwindowsysteminterface.h>
QT_BEGIN_NAMESPACE
// Note: All event dispatcher functionality is implemented in QEventDispatcherWasm
-// in QtCore, except for processWindowSystemEvents() below which uses API from QtGui.
-void QWasmEventDispatcher::processWindowSystemEvents(QEventLoop::ProcessEventsFlags flags)
+// in QtCore, except for processPostedEvents() below which uses API from QtGui.
+bool QWasmEventDispatcher::processPostedEvents()
{
- QWindowSystemInterface::sendWindowSystemEvents(flags);
+ QEventDispatcherWasm::processPostedEvents();
+ return QWindowSystemInterface::sendWindowSystemEvents(QEventLoop::AllEvents);
+}
+
+void QWasmEventDispatcher::onLoaded()
+{
+ // This function is called when the application is ready to paint
+ // the first frame. Send the qtlaoder onLoaded event first (via
+ // the base class implementation), and then enable/call requestUpdate
+ // to deliver a frame.
+ QEventDispatcherWasm::onLoaded();
+
+ // Make sure all screens have a defined size; and pick
+ // up size changes due to onLoaded event handling.
+ QWasmIntegration *wasmIntegration = QWasmIntegration::get();
+ wasmIntegration->resizeAllScreens();
+
+ wasmIntegration->releaseRequesetUpdateHold();
}
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/wasm/qwasmeventdispatcher.h b/src/plugins/platforms/wasm/qwasmeventdispatcher.h
index a28fa7263b..cbf10482e3 100644
--- a/src/plugins/platforms/wasm/qwasmeventdispatcher.h
+++ b/src/plugins/platforms/wasm/qwasmeventdispatcher.h
@@ -11,7 +11,8 @@ QT_BEGIN_NAMESPACE
class QWasmEventDispatcher : public QEventDispatcherWasm
{
protected:
- void processWindowSystemEvents(QEventLoop::ProcessEventsFlags flags) override;
+ bool processPostedEvents() override;
+ void onLoaded() override;
};
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/wasm/qwasmeventtranslator.cpp b/src/plugins/platforms/wasm/qwasmeventtranslator.cpp
deleted file mode 100644
index 01f15e7dff..0000000000
--- a/src/plugins/platforms/wasm/qwasmeventtranslator.cpp
+++ /dev/null
@@ -1,360 +0,0 @@
-// Copyright (C) 2018 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
-
-#include "qwasmeventtranslator.h"
-#include "qwasmeventdispatcher.h"
-#include "qwasmcompositor.h"
-#include "qwasmintegration.h"
-#include "qwasmclipboard.h"
-#include "qwasmstring.h"
-#include "qwasmcursor.h"
-#include <QtGui/qevent.h>
-#include <qpa/qwindowsysteminterface.h>
-#include <QtCore/qcoreapplication.h>
-#include <QtCore/qglobal.h>
-#include <QtCore/qobject.h>
-
-#include <QtCore/qdeadlinetimer.h>
-#include <private/qmakearray_p.h>
-#include <QtCore/qnamespace.h>
-#include <QCursor>
-#include <QtCore/private/qstringiterator_p.h>
-
-#include <emscripten/bind.h>
-
-#include <iostream>
-
-QT_BEGIN_NAMESPACE
-
-using namespace emscripten;
-
-namespace {
-constexpr std::string_view WebDeadKeyValue = "Dead";
-
-struct Emkb2QtData
-{
- static constexpr char StringTerminator = '\0';
-
- const char *em;
- unsigned int qt;
-
- constexpr bool operator<=(const Emkb2QtData &that) const noexcept
- {
- return !(strcmp(that) > 0);
- }
-
- bool operator<(const Emkb2QtData &that) const noexcept { return ::strcmp(em, that.em) < 0; }
-
- constexpr bool operator==(const Emkb2QtData &that) const noexcept { return strcmp(that) == 0; }
-
- constexpr int strcmp(const Emkb2QtData &that, const int i = 0) const
- {
- return em[i] == StringTerminator && that.em[i] == StringTerminator ? 0
- : em[i] == StringTerminator ? -1
- : that.em[i] == StringTerminator ? 1
- : em[i] < that.em[i] ? -1
- : em[i] > that.em[i] ? 1
- : strcmp(that, i + 1);
- }
-};
-
-template<unsigned int Qt, char ... EmChar>
-struct Emkb2Qt
-{
- static constexpr const char storage[sizeof ... (EmChar) + 1] = {EmChar..., '\0'};
- using Type = Emkb2QtData;
- static constexpr Type data() noexcept { return Type{storage, Qt}; }
-};
-
-template<unsigned int Qt, char ... EmChar> constexpr char Emkb2Qt<Qt, EmChar...>::storage[];
-
-static constexpr const auto WebToQtKeyCodeMappings = qMakeArray(
- QSortedData<
- Emkb2Qt< Qt::Key_Escape, 'E','s','c','a','p','e' >,
- Emkb2Qt< Qt::Key_Tab, 'T','a','b' >,
- Emkb2Qt< Qt::Key_Backspace, 'B','a','c','k','s','p','a','c','e' >,
- Emkb2Qt< Qt::Key_Return, 'E','n','t','e','r' >,
- Emkb2Qt< Qt::Key_Insert, 'I','n','s','e','r','t' >,
- Emkb2Qt< Qt::Key_Delete, 'D','e','l','e','t','e' >,
- Emkb2Qt< Qt::Key_Pause, 'P','a','u','s','e' >,
- Emkb2Qt< Qt::Key_Pause, 'C','l','e','a','r' >,
- Emkb2Qt< Qt::Key_Home, 'H','o','m','e' >,
- Emkb2Qt< Qt::Key_End, 'E','n','d' >,
- Emkb2Qt< Qt::Key_Left, 'A','r','r','o','w','L','e','f','t' >,
- Emkb2Qt< Qt::Key_Up, 'A','r','r','o','w','U','p' >,
- Emkb2Qt< Qt::Key_Right, 'A','r','r','o','w','R','i','g','h','t' >,
- Emkb2Qt< Qt::Key_Down, 'A','r','r','o','w','D','o','w','n' >,
- Emkb2Qt< Qt::Key_PageUp, 'P','a','g','e','U','p' >,
- Emkb2Qt< Qt::Key_PageDown, 'P','a','g','e','D','o','w','n' >,
- Emkb2Qt< Qt::Key_Shift, 'S','h','i','f','t' >,
- Emkb2Qt< Qt::Key_Control, 'C','o','n','t','r','o','l' >,
- Emkb2Qt< Qt::Key_Meta, 'M','e','t','a'>,
- Emkb2Qt< Qt::Key_Meta, 'O','S'>,
- Emkb2Qt< Qt::Key_Alt, 'A','l','t','L','e','f','t' >,
- Emkb2Qt< Qt::Key_Alt, 'A','l','t' >,
- Emkb2Qt< Qt::Key_CapsLock, 'C','a','p','s','L','o','c','k' >,
- Emkb2Qt< Qt::Key_NumLock, 'N','u','m','L','o','c','k' >,
- Emkb2Qt< Qt::Key_ScrollLock, 'S','c','r','o','l','l','L','o','c','k' >,
- Emkb2Qt< Qt::Key_F1, 'F','1' >,
- Emkb2Qt< Qt::Key_F2, 'F','2' >,
- Emkb2Qt< Qt::Key_F3, 'F','3' >,
- Emkb2Qt< Qt::Key_F4, 'F','4' >,
- Emkb2Qt< Qt::Key_F5, 'F','5' >,
- Emkb2Qt< Qt::Key_F6, 'F','6' >,
- Emkb2Qt< Qt::Key_F7, 'F','7' >,
- Emkb2Qt< Qt::Key_F8, 'F','8' >,
- Emkb2Qt< Qt::Key_F9, 'F','9' >,
- Emkb2Qt< Qt::Key_F10, 'F','1','0' >,
- Emkb2Qt< Qt::Key_F11, 'F','1','1' >,
- Emkb2Qt< Qt::Key_F12, 'F','1','2' >,
- Emkb2Qt< Qt::Key_F13, 'F','1','3' >,
- Emkb2Qt< Qt::Key_F14, 'F','1','4' >,
- Emkb2Qt< Qt::Key_F15, 'F','1','5' >,
- Emkb2Qt< Qt::Key_F16, 'F','1','6' >,
- Emkb2Qt< Qt::Key_F17, 'F','1','7' >,
- Emkb2Qt< Qt::Key_F18, 'F','1','8' >,
- Emkb2Qt< Qt::Key_F19, 'F','1','9' >,
- Emkb2Qt< Qt::Key_F20, 'F','2','0' >,
- Emkb2Qt< Qt::Key_F21, 'F','2','1' >,
- Emkb2Qt< Qt::Key_F22, 'F','2','2' >,
- Emkb2Qt< Qt::Key_F23, 'F','2','3' >,
- Emkb2Qt< Qt::Key_Paste, 'P','a','s','t','e' >,
- Emkb2Qt< Qt::Key_AltGr, 'A','l','t','R','i','g','h','t' >,
- Emkb2Qt< Qt::Key_Help, 'H','e','l','p' >,
- Emkb2Qt< Qt::Key_yen, 'I','n','t','l','Y','e','n' >,
- Emkb2Qt< Qt::Key_Menu, 'C','o','n','t','e','x','t','M','e','n','u' >
- >::Data{}
- );
-
-static constexpr const auto WebToQtKeyCodeMappingsWithShift = qMakeArray(
- QSortedData<
- // shifted
- Emkb2Qt< Qt::Key_Agrave, '\xc3','\x80' >,
- Emkb2Qt< Qt::Key_Aacute, '\xc3','\x81' >,
- Emkb2Qt< Qt::Key_Acircumflex, '\xc3','\x82' >,
- Emkb2Qt< Qt::Key_Adiaeresis, '\xc3','\x84' >,
- Emkb2Qt< Qt::Key_AE, '\xc3','\x86' >,
- Emkb2Qt< Qt::Key_Atilde, '\xc3','\x83' >,
- Emkb2Qt< Qt::Key_Aring, '\xc3','\x85' >,
- Emkb2Qt< Qt::Key_Egrave, '\xc3','\x88' >,
- Emkb2Qt< Qt::Key_Eacute, '\xc3','\x89' >,
- Emkb2Qt< Qt::Key_Ecircumflex, '\xc3','\x8a' >,
- Emkb2Qt< Qt::Key_Ediaeresis, '\xc3','\x8b' >,
- Emkb2Qt< Qt::Key_Iacute, '\xc3','\x8d' >,
- Emkb2Qt< Qt::Key_Icircumflex, '\xc3','\x8e' >,
- Emkb2Qt< Qt::Key_Idiaeresis, '\xc3','\x8f' >,
- Emkb2Qt< Qt::Key_Igrave, '\xc3','\x8c' >,
- Emkb2Qt< Qt::Key_Ocircumflex, '\xc3','\x94' >,
- Emkb2Qt< Qt::Key_Odiaeresis, '\xc3','\x96' >,
- Emkb2Qt< Qt::Key_Ograve, '\xc3','\x92' >,
- Emkb2Qt< Qt::Key_Oacute, '\xc3','\x93' >,
- Emkb2Qt< Qt::Key_Ooblique, '\xc3','\x98' >,
- Emkb2Qt< Qt::Key_Otilde, '\xc3','\x95' >,
- Emkb2Qt< Qt::Key_Ucircumflex, '\xc3','\x9b' >,
- Emkb2Qt< Qt::Key_Udiaeresis, '\xc3','\x9c' >,
- Emkb2Qt< Qt::Key_Ugrave, '\xc3','\x99' >,
- Emkb2Qt< Qt::Key_Uacute, '\xc3','\x9a' >,
- Emkb2Qt< Qt::Key_Ntilde, '\xc3','\x91' >,
- Emkb2Qt< Qt::Key_Ccedilla, '\xc3','\x87' >,
- Emkb2Qt< Qt::Key_ydiaeresis, '\xc3','\x8f' >,
- Emkb2Qt< Qt::Key_Yacute, '\xc3','\x9d' >
- >::Data{}
-);
-
-std::optional<Qt::Key> findMappingByBisection(const char *toFind)
-{
- const Emkb2QtData searchKey{ toFind, 0 };
- const auto it = std::lower_bound(WebToQtKeyCodeMappings.cbegin(), WebToQtKeyCodeMappings.cend(),
- searchKey);
- return it != WebToQtKeyCodeMappings.cend() && searchKey == *it ? static_cast<Qt::Key>(it->qt)
- : std::optional<Qt::Key>();
-}
-
-bool isDeadKeyEvent(const EmscriptenKeyboardEvent *emKeyEvent)
-{
- return qstrncmp(emKeyEvent->key, WebDeadKeyValue.data(), WebDeadKeyValue.size()) == 0;
-}
-
-Qt::Key translateEmscriptKey(const EmscriptenKeyboardEvent *emscriptKey)
-{
- if (isDeadKeyEvent(emscriptKey)) {
- if (auto mapping = findMappingByBisection(emscriptKey->code))
- return *mapping;
- }
- if (auto mapping = findMappingByBisection(emscriptKey->key))
- return *mapping;
-
- // cast to unicode key
- QString str = QString::fromUtf8(emscriptKey->key).toUpper();
- QStringIterator i(str);
- return static_cast<Qt::Key>(i.next(0));
-}
-
-struct KeyMapping { Qt::Key from, to; };
-
-constexpr KeyMapping tildeKeyTable[] = { // ~
- { Qt::Key_A, Qt::Key_Atilde },
- { Qt::Key_N, Qt::Key_Ntilde },
- { Qt::Key_O, Qt::Key_Otilde },
-};
-constexpr KeyMapping graveKeyTable[] = { // `
- { Qt::Key_A, Qt::Key_Agrave },
- { Qt::Key_E, Qt::Key_Egrave },
- { Qt::Key_I, Qt::Key_Igrave },
- { Qt::Key_O, Qt::Key_Ograve },
- { Qt::Key_U, Qt::Key_Ugrave },
-};
-constexpr KeyMapping acuteKeyTable[] = { // '
- { Qt::Key_A, Qt::Key_Aacute },
- { Qt::Key_E, Qt::Key_Eacute },
- { Qt::Key_I, Qt::Key_Iacute },
- { Qt::Key_O, Qt::Key_Oacute },
- { Qt::Key_U, Qt::Key_Uacute },
- { Qt::Key_Y, Qt::Key_Yacute },
-};
-constexpr KeyMapping diaeresisKeyTable[] = { // umlaut ¨
- { Qt::Key_A, Qt::Key_Adiaeresis },
- { Qt::Key_E, Qt::Key_Ediaeresis },
- { Qt::Key_I, Qt::Key_Idiaeresis },
- { Qt::Key_O, Qt::Key_Odiaeresis },
- { Qt::Key_U, Qt::Key_Udiaeresis },
- { Qt::Key_Y, Qt::Key_ydiaeresis },
-};
-constexpr KeyMapping circumflexKeyTable[] = { // ^
- { Qt::Key_A, Qt::Key_Acircumflex },
- { Qt::Key_E, Qt::Key_Ecircumflex },
- { Qt::Key_I, Qt::Key_Icircumflex },
- { Qt::Key_O, Qt::Key_Ocircumflex },
- { Qt::Key_U, Qt::Key_Ucircumflex },
-};
-
-static Qt::Key find_impl(const KeyMapping *first, const KeyMapping *last, Qt::Key key) noexcept
-{
- while (first != last) {
- if (first->from == key)
- return first->to;
- ++first;
- }
- return Qt::Key_unknown;
-}
-
-template <size_t N>
-static Qt::Key find(const KeyMapping (&map)[N], Qt::Key key) noexcept
-{
- return find_impl(map, map + N, key);
-}
-
-Qt::Key translateBaseKeyUsingDeadKey(Qt::Key accentBaseKey, Qt::Key deadKey)
-{
- switch (deadKey) {
- case Qt::Key_QuoteLeft: {
- // ` macOS: Key_Dead_Grave
- return platform() == Platform::MacOS ? find(graveKeyTable, accentBaseKey)
- : find(diaeresisKeyTable, accentBaseKey);
- }
- case Qt::Key_O: // ´ Key_Dead_Grave
- return find(graveKeyTable, accentBaseKey);
- case Qt::Key_E: // ´ Key_Dead_Acute
- return find(acuteKeyTable, accentBaseKey);
- case Qt::Key_AsciiTilde:
- case Qt::Key_N: // Key_Dead_Tilde
- return find(tildeKeyTable, accentBaseKey);
- case Qt::Key_U: // ¨ Key_Dead_Diaeresis
- return find(diaeresisKeyTable, accentBaseKey);
- case Qt::Key_I: // macOS Key_Dead_Circumflex
- case Qt::Key_6: // linux
- case Qt::Key_Apostrophe: // linux
- return find(circumflexKeyTable, accentBaseKey);
- default:
- return Qt::Key_unknown;
- };
-}
-
-template<class T>
-std::optional<QString> findKeyTextByKeyId(const T &mappingArray, Qt::Key qtKey)
-{
- const auto it = std::find_if(mappingArray.cbegin(), mappingArray.cend(),
- [qtKey](const Emkb2QtData &data) { return data.qt == qtKey; });
- return it != mappingArray.cend() ? it->em : std::optional<QString>();
-}
-} // namespace
-
-QWasmEventTranslator::QWasmEventTranslator() = default;
-
-QWasmEventTranslator::~QWasmEventTranslator() = default;
-
-QCursor QWasmEventTranslator::cursorForMode(QWasmCompositor::ResizeMode m)
-{
- switch (m) {
- case QWasmCompositor::ResizeTopLeft:
- case QWasmCompositor::ResizeBottomRight:
- return Qt::SizeFDiagCursor;
- case QWasmCompositor::ResizeBottomLeft:
- case QWasmCompositor::ResizeTopRight:
- return Qt::SizeBDiagCursor;
- case QWasmCompositor::ResizeTop:
- case QWasmCompositor::ResizeBottom:
- return Qt::SizeVerCursor;
- case QWasmCompositor::ResizeLeft:
- case QWasmCompositor::ResizeRight:
- return Qt::SizeHorCursor;
- case QWasmCompositor::ResizeNone:
- return Qt::ArrowCursor;
- }
- return Qt::ArrowCursor;
-}
-
-QWasmEventTranslator::TranslatedEvent
-QWasmEventTranslator::translateKeyEvent(int emEventType, const EmscriptenKeyboardEvent *keyEvent)
-{
- TranslatedEvent ret;
- switch (emEventType) {
- case EMSCRIPTEN_EVENT_KEYDOWN:
- ret.type = QEvent::KeyPress;
- break;
- case EMSCRIPTEN_EVENT_KEYUP:
- ret.type = QEvent::KeyRelease;
- break;
- default:
- // Should not be reached - do not call with this event type.
- Q_ASSERT(false);
- break;
- };
-
- ret.key = translateEmscriptKey(keyEvent);
-
- if (isDeadKeyEvent(keyEvent) || ret.key == Qt::Key_AltGr) {
- if (keyEvent->shiftKey && ret.key == Qt::Key_QuoteLeft)
- ret.key = Qt::Key_AsciiTilde;
- m_emDeadKey = ret.key;
- }
-
- if (m_emDeadKey != Qt::Key_unknown
- && (m_keyModifiedByDeadKeyOnPress == Qt::Key_unknown
- || ret.key == m_keyModifiedByDeadKeyOnPress)) {
- const Qt::Key baseKey = ret.key;
- const Qt::Key translatedKey = translateBaseKeyUsingDeadKey(baseKey, m_emDeadKey);
- if (translatedKey != Qt::Key_unknown)
- ret.key = translatedKey;
-
- if (auto text = keyEvent->shiftKey
- ? findKeyTextByKeyId(WebToQtKeyCodeMappingsWithShift, ret.key)
- : findKeyTextByKeyId(WebToQtKeyCodeMappings, ret.key)) {
- if (ret.type == QEvent::KeyPress) {
- Q_ASSERT(m_keyModifiedByDeadKeyOnPress == Qt::Key_unknown);
- m_keyModifiedByDeadKeyOnPress = baseKey;
- } else {
- Q_ASSERT(ret.type == QEvent::KeyRelease);
- Q_ASSERT(m_keyModifiedByDeadKeyOnPress == baseKey);
- m_keyModifiedByDeadKeyOnPress = Qt::Key_unknown;
- m_emDeadKey = Qt::Key_unknown;
- }
- ret.text = *text;
- return ret;
- }
- }
- ret.text = QString::fromUtf8(keyEvent->key);
- return ret;
-}
-
-QT_END_NAMESPACE
diff --git a/src/plugins/platforms/wasm/qwasmeventtranslator.h b/src/plugins/platforms/wasm/qwasmeventtranslator.h
deleted file mode 100644
index 8f4e9f6d05..0000000000
--- a/src/plugins/platforms/wasm/qwasmeventtranslator.h
+++ /dev/null
@@ -1,48 +0,0 @@
-// Copyright (C) 2018 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
-
-#ifndef QWASMEVENTTRANSLATOR_H
-#define QWASMEVENTTRANSLATOR_H
-
-#include <QtCore/qobject.h>
-#include <QtCore/qrect.h>
-#include <QtCore/qpoint.h>
-#include <emscripten/html5.h>
-#include "qwasmwindow.h"
-#include <QtGui/qinputdevice.h>
-#include <QHash>
-#include <QCursor>
-#include "qwasmevent.h"
-#include "qwasmplatform.h"
-
-QT_BEGIN_NAMESPACE
-
-class QWindow;
-
-class QWasmEventTranslator : public QObject
-{
- Q_OBJECT
-
-public:
- struct TranslatedEvent
- {
- QEvent::Type type;
- Qt::Key key;
- QString text;
- };
- explicit QWasmEventTranslator();
- ~QWasmEventTranslator();
-
- static QCursor cursorForMode(QWasmCompositor::ResizeMode mode);
-
- TranslatedEvent translateKeyEvent(int emEventType, const EmscriptenKeyboardEvent *keyEvent);
-
-private:
- static quint64 getTimestamp();
-
- Qt::Key m_emDeadKey = Qt::Key_unknown;
- Qt::Key m_keyModifiedByDeadKeyOnPress = Qt::Key_unknown;
-};
-
-QT_END_NAMESPACE
-#endif // QWASMEVENTTRANSLATOR_H
diff --git a/src/plugins/platforms/wasm/qwasmfontdatabase.cpp b/src/plugins/platforms/wasm/qwasmfontdatabase.cpp
index 7f90986121..3f3dc10f71 100644
--- a/src/plugins/platforms/wasm/qwasmfontdatabase.cpp
+++ b/src/plugins/platforms/wasm/qwasmfontdatabase.cpp
@@ -2,22 +2,271 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#include "qwasmfontdatabase.h"
+#include "qwasmintegration.h"
#include <QtCore/qfile.h>
+#include <QtCore/private/qstdweb_p.h>
+#include <QtCore/private/qeventdispatcher_wasm_p.h>
+#include <QtGui/private/qguiapplication_p.h>
+
+#include <emscripten.h>
+#include <emscripten/val.h>
+#include <emscripten/bind.h>
+
+#include <map>
+#include <array>
QT_BEGIN_NAMESPACE
+using namespace emscripten;
using namespace Qt::StringLiterals;
-void QWasmFontDatabase::populateFontDatabase()
+
+namespace {
+
+class FontData
+{
+public:
+ FontData(val fontData)
+ :m_fontData(fontData) {}
+
+ QString family() const
+ {
+ return QString::fromStdString(m_fontData["family"].as<std::string>());
+ }
+
+ QString fullName() const
+ {
+ return QString::fromStdString(m_fontData["fullName"].as<std::string>());
+ }
+
+ QString postscriptName() const
+ {
+ return QString::fromStdString(m_fontData["postscriptName"].as<std::string>());
+ }
+
+ QString style() const
+ {
+ return QString::fromStdString(m_fontData["style"].as<std::string>());
+ }
+
+ val value() const
+ {
+ return m_fontData;
+ }
+
+private:
+ val m_fontData;
+};
+
+val makeObject(const char *key, const char *value)
+{
+ val obj = val::object();
+ obj.set(key, std::string(value));
+ return obj;
+}
+
+void printError(val err) {
+ qCWarning(lcQpaFonts)
+ << QString::fromStdString(err["name"].as<std::string>())
+ << QString::fromStdString(err["message"].as<std::string>());
+ QWasmFontDatabase::endAllFontFileLoading();
+}
+
+void checkFontAccessPermitted(std::function<void(bool)> callback)
+{
+ const val permissions = val::global("navigator")["permissions"];
+ if (permissions.isUndefined()) {
+ callback(false);
+ return;
+ }
+
+ qstdweb::Promise::make(permissions, "query", {
+ .thenFunc = [callback](val status) {
+ callback(status["state"].as<std::string>() == "granted");
+ },
+ }, makeObject("name", "local-fonts"));
+}
+
+void queryLocalFonts(std::function<void(const QList<FontData> &)> callback)
+{
+ emscripten::val window = emscripten::val::global("window");
+ qstdweb::Promise::make(window, "queryLocalFonts", {
+ .thenFunc = [callback](emscripten::val fontArray) {
+ QList<FontData> fonts;
+ const int count = fontArray["length"].as<int>();
+ fonts.reserve(count);
+ for (int i = 0; i < count; ++i)
+ fonts.append(FontData(fontArray.call<emscripten::val>("at", i)));
+ callback(fonts);
+ },
+ .catchFunc = printError
+ });
+}
+
+void readBlob(val blob, std::function<void(const QByteArray &)> callback)
+{
+ qstdweb::Promise::make(blob, "arrayBuffer", {
+ .thenFunc = [callback](emscripten::val fontArrayBuffer) {
+ QByteArray fontData = qstdweb::Uint8Array(qstdweb::ArrayBuffer(fontArrayBuffer)).copyToQByteArray();
+ callback(fontData);
+ },
+ .catchFunc = printError
+ });
+}
+
+void readFont(FontData font, std::function<void(const QByteArray &)> callback)
+{
+ qstdweb::Promise::make(font.value(), "blob", {
+ .thenFunc = [callback](val blob) {
+ readBlob(blob, [callback](const QByteArray &data) {
+ callback(data);
+ });
+ },
+ .catchFunc = printError
+ });
+}
+
+emscripten::val getLocalFontsConfigProperty(const char *name) {
+ emscripten::val qt = val::module_property("qt");
+ if (qt.isUndefined())
+ return emscripten::val();
+ emscripten::val localFonts = qt["localFonts"];
+ if (localFonts.isUndefined())
+ return emscripten::val();
+ return localFonts[name];
+};
+
+bool getLocalFontsBoolConfigPropertyWithDefault(const char *name, bool defaultValue) {
+ emscripten::val prop = getLocalFontsConfigProperty(name);
+ if (prop.isUndefined())
+ return defaultValue;
+ return prop.as<bool>();
+};
+
+QString getLocalFontsStringConfigPropertyWithDefault(const char *name, QString defaultValue) {
+ emscripten::val prop = getLocalFontsConfigProperty(name);
+ if (prop.isUndefined())
+ return defaultValue;
+ return QString::fromStdString(prop.as<std::string>());
+};
+
+QStringList getLocalFontsStringListConfigPropertyWithDefault(const char *name, QStringList defaultValue) {
+ emscripten::val array = getLocalFontsConfigProperty(name);
+ if (array.isUndefined())
+ return defaultValue;
+
+ QStringList list;
+ int size = array["length"].as<int>();
+ for (int i = 0; i < size; ++i) {
+ emscripten::val element = array.call<emscripten::val>("at", i);
+ QString string = QString::fromStdString(element.as<std::string>());
+ if (!string.isEmpty())
+ list.append(string);
+ }
+ return list;
+};
+
+} // namespace
+
+QWasmFontDatabase::QWasmFontDatabase()
+:QFreeTypeFontDatabase()
+{
+ m_localFontsApiSupported = val::global("window")["queryLocalFonts"].isUndefined() == false;
+ if (m_localFontsApiSupported)
+ beginFontDatabaseStartupTask();
+}
+
+QWasmFontDatabase *QWasmFontDatabase::get()
+{
+ return static_cast<QWasmFontDatabase *>(QWasmIntegration::get()->fontDatabase());
+}
+
+// Populates the font database with local fonts. Will make the browser ask
+// the user for permission if needed. Does nothing if the Local Font Access API
+// is not supported.
+void QWasmFontDatabase::populateLocalfonts()
{
- // Load font file from resources. Currently
- // all fonts needs to be bundled with the nexe
- // as Qt resources.
+ // Decide which font families to populate based on user preferences
+ QStringList selectedLocalFontFamilies;
+ bool allFamilies = false;
+ switch (m_localFontFamilyLoadSet) {
+ case NoFontFamilies:
+ default:
+ // keep empty selectedLocalFontFamilies
+ break;
+ case DefaultFontFamilies: {
+ const QStringList webSafeFontFamilies =
+ {"Arial", "Verdana", "Tahoma", "Trebuchet", "Times New Roman",
+ "Georgia", "Garamond", "Courier New"};
+ selectedLocalFontFamilies = webSafeFontFamilies;
+ } break;
+ case AllFontFamilies:
+ allFamilies = true;
+ break;
+ }
+
+ selectedLocalFontFamilies += m_extraLocalFontFamilies;
+
+ if (selectedLocalFontFamilies.isEmpty() && !allFamilies) {
+ endAllFontFileLoading();
+ return;
+ }
+
+ populateLocalFontFamilies(selectedLocalFontFamilies, allFamilies);
+}
+
+namespace {
+ QStringList toStringList(emscripten::val array)
+ {
+ QStringList list;
+ int size = array["length"].as<int>();
+ for (int i = 0; i < size; ++i) {
+ emscripten::val element = array.call<emscripten::val>("at", i);
+ QString string = QString::fromStdString(element.as<std::string>());
+ if (!string.isEmpty())
+ list.append(string);
+ }
+ return list;
+ }
+}
+
+void QWasmFontDatabase::populateLocalFontFamilies(emscripten::val families)
+{
+ if (!m_localFontsApiSupported)
+ return;
+ populateLocalFontFamilies(toStringList(families), false);
+}
+
+void QWasmFontDatabase::populateLocalFontFamilies(const QStringList &fontFamilies, bool allFamilies)
+{
+ queryLocalFonts([fontFamilies, allFamilies](const QList<FontData> &fonts) {
+ refFontFileLoading();
+ QList<FontData> filteredFonts;
+ std::copy_if(fonts.begin(), fonts.end(), std::back_inserter(filteredFonts),
+ [fontFamilies, allFamilies](FontData fontData) {
+ return allFamilies || fontFamilies.contains(fontData.family());
+ });
+
+ for (const FontData &font: filteredFonts) {
+ refFontFileLoading();
+ readFont(font, [font](const QByteArray &fontData){
+ QFreeTypeFontDatabase::registerFontFamily(font.family());
+ QFreeTypeFontDatabase::addTTFile(fontData, QByteArray());
+ derefFontFileLoading();
+ });
+ }
+ derefFontFileLoading();
+ });
+
+}
+
+void QWasmFontDatabase::populateFontDatabase()
+{
+ // Load bundled font file from resources.
const QString fontFileNames[] = {
QStringLiteral(":/fonts/DejaVuSansMono.ttf"),
- QStringLiteral(":/fonts/Vera.ttf"),
QStringLiteral(":/fonts/DejaVuSans.ttf"),
};
for (const QString &fontFileName : fontFileNames) {
@@ -27,11 +276,45 @@ void QWasmFontDatabase::populateFontDatabase()
QFreeTypeFontDatabase::addTTFile(theFont.readAll(), fontFileName.toLatin1());
}
+
+ // Get config options for controlling local fonts usage
+ m_queryLocalFontsPermission = getLocalFontsBoolConfigPropertyWithDefault("requestPermission", false);
+ QString fontFamilyLoadSet = getLocalFontsStringConfigPropertyWithDefault("familiesCollection", "DefaultFontFamilies");
+ m_extraLocalFontFamilies = getLocalFontsStringListConfigPropertyWithDefault("extraFamilies", QStringList());
+
+ if (fontFamilyLoadSet == "NoFontFamilies") {
+ m_localFontFamilyLoadSet = NoFontFamilies;
+ } else if (fontFamilyLoadSet == "DefaultFontFamilies") {
+ m_localFontFamilyLoadSet = DefaultFontFamilies;
+ } else if (fontFamilyLoadSet == "AllFontFamilies") {
+ m_localFontFamilyLoadSet = AllFontFamilies;
+ } else {
+ m_localFontFamilyLoadSet = NoFontFamilies;
+ qWarning() << "Unknown fontFamilyLoadSet value" << fontFamilyLoadSet;
+ }
+
+ if (!m_localFontsApiSupported)
+ return;
+
+ // Populate the font database with local fonts. Either try unconditianlly
+ // if displyaing a fonts permissions dialog at startup is allowed, or else
+ // only if we already have permission.
+ if (m_queryLocalFontsPermission) {
+ populateLocalfonts();
+ } else {
+ checkFontAccessPermitted([this](bool granted) {
+ if (granted)
+ populateLocalfonts();
+ else
+ endAllFontFileLoading();
+ });
+ }
}
QFontEngine *QWasmFontDatabase::fontEngine(const QFontDef &fontDef, void *handle)
{
- return QFreeTypeFontDatabase::fontEngine(fontDef, handle);
+ QFontEngine *fontEngine = QFreeTypeFontDatabase::fontEngine(fontDef, handle);
+ return fontEngine;
}
QStringList QWasmFontDatabase::fallbacksForFamily(const QString &family, QFont::Style style,
@@ -41,11 +324,13 @@ QStringList QWasmFontDatabase::fallbacksForFamily(const QString &family, QFont::
QStringList fallbacks
= QFreeTypeFontDatabase::fallbacksForFamily(family, style, styleHint, script);
- // Add the vera.ttf font (loaded in populateFontDatabase above) as a falback font
+ // Add the DejaVuSans.ttf font (loaded in populateFontDatabase above) as a falback font
// to all other fonts (except itself).
- const QString veraFontFamily = QStringLiteral("Bitstream Vera Sans");
- if (family != veraFontFamily)
- fallbacks.append(veraFontFamily);
+ static const QString wasmFallbackFonts[] = { "DejaVu Sans" };
+ for (auto wasmFallbackFont : wasmFallbackFonts) {
+ if (family != wasmFallbackFont && !fallbacks.contains(wasmFallbackFont))
+ fallbacks.append(wasmFallbackFont);
+ }
return fallbacks;
}
@@ -57,7 +342,63 @@ void QWasmFontDatabase::releaseHandle(void *handle)
QFont QWasmFontDatabase::defaultFont() const
{
- return QFont("Bitstream Vera Sans"_L1);
+ return QFont("DejaVu Sans"_L1);
+}
+
+namespace {
+ int g_pendingFonts = 0;
+ bool g_fontStartupTaskCompleted = false;
+}
+
+// Registers font loading as a startup task, which makes Qt delay
+// sending onLoaded event until font loading has completed.
+void QWasmFontDatabase::beginFontDatabaseStartupTask()
+{
+ g_fontStartupTaskCompleted = false;
+ QEventDispatcherWasm::registerStartupTask();
+}
+
+// Ends the font loading startup task.
+void QWasmFontDatabase::endFontDatabaseStartupTask()
+{
+ if (!g_fontStartupTaskCompleted) {
+ g_fontStartupTaskCompleted = true;
+ QEventDispatcherWasm::completeStarupTask();
+ }
+}
+
+// Registers that a font file will be loaded.
+void QWasmFontDatabase::refFontFileLoading()
+{
+ g_pendingFonts += 1;
+}
+
+// Registers that one font file has been loaded, and sends notifactions
+// when all pending font files have been loaded.
+void QWasmFontDatabase::derefFontFileLoading()
+{
+ if (--g_pendingFonts <= 0) {
+ QFontCache::instance()->clear();
+ emit qGuiApp->fontDatabaseChanged();
+ endFontDatabaseStartupTask();
+ }
+}
+
+// Unconditionally ends local font loading, for instance if there
+// are no fonts to load or if there was an unexpected error.
+void QWasmFontDatabase::endAllFontFileLoading()
+{
+ bool hadPandingfonts = g_pendingFonts > 0;
+ if (hadPandingfonts) {
+ // The hadPandingfonts counter might no longer be correct; disable counting
+ // and send notifications unconditionally.
+ g_pendingFonts = 0;
+ QFontCache::instance()->clear();
+ emit qGuiApp->fontDatabaseChanged();
+ }
+
+ endFontDatabaseStartupTask();
}
+
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/wasm/qwasmfontdatabase.h b/src/plugins/platforms/wasm/qwasmfontdatabase.h
index ccb71b8e0b..a1c8f1ff48 100644
--- a/src/plugins/platforms/wasm/qwasmfontdatabase.h
+++ b/src/plugins/platforms/wasm/qwasmfontdatabase.h
@@ -6,11 +6,16 @@
#include <QtGui/private/qfreetypefontdatabase_p.h>
+#include <emscripten/val.h>
+
QT_BEGIN_NAMESPACE
class QWasmFontDatabase : public QFreeTypeFontDatabase
{
public:
+ QWasmFontDatabase();
+ static QWasmFontDatabase *get();
+
void populateFontDatabase() override;
QFontEngine *fontEngine(const QFontDef &fontDef, void *handle) override;
QStringList fallbacksForFamily(const QString &family, QFont::Style style,
@@ -18,6 +23,27 @@ public:
QChar::Script script) const override;
void releaseHandle(void *handle) override;
QFont defaultFont() const override;
+
+ void populateLocalfonts();
+ void populateLocalFontFamilies(emscripten::val families);
+ void populateLocalFontFamilies(const QStringList &famliies, bool allFamilies);
+
+ static void beginFontDatabaseStartupTask();
+ static void endFontDatabaseStartupTask();
+ static void refFontFileLoading();
+ static void derefFontFileLoading();
+ static void endAllFontFileLoading();
+
+private:
+ bool m_localFontsApiSupported = false;
+ bool m_queryLocalFontsPermission = false;
+ enum FontFamilyLoadSet {
+ NoFontFamilies,
+ DefaultFontFamilies,
+ AllFontFamilies,
+ };
+ FontFamilyLoadSet m_localFontFamilyLoadSet;
+ QStringList m_extraLocalFontFamilies;
};
QT_END_NAMESPACE
#endif
diff --git a/src/plugins/platforms/wasm/qwasminputcontext.cpp b/src/plugins/platforms/wasm/qwasminputcontext.cpp
index 9da0a9dc34..92fc1767d1 100644
--- a/src/plugins/platforms/wasm/qwasminputcontext.cpp
+++ b/src/plugins/platforms/wasm/qwasminputcontext.cpp
@@ -5,9 +5,9 @@
#include "qwasminputcontext.h"
#include "qwasmintegration.h"
+#include "qwasmplatform.h"
#include <QRectF>
#include <qpa/qplatforminputcontext.h>
-#include "qwasmeventtranslator.h"
#include "qwasmscreen.h"
#include <qguiapplication.h>
#include <qwindow.h>
@@ -24,14 +24,13 @@ static void inputCallback(emscripten::val event)
if (length <= 0)
return;
- // use only last character
- emscripten::val _incomingCharVal = event["target"]["value"][length - 1];
+ emscripten::val _incomingCharVal = event["data"];
if (_incomingCharVal != emscripten::val::undefined() && _incomingCharVal != emscripten::val::null()) {
QString str = QString::fromStdString(_incomingCharVal.as<std::string>());
QWasmInputContext *wasmInput =
- reinterpret_cast<QWasmInputContext*>(event["target"]["data-context"].as<quintptr>());
- wasmInput->inputStringChanged(str, wasmInput);
+ reinterpret_cast<QWasmInputContext*>(event["target"]["data-qinputcontext"].as<quintptr>());
+ wasmInput->inputStringChanged(str, EMSCRIPTEN_EVENT_KEYDOWN, wasmInput);
}
// this clears the input string, so backspaces do not send a character
// but stops suggestions
@@ -39,7 +38,7 @@ static void inputCallback(emscripten::val event)
}
EMSCRIPTEN_BINDINGS(clipboard_module) {
- function("qt_InputContextCallback", &inputCallback);
+ function("qtInputContextCallback", &inputCallback);
}
QWasmInputContext::QWasmInputContext()
@@ -48,25 +47,21 @@ QWasmInputContext::QWasmInputContext()
m_inputElement = document.call<emscripten::val>("createElement", std::string("input"));
m_inputElement.set("type", "text");
m_inputElement.set("style", "position:absolute;left:-1000px;top:-1000px"); // offscreen
- m_inputElement.set("contentaediable","true");
+ m_inputElement.set("contenteditable","true");
- if (platform() == Platform::Android) {
- emscripten::val body = document["body"];
- body.call<void>("appendChild", m_inputElement);
+ if (platform() == Platform::Android || platform() == Platform::Windows) {
+ const std::string inputType = platform() == Platform::Windows ? "textInput" : "input";
- m_inputElement.call<void>("addEventListener", std::string("input"),
- emscripten::val::module_property("qt_InputContextCallback"),
+ document.call<void>("addEventListener", inputType,
+ emscripten::val::module_property("qtInputContextCallback"),
emscripten::val(false));
- m_inputElement.set("data-context",
+ m_inputElement.set("data-qinputcontext",
emscripten::val(quintptr(reinterpret_cast<void *>(this))));
-
- // android sends Enter through target window, let's just handle this here
- emscripten_set_keydown_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, (void *)this, 1,
- &androidKeyboardCallback);
-
+ emscripten::val body = document["body"];
+ body.call<void>("appendChild", m_inputElement);
}
- if (platform() == Platform::MacOS || platform() == Platform::iPhone)
- {
+
+ if (platform() == Platform::MacOS || platform() == Platform::iOS) {
auto callback = [=](emscripten::val) {
m_inputElement["parentElement"].call<void>("removeChild", m_inputElement);
inputPanelIsOpen = false;
@@ -80,7 +75,7 @@ QWasmInputContext::QWasmInputContext()
QWasmInputContext::~QWasmInputContext()
{
- if (platform() == Platform::Android)
+ if (platform() == Platform::Android || platform() == Platform::Windows)
emscripten_set_keydown_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, 0, 0, NULL);
}
@@ -89,14 +84,11 @@ void QWasmInputContext::focusWindowChanged(QWindow *focusWindow)
m_focusWindow = focusWindow;
}
-emscripten::val QWasmInputContext::focusCanvas()
+emscripten::val QWasmInputContext::inputHandlerElementForFocusedWindow()
{
if (!m_focusWindow)
return emscripten::val::undefined();
- QScreen *screen = m_focusWindow->screen();
- if (!screen)
- return emscripten::val::undefined();
- return QWasmScreen::get(screen)->canvas();
+ return static_cast<QWasmWindow *>(m_focusWindow->handle())->inputHandlerElement();
}
void QWasmInputContext::update(Qt::InputMethodQueries queries)
@@ -107,24 +99,26 @@ void QWasmInputContext::update(Qt::InputMethodQueries queries)
void QWasmInputContext::showInputPanel()
{
if (platform() == Platform::Windows
- && inputPanelIsOpen) // call this only once for win32
- return;
+ && !inputPanelIsOpen) { // call this only once for win32
+ m_inputElement.call<void>("focus");
+ return;
+ }
// this is called each time the keyboard is touched
- // Add the input element as a child of the canvas for the
+ // Add the input element as a child of the screen for the
// currently focused window and give it focus. The browser
// will not display the input element, but mobile browsers
// should display the virtual keyboard. Key events will be
// captured by the keyboard event handler installed on the
- // canvas.
+ // screen element.
if (platform() == Platform::MacOS // keep for compatibility
- || platform() == Platform::iPhone
+ || platform() == Platform::iOS
|| platform() == Platform::Windows) {
- emscripten::val canvas = focusCanvas();
- if (canvas == emscripten::val::undefined())
+ emscripten::val inputWrapper = inputHandlerElementForFocusedWindow();
+ if (inputWrapper.isUndefined())
return;
- canvas.call<void>("appendChild", m_inputElement);
+ inputWrapper.call<void>("appendChild", m_inputElement);
}
m_inputElement.call<void>("focus");
@@ -139,29 +133,29 @@ void QWasmInputContext::hideInputPanel()
inputPanelIsOpen = false;
}
-void QWasmInputContext::inputStringChanged(QString &inputString, QWasmInputContext *context)
+void QWasmInputContext::inputStringChanged(QString &inputString, int eventType, QWasmInputContext *context)
{
Q_UNUSED(context)
QKeySequence keys = QKeySequence::fromString(inputString);
- // synthesize this keyevent as android is not normal
- QWindowSystemInterface::handleKeyEvent<QWindowSystemInterface::SynchronousDelivery>(
- 0, QEvent::KeyPress,keys[0].key(), keys[0].keyboardModifiers(), inputString);
-}
-
-int QWasmInputContext::androidKeyboardCallback(int eventType,
- const EmscriptenKeyboardEvent *keyEvent,
- void *userData)
-{
- // we get Enter, Backspace and function keys via emscripten on target window
- Q_UNUSED(eventType)
- QString strKey(keyEvent->key);
- if (strKey == "Unidentified" || strKey == "Process")
- return false;
+ Qt::Key thisKey = keys[0].key();
- QWasmInputContext *wasmInput = reinterpret_cast<QWasmInputContext*>(userData);
- wasmInput->inputStringChanged(strKey, wasmInput);
+ // synthesize this keyevent as android is not normal
+ if (inputString.size() > 2 && (thisKey < Qt::Key_F35
+ || thisKey > Qt::Key_Back)) {
+ inputString.clear();
+ }
+ if (inputString == QStringLiteral("Escape")) {
+ thisKey = Qt::Key_Escape;
+ inputString.clear();
+ } else if (thisKey == Qt::Key(0)) {
+ thisKey = Qt::Key_Return;
+ }
- return true;
+ QWindowSystemInterface::handleKeyEvent(
+ 0, eventType == EMSCRIPTEN_EVENT_KEYDOWN ? QEvent::KeyPress : QEvent::KeyRelease,
+ thisKey, keys[0].keyboardModifiers(),
+ eventType == EMSCRIPTEN_EVENT_KEYDOWN ? inputString : QStringLiteral(""));
}
+
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/wasm/qwasminputcontext.h b/src/plugins/platforms/wasm/qwasminputcontext.h
index 58f3920c30..10dd1a0950 100644
--- a/src/plugins/platforms/wasm/qwasminputcontext.h
+++ b/src/plugins/platforms/wasm/qwasminputcontext.h
@@ -29,18 +29,17 @@ public:
bool isValid() const override { return true; }
void focusWindowChanged(QWindow *focusWindow);
- emscripten::val focusCanvas();
- void inputStringChanged(QString &, QWasmInputContext *context);
+ void inputStringChanged(QString &, int eventType, QWasmInputContext *context);
+ emscripten::val m_inputElement = emscripten::val::null();
private:
+ emscripten::val inputHandlerElementForFocusedWindow();
+
bool m_inputPanelVisible = false;
QPointer<QWindow> m_focusWindow;
- emscripten::val m_inputElement = emscripten::val::null();
std::unique_ptr<qstdweb::EventCallback> m_blurEventHandler;
std::unique_ptr<qstdweb::EventCallback> m_inputEventHandler;
- static int androidKeyboardCallback(int eventType,
- const EmscriptenKeyboardEvent *keyEvent, void *userData);
bool inputPanelIsOpen = false;
};
diff --git a/src/plugins/platforms/wasm/qwasmintegration.cpp b/src/plugins/platforms/wasm/qwasmintegration.cpp
index a29e18e98a..f5cc3e2eee 100644
--- a/src/plugins/platforms/wasm/qwasmintegration.cpp
+++ b/src/plugins/platforms/wasm/qwasmintegration.cpp
@@ -2,7 +2,6 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#include "qwasmintegration.h"
-#include "qwasmeventtranslator.h"
#include "qwasmeventdispatcher.h"
#include "qwasmcompositor.h"
#include "qwasmopenglcontext.h"
@@ -11,21 +10,18 @@
#include "qwasmaccessibility.h"
#include "qwasmservices.h"
#include "qwasmoffscreensurface.h"
-#include "qwasmstring.h"
-
+#include "qwasmplatform.h"
#include "qwasmwindow.h"
-#ifndef QT_NO_OPENGL
-# include "qwasmbackingstore.h"
-#endif
+#include "qwasmbackingstore.h"
#include "qwasmfontdatabase.h"
-#if defined(Q_OS_UNIX)
-#include <QtGui/private/qgenericunixeventdispatcher_p.h>
-#endif
+#include "qwasmdrag.h"
+
#include <qpa/qplatformwindow.h>
#include <QtGui/qscreen.h>
#include <qpa/qwindowsysteminterface.h>
#include <QtCore/qcoreapplication.h>
#include <qpa/qplatforminputcontextfactory_p.h>
+#include <qpa/qwindowsysteminterface_p.h>
#include <emscripten/bind.h>
#include <emscripten/val.h>
@@ -36,18 +32,25 @@
QT_BEGIN_NAMESPACE
+extern void qt_set_sequence_auto_mnemonic(bool);
+
using namespace emscripten;
using namespace Qt::StringLiterals;
+static void setContainerElements(emscripten::val elementArray)
+{
+ QWasmIntegration::get()->setContainerElements(elementArray);
+}
+
static void addContainerElement(emscripten::val element)
{
- QWasmIntegration::get()->addScreen(element);
+ QWasmIntegration::get()->addContainerElement(element);
}
static void removeContainerElement(emscripten::val element)
{
- QWasmIntegration::get()->removeScreen(element);
+ QWasmIntegration::get()->removeContainerElement(element);
}
static void resizeContainerElement(emscripten::val element)
@@ -66,79 +69,80 @@ static void resizeAllScreens(emscripten::val event)
QWasmIntegration::get()->resizeAllScreens();
}
+static void loadLocalFontFamilies(emscripten::val event)
+{
+ QWasmIntegration::get()->loadLocalFontFamilies(event);
+}
+
EMSCRIPTEN_BINDINGS(qtQWasmIntegraton)
{
+ function("qtSetContainerElements", &setContainerElements);
function("qtAddContainerElement", &addContainerElement);
function("qtRemoveContainerElement", &removeContainerElement);
function("qtResizeContainerElement", &resizeContainerElement);
function("qtUpdateDpi", &qtUpdateDpi);
function("qtResizeAllScreens", &resizeAllScreens);
+ function("qtLoadLocalFontFamilies", &loadLocalFontFamilies);
}
QWasmIntegration *QWasmIntegration::s_instance;
QWasmIntegration::QWasmIntegration()
- : m_fontDb(nullptr),
- m_desktopServices(nullptr),
- m_clipboard(new QWasmClipboard),
- m_accessibility(new QWasmAccessibility)
+ : m_fontDb(nullptr)
+ , m_desktopServices(nullptr)
+ , m_clipboard(new QWasmClipboard)
+#if QT_CONFIG(accessibility)
+ , m_accessibility(new QWasmAccessibility)
+#endif
{
s_instance = this;
- touchPoints = emscripten::val::global("navigator")["maxTouchPoints"].as<int>();
+ if (platform() == Platform::MacOS)
+ qt_set_sequence_auto_mnemonic(false);
- // Create screens for container elements. Each container element can be a div element (preferred),
- // or a canvas element (legacy). Qt versions prior to 6.x read the "qtCanvasElements" module property,
- // which we continue to do to preserve compatibility. The preferred property is now "qtContainerElements".
+ touchPoints = emscripten::val::global("navigator")["maxTouchPoints"].as<int>();
+ QWindowSystemInterfacePrivate::TabletEvent::setPlatformSynthesizesMouse(false);
+
+ // Create screens for container elements. Each container element will ultimately become a
+ // div element. Qt historically supported supplying canvas for screen elements - these elements
+ // will be transformed into divs and warnings about deprecation will be printed. See
+ // QWasmScreen ctor.
+ emscripten::val filtered = emscripten::val::array();
emscripten::val qtContainerElements = val::module_property("qtContainerElements");
- emscripten::val qtCanvasElements = val::module_property("qtCanvasElements");
- if (!qtContainerElements.isUndefined()) {
- emscripten::val length = qtContainerElements["length"];
- int count = length.as<int>();
- if (length.isUndefined())
- qWarning("qtContainerElements does not have the length property set. Qt expects an array of html elements (possibly containing one element only)");
- for (int i = 0; i < count; ++i) {
+ if (qtContainerElements.isArray()) {
+ for (int i = 0; i < qtContainerElements["length"].as<int>(); ++i) {
emscripten::val element = qtContainerElements[i].as<emscripten::val>();
- if (element.isNull() ||element.isUndefined()) {
- qWarning() << "Skipping null or undefined element in qtContainerElements";
- } else {
- addScreen(element);
- }
+ if (element.isNull() || element.isUndefined())
+ qWarning() << "Skipping null or undefined element in qtContainerElements";
+ else
+ filtered.call<void>("push", element);
}
- } else if (!qtCanvasElements.isUndefined()) {
- qWarning() << "The qtCanvaseElements property is deprecated. Qt will stop reading"
- << "it in some future version, please use qtContainerElements instead";
- emscripten::val length = qtCanvasElements["length"];
- int count = length.as<int>();
- for (int i = 0; i < count; ++i)
- addScreen(qtCanvasElements[i].as<emscripten::val>());
} else {
// No screens, which may or may not be intended
- qWarning() << "Note: The qtContainerElements module property was not set. Proceeding with no screens.";
+ qWarning() << "The qtContainerElements module property was not set or is invalid. "
+ "Proceeding with no screens.";
}
+ setContainerElements(filtered);
// install browser window resize handler
- auto onWindowResize = [](int eventType, const EmscriptenUiEvent *e, void *userData) -> int {
- Q_UNUSED(eventType);
- Q_UNUSED(e);
- Q_UNUSED(userData);
-
- // This resize event is called when the HTML window is resized. Depending
- // on the page layout the canvas(es) might also have been resized, so we
- // update the Qt screen sizes (and canvas render sizes).
- if (QWasmIntegration *integration = QWasmIntegration::get())
- integration->resizeAllScreens();
- return 0;
- };
- emscripten_set_resize_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, nullptr, EM_TRUE, onWindowResize);
+ emscripten_set_resize_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, nullptr, EM_TRUE,
+ [](int, const EmscriptenUiEvent *, void *) -> int {
+ // This resize event is called when the HTML window is
+ // resized. Depending on the page layout the elements might
+ // also have been resized, so we update the Qt screen sizes
+ // (and canvas render sizes).
+ if (QWasmIntegration *integration = QWasmIntegration::get())
+ integration->resizeAllScreens();
+ return 0;
+ });
// install visualViewport resize handler which picks up size and scale change on mobile.
emscripten::val visualViewport = emscripten::val::global("window")["visualViewport"];
if (!visualViewport.isUndefined()) {
visualViewport.call<void>("addEventListener", val("resize"),
- val::module_property("qtResizeAllScreens"));
+ val::module_property("qtResizeAllScreens"));
}
- m_drag = new QWasmDrag();
+ m_drag = std::make_unique<QWasmDrag>();
}
QWasmIntegration::~QWasmIntegration()
@@ -155,11 +159,12 @@ QWasmIntegration::~QWasmIntegration()
delete m_desktopServices;
if (m_platformInputContext)
delete m_platformInputContext;
- delete m_drag;
+#if QT_CONFIG(accessibility)
delete m_accessibility;
+#endif
for (const auto &elementAndScreen : m_screens)
- elementAndScreen.second->deleteScreen();
+ elementAndScreen.wasmScreen->deleteScreen();
m_screens.clear();
@@ -182,20 +187,18 @@ bool QWasmIntegration::hasCapability(QPlatformIntegration::Capability cap) const
QPlatformWindow *QWasmIntegration::createPlatformWindow(QWindow *window) const
{
- QWasmCompositor *compositor = QWasmScreen::get(window->screen())->compositor();
- return new QWasmWindow(window, compositor, m_backingStores.value(window));
+ auto *wasmScreen = QWasmScreen::get(window->screen());
+ QWasmCompositor *compositor = wasmScreen->compositor();
+ return new QWasmWindow(window, wasmScreen->deadKeySupport(), compositor,
+ m_backingStores.value(window));
}
QPlatformBackingStore *QWasmIntegration::createPlatformBackingStore(QWindow *window) const
{
-#ifndef QT_NO_OPENGL
QWasmCompositor *compositor = QWasmScreen::get(window->screen())->compositor();
QWasmBackingStore *backingStore = new QWasmBackingStore(compositor, window);
m_backingStores.insert(window, backingStore);
return backingStore;
-#else
- return nullptr;
-#endif
}
void QWasmIntegration::removeBackingStore(QWindow* window)
@@ -203,21 +206,31 @@ void QWasmIntegration::removeBackingStore(QWindow* window)
m_backingStores.remove(window);
}
+void QWasmIntegration::releaseRequesetUpdateHold()
+{
+ if (QWasmCompositor::releaseRequestUpdateHold())
+ {
+ for (const auto &elementAndScreen : m_screens) {
+ elementAndScreen.wasmScreen->compositor()->requestUpdate();
+ }
+ }
+}
+
#ifndef QT_NO_OPENGL
QPlatformOpenGLContext *QWasmIntegration::createPlatformOpenGLContext(QOpenGLContext *context) const
{
- return new QWasmOpenGLContext(context->format());
+ return new QWasmOpenGLContext(context);
}
#endif
void QWasmIntegration::initialize()
{
- if (touchPoints < 1) // only touchscreen need inputcontexts
+ auto icStrs = QPlatformInputContextFactory::requested();
+ if (icStrs.isEmpty() && touchPoints < 1)
return;
- QString icStr = QPlatformInputContextFactory::requested();
- if (!icStr.isNull())
- m_inputContext.reset(QPlatformInputContextFactory::create(icStr));
+ if (!icStrs.isEmpty())
+ m_inputContext.reset(QPlatformInputContextFactory::create(icStrs));
else
m_inputContext.reset(new QWasmInputContext());
}
@@ -229,7 +242,7 @@ QPlatformInputContext *QWasmIntegration::inputContext() const
QPlatformOffscreenSurface *QWasmIntegration::createPlatformOffscreenSurface(QOffscreenSurface *surface) const
{
- return new QWasmOffscrenSurface(surface);
+ return new QWasmOffscreenSurface(surface);
}
QPlatformFontDatabase *QWasmIntegration::fontDatabase() const
@@ -247,10 +260,14 @@ QAbstractEventDispatcher *QWasmIntegration::createEventDispatcher() const
QVariant QWasmIntegration::styleHint(QPlatformIntegration::StyleHint hint) const
{
- if (hint == ShowIsFullScreen)
+ switch (hint) {
+ case ShowIsFullScreen:
return true;
-
- return QPlatformIntegration::styleHint(hint);
+ case UnderlineShortcut:
+ return platform() != Platform::MacOS;
+ default:
+ return QPlatformIntegration::styleHint(hint);
+ }
}
Qt::WindowState QWasmIntegration::defaultWindowState(Qt::WindowFlags flags) const
@@ -293,35 +310,93 @@ QPlatformAccessibility *QWasmIntegration::accessibility() const
}
#endif
+void QWasmIntegration::setContainerElements(emscripten::val elementArray)
+{
+ const auto *primaryScreenBefore = m_screens.isEmpty() ? nullptr : m_screens[0].wasmScreen;
+ QList<ScreenMapping> newScreens;
+
+ QList<QWasmScreen *> screensToDelete;
+ std::transform(m_screens.begin(), m_screens.end(), std::back_inserter(screensToDelete),
+ [](const ScreenMapping &mapping) { return mapping.wasmScreen; });
+
+ for (int i = 0; i < elementArray["length"].as<int>(); ++i) {
+ const auto element = elementArray[i];
+ const auto it = std::find_if(
+ m_screens.begin(), m_screens.end(),
+ [&element](const ScreenMapping &screen) { return screen.emscriptenVal == element; });
+ QWasmScreen *screen;
+ if (it != m_screens.end()) {
+ screen = it->wasmScreen;
+ screensToDelete.erase(std::remove_if(screensToDelete.begin(), screensToDelete.end(),
+ [screen](const QWasmScreen *removedScreen) {
+ return removedScreen == screen;
+ }),
+ screensToDelete.end());
+ } else {
+ screen = new QWasmScreen(element);
+ QWindowSystemInterface::handleScreenAdded(screen);
+ }
+ newScreens.push_back({element, screen});
+ }
-void QWasmIntegration::addScreen(const emscripten::val &element)
+ std::for_each(screensToDelete.begin(), screensToDelete.end(),
+ [](QWasmScreen *removed) { removed->deleteScreen(); });
+
+ m_screens = newScreens;
+ auto *primaryScreenAfter = m_screens.isEmpty() ? nullptr : m_screens[0].wasmScreen;
+ if (primaryScreenAfter && primaryScreenAfter != primaryScreenBefore)
+ QWindowSystemInterface::handlePrimaryScreenChanged(primaryScreenAfter);
+}
+
+void QWasmIntegration::addContainerElement(emscripten::val element)
{
+ Q_ASSERT_X(m_screens.end()
+ == std::find_if(m_screens.begin(), m_screens.end(),
+ [&element](const ScreenMapping &screen) {
+ return screen.emscriptenVal == element;
+ }),
+ Q_FUNC_INFO, "Double-add of an element");
+
QWasmScreen *screen = new QWasmScreen(element);
- m_screens.append(qMakePair(element, screen));
- m_clipboard->installEventHandlers(element);
QWindowSystemInterface::handleScreenAdded(screen);
+ m_screens.push_back({element, screen});
}
-void QWasmIntegration::removeScreen(const emscripten::val &element)
+void QWasmIntegration::removeContainerElement(emscripten::val element)
{
- auto it = std::find_if(m_screens.begin(), m_screens.end(),
- [&] (const QPair<emscripten::val, QWasmScreen *> &candidate) { return candidate.first.equals(element); });
+ const auto *primaryScreenBefore = m_screens.isEmpty() ? nullptr : m_screens[0].wasmScreen;
+
+ const auto it =
+ std::find_if(m_screens.begin(), m_screens.end(),
+ [&element](const ScreenMapping &screen) { return screen.emscriptenVal == element; });
if (it == m_screens.end()) {
- qWarning() << "Attempting to remove non-existing screen for element" << QWasmString::toQString(element["id"]);;
+ qWarning() << "Attempt to remove a nonexistent screen.";
return;
}
- it->second->deleteScreen();
+
+ QWasmScreen *removedScreen = it->wasmScreen;
+ removedScreen->deleteScreen();
+
+ m_screens.erase(std::remove_if(m_screens.begin(), m_screens.end(),
+ [removedScreen](const ScreenMapping &mapping) {
+ return removedScreen == mapping.wasmScreen;
+ }),
+ m_screens.end());
+ auto *primaryScreenAfter = m_screens.isEmpty() ? nullptr : m_screens[0].wasmScreen;
+ if (primaryScreenAfter && primaryScreenAfter != primaryScreenBefore)
+ QWindowSystemInterface::handlePrimaryScreenChanged(primaryScreenAfter);
}
void QWasmIntegration::resizeScreen(const emscripten::val &element)
{
auto it = std::find_if(m_screens.begin(), m_screens.end(),
- [&] (const QPair<emscripten::val, QWasmScreen *> &candidate) { return candidate.first.equals(element); });
+ [&] (const ScreenMapping &candidate) { return candidate.emscriptenVal.equals(element); });
if (it == m_screens.end()) {
- qWarning() << "Attempting to resize non-existing screen for element" << QWasmString::toQString(element["id"]);;
+ qWarning() << "Attempting to resize non-existing screen for element"
+ << QString::fromEcmaString(element["id"]);
return;
}
- it->second->updateQScreenAndCanvasRenderSize();
+ it->wasmScreen->updateQScreenAndCanvasRenderSize();
}
void QWasmIntegration::updateDpi()
@@ -331,13 +406,18 @@ void QWasmIntegration::updateDpi()
return;
qreal dpiValue = dpi.as<qreal>();
for (const auto &elementAndScreen : m_screens)
- QWindowSystemInterface::handleScreenLogicalDotsPerInchChange(elementAndScreen.second->screen(), dpiValue, dpiValue);
+ QWindowSystemInterface::handleScreenLogicalDotsPerInchChange(elementAndScreen.wasmScreen->screen(), dpiValue, dpiValue);
}
void QWasmIntegration::resizeAllScreens()
{
for (const auto &elementAndScreen : m_screens)
- elementAndScreen.second->updateQScreenAndCanvasRenderSize();
+ elementAndScreen.wasmScreen->updateQScreenAndCanvasRenderSize();
+}
+
+void QWasmIntegration::loadLocalFontFamilies(emscripten::val families)
+{
+ m_fontDb->populateLocalFontFamilies(families);
}
quint64 QWasmIntegration::getTimestamp()
@@ -348,7 +428,7 @@ quint64 QWasmIntegration::getTimestamp()
#if QT_CONFIG(draganddrop)
QPlatformDrag *QWasmIntegration::drag() const
{
- return m_drag;
+ return m_drag.get();
}
#endif // QT_CONFIG(draganddrop)
diff --git a/src/plugins/platforms/wasm/qwasmintegration.h b/src/plugins/platforms/wasm/qwasmintegration.h
index 76296ff1a7..870bd0d16b 100644
--- a/src/plugins/platforms/wasm/qwasmintegration.h
+++ b/src/plugins/platforms/wasm/qwasmintegration.h
@@ -6,23 +6,20 @@
#include "qwasmwindow.h"
+#include "qwasminputcontext.h"
+
#include <qpa/qplatformintegration.h>
#include <qpa/qplatformscreen.h>
#include <qpa/qplatforminputcontext.h>
#include <QtCore/qhash.h>
+#include <private/qstdweb_p.h>
+
#include <emscripten.h>
#include <emscripten/html5.h>
#include <emscripten/val.h>
-#include "qwasminputcontext.h"
-#include <private/qstdweb_p.h>
-
-#if QT_CONFIG(draganddrop)
-#include "qwasmdrag.h"
-#endif
-
QT_BEGIN_NAMESPACE
class QWasmEventTranslator;
@@ -35,6 +32,7 @@ class QWasmBackingStore;
class QWasmClipboard;
class QWasmAccessibility;
class QWasmServices;
+class QWasmDrag;
class QWasmIntegration : public QObject, public QPlatformIntegration
{
@@ -72,21 +70,29 @@ public:
QWasmInputContext *getWasmInputContext() { return m_platformInputContext; }
static QWasmIntegration *get() { return s_instance; }
- void addScreen(const emscripten::val &canvas);
- void removeScreen(const emscripten::val &canvas);
+ void setContainerElements(emscripten::val elementArray);
+ void addContainerElement(emscripten::val elementArray);
+ void removeContainerElement(emscripten::val elementArray);
void resizeScreen(const emscripten::val &canvas);
- void resizeAllScreens();
void updateDpi();
+ void resizeAllScreens();
+ void loadLocalFontFamilies(emscripten::val families);
void removeBackingStore(QWindow* window);
+ void releaseRequesetUpdateHold();
static quint64 getTimestamp();
int touchPoints;
private:
+ struct ScreenMapping {
+ emscripten::val emscriptenVal;
+ QWasmScreen *wasmScreen;
+ };
+
mutable QWasmFontDatabase *m_fontDb;
mutable QWasmServices *m_desktopServices;
mutable QHash<QWindow *, QWasmBackingStore *> m_backingStores;
- QList<QPair<emscripten::val, QWasmScreen *>> m_screens;
+ QList<ScreenMapping> m_screens;
mutable QWasmClipboard *m_clipboard;
mutable QWasmAccessibility *m_accessibility;
@@ -97,7 +103,7 @@ private:
mutable QWasmInputContext *m_platformInputContext = nullptr;
#if QT_CONFIG(draganddrop)
- QWasmDrag *m_drag;
+ std::unique_ptr<QWasmDrag> m_drag;
#endif
};
diff --git a/src/plugins/platforms/wasm/qwasmkeytranslator.cpp b/src/plugins/platforms/wasm/qwasmkeytranslator.cpp
new file mode 100644
index 0000000000..8f5240d2d0
--- /dev/null
+++ b/src/plugins/platforms/wasm/qwasmkeytranslator.cpp
@@ -0,0 +1,295 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "qwasmkeytranslator.h"
+#include "qwasmevent.h"
+
+#include <QtCore/private/qmakearray_p.h>
+#include <QtCore/qglobal.h>
+#include <QtCore/qobject.h>
+
+#include <algorithm>
+
+QT_BEGIN_NAMESPACE
+
+namespace {
+struct WebKb2QtData
+{
+ static constexpr char StringTerminator = '\0';
+
+ const char *web;
+ unsigned int qt;
+
+ constexpr bool operator<=(const WebKb2QtData &that) const noexcept
+ {
+ return !(strcmp(that) > 0);
+ }
+
+ bool operator<(const WebKb2QtData &that) const noexcept { return ::strcmp(web, that.web) < 0; }
+
+ constexpr bool operator==(const WebKb2QtData &that) const noexcept { return strcmp(that) == 0; }
+
+ constexpr int strcmp(const WebKb2QtData &that, const int i = 0) const
+ {
+ return web[i] == StringTerminator && that.web[i] == StringTerminator ? 0
+ : web[i] == StringTerminator ? -1
+ : that.web[i] == StringTerminator ? 1
+ : web[i] < that.web[i] ? -1
+ : web[i] > that.web[i] ? 1
+ : strcmp(that, i + 1);
+ }
+};
+
+template<unsigned int Qt, char... WebChar>
+struct Web2Qt
+{
+ static constexpr const char storage[sizeof...(WebChar) + 1] = { WebChar..., '\0' };
+ using Type = WebKb2QtData;
+ static constexpr Type data() noexcept { return Type{ storage, Qt }; }
+};
+
+template<unsigned int Qt, char... WebChar>
+constexpr char Web2Qt<Qt, WebChar...>::storage[];
+
+static constexpr const auto WebToQtKeyCodeMappings = qMakeArray(
+ QSortedData<Web2Qt<Qt::Key_Alt, 'A', 'l', 't', 'L', 'e', 'f', 't'>,
+ Web2Qt<Qt::Key_Alt, 'A', 'l', 't'>,
+ Web2Qt<Qt::Key_AltGr, 'A', 'l', 't', 'R', 'i', 'g', 'h', 't'>,
+ Web2Qt<Qt::Key_Apostrophe, 'Q', 'u', 'o', 't', 'e'>,
+ Web2Qt<Qt::Key_Backspace, 'B', 'a', 'c', 'k', 's', 'p', 'a', 'c', 'e'>,
+ Web2Qt<Qt::Key_CapsLock, 'C', 'a', 'p', 's', 'L', 'o', 'c', 'k'>,
+ Web2Qt<Qt::Key_Control, 'C', 'o', 'n', 't', 'r', 'o', 'l'>,
+ Web2Qt<Qt::Key_Delete, 'D', 'e', 'l', 'e', 't', 'e'>,
+ Web2Qt<Qt::Key_Down, 'A', 'r', 'r', 'o', 'w', 'D', 'o', 'w', 'n'>,
+ Web2Qt<Qt::Key_Escape, 'E', 's', 'c', 'a', 'p', 'e'>,
+ Web2Qt<Qt::Key_F1, 'F', '1'>, Web2Qt<Qt::Key_F2, 'F', '2'>,
+ Web2Qt<Qt::Key_F11, 'F', '1', '1'>, Web2Qt<Qt::Key_F12, 'F', '1', '2'>,
+ Web2Qt<Qt::Key_F13, 'F', '1', '3'>, Web2Qt<Qt::Key_F14, 'F', '1', '4'>,
+ Web2Qt<Qt::Key_F15, 'F', '1', '5'>, Web2Qt<Qt::Key_F16, 'F', '1', '6'>,
+ Web2Qt<Qt::Key_F17, 'F', '1', '7'>, Web2Qt<Qt::Key_F18, 'F', '1', '8'>,
+ Web2Qt<Qt::Key_F19, 'F', '1', '9'>, Web2Qt<Qt::Key_F20, 'F', '2', '0'>,
+ Web2Qt<Qt::Key_F21, 'F', '2', '1'>, Web2Qt<Qt::Key_F22, 'F', '2', '2'>,
+ Web2Qt<Qt::Key_F23, 'F', '2', '3'>,
+ Web2Qt<Qt::Key_F3, 'F', '3'>, Web2Qt<Qt::Key_F4, 'F', '4'>,
+ Web2Qt<Qt::Key_F5, 'F', '5'>, Web2Qt<Qt::Key_F6, 'F', '6'>,
+ Web2Qt<Qt::Key_F7, 'F', '7'>, Web2Qt<Qt::Key_F8, 'F', '8'>,
+ Web2Qt<Qt::Key_F9, 'F', '9'>, Web2Qt<Qt::Key_F10, 'F', '1', '0'>,
+ Web2Qt<Qt::Key_Help, 'H', 'e', 'l', 'p'>,
+ Web2Qt<Qt::Key_Home, 'H', 'o', 'm', 'e'>, Web2Qt<Qt::Key_End, 'E', 'n', 'd'>,
+ Web2Qt<Qt::Key_Insert, 'I', 'n', 's', 'e', 'r', 't'>,
+ Web2Qt<Qt::Key_Left, 'A', 'r', 'r', 'o', 'w', 'L', 'e', 'f', 't'>,
+ Web2Qt<Qt::Key_Meta, 'M', 'e', 't', 'a'>, Web2Qt<Qt::Key_Meta, 'O', 'S'>,
+ Web2Qt<Qt::Key_Menu, 'C', 'o', 'n', 't', 'e', 'x', 't', 'M', 'e', 'n', 'u'>,
+ Web2Qt<Qt::Key_NumLock, 'N', 'u', 'm', 'L', 'o', 'c', 'k'>,
+ Web2Qt<Qt::Key_PageDown, 'P', 'a', 'g', 'e', 'D', 'o', 'w', 'n'>,
+ Web2Qt<Qt::Key_PageUp, 'P', 'a', 'g', 'e', 'U', 'p'>,
+ Web2Qt<Qt::Key_Paste, 'P', 'a', 's', 't', 'e'>,
+ Web2Qt<Qt::Key_Pause, 'C', 'l', 'e', 'a', 'r'>,
+ Web2Qt<Qt::Key_Pause, 'P', 'a', 'u', 's', 'e'>,
+ Web2Qt<Qt::Key_QuoteLeft, 'B', 'a', 'c', 'k', 'q', 'u', 'o', 't', 'e'>,
+ Web2Qt<Qt::Key_QuoteLeft, 'I', 'n', 't', 'l', 'B', 'a', 'c', 'k', 's', 'l', 'a', 's', 'h'>,
+ Web2Qt<Qt::Key_Return, 'E', 'n', 't', 'e', 'r'>,
+ Web2Qt<Qt::Key_Right, 'A', 'r', 'r', 'o', 'w', 'R', 'i', 'g', 'h', 't'>,
+ Web2Qt<Qt::Key_ScrollLock, 'S', 'c', 'r', 'o', 'l', 'l', 'L', 'o', 'c', 'k'>,
+ Web2Qt<Qt::Key_Shift, 'S', 'h', 'i', 'f', 't'>,
+ Web2Qt<Qt::Key_Tab, 'T', 'a', 'b'>,
+ Web2Qt<Qt::Key_Up, 'A', 'r', 'r', 'o', 'w', 'U', 'p'>,
+ Web2Qt<Qt::Key_yen, 'I', 'n', 't', 'l', 'Y', 'e', 'n'>>::Data{});
+
+static constexpr const auto DiacriticalCharsKeyToTextLowercase = qMakeArray(
+ QSortedData<
+ Web2Qt<Qt::Key_Aacute, '\xc3', '\xa1'>,
+ Web2Qt<Qt::Key_Acircumflex, '\xc3', '\xa2'>,
+ Web2Qt<Qt::Key_Adiaeresis, '\xc3', '\xa4'>,
+ Web2Qt<Qt::Key_AE, '\xc3', '\xa6'>,
+ Web2Qt<Qt::Key_Agrave, '\xc3', '\xa0'>,
+ Web2Qt<Qt::Key_Aring, '\xc3', '\xa5'>,
+ Web2Qt<Qt::Key_Atilde, '\xc3', '\xa3'>,
+ Web2Qt<Qt::Key_Ccedilla, '\xc3', '\xa7'>,
+ Web2Qt<Qt::Key_Eacute, '\xc3', '\xa9'>,
+ Web2Qt<Qt::Key_Ecircumflex, '\xc3', '\xaa'>,
+ Web2Qt<Qt::Key_Ediaeresis, '\xc3', '\xab'>,
+ Web2Qt<Qt::Key_Egrave, '\xc3', '\xa8'>,
+ Web2Qt<Qt::Key_Iacute, '\xc3', '\xad'>,
+ Web2Qt<Qt::Key_Icircumflex, '\xc3', '\xae'>,
+ Web2Qt<Qt::Key_Idiaeresis, '\xc3', '\xaf'>,
+ Web2Qt<Qt::Key_Igrave, '\xc3', '\xac'>,
+ Web2Qt<Qt::Key_Ntilde, '\xc3', '\xb1'>,
+ Web2Qt<Qt::Key_Oacute, '\xc3', '\xb3'>,
+ Web2Qt<Qt::Key_Ocircumflex, '\xc3', '\xb4'>,
+ Web2Qt<Qt::Key_Odiaeresis, '\xc3', '\xb6'>,
+ Web2Qt<Qt::Key_Ograve, '\xc3', '\xb2'>,
+ Web2Qt<Qt::Key_Ooblique, '\xc3', '\xb8'>,
+ Web2Qt<Qt::Key_Otilde, '\xc3', '\xb5'>,
+ Web2Qt<Qt::Key_Uacute, '\xc3', '\xba'>,
+ Web2Qt<Qt::Key_Ucircumflex, '\xc3', '\xbb'>,
+ Web2Qt<Qt::Key_Udiaeresis, '\xc3', '\xbc'>,
+ Web2Qt<Qt::Key_Ugrave, '\xc3', '\xb9'>,
+ Web2Qt<Qt::Key_Yacute, '\xc3', '\xbd'>,
+ Web2Qt<Qt::Key_ydiaeresis, '\xc3', '\xbf'>>::Data{});
+
+static constexpr const auto DiacriticalCharsKeyToTextUppercase = qMakeArray(
+ QSortedData<
+ Web2Qt<Qt::Key_Aacute, '\xc3', '\x81'>,
+ Web2Qt<Qt::Key_Acircumflex, '\xc3', '\x82'>,
+ Web2Qt<Qt::Key_Adiaeresis, '\xc3', '\x84'>,
+ Web2Qt<Qt::Key_AE, '\xc3', '\x86'>,
+ Web2Qt<Qt::Key_Agrave, '\xc3', '\x80'>,
+ Web2Qt<Qt::Key_Aring, '\xc3', '\x85'>,
+ Web2Qt<Qt::Key_Atilde, '\xc3', '\x83'>,
+ Web2Qt<Qt::Key_Ccedilla, '\xc3', '\x87'>,
+ Web2Qt<Qt::Key_Eacute, '\xc3', '\x89'>,
+ Web2Qt<Qt::Key_Ecircumflex, '\xc3', '\x8a'>,
+ Web2Qt<Qt::Key_Ediaeresis, '\xc3', '\x8b'>,
+ Web2Qt<Qt::Key_Egrave, '\xc3', '\x88'>,
+ Web2Qt<Qt::Key_Iacute, '\xc3', '\x8d'>,
+ Web2Qt<Qt::Key_Icircumflex, '\xc3', '\x8e'>,
+ Web2Qt<Qt::Key_Idiaeresis, '\xc3', '\x8f'>,
+ Web2Qt<Qt::Key_Igrave, '\xc3', '\x8c'>,
+ Web2Qt<Qt::Key_Ntilde, '\xc3', '\x91'>,
+ Web2Qt<Qt::Key_Oacute, '\xc3', '\x93'>,
+ Web2Qt<Qt::Key_Ocircumflex, '\xc3', '\x94'>,
+ Web2Qt<Qt::Key_Odiaeresis, '\xc3', '\x96'>,
+ Web2Qt<Qt::Key_Ograve, '\xc3', '\x92'>,
+ Web2Qt<Qt::Key_Ooblique, '\xc3', '\x98'>,
+ Web2Qt<Qt::Key_Otilde, '\xc3', '\x95'>,
+ Web2Qt<Qt::Key_Uacute, '\xc3', '\x9a'>,
+ Web2Qt<Qt::Key_Ucircumflex, '\xc3', '\x9b'>,
+ Web2Qt<Qt::Key_Udiaeresis, '\xc3', '\x9c'>,
+ Web2Qt<Qt::Key_Ugrave, '\xc3', '\x99'>,
+ Web2Qt<Qt::Key_Yacute, '\xc3', '\x9d'>,
+ Web2Qt<Qt::Key_ydiaeresis, '\xc5', '\xb8'>>::Data{});
+
+static_assert(DiacriticalCharsKeyToTextLowercase.size()
+ == DiacriticalCharsKeyToTextUppercase.size(),
+ "Add the new key to both arrays");
+
+struct KeyMapping
+{
+ Qt::Key from, to;
+};
+
+constexpr KeyMapping tildeKeyTable[] = {
+ // ~
+ { Qt::Key_A, Qt::Key_Atilde },
+ { Qt::Key_N, Qt::Key_Ntilde },
+ { Qt::Key_O, Qt::Key_Otilde },
+};
+constexpr KeyMapping graveKeyTable[] = {
+ // `
+ { Qt::Key_A, Qt::Key_Agrave }, { Qt::Key_E, Qt::Key_Egrave }, { Qt::Key_I, Qt::Key_Igrave },
+ { Qt::Key_O, Qt::Key_Ograve }, { Qt::Key_U, Qt::Key_Ugrave },
+};
+constexpr KeyMapping acuteKeyTable[] = {
+ // '
+ { Qt::Key_A, Qt::Key_Aacute }, { Qt::Key_E, Qt::Key_Eacute }, { Qt::Key_I, Qt::Key_Iacute },
+ { Qt::Key_O, Qt::Key_Oacute }, { Qt::Key_U, Qt::Key_Uacute }, { Qt::Key_Y, Qt::Key_Yacute },
+};
+constexpr KeyMapping diaeresisKeyTable[] = {
+ // umlaut ¨
+ { Qt::Key_A, Qt::Key_Adiaeresis }, { Qt::Key_E, Qt::Key_Ediaeresis },
+ { Qt::Key_I, Qt::Key_Idiaeresis }, { Qt::Key_O, Qt::Key_Odiaeresis },
+ { Qt::Key_U, Qt::Key_Udiaeresis }, { Qt::Key_Y, Qt::Key_ydiaeresis },
+};
+constexpr KeyMapping circumflexKeyTable[] = {
+ // ^
+ { Qt::Key_A, Qt::Key_Acircumflex }, { Qt::Key_E, Qt::Key_Ecircumflex },
+ { Qt::Key_I, Qt::Key_Icircumflex }, { Qt::Key_O, Qt::Key_Ocircumflex },
+ { Qt::Key_U, Qt::Key_Ucircumflex },
+};
+
+static Qt::Key find_impl(const KeyMapping *first, const KeyMapping *last, Qt::Key key) noexcept
+{
+ while (first != last) {
+ if (first->from == key)
+ return first->to;
+ ++first;
+ }
+ return Qt::Key_unknown;
+}
+
+template<size_t N>
+static Qt::Key find(const KeyMapping (&map)[N], Qt::Key key) noexcept
+{
+ return find_impl(map, map + N, key);
+}
+
+Qt::Key translateBaseKeyUsingDeadKey(Qt::Key accentBaseKey, Qt::Key deadKey)
+{
+ switch (deadKey) {
+ case Qt::Key_Dead_Grave:
+ return find(graveKeyTable, accentBaseKey);
+ case Qt::Key_Dead_Acute:
+ return find(acuteKeyTable, accentBaseKey);
+ case Qt::Key_Dead_Tilde:
+ return find(tildeKeyTable, accentBaseKey);
+ case Qt::Key_Dead_Diaeresis:
+ return find(diaeresisKeyTable, accentBaseKey);
+ case Qt::Key_Dead_Circumflex:
+ return find(circumflexKeyTable, accentBaseKey);
+ default:
+ return Qt::Key_unknown;
+ };
+}
+
+template<class T>
+std::optional<QString> findKeyTextByKeyId(const T &mappingArray, Qt::Key qtKey)
+{
+ const auto it = std::find_if(mappingArray.cbegin(), mappingArray.cend(),
+ [qtKey](const WebKb2QtData &data) { return data.qt == qtKey; });
+ return it != mappingArray.cend() ? it->web : std::optional<QString>();
+}
+} // namespace
+
+std::optional<Qt::Key> QWasmKeyTranslator::mapWebKeyTextToQtKey(const char *toFind)
+{
+ const WebKb2QtData searchKey{ toFind, 0 };
+ const auto it = std::lower_bound(WebToQtKeyCodeMappings.cbegin(), WebToQtKeyCodeMappings.cend(),
+ searchKey);
+ return it != WebToQtKeyCodeMappings.cend() && searchKey == *it ? static_cast<Qt::Key>(it->qt)
+ : std::optional<Qt::Key>();
+}
+
+QWasmDeadKeySupport::QWasmDeadKeySupport() = default;
+
+QWasmDeadKeySupport::~QWasmDeadKeySupport() = default;
+
+void QWasmDeadKeySupport::applyDeadKeyTranslations(KeyEvent *event)
+{
+ if (event->deadKey) {
+ m_activeDeadKey = event->key;
+ } else if (m_activeDeadKey != Qt::Key_unknown
+ && (((m_keyModifiedByDeadKeyOnPress == Qt::Key_unknown
+ && event->type == EventType::KeyDown))
+ || (m_keyModifiedByDeadKeyOnPress == event->key
+ && event->type == EventType::KeyUp))) {
+ const Qt::Key baseKey = event->key;
+ const Qt::Key translatedKey = translateBaseKeyUsingDeadKey(baseKey, m_activeDeadKey);
+ if (translatedKey != Qt::Key_unknown) {
+ event->key = translatedKey;
+
+ auto foundText = event->modifiers.testFlag(Qt::ShiftModifier)
+ ? findKeyTextByKeyId(DiacriticalCharsKeyToTextUppercase, event->key)
+ : findKeyTextByKeyId(DiacriticalCharsKeyToTextLowercase, event->key);
+ Q_ASSERT(foundText.has_value());
+ event->text = foundText->size() == 1 ? *foundText : QString();
+ }
+
+ if (!event->text.isEmpty()) {
+ if (event->type == EventType::KeyDown) {
+ // Assume the first keypress with an active dead key is treated as modified,
+ // regardless of whether it has actually been modified or not. Take into account
+ // only events that produce actual key text.
+ if (!event->text.isEmpty())
+ m_keyModifiedByDeadKeyOnPress = baseKey;
+ } else {
+ Q_ASSERT(event->type == EventType::KeyUp);
+ Q_ASSERT(m_keyModifiedByDeadKeyOnPress == baseKey);
+ m_keyModifiedByDeadKeyOnPress = Qt::Key_unknown;
+ m_activeDeadKey = Qt::Key_unknown;
+ }
+ }
+ }
+}
+
+QT_END_NAMESPACE
diff --git a/src/plugins/platforms/wasm/qwasmkeytranslator.h b/src/plugins/platforms/wasm/qwasmkeytranslator.h
new file mode 100644
index 0000000000..11a89e6193
--- /dev/null
+++ b/src/plugins/platforms/wasm/qwasmkeytranslator.h
@@ -0,0 +1,34 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef QWASMKEYTRANSLATOR_H
+#define QWASMKEYTRANSLATOR_H
+
+#include <QtCore/qnamespace.h>
+#include <QtCore/qtypes.h>
+
+#include <optional>
+
+QT_BEGIN_NAMESPACE
+
+struct KeyEvent;
+
+namespace QWasmKeyTranslator {
+std::optional<Qt::Key> mapWebKeyTextToQtKey(const char *toFind);
+}
+
+class QWasmDeadKeySupport
+{
+public:
+ explicit QWasmDeadKeySupport();
+ ~QWasmDeadKeySupport();
+
+ void applyDeadKeyTranslations(KeyEvent *event);
+
+private:
+ Qt::Key m_activeDeadKey = Qt::Key_unknown;
+ Qt::Key m_keyModifiedByDeadKeyOnPress = Qt::Key_unknown;
+};
+
+QT_END_NAMESPACE
+#endif // QWASMKEYTRANSLATOR_H
diff --git a/src/plugins/platforms/wasm/qwasmoffscreensurface.cpp b/src/plugins/platforms/wasm/qwasmoffscreensurface.cpp
index 79ec9af62b..dcfc4433e6 100644
--- a/src/plugins/platforms/wasm/qwasmoffscreensurface.cpp
+++ b/src/plugins/platforms/wasm/qwasmoffscreensurface.cpp
@@ -5,15 +5,31 @@
QT_BEGIN_NAMESPACE
-QWasmOffscrenSurface::QWasmOffscrenSurface(QOffscreenSurface *offscreenSurface)
- :QPlatformOffscreenSurface(offscreenSurface)
+QWasmOffscreenSurface::QWasmOffscreenSurface(QOffscreenSurface *offscreenSurface)
+ : QPlatformOffscreenSurface(offscreenSurface), m_offscreenCanvas(emscripten::val::undefined())
{
+ const auto offscreenCanvasClass = emscripten::val::global("OffscreenCanvas");
+ // The OffscreenCanvas is not supported on some browsers, most notably on Safari.
+ if (!offscreenCanvasClass)
+ return;
+ m_offscreenCanvas = offscreenCanvasClass.new_(offscreenSurface->size().width(),
+ offscreenSurface->size().height());
+
+ m_specialTargetId = std::string("!qtoffscreen_") + std::to_string(uintptr_t(this));
+
+ emscripten::val::module_property("specialHTMLTargets")
+ .set(m_specialTargetId, m_offscreenCanvas);
}
-QWasmOffscrenSurface::~QWasmOffscrenSurface()
+QWasmOffscreenSurface::~QWasmOffscreenSurface()
{
+ emscripten::val::module_property("specialHTMLTargets").delete_(m_specialTargetId);
+}
+bool QWasmOffscreenSurface::isValid() const
+{
+ return !m_offscreenCanvas.isNull() && !m_offscreenCanvas.isUndefined();
}
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/wasm/qwasmoffscreensurface.h b/src/plugins/platforms/wasm/qwasmoffscreensurface.h
index ad9c556d10..1c71310448 100644
--- a/src/plugins/platforms/wasm/qwasmoffscreensurface.h
+++ b/src/plugins/platforms/wasm/qwasmoffscreensurface.h
@@ -6,16 +6,25 @@
#include <qpa/qplatformoffscreensurface.h>
+#include <emscripten/val.h>
+
+#include <string>
+
QT_BEGIN_NAMESPACE
class QOffscreenSurface;
-class QWasmOffscrenSurface : public QPlatformOffscreenSurface
+class QWasmOffscreenSurface final : public QPlatformOffscreenSurface
{
public:
- explicit QWasmOffscrenSurface(QOffscreenSurface *offscreenSurface);
- ~QWasmOffscrenSurface();
-private:
+ explicit QWasmOffscreenSurface(QOffscreenSurface *offscreenSurface);
+ ~QWasmOffscreenSurface() final;
+ const std::string &id() const { return m_specialTargetId; }
+ bool isValid() const override;
+
+private:
+ std::string m_specialTargetId;
+ emscripten::val m_offscreenCanvas;
};
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/wasm/qwasmopenglcontext.cpp b/src/plugins/platforms/wasm/qwasmopenglcontext.cpp
index da9219bd6e..8a4664ec8c 100644
--- a/src/plugins/platforms/wasm/qwasmopenglcontext.cpp
+++ b/src/plugins/platforms/wasm/qwasmopenglcontext.cpp
@@ -2,38 +2,43 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#include "qwasmopenglcontext.h"
+
+#include "qwasmoffscreensurface.h"
#include "qwasmintegration.h"
#include <EGL/egl.h>
+#include <emscripten/bind.h>
#include <emscripten/val.h>
+namespace {
+void qtDoNothing(emscripten::val) { }
+} // namespace
+
+EMSCRIPTEN_BINDINGS(qwasmopenglcontext)
+{
+ function("qtDoNothing", &qtDoNothing);
+}
+
QT_BEGIN_NAMESPACE
-QWasmOpenGLContext::QWasmOpenGLContext(const QSurfaceFormat &format)
- : m_requestedFormat(format)
+QWasmOpenGLContext::QWasmOpenGLContext(QOpenGLContext *context)
+ : m_actualFormat(context->format()), m_qGlContext(context)
{
- m_requestedFormat.setRenderableType(QSurfaceFormat::OpenGLES);
+ m_actualFormat.setRenderableType(QSurfaceFormat::OpenGLES);
// if we set one, we need to set the other as well since in webgl, these are tied together
- if (format.depthBufferSize() < 0 && format.stencilBufferSize() > 0)
- m_requestedFormat.setDepthBufferSize(16);
-
- if (format.stencilBufferSize() < 0 && format.depthBufferSize() > 0)
- m_requestedFormat.setStencilBufferSize(8);
+ if (m_actualFormat.depthBufferSize() < 0 && m_actualFormat.stencilBufferSize() > 0)
+ m_actualFormat.setDepthBufferSize(16);
+ if (m_actualFormat.stencilBufferSize() < 0 && m_actualFormat.depthBufferSize() > 0)
+ m_actualFormat.setStencilBufferSize(8);
}
QWasmOpenGLContext::~QWasmOpenGLContext()
{
- if (m_context) {
- // Destroy GL context. Work around bug in emscripten_webgl_destroy_context
- // which removes all event handlers on the canvas by temporarily removing
- // emscripten's JSEvents global object.
- emscripten::val jsEvents = emscripten::val::global("window")["JSEvents"];
- emscripten::val::global("window").set("JSEvents", emscripten::val::undefined());
- emscripten_webgl_destroy_context(m_context);
- emscripten::val::global("window").set("JSEvents", jsEvents);
- m_context = 0;
- }
+ // Destroy GL context. Work around bug in emscripten_webgl_destroy_context
+ // which removes all event handlers on the canvas by temporarily replacing the function
+ // that does the removal with a function that does nothing.
+ destroyWebGLContext(m_ownedWebGLContext.handle);
}
bool QWasmOpenGLContext::isOpenGLVersionSupported(QSurfaceFormat format)
@@ -46,27 +51,66 @@ bool QWasmOpenGLContext::isOpenGLVersionSupported(QSurfaceFormat format)
(format.majorVersion() == 3 && format.minorVersion() == 0));
}
-bool QWasmOpenGLContext::maybeCreateEmscriptenContext(QPlatformSurface *surface)
+EMSCRIPTEN_WEBGL_CONTEXT_HANDLE
+QWasmOpenGLContext::obtainEmscriptenContext(QPlatformSurface *surface)
{
- // Native emscripten/WebGL contexts are tied to a single screen/canvas. The first
- // call to this function creates a native canvas for the given screen, subsequent
- // calls verify that the surface is on/off the same screen.
- QPlatformScreen *screen = surface->screen();
- if (m_context && !screen)
- return false; // Alternative: return true to support makeCurrent on QOffScreenSurface with
- // no screen. However, Qt likes to substitute QGuiApplication::primaryScreen()
- // for null screens, which foils this plan.
- if (!screen)
- return false;
- if (m_context)
- return m_screen == screen;
+ if (m_ownedWebGLContext.surface == surface)
+ return m_ownedWebGLContext.handle;
+
+ if (surface->surface()->surfaceClass() == QSurface::Offscreen) {
+ // Reuse the existing context for offscreen drawing, even if it happens to be a canvas
+ // context. This is because it is impossible to re-home an existing context to the
+ // new surface and works as an emulation measure.
+ if (m_ownedWebGLContext.handle)
+ return m_ownedWebGLContext.handle;
+
+ // The non-shared offscreen context is heavily limited on WASM, but we provide it
+ // anyway for potential pixel readbacks.
+ m_ownedWebGLContext =
+ QOpenGLContextData{ .surface = surface,
+ .handle = createEmscriptenContext(
+ static_cast<QWasmOffscreenSurface *>(surface)->id(),
+ m_actualFormat) };
+ } else {
+ destroyWebGLContext(m_ownedWebGLContext.handle);
+
+ // Create a full on-screen context for the window canvas.
+ m_ownedWebGLContext = QOpenGLContextData{
+ .surface = surface,
+ .handle = createEmscriptenContext(static_cast<QWasmWindow *>(surface)->canvasSelector(),
+ m_actualFormat)
+ };
+ }
+
+ EmscriptenWebGLContextAttributes actualAttributes;
+
+ EMSCRIPTEN_RESULT attributesResult = emscripten_webgl_get_context_attributes(m_ownedWebGLContext.handle, &actualAttributes);
+ if (attributesResult == EMSCRIPTEN_RESULT_SUCCESS) {
+ if (actualAttributes.majorVersion == 1) {
+ m_actualFormat.setMajorVersion(2);
+ } else if (actualAttributes.majorVersion == 2) {
+ m_actualFormat.setMajorVersion(3);
+ }
+ m_actualFormat.setMinorVersion(0);
+ }
+
+ return m_ownedWebGLContext.handle;
+}
- m_context = createEmscriptenContext(QWasmScreen::get(screen)->canvasTargetId(), m_requestedFormat);
- m_screen = screen;
- return true;
+void QWasmOpenGLContext::destroyWebGLContext(EMSCRIPTEN_WEBGL_CONTEXT_HANDLE contextHandle)
+{
+ if (!contextHandle)
+ return;
+ emscripten::val jsEvents = emscripten::val::module_property("JSEvents");
+ emscripten::val savedRemoveAllHandlersOnTargetFunction = jsEvents["removeAllHandlersOnTarget"];
+ jsEvents.set("removeAllHandlersOnTarget", emscripten::val::module_property("qtDoNothing"));
+ emscripten_webgl_destroy_context(contextHandle);
+ jsEvents.set("removeAllHandlersOnTarget", savedRemoveAllHandlersOnTargetFunction);
}
-EMSCRIPTEN_WEBGL_CONTEXT_HANDLE QWasmOpenGLContext::createEmscriptenContext(const QString &canvasTargetId, QSurfaceFormat format)
+EMSCRIPTEN_WEBGL_CONTEXT_HANDLE
+QWasmOpenGLContext::createEmscriptenContext(const std::string &canvasSelector,
+ QSurfaceFormat format)
{
EmscriptenWebGLContextAttributes attributes;
emscripten_webgl_init_context_attributes(&attributes); // Populate with default attributes
@@ -75,27 +119,30 @@ EMSCRIPTEN_WEBGL_CONTEXT_HANDLE QWasmOpenGLContext::createEmscriptenContext(cons
attributes.failIfMajorPerformanceCaveat = false;
attributes.antialias = true;
attributes.enableExtensionsByDefault = true;
- attributes.majorVersion = format.majorVersion() - 1;
- attributes.minorVersion = format.minorVersion();
-
+ attributes.majorVersion = 2; // try highest supported version ES3.0 / WebGL 2.0
+ attributes.minorVersion = 0; // emscripten only supports minor version 0
// WebGL doesn't allow separate attach buffers to STENCIL_ATTACHMENT and DEPTH_ATTACHMENT
// we need both or none
- bool useDepthStencil = (format.depthBufferSize() > 0 || format.stencilBufferSize() > 0);
+ const bool useDepthStencil = (format.depthBufferSize() > 0 || format.stencilBufferSize() > 0);
// WebGL offers enable/disable control but not size control for these
attributes.alpha = format.alphaBufferSize() > 0;
attributes.depth = useDepthStencil;
attributes.stencil = useDepthStencil;
+ EMSCRIPTEN_RESULT contextResult = emscripten_webgl_create_context(canvasSelector.c_str(), &attributes);
- QByteArray convasSelector = canvasTargetId.toUtf8();
- EMSCRIPTEN_WEBGL_CONTEXT_HANDLE context = emscripten_webgl_create_context(convasSelector.constData(), &attributes);
-
- return context;
+ if (contextResult <= 0) {
+ // fallback to opengles2/webgl1
+ // for devices that do not support opengles3/webgl2
+ attributes.majorVersion = 1;
+ contextResult = emscripten_webgl_create_context(canvasSelector.c_str(), &attributes);
+ }
+ return contextResult;
}
QSurfaceFormat QWasmOpenGLContext::format() const
{
- return m_requestedFormat;
+ return m_actualFormat;
}
GLuint QWasmOpenGLContext::defaultFramebufferObject(QPlatformSurface *surface) const
@@ -105,11 +152,22 @@ GLuint QWasmOpenGLContext::defaultFramebufferObject(QPlatformSurface *surface) c
bool QWasmOpenGLContext::makeCurrent(QPlatformSurface *surface)
{
- bool ok = maybeCreateEmscriptenContext(surface);
- if (!ok)
+ static bool sentSharingWarning = false;
+ if (!sentSharingWarning && isSharing()) {
+ qWarning() << "The functionality for sharing OpenGL contexts is limited, see documentation";
+ sentSharingWarning = true;
+ }
+
+ if (auto *shareContext = m_qGlContext->shareContext())
+ return shareContext->makeCurrent(surface->surface());
+
+ const auto context = obtainEmscriptenContext(surface);
+ if (!context)
return false;
- return emscripten_webgl_make_context_current(m_context) == EMSCRIPTEN_RESULT_SUCCESS;
+ m_usedWebGLContextHandle = context;
+
+ return emscripten_webgl_make_context_current(context) == EMSCRIPTEN_RESULT_SUCCESS;
}
void QWasmOpenGLContext::swapBuffers(QPlatformSurface *surface)
@@ -125,17 +183,17 @@ void QWasmOpenGLContext::doneCurrent()
bool QWasmOpenGLContext::isSharing() const
{
- return false;
+ return m_qGlContext->shareContext();
}
bool QWasmOpenGLContext::isValid() const
{
- if (!(isOpenGLVersionSupported(m_requestedFormat)))
+ if (!isOpenGLVersionSupported(m_actualFormat))
return false;
// Note: we get isValid() calls before we see the surface and can
// create a native context, so no context is also a valid state.
- return !m_context || !emscripten_is_webgl_context_lost(m_context);
+ return !m_usedWebGLContextHandle || !emscripten_is_webgl_context_lost(m_usedWebGLContextHandle);
}
QFunctionPointer QWasmOpenGLContext::getProcAddress(const char *procName)
diff --git a/src/plugins/platforms/wasm/qwasmopenglcontext.h b/src/plugins/platforms/wasm/qwasmopenglcontext.h
index 9cd10f3ea6..2a8bcc5d9b 100644
--- a/src/plugins/platforms/wasm/qwasmopenglcontext.h
+++ b/src/plugins/platforms/wasm/qwasmopenglcontext.h
@@ -1,6 +1,9 @@
// Copyright (C) 2018 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+#ifndef QWASMOPENGLCONTEXT_H
+#define QWASMOPENGLCONTEXT_H
+
#include <qpa/qplatformopenglcontext.h>
#include <emscripten.h>
@@ -8,11 +11,13 @@
QT_BEGIN_NAMESPACE
+class QOpenGLContext;
class QPlatformScreen;
+class QPlatformSurface;
class QWasmOpenGLContext : public QPlatformOpenGLContext
{
public:
- QWasmOpenGLContext(const QSurfaceFormat &format);
+ explicit QWasmOpenGLContext(QOpenGLContext *context);
~QWasmOpenGLContext();
QSurfaceFormat format() const override;
@@ -25,14 +30,25 @@ public:
QFunctionPointer getProcAddress(const char *procName) override;
private:
+ struct QOpenGLContextData
+ {
+ QPlatformSurface *surface = nullptr;
+ EMSCRIPTEN_WEBGL_CONTEXT_HANDLE handle = 0;
+ };
+
static bool isOpenGLVersionSupported(QSurfaceFormat format);
- bool maybeCreateEmscriptenContext(QPlatformSurface *surface);
- static EMSCRIPTEN_WEBGL_CONTEXT_HANDLE createEmscriptenContext(const QString &canvasId, QSurfaceFormat format);
+ EMSCRIPTEN_WEBGL_CONTEXT_HANDLE obtainEmscriptenContext(QPlatformSurface *surface);
+ static EMSCRIPTEN_WEBGL_CONTEXT_HANDLE
+ createEmscriptenContext(const std::string &canvasSelector, QSurfaceFormat format);
+
+ static void destroyWebGLContext(EMSCRIPTEN_WEBGL_CONTEXT_HANDLE contextHandle);
- QSurfaceFormat m_requestedFormat;
- QPlatformScreen *m_screen = nullptr;
- EMSCRIPTEN_WEBGL_CONTEXT_HANDLE m_context = 0;
+ QSurfaceFormat m_actualFormat;
+ QOpenGLContext *m_qGlContext;
+ QOpenGLContextData m_ownedWebGLContext;
+ EMSCRIPTEN_WEBGL_CONTEXT_HANDLE m_usedWebGLContextHandle = 0;
};
QT_END_NAMESPACE
+#endif // QWASMOPENGLCONTEXT_H
diff --git a/src/plugins/platforms/wasm/qwasmplatform.cpp b/src/plugins/platforms/wasm/qwasmplatform.cpp
index c641e345e4..e54992be1d 100644
--- a/src/plugins/platforms/wasm/qwasmplatform.cpp
+++ b/src/plugins/platforms/wasm/qwasmplatform.cpp
@@ -13,8 +13,9 @@ Platform platform()
if (rawPlatform.call<bool>("includes", emscripten::val("Mac")))
return Platform::MacOS;
- if (rawPlatform.call<bool>("includes", emscripten::val("iPhone")))
- return Platform::iPhone;
+ if (rawPlatform.call<bool>("includes", emscripten::val("iPhone"))
+ || rawPlatform.call<bool>("includes", emscripten::val("iPad")))
+ return Platform::iOS;
if (rawPlatform.call<bool>("includes", emscripten::val("Win32")))
return Platform::Windows;
if (rawPlatform.call<bool>("includes", emscripten::val("Linux"))) {
diff --git a/src/plugins/platforms/wasm/qwasmplatform.h b/src/plugins/platforms/wasm/qwasmplatform.h
index 239efdeae9..5b32e43633 100644
--- a/src/plugins/platforms/wasm/qwasmplatform.h
+++ b/src/plugins/platforms/wasm/qwasmplatform.h
@@ -19,7 +19,7 @@ enum class Platform {
Windows,
Linux,
Android,
- iPhone,
+ iOS
};
Platform platform();
diff --git a/src/plugins/platforms/wasm/qwasmscreen.cpp b/src/plugins/platforms/wasm/qwasmscreen.cpp
index 92a23c9206..ddf8140c48 100644
--- a/src/plugins/platforms/wasm/qwasmscreen.cpp
+++ b/src/plugins/platforms/wasm/qwasmscreen.cpp
@@ -2,11 +2,12 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#include "qwasmscreen.h"
-#include "qwasmwindow.h"
-#include "qwasmeventtranslator.h"
+
#include "qwasmcompositor.h"
+#include "qwasmcssstyle.h"
#include "qwasmintegration.h"
-#include "qwasmstring.h"
+#include "qwasmkeytranslator.h"
+#include "qwasmwindow.h"
#include <emscripten/bind.h>
#include <emscripten/val.h>
@@ -16,105 +17,109 @@
#include <QtGui/qguiapplication.h>
#include <private/qhighdpiscaling_p.h>
+#include <tuple>
+
QT_BEGIN_NAMESPACE
using namespace emscripten;
-const char * QWasmScreen::m_canvasResizeObserverCallbackContextPropertyName = "data-qtCanvasResizeObserverCallbackContext";
+const char *QWasmScreen::m_canvasResizeObserverCallbackContextPropertyName =
+ "data-qtCanvasResizeObserverCallbackContext";
QWasmScreen::QWasmScreen(const emscripten::val &containerOrCanvas)
- : m_container(containerOrCanvas)
- , m_canvas(emscripten::val::undefined())
- , m_compositor(new QWasmCompositor(this))
- , m_eventTranslator(new QWasmEventTranslator())
+ : m_container(containerOrCanvas),
+ m_intermediateContainer(emscripten::val::undefined()),
+ m_shadowContainer(emscripten::val::undefined()),
+ m_compositor(new QWasmCompositor(this)),
+ m_deadKeySupport(std::make_unique<QWasmDeadKeySupport>())
{
- // Each screen is backed by a html canvas element. Use either
- // a user-supplied canvas or create one as a child of the user-
- // supplied root element.
- std::string tagName = containerOrCanvas["tagName"].as<std::string>();
- if (tagName == "CANVAS" || tagName == "canvas") {
- m_canvas = containerOrCanvas;
- } else {
- // Create the canvas (for the correct document) as a child of the container
- m_canvas = containerOrCanvas["ownerDocument"].call<emscripten::val>("createElement", std::string("canvas"));
- containerOrCanvas.call<void>("appendChild", m_canvas);
- std::string screenId = std::string("qtcanvas_") + std::to_string(uint32_t(this));
- m_canvas.set("id", screenId);
-
- // Make the canvas occupy 100% of parent
- emscripten::val style = m_canvas["style"];
- style.set("width", std::string("100%"));
- style.set("height", std::string("100%"));
+ auto document = m_container["ownerDocument"];
+ // Each screen is represented by a div container. All of the windows exist therein as
+ // its children. Qt versions < 6.5 used to represent screens as canvas. Support that by
+ // transforming the canvas into a div.
+ if (m_container["tagName"].call<std::string>("toLowerCase") == "canvas") {
+ qWarning() << "Support for canvas elements as an element backing screen is deprecated. The "
+ "canvas provided for the screen will be transformed into a div.";
+ auto container = document.call<emscripten::val>("createElement", emscripten::val("div"));
+ m_container["parentNode"].call<void>("replaceChild", container, m_container);
+ m_container = container;
}
- emscripten::val style = m_canvas["style"];
+ // Create an intermediate container which we can remove during cleanup in ~QWasmScreen().
+ // This is required due to the attachShadow() call below; there is no corresponding
+ // "detachShadow()" API to return the container to its previous state.
+ m_intermediateContainer = document.call<emscripten::val>("createElement", emscripten::val("div"));
+ m_intermediateContainer.set("id", std::string("qt-shadow-container"));
+ emscripten::val intermediateContainerStyle = m_intermediateContainer["style"];
+ intermediateContainerStyle.set("width", std::string("100%"));
+ intermediateContainerStyle.set("height", std::string("100%"));
+ m_container.call<void>("appendChild", m_intermediateContainer);
+
+ auto shadowOptions = emscripten::val::object();
+ shadowOptions.set("mode", "open");
+ auto shadow = m_intermediateContainer.call<emscripten::val>("attachShadow", shadowOptions);
- // Configure container and canvas for accessibility support: set "position: relative"
- // so that a11y child elements can be positioned with "position: absolute", and hide
- // the canvas from screen readers.
- m_container["style"].set("position", std::string("relative"));
- m_canvas.call<void>("setAttribute", std::string("aria-hidden"), std::string("true")); // FIXME make the canvas non-focusable, as required by the aria-hidden role
- style.set("z-index", std::string("1")); // a11y elements are at 0
+ m_shadowContainer = document.call<emscripten::val>("createElement", emscripten::val("div"));
- style.set("border", std::string("0px none"));
- style.set("background-color", std::string("white"));
+ shadow.call<void>("appendChild", QWasmCSSStyle::createStyleElement(m_shadowContainer));
- // Set contenteditable so that the canvas gets clipboard events,
- // then hide the resulting focus frame, and reset the cursor.
- m_canvas.set("contentEditable", std::string("true"));
- // set inputmode to none to stop mobile keyboard opening
- // when user clicks anywhere on the canvas.
- m_canvas.set("inputmode", std::string("none"));
- style.set("outline", std::string("0px solid transparent"));
- style.set("caret-color", std::string("transparent"));
- style.set("cursor", std::string("default"));
+ shadow.call<void>("appendChild", m_shadowContainer);
+
+ m_shadowContainer.set("id", std::string("qt-screen-") + std::to_string(uintptr_t(this)));
+
+ m_shadowContainer["classList"].call<void>("add", std::string("qt-screen"));
// Disable the default context menu; Qt applications typically
// provide custom right-click behavior.
- m_onContextMenu = std::make_unique<qstdweb::EventCallback>(m_canvas, "contextmenu", [](emscripten::val event){
- event.call<void>("preventDefault");
- });
-
- // Create "specialHTMLTargets" mapping for the canvas. Normally, Emscripten
- // uses the html element id when targeting elements, for example when registering
- // event callbacks. However, this approach is limited to supporting single-document
- // apps/ages only, since Emscripten uses the main document to look up the element.
- // As a workaround for this, Emscripten supports registering custom mappings in the
- // "specialHTMLTargets" object. Add a mapping for the canvas for this screen.
- //
- // This functionality is gated on "specialHTMLTargets" being available as a module
- // property. One way to ensure this is the case is to add it to EXPORTED_RUNTIME_METHODS.
- // Qt does not currently do this by default since if added it _must_ be used in order
- // to avoid an undefined reference error at startup, and there are cases when Qt won't use
- // it, for example if QGuiApplication is not usded.
- if (hasSpecialHtmlTargets())
- emscripten::val::module_property("specialHTMLTargets").set(canvasSpecialHtmlTargetId(), m_canvas);
-
- // Install event handlers on the container/canvas. This must be
- // done after the canvas has been created above.
- m_compositor->initEventHandlers();
+ m_onContextMenu = std::make_unique<qstdweb::EventCallback>(
+ m_shadowContainer, "contextmenu",
+ [](emscripten::val event) { event.call<void>("preventDefault"); });
+ // Create "specialHTMLTargets" mapping for the canvas - the element might be unreachable based
+ // on its id only under some conditions, like the target being embedded in a shadow DOM or a
+ // subframe.
+ emscripten::val::module_property("specialHTMLTargets")
+ .set(eventTargetId().toStdString(), m_shadowContainer);
+
+ emscripten::val::module_property("specialHTMLTargets")
+ .set(outerScreenId().toStdString(), m_container);
updateQScreenAndCanvasRenderSize();
- m_canvas.call<void>("focus");
+ m_shadowContainer.call<void>("focus");
+
+ m_touchDevice = std::make_unique<QPointingDevice>(
+ "touchscreen", 1, QInputDevice::DeviceType::TouchScreen,
+ QPointingDevice::PointerType::Finger,
+ QPointingDevice::Capability::Position | QPointingDevice::Capability::Area
+ | QPointingDevice::Capability::NormalizedPosition,
+ 10, 0);
+ m_tabletDevice = std::make_unique<QPointingDevice>(
+ "stylus", 2, QInputDevice::DeviceType::Stylus,
+ QPointingDevice::PointerType::Pen,
+ QPointingDevice::Capability::Position | QPointingDevice::Capability::Pressure
+ | QPointingDevice::Capability::NormalizedPosition
+ | QInputDevice::Capability::MouseEmulation
+ | QInputDevice::Capability::Hover | QInputDevice::Capability::Rotation
+ | QInputDevice::Capability::XTilt | QInputDevice::Capability::YTilt
+ | QInputDevice::Capability::TangentialPressure,
+ 0, 0);
+
+ QWindowSystemInterface::registerInputDevice(m_touchDevice.get());
}
QWasmScreen::~QWasmScreen()
{
- // Delete the compositor before removing the screen from specialHTMLTargets,
- // since its destructor needs to look up the target when deregistering
- // event handlers.
- m_compositor = nullptr;
+ m_intermediateContainer.call<void>("remove");
- if (hasSpecialHtmlTargets())
- emscripten::val::module_property("specialHTMLTargets")
- .set(canvasSpecialHtmlTargetId(), emscripten::val::undefined());
+ emscripten::val::module_property("specialHTMLTargets")
+ .set(eventTargetId().toStdString(), emscripten::val::undefined());
- m_canvas.set(m_canvasResizeObserverCallbackContextPropertyName, emscripten::val(intptr_t(0)));
+ m_shadowContainer.set(m_canvasResizeObserverCallbackContextPropertyName,
+ emscripten::val(intptr_t(0)));
}
void QWasmScreen::deleteScreen()
{
- m_compositor->destroy();
+ // Deletes |this|!
QWindowSystemInterface::handleScreenRemoved(this);
}
@@ -135,58 +140,21 @@ QWasmCompositor *QWasmScreen::compositor()
return m_compositor.get();
}
-QWasmEventTranslator *QWasmScreen::eventTranslator()
-{
- return m_eventTranslator.get();
-}
-
-emscripten::val QWasmScreen::container() const
-{
- return m_container;
-}
-
-emscripten::val QWasmScreen::canvas() const
-{
- return m_canvas;
-}
-
-// Returns the html element id for the screen's canvas.
-QString QWasmScreen::canvasId() const
-{
- return QWasmString::toQString(m_canvas["id"]);
-}
-
-// Returns the canvas _target_ id, for use with Emscripten's event registration
-// functions. This either based on the id registered in specialHtmlTargets, or
-// on the canvas id.
-QString QWasmScreen::canvasTargetId() const
+emscripten::val QWasmScreen::element() const
{
- if (hasSpecialHtmlTargets())
- return QString::fromStdString(canvasSpecialHtmlTargetId());
- else
- return QStringLiteral("#") + canvasId();
+ return m_shadowContainer;
}
-std::string QWasmScreen::canvasSpecialHtmlTargetId() const
+QString QWasmScreen::eventTargetId() const
{
// Return a globally unique id for the canvas. We can choose any string,
// as long as it starts with a "!".
- return std::string("!qtcanvas_") + std::to_string(uint32_t(this));
+ return QString("!qtcanvas_%1").arg(uintptr_t(this));
}
-bool QWasmScreen::hasSpecialHtmlTargets() const
+QString QWasmScreen::outerScreenId() const
{
- static bool gotIt = []{
- // Enable use of specialHTMLTargets, if available
- emscripten::val htmlTargets = emscripten::val::module_property("specialHTMLTargets");
- if (htmlTargets.isUndefined())
- return false;
-
- // Check that the object has the expected type - it can also be
- // defined as an abort() function which prints an error on usage.
- return htmlTargets["constructor"]["name"].as<std::string>() == std::string("Array");
- }();
- return gotIt;
+ return QString("!outerscreen_%1").arg(uintptr_t(this));
}
QRect QWasmScreen::geometry() const
@@ -238,7 +206,7 @@ qreal QWasmScreen::devicePixelRatio() const
QString QWasmScreen::name() const
{
- return canvasId();
+ return QString::fromEcmaString(m_shadowContainer["id"]);
}
QPlatformCursor *QWasmScreen::cursor() const
@@ -255,21 +223,30 @@ void QWasmScreen::resizeMaximizedWindows()
QWindow *QWasmScreen::topWindow() const
{
- return m_compositor->keyWindow();
+ return activeChild() ? activeChild()->window() : nullptr;
}
QWindow *QWasmScreen::topLevelAt(const QPoint &p) const
{
- return m_compositor->windowAt(p);
+ const auto found =
+ std::find_if(childStack().begin(), childStack().end(), [&p](const QWasmWindow *window) {
+ const QRect geometry = window->windowFrameGeometry();
+
+ return window->isVisible() && geometry.contains(p);
+ });
+ return found != childStack().end() ? (*found)->window() : nullptr;
}
-QPoint QWasmScreen::translateAndClipGlobalPoint(const QPoint &p) const
+QPointF QWasmScreen::mapFromLocal(const QPointF &p) const
{
- return QPoint(
- std::max(screen()->geometry().left(),
- std::min(screen()->geometry().right(), screen()->geometry().left() + p.x())),
- std::max(screen()->geometry().top(),
- std::min(screen()->geometry().bottom(), screen()->geometry().top() + p.y())));
+ return geometry().topLeft() + p;
+}
+
+QPointF QWasmScreen::clipPoint(const QPointF &p) const
+{
+ const auto geometryF = screen()->geometry().toRectF();
+ return QPointF(qBound(geometryF.left(), p.x(), geometryF.right()),
+ qBound(geometryF.top(), p.y(), geometryF.bottom()));
}
void QWasmScreen::invalidateSize()
@@ -280,10 +257,23 @@ void QWasmScreen::invalidateSize()
void QWasmScreen::setGeometry(const QRect &rect)
{
m_geometry = rect;
- QWindowSystemInterface::handleScreenGeometryChange(QPlatformScreen::screen(), geometry(), availableGeometry());
+ QWindowSystemInterface::handleScreenGeometryChange(QPlatformScreen::screen(), geometry(),
+ availableGeometry());
resizeMaximizedWindows();
}
+void QWasmScreen::onSubtreeChanged(QWasmWindowTreeNodeChangeType changeType,
+ QWasmWindowTreeNode *parent, QWasmWindow *child)
+{
+ Q_UNUSED(parent);
+ if (changeType == QWasmWindowTreeNodeChangeType::NodeInsertion && parent == this
+ && childStack().size() == 1) {
+ child->window()->setFlag(Qt::WindowStaysOnBottomHint);
+ }
+ QWasmWindowTreeNode::onSubtreeChanged(changeType, parent, child);
+ m_compositor->onWindowTreeChanged(changeType, child);
+}
+
void QWasmScreen::updateQScreenAndCanvasRenderSize()
{
// The HTML canvas has two sizes: the CSS size and the canvas render size.
@@ -292,28 +282,26 @@ void QWasmScreen::updateQScreenAndCanvasRenderSize()
// size must be set manually and is not auto-updated on CSS size change.
// Setting the render size to a value larger than the CSS size enables high-dpi
// rendering.
-
- QByteArray canvasSelector = canvasTargetId().toUtf8();
double css_width;
double css_height;
- emscripten_get_element_css_size(canvasSelector.constData(), &css_width, &css_height);
+ emscripten_get_element_css_size(outerScreenId().toUtf8().constData(), &css_width, &css_height);
QSizeF cssSize(css_width, css_height);
QSizeF canvasSize = cssSize * devicePixelRatio();
- m_canvas.set("width", canvasSize.width());
- m_canvas.set("height", canvasSize.height());
+ m_shadowContainer.set("width", canvasSize.width());
+ m_shadowContainer.set("height", canvasSize.height());
// Returns the html elements document/body position
auto getElementBodyPosition = [](const emscripten::val &element) -> QPoint {
- emscripten::val bodyRect = element["ownerDocument"]["body"].call<emscripten::val>("getBoundingClientRect");
+ emscripten::val bodyRect =
+ element["ownerDocument"]["body"].call<emscripten::val>("getBoundingClientRect");
emscripten::val canvasRect = element.call<emscripten::val>("getBoundingClientRect");
- return QPoint (canvasRect["left"].as<int>() - bodyRect["left"].as<int>(),
- canvasRect["top"].as<int>() - bodyRect["top"].as<int>());
+ return QPoint(canvasRect["left"].as<int>() - bodyRect["left"].as<int>(),
+ canvasRect["top"].as<int>() - bodyRect["top"].as<int>());
};
- setGeometry(QRect(getElementBodyPosition(m_canvas), cssSize.toSize()));
- m_compositor->requestUpdateAllWindows();
+ setGeometry(QRect(getElementBodyPosition(m_shadowContainer), cssSize.toSize()));
}
void QWasmScreen::canvasResizeObserverCallback(emscripten::val entries, emscripten::val)
@@ -322,20 +310,23 @@ void QWasmScreen::canvasResizeObserverCallback(emscripten::val entries, emscript
if (count == 0)
return;
emscripten::val entry = entries[0];
- QWasmScreen *screen =
- reinterpret_cast<QWasmScreen *>(entry["target"][m_canvasResizeObserverCallbackContextPropertyName].as<intptr_t>());
+ QWasmScreen *screen = reinterpret_cast<QWasmScreen *>(
+ entry["target"][m_canvasResizeObserverCallbackContextPropertyName].as<intptr_t>());
if (!screen) {
qWarning() << "QWasmScreen::canvasResizeObserverCallback: missing screen pointer";
return;
}
// We could access contentBoxSize|contentRect|devicePixelContentBoxSize on the entry here, but
- // these are not universally supported across all browsers. Get the sizes from the canvas instead.
+ // these are not universally supported across all browsers. Get the sizes from the canvas
+ // instead.
screen->updateQScreenAndCanvasRenderSize();
}
-EMSCRIPTEN_BINDINGS(qtCanvasResizeObserverCallback) {
- emscripten::function("qtCanvasResizeObserverCallback", &QWasmScreen::canvasResizeObserverCallback);
+EMSCRIPTEN_BINDINGS(qtCanvasResizeObserverCallback)
+{
+ emscripten::function("qtCanvasResizeObserverCallback",
+ &QWasmScreen::canvasResizeObserverCallback);
}
void QWasmScreen::installCanvasResizeObserver()
@@ -343,15 +334,40 @@ void QWasmScreen::installCanvasResizeObserver()
emscripten::val ResizeObserver = emscripten::val::global("ResizeObserver");
if (ResizeObserver == emscripten::val::undefined())
return; // ResizeObserver API is not available
- emscripten::val resizeObserver = ResizeObserver.new_(emscripten::val::module_property("qtCanvasResizeObserverCallback"));
+ emscripten::val resizeObserver =
+ ResizeObserver.new_(emscripten::val::module_property("qtCanvasResizeObserverCallback"));
if (resizeObserver == emscripten::val::undefined())
return; // Something went horribly wrong
// We need to get back to this instance from the (static) resize callback;
// set a "data-" property on the canvas element.
- m_canvas.set(m_canvasResizeObserverCallbackContextPropertyName, emscripten::val(intptr_t(this)));
+ m_shadowContainer.set(m_canvasResizeObserverCallbackContextPropertyName,
+ emscripten::val(intptr_t(this)));
- resizeObserver.call<void>("observe", m_canvas);
+ resizeObserver.call<void>("observe", m_shadowContainer);
+}
+
+emscripten::val QWasmScreen::containerElement()
+{
+ return m_shadowContainer;
+}
+
+QWasmWindowTreeNode *QWasmScreen::parentNode()
+{
+ return nullptr;
+}
+
+QList<QWasmWindow *> QWasmScreen::allWindows()
+{
+ QList<QWasmWindow *> windows;
+ for (auto *child : childStack()) {
+ QWindowList list = child->window()->findChildren<QWindow *>(Qt::FindChildrenRecursively);
+ std::transform(
+ list.begin(), list.end(), std::back_inserter(windows),
+ [](const QWindow *window) { return static_cast<QWasmWindow *>(window->handle()); });
+ windows.push_back(child);
+ }
+ return windows;
}
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/wasm/qwasmscreen.h b/src/plugins/platforms/wasm/qwasmscreen.h
index 405ea7e7b8..da171d3f50 100644
--- a/src/plugins/platforms/wasm/qwasmscreen.h
+++ b/src/plugins/platforms/wasm/qwasmscreen.h
@@ -6,6 +6,8 @@
#include "qwasmcursor.h"
+#include "qwasmwindowtreenode.h"
+
#include <qpa/qplatformscreen.h>
#include <QtCore/qscopedpointer.h>
@@ -20,10 +22,10 @@ class QPlatformOpenGLContext;
class QWasmWindow;
class QWasmBackingStore;
class QWasmCompositor;
-class QWasmEventTranslator;
+class QWasmDeadKeySupport;
class QOpenGLContext;
-class QWasmScreen : public QObject, public QPlatformScreen
+class QWasmScreen : public QObject, public QPlatformScreen, public QWasmWindowTreeNode
{
Q_OBJECT
public:
@@ -33,13 +35,16 @@ public:
static QWasmScreen *get(QPlatformScreen *screen);
static QWasmScreen *get(QScreen *screen);
- emscripten::val container() const;
- emscripten::val canvas() const;
- QString canvasId() const;
- QString canvasTargetId() const;
+ emscripten::val element() const;
+ QString eventTargetId() const;
+ QString outerScreenId() const;
+ QPointingDevice *touchDevice() { return m_touchDevice.get(); }
+ QPointingDevice *tabletDevice() { return m_tabletDevice.get(); }
QWasmCompositor *compositor();
- QWasmEventTranslator *eventTranslator();
+ QWasmDeadKeySupport *deadKeySupport() { return m_deadKeySupport.get(); }
+
+ QList<QWasmWindow *> allWindows();
QRect geometry() const override;
int depth() const override;
@@ -53,7 +58,12 @@ public:
QWindow *topWindow() const;
QWindow *topLevelAt(const QPoint &p) const override;
- QPoint translateAndClipGlobalPoint(const QPoint &p) const;
+ // QWasmWindowTreeNode:
+ emscripten::val containerElement() final;
+ QWasmWindowTreeNode *parentNode() final;
+
+ QPointF mapFromLocal(const QPointF &p) const;
+ QPointF clipPoint(const QPointF &p) const;
void invalidateSize();
void updateQScreenAndCanvasRenderSize();
@@ -64,18 +74,22 @@ public slots:
void setGeometry(const QRect &rect);
private:
- std::string canvasSpecialHtmlTargetId() const;
- bool hasSpecialHtmlTargets() const;
+ // QWasmWindowTreeNode:
+ void onSubtreeChanged(QWasmWindowTreeNodeChangeType changeType, QWasmWindowTreeNode *parent,
+ QWasmWindow *child) final;
emscripten::val m_container;
- emscripten::val m_canvas;
+ emscripten::val m_intermediateContainer;
+ emscripten::val m_shadowContainer;
std::unique_ptr<QWasmCompositor> m_compositor;
- std::unique_ptr<QWasmEventTranslator> m_eventTranslator;
+ std::unique_ptr<QPointingDevice> m_touchDevice;
+ std::unique_ptr<QPointingDevice> m_tabletDevice;
+ std::unique_ptr<QWasmDeadKeySupport> m_deadKeySupport;
QRect m_geometry = QRect(0, 0, 100, 100);
int m_depth = 32;
QImage::Format m_format = QImage::Format_RGB32;
QWasmCursor m_cursor;
- static const char * m_canvasResizeObserverCallbackContextPropertyName;
+ static const char *m_canvasResizeObserverCallbackContextPropertyName;
std::unique_ptr<qstdweb::EventCallback> m_onContextMenu;
};
diff --git a/src/plugins/platforms/wasm/qwasmservices.cpp b/src/plugins/platforms/wasm/qwasmservices.cpp
index b9f48090e1..e767295e41 100644
--- a/src/plugins/platforms/wasm/qwasmservices.cpp
+++ b/src/plugins/platforms/wasm/qwasmservices.cpp
@@ -2,7 +2,6 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#include "qwasmservices.h"
-#include "qwasmstring.h"
#include <QtCore/QUrl>
#include <QtCore/QDebug>
@@ -13,8 +12,8 @@ QT_BEGIN_NAMESPACE
bool QWasmServices::openUrl(const QUrl &url)
{
- emscripten::val jsUrl = QWasmString::fromQString(url.toString());
- emscripten::val::global("window").call<void>("open", jsUrl, emscripten::val("_blank"));
+ emscripten::val::global("window").call<void>("open", url.toString().toEcmaString(),
+ emscripten::val("_blank"));
return true;
}
diff --git a/src/plugins/platforms/wasm/qwasmstring.cpp b/src/plugins/platforms/wasm/qwasmstring.cpp
deleted file mode 100644
index 3de84afef3..0000000000
--- a/src/plugins/platforms/wasm/qwasmstring.cpp
+++ /dev/null
@@ -1,36 +0,0 @@
-// Copyright (C) 2020 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
-
-#include "qwasmstring.h"
-
-QT_BEGIN_NAMESPACE
-
-using namespace emscripten;
-
-val QWasmString::fromQString(const QString &str)
-{
- static const val UTF16ToString(
- val::module_property("UTF16ToString"));
-
- auto ptr = quintptr(str.utf16());
- return UTF16ToString(val(ptr));
-}
-
-QString QWasmString::toQString(const val &v)
-{
- QString result;
- if (!v.isString())
- return result;
-
- static const val stringToUTF16(
- val::module_property("stringToUTF16"));
- static const val length("length");
-
- int len = v[length].as<int>();
- result.resize(len);
- auto ptr = quintptr(result.utf16());
- stringToUTF16(v, val(ptr), val((len + 1) * 2));
- return result;
-}
-
-QT_END_NAMESPACE
diff --git a/src/plugins/platforms/wasm/qwasmstring.h b/src/plugins/platforms/wasm/qwasmstring.h
deleted file mode 100644
index 62927ee93c..0000000000
--- a/src/plugins/platforms/wasm/qwasmstring.h
+++ /dev/null
@@ -1,19 +0,0 @@
-// Copyright (C) 2020 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
-
-#pragma once
-
-#include <qstring.h>
-
-#include <emscripten/val.h>
-
-QT_BEGIN_NAMESPACE
-
-class QWasmString
-{
-public:
- static emscripten::val fromQString(const QString &str);
- static QString toQString(const emscripten::val &v);
-};
-QT_END_NAMESPACE
-
diff --git a/src/plugins/platforms/wasm/qwasmstylepixmaps_p.h b/src/plugins/platforms/wasm/qwasmstylepixmaps_p.h
deleted file mode 100644
index 0f061a38b3..0000000000
--- a/src/plugins/platforms/wasm/qwasmstylepixmaps_p.h
+++ /dev/null
@@ -1,159 +0,0 @@
-// Copyright (C) 2018 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
-
-#ifndef QWASMSTYLEPIXMAPS_P_H
-#define QWASMSTYLEPIXMAPS_P_H
-
-//
-// W A R N I N G
-// -------------
-//
-// This file is not part of the Qt API. It exists purely as an
-// implementation detail. This header file may change from version to
-// version without notice, or even be removed.
-//
-// We mean it.
-//
-
-QT_BEGIN_NAMESPACE
-
-/* XPM */
-static const char * const qt_menu_xpm[] = {
-"16 16 72 1",
-" c None",
-". c #65AF36",
-"+ c #66B036",
-"@ c #77B94C",
-"# c #A7D28C",
-"$ c #BADBA4",
-"% c #A4D088",
-"& c #72B646",
-"* c #9ACB7A",
-"= c #7FBD56",
-"- c #85C05F",
-"; c #F4F9F0",
-"> c #FFFFFF",
-", c #E5F1DC",
-"' c #ECF5E7",
-") c #7ABA50",
-"! c #83BF5C",
-"~ c #AED595",
-"{ c #D7EACA",
-"] c #A9D28D",
-"^ c #BCDDA8",
-"/ c #C4E0B1",
-"( c #81BE59",
-"_ c #D0E7C2",
-": c #D4E9C6",
-"< c #6FB542",
-"[ c #6EB440",
-"} c #88C162",
-"| c #98CA78",
-"1 c #F4F9F1",
-"2 c #8FC56C",
-"3 c #F1F8EC",
-"4 c #E8F3E1",
-"5 c #D4E9C7",
-"6 c #74B748",
-"7 c #80BE59",
-"8 c #73B747",
-"9 c #6DB43F",
-"0 c #CBE4BA",
-"a c #80BD58",
-"b c #6DB33F",
-"c c #FEFFFE",
-"d c #68B138",
-"e c #F9FCF7",
-"f c #91C66F",
-"g c #E8F3E0",
-"h c #DCEDD0",
-"i c #91C66E",
-"j c #A3CF86",
-"k c #C9E3B8",
-"l c #B0D697",
-"m c #E3F0DA",
-"n c #95C873",
-"o c #E6F2DE",
-"p c #9ECD80",
-"q c #BEDEAA",
-"r c #C7E2B6",
-"s c #79BA4F",
-"t c #6EB441",
-"u c #BCDCA7",
-"v c #FAFCF8",
-"w c #F6FAF3",
-"x c #84BF5D",
-"y c #EDF6E7",
-"z c #FAFDF9",
-"A c #88C263",
-"B c #98CA77",
-"C c #CDE5BE",
-"D c #67B037",
-"E c #D9EBCD",
-"F c #6AB23C",
-"G c #77B94D",
-" .++++++++++++++",
-".+++++++++++++++",
-"+++@#$%&+++*=+++",
-"++-;>,>')+!>~+++",
-"++{>]+^>/(_>:~<+",
-"+[>>}+|>123>456+",
-"+7>>8+->>90>~+++",
-"+a>>b+a>c[0>~+++",
-"+de>=+f>g+0>~+++",
-"++h>i+j>k+0>~+++",
-"++l>mno>p+q>rst+",
-"++duv>wl++xy>zA+",
-"++++B>Cb++++&D++",
-"+++++0zE++++++++",
-"++++++FG+++++++.",
-"++++++++++++++. "};
-
-static const char * const qt_close_xpm[] = {
-"10 10 2 1",
-"# c #000000",
-". c None",
-"..........",
-".##....##.",
-"..##..##..",
-"...####...",
-"....##....",
-"...####...",
-"..##..##..",
-".##....##.",
-"..........",
-".........."};
-
-static const char * const qt_maximize_xpm[]={
-"10 10 2 1",
-"# c #000000",
-". c None",
-"#########.",
-"#########.",
-"#.......#.",
-"#.......#.",
-"#.......#.",
-"#.......#.",
-"#.......#.",
-"#.......#.",
-"#########.",
-".........."};
-
-
-static const char * const qt_normalizeup_xpm[] = {
-"10 10 2 1",
-"# c #000000",
-". c None",
-"...######.",
-"...######.",
-"...#....#.",
-".######.#.",
-".######.#.",
-".#....###.",
-".#....#...",
-".#....#...",
-".######...",
-".........."};
-
-QT_END_NAMESPACE
-#endif // QWASMSTYLEPIXMAPS_P_H
diff --git a/src/plugins/platforms/wasm/qwasmtheme.cpp b/src/plugins/platforms/wasm/qwasmtheme.cpp
index 51399159c4..b188dcb4b6 100644
--- a/src/plugins/platforms/wasm/qwasmtheme.cpp
+++ b/src/plugins/platforms/wasm/qwasmtheme.cpp
@@ -4,6 +4,7 @@
#include "qwasmtheme.h"
#include <QtCore/qvariant.h>
#include <QFontDatabase>
+#include <QList>
QT_BEGIN_NAMESPACE
diff --git a/src/plugins/platforms/wasm/qwasmwindow.cpp b/src/plugins/platforms/wasm/qwasmwindow.cpp
index bfcc556d6d..b8197c5113 100644
--- a/src/plugins/platforms/wasm/qwasmwindow.cpp
+++ b/src/plugins/platforms/wasm/qwasmwindow.cpp
@@ -3,71 +3,230 @@
#include <qpa/qwindowsysteminterface.h>
#include <private/qguiapplication_p.h>
-#include <QtGui/private/qopenglcontext_p.h>
+#include <QtCore/qfile.h>
#include <QtGui/private/qwindow_p.h>
-#include <QtGui/qopenglcontext.h>
-
+#include <QtGui/private/qhighdpiscaling_p.h>
+#include <private/qpixmapcache_p.h>
+#include <QtGui/qopenglfunctions.h>
+#include <QBuffer>
+
+#include "qwasmbase64iconstore.h"
+#include "qwasmdom.h"
+#include "qwasmclipboard.h"
+#include "qwasmintegration.h"
+#include "qwasmkeytranslator.h"
#include "qwasmwindow.h"
+#include "qwasmwindowclientarea.h"
#include "qwasmscreen.h"
#include "qwasmcompositor.h"
+#include "qwasmevent.h"
#include "qwasmeventdispatcher.h"
+#include "qwasmaccessibility.h"
+#include "qwasmclipboard.h"
#include <iostream>
+#include <sstream>
+
+#include <emscripten/val.h>
+#include <QtCore/private/qstdweb_p.h>
QT_BEGIN_NAMESPACE
+namespace {
+QWasmWindowStack::PositionPreference positionPreferenceFromWindowFlags(Qt::WindowFlags flags)
+{
+ if (flags.testFlag(Qt::WindowStaysOnTopHint))
+ return QWasmWindowStack::PositionPreference::StayOnTop;
+ if (flags.testFlag(Qt::WindowStaysOnBottomHint))
+ return QWasmWindowStack::PositionPreference::StayOnBottom;
+ return QWasmWindowStack::PositionPreference::Regular;
+}
+} // namespace
+
Q_GUI_EXPORT int qt_defaultDpiX();
-QWasmWindow::QWasmWindow(QWindow *w, QWasmCompositor *compositor, QWasmBackingStore *backingStore)
+QWasmWindow::QWasmWindow(QWindow *w, QWasmDeadKeySupport *deadKeySupport,
+ QWasmCompositor *compositor, QWasmBackingStore *backingStore)
: QPlatformWindow(w),
m_window(w),
m_compositor(compositor),
- m_backingStore(backingStore)
+ m_backingStore(backingStore),
+ m_deadKeySupport(deadKeySupport),
+ m_document(dom::document()),
+ m_qtWindow(m_document.call<emscripten::val>("createElement", emscripten::val("div"))),
+ m_windowContents(m_document.call<emscripten::val>("createElement", emscripten::val("div"))),
+ m_canvasContainer(m_document.call<emscripten::val>("createElement", emscripten::val("div"))),
+ m_a11yContainer(m_document.call<emscripten::val>("createElement", emscripten::val("div"))),
+ m_canvas(m_document.call<emscripten::val>("createElement", emscripten::val("canvas")))
{
- m_needsCompositor = w->surfaceType() != QSurface::OpenGLSurface;
+ m_qtWindow.set("className", "qt-window");
+ m_qtWindow["style"].set("display", std::string("none"));
+
+ m_nonClientArea = std::make_unique<NonClientArea>(this, m_qtWindow);
+ m_nonClientArea->titleBar()->setTitle(window()->title());
+
+ m_clientArea = std::make_unique<ClientArea>(this, compositor->screen(), m_windowContents);
+
+ m_windowContents.set("className", "qt-window-contents");
+ m_qtWindow.call<void>("appendChild", m_windowContents);
+
+ m_canvas["classList"].call<void>("add", emscripten::val("qt-window-content"));
+
+ // Set contenteditable so that the canvas gets clipboard events,
+ // then hide the resulting focus frame.
+ m_canvas.set("contentEditable", std::string("true"));
+ m_canvas["style"].set("outline", std::string("none"));
+
+ QWasmClipboard::installEventHandlers(m_canvas);
+
+ // set inputMode to none to stop mobile keyboard opening
+ // when user clicks anywhere on the canvas.
+ m_canvas.set("inputMode", std::string("none"));
+
+ // Hide the canvas from screen readers.
+ m_canvas.call<void>("setAttribute", std::string("aria-hidden"), std::string("true"));
+
+ m_windowContents.call<void>("appendChild", m_canvasContainer);
+
+ m_canvasContainer["classList"].call<void>("add", emscripten::val("qt-window-canvas-container"));
+ m_canvasContainer.call<void>("appendChild", m_canvas);
+
+ m_canvasContainer.call<void>("appendChild", m_a11yContainer);
+ m_a11yContainer["classList"].call<void>("add", emscripten::val("qt-window-a11y-container"));
+
+ const bool rendersTo2dContext = w->surfaceType() != QSurface::OpenGLSurface;
+ if (rendersTo2dContext)
+ m_context2d = m_canvas.call<emscripten::val>("getContext", emscripten::val("2d"));
static int serialNo = 0;
- m_winid = ++serialNo;
+ m_winId = ++serialNo;
+ m_qtWindow.set("id", "qt-window-" + std::to_string(m_winId));
+ emscripten::val::module_property("specialHTMLTargets").set(canvasSelector(), m_canvas);
+
+ m_flags = window()->flags();
+
+ const auto pointerCallback = std::function([this](emscripten::val event) {
+ if (processPointer(*PointerEvent::fromWeb(event)))
+ event.call<void>("preventDefault");
+ });
+
+ m_pointerEnterCallback =
+ std::make_unique<qstdweb::EventCallback>(m_qtWindow, "pointerenter", pointerCallback);
+ m_pointerLeaveCallback =
+ std::make_unique<qstdweb::EventCallback>(m_qtWindow, "pointerleave", pointerCallback);
+
+ m_wheelEventCallback = std::make_unique<qstdweb::EventCallback>(
+ m_qtWindow, "wheel", [this](emscripten::val event) {
+ if (processWheel(*WheelEvent::fromWeb(event)))
+ event.call<void>("preventDefault");
+ });
+
+ const auto keyCallback = std::function([this](emscripten::val event) {
+ if (processKey(*KeyEvent::fromWebWithDeadKeyTranslation(event, m_deadKeySupport)))
+ event.call<void>("preventDefault");
+ event.call<void>("stopPropagation");
+ });
+
+ emscripten::val keyFocusWindow;
+ if (QWasmIntegration::get()->inputContext()) {
+ QWasmInputContext *wasmContext =
+ static_cast<QWasmInputContext *>(QWasmIntegration::get()->inputContext());
+ // if there is an touchscreen input context,
+ // use that window for key input
+ keyFocusWindow = wasmContext->m_inputElement;
+ } else {
+ keyFocusWindow = m_qtWindow;
+ }
- m_compositor->addWindow(this);
+ m_keyDownCallback =
+ std::make_unique<qstdweb::EventCallback>(keyFocusWindow, "keydown", keyCallback);
+ m_keyUpCallback = std::make_unique<qstdweb::EventCallback>(keyFocusWindow, "keyup", keyCallback);
- // Pure OpenGL windows draw directly using egl, disable the compositor.
- m_compositor->setEnabled(w->surfaceType() != QSurface::OpenGLSurface);
+ setParent(parent());
}
QWasmWindow::~QWasmWindow()
{
- m_compositor->removeWindow(this);
+ emscripten::val::module_property("specialHTMLTargets").delete_(canvasSelector());
+ m_canvasContainer.call<void>("removeChild", m_canvas);
+ m_context2d = emscripten::val::undefined();
+ commitParent(nullptr);
if (m_requestAnimationFrameId > -1)
emscripten_cancel_animation_frame(m_requestAnimationFrameId);
+#if QT_CONFIG(accessibility)
+ QWasmAccessibility::removeAccessibilityEnableButton(window());
+#endif
}
-void QWasmWindow::destroy()
+QSurfaceFormat QWasmWindow::format() const
{
- if (m_backingStore)
- m_backingStore->destroy();
+ return window()->requestedFormat();
}
-void QWasmWindow::initialize()
+QWasmWindow *QWasmWindow::fromWindow(QWindow *window)
{
- QRect rect = windowGeometry();
+ return static_cast<QWasmWindow *>(window->handle());
+}
- QPlatformWindow::setGeometry(rect);
+void QWasmWindow::onRestoreClicked()
+{
+ window()->setWindowState(Qt::WindowNoState);
+}
- const QSize minimumSize = windowMinimumSize();
- if (rect.width() > 0 || rect.height() > 0) {
- rect.setWidth(qBound(1, rect.width(), 2000));
- rect.setHeight(qBound(1, rect.height(), 2000));
- } else if (minimumSize.width() > 0 || minimumSize.height() > 0) {
- rect.setSize(minimumSize);
- }
+void QWasmWindow::onMaximizeClicked()
+{
+ window()->setWindowState(Qt::WindowMaximized);
+}
+
+void QWasmWindow::onToggleMaximized()
+{
+ window()->setWindowState(m_state.testFlag(Qt::WindowMaximized) ? Qt::WindowNoState
+ : Qt::WindowMaximized);
+}
+
+void QWasmWindow::onCloseClicked()
+{
+ window()->close();
+}
+
+void QWasmWindow::onNonClientAreaInteraction()
+{
+ requestActivateWindow();
+ QGuiApplicationPrivate::instance()->closeAllPopups();
+}
+
+bool QWasmWindow::onNonClientEvent(const PointerEvent &event)
+{
+ QPointF pointInScreen = platformScreen()->mapFromLocal(
+ dom::mapPoint(event.target(), platformScreen()->element(), event.localPoint));
+ return QWindowSystemInterface::handleMouseEvent(
+ window(), QWasmIntegration::getTimestamp(), window()->mapFromGlobal(pointInScreen),
+ pointInScreen, event.mouseButtons, event.mouseButton,
+ MouseEvent::mouseEventTypeFromEventType(event.type, WindowArea::NonClient),
+ event.modifiers);
+}
+
+void QWasmWindow::initialize()
+{
+ auto initialGeometry = QPlatformWindow::initialGeometry(window(),
+ windowGeometry(), defaultWindowSize, defaultWindowSize);
+ m_normalGeometry = initialGeometry;
setWindowState(window()->windowStates());
setWindowFlags(window()->flags());
setWindowTitle(window()->title());
+ setMask(QHighDpi::toNativeLocalRegion(window()->mask(), window()));
+
if (window()->isTopLevel())
setWindowIcon(window()->icon());
- m_normalGeometry = rect;
+ QPlatformWindow::setGeometry(m_normalGeometry);
+
+#if QT_CONFIG(accessibility)
+ // Add accessibility-enable button. The user can activate this
+ // button to opt-in to accessibility.
+ if (window()->isTopLevel())
+ QWasmAccessibility::addAccessibilityEnableButton(window());
+#endif
}
QWasmScreen *QWasmWindow::platformScreen() const
@@ -75,351 +234,468 @@ QWasmScreen *QWasmWindow::platformScreen() const
return static_cast<QWasmScreen *>(window()->screen()->handle());
}
-void QWasmWindow::setGeometry(const QRect &rect)
+void QWasmWindow::paint()
+{
+ if (!m_backingStore || !isVisible() || m_context2d.isUndefined())
+ return;
+
+ auto image = m_backingStore->getUpdatedWebImage(this);
+ if (image.isUndefined())
+ return;
+ m_context2d.call<void>("putImageData", image, emscripten::val(0), emscripten::val(0));
+}
+
+void QWasmWindow::setZOrder(int z)
{
- QRect r = rect;
- if (m_needsCompositor) {
- int yMin = window()->geometry().top() - window()->frameGeometry().top();
+ m_qtWindow["style"].set("zIndex", std::to_string(z));
+}
- if (r.y() < yMin)
- r.moveTop(yMin);
+void QWasmWindow::setWindowCursor(QByteArray cssCursorName)
+{
+ m_windowContents["style"].set("cursor", emscripten::val(cssCursorName.constData()));
+}
+
+void QWasmWindow::setGeometry(const QRect &rect)
+{
+ const auto margins = frameMargins();
+
+ const QRect clientAreaRect = ([this, &rect, &margins]() {
+ if (m_state.testFlag(Qt::WindowFullScreen))
+ return platformScreen()->geometry();
+ if (m_state.testFlag(Qt::WindowMaximized))
+ return platformScreen()->availableGeometry().marginsRemoved(frameMargins());
+
+ auto offset = rect.topLeft() - (!parent() ? screen()->geometry().topLeft() : QPoint());
+
+ // In viewport
+ auto containerGeometryInViewport =
+ QRectF::fromDOMRect(parentNode()->containerElement().call<emscripten::val>(
+ "getBoundingClientRect"))
+ .toRect();
+
+ auto rectInViewport = QRect(containerGeometryInViewport.topLeft() + offset, rect.size());
+
+ QRect cappedGeometry(rectInViewport);
+ if (!parent()) {
+ // Clamp top level windows top position to the screen bounds
+ cappedGeometry.moveTop(
+ std::max(std::min(rectInViewport.y(), containerGeometryInViewport.bottom()),
+ containerGeometryInViewport.y() + margins.top()));
+ }
+ cappedGeometry.setSize(
+ cappedGeometry.size().expandedTo(windowMinimumSize()).boundedTo(windowMaximumSize()));
+ return QRect(QPoint(rect.x(), rect.y() + cappedGeometry.y() - rectInViewport.y()),
+ rect.size());
+ })();
+ m_nonClientArea->onClientAreaWidthChange(clientAreaRect.width());
+
+ const auto frameRect =
+ clientAreaRect
+ .adjusted(-margins.left(), -margins.top(), margins.right(), margins.bottom())
+ .translated(!parent() ? -screen()->geometry().topLeft() : QPoint());
+
+ m_qtWindow["style"].set("left", std::to_string(frameRect.left()) + "px");
+ m_qtWindow["style"].set("top", std::to_string(frameRect.top()) + "px");
+ m_canvasContainer["style"].set("width", std::to_string(clientAreaRect.width()) + "px");
+ m_canvasContainer["style"].set("height", std::to_string(clientAreaRect.height()) + "px");
+ m_a11yContainer["style"].set("width", std::to_string(clientAreaRect.width()) + "px");
+ m_a11yContainer["style"].set("height", std::to_string(clientAreaRect.height()) + "px");
+
+ // Important for the title flexbox to shrink correctly
+ m_windowContents["style"].set("width", std::to_string(clientAreaRect.width()) + "px");
+
+ QSizeF canvasSize = clientAreaRect.size() * devicePixelRatio();
+
+ m_canvas.set("width", canvasSize.width());
+ m_canvas.set("height", canvasSize.height());
+
+ bool shouldInvalidate = true;
+ if (!m_state.testFlag(Qt::WindowFullScreen) && !m_state.testFlag(Qt::WindowMaximized)) {
+ shouldInvalidate = m_normalGeometry.size() != clientAreaRect.size();
+ m_normalGeometry = clientAreaRect;
}
- if (!m_windowState.testFlag(Qt::WindowFullScreen) && !m_windowState.testFlag(Qt::WindowMaximized))
- m_normalGeometry = r;
- QPlatformWindow::setGeometry(r);
- QWindowSystemInterface::handleGeometryChange(window(), r);
- invalidate();
+ QWindowSystemInterface::handleGeometryChange(window(), clientAreaRect);
+ if (shouldInvalidate)
+ invalidate();
+ else
+ m_compositor->requestUpdateWindow(this);
}
void QWasmWindow::setVisible(bool visible)
{
+ // TODO(mikolajboc): isVisible()?
+ const bool nowVisible = m_qtWindow["style"]["display"].as<std::string>() == "block";
+ if (visible == nowVisible)
+ return;
+
+ m_compositor->requestUpdateWindow(this, QWasmCompositor::ExposeEventDelivery);
+ m_qtWindow["style"].set("display", visible ? "block" : "none");
+ if (window()->isActive())
+ m_canvas.call<void>("focus");
if (visible)
applyWindowState();
- m_compositor->setVisible(this, visible);
}
-bool QWasmWindow::isVisible()
+bool QWasmWindow::isVisible() const
{
return window()->isVisible();
}
QMargins QWasmWindow::frameMargins() const
{
- int border = hasTitleBar() ? 4. * (qreal(qt_defaultDpiX()) / 96.0) : 0;
- int titleBarHeight = hasTitleBar() ? titleHeight() : 0;
-
- QMargins margins;
- margins.setLeft(border);
- margins.setRight(border);
- margins.setTop(2*border + titleBarHeight);
- margins.setBottom(border);
-
- return margins;
+ const auto frameRect =
+ QRectF::fromDOMRect(m_qtWindow.call<emscripten::val>("getBoundingClientRect"));
+ const auto canvasRect =
+ QRectF::fromDOMRect(m_windowContents.call<emscripten::val>("getBoundingClientRect"));
+ return QMarginsF(canvasRect.left() - frameRect.left(), canvasRect.top() - frameRect.top(),
+ frameRect.right() - canvasRect.right(),
+ frameRect.bottom() - canvasRect.bottom())
+ .toMargins();
}
void QWasmWindow::raise()
{
- m_compositor->raise(this);
- if (window()->isVisible())
- QWindowSystemInterface::handleExposeEvent(window(), QRect(QPoint(0, 0), geometry().size()));
+ bringToTop();
invalidate();
+ if (QWasmIntegration::get()->inputContext())
+ m_canvas.call<void>("focus");
}
void QWasmWindow::lower()
{
- m_compositor->lower(this);
- if (window()->isVisible())
- QWindowSystemInterface::handleExposeEvent(window(), QRect(QPoint(0, 0), geometry().size()));
+ sendToBottom();
invalidate();
}
WId QWasmWindow::winId() const
{
- return m_winid;
+ return m_winId;
}
void QWasmWindow::propagateSizeHints()
{
- QRect rect = windowGeometry();
- if (rect.size().width() < windowMinimumSize().width()
- && rect.size().height() < windowMinimumSize().height()) {
- rect.setSize(windowMinimumSize());
- setGeometry(rect);
- }
+ // setGeometry() will take care of minimum and maximum size constraints
+ setGeometry(windowGeometry());
+ m_nonClientArea->propagateSizeHints();
}
-void QWasmWindow::injectMousePressed(const QPoint &local, const QPoint &global,
- Qt::MouseButton button, Qt::KeyboardModifiers mods)
+void QWasmWindow::setOpacity(qreal level)
{
- Q_UNUSED(local);
- Q_UNUSED(mods);
+ m_qtWindow["style"].set("opacity", qBound(0.0, level, 1.0));
+}
- if (!hasTitleBar() || button != Qt::LeftButton)
- return;
+void QWasmWindow::invalidate()
+{
+ m_compositor->requestUpdateWindow(this);
+}
- if (maxButtonRect().contains(global))
- m_activeControl = QWasmCompositor::SC_TitleBarMaxButton;
- else if (minButtonRect().contains(global))
- m_activeControl = QWasmCompositor::SC_TitleBarMinButton;
- else if (closeButtonRect().contains(global))
- m_activeControl = QWasmCompositor::SC_TitleBarCloseButton;
- else if (normButtonRect().contains(global))
- m_activeControl = QWasmCompositor::SC_TitleBarNormalButton;
+void QWasmWindow::onActivationChanged(bool active)
+{
+ dom::syncCSSClassWith(m_qtWindow, "inactive", !active);
+}
- invalidate();
+void QWasmWindow::setWindowFlags(Qt::WindowFlags flags)
+{
+ if (flags.testFlag(Qt::WindowStaysOnTopHint) != m_flags.testFlag(Qt::WindowStaysOnTopHint)
+ || flags.testFlag(Qt::WindowStaysOnBottomHint)
+ != m_flags.testFlag(Qt::WindowStaysOnBottomHint)) {
+ onPositionPreferenceChanged(positionPreferenceFromWindowFlags(flags));
+ }
+ m_flags = flags;
+ dom::syncCSSClassWith(m_qtWindow, "frameless", !hasFrame());
+ dom::syncCSSClassWith(m_qtWindow, "has-border", hasBorder());
+ dom::syncCSSClassWith(m_qtWindow, "has-shadow", hasShadow());
+ dom::syncCSSClassWith(m_qtWindow, "has-title", hasTitleBar());
+ dom::syncCSSClassWith(m_qtWindow, "transparent-for-input",
+ flags.testFlag(Qt::WindowTransparentForInput));
+
+ m_nonClientArea->titleBar()->setMaximizeVisible(hasMaximizeButton());
+ m_nonClientArea->titleBar()->setCloseVisible(m_flags.testFlag(Qt::WindowCloseButtonHint));
}
-void QWasmWindow::injectMouseReleased(const QPoint &local, const QPoint &global,
- Qt::MouseButton button, Qt::KeyboardModifiers mods)
+void QWasmWindow::setWindowState(Qt::WindowStates newState)
{
- Q_UNUSED(local);
- Q_UNUSED(mods);
+ // Child windows can not have window states other than Qt::WindowActive
+ if (parent())
+ newState &= Qt::WindowActive;
- if (!hasTitleBar() || button != Qt::LeftButton)
- return;
+ const Qt::WindowStates oldState = m_state;
- if (closeButtonRect().contains(global) && m_activeControl == QWasmCompositor::SC_TitleBarCloseButton) {
- window()->close();
+ if (newState.testFlag(Qt::WindowMinimized)) {
+ newState.setFlag(Qt::WindowMinimized, false);
+ qWarning("Qt::WindowMinimized is not implemented in wasm");
+ window()->setWindowStates(newState);
return;
}
- if (maxButtonRect().contains(global) && m_activeControl == QWasmCompositor::SC_TitleBarMaxButton) {
- window()->setWindowState(Qt::WindowMaximized);
- }
-
- if (normButtonRect().contains(global) && m_activeControl == QWasmCompositor::SC_TitleBarNormalButton) {
- window()->setWindowState(Qt::WindowNoState);
- }
+ if (newState == oldState)
+ return;
- m_activeControl = QWasmCompositor::SC_None;
+ m_state = newState;
+ m_previousWindowState = oldState;
- invalidate();
+ applyWindowState();
}
-int QWasmWindow::titleHeight() const
+void QWasmWindow::setWindowTitle(const QString &title)
{
- return 18. * (qreal(qt_defaultDpiX()) / 96.0);//dpiScaled(18.);
+ m_nonClientArea->titleBar()->setTitle(title);
}
-int QWasmWindow::borderWidth() const
+void QWasmWindow::setWindowIcon(const QIcon &icon)
{
- return 4. * (qreal(qt_defaultDpiX()) / 96.0);// dpiScaled(4.);
+ const auto dpi = screen()->devicePixelRatio();
+ auto pixmap = icon.pixmap(10 * dpi, 10 * dpi);
+ if (pixmap.isNull()) {
+ m_nonClientArea->titleBar()->setIcon(
+ Base64IconStore::get()->getIcon(Base64IconStore::IconType::QtLogo), "svg+xml");
+ return;
+ }
+
+ QByteArray bytes;
+ QBuffer buffer(&bytes);
+ pixmap.save(&buffer, "png");
+ m_nonClientArea->titleBar()->setIcon(bytes.toBase64().toStdString(), "png");
}
-QRegion QWasmWindow::titleGeometry() const
+void QWasmWindow::applyWindowState()
{
- int border = borderWidth();
+ QRect newGeom;
+
+ const bool isFullscreen = m_state.testFlag(Qt::WindowFullScreen);
+ const bool isMaximized = m_state.testFlag(Qt::WindowMaximized);
+ if (isFullscreen)
+ newGeom = platformScreen()->geometry();
+ else if (isMaximized)
+ newGeom = platformScreen()->availableGeometry().marginsRemoved(frameMargins());
+ else
+ newGeom = normalGeometry();
- QRegion result(window()->frameGeometry().x() + border,
- window()->frameGeometry().y() + border,
- window()->frameGeometry().width() - 2*border,
- titleHeight());
+ dom::syncCSSClassWith(m_qtWindow, "has-border", hasBorder());
+ dom::syncCSSClassWith(m_qtWindow, "maximized", isMaximized);
- result -= titleControlRegion();
+ m_nonClientArea->titleBar()->setRestoreVisible(isMaximized);
+ m_nonClientArea->titleBar()->setMaximizeVisible(hasMaximizeButton());
- return result;
+ if (isVisible())
+ QWindowSystemInterface::handleWindowStateChanged(window(), m_state, m_previousWindowState);
+ setGeometry(newGeom);
}
-QRegion QWasmWindow::resizeRegion() const
+void QWasmWindow::commitParent(QWasmWindowTreeNode *parent)
{
- int border = borderWidth();
- QRegion result(window()->frameGeometry().adjusted(-border, -border, border, border));
- result -= window()->frameGeometry().adjusted(border, border, -border, -border);
-
- return result;
+ onParentChanged(m_commitedParent, parent, positionPreferenceFromWindowFlags(window()->flags()));
+ m_commitedParent = parent;
}
-bool QWasmWindow::isPointOnTitle(QPoint point) const
+bool QWasmWindow::processKey(const KeyEvent &event)
{
- return hasTitleBar() ? titleGeometry().contains(point) : false;
+ constexpr bool ProceedToNativeEvent = false;
+ Q_ASSERT(event.type == EventType::KeyDown || event.type == EventType::KeyUp);
+
+ const auto clipboardResult =
+ QWasmIntegration::get()->getWasmClipboard()->processKeyboard(event);
+
+ using ProcessKeyboardResult = QWasmClipboard::ProcessKeyboardResult;
+ if (clipboardResult == ProcessKeyboardResult::NativeClipboardEventNeeded)
+ return ProceedToNativeEvent;
+
+ const auto result = QWindowSystemInterface::handleKeyEvent(
+ 0, event.type == EventType::KeyDown ? QEvent::KeyPress : QEvent::KeyRelease, event.key,
+ event.modifiers, event.text);
+ return clipboardResult == ProcessKeyboardResult::NativeClipboardEventAndCopiedDataNeeded
+ ? ProceedToNativeEvent
+ : result;
}
-bool QWasmWindow::isPointOnResizeRegion(QPoint point) const
+bool QWasmWindow::processPointer(const PointerEvent &event)
{
- if (window()->flags().testFlag(Qt::Popup))
+ if (event.pointerType != PointerType::Mouse && event.pointerType != PointerType::Pen)
return false;
- return resizeRegion().contains(point);
-}
-
-QWasmCompositor::ResizeMode QWasmWindow::resizeModeAtPoint(QPoint point) const
-{
- QPoint p1 = window()->frameGeometry().topLeft() - QPoint(5, 5);
- QPoint p2 = window()->frameGeometry().bottomRight() + QPoint(5, 5);
- int corner = 20;
-
- QRect top(p1, QPoint(p2.x(), p1.y() + corner));
- QRect middle(QPoint(p1.x(), p1.y() + corner), QPoint(p2.x(), p2.y() - corner));
- QRect bottom(QPoint(p1.x(), p2.y() - corner), p2);
-
- QRect left(p1, QPoint(p1.x() + corner, p2.y()));
- QRect center(QPoint(p1.x() + corner, p1.y()), QPoint(p2.x() - corner, p2.y()));
- QRect right(QPoint(p2.x() - corner, p1.y()), p2);
-
- if (top.contains(point)) {
- // Top
- if (left.contains(point))
- return QWasmCompositor::ResizeTopLeft;
- if (center.contains(point))
- return QWasmCompositor::ResizeTop;
- if (right.contains(point))
- return QWasmCompositor::ResizeTopRight;
- } else if (middle.contains(point)) {
- // Middle
- if (left.contains(point))
- return QWasmCompositor::ResizeLeft;
- if (right.contains(point))
- return QWasmCompositor::ResizeRight;
- } else if (bottom.contains(point)) {
- // Bottom
- if (left.contains(point))
- return QWasmCompositor::ResizeBottomLeft;
- if (center.contains(point))
- return QWasmCompositor::ResizeBottom;
- if (right.contains(point))
- return QWasmCompositor::ResizeBottomRight;
+
+ switch (event.type) {
+ case EventType::PointerEnter: {
+ const auto pointInScreen = platformScreen()->mapFromLocal(
+ dom::mapPoint(event.target(), platformScreen()->element(), event.localPoint));
+ QWindowSystemInterface::handleEnterEvent(
+ window(), m_window->mapFromGlobal(pointInScreen), pointInScreen);
+ break;
+ }
+ case EventType::PointerLeave:
+ QWindowSystemInterface::handleLeaveEvent(window());
+ break;
+ default:
+ break;
}
- return QWasmCompositor::ResizeNone;
+ return false;
}
-QRect getSubControlRect(const QWasmWindow *window, QWasmCompositor::SubControls subControl)
+bool QWasmWindow::processWheel(const WheelEvent &event)
{
- QWasmCompositor::QWasmTitleBarOptions options = QWasmCompositor::makeTitleBarOptions(window);
+ // Web scroll deltas are inverted from Qt deltas - negate.
+ const int scrollFactor = -([&event]() {
+ switch (event.deltaMode) {
+ case DeltaMode::Pixel:
+ return 1;
+ case DeltaMode::Line:
+ return 12;
+ case DeltaMode::Page:
+ return 20;
+ };
+ })();
- QRect r = QWasmCompositor::titlebarRect(options, subControl);
- r.translate(window->window()->frameGeometry().x(), window->window()->frameGeometry().y());
+ const auto pointInScreen = platformScreen()->mapFromLocal(
+ dom::mapPoint(event.target(), platformScreen()->element(), event.localPoint));
- return r;
+ return QWindowSystemInterface::handleWheelEvent(
+ window(), QWasmIntegration::getTimestamp(), window()->mapFromGlobal(pointInScreen),
+ pointInScreen, (event.delta * scrollFactor).toPoint(),
+ (event.delta * scrollFactor).toPoint(), event.modifiers, Qt::NoScrollPhase,
+ Qt::MouseEventNotSynthesized, event.webkitDirectionInvertedFromDevice);
}
-QRect QWasmWindow::maxButtonRect() const
+QRect QWasmWindow::normalGeometry() const
{
- return getSubControlRect(this, QWasmCompositor::SC_TitleBarMaxButton);
+ return m_normalGeometry;
}
-QRect QWasmWindow::minButtonRect() const
+qreal QWasmWindow::devicePixelRatio() const
{
- return getSubControlRect(this, QWasmCompositor::SC_TitleBarMinButton);
+ return screen()->devicePixelRatio();
}
-QRect QWasmWindow::closeButtonRect() const
+void QWasmWindow::requestUpdate()
{
- return getSubControlRect(this, QWasmCompositor::SC_TitleBarCloseButton);
+ m_compositor->requestUpdateWindow(this, QWasmCompositor::UpdateRequestDelivery);
}
-QRect QWasmWindow::normButtonRect() const
+bool QWasmWindow::hasFrame() const
{
- return getSubControlRect(this, QWasmCompositor::SC_TitleBarNormalButton);
+ return !m_flags.testFlag(Qt::FramelessWindowHint);
}
-QRect QWasmWindow::sysMenuRect() const
+bool QWasmWindow::hasBorder() const
{
- return getSubControlRect(this, QWasmCompositor::SC_TitleBarSysMenu);
+ return hasFrame() && !m_state.testFlag(Qt::WindowFullScreen) && !m_flags.testFlag(Qt::SubWindow)
+ && !windowIsPopupType(m_flags) && !parent();
}
-QRegion QWasmWindow::titleControlRegion() const
+bool QWasmWindow::hasTitleBar() const
{
- QRegion result;
- result += closeButtonRect();
- result += minButtonRect();
- result += maxButtonRect();
- result += sysMenuRect();
-
- return result;
+ return hasBorder() && m_flags.testFlag(Qt::WindowTitleHint);
}
-void QWasmWindow::invalidate()
+bool QWasmWindow::hasShadow() const
{
- m_compositor->requestUpdateWindow(this);
+ return hasBorder() && !m_flags.testFlag(Qt::NoDropShadowWindowHint);
}
-QWasmCompositor::SubControls QWasmWindow::activeSubControl() const
+bool QWasmWindow::hasMaximizeButton() const
{
- return m_activeControl;
+ return !m_state.testFlag(Qt::WindowMaximized) && m_flags.testFlag(Qt::WindowMaximizeButtonHint);
}
-void QWasmWindow::setWindowState(Qt::WindowStates newState)
+bool QWasmWindow::windowIsPopupType(Qt::WindowFlags flags) const
{
- const Qt::WindowStates oldState = m_windowState;
- bool isActive = oldState.testFlag(Qt::WindowActive);
+ if (flags.testFlag(Qt::Tool))
+ return false; // Qt::Tool has the Popup bit set but isn't an actual Popup window
- if (newState.testFlag(Qt::WindowMinimized)) {
- newState.setFlag(Qt::WindowMinimized, false);
- qWarning("Qt::WindowMinimized is not implemented in wasm");
- }
+ return (flags.testFlag(Qt::Popup));
+}
- // Always keep OpenGL apps fullscreen
- if (!m_needsCompositor && !newState.testFlag(Qt::WindowFullScreen)) {
- newState.setFlag(Qt::WindowFullScreen, true);
- qWarning("Qt::WindowFullScreen must be set for OpenGL surfaces");
+void QWasmWindow::requestActivateWindow()
+{
+ QWindow *modalWindow;
+ if (QGuiApplicationPrivate::instance()->isWindowBlocked(window(), &modalWindow)) {
+ static_cast<QWasmWindow *>(modalWindow->handle())->requestActivateWindow();
+ return;
}
- // Ignore WindowActive flag in comparison, as we want to preserve it either way
- if ((newState & ~Qt::WindowActive) == (oldState & ~Qt::WindowActive))
- return;
+ raise();
+ setAsActiveNode();
- newState.setFlag(Qt::WindowActive, isActive);
+ if (!QWasmIntegration::get()->inputContext())
+ m_canvas.call<void>("focus");
- m_previousWindowState = oldState;
- m_windowState = newState;
+ QPlatformWindow::requestActivateWindow();
+}
- if (isVisible()) {
- applyWindowState();
- }
+bool QWasmWindow::setMouseGrabEnabled(bool grab)
+{
+ Q_UNUSED(grab);
+ return false;
}
-void QWasmWindow::applyWindowState()
+bool QWasmWindow::windowEvent(QEvent *event)
{
- QRect newGeom;
+ switch (event->type()) {
+ case QEvent::WindowBlocked:
+ m_qtWindow["classList"].call<void>("add", emscripten::val("blocked"));
+ return false; // Propagate further
+ case QEvent::WindowUnblocked:;
+ m_qtWindow["classList"].call<void>("remove", emscripten::val("blocked"));
+ return false; // Propagate further
+ default:
+ return QPlatformWindow::windowEvent(event);
+ }
+}
- if (m_windowState.testFlag(Qt::WindowFullScreen))
- newGeom = platformScreen()->geometry();
- else if (m_windowState.testFlag(Qt::WindowMaximized))
- newGeom = platformScreen()->availableGeometry();
- else
- newGeom = normalGeometry();
+void QWasmWindow::setMask(const QRegion &region)
+{
+ if (region.isEmpty()) {
+ m_qtWindow["style"].set("clipPath", emscripten::val(""));
+ return;
+ }
- QWindowSystemInterface::handleWindowStateChanged(window(), m_windowState, m_previousWindowState);
- setGeometry(newGeom);
+ std::ostringstream cssClipPath;
+ cssClipPath << "path('";
+ for (const auto &rect : region) {
+ const auto cssRect = rect.adjusted(0, 0, 1, 1);
+ cssClipPath << "M " << cssRect.left() << " " << cssRect.top() << " ";
+ cssClipPath << "L " << cssRect.right() << " " << cssRect.top() << " ";
+ cssClipPath << "L " << cssRect.right() << " " << cssRect.bottom() << " ";
+ cssClipPath << "L " << cssRect.left() << " " << cssRect.bottom() << " z ";
+ }
+ cssClipPath << "')";
+ m_qtWindow["style"].set("clipPath", emscripten::val(cssClipPath.str()));
}
-QRect QWasmWindow::normalGeometry() const
+void QWasmWindow::setParent(const QPlatformWindow *)
{
- return m_normalGeometry;
+ commitParent(parentNode());
}
-qreal QWasmWindow::devicePixelRatio() const
+std::string QWasmWindow::canvasSelector() const
{
- return screen()->devicePixelRatio();
+ return "!qtwindow" + std::to_string(m_winId);
}
-void QWasmWindow::requestUpdate()
+emscripten::val QWasmWindow::containerElement()
{
- m_compositor->requestUpdateWindow(this, QWasmCompositor::UpdateRequestDelivery);
+ return m_windowContents;
}
-bool QWasmWindow::hasTitleBar() const
+QWasmWindowTreeNode *QWasmWindow::parentNode()
{
- Qt::WindowFlags flags = window()->flags();
- return !(m_windowState & Qt::WindowFullScreen)
- && flags.testFlag(Qt::WindowTitleHint)
- && !(windowIsPopupType(flags))
- && m_needsCompositor;
+ if (parent())
+ return static_cast<QWasmWindow *>(parent());
+ return platformScreen();
}
-bool QWasmWindow::windowIsPopupType(Qt::WindowFlags flags) const
+QWasmWindow *QWasmWindow::asWasmWindow()
{
- if (flags.testFlag(Qt::Tool))
- return false; // Qt::Tool has the Popup bit set but isn't
-
- return (flags.testFlag(Qt::Popup));
+ return this;
}
-void QWasmWindow::requestActivateWindow()
+void QWasmWindow::onParentChanged(QWasmWindowTreeNode *previous, QWasmWindowTreeNode *current,
+ QWasmWindowStack::PositionPreference positionPreference)
{
- if (window()->isTopLevel())
- raise();
- QPlatformWindow::requestActivateWindow();
+ if (previous)
+ previous->containerElement().call<void>("removeChild", m_qtWindow);
+ if (current)
+ current->containerElement().call<void>("appendChild", m_qtWindow);
+ QWasmWindowTreeNode::onParentChanged(previous, current, positionPreference);
}
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/wasm/qwasmwindow.h b/src/plugins/platforms/wasm/qwasmwindow.h
index 6503c4a5c6..ab0dc68e83 100644
--- a/src/plugins/platforms/wasm/qwasmwindow.h
+++ b/src/plugins/platforms/wasm/qwasmwindow.h
@@ -6,83 +6,163 @@
#include "qwasmintegration.h"
#include <qpa/qplatformwindow.h>
+#include <qpa/qplatformwindow_p.h>
#include <emscripten/html5.h>
#include "qwasmbackingstore.h"
#include "qwasmscreen.h"
#include "qwasmcompositor.h"
+#include "qwasmwindownonclientarea.h"
+#include "qwasmwindowstack.h"
+#include "qwasmwindowtreenode.h"
+
+#include <QtCore/private/qstdweb_p.h>
+#include "QtGui/qopenglcontext.h"
+#include <QtOpenGL/qopengltextureblitter.h>
+
+#include <emscripten/val.h>
+
+#include <memory>
QT_BEGIN_NAMESPACE
-class QWasmWindow : public QPlatformWindow
+namespace qstdweb {
+class EventCallback;
+}
+
+class ClientArea;
+struct KeyEvent;
+struct PointerEvent;
+class QWasmDeadKeySupport;
+struct WheelEvent;
+
+class QWasmWindow final : public QPlatformWindow,
+ public QWasmWindowTreeNode,
+ public QNativeInterface::Private::QWasmWindow
{
public:
- QWasmWindow(QWindow *w, QWasmCompositor *compositor, QWasmBackingStore *backingStore);
- ~QWasmWindow();
- void destroy();
-
+ QWasmWindow(QWindow *w, QWasmDeadKeySupport *deadKeySupport, QWasmCompositor *compositor,
+ QWasmBackingStore *backingStore);
+ ~QWasmWindow() final;
+
+ static QWasmWindow *fromWindow(QWindow *window);
+ QSurfaceFormat format() const override;
+
+ void paint();
+ void setZOrder(int order);
+ void setWindowCursor(QByteArray cssCursorName);
+ void onActivationChanged(bool active);
+ bool isVisible() const;
+
+ void onNonClientAreaInteraction();
+ void onRestoreClicked();
+ void onMaximizeClicked();
+ void onToggleMaximized();
+ void onCloseClicked();
+ bool onNonClientEvent(const PointerEvent &event);
+
+ // QPlatformWindow:
void initialize() override;
-
void setGeometry(const QRect &) override;
void setVisible(bool visible) override;
- bool isVisible();
QMargins frameMargins() const override;
-
WId winId() const override;
-
void propagateSizeHints() override;
+ void setOpacity(qreal level) override;
void raise() override;
void lower() override;
QRect normalGeometry() const override;
qreal devicePixelRatio() const override;
void requestUpdate() override;
void requestActivateWindow() override;
+ void setWindowFlags(Qt::WindowFlags flags) override;
+ void setWindowState(Qt::WindowStates state) override;
+ void setWindowTitle(const QString &title) override;
+ void setWindowIcon(const QIcon &icon) override;
+ bool setKeyboardGrabEnabled(bool) override { return false; }
+ bool setMouseGrabEnabled(bool grab) final;
+ bool windowEvent(QEvent *event) final;
+ void setMask(const QRegion &region) final;
+ void setParent(const QPlatformWindow *window) final;
QWasmScreen *platformScreen() const;
void setBackingStore(QWasmBackingStore *store) { m_backingStore = store; }
QWasmBackingStore *backingStore() const { return m_backingStore; }
QWindow *window() const { return m_window; }
- void injectMousePressed(const QPoint &local, const QPoint &global,
- Qt::MouseButton button, Qt::KeyboardModifiers mods);
- void injectMouseReleased(const QPoint &local, const QPoint &global,
- Qt::MouseButton button, Qt::KeyboardModifiers mods);
-
- int titleHeight() const;
- int borderWidth() const;
- QRegion titleGeometry() const;
- QRegion resizeRegion() const;
- bool isPointOnTitle(QPoint point) const;
- bool isPointOnResizeRegion(QPoint point) const;
- QWasmCompositor::ResizeMode resizeModeAtPoint(QPoint point) const;
- QRect maxButtonRect() const;
- QRect minButtonRect() const;
- QRect closeButtonRect() const;
- QRect sysMenuRect() const;
- QRect normButtonRect() const;
- QRegion titleControlRegion() const;
- QWasmCompositor::SubControls activeSubControl() const;
+ std::string canvasSelector() const;
- void setWindowState(Qt::WindowStates state) override;
- void applyWindowState();
- bool setKeyboardGrabEnabled(bool) override { return false; }
- bool setMouseGrabEnabled(bool) override { return false; }
+ emscripten::val context2d() const { return m_context2d; }
+ emscripten::val a11yContainer() const { return m_a11yContainer; }
+ emscripten::val inputHandlerElement() const { return m_windowContents; }
+
+ // QNativeInterface::Private::QWasmWindow
+ emscripten::val document() const override { return m_document; }
+ emscripten::val clientArea() const override { return m_qtWindow; }
+
+ // QWasmWindowTreeNode:
+ emscripten::val containerElement() final;
+ QWasmWindowTreeNode *parentNode() final;
+
+private:
+ friend class QWasmScreen;
+ static constexpr auto defaultWindowSize = 160;
+
+ // QWasmWindowTreeNode:
+ QWasmWindow *asWasmWindow() final;
+ void onParentChanged(QWasmWindowTreeNode *previous, QWasmWindowTreeNode *current,
+ QWasmWindowStack::PositionPreference positionPreference) final;
-protected:
void invalidate();
+ bool hasFrame() const;
bool hasTitleBar() const;
+ bool hasBorder() const;
+ bool hasShadow() const;
+ bool hasMaximizeButton() const;
+ void applyWindowState();
+ void commitParent(QWasmWindowTreeNode *parent);
-protected:
- friend class QWasmScreen;
+ bool processKey(const KeyEvent &event);
+ bool processPointer(const PointerEvent &event);
+ bool processWheel(const WheelEvent &event);
- QWindow* m_window = nullptr;
+ QWindow *m_window = nullptr;
QWasmCompositor *m_compositor = nullptr;
QWasmBackingStore *m_backingStore = nullptr;
+ QWasmDeadKeySupport *m_deadKeySupport;
QRect m_normalGeometry {0, 0, 0 ,0};
- Qt::WindowStates m_windowState = Qt::WindowNoState;
+ emscripten::val m_document;
+ emscripten::val m_qtWindow;
+ emscripten::val m_windowContents;
+ emscripten::val m_canvasContainer;
+ emscripten::val m_a11yContainer;
+ emscripten::val m_canvas;
+ emscripten::val m_context2d = emscripten::val::undefined();
+
+ std::unique_ptr<NonClientArea> m_nonClientArea;
+ std::unique_ptr<ClientArea> m_clientArea;
+
+ QWasmWindowTreeNode *m_commitedParent = nullptr;
+
+ std::unique_ptr<qstdweb::EventCallback> m_keyDownCallback;
+ std::unique_ptr<qstdweb::EventCallback> m_keyUpCallback;
+
+ std::unique_ptr<qstdweb::EventCallback> m_pointerLeaveCallback;
+ std::unique_ptr<qstdweb::EventCallback> m_pointerEnterCallback;
+
+ std::unique_ptr<qstdweb::EventCallback> m_dropCallback;
+
+ std::unique_ptr<qstdweb::EventCallback> m_wheelEventCallback;
+
+ Qt::WindowStates m_state = Qt::WindowNoState;
Qt::WindowStates m_previousWindowState = Qt::WindowNoState;
- QWasmCompositor::SubControls m_activeControl = QWasmCompositor::SC_None;
- WId m_winid = 0;
+
+ Qt::WindowFlags m_flags = Qt::Widget;
+
+ QPoint m_lastPointerMovePoint;
+
+ WId m_winId = 0;
+ bool m_wantCapture = false;
bool m_hasTitle = false;
bool m_needsCompositor = false;
long m_requestAnimationFrameId = -1;
@@ -90,5 +170,6 @@ protected:
friend class QWasmEventTranslator;
bool windowIsPopupType(Qt::WindowFlags flags) const;
};
+
QT_END_NAMESPACE
#endif // QWASMWINDOW_H
diff --git a/src/plugins/platforms/wasm/qwasmwindowclientarea.cpp b/src/plugins/platforms/wasm/qwasmwindowclientarea.cpp
new file mode 100644
index 0000000000..6da3e24c05
--- /dev/null
+++ b/src/plugins/platforms/wasm/qwasmwindowclientarea.cpp
@@ -0,0 +1,195 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "qwasmwindowclientarea.h"
+
+#include "qwasmdom.h"
+#include "qwasmevent.h"
+#include "qwasmscreen.h"
+#include "qwasmwindow.h"
+#include "qwasmdrag.h"
+
+#include <QtGui/private/qguiapplication_p.h>
+#include <QtGui/qpointingdevice.h>
+
+#include <QtCore/qassert.h>
+
+QT_BEGIN_NAMESPACE
+
+ClientArea::ClientArea(QWasmWindow *window, QWasmScreen *screen, emscripten::val element)
+ : m_screen(screen), m_window(window), m_element(element)
+{
+ const auto callback = std::function([this](emscripten::val event) {
+ processPointer(*PointerEvent::fromWeb(event));
+ event.call<void>("preventDefault");
+ event.call<void>("stopPropagation");
+ });
+
+ m_pointerDownCallback =
+ std::make_unique<qstdweb::EventCallback>(element, "pointerdown", callback);
+ m_pointerMoveCallback =
+ std::make_unique<qstdweb::EventCallback>(element, "pointermove", callback);
+ m_pointerUpCallback = std::make_unique<qstdweb::EventCallback>(element, "pointerup", callback);
+ m_pointerCancelCallback =
+ std::make_unique<qstdweb::EventCallback>(element, "pointercancel", callback);
+
+ element.call<void>("setAttribute", emscripten::val("draggable"), emscripten::val("true"));
+
+ m_dragStartCallback = std::make_unique<qstdweb::EventCallback>(
+ element, "dragstart", [this](emscripten::val webEvent) {
+ webEvent.call<void>("preventDefault");
+ auto event = *DragEvent::fromWeb(webEvent, m_window->window());
+ QWasmDrag::instance()->onNativeDragStarted(&event);
+ });
+ m_dragOverCallback = std::make_unique<qstdweb::EventCallback>(
+ element, "dragover", [this](emscripten::val webEvent) {
+ webEvent.call<void>("preventDefault");
+ auto event = *DragEvent::fromWeb(webEvent, m_window->window());
+ QWasmDrag::instance()->onNativeDragOver(&event);
+ });
+ m_dropCallback = std::make_unique<qstdweb::EventCallback>(
+ element, "drop", [this](emscripten::val webEvent) {
+ webEvent.call<void>("preventDefault");
+ auto event = *DragEvent::fromWeb(webEvent, m_window->window());
+ QWasmDrag::instance()->onNativeDrop(&event);
+ });
+ m_dragEndCallback = std::make_unique<qstdweb::EventCallback>(
+ element, "dragend", [this](emscripten::val webEvent) {
+ webEvent.call<void>("preventDefault");
+ auto event = *DragEvent::fromWeb(webEvent, m_window->window());
+ QWasmDrag::instance()->onNativeDragFinished(&event);
+ });
+
+}
+
+bool ClientArea::processPointer(const PointerEvent &event)
+{
+
+ switch (event.type) {
+ case EventType::PointerDown:
+ m_element.call<void>("setPointerCapture", event.pointerId);
+ if ((m_window->window()->flags() & Qt::WindowDoesNotAcceptFocus)
+ != Qt::WindowDoesNotAcceptFocus
+ && m_window->window()->isTopLevel())
+ m_window->window()->requestActivate();
+ break;
+ case EventType::PointerUp:
+ m_element.call<void>("releasePointerCapture", event.pointerId);
+ break;
+ default:
+ break;
+ };
+
+ const bool eventAccepted = deliverEvent(event);
+ if (!eventAccepted && event.type == EventType::PointerDown)
+ QGuiApplicationPrivate::instance()->closeAllPopups();
+ return eventAccepted;
+}
+
+bool ClientArea::deliverEvent(const PointerEvent &event)
+{
+ const auto pointInScreen = m_screen->mapFromLocal(
+ dom::mapPoint(event.target(), m_screen->element(), event.localPoint));
+
+ const auto geometryF = m_screen->geometry().toRectF();
+ const QPointF targetPointClippedToScreen(
+ qBound(geometryF.left(), pointInScreen.x(), geometryF.right()),
+ qBound(geometryF.top(), pointInScreen.y(), geometryF.bottom()));
+
+ if (event.pointerType == PointerType::Mouse) {
+ const QEvent::Type eventType =
+ MouseEvent::mouseEventTypeFromEventType(event.type, WindowArea::Client);
+
+ return eventType != QEvent::None
+ && QWindowSystemInterface::handleMouseEvent(
+ m_window->window(), QWasmIntegration::getTimestamp(),
+ m_window->window()->mapFromGlobal(targetPointClippedToScreen),
+ targetPointClippedToScreen, event.mouseButtons, event.mouseButton,
+ eventType, event.modifiers);
+ }
+
+ if (event.pointerType == PointerType::Pen) {
+ qreal pressure;
+ switch (event.type) {
+ case EventType::PointerDown :
+ case EventType::PointerMove :
+ pressure = event.pressure;
+ break;
+ case EventType::PointerUp :
+ pressure = 0.0;
+ break;
+ default:
+ return false;
+ }
+ // Tilt in the browser is in the range +-90, but QTabletEvent only goes to +-60.
+ qreal xTilt = qBound(-60.0, event.tiltX, 60.0);
+ qreal yTilt = qBound(-60.0, event.tiltY, 60.0);
+ // Barrel rotation is reported as 0 to 359, but QTabletEvent wants a signed value.
+ qreal rotation = event.twist > 180.0 ? 360.0 - event.twist : event.twist;
+ return QWindowSystemInterface::handleTabletEvent(
+ m_window->window(), QWasmIntegration::getTimestamp(), m_screen->tabletDevice(),
+ m_window->window()->mapFromGlobal(targetPointClippedToScreen),
+ targetPointClippedToScreen, event.mouseButtons, pressure, xTilt, yTilt,
+ event.tangentialPressure, rotation, event.modifiers);
+ }
+
+ QWindowSystemInterface::TouchPoint *touchPoint;
+
+ QPointF pointInTargetWindowCoords =
+ QPointF(m_window->window()->mapFromGlobal(targetPointClippedToScreen));
+ QPointF normalPosition(pointInTargetWindowCoords.x() / m_window->window()->width(),
+ pointInTargetWindowCoords.y() / m_window->window()->height());
+
+ const auto tp = m_pointerIdToTouchPoints.find(event.pointerId);
+ if (event.pointerType != PointerType::Pen && tp != m_pointerIdToTouchPoints.end()) {
+ touchPoint = &tp.value();
+ } else {
+ touchPoint = &m_pointerIdToTouchPoints
+ .insert(event.pointerId, QWindowSystemInterface::TouchPoint())
+ .value();
+
+ // Assign touch point id. TouchPoint::id is int, but QGuiApplicationPrivate::processTouchEvent()
+ // will not synthesize mouse events for touch points with negative id; use the absolute value for
+ // the touch point id.
+ touchPoint->id = qAbs(event.pointerId);
+
+ touchPoint->state = QEventPoint::State::Pressed;
+ }
+
+ const bool stationaryTouchPoint = (normalPosition == touchPoint->normalPosition);
+ touchPoint->normalPosition = normalPosition;
+ touchPoint->area = QRectF(targetPointClippedToScreen, QSizeF(event.width, event.height))
+ .translated(-event.width / 2, -event.height / 2);
+ touchPoint->pressure = event.pressure;
+
+ switch (event.type) {
+ case EventType::PointerUp:
+ touchPoint->state = QEventPoint::State::Released;
+ break;
+ case EventType::PointerMove:
+ touchPoint->state = (stationaryTouchPoint ? QEventPoint::State::Stationary
+ : QEventPoint::State::Updated);
+ break;
+ default:
+ break;
+ }
+
+ QList<QWindowSystemInterface::TouchPoint> touchPointList;
+ touchPointList.reserve(m_pointerIdToTouchPoints.size());
+ std::transform(m_pointerIdToTouchPoints.begin(), m_pointerIdToTouchPoints.end(),
+ std::back_inserter(touchPointList),
+ [](const QWindowSystemInterface::TouchPoint &val) { return val; });
+
+ if (event.type == EventType::PointerUp)
+ m_pointerIdToTouchPoints.remove(event.pointerId);
+
+ return event.type == EventType::PointerCancel
+ ? QWindowSystemInterface::handleTouchCancelEvent(
+ m_window->window(), QWasmIntegration::getTimestamp(), m_screen->touchDevice(),
+ event.modifiers)
+ : QWindowSystemInterface::handleTouchEvent(
+ m_window->window(), QWasmIntegration::getTimestamp(), m_screen->touchDevice(),
+ touchPointList, event.modifiers);
+}
+
+QT_END_NAMESPACE
diff --git a/src/plugins/platforms/wasm/qwasmwindowclientarea.h b/src/plugins/platforms/wasm/qwasmwindowclientarea.h
new file mode 100644
index 0000000000..ba745a59a8
--- /dev/null
+++ b/src/plugins/platforms/wasm/qwasmwindowclientarea.h
@@ -0,0 +1,52 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef QWASMWINDOWCLIENTAREA_H
+#define QWASMWINDOWCLIENTAREA_H
+
+#include <QtCore/qnamespace.h>
+#include <qpa/qwindowsysteminterface.h>
+#include <QtCore/QMap>
+
+#include <emscripten/val.h>
+
+#include <memory>
+
+QT_BEGIN_NAMESPACE
+
+namespace qstdweb {
+class EventCallback;
+}
+
+struct PointerEvent;
+class QWasmScreen;
+class QWasmWindow;
+
+class ClientArea
+{
+public:
+ ClientArea(QWasmWindow *window, QWasmScreen *screen, emscripten::val element);
+
+private:
+ bool processPointer(const PointerEvent &event);
+ bool deliverEvent(const PointerEvent &event);
+
+ std::unique_ptr<qstdweb::EventCallback> m_pointerDownCallback;
+ std::unique_ptr<qstdweb::EventCallback> m_pointerMoveCallback;
+ std::unique_ptr<qstdweb::EventCallback> m_pointerUpCallback;
+ std::unique_ptr<qstdweb::EventCallback> m_pointerCancelCallback;
+
+ std::unique_ptr<qstdweb::EventCallback> m_dragOverCallback;
+ std::unique_ptr<qstdweb::EventCallback> m_dragStartCallback;
+ std::unique_ptr<qstdweb::EventCallback> m_dragEndCallback;
+ std::unique_ptr<qstdweb::EventCallback> m_dropCallback;
+
+ QMap<int, QWindowSystemInterface::TouchPoint> m_pointerIdToTouchPoints;
+
+ QWasmScreen *m_screen;
+ QWasmWindow *m_window;
+ emscripten::val m_element;
+};
+
+QT_END_NAMESPACE
+#endif // QWASMWINDOWNONCLIENTAREA_H
diff --git a/src/plugins/platforms/wasm/qwasmwindownonclientarea.cpp b/src/plugins/platforms/wasm/qwasmwindownonclientarea.cpp
new file mode 100644
index 0000000000..00fa8fb236
--- /dev/null
+++ b/src/plugins/platforms/wasm/qwasmwindownonclientarea.cpp
@@ -0,0 +1,460 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "qwasmwindownonclientarea.h"
+
+#include "qwasmbase64iconstore.h"
+#include "qwasmdom.h"
+#include "qwasmevent.h"
+#include "qwasmintegration.h"
+
+#include <qpa/qwindowsysteminterface.h>
+
+#include <QtCore/qassert.h>
+
+QT_BEGIN_NAMESPACE
+
+WebImageButton::Callbacks::Callbacks() = default;
+WebImageButton::Callbacks::Callbacks(std::function<void()> onInteraction,
+ std::function<void()> onClick)
+ : m_onInteraction(std::move(onInteraction)), m_onClick(std::move(onClick))
+{
+ Q_ASSERT_X(!!m_onInteraction == !!m_onClick, Q_FUNC_INFO,
+ "Both callbacks need to be either null or non-null");
+}
+WebImageButton::Callbacks::~Callbacks() = default;
+
+WebImageButton::Callbacks::Callbacks(Callbacks &&) = default;
+WebImageButton::Callbacks &WebImageButton::Callbacks::operator=(Callbacks &&) = default;
+
+void WebImageButton::Callbacks::onInteraction()
+{
+ return m_onInteraction();
+}
+
+void WebImageButton::Callbacks::onClick()
+{
+ return m_onClick();
+}
+
+WebImageButton::WebImageButton()
+ : m_containerElement(
+ dom::document().call<emscripten::val>("createElement", emscripten::val("div"))),
+ m_imgElement(dom::document().call<emscripten::val>("createElement", emscripten::val("img")))
+{
+ m_imgElement.set("draggable", false);
+
+ m_containerElement["classList"].call<void>("add", emscripten::val("image-button"));
+ m_containerElement.call<void>("appendChild", m_imgElement);
+}
+
+WebImageButton::~WebImageButton() = default;
+
+void WebImageButton::setCallbacks(Callbacks callbacks)
+{
+ if (callbacks) {
+ if (!m_webClickEventCallback) {
+ m_webMouseDownEventCallback = std::make_unique<qstdweb::EventCallback>(
+ m_containerElement, "pointerdown", [this](emscripten::val event) {
+ event.call<void>("preventDefault");
+ event.call<void>("stopPropagation");
+ m_callbacks.onInteraction();
+ });
+ m_webClickEventCallback = std::make_unique<qstdweb::EventCallback>(
+ m_containerElement, "click", [this](emscripten::val event) {
+ m_callbacks.onClick();
+ event.call<void>("stopPropagation");
+ });
+ }
+ } else {
+ m_webMouseDownEventCallback.reset();
+ m_webClickEventCallback.reset();
+ }
+ dom::syncCSSClassWith(m_containerElement, "action-button", !!callbacks);
+ m_callbacks = std::move(callbacks);
+}
+
+void WebImageButton::setImage(std::string_view imageData, std::string_view format)
+{
+ m_imgElement.set("src",
+ "data:image/" + std::string(format) + ";base64," + std::string(imageData));
+}
+
+void WebImageButton::setVisible(bool visible)
+{
+ m_containerElement["style"].set("display", visible ? "flex" : "none");
+}
+
+Resizer::ResizerElement::ResizerElement(emscripten::val parentElement, Qt::Edges edges,
+ Resizer *resizer)
+ : m_element(dom::document().call<emscripten::val>("createElement", emscripten::val("div"))),
+ m_edges(edges),
+ m_resizer(resizer)
+{
+ Q_ASSERT_X(m_resizer, Q_FUNC_INFO, "Resizer cannot be null");
+
+ m_element["classList"].call<void>("add", emscripten::val("resize-outline"));
+ m_element["classList"].call<void>("add", emscripten::val(cssClassNameForEdges(edges)));
+
+ parentElement.call<void>("appendChild", m_element);
+
+ m_mouseDownEvent = std::make_unique<qstdweb::EventCallback>(
+ m_element, "pointerdown", [this](emscripten::val event) {
+ if (!onPointerDown(*PointerEvent::fromWeb(event)))
+ return;
+ m_resizer->onInteraction();
+ event.call<void>("preventDefault");
+ event.call<void>("stopPropagation");
+ });
+ m_mouseMoveEvent = std::make_unique<qstdweb::EventCallback>(
+ m_element, "pointermove", [this](emscripten::val event) {
+ if (onPointerMove(*PointerEvent::fromWeb(event)))
+ event.call<void>("preventDefault");
+ });
+ m_mouseUpEvent = std::make_unique<qstdweb::EventCallback>(
+ m_element, "pointerup", [this](emscripten::val event) {
+ if (onPointerUp(*PointerEvent::fromWeb(event))) {
+ event.call<void>("preventDefault");
+ event.call<void>("stopPropagation");
+ }
+ });
+}
+
+Resizer::ResizerElement::~ResizerElement()
+{
+ m_element["parentElement"].call<emscripten::val>("removeChild", m_element);
+}
+
+Resizer::ResizerElement::ResizerElement(ResizerElement &&other) = default;
+
+bool Resizer::ResizerElement::onPointerDown(const PointerEvent &event)
+{
+ m_element.call<void>("setPointerCapture", event.pointerId);
+ m_capturedPointerId = event.pointerId;
+
+ m_resizer->startResize(m_edges, event);
+ return true;
+}
+
+bool Resizer::ResizerElement::onPointerMove(const PointerEvent &event)
+{
+ if (m_capturedPointerId != event.pointerId)
+ return false;
+
+ m_resizer->continueResize(event);
+ return true;
+}
+
+bool Resizer::ResizerElement::onPointerUp(const PointerEvent &event)
+{
+ if (m_capturedPointerId != event.pointerId)
+ return false;
+
+ m_resizer->finishResize();
+ m_element.call<void>("releasePointerCapture", event.pointerId);
+ m_capturedPointerId = -1;
+ return true;
+}
+
+Resizer::Resizer(QWasmWindow *window, emscripten::val parentElement)
+ : m_window(window), m_windowElement(parentElement)
+{
+ Q_ASSERT_X(m_window, Q_FUNC_INFO, "Window must not be null");
+
+ constexpr std::array<int, 8> ResizeEdges = { Qt::TopEdge | Qt::LeftEdge,
+ Qt::TopEdge,
+ Qt::TopEdge | Qt::RightEdge,
+ Qt::LeftEdge,
+ Qt::RightEdge,
+ Qt::BottomEdge | Qt::LeftEdge,
+ Qt::BottomEdge,
+ Qt::BottomEdge | Qt::RightEdge };
+ std::transform(std::begin(ResizeEdges), std::end(ResizeEdges), std::back_inserter(m_elements),
+ [parentElement, this](int edges) {
+ return std::make_unique<ResizerElement>(parentElement,
+ Qt::Edges::fromInt(edges), this);
+ });
+}
+
+Resizer::~Resizer() = default;
+
+ResizeConstraints Resizer::getResizeConstraints() {
+ const auto *window = m_window->window();
+ const auto minShrink = QPoint(window->minimumWidth() - window->geometry().width(),
+ window->minimumHeight() - window->geometry().height());
+ const auto maxGrow = QPoint(window->maximumWidth() - window->geometry().width(),
+ window->maximumHeight() - window->geometry().height());
+
+ const auto frameRect =
+ QRectF::fromDOMRect(m_windowElement.call<emscripten::val>("getBoundingClientRect"));
+ auto containerGeometry =
+ QRectF::fromDOMRect(m_window->parentNode()->containerElement().call<emscripten::val>(
+ "getBoundingClientRect"));
+
+ const int maxGrowTop = frameRect.top() - containerGeometry.top();
+
+ return ResizeConstraints{minShrink, maxGrow, maxGrowTop};
+}
+
+void Resizer::onInteraction()
+{
+ m_window->onNonClientAreaInteraction();
+}
+
+void Resizer::startResize(Qt::Edges resizeEdges, const PointerEvent &event)
+{
+ Q_ASSERT_X(!m_currentResizeData, Q_FUNC_INFO, "Another resize in progress");
+
+ m_currentResizeData.reset(new ResizeData{
+ .edges = resizeEdges,
+ .originInScreenCoords = dom::mapPoint(
+ event.target(), m_window->platformScreen()->element(), event.localPoint),
+ });
+
+ const auto resizeConstraints = getResizeConstraints();
+ m_currentResizeData->minShrink = resizeConstraints.minShrink;
+
+ m_currentResizeData->maxGrow =
+ QPoint(resizeConstraints.maxGrow.x(),
+ std::min(resizeEdges & Qt::Edge::TopEdge ? resizeConstraints.maxGrowTop : INT_MAX,
+ resizeConstraints.maxGrow.y()));
+
+ m_currentResizeData->initialBounds = m_window->window()->geometry();
+}
+
+void Resizer::continueResize(const PointerEvent &event)
+{
+ const auto pointInScreen =
+ dom::mapPoint(event.target(), m_window->platformScreen()->element(), event.localPoint);
+ const auto amount = (pointInScreen - m_currentResizeData->originInScreenCoords).toPoint();
+ const QPoint cappedGrowVector(
+ std::min(m_currentResizeData->maxGrow.x(),
+ std::max(m_currentResizeData->minShrink.x(),
+ (m_currentResizeData->edges & Qt::Edge::LeftEdge) ? -amount.x()
+ : (m_currentResizeData->edges & Qt::Edge::RightEdge)
+ ? amount.x()
+ : 0)),
+ std::min(m_currentResizeData->maxGrow.y(),
+ std::max(m_currentResizeData->minShrink.y(),
+ (m_currentResizeData->edges & Qt::Edge::TopEdge) ? -amount.y()
+ : (m_currentResizeData->edges & Qt::Edge::BottomEdge)
+ ? amount.y()
+ : 0)));
+
+ auto bounds = m_currentResizeData->initialBounds.adjusted(
+ (m_currentResizeData->edges & Qt::Edge::LeftEdge) ? -cappedGrowVector.x() : 0,
+ (m_currentResizeData->edges & Qt::Edge::TopEdge) ? -cappedGrowVector.y() : 0,
+ (m_currentResizeData->edges & Qt::Edge::RightEdge) ? cappedGrowVector.x() : 0,
+ (m_currentResizeData->edges & Qt::Edge::BottomEdge) ? cappedGrowVector.y() : 0);
+
+ m_window->window()->setGeometry(bounds);
+}
+
+void Resizer::finishResize()
+{
+ Q_ASSERT_X(m_currentResizeData, Q_FUNC_INFO, "No resize in progress");
+ m_currentResizeData.reset();
+}
+
+TitleBar::TitleBar(QWasmWindow *window, emscripten::val parentElement)
+ : m_window(window),
+ m_element(dom::document().call<emscripten::val>("createElement", emscripten::val("div"))),
+ m_label(dom::document().call<emscripten::val>("createElement", emscripten::val("div")))
+{
+ m_icon = std::make_unique<WebImageButton>();
+ m_icon->setImage(Base64IconStore::get()->getIcon(Base64IconStore::IconType::QtLogo), "svg+xml");
+ m_element.call<void>("appendChild", m_icon->htmlElement());
+ m_element.set("className", "title-bar");
+
+ auto spacer = dom::document().call<emscripten::val>("createElement", emscripten::val("div"));
+ spacer["style"].set("width", "4px");
+ m_element.call<void>("appendChild", spacer);
+
+ m_label.set("className", "window-name");
+
+ m_element.call<void>("appendChild", m_label);
+
+ spacer = dom::document().call<emscripten::val>("createElement", emscripten::val("div"));
+ spacer.set("className", "spacer");
+ m_element.call<void>("appendChild", spacer);
+
+ m_restore = std::make_unique<WebImageButton>();
+ m_restore->setImage(Base64IconStore::get()->getIcon(Base64IconStore::IconType::Restore),
+ "svg+xml");
+ m_restore->setCallbacks(
+ WebImageButton::Callbacks([this]() { m_window->onNonClientAreaInteraction(); },
+ [this]() { m_window->onRestoreClicked(); }));
+
+ m_element.call<void>("appendChild", m_restore->htmlElement());
+
+ m_maximize = std::make_unique<WebImageButton>();
+ m_maximize->setImage(Base64IconStore::get()->getIcon(Base64IconStore::IconType::Maximize),
+ "svg+xml");
+ m_maximize->setCallbacks(
+ WebImageButton::Callbacks([this]() { m_window->onNonClientAreaInteraction(); },
+ [this]() { m_window->onMaximizeClicked(); }));
+
+ m_element.call<void>("appendChild", m_maximize->htmlElement());
+
+ m_close = std::make_unique<WebImageButton>();
+ m_close->setImage(Base64IconStore::get()->getIcon(Base64IconStore::IconType::X), "svg+xml");
+ m_close->setCallbacks(
+ WebImageButton::Callbacks([this]() { m_window->onNonClientAreaInteraction(); },
+ [this]() { m_window->onCloseClicked(); }));
+
+ m_element.call<void>("appendChild", m_close->htmlElement());
+
+ parentElement.call<void>("appendChild", m_element);
+
+ m_mouseDownEvent = std::make_unique<qstdweb::EventCallback>(
+ m_element, "pointerdown", [this](emscripten::val event) {
+ if (!onPointerDown(*PointerEvent::fromWeb(event)))
+ return;
+ m_window->onNonClientAreaInteraction();
+ event.call<void>("preventDefault");
+ event.call<void>("stopPropagation");
+ });
+ m_mouseMoveEvent = std::make_unique<qstdweb::EventCallback>(
+ m_element, "pointermove", [this](emscripten::val event) {
+ if (onPointerMove(*PointerEvent::fromWeb(event))) {
+ event.call<void>("preventDefault");
+ }
+ });
+ m_mouseUpEvent = std::make_unique<qstdweb::EventCallback>(
+ m_element, "pointerup", [this](emscripten::val event) {
+ if (onPointerUp(*PointerEvent::fromWeb(event))) {
+ event.call<void>("preventDefault");
+ event.call<void>("stopPropagation");
+ }
+ });
+ m_doubleClickEvent = std::make_unique<qstdweb::EventCallback>(
+ m_element, "dblclick", [this](emscripten::val event) {
+ if (onDoubleClick()) {
+ event.call<void>("preventDefault");
+ event.call<void>("stopPropagation");
+ }
+ });
+}
+
+TitleBar::~TitleBar()
+{
+ m_element["parentElement"].call<emscripten::val>("removeChild", m_element);
+}
+
+void TitleBar::setTitle(const QString &title)
+{
+ m_label.set("innerText", emscripten::val(title.toStdString()));
+}
+
+void TitleBar::setRestoreVisible(bool visible)
+{
+ m_restore->setVisible(visible);
+}
+
+void TitleBar::setMaximizeVisible(bool visible)
+{
+ m_maximize->setVisible(visible);
+}
+
+void TitleBar::setCloseVisible(bool visible)
+{
+ m_close->setVisible(visible);
+}
+
+void TitleBar::setIcon(std::string_view imageData, std::string_view format)
+{
+ m_icon->setImage(imageData, format);
+}
+
+void TitleBar::setWidth(int width)
+{
+ m_element["style"].set("width", std::to_string(width) + "px");
+}
+
+QRectF TitleBar::geometry() const
+{
+ return QRectF::fromDOMRect(m_element.call<emscripten::val>("getBoundingClientRect"));
+}
+
+bool TitleBar::onPointerDown(const PointerEvent &event)
+{
+ m_element.call<void>("setPointerCapture", event.pointerId);
+ m_capturedPointerId = event.pointerId;
+
+ m_moveStartWindowPosition = m_window->window()->position();
+ m_moveStartPoint = clipPointWithScreen(event.localPoint);
+ m_window->onNonClientEvent(event);
+ return true;
+}
+
+bool TitleBar::onPointerMove(const PointerEvent &event)
+{
+ if (m_capturedPointerId != event.pointerId)
+ return false;
+
+ const QPoint delta = (clipPointWithScreen(event.localPoint) - m_moveStartPoint).toPoint();
+
+ m_window->window()->setPosition(m_moveStartWindowPosition + delta);
+ m_window->onNonClientEvent(event);
+ return true;
+}
+
+bool TitleBar::onPointerUp(const PointerEvent &event)
+{
+ if (m_capturedPointerId != event.pointerId)
+ return false;
+
+ m_element.call<void>("releasePointerCapture", event.pointerId);
+ m_capturedPointerId = -1;
+ m_window->onNonClientEvent(event);
+ return true;
+}
+
+bool TitleBar::onDoubleClick()
+{
+ m_window->onToggleMaximized();
+ return true;
+}
+
+QPointF TitleBar::clipPointWithScreen(const QPointF &pointInTitleBarCoords) const
+{
+ auto containerRect =
+ QRectF::fromDOMRect(m_window->parentNode()->containerElement().call<emscripten::val>(
+ "getBoundingClientRect"));
+ const auto p = dom::mapPoint(m_element, m_window->parentNode()->containerElement(),
+ pointInTitleBarCoords);
+
+ auto result = QPointF(qBound(0., qreal(p.x()), containerRect.width()),
+ qBound(0., qreal(p.y()), containerRect.height()));
+ return m_window->parent() ? result : m_window->platformScreen()->mapFromLocal(result).toPoint();
+}
+
+NonClientArea::NonClientArea(QWasmWindow *window, emscripten::val qtWindowElement)
+ : m_qtWindowElement(qtWindowElement),
+ m_resizer(std::make_unique<Resizer>(window, m_qtWindowElement)),
+ m_titleBar(std::make_unique<TitleBar>(window, m_qtWindowElement))
+{
+ updateResizability();
+}
+
+NonClientArea::~NonClientArea() = default;
+
+void NonClientArea::onClientAreaWidthChange(int width)
+{
+ m_titleBar->setWidth(width);
+}
+
+void NonClientArea::propagateSizeHints()
+{
+ updateResizability();
+}
+
+void NonClientArea::updateResizability()
+{
+ const auto resizeConstraints = m_resizer->getResizeConstraints();
+ const bool nonResizable = resizeConstraints.minShrink.isNull()
+ && resizeConstraints.maxGrow.isNull() && resizeConstraints.maxGrowTop == 0;
+ dom::syncCSSClassWith(m_qtWindowElement, "no-resize", nonResizable);
+}
+
+QT_END_NAMESPACE
diff --git a/src/plugins/platforms/wasm/qwasmwindownonclientarea.h b/src/plugins/platforms/wasm/qwasmwindownonclientarea.h
new file mode 100644
index 0000000000..78c77585a0
--- /dev/null
+++ b/src/plugins/platforms/wasm/qwasmwindownonclientarea.h
@@ -0,0 +1,227 @@
+// Copyright (C) 2018 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef QWASMWINDOWNONCLIENTAREA_H
+#define QWASMWINDOWNONCLIENTAREA_H
+
+#include <QtCore/qrect.h>
+#include <QtCore/qtconfigmacros.h>
+#include <QtCore/qnamespace.h>
+
+#include <emscripten/val.h>
+
+#include <functional>
+#include <memory>
+#include <string_view>
+#include <vector>
+
+QT_BEGIN_NAMESPACE
+
+namespace qstdweb {
+class EventCallback;
+}
+
+struct PointerEvent;
+class QWindow;
+class Resizer;
+class TitleBar;
+class QWasmWindow;
+
+class NonClientArea
+{
+public:
+ NonClientArea(QWasmWindow *window, emscripten::val containerElement);
+ ~NonClientArea();
+
+ void onClientAreaWidthChange(int width);
+ void propagateSizeHints();
+ TitleBar *titleBar() const { return m_titleBar.get(); }
+
+private:
+ void updateResizability();
+
+ emscripten::val m_qtWindowElement;
+ std::unique_ptr<Resizer> m_resizer;
+ std::unique_ptr<TitleBar> m_titleBar;
+};
+
+class WebImageButton
+{
+public:
+ class Callbacks
+ {
+ public:
+ Callbacks();
+ Callbacks(std::function<void()> onInteraction, std::function<void()> onClick);
+ ~Callbacks();
+
+ Callbacks(const Callbacks &) = delete;
+ Callbacks(Callbacks &&);
+ Callbacks &operator=(const Callbacks &) = delete;
+ Callbacks &operator=(Callbacks &&);
+
+ operator bool() const { return !!m_onInteraction; }
+
+ void onInteraction();
+ void onClick();
+
+ private:
+ std::function<void()> m_onInteraction;
+ std::function<void()> m_onClick;
+ };
+
+ WebImageButton();
+ ~WebImageButton();
+
+ void setCallbacks(Callbacks callbacks);
+ void setImage(std::string_view imageData, std::string_view format);
+ void setVisible(bool visible);
+
+ emscripten::val htmlElement() const { return m_containerElement; }
+ emscripten::val imageElement() const { return m_imgElement; }
+
+private:
+ emscripten::val m_containerElement;
+ emscripten::val m_imgElement;
+
+ std::unique_ptr<qstdweb::EventCallback> m_webMouseMoveEventCallback;
+ std::unique_ptr<qstdweb::EventCallback> m_webMouseDownEventCallback;
+ std::unique_ptr<qstdweb::EventCallback> m_webClickEventCallback;
+
+ Callbacks m_callbacks;
+};
+
+struct ResizeConstraints {
+ QPoint minShrink;
+ QPoint maxGrow;
+ int maxGrowTop;
+};
+
+class Resizer
+{
+public:
+ class ResizerElement
+ {
+ public:
+ static constexpr const char *cssClassNameForEdges(Qt::Edges edges)
+ {
+ switch (edges) {
+ case Qt::TopEdge | Qt::LeftEdge:;
+ return "nw";
+ case Qt::TopEdge:
+ return "n";
+ case Qt::TopEdge | Qt::RightEdge:
+ return "ne";
+ case Qt::LeftEdge:
+ return "w";
+ case Qt::RightEdge:
+ return "e";
+ case Qt::BottomEdge | Qt::LeftEdge:
+ return "sw";
+ case Qt::BottomEdge:
+ return "s";
+ case Qt::BottomEdge | Qt::RightEdge:
+ return "se";
+ default:
+ return "";
+ }
+ }
+
+ ResizerElement(emscripten::val parentElement, Qt::Edges edges, Resizer *resizer);
+ ~ResizerElement();
+ ResizerElement(const ResizerElement &other) = delete;
+ ResizerElement(ResizerElement &&other);
+ ResizerElement &operator=(const ResizerElement &other) = delete;
+ ResizerElement &operator=(ResizerElement &&other) = delete;
+
+ bool onPointerDown(const PointerEvent &event);
+ bool onPointerMove(const PointerEvent &event);
+ bool onPointerUp(const PointerEvent &event);
+
+ private:
+ emscripten::val m_element;
+
+ int m_capturedPointerId = -1;
+
+ const Qt::Edges m_edges;
+
+ Resizer *m_resizer;
+
+ std::unique_ptr<qstdweb::EventCallback> m_mouseDownEvent;
+ std::unique_ptr<qstdweb::EventCallback> m_mouseMoveEvent;
+ std::unique_ptr<qstdweb::EventCallback> m_mouseUpEvent;
+ };
+
+ using ClickCallback = std::function<void()>;
+
+ Resizer(QWasmWindow *window, emscripten::val parentElement);
+ ~Resizer();
+
+ ResizeConstraints getResizeConstraints();
+
+private:
+ void onInteraction();
+ void startResize(Qt::Edges resizeEdges, const PointerEvent &event);
+ void continueResize(const PointerEvent &event);
+ void finishResize();
+
+ struct ResizeData
+ {
+ Qt::Edges edges = Qt::Edges::fromInt(0);
+ QPointF originInScreenCoords;
+ QPoint minShrink;
+ QPoint maxGrow;
+ QRect initialBounds;
+ };
+ std::unique_ptr<ResizeData> m_currentResizeData;
+
+ QWasmWindow *m_window;
+ emscripten::val m_windowElement;
+ std::vector<std::unique_ptr<ResizerElement>> m_elements;
+};
+
+class TitleBar
+{
+public:
+ TitleBar(QWasmWindow *window, emscripten::val parentElement);
+ ~TitleBar();
+
+ void setTitle(const QString &title);
+ void setRestoreVisible(bool visible);
+ void setMaximizeVisible(bool visible);
+ void setCloseVisible(bool visible);
+ void setIcon(std::string_view imageData, std::string_view format);
+ void setWidth(int width);
+
+ QRectF geometry() const;
+
+private:
+ bool onPointerDown(const PointerEvent &event);
+ bool onPointerMove(const PointerEvent &event);
+ bool onPointerUp(const PointerEvent &event);
+ bool onDoubleClick();
+
+ QPointF clipPointWithScreen(const QPointF &pointInTitleBarCoords) const;
+
+ QWasmWindow *m_window;
+
+ emscripten::val m_element;
+ emscripten::val m_label;
+
+ std::unique_ptr<WebImageButton> m_close;
+ std::unique_ptr<WebImageButton> m_maximize;
+ std::unique_ptr<WebImageButton> m_restore;
+ std::unique_ptr<WebImageButton> m_icon;
+
+ int m_capturedPointerId = -1;
+ QPointF m_moveStartPoint;
+ QPoint m_moveStartWindowPosition;
+
+ std::unique_ptr<qstdweb::EventCallback> m_mouseDownEvent;
+ std::unique_ptr<qstdweb::EventCallback> m_mouseMoveEvent;
+ std::unique_ptr<qstdweb::EventCallback> m_mouseUpEvent;
+ std::unique_ptr<qstdweb::EventCallback> m_doubleClickEvent;
+};
+
+QT_END_NAMESPACE
+#endif // QWASMWINDOWNONCLIENTAREA_H
diff --git a/src/plugins/platforms/wasm/qwasmwindowstack.cpp b/src/plugins/platforms/wasm/qwasmwindowstack.cpp
new file mode 100644
index 0000000000..d3769c7a1b
--- /dev/null
+++ b/src/plugins/platforms/wasm/qwasmwindowstack.cpp
@@ -0,0 +1,203 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "qwasmwindowstack.h"
+
+QT_BEGIN_NAMESPACE
+
+QWasmWindowStack::QWasmWindowStack(WindowOrderChangedCallbackType windowOrderChangedCallback)
+ : m_windowOrderChangedCallback(std::move(windowOrderChangedCallback)),
+ m_regularWindowsBegin(m_windowStack.begin()),
+ m_alwaysOnTopWindowsBegin(m_windowStack.begin())
+{
+}
+
+QWasmWindowStack::~QWasmWindowStack() = default;
+
+void QWasmWindowStack::pushWindow(QWasmWindow *window, PositionPreference position)
+{
+ Q_ASSERT(m_windowStack.count(window) == 0);
+
+ if (position == PositionPreference::StayOnTop) {
+ const auto stayOnTopDistance =
+ std::distance(m_windowStack.begin(), m_alwaysOnTopWindowsBegin);
+ const auto regularDistance = std::distance(m_windowStack.begin(), m_regularWindowsBegin);
+ m_windowStack.push_back(window);
+ m_alwaysOnTopWindowsBegin = m_windowStack.begin() + stayOnTopDistance;
+ m_regularWindowsBegin = m_windowStack.begin() + regularDistance;
+ } else if (position == PositionPreference::Regular) {
+ const auto regularDistance = std::distance(m_windowStack.begin(), m_regularWindowsBegin);
+ m_alwaysOnTopWindowsBegin = m_windowStack.insert(m_alwaysOnTopWindowsBegin, window) + 1;
+ m_regularWindowsBegin = m_windowStack.begin() + regularDistance;
+ } else {
+ const auto stayOnTopDistance =
+ std::distance(m_windowStack.begin(), m_alwaysOnTopWindowsBegin);
+ m_regularWindowsBegin = m_windowStack.insert(m_regularWindowsBegin, window) + 1;
+ m_alwaysOnTopWindowsBegin = m_windowStack.begin() + stayOnTopDistance + 1;
+ }
+
+ m_windowOrderChangedCallback();
+}
+
+void QWasmWindowStack::removeWindow(QWasmWindow *window)
+{
+ Q_ASSERT(m_windowStack.count(window) == 1);
+
+ auto it = std::find(m_windowStack.begin(), m_windowStack.end(), window);
+ const auto position = getWindowPositionPreference(it);
+ const auto stayOnTopDistance = std::distance(m_windowStack.begin(), m_alwaysOnTopWindowsBegin);
+ const auto regularDistance = std::distance(m_windowStack.begin(), m_regularWindowsBegin);
+
+ m_windowStack.erase(it);
+
+ m_alwaysOnTopWindowsBegin = m_windowStack.begin() + stayOnTopDistance
+ - (position != PositionPreference::StayOnTop ? 1 : 0);
+ m_regularWindowsBegin = m_windowStack.begin() + regularDistance
+ - (position == PositionPreference::StayOnBottom ? 1 : 0);
+
+ m_windowOrderChangedCallback();
+}
+
+void QWasmWindowStack::raise(QWasmWindow *window)
+{
+ Q_ASSERT(m_windowStack.count(window) == 1);
+
+ if (window == topWindow())
+ return;
+
+ auto it = std::find(m_windowStack.begin(), m_windowStack.end(), window);
+ auto itEnd = ([this, position = getWindowPositionPreference(it)]() {
+ switch (position) {
+ case PositionPreference::StayOnTop:
+ return m_windowStack.end();
+ case PositionPreference::Regular:
+ return m_alwaysOnTopWindowsBegin;
+ case PositionPreference::StayOnBottom:
+ return m_regularWindowsBegin;
+ }
+ })();
+ std::rotate(it, it + 1, itEnd);
+ m_windowOrderChangedCallback();
+}
+
+void QWasmWindowStack::lower(QWasmWindow *window)
+{
+ Q_ASSERT(m_windowStack.count(window) == 1);
+
+ if (window == *m_windowStack.begin())
+ return;
+
+ auto it = std::find(m_windowStack.begin(), m_windowStack.end(), window);
+ auto itBegin = ([this, position = getWindowPositionPreference(it)]() {
+ switch (position) {
+ case PositionPreference::StayOnTop:
+ return m_alwaysOnTopWindowsBegin;
+ case PositionPreference::Regular:
+ return m_regularWindowsBegin;
+ case PositionPreference::StayOnBottom:
+ return m_windowStack.begin();
+ }
+ })();
+
+ std::rotate(itBegin, it, it + 1);
+ m_windowOrderChangedCallback();
+}
+
+void QWasmWindowStack::windowPositionPreferenceChanged(QWasmWindow *window,
+ PositionPreference position)
+{
+ auto it = std::find(m_windowStack.begin(), m_windowStack.end(), window);
+ const auto currentPosition = getWindowPositionPreference(it);
+
+ const auto zones = static_cast<int>(position) - static_cast<int>(currentPosition);
+ Q_ASSERT(zones != 0);
+
+ if (zones < 0) {
+ // Perform right rotation so that the window lands on top of regular windows
+ const auto begin = std::make_reverse_iterator(it + 1);
+ const auto end = position == PositionPreference::Regular
+ ? std::make_reverse_iterator(m_alwaysOnTopWindowsBegin)
+ : std::make_reverse_iterator(m_regularWindowsBegin);
+ std::rotate(begin, begin + 1, end);
+ if (zones == -2) {
+ ++m_alwaysOnTopWindowsBegin;
+ ++m_regularWindowsBegin;
+ } else if (position == PositionPreference::Regular) {
+ ++m_alwaysOnTopWindowsBegin;
+ } else {
+ ++m_regularWindowsBegin;
+ }
+ } else {
+ // Perform left rotation so that the window lands at the bottom of always on top windows
+ const auto begin = it;
+ const auto end = position == PositionPreference::Regular ? m_regularWindowsBegin
+ : m_alwaysOnTopWindowsBegin;
+ std::rotate(begin, begin + 1, end);
+ if (zones == 2) {
+ --m_alwaysOnTopWindowsBegin;
+ --m_regularWindowsBegin;
+ } else if (position == PositionPreference::Regular) {
+ --m_regularWindowsBegin;
+ } else {
+ --m_alwaysOnTopWindowsBegin;
+ }
+ }
+ m_windowOrderChangedCallback();
+}
+
+QWasmWindowStack::iterator QWasmWindowStack::begin()
+{
+ return m_windowStack.rbegin();
+}
+
+QWasmWindowStack::iterator QWasmWindowStack::end()
+{
+ return m_windowStack.rend();
+}
+
+QWasmWindowStack::const_iterator QWasmWindowStack::begin() const
+{
+ return m_windowStack.rbegin();
+}
+
+QWasmWindowStack::const_iterator QWasmWindowStack::end() const
+{
+ return m_windowStack.rend();
+}
+
+QWasmWindowStack::const_reverse_iterator QWasmWindowStack::rbegin() const
+{
+ return m_windowStack.begin();
+}
+
+QWasmWindowStack::const_reverse_iterator QWasmWindowStack::rend() const
+{
+ return m_windowStack.end();
+}
+
+bool QWasmWindowStack::empty() const
+{
+ return m_windowStack.empty();
+}
+
+size_t QWasmWindowStack::size() const
+{
+ return m_windowStack.size();
+}
+
+QWasmWindow *QWasmWindowStack::topWindow() const
+{
+ return m_windowStack.empty() ? nullptr : m_windowStack.last();
+}
+
+QWasmWindowStack::PositionPreference
+QWasmWindowStack::getWindowPositionPreference(StorageType::iterator windowIt) const
+{
+ if (windowIt >= m_alwaysOnTopWindowsBegin)
+ return PositionPreference::StayOnTop;
+ if (windowIt >= m_regularWindowsBegin)
+ return PositionPreference::Regular;
+ return PositionPreference::StayOnBottom;
+}
+
+QT_END_NAMESPACE
diff --git a/src/plugins/platforms/wasm/qwasmwindowstack.h b/src/plugins/platforms/wasm/qwasmwindowstack.h
new file mode 100644
index 0000000000..c75001157a
--- /dev/null
+++ b/src/plugins/platforms/wasm/qwasmwindowstack.h
@@ -0,0 +1,75 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef QWASMWINDOWSTACK_H
+#define QWASMWINDOWSTACK_H
+
+#include <qglobal.h>
+#include <QtCore/qlist.h>
+
+#include <vector>
+
+QT_BEGIN_NAMESPACE
+
+class QWasmWindow;
+
+// Maintains a z-order hierarchy for a set of windows. The first added window is always treated as
+// the 'root', which always stays at the bottom. Other windows are 'regular', which means they are
+// subject to z-order changes via |raise| and |lower|/
+// If the root is ever removed, all of the current and future windows in the stack are treated as
+// regular.
+// Access to the top element is facilitated by |topWindow|.
+// Changes to the top element are signaled via the |topWindowChangedCallback| supplied at
+// construction.
+class Q_AUTOTEST_EXPORT QWasmWindowStack
+{
+public:
+ using WindowOrderChangedCallbackType = std::function<void()>;
+
+ using StorageType = QList<QWasmWindow *>;
+
+ using iterator = StorageType::reverse_iterator;
+ using const_iterator = StorageType::const_reverse_iterator;
+ using const_reverse_iterator = StorageType::const_iterator;
+
+ enum class PositionPreference {
+ StayOnBottom,
+ Regular,
+ StayOnTop,
+ };
+
+ explicit QWasmWindowStack(WindowOrderChangedCallbackType topWindowChangedCallback);
+ ~QWasmWindowStack();
+
+ void pushWindow(QWasmWindow *window, PositionPreference position);
+ void removeWindow(QWasmWindow *window);
+ void raise(QWasmWindow *window);
+ void lower(QWasmWindow *window);
+ void windowPositionPreferenceChanged(QWasmWindow *window, PositionPreference position);
+
+ // Iterates top-to-bottom
+ iterator begin();
+ iterator end();
+ const_iterator begin() const;
+ const_iterator end() const;
+
+ // Iterates bottom-to-top
+ const_reverse_iterator rbegin() const;
+ const_reverse_iterator rend() const;
+
+ bool empty() const;
+ size_t size() const;
+ QWasmWindow *topWindow() const;
+
+private:
+ PositionPreference getWindowPositionPreference(StorageType::iterator windowIt) const;
+
+ WindowOrderChangedCallbackType m_windowOrderChangedCallback;
+ QList<QWasmWindow *> m_windowStack;
+ StorageType::iterator m_regularWindowsBegin;
+ StorageType::iterator m_alwaysOnTopWindowsBegin;
+};
+
+QT_END_NAMESPACE
+
+#endif // QWASMWINDOWSTACK_H
diff --git a/src/plugins/platforms/wasm/qwasmwindowtreenode.cpp b/src/plugins/platforms/wasm/qwasmwindowtreenode.cpp
new file mode 100644
index 0000000000..ea8d8dbcfa
--- /dev/null
+++ b/src/plugins/platforms/wasm/qwasmwindowtreenode.cpp
@@ -0,0 +1,108 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "qwasmwindowtreenode.h"
+
+#include "qwasmwindow.h"
+
+QWasmWindowTreeNode::QWasmWindowTreeNode()
+ : m_childStack(std::bind(&QWasmWindowTreeNode::onTopWindowChanged, this))
+{
+}
+
+QWasmWindowTreeNode::~QWasmWindowTreeNode() = default;
+
+void QWasmWindowTreeNode::onParentChanged(QWasmWindowTreeNode *previousParent,
+ QWasmWindowTreeNode *currentParent,
+ QWasmWindowStack::PositionPreference positionPreference)
+{
+ auto *window = asWasmWindow();
+ if (previousParent) {
+ previousParent->m_childStack.removeWindow(window);
+ previousParent->onSubtreeChanged(QWasmWindowTreeNodeChangeType::NodeRemoval, previousParent,
+ window);
+ }
+
+ if (currentParent) {
+ currentParent->m_childStack.pushWindow(window, positionPreference);
+ currentParent->onSubtreeChanged(QWasmWindowTreeNodeChangeType::NodeInsertion, currentParent,
+ window);
+ }
+}
+
+QWasmWindow *QWasmWindowTreeNode::asWasmWindow()
+{
+ return nullptr;
+}
+
+void QWasmWindowTreeNode::onSubtreeChanged(QWasmWindowTreeNodeChangeType changeType,
+ QWasmWindowTreeNode *parent, QWasmWindow *child)
+{
+ if (changeType == QWasmWindowTreeNodeChangeType::NodeInsertion && parent == this
+ && m_childStack.topWindow()
+ && m_childStack.topWindow()->window()) {
+
+ const QVariant showWithoutActivating = m_childStack.topWindow()->window()->property("_q_showWithoutActivating");
+ if (!showWithoutActivating.isValid() || !showWithoutActivating.toBool())
+ m_childStack.topWindow()->requestActivateWindow();
+ }
+
+ if (parentNode())
+ parentNode()->onSubtreeChanged(changeType, parent, child);
+}
+
+void QWasmWindowTreeNode::setWindowZOrder(QWasmWindow *window, int z)
+{
+ window->setZOrder(z);
+}
+
+void QWasmWindowTreeNode::onPositionPreferenceChanged(
+ QWasmWindowStack::PositionPreference positionPreference)
+{
+ if (parentNode()) {
+ parentNode()->m_childStack.windowPositionPreferenceChanged(asWasmWindow(),
+ positionPreference);
+ }
+}
+
+void QWasmWindowTreeNode::setAsActiveNode()
+{
+ if (parentNode())
+ parentNode()->setActiveChildNode(asWasmWindow());
+}
+
+void QWasmWindowTreeNode::bringToTop()
+{
+ if (!parentNode())
+ return;
+ parentNode()->m_childStack.raise(asWasmWindow());
+ parentNode()->bringToTop();
+}
+
+void QWasmWindowTreeNode::sendToBottom()
+{
+ if (!parentNode())
+ return;
+ m_childStack.lower(asWasmWindow());
+}
+
+void QWasmWindowTreeNode::onTopWindowChanged()
+{
+ constexpr int zOrderForElementInFrontOfScreen = 3;
+ int z = zOrderForElementInFrontOfScreen;
+ std::for_each(m_childStack.rbegin(), m_childStack.rend(),
+ [this, &z](QWasmWindow *window) { setWindowZOrder(window, z++); });
+}
+
+void QWasmWindowTreeNode::setActiveChildNode(QWasmWindow *activeChild)
+{
+ m_activeChild = activeChild;
+
+ auto it = m_childStack.begin();
+ if (it == m_childStack.end())
+ return;
+ for (; it != m_childStack.end(); ++it)
+ (*it)->onActivationChanged(*it == m_activeChild);
+
+ setAsActiveNode();
+}
diff --git a/src/plugins/platforms/wasm/qwasmwindowtreenode.h b/src/plugins/platforms/wasm/qwasmwindowtreenode.h
new file mode 100644
index 0000000000..344fdb43cb
--- /dev/null
+++ b/src/plugins/platforms/wasm/qwasmwindowtreenode.h
@@ -0,0 +1,53 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#ifndef QWASMWINDOWTREENODE_H
+#define QWASMWINDOWTREENODE_H
+
+#include "qwasmwindowstack.h"
+
+namespace emscripten {
+class val;
+}
+
+class QWasmWindow;
+
+enum class QWasmWindowTreeNodeChangeType {
+ NodeInsertion,
+ NodeRemoval,
+};
+
+class QWasmWindowTreeNode
+{
+public:
+ QWasmWindowTreeNode();
+ virtual ~QWasmWindowTreeNode();
+
+ virtual emscripten::val containerElement() = 0;
+ virtual QWasmWindowTreeNode *parentNode() = 0;
+
+protected:
+ virtual void onParentChanged(QWasmWindowTreeNode *previous, QWasmWindowTreeNode *current,
+ QWasmWindowStack::PositionPreference positionPreference);
+ virtual QWasmWindow *asWasmWindow();
+ virtual void onSubtreeChanged(QWasmWindowTreeNodeChangeType changeType,
+ QWasmWindowTreeNode *parent, QWasmWindow *child);
+ virtual void setWindowZOrder(QWasmWindow *window, int z);
+
+ void onPositionPreferenceChanged(QWasmWindowStack::PositionPreference positionPreference);
+ void setAsActiveNode();
+ void bringToTop();
+ void sendToBottom();
+
+ const QWasmWindowStack &childStack() const { return m_childStack; }
+ QWasmWindow *activeChild() const { return m_activeChild; }
+
+private:
+ void onTopWindowChanged();
+ void setActiveChildNode(QWasmWindow *activeChild);
+
+ QWasmWindowStack m_childStack;
+ QWasmWindow *m_activeChild = nullptr;
+};
+
+#endif // QWASMWINDOWTREENODE_H
diff --git a/src/plugins/platforms/wasm/resources/maximize.svg b/src/plugins/platforms/wasm/resources/maximize.svg
new file mode 100644
index 0000000000..b5fad4f707
--- /dev/null
+++ b/src/plugins/platforms/wasm/resources/maximize.svg
@@ -0,0 +1 @@
+<svg width="1024" height="1024" xmlns="http://www.w3.org/2000/svg" class="svg-icon"><path stroke="null" d="M-.333-.333h1024.666v1024.666H-.333V-.333M127.75 255.833V896.25h768.5V255.833h-768.5z"/></svg> \ No newline at end of file
diff --git a/src/plugins/platforms/wasm/resources/qtlogo.svg b/src/plugins/platforms/wasm/resources/qtlogo.svg
new file mode 100644
index 0000000000..bfe2493f46
--- /dev/null
+++ b/src/plugins/platforms/wasm/resources/qtlogo.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="616" height="452" viewBox="0 0 462 339"><path fill="#41cd52" d="M63.5 0H462v274.79c-21.4 21.47-42.87 42.87-64.39 64.21H0V63.39C21.08 42.18 42.34 21.13 63.5 0Z"/><path d="M122.37 71.33C137.5 61.32 156.21 58.79 174 58.95c16.94.21 34.72 3.18 48.76 13.29 10.2 7.17 16.83 18.24 21.25 29.69 7.15 18.8 9.25 39.1 9.49 59.08.03 20.12-.88 40.68-7.54 59.85-4.46 13.04-12.95 24.62-24.15 32.66 8.06 13.06 16.28 26.02 24.34 39.08-10.13 4.67-20.23 9.37-30.37 14.02-8.63-14.24-17.22-28.51-25.88-42.73-11.71 1.92-23.69 1.77-35.46.47-14.1-1.69-28.47-5.99-39.35-15.48-8.36-7.24-13.61-17.37-17.2-27.67-5.88-17.42-7.46-35.96-7.73-54.24-.14-19.76 1.12-39.83 7.08-58.79 4.61-14.26 12.24-28.49 25.13-36.85ZM294.13 70.69c10.6-.01 21.2-.01 31.8 0 .03 14.02-.01 28.03.02 42.05 13.55.02 27.1 0 40.65.01-.23 9.1-.48 18.2-.74 27.3-13.54.03-27.07-.01-40.61.02.03 22.98-.07 45.96.05 68.94.26 6.29.12 12.93 2.89 18.74 2.02 4.48 7.46 5.63 11.89 5.78 8.35-.03 16.69-.52 25.04-.67.51 8.36 1 16.73 1.48 25.09-16.61 2.79-34.04 6.13-50.54.91-6.95-2.06-13.43-6.67-16.25-13.54-5.05-11.69-5.46-24.7-5.68-37.25-.02-22.67 0-45.33-.01-68-7.39-.02-14.78.01-22.17-.02-.02-9.09-.02-18.19 0-27.29 7.39-.03 14.77.01 22.16-.02.03-14.02-.01-28.03.02-42.05Z" fill="#fff"/><path fill="#41cd52" d="M160.51 87.7c10.29-1.34 21.09-.98 30.83 2.91 7.89 3.12 14.59 9.23 18.13 16.97 5.43 11.73 7.51 24.68 8.56 37.47 1.14 17.02.98 34.2-1.37 51.12-1.65 10.07-4 20.68-10.82 28.62-6.92 7.97-17.59 11.39-27.83 12.19-10.8.79-22.19 0-31.94-5.11-5.69-3.03-10.52-7.78-13.34-13.6-3.42-6.97-5.3-14.58-6.62-22.2-3.98-24.16-4.94-49.16.5-73.18 2.24-9.06 5.5-18.36 12.12-25.19 5.76-5.85 13.78-8.87 21.78-10Z"/></svg> \ No newline at end of file
diff --git a/src/plugins/platforms/wasm/resources/restore.svg b/src/plugins/platforms/wasm/resources/restore.svg
new file mode 100644
index 0000000000..70ee19170b
--- /dev/null
+++ b/src/plugins/platforms/wasm/resources/restore.svg
@@ -0,0 +1 @@
+<svg width="1024" height="1024" xmlns="http://www.w3.org/2000/svg" class="svg-icon"><path stroke="null" d="M449.191 44.905h535.142v528.951H449.191V44.906m66.893 132.237v330.594H917.44V177.143H516.084z"/><path stroke="null" d="M54.906 453.476h535.141v528.952H54.906V453.476m66.892 132.238V916.31h401.357V585.714H121.798z"/></svg> \ No newline at end of file
diff --git a/src/plugins/platforms/wasm/resources/x.svg b/src/plugins/platforms/wasm/resources/x.svg
new file mode 100644
index 0000000000..1d9ba7361a
--- /dev/null
+++ b/src/plugins/platforms/wasm/resources/x.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 460.775 460.775" style="enable-background:new 0 0 460.775 460.775" xml:space="preserve"><path d="M285.08 230.397 456.218 59.27c6.076-6.077 6.076-15.911 0-21.986L423.511 4.565a15.55 15.55 0 0 0-21.985 0l-171.138 171.14L59.25 4.565a15.551 15.551 0 0 0-21.985 0L4.558 37.284c-6.077 6.075-6.077 15.909 0 21.986l171.138 171.128L4.575 401.505c-6.074 6.077-6.074 15.911 0 21.986l32.709 32.719a15.555 15.555 0 0 0 21.986 0l171.117-171.12 171.118 171.12a15.551 15.551 0 0 0 21.985 0l32.709-32.719c6.074-6.075 6.074-15.909 0-21.986L285.08 230.397z"/></svg> \ No newline at end of file
diff --git a/src/plugins/platforms/wasm/wasm_shell.html b/src/plugins/platforms/wasm/wasm_shell.html
index ecbce6c043..702ea1f59d 100644
--- a/src/plugins/platforms/wasm/wasm_shell.html
+++ b/src/plugins/platforms/wasm/wasm_shell.html
@@ -12,9 +12,8 @@
<title>@APPNAME@</title>
<style>
/* Make the html body cover the entire (visual) viewport with no scroll bars. */
- html, body { padding: 0; margin: 0; overflow:hidden; height: 100% }
- /* Make the canvas cover the entire body */
- canvas { height:100%; width:100%; }
+ html, body { padding: 0; margin: 0; overflow: hidden; height: 100% }
+ #screen { width: 100%; height: 100%; }
</style>
</head>
<body onload="init()">
@@ -26,44 +25,50 @@
<noscript>JavaScript is disabled. Please enable JavaScript to use this application.</noscript>
</center>
</figure>
- <canvas id="qtcanvas"></canvas>
+ <div id="screen"></div>
- <script type='text/javascript'>
- let qtLoader = undefined;
- function init() {
- var spinner = document.querySelector('#qtspinner');
- var canvas = document.querySelector('#qtcanvas');
- var status = document.querySelector('#qtstatus')
+ <script type="text/javascript">
+ async function init()
+ {
+ const spinner = document.querySelector('#qtspinner');
+ const screen = document.querySelector('#screen');
+ const status = document.querySelector('#qtstatus');
- qtLoader = QtLoader({
- canvasElements : [canvas],
- showLoader: function(loaderStatus) {
- spinner.style.display = 'block';
- canvas.style.display = 'none';
- status.innerHTML = loaderStatus + "...";
- },
- showError: function(errorText) {
- status.innerHTML = errorText;
- spinner.style.display = 'block';
- canvas.style.display = 'none';
- },
- showExit: function() {
- status.innerHTML = "Application exit";
- if (qtLoader.exitCode !== undefined)
- status.innerHTML += " with code " + qtLoader.exitCode;
- if (qtLoader.exitText !== undefined)
- status.innerHTML += " (" + qtLoader.exitText + ")";
- spinner.style.display = 'block';
- canvas.style.display = 'none';
- },
- showCanvas: function() {
- spinner.style.display = 'none';
- canvas.style.display = 'block';
- },
- });
- qtLoader.loadEmscriptenModule("@APPNAME@");
- }
+ const showUi = (ui) => {
+ [spinner, screen].forEach(element => element.style.display = 'none');
+ if (screen === ui)
+ screen.style.position = 'default';
+ ui.style.display = 'block';
+ }
+
+ try {
+ showUi(spinner);
+ status.innerHTML = 'Loading...';
+
+ const instance = await qtLoad({
+ qt: {
+ onLoaded: () => showUi(screen),
+ onExit: exitData =>
+ {
+ status.innerHTML = 'Application exit';
+ status.innerHTML +=
+ exitData.code !== undefined ? ` with code ${exitData.code}` : '';
+ status.innerHTML +=
+ exitData.text !== undefined ? ` (${exitData.text})` : '';
+ showUi(spinner);
+ },
+ entryFunction: window.@APPEXPORTNAME@,
+ containerElements: [screen],
+ @PRELOAD@
+ }
+ });
+ } catch (e) {
+ console.error(e);
+ console.error(e.stack);
+ }
+ }
</script>
+ <script src="@APPNAME@.js"></script>
<script type="text/javascript" src="qtloader.js"></script>
</body>
</html>
diff --git a/src/plugins/platforms/windows/CMakeLists.txt b/src/plugins/platforms/windows/CMakeLists.txt
index af9c1fccb4..fb73b70c29 100644
--- a/src/plugins/platforms/windows/CMakeLists.txt
+++ b/src/plugins/platforms/windows/CMakeLists.txt
@@ -1,7 +1,5 @@
# Copyright (C) 2022 The Qt Company Ltd.
-# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-
-# Generated from windows.pro.
+# SPDX-License-Identifier: BSD-3-Clause
#####################################################################
## QWindowsIntegrationPlugin Plugin:
@@ -10,7 +8,7 @@
qt_internal_add_plugin(QWindowsIntegrationPlugin
OUTPUT_NAME qwindows
PLUGIN_TYPE platforms
- DEFAULT_IF ${QT_QPA_DEFAULT_PLATFORM} MATCHES windows # special case
+ DEFAULT_IF ${QT_QPA_DEFAULT_PLATFORM} MATCHES windows
SOURCES
main.cpp
qtwindowsglobal.h
@@ -24,12 +22,13 @@ qt_internal_add_plugin(QWindowsIntegrationPlugin
qwindowsdropdataobject.cpp qwindowsdropdataobject.h
qwindowsgdiintegration.cpp qwindowsgdiintegration.h
qwindowsgdinativeinterface.cpp qwindowsgdinativeinterface.h
+ qwindowsiconengine.cpp qwindowsiconengine.h
qwindowsinputcontext.cpp qwindowsinputcontext.h
qwindowsintegration.cpp qwindowsintegration.h
qwindowsinternalmimedata.cpp qwindowsinternalmimedata.h
qwindowskeymapper.cpp qwindowskeymapper.h
qwindowsmenu.cpp qwindowsmenu.h
- qwindowsmime.cpp qwindowsmime.h
+ qwindowsmimeregistry.cpp qwindowsmimeregistry.h
qwindowsmousehandler.cpp qwindowsmousehandler.h
qwindowsnativeinterface.cpp qwindowsnativeinterface.h
qwindowsole.cpp qwindowsole.h
@@ -40,6 +39,8 @@ qt_internal_add_plugin(QWindowsIntegrationPlugin
qwindowstheme.cpp qwindowstheme.h
qwindowsthreadpoolrunner.h
qwindowswindow.cpp qwindowswindow.h
+ NO_UNITY_BUILD_SOURCES
+ qwindowspointerhandler.cpp
DEFINES
QT_NO_CAST_FROM_ASCII
QT_NO_FOREACH
@@ -56,6 +57,7 @@ qt_internal_add_plugin(QWindowsIntegrationPlugin
imm32
ole32
oleaut32
+ setupapi
shell32
shlwapi
user32
@@ -83,10 +85,6 @@ qt_internal_add_resource(QWindowsIntegrationPlugin "openglblacklists"
${openglblacklists_resource_files}
)
-
-#### Keys ignored in scope 1:.:.:windows.pro:<TRUE>:
-# OTHER_FILES = "windows.json"
-
## Scopes:
#####################################################################
@@ -98,9 +96,6 @@ qt_internal_extend_target(QWindowsIntegrationPlugin CONDITION QT_FEATURE_opengl
Qt::OpenGLPrivate
)
-#### Keys ignored in scope 3:.:.:windows.pro:NOT TARGET___equals____ss_QT_DEFAULT_QPA_PLUGIN:
-# PLUGIN_EXTENDS = "-"
-
qt_internal_extend_target(QWindowsIntegrationPlugin CONDITION QT_FEATURE_opengl AND NOT QT_FEATURE_dynamicgl
LIBRARIES
opengl32
@@ -109,6 +104,8 @@ qt_internal_extend_target(QWindowsIntegrationPlugin CONDITION QT_FEATURE_opengl
qt_internal_extend_target(QWindowsIntegrationPlugin CONDITION MINGW
LIBRARIES
uuid
+ NO_PCH_SOURCES
+ qwindowspointerhandler.cpp
)
qt_internal_extend_target(QWindowsIntegrationPlugin CONDITION QT_FEATURE_systemtrayicon
@@ -179,6 +176,7 @@ endif()
qt_internal_extend_target(QWindowsIntegrationPlugin CONDITION QT_FEATURE_accessibility
SOURCES
+ uiautomation/qwindowsuiautomation.cpp uiautomation/qwindowsuiautomation.h
uiautomation/qwindowsuiaaccessibility.cpp uiautomation/qwindowsuiaaccessibility.h
uiautomation/qwindowsuiabaseprovider.cpp uiautomation/qwindowsuiabaseprovider.h
uiautomation/qwindowsuiaexpandcollapseprovider.cpp uiautomation/qwindowsuiaexpandcollapseprovider.h
@@ -200,13 +198,17 @@ qt_internal_extend_target(QWindowsIntegrationPlugin CONDITION QT_FEATURE_accessi
uiautomation/qwindowsuiawindowprovider.cpp uiautomation/qwindowsuiawindowprovider.h
)
+if(QT_FEATURE_accessibility)
+ find_library(UI_AUTOMATION_LIBRARY uiautomationcore)
+ if(UI_AUTOMATION_LIBRARY)
+ qt_internal_extend_target(QWindowsIntegrationPlugin
+ LIBRARIES
+ ${UI_AUTOMATION_LIBRARY}
+ )
+ endif()
+endif()
+
qt_internal_extend_target(QWindowsIntegrationPlugin CONDITION MINGW AND QT_FEATURE_accessibility
LIBRARIES
uuid
)
-
-# begin special case
-if (MINGW)
- set_source_files_properties(qwindowspointerhandler.cpp PROPERTIES SKIP_PRECOMPILE_HEADERS ON)
-endif()
-# end special case
diff --git a/src/plugins/platforms/windows/qtwindowsglobal.h b/src/plugins/platforms/windows/qtwindowsglobal.h
index 9a65603e24..96a72600eb 100644
--- a/src/plugins/platforms/windows/qtwindowsglobal.h
+++ b/src/plugins/platforms/windows/qtwindowsglobal.h
@@ -65,7 +65,7 @@ QT_BEGIN_NAMESPACE
namespace QtWindows
{
-enum
+enum WindowsEventTypeFlags
{
WindowEventFlag = 0x10000,
MouseEventFlag = 0x20000,
@@ -107,6 +107,7 @@ enum WindowsEventType // Simplify event types
PointerActivateWindowEvent = WindowEventFlag + 24,
DpiScaledSizeEvent = WindowEventFlag + 25,
DpiChangedAfterParentEvent = WindowEventFlag + 27,
+ TaskbarButtonCreated = WindowEventFlag + 28,
MouseEvent = MouseEventFlag + 1,
MouseWheelEvent = MouseEventFlag + 2,
CursorEvent = MouseEventFlag + 3,
@@ -118,7 +119,7 @@ enum WindowsEventType // Simplify event types
NonClientPointerEvent = NonClientEventFlag + PointerEventFlag + 4,
KeyEvent = KeyEventFlag + 1,
KeyDownEvent = KeyEventFlag + KeyDownEventFlag + 1,
- KeyboardLayoutChangeEvent = KeyEventFlag + 2,
+ InputLanguageChangeEvent = KeyEventFlag + 2,
InputMethodKeyEvent = InputMethodEventFlag + KeyEventFlag + 1,
InputMethodKeyDownEvent = InputMethodEventFlag + KeyEventFlag + KeyDownEventFlag + 1,
ClipboardEvent = ClipboardEventFlag + 1,
@@ -146,20 +147,31 @@ enum WindowsEventType // Simplify event types
GestureEvent = 124,
UnknownEvent = 542
};
+Q_DECLARE_MIXED_ENUM_OPERATORS(bool, WindowsEventTypeFlags, WindowsEventType);
+Q_DECLARE_MIXED_ENUM_OPERATORS(bool, WindowsEventType, WindowsEventTypeFlags);
-// Matches Process_DPI_Awareness (Windows 8.1 onwards), used for SetProcessDpiAwareness()
-enum ProcessDpiAwareness
+enum class DpiAwareness
{
- ProcessDpiUnaware,
- ProcessSystemDpiAware,
- ProcessPerMonitorDpiAware,
- ProcessPerMonitorV2DpiAware // Qt extension (not in Process_DPI_Awareness)
+ Invalid = -1,
+ Unaware,
+ System,
+ PerMonitor,
+ PerMonitorVersion2,
+ Unaware_GdiScaled
};
} // namespace QtWindows
inline QtWindows::WindowsEventType windowsEventType(UINT message, WPARAM wParamIn, LPARAM lParamIn)
{
+ static const UINT WM_TASKBAR_BUTTON_CREATED = []{
+ UINT message = RegisterWindowMessage(L"TaskbarButtonCreated");
+ // In case the application is run elevated, allow the
+ // TaskbarButtonCreated message through.
+ ChangeWindowMessageFilter(message, MSGFLT_ADD);
+ return message;
+ }();
+
switch (message) {
case WM_PAINT:
case WM_ERASEBKGND:
@@ -218,7 +230,7 @@ inline QtWindows::WindowsEventType windowsEventType(UINT message, WPARAM wParamI
return QtWindows::InputMethodKeyDownEvent;
#ifdef WM_INPUTLANGCHANGE
case WM_INPUTLANGCHANGE:
- return QtWindows::KeyboardLayoutChangeEvent;
+ return QtWindows::InputLanguageChangeEvent;
#endif // WM_INPUTLANGCHANGE
case WM_TOUCH:
return QtWindows::TouchEvent;
@@ -310,9 +322,15 @@ inline QtWindows::WindowsEventType windowsEventType(UINT message, WPARAM wParamI
return QtWindows::NonClientPointerEvent;
if (message >= WM_POINTERUPDATE && message <= WM_POINTERHWHEEL)
return QtWindows::PointerEvent;
+ if (message == WM_TASKBAR_BUTTON_CREATED)
+ return QtWindows::TaskbarButtonCreated;
return QtWindows::UnknownEvent;
}
+#ifndef QT_NO_DEBUG_STREAM
+extern QDebug operator<<(QDebug, QtWindows::DpiAwareness);
+#endif
+
QT_END_NAMESPACE
#endif // QTWINDOWSGLOBAL_H
diff --git a/src/plugins/platforms/windows/qwindowsapplication.cpp b/src/plugins/platforms/windows/qwindowsapplication.cpp
index ce52cc817e..60cbf1f7ba 100644
--- a/src/plugins/platforms/windows/qwindowsapplication.cpp
+++ b/src/plugins/platforms/windows/qwindowsapplication.cpp
@@ -4,11 +4,17 @@
#include "qwindowsapplication.h"
#include "qwindowsclipboard.h"
#include "qwindowscontext.h"
-#include "qwindowsmime.h"
+#include "qwindowsmimeregistry.h"
#include "qwin10helpers.h"
#include "qwindowsopengltester.h"
+#include "qwindowswindow.h"
+#include "qwindowsintegration.h"
+#include "qwindowstheme.h"
-#include <QtCore/QVariant>
+#include <QtCore/qvariant.h>
+#include <QtCore/private/qfunctions_win_p.h>
+
+#include <QtGui/qpalette.h>
QT_BEGIN_NAMESPACE
@@ -36,6 +42,11 @@ void QWindowsApplication::setWindowActivationBehavior(WindowActivationBehavior b
m_windowActivationBehavior = behavior;
}
+void QWindowsApplication::setHasBorderInFullScreenDefault(bool border)
+{
+ QWindowsWindow::setHasBorderInFullScreenDefault(border);
+}
+
bool QWindowsApplication::isTabletMode() const
{
#if QT_CONFIG(clipboard)
@@ -76,13 +87,13 @@ void QWindowsApplication::setDarkModeHandling(QWindowsApplication::DarkModeHandl
m_darkModeHandling = handling;
}
-void QWindowsApplication::registerMime(QNativeInterface::Private::QWindowsMime *mime)
+void QWindowsApplication::registerMime(QWindowsMimeConverter *mime)
{
if (auto ctx = QWindowsContext::instance())
ctx->mimeConverter().registerMime(mime);
}
-void QWindowsApplication::unregisterMime(QNativeInterface::Private::QWindowsMime *mime)
+void QWindowsApplication::unregisterMime(QWindowsMimeConverter *mime)
{
if (auto ctx = QWindowsContext::instance())
ctx->mimeConverter().unregisterMime(mime);
@@ -90,7 +101,7 @@ void QWindowsApplication::unregisterMime(QNativeInterface::Private::QWindowsMime
int QWindowsApplication::registerMimeType(const QString &mime)
{
- return QWindowsMimeConverter::registerMimeType(mime);
+ return QWindowsMimeRegistry::registerMimeType(mime);
}
HWND QWindowsApplication::createMessageWindow(const QString &classNameTemplate,
@@ -132,4 +143,9 @@ QVariant QWindowsApplication::gpuList() const
return result;
}
+void QWindowsApplication::populateLightSystemPalette(QPalette &result) const
+{
+ result = QWindowsTheme::systemPalette(Qt::ColorScheme::Light);
+}
+
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/windows/qwindowsapplication.h b/src/plugins/platforms/windows/qwindowsapplication.h
index fc6d8b5b76..efacd74e18 100644
--- a/src/plugins/platforms/windows/qwindowsapplication.h
+++ b/src/plugins/platforms/windows/qwindowsapplication.h
@@ -17,6 +17,8 @@ public:
WindowActivationBehavior windowActivationBehavior() const override;
void setWindowActivationBehavior(WindowActivationBehavior behavior) override;
+ void setHasBorderInFullScreenDefault(bool border) override;
+
bool isTabletMode() const override;
bool isWinTabEnabled() const override;
@@ -26,8 +28,8 @@ public:
DarkModeHandling darkModeHandling() const override;
void setDarkModeHandling(DarkModeHandling handling) override;
- void registerMime(QNativeInterface::Private::QWindowsMime *mime) override;
- void unregisterMime(QNativeInterface::Private::QWindowsMime *mime) override;
+ void registerMime(QWindowsMimeConverter *mime) override;
+ void unregisterMime(QWindowsMimeConverter *mime) override;
int registerMimeType(const QString &mime) override;
@@ -41,6 +43,8 @@ public:
QVariant gpu() const override;
QVariant gpuList() const override;
+ void populateLightSystemPalette(QPalette &palette) const override;
+
private:
WindowActivationBehavior m_windowActivationBehavior = DefaultActivateWindow;
TouchWindowTouchTypes m_touchWindowTouchTypes = NormalTouch;
diff --git a/src/plugins/platforms/windows/qwindowsbackingstore.cpp b/src/plugins/platforms/windows/qwindowsbackingstore.cpp
index 0f9d0172d9..07918f6094 100644
--- a/src/plugins/platforms/windows/qwindowsbackingstore.cpp
+++ b/src/plugins/platforms/windows/qwindowsbackingstore.cpp
@@ -117,7 +117,7 @@ void QWindowsBackingStore::resize(const QSize &size, const QRegion &region)
if (QImage::toPixelFormat(format).alphaUsage() == QPixelFormat::UsesAlpha)
m_alphaNeedsFill = true;
else // upgrade but here we know app painting does not rely on alpha hence no need to fill
- format = qt_maybeAlphaVersionWithSameDepth(format);
+ format = qt_maybeDataCompatibleAlphaVersion(format);
QWindowsNativeImage *oldwni = m_image.data();
auto *newwni = new QWindowsNativeImage(size.width(), size.height(), format);
diff --git a/src/plugins/platforms/windows/qwindowsclipboard.cpp b/src/plugins/platforms/windows/qwindowsclipboard.cpp
index 9bdd331467..7a6d41e0b3 100644
--- a/src/plugins/platforms/windows/qwindowsclipboard.cpp
+++ b/src/plugins/platforms/windows/qwindowsclipboard.cpp
@@ -4,7 +4,6 @@
#include "qwindowsclipboard.h"
#include "qwindowscontext.h"
#include "qwindowsole.h"
-#include "qwindowsmime.h"
#include <QtGui/qguiapplication.h>
#include <QtGui/qclipboard.h>
@@ -17,6 +16,7 @@
#include <QtCore/qthread.h>
#include <QtCore/qvariant.h>
#include <QtCore/qurl.h>
+#include <QtCore/private/qsystemerror_p.h>
#include <QtGui/private/qwindowsguieventdispatcher_p.h>
@@ -301,7 +301,7 @@ void QWindowsClipboard::setMimeData(QMimeData *mimeData, QClipboard::Mode mode)
mimeData->formats().join(u", ") : QString(QStringLiteral("NULL"));
qErrnoWarning("OleSetClipboard: Failed to set mime data (%s) on clipboard: %s",
qPrintable(mimeDataFormats),
- QWindowsContext::comErrorString(src).constData());
+ qPrintable(QSystemError::windowsComString(src)));
releaseIData();
return;
}
diff --git a/src/plugins/platforms/windows/qwindowscombase.h b/src/plugins/platforms/windows/qwindowscombase.h
index 04d4dc51cf..c54ff7a11f 100644
--- a/src/plugins/platforms/windows/qwindowscombase.h
+++ b/src/plugins/platforms/windows/qwindowscombase.h
@@ -4,6 +4,8 @@
#ifndef QWINDOWSCOMBASE_H
#define QWINDOWSCOMBASE_H
+#include <qt_windows.h>
+
#include <QtCore/qglobal.h>
#include <unknwn.h>
@@ -41,36 +43,6 @@ bool qWindowsComQueryUnknownInterfaceMulti(Derived *d, REFIID id, LPVOID *iface)
return false;
}
-// Helper base class to provide IUnknown methods for COM classes (single inheritance)
-template <class ComInterface> class QWindowsComBase : public ComInterface
-{
- Q_DISABLE_COPY_MOVE(QWindowsComBase)
-public:
- explicit QWindowsComBase(ULONG initialRefCount = 1) : m_ref(initialRefCount) {}
- virtual ~QWindowsComBase() = default;
-
- HRESULT STDMETHODCALLTYPE QueryInterface(REFIID id, LPVOID *iface) override
- {
- *iface = nullptr;
- return qWindowsComQueryInterface<IUnknown>(this, id, iface) || qWindowsComQueryInterface<ComInterface>(this, id, iface)
- ? S_OK : E_NOINTERFACE;
- }
-
- ULONG STDMETHODCALLTYPE AddRef() override { return ++m_ref; }
-
- ULONG STDMETHODCALLTYPE Release() override
- {
- if (!--m_ref) {
- delete this;
- return 0;
- }
- return m_ref;
- }
-
-private:
- ULONG m_ref;
-};
-
// Clang does not consider __declspec(nothrow) as nothrow
QT_WARNING_DISABLE_CLANG("-Wmicrosoft-exception-spec")
QT_WARNING_DISABLE_CLANG("-Wmissing-exception-spec")
diff --git a/src/plugins/platforms/windows/qwindowscontext.cpp b/src/plugins/platforms/windows/qwindowscontext.cpp
index b52d624a76..c85c44d949 100644
--- a/src/plugins/platforms/windows/qwindowscontext.cpp
+++ b/src/plugins/platforms/windows/qwindowscontext.cpp
@@ -11,7 +11,7 @@
#include "qwindowspointerhandler.h"
#include "qtwindowsglobal.h"
#include "qwindowsmenu.h"
-#include "qwindowsmime.h"
+#include "qwindowsmimeregistry.h"
#include "qwindowsinputcontext.h"
#if QT_CONFIG(tabletevent)
# include "qwindowstabletsupport.h"
@@ -45,13 +45,17 @@
#include <QtCore/qscopedpointer.h>
#include <QtCore/quuid.h>
#include <QtCore/private/qwinregistry_p.h>
+#if QT_CONFIG(cpp_winrt)
+# include <QtCore/private/qfactorycacheregistration_p.h>
+#endif
+#include <QtCore/private/qsystemerror_p.h>
#include <QtGui/private/qwindowsguieventdispatcher_p.h>
+#include <QtGui/private/qwindowsthemecache_p.h>
#include <stdlib.h>
#include <stdio.h>
#include <windowsx.h>
-#include <comdef.h>
#include <dbt.h>
#include <wtsapi32.h>
#include <shellscalingapi.h>
@@ -60,7 +64,7 @@ QT_BEGIN_NAMESPACE
using namespace Qt::StringLiterals;
-Q_LOGGING_CATEGORY(lcQpaWindows, "qt.qpa.windows")
+Q_LOGGING_CATEGORY(lcQpaWindow, "qt.qpa.window")
Q_LOGGING_CATEGORY(lcQpaEvents, "qt.qpa.events")
Q_LOGGING_CATEGORY(lcQpaGl, "qt.qpa.gl")
Q_LOGGING_CATEGORY(lcQpaMime, "qt.qpa.mime")
@@ -117,26 +121,6 @@ static inline bool sessionManagerInteractionBlocked()
static inline bool sessionManagerInteractionBlocked() { return false; }
#endif
-static inline int windowDpiAwareness(HWND hwnd)
-{
- return static_cast<int>(GetAwarenessFromDpiAwarenessContext(GetWindowDpiAwarenessContext(hwnd)));
-}
-
-// Note: This only works within WM_NCCREATE
-static bool enableNonClientDpiScaling(HWND hwnd)
-{
- bool result = false;
- if (windowDpiAwareness(hwnd) == 2) {
- result = EnableNonClientDpiScaling(hwnd) != FALSE;
- if (!result) {
- const DWORD errorCode = GetLastError();
- qErrnoWarning(int(errorCode), "EnableNonClientDpiScaling() failed for HWND %p (%lu)",
- hwnd, errorCode);
- }
- }
- return result;
-}
-
QWindowsContext *QWindowsContext::m_instance = nullptr;
/*!
@@ -159,7 +143,7 @@ struct QWindowsContextPrivate {
QWindowsKeyMapper m_keyMapper;
QWindowsMouseHandler m_mouseHandler;
QWindowsPointerHandler m_pointerHandler;
- QWindowsMimeConverter m_mimeConverter;
+ QWindowsMimeRegistry m_mimeConverter;
QWindowsScreenManager m_screenManager;
QSharedPointer<QWindowCreationContext> m_creationContext;
#if QT_CONFIG(tabletevent)
@@ -191,7 +175,7 @@ QWindowsContextPrivate::QWindowsContextPrivate()
m_darkMode = QWindowsTheme::queryDarkMode();
if (FAILED(m_oleInitializeResult)) {
qWarning() << "QWindowsContext: OleInitialize() failed: "
- << QWindowsContext::comErrorString(m_oleInitializeResult);
+ << QSystemError::windowsComString(m_oleInitializeResult);
}
}
@@ -221,8 +205,12 @@ QWindowsContext::~QWindowsContext()
DestroyWindow(d->m_powerDummyWindow);
unregisterWindowClasses();
- if (d->m_oleInitializeResult == S_OK || d->m_oleInitializeResult == S_FALSE)
+ if (d->m_oleInitializeResult == S_OK || d->m_oleInitializeResult == S_FALSE) {
+#ifdef QT_USE_FACTORY_CACHE_REGISTRATION
+ detail::QWinRTFactoryCacheRegistration::clearAllCaches();
+#endif
OleUninitialize();
+ }
d->m_screenManager.clearScreens(); // Order: Potentially calls back to the windows.
if (d->m_displayContext)
@@ -264,7 +252,7 @@ void QWindowsContext::registerTouchWindows()
{
if (QGuiApplicationPrivate::is_app_running
&& (d->m_systemInfo & QWindowsContext::SI_SupportsTouch) != 0) {
- for (QWindowsWindow *w : qAsConst(d->m_windows))
+ for (QWindowsWindow *w : std::as_const(d->m_windows))
w->registerTouchWindow();
}
}
@@ -361,40 +349,118 @@ void QWindowsContext::setDetectAltGrModifier(bool a)
d->m_keyMapper.setDetectAltGrModifier(a);
}
-int QWindowsContext::processDpiAwareness()
-{
- PROCESS_DPI_AWARENESS result;
- if (SUCCEEDED(GetProcessDpiAwareness(nullptr, &result))) {
- return static_cast<int>(result);
+[[nodiscard]] static inline QtWindows::DpiAwareness
+ dpiAwarenessContextToQtDpiAwareness(DPI_AWARENESS_CONTEXT context)
+{
+ // IsValidDpiAwarenessContext() will handle the NULL pointer case.
+ if (!IsValidDpiAwarenessContext(context))
+ return QtWindows::DpiAwareness::Invalid;
+ if (AreDpiAwarenessContextsEqual(context, DPI_AWARENESS_CONTEXT_UNAWARE_GDISCALED))
+ return QtWindows::DpiAwareness::Unaware_GdiScaled;
+ if (AreDpiAwarenessContextsEqual(context, DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2))
+ return QtWindows::DpiAwareness::PerMonitorVersion2;
+ if (AreDpiAwarenessContextsEqual(context, DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE))
+ return QtWindows::DpiAwareness::PerMonitor;
+ if (AreDpiAwarenessContextsEqual(context, DPI_AWARENESS_CONTEXT_SYSTEM_AWARE))
+ return QtWindows::DpiAwareness::System;
+ if (AreDpiAwarenessContextsEqual(context, DPI_AWARENESS_CONTEXT_UNAWARE))
+ return QtWindows::DpiAwareness::Unaware;
+ return QtWindows::DpiAwareness::Invalid;
+}
+
+QtWindows::DpiAwareness QWindowsContext::windowDpiAwareness(HWND hwnd)
+{
+ if (!hwnd)
+ return QtWindows::DpiAwareness::Invalid;
+ const auto context = GetWindowDpiAwarenessContext(hwnd);
+ return dpiAwarenessContextToQtDpiAwareness(context);
+}
+
+QtWindows::DpiAwareness QWindowsContext::processDpiAwareness()
+{
+ // Although we have GetDpiAwarenessContextForProcess(), however,
+ // it's only available on Win10 1903+, which is a little higher
+ // than Qt's minimum supported version (1809), so we can't use it.
+ // Luckily, MS docs said GetThreadDpiAwarenessContext() will also
+ // return the default DPI_AWARENESS_CONTEXT for the process if
+ // SetThreadDpiAwarenessContext() was never called. So we can use
+ // it as an equivalent.
+ const auto context = GetThreadDpiAwarenessContext();
+ return dpiAwarenessContextToQtDpiAwareness(context);
+}
+
+[[nodiscard]] static inline DPI_AWARENESS_CONTEXT
+ qtDpiAwarenessToDpiAwarenessContext(QtWindows::DpiAwareness dpiAwareness)
+{
+ switch (dpiAwareness) {
+ case QtWindows::DpiAwareness::Invalid:
+ return nullptr;
+ case QtWindows::DpiAwareness::Unaware:
+ return DPI_AWARENESS_CONTEXT_UNAWARE;
+ case QtWindows::DpiAwareness::System:
+ return DPI_AWARENESS_CONTEXT_SYSTEM_AWARE;
+ case QtWindows::DpiAwareness::PerMonitor:
+ return DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE;
+ case QtWindows::DpiAwareness::PerMonitorVersion2:
+ return DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2;
+ case QtWindows::DpiAwareness::Unaware_GdiScaled:
+ return DPI_AWARENESS_CONTEXT_UNAWARE_GDISCALED;
}
- return -1;
+ return nullptr;
}
-void QWindowsContext::setProcessDpiAwareness(QtWindows::ProcessDpiAwareness dpiAwareness)
+#ifndef QT_NO_DEBUG_STREAM
+QDebug operator<<(QDebug d, QtWindows::DpiAwareness dpiAwareness)
{
- qCDebug(lcQpaWindows) << __FUNCTION__ << dpiAwareness;
- const HRESULT hr = SetProcessDpiAwareness(static_cast<PROCESS_DPI_AWARENESS>(dpiAwareness));
- // E_ACCESSDENIED means set externally (MSVC manifest or external app loading Qt plugin).
- // Silence warning in that case unless debug is enabled.
- if (FAILED(hr) && (hr != E_ACCESSDENIED || lcQpaWindows().isDebugEnabled())) {
- qWarning().noquote().nospace() << "SetProcessDpiAwareness("
- << dpiAwareness << ") failed: " << QWindowsContext::comErrorString(hr)
- << ", using " << QWindowsContext::processDpiAwareness();
+ const QDebugStateSaver saver(d);
+ QString message = u"QtWindows::DpiAwareness::"_s;
+ switch (dpiAwareness) {
+ case QtWindows::DpiAwareness::Invalid:
+ message += u"Invalid"_s;
+ break;
+ case QtWindows::DpiAwareness::Unaware:
+ message += u"Unaware"_s;
+ break;
+ case QtWindows::DpiAwareness::System:
+ message += u"System"_s;
+ break;
+ case QtWindows::DpiAwareness::PerMonitor:
+ message += u"PerMonitor"_s;
+ break;
+ case QtWindows::DpiAwareness::PerMonitorVersion2:
+ message += u"PerMonitorVersion2"_s;
+ break;
+ case QtWindows::DpiAwareness::Unaware_GdiScaled:
+ message += u"Unaware_GdiScaled"_s;
+ break;
}
+ d.nospace().noquote() << message;
+ return d;
}
+#endif
-bool QWindowsContext::setProcessDpiV2Awareness()
+bool QWindowsContext::setProcessDpiAwareness(QtWindows::DpiAwareness dpiAwareness)
{
- qCDebug(lcQpaWindows) << __FUNCTION__;
- const BOOL ok = SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);
- if (!ok) {
- const HRESULT errorCode = GetLastError();
- qCWarning(lcQpaWindows).noquote().nospace() << "setProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2) failed: "
- << QWindowsContext::comErrorString(errorCode);
+ qCDebug(lcQpaWindow) << __FUNCTION__ << dpiAwareness;
+ if (processDpiAwareness() == dpiAwareness)
+ return true;
+ const auto context = qtDpiAwarenessToDpiAwarenessContext(dpiAwareness);
+ if (!IsValidDpiAwarenessContext(context)) {
+ qCWarning(lcQpaWindow) << dpiAwareness << "is not supported by current system.";
return false;
}
-
- QWindowsContextPrivate::m_v2DpiAware = true;
+ if (!SetProcessDpiAwarenessContext(context)) {
+ qCWarning(lcQpaWindow).noquote().nospace()
+ << "SetProcessDpiAwarenessContext() failed: "
+ << QSystemError::windowsString()
+ << "\nQt's default DPI awareness context is "
+ << "DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2. If you know what you "
+ << "are doing, you can overwrite this default using qt.conf "
+ << "(https://doc.qt.io/qt-6/highdpi.html#configuring-windows).";
+ return false;
+ }
+ QWindowsContextPrivate::m_v2DpiAware
+ = processDpiAwareness() == QtWindows::DpiAwareness::PerMonitorVersion2;
return true;
}
@@ -418,9 +484,9 @@ bool QWindowsContext::useRTLExtensions() const
return d->m_keyMapper.useRTLExtensions();
}
-QList<int> QWindowsContext::possibleKeys(const QKeyEvent *e) const
+QPlatformKeyMapper *QWindowsContext::keyMapper() const
{
- return d->m_keyMapper.possibleKeys(e);
+ return &d->m_keyMapper;
}
QWindowsContext::HandleBaseWindowHash &QWindowsContext::windows()
@@ -472,6 +538,8 @@ QString QWindowsContext::classNamePrefix()
# define xstr(s) str(s)
# define str(s) #s
str << xstr(QT_NAMESPACE);
+# undef str
+# undef xstr
#endif
}
return result;
@@ -532,7 +600,7 @@ QString QWindowsContext::registerWindowClass(const QWindow *w)
if (icon)
cname += "Icon"_L1;
- return registerWindowClass(cname, qWindowsWndProc, style, GetSysColorBrush(COLOR_WINDOW), icon);
+ return registerWindowClass(cname, qWindowsWndProc, style, nullptr, icon);
}
QString QWindowsContext::registerWindowClass(QString cname,
@@ -591,7 +659,7 @@ QString QWindowsContext::registerWindowClass(QString cname,
qPrintable(cname));
d->m_registeredWindowClassNames.insert(cname);
- qCDebug(lcQpaWindows).nospace() << __FUNCTION__ << ' ' << cname
+ qCDebug(lcQpaWindow).nospace() << __FUNCTION__ << ' ' << cname
<< " style=0x" << Qt::hex << style << Qt::dec
<< " brush=" << brush << " icon=" << icon << " atom=" << atom;
return cname;
@@ -601,7 +669,7 @@ void QWindowsContext::unregisterWindowClasses()
{
const auto appInstance = static_cast<HINSTANCE>(GetModuleHandle(nullptr));
- for (const QString &name : qAsConst(d->m_registeredWindowClassNames)) {
+ for (const QString &name : std::as_const(d->m_registeredWindowClassNames)) {
if (!UnregisterClass(reinterpret_cast<LPCWSTR>(name.utf16()), appInstance) && QWindowsContext::verbose)
qErrnoWarning("UnregisterClass failed for '%s'", qPrintable(name));
}
@@ -613,23 +681,6 @@ int QWindowsContext::screenDepth() const
return GetDeviceCaps(d->m_displayContext, BITSPIXEL);
}
-QString QWindowsContext::windowsErrorMessage(unsigned long errorCode)
-{
- QString rc = QString::fromLatin1("#%1: ").arg(errorCode);
- char16_t *lpMsgBuf;
-
- const DWORD len = FormatMessage(
- FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
- nullptr, errorCode, 0, reinterpret_cast<LPTSTR>(&lpMsgBuf), 0, nullptr);
- if (len) {
- rc = QString::fromUtf16(lpMsgBuf, int(len));
- LocalFree(lpMsgBuf);
- } else {
- rc += QString::fromLatin1("<unknown error>");
- }
- return rc;
-}
-
void QWindowsContext::addWindow(HWND hwnd, QWindowsWindow *w)
{
d->m_windows.insert(hwnd, w);
@@ -792,7 +843,7 @@ bool QWindowsContext::isSessionLocked()
return result;
}
-QWindowsMimeConverter &QWindowsContext::mimeConverter() const
+QWindowsMimeRegistry &QWindowsContext::mimeConverter() const
{
return d->m_mimeConverter;
}
@@ -830,79 +881,6 @@ HWND QWindowsContext::createDummyWindow(const QString &classNameIn,
HWND_MESSAGE, nullptr, static_cast<HINSTANCE>(GetModuleHandle(nullptr)), nullptr);
}
-/*!
- \brief Common COM error strings.
-*/
-
-QByteArray QWindowsContext::comErrorString(HRESULT hr)
-{
- QByteArray result = QByteArrayLiteral("COM error 0x")
- + QByteArray::number(quintptr(hr), 16) + ' ';
- switch (hr) {
- case S_OK:
- result += QByteArrayLiteral("S_OK");
- break;
- case S_FALSE:
- result += QByteArrayLiteral("S_FALSE");
- break;
- case E_UNEXPECTED:
- result += QByteArrayLiteral("E_UNEXPECTED");
- break;
- case E_ACCESSDENIED:
- result += QByteArrayLiteral("E_ACCESSDENIED");
- break;
- case CO_E_ALREADYINITIALIZED:
- result += QByteArrayLiteral("CO_E_ALREADYINITIALIZED");
- break;
- case CO_E_NOTINITIALIZED:
- result += QByteArrayLiteral("CO_E_NOTINITIALIZED");
- break;
- case RPC_E_CHANGED_MODE:
- result += QByteArrayLiteral("RPC_E_CHANGED_MODE");
- break;
- case OLE_E_WRONGCOMPOBJ:
- result += QByteArrayLiteral("OLE_E_WRONGCOMPOBJ");
- break;
- case CO_E_NOT_SUPPORTED:
- result += QByteArrayLiteral("CO_E_NOT_SUPPORTED");
- break;
- case E_NOTIMPL:
- result += QByteArrayLiteral("E_NOTIMPL");
- break;
- case E_INVALIDARG:
- result += QByteArrayLiteral("E_INVALIDARG");
- break;
- case E_NOINTERFACE:
- result += QByteArrayLiteral("E_NOINTERFACE");
- break;
- case E_POINTER:
- result += QByteArrayLiteral("E_POINTER");
- break;
- case E_HANDLE:
- result += QByteArrayLiteral("E_HANDLE");
- break;
- case E_ABORT:
- result += QByteArrayLiteral("E_ABORT");
- break;
- case E_FAIL:
- result += QByteArrayLiteral("E_FAIL");
- break;
- case RPC_E_WRONG_THREAD:
- result += QByteArrayLiteral("RPC_E_WRONG_THREAD");
- break;
- case RPC_E_THREAD_NOT_INIT:
- result += QByteArrayLiteral("RPC_E_THREAD_NOT_INIT");
- break;
- default:
- break;
- }
- _com_error error(hr);
- result += QByteArrayLiteral(" (");
- result += QString::fromWCharArray(error.ErrorMessage()).toUtf8();
- result += ')';
- return result;
-}
-
void QWindowsContext::forceNcCalcSize(HWND hwnd)
{
// Force WM_NCCALCSIZE to adjust margin
@@ -997,6 +975,21 @@ static inline bool isInputMessage(UINT m)
|| (m >= WM_KEYFIRST && m <= WM_KEYLAST);
}
+// Note: This only works within WM_NCCREATE
+static bool enableNonClientDpiScaling(HWND hwnd)
+{
+ bool result = false;
+ if (QWindowsContext::windowDpiAwareness(hwnd) == QtWindows::DpiAwareness::PerMonitor) {
+ result = EnableNonClientDpiScaling(hwnd) != FALSE;
+ if (!result) {
+ const DWORD errorCode = GetLastError();
+ qErrnoWarning(int(errorCode), "EnableNonClientDpiScaling() failed for HWND %p (%lu)",
+ hwnd, errorCode);
+ }
+ }
+ return result;
+}
+
/*!
\brief Main windows procedure registered for windows.
@@ -1013,9 +1006,10 @@ bool QWindowsContext::windowsProc(HWND hwnd, UINT message,
MSG msg;
msg.hwnd = hwnd; // re-create MSG structure
- msg.message = message; // time and pt fields ignored
+ msg.message = message;
msg.wParam = wParam;
msg.lParam = lParam;
+ msg.time = GetMessageTime();
msg.pt.x = msg.pt.y = 0;
if (et != QtWindows::CursorEvent && (et & (QtWindows::MouseEventFlag | QtWindows::NonClientEventFlag))) {
msg.pt.x = GET_X_LPARAM(lParam);
@@ -1103,6 +1097,7 @@ bool QWindowsContext::windowsProc(HWND hwnd, UINT message,
const bool darkModeChanged = darkMode != QWindowsContextPrivate::m_darkMode;
QWindowsContextPrivate::m_darkMode = darkMode;
auto integration = QWindowsIntegration::instance();
+ integration->updateApplicationBadge();
if (integration->darkModeHandling().testFlag(QWindowsApplication::DarkModeStyle)) {
QWindowsTheme::instance()->refresh();
QWindowSystemInterface::handleThemeChange();
@@ -1129,7 +1124,7 @@ bool QWindowsContext::windowsProc(HWND hwnd, UINT message,
d->m_creationContext->applyToMinMaxInfo(reinterpret_cast<MINMAXINFO *>(lParam));
return true;
case QtWindows::ResizeEvent:
- d->m_creationContext->obtainedSize = QSize(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
+ d->m_creationContext->obtainedSize = QSize(LOWORD(lParam), HIWORD(lParam));
return true;
case QtWindows::MoveEvent:
d->m_creationContext->obtainedPos = QPoint(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
@@ -1172,7 +1167,7 @@ bool QWindowsContext::windowsProc(HWND hwnd, UINT message,
if (wParam == DBT_DEVNODES_CHANGED)
initTouch();
break;
- case QtWindows::KeyboardLayoutChangeEvent:
+ case QtWindows::InputLanguageChangeEvent:
if (QWindowsInputContext *wic = windowsInputContext())
wic->handleInputLanguageChanged(wParam, lParam);
Q_FALLTHROUGH();
@@ -1202,7 +1197,7 @@ bool QWindowsContext::windowsProc(HWND hwnd, UINT message,
platformWindow->handleMoved();
return true;
case QtWindows::ResizeEvent:
- platformWindow->handleResized(static_cast<int>(wParam));
+ platformWindow->handleResized(static_cast<int>(wParam), lParam);
return true;
case QtWindows::QuerySizeHints:
platformWindow->getSizeHints(reinterpret_cast<MINMAXINFO *>(lParam));
@@ -1216,21 +1211,29 @@ bool QWindowsContext::windowsProc(HWND hwnd, UINT message,
case QtWindows::ExposeEvent:
return platformWindow->handleWmPaint(hwnd, message, wParam, lParam, result);
case QtWindows::NonClientMouseEvent:
- if ((d->m_systemInfo & QWindowsContext::SI_SupportsPointer) && platformWindow->frameStrutEventsEnabled())
+ if (!platformWindow->frameStrutEventsEnabled())
+ break;
+ if ((d->m_systemInfo & QWindowsContext::SI_SupportsPointer))
return sessionManagerInteractionBlocked() || d->m_pointerHandler.translateMouseEvent(platformWindow->window(), hwnd, et, msg, result);
else
return sessionManagerInteractionBlocked() || d->m_mouseHandler.translateMouseEvent(platformWindow->window(), hwnd, et, msg, result);
case QtWindows::NonClientPointerEvent:
- if ((d->m_systemInfo & QWindowsContext::SI_SupportsPointer) && platformWindow->frameStrutEventsEnabled())
+ if (!platformWindow->frameStrutEventsEnabled())
+ break;
+ if ((d->m_systemInfo & QWindowsContext::SI_SupportsPointer))
return sessionManagerInteractionBlocked() || d->m_pointerHandler.translatePointerEvent(platformWindow->window(), hwnd, et, msg, result);
break;
case QtWindows::EnterSizeMoveEvent:
platformWindow->setFlag(QWindowsWindow::ResizeMoveActive);
+ if (!IsZoomed(hwnd))
+ platformWindow->updateRestoreGeometry();
return true;
case QtWindows::ExitSizeMoveEvent:
platformWindow->clearFlag(QWindowsWindow::ResizeMoveActive);
platformWindow->checkForScreenChanged();
handleExitSizeMove(platformWindow->window());
+ if (!IsZoomed(hwnd))
+ platformWindow->updateRestoreGeometry();
return true;
case QtWindows::ScrollEvent:
if (!(d->m_systemInfo & QWindowsContext::SI_SupportsPointer))
@@ -1276,6 +1279,7 @@ bool QWindowsContext::windowsProc(HWND hwnd, UINT message,
QWindowSystemInterface::handleCloseEvent(platformWindow->window());
return true;
case QtWindows::ThemeChanged: {
+ QWindowsThemeCache::clearThemeCache(platformWindow->handle());
// Switch from Aero to Classic changes margins.
if (QWindowsTheme *theme = QWindowsTheme::instance())
theme->windowsThemeChanged(platformWindow->window());
@@ -1367,6 +1371,11 @@ bool QWindowsContext::windowsProc(HWND hwnd, UINT message,
return true;
}
#endif // !defined(QT_NO_SESSIONMANAGER)
+ case QtWindows::TaskbarButtonCreated:
+ // Apply application badge if this is the first time we have a taskbar
+ // button, or after Explorer restart.
+ QWindowsIntegration::instance()->updateApplicationBadge();
+ break;
default:
break;
}
@@ -1409,7 +1418,7 @@ void QWindowsContext::handleFocusEvent(QtWindows::WindowsEventType et,
}
if (nextActiveWindow != d->m_lastActiveWindow) {
d->m_lastActiveWindow = nextActiveWindow;
- QWindowSystemInterface::handleWindowActivated(nextActiveWindow, Qt::ActiveWindowFocusReason);
+ QWindowSystemInterface::handleFocusWindowChanged(nextActiveWindow, Qt::ActiveWindowFocusReason);
}
}
@@ -1439,7 +1448,7 @@ bool QWindowsContext::handleContextMenuEvent(QWindow *window, const MSG &msg)
}
QWindowSystemInterface::handleContextMenuEvent(window, mouseTriggered, pos, globalPos,
- QWindowsKeyMapper::queryKeyboardModifiers());
+ keyMapper()->queryKeyboardModifiers());
return true;
}
#endif
@@ -1460,7 +1469,7 @@ void QWindowsContext::handleExitSizeMove(QWindow *window)
const Qt::MouseButtons appButtons = QGuiApplication::mouseButtons();
if (currentButtons == appButtons)
return;
- const Qt::KeyboardModifiers keyboardModifiers = QWindowsKeyMapper::queryKeyboardModifiers();
+ const Qt::KeyboardModifiers keyboardModifiers = keyMapper()->queryKeyboardModifiers();
const QPoint globalPos = QWindowsCursor::mousePosition();
const QPlatformWindow *platWin = window->handle();
const QPoint localPos = platWin->mapFromGlobal(globalPos);
@@ -1469,8 +1478,7 @@ void QWindowsContext::handleExitSizeMove(QWindow *window)
for (Qt::MouseButton button : {Qt::LeftButton, Qt::RightButton, Qt::MiddleButton}) {
if (appButtons.testFlag(button) && !currentButtons.testFlag(button)) {
QWindowSystemInterface::handleMouseEvent(window, localPos, globalPos,
- currentButtons, button, type,
- keyboardModifiers);
+ currentButtons, button, type, keyboardModifiers);
}
}
if (d->m_systemInfo & QWindowsContext::SI_SupportsPointer)
@@ -1564,7 +1572,7 @@ extern "C" LRESULT QT_WIN_CALLBACK qWindowsWndProc(HWND hwnd, UINT message, WPAR
marginsFromRects(ncCalcSizeFrame, rectFromNcCalcSize(message, wParam, lParam, 0));
if (margins.left() >= 0) {
if (platformWindow) {
- qCDebug(lcQpaWindows) << __FUNCTION__ << "WM_NCCALCSIZE for" << hwnd << margins;
+ qCDebug(lcQpaWindow) << __FUNCTION__ << "WM_NCCALCSIZE for" << hwnd << margins;
platformWindow->setFullFrameMargins(margins);
} else {
const QSharedPointer<QWindowCreationContext> ctx = QWindowsContext::instance()->windowCreationContext();
diff --git a/src/plugins/platforms/windows/qwindowscontext.h b/src/plugins/platforms/windows/qwindowscontext.h
index af92690b3b..1089224433 100644
--- a/src/plugins/platforms/windows/qwindowscontext.h
+++ b/src/plugins/platforms/windows/qwindowscontext.h
@@ -15,12 +15,9 @@
#include <shlobj.h>
#include <shlwapi.h>
-struct IBindCtx;
-struct _SHSTOCKICONINFO;
-
QT_BEGIN_NAMESPACE
-Q_DECLARE_LOGGING_CATEGORY(lcQpaWindows)
+Q_DECLARE_LOGGING_CATEGORY(lcQpaWindow)
Q_DECLARE_LOGGING_CATEGORY(lcQpaEvents)
Q_DECLARE_LOGGING_CATEGORY(lcQpaGl)
Q_DECLARE_LOGGING_CATEGORY(lcQpaMime)
@@ -36,17 +33,17 @@ Q_DECLARE_LOGGING_CATEGORY(lcQpaScreen)
class QWindow;
class QPlatformScreen;
class QPlatformWindow;
+class QPlatformKeyMapper;
class QWindowsMenuBar;
class QWindowsScreenManager;
class QWindowsTabletSupport;
class QWindowsWindow;
-class QWindowsMimeConverter;
+class QWindowsMimeRegistry;
struct QWindowCreationContext;
struct QWindowsContextPrivate;
class QPoint;
class QKeyEvent;
class QPointingDevice;
-
class QWindowsContext
{
Q_DISABLE_COPY_MOVE(QWindowsContext)
@@ -91,8 +88,6 @@ public:
static QWindowsContext *instance();
- static QString windowsErrorMessage(unsigned long errorCode);
-
void addWindow(HWND, QWindowsWindow *w);
void removeWindow(HWND);
@@ -120,9 +115,10 @@ public:
QSharedPointer<QWindowCreationContext> windowCreationContext() const;
static void setTabletAbsoluteRange(int a);
- void setProcessDpiAwareness(QtWindows::ProcessDpiAwareness dpiAwareness);
- static int processDpiAwareness();
- bool setProcessDpiV2Awareness();
+
+ static bool setProcessDpiAwareness(QtWindows::DpiAwareness dpiAwareness);
+ static QtWindows::DpiAwareness processDpiAwareness();
+ static QtWindows::DpiAwareness windowDpiAwareness(HWND hwnd);
static bool isDarkMode();
@@ -132,17 +128,16 @@ public:
unsigned systemInfo() const;
bool useRTLExtensions() const;
- QList<int> possibleKeys(const QKeyEvent *e) const;
+ QPlatformKeyMapper *keyMapper() const;
HandleBaseWindowHash &windows();
static bool isSessionLocked();
- QWindowsMimeConverter &mimeConverter() const;
+ QWindowsMimeRegistry &mimeConverter() const;
QWindowsScreenManager &screenManager();
QWindowsTabletSupport *tabletSupport() const;
- static QByteArray comErrorString(HRESULT hr);
bool asyncExpose() const;
void setAsyncExpose(bool value);
diff --git a/src/plugins/platforms/windows/qwindowscursor.cpp b/src/plugins/platforms/windows/qwindowscursor.cpp
index 5b904709ef..b416886120 100644
--- a/src/plugins/platforms/windows/qwindowscursor.cpp
+++ b/src/plugins/platforms/windows/qwindowscursor.cpp
@@ -14,6 +14,7 @@
#include <QtGui/qscreen.h>
#include <QtGui/private/qguiapplication_p.h> // getPixmapCursor()
#include <QtGui/private/qhighdpiscaling_p.h>
+#include <QtGui/private/qpixmap_win_p.h>
#include <QtCore/private/qwinregistry_p.h>
#include <QtCore/qdebug.h>
@@ -29,8 +30,7 @@ static bool initResources()
QT_BEGIN_NAMESPACE
-Q_GUI_EXPORT HBITMAP qt_pixmapToWinHBITMAP(const QPixmap &p, int hbitmapFormat = 0);
-Q_GUI_EXPORT HBITMAP qt_createIconMask(const QBitmap &bitmap);
+using namespace Qt::Literals::StringLiterals;
/*!
\class QWindowsCursorCacheKey
@@ -105,14 +105,16 @@ static HCURSOR createBitmapCursor(const QImage &bbits, const QImage &mbits,
hotSpot.setX(width / 2);
if (hotSpot.y() < 0)
hotSpot.setY(height / 2);
- const int n = qMax(1, width / 8);
- QScopedArrayPointer<uchar> xBits(new uchar[height * n]);
- QScopedArrayPointer<uchar> xMask(new uchar[height * n]);
+ // a ddb is word aligned, QImage depends on bow it was created
+ const auto bplDdb = qMax(1, ((width + 15) >> 4) << 1);
+ const auto bplImg = int(bbits.bytesPerLine());
+ QScopedArrayPointer<uchar> xBits(new uchar[height * bplDdb]);
+ QScopedArrayPointer<uchar> xMask(new uchar[height * bplDdb]);
int x = 0;
for (int i = 0; i < height; ++i) {
const uchar *bits = bbits.constScanLine(i);
const uchar *mask = mbits.constScanLine(i);
- for (int j = 0; j < n; ++j) {
+ for (int j = 0; j < bplImg && j < bplDdb; ++j) {
uchar b = bits[j];
uchar m = mask[j];
if (invb)
@@ -123,6 +125,11 @@ static HCURSOR createBitmapCursor(const QImage &bbits, const QImage &mbits,
xMask[x] = b ^ m;
++x;
}
+ for (int i = bplImg; i < bplDdb; ++i) {
+ xBits[x] = 0;
+ xMask[x] = 0;
+ ++x;
+ }
}
return CreateCursor(GetModuleHandle(nullptr), hotSpot.x(), hotSpot.y(), width, height,
xBits.data(), xMask.data());
@@ -140,8 +147,8 @@ static HCURSOR createBitmapCursor(const QCursor &cursor, qreal scaleFactor = 1)
bbits = bbits.scaled(scaledSize, Qt::KeepAspectRatio, Qt::SmoothTransformation);
mbits = mbits.scaled(scaledSize, Qt::KeepAspectRatio, Qt::SmoothTransformation);
}
- bbits = bbits.convertToFormat(QImage::Format_Mono);
- mbits = mbits.convertToFormat(QImage::Format_Mono);
+ bbits = std::move(bbits).convertToFormat(QImage::Format_Mono);
+ mbits = std::move(mbits).convertToFormat(QImage::Format_Mono);
const bool invb = bbits.colorCount() > 1 && qGray(bbits.color(0)) < qGray(bbits.color(1));
const bool invm = mbits.colorCount() > 1 && qGray(mbits.color(0)) < qGray(mbits.color(1));
return createBitmapCursor(bbits, mbits, cursor.hotSpot(), invb, invm);
@@ -438,8 +445,8 @@ QWindowsCursor::PixmapCursor QWindowsCursor::customCursor(Qt::CursorShape cursor
if (!bestFit)
return PixmapCursor();
- const QPixmap rawImage(QStringLiteral(":/qt-project.org/windows/cursors/images/") +
- QString::fromLatin1(bestFit->fileName));
+ const QPixmap rawImage(":/qt-project.org/windows/cursors/images/"_L1 +
+ QLatin1StringView(bestFit->fileName));
return PixmapCursor(rawImage, QPoint(bestFit->hotSpotX, bestFit->hotSpotY));
}
#endif // !QT_NO_IMAGEFORMAT_PNG
diff --git a/src/plugins/platforms/windows/qwindowsdialoghelpers.cpp b/src/plugins/platforms/windows/qwindowsdialoghelpers.cpp
index d851964b9d..0ce0b0e2a7 100644
--- a/src/plugins/platforms/windows/qwindowsdialoghelpers.cpp
+++ b/src/plugins/platforms/windows/qwindowsdialoghelpers.cpp
@@ -3,12 +3,7 @@
#define QT_NO_URL_CAST_FROM_STRING 1
-#ifndef _WIN32_WINNT
-#define _WIN32_WINNT 0x0A00
-#endif
-
#include <QtCore/qt_windows.h>
-#include "qwindowscombase.h"
#include "qwindowsdialoghelpers.h"
#include "qwindowscontext.h"
@@ -35,6 +30,9 @@
#include <QtCore/qmutex.h>
#include <QtCore/quuid.h>
#include <QtCore/qtemporaryfile.h>
+#include <QtCore/private/qfunctions_win_p.h>
+#include <QtCore/private/qsystemerror_p.h>
+#include <QtCore/private/qcomobject_p.h>
#include <algorithm>
#include <vector>
@@ -45,34 +43,6 @@ QT_BEGIN_NAMESPACE
using namespace Qt::StringLiterals;
-#ifndef QT_NO_DEBUG_STREAM
-/* Output UID (IID, CLSID) as C++ constants.
- * The constants are contained in the Windows SDK libs, but not for MinGW. */
-static inline QString guidToString(const GUID &g)
-{
- QString rc;
- QTextStream str(&rc);
- str.setIntegerBase(16);
- str.setNumberFlags(str.numberFlags() | QTextStream::ShowBase);
- str << '{' << g.Data1 << ", " << g.Data2 << ", " << g.Data3;
- str.setFieldWidth(2);
- str.setFieldAlignment(QTextStream::AlignRight);
- str.setPadChar(u'0');
- str << ",{" << g.Data4[0] << ", " << g.Data4[1] << ", " << g.Data4[2] << ", " << g.Data4[3]
- << ", " << g.Data4[4] << ", " << g.Data4[5] << ", " << g.Data4[6] << ", " << g.Data4[7]
- << "}};";
- return rc;
-}
-
-inline QDebug operator<<(QDebug d, const GUID &g)
-{
- QDebugStateSaver saver(d);
- d.nospace();
- d << guidToString(g);
- return d;
-}
-#endif // !QT_NO_DEBUG_STREAM
-
// Return an allocated wchar_t array from a QString, reserve more memory if desired.
static wchar_t *qStringToWCharArray(const QString &s, size_t reserveSize = 0)
{
@@ -239,7 +209,7 @@ QWindowsNativeDialogBase *QWindowsDialogHelperBase<BaseClass>::ensureNativeDialo
// Create dialog and apply common settings. Check "executed" flag as well
// since for example IFileDialog::Show() works only once.
if (m_nativeDialog.isNull() || m_nativeDialog->executed())
- m_nativeDialog = QWindowsNativeDialogBasePtr(createNativeDialog());
+ m_nativeDialog = QWindowsNativeDialogBasePtr(createNativeDialog(), &QObject::deleteLater);
return m_nativeDialog.data();
}
@@ -268,6 +238,7 @@ private:
void QWindowsDialogThread::run()
{
qCDebug(lcQpaDialogs) << '>' << __FUNCTION__;
+ QComHelper comInit(COINIT_APARTMENTTHREADED);
m_dialog->exec(m_owner);
qCDebug(lcQpaDialogs) << '<' << __FUNCTION__;
}
@@ -324,12 +295,13 @@ void QWindowsDialogHelperBase<BaseClass>::stopTimer()
}
}
-
template <class BaseClass>
void QWindowsDialogHelperBase<BaseClass>::hide()
{
- if (m_nativeDialog)
+ if (m_nativeDialog) {
m_nativeDialog->close();
+ m_nativeDialog.clear();
+ }
m_ownerWindow = nullptr;
}
@@ -451,7 +423,7 @@ inline void QWindowsFileDialogSharedData::fromOptions(const QSharedPointer<QFile
class QWindowsNativeFileDialogBase;
-class QWindowsNativeFileDialogEventHandler : public QWindowsComBase<IFileDialogEvents>
+class QWindowsNativeFileDialogEventHandler : public QComObject<IFileDialogEvents>
{
Q_DISABLE_COPY_MOVE(QWindowsNativeFileDialogEventHandler)
public:
@@ -544,8 +516,18 @@ QWindowsShellItem::QWindowsShellItem(IShellItem *item)
: m_item(item)
, m_attributes(0)
{
- if (FAILED(item->GetAttributes(SFGAO_CAPABILITYMASK | SFGAO_DISPLAYATTRMASK | SFGAO_CONTENTSMASK | SFGAO_STORAGECAPMASK, &m_attributes)))
+ SFGAOF mask = (SFGAO_CAPABILITYMASK | SFGAO_CONTENTSMASK | SFGAO_STORAGECAPMASK);
+
+ // Check for attributes which might be expensive to enumerate for subfolders
+ if (FAILED(item->GetAttributes((SFGAO_STREAM | SFGAO_COMPRESSED), &m_attributes))) {
m_attributes = 0;
+ } else {
+ // If the item is compressed or stream, skip expensive subfolder test
+ if (m_attributes & (SFGAO_STREAM | SFGAO_COMPRESSED))
+ mask &= ~SFGAO_HASSUBFOLDER;
+ if (FAILED(item->GetAttributes(mask, &m_attributes)))
+ m_attributes = 0;
+ }
}
QString QWindowsShellItem::path() const
@@ -584,7 +566,7 @@ QUrl QWindowsShellItem::url() const
if (urlV.isValid())
return urlV;
// Last resort: encode the absolute desktop parsing id as data URL
- const QString data = QStringLiteral("data:text/plain;base64,")
+ const QString data = "data:text/plain;base64,"_L1
+ QLatin1StringView(desktopAbsoluteParsing().toLatin1().toBase64());
return QUrl(data);
}
@@ -625,7 +607,7 @@ bool QWindowsShellItem::copyData(QIODevice *out, QString *errorMessage)
HRESULT hr = m_item->BindToHandler(nullptr, BHID_Stream, IID_PPV_ARGS(&istream));
if (FAILED(hr)) {
*errorMessage = "BindToHandler() failed: "_L1
- + QLatin1StringView(QWindowsContext::comErrorString(hr));
+ + QSystemError::windowsComString(hr);
return false;
}
enum : ULONG { bufSize = 102400 };
@@ -642,7 +624,7 @@ bool QWindowsShellItem::copyData(QIODevice *out, QString *errorMessage)
istream->Release();
if (hr != S_OK && hr != S_FALSE) {
*errorMessage = "Read() failed: "_L1
- + QLatin1StringView(QWindowsContext::comErrorString(hr));
+ + QSystemError::windowsComString(hr);
return false;
}
return true;
@@ -1355,7 +1337,7 @@ Q_GLOBAL_STATIC(QStringList, temporaryItemCopies)
static void cleanupTemporaryItemCopies()
{
- for (const QString &file : qAsConst(*temporaryItemCopies()))
+ for (const QString &file : std::as_const(*temporaryItemCopies()))
QFile::remove(file);
}
@@ -1377,7 +1359,7 @@ QString tempFilePattern(QString name)
int lastDot = name.lastIndexOf(u'.');
if (lastDot < 0)
lastDot = name.size();
- name.insert(lastDot, QStringLiteral("_XXXXXX"));
+ name.insert(lastDot, "_XXXXXX"_L1);
for (int i = lastDot - 1; i >= 0; --i) {
if (!validFileNameCharacter(name.at(i)))
@@ -1569,7 +1551,7 @@ QWindowsNativeDialogBase *QWindowsFileDialogHelper::createNativeDialog()
if (!info.isDir())
result->selectFile(info.fileName());
} else {
- result->selectFile(url.path()); // TODO url.fileName() once it exists
+ result->selectFile(url.fileName());
}
}
// No need to select initialNameFilter if mode is Dir
@@ -1603,7 +1585,7 @@ void QWindowsFileDialogHelper::selectFile(const QUrl &fileName)
qCDebug(lcQpaDialogs) << __FUNCTION__ << fileName.toString();
if (hasNativeDialog()) // Might be invoked from the QFileDialog constructor.
- nativeFileDialog()->selectFile(fileName.toLocalFile()); // ## should use QUrl::fileName() once it exists
+ nativeFileDialog()->selectFile(fileName.fileName());
}
QList<QUrl> QWindowsFileDialogHelper::selectedFiles() const
@@ -1711,14 +1693,6 @@ static int QT_WIN_CALLBACK xpFileDialogGetExistingDirCallbackProc(HWND hwnd, UIN
return dialog->existingDirCallback(hwnd, uMsg, lParam);
}
-/* The correct declaration of the SHGetPathFromIDList symbol is
- * being used in mingw-w64 as of r6215, which is a v3 snapshot. */
-#if defined(Q_CC_MINGW) && (!defined(__MINGW64_VERSION_MAJOR) || __MINGW64_VERSION_MAJOR < 3)
-typedef ITEMIDLIST *qt_LpItemIdList;
-#else
-using qt_LpItemIdList = PIDLIST_ABSOLUTE;
-#endif
-
int QWindowsXpNativeFileDialog::existingDirCallback(HWND hwnd, UINT uMsg, LPARAM lParam)
{
switch (uMsg) {
@@ -1732,7 +1706,7 @@ int QWindowsXpNativeFileDialog::existingDirCallback(HWND hwnd, UINT uMsg, LPARAM
break;
case BFFM_SELCHANGED: {
wchar_t path[MAX_PATH];
- const bool ok = SHGetPathFromIDList(reinterpret_cast<qt_LpItemIdList>(lParam), path)
+ const bool ok = SHGetPathFromIDList(reinterpret_cast<PIDLIST_ABSOLUTE>(lParam), path)
&& path[0];
SendMessage(hwnd, BFFM_ENABLEOK, ok ? 1 : 0, 1);
}
@@ -1754,7 +1728,7 @@ QList<QUrl> QWindowsXpNativeFileDialog::execExistingDir(HWND owner)
bi.lpfn = xpFileDialogGetExistingDirCallbackProc;
bi.lParam = LPARAM(this);
QList<QUrl> selectedFiles;
- if (qt_LpItemIdList pItemIDList = SHBrowseForFolder(&bi)) {
+ if (const auto pItemIDList = SHBrowseForFolder(&bi)) {
wchar_t path[MAX_PATH];
path[0] = 0;
if (SHGetPathFromIDList(pItemIDList, path) && path[0])
diff --git a/src/plugins/platforms/windows/qwindowsdrag.cpp b/src/plugins/platforms/windows/qwindowsdrag.cpp
index 33b1cf2abd..c6f55c3509 100644
--- a/src/plugins/platforms/windows/qwindowsdrag.cpp
+++ b/src/plugins/platforms/windows/qwindowsdrag.cpp
@@ -28,6 +28,8 @@
#include <QtCore/qdebug.h>
#include <QtCore/qbuffer.h>
#include <QtCore/qpoint.h>
+#include <QtCore/qpointer.h>
+#include <QtCore/private/qcomobject_p.h>
#include <shlobj.h>
@@ -167,7 +169,7 @@ static Qt::MouseButtons lastButtons = Qt::NoButton;
\internal
*/
-class QWindowsOleDropSource : public QWindowsComBase<IDropSource>
+class QWindowsOleDropSource : public QComObject<IDropSource>
{
public:
enum Mode {
@@ -348,7 +350,7 @@ QWindowsOleDropSource::QueryContinueDrag(BOOL fEscapePressed, DWORD grfKeyState)
} else {
if (buttons && !m_currentButtons) {
m_currentButtons = buttons;
- } else if (!(m_currentButtons & buttons)) { // Button changed: Complete Drop operation.
+ } else if (m_currentButtons != buttons) { // Button changed: Complete Drop operation.
result = DRAGDROP_S_DROP;
}
}
@@ -526,7 +528,8 @@ QWindowsOleDropTarget::DragLeave()
qCDebug(lcQpaMime) << __FUNCTION__ << ' ' << m_window;
- lastModifiers = QWindowsKeyMapper::queryKeyboardModifiers();
+ const auto *keyMapper = QWindowsContext::instance()->keyMapper();
+ lastModifiers = keyMapper->queryKeyboardModifiers();
lastButtons = QWindowsMouseHandler::queryMouseButtons();
QWindowSystemInterface::handleDrag(m_window, nullptr, QPoint(), Qt::IgnoreAction,
@@ -611,7 +614,6 @@ QWindowsOleDropTarget::Drop(LPDATAOBJECT pDataObj, DWORD grfKeyState,
*/
bool QWindowsDrag::m_canceled = false;
-bool QWindowsDrag::m_dragging = false;
QWindowsDrag::QWindowsDrag() = default;
@@ -644,6 +646,86 @@ IDropTargetHelper* QWindowsDrag::dropHelper() {
return m_cachedDropTargetHelper;
}
+// Workaround for DoDragDrop() not working with touch/pen input, causing DnD to hang until the mouse is moved.
+// We process pointer messages for touch/pen and generate mouse input through SendInput() to trigger DoDragDrop()
+static HRESULT startDoDragDrop(LPDATAOBJECT pDataObj, LPDROPSOURCE pDropSource, DWORD dwOKEffects, LPDWORD pdwEffect)
+{
+ QWindow *underMouse = QWindowsContext::instance()->windowUnderMouse();
+ const bool hasMouseCapture = underMouse && static_cast<QWindowsWindow *>(underMouse->handle())->hasMouseCapture();
+ const HWND hwnd = hasMouseCapture ? reinterpret_cast<HWND>(underMouse->winId()) : ::GetFocus();
+ bool starting = false;
+
+ for (;;) {
+ MSG msg{};
+ if (::GetMessage(&msg, hwnd, 0, 0) > 0) {
+
+ if (msg.message == WM_MOUSEMOVE) {
+
+ // Only consider the first simulated event, or actual mouse messages.
+ if (!starting && (msg.wParam & (MK_LBUTTON | MK_MBUTTON | MK_RBUTTON | MK_XBUTTON1 | MK_XBUTTON2)) == 0)
+ return E_FAIL;
+
+ return ::DoDragDrop(pDataObj, pDropSource, dwOKEffects, pdwEffect);
+ }
+
+ if (msg.message == WM_POINTERUPDATE) {
+
+ const quint32 pointerId = GET_POINTERID_WPARAM(msg.wParam);
+
+ POINTER_INFO pointerInfo{};
+ if (!GetPointerInfo(pointerId, &pointerInfo))
+ return E_FAIL;
+
+ if (pointerInfo.pointerFlags & POINTER_FLAG_PRIMARY) {
+
+ DWORD flags = MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_VIRTUALDESK | MOUSEEVENTF_MOVE;
+ if (IS_POINTER_FIRSTBUTTON_WPARAM(msg.wParam))
+ flags |= MOUSEEVENTF_LEFTDOWN;
+ if (IS_POINTER_SECONDBUTTON_WPARAM(msg.wParam))
+ flags |= MOUSEEVENTF_RIGHTDOWN;
+ if (IS_POINTER_THIRDBUTTON_WPARAM(msg.wParam))
+ flags |= MOUSEEVENTF_MIDDLEDOWN;
+
+ if (!starting) {
+ POINT pt{};
+ if (::GetCursorPos(&pt)) {
+
+ // Send mouse input that can generate a WM_MOUSEMOVE message.
+ if ((flags & MOUSEEVENTF_LEFTDOWN || flags & MOUSEEVENTF_RIGHTDOWN || flags & MOUSEEVENTF_MIDDLEDOWN)
+ && (pt.x != pointerInfo.ptPixelLocation.x || pt.y != pointerInfo.ptPixelLocation.y)) {
+
+ const int origin_x = ::GetSystemMetrics(SM_XVIRTUALSCREEN);
+ const int origin_y = ::GetSystemMetrics(SM_YVIRTUALSCREEN);
+ const int virt_w = ::GetSystemMetrics(SM_CXVIRTUALSCREEN);
+ const int virt_h = ::GetSystemMetrics(SM_CYVIRTUALSCREEN);
+ const int virt_x = pointerInfo.ptPixelLocation.x - origin_x;
+ const int virt_y = pointerInfo.ptPixelLocation.y - origin_y;
+
+ INPUT input{};
+ input.type = INPUT_MOUSE;
+ input.mi.dx = static_cast<DWORD>(virt_x * (65535.0 / virt_w));
+ input.mi.dy = static_cast<DWORD>(virt_y * (65535.0 / virt_h));
+ input.mi.dwFlags = flags;
+
+ ::SendInput(1, &input, sizeof(input));
+ starting = true;
+ }
+ }
+ }
+ }
+ } else {
+ // Handle other messages.
+ qWindowsWndProc(msg.hwnd, msg.message, msg.wParam, msg.lParam);
+
+ if (msg.message == WM_POINTERLEAVE)
+ return E_FAIL;
+ }
+ } else {
+ return E_FAIL;
+ }
+ }
+}
+
Qt::DropAction QWindowsDrag::drag(QDrag *drag)
{
// TODO: Accessibility handling?
@@ -659,10 +741,7 @@ Qt::DropAction QWindowsDrag::drag(QDrag *drag)
const DWORD allowedEffects = translateToWinDragEffects(possibleActions);
qCDebug(lcQpaMime) << '>' << __FUNCTION__ << "possible Actions=0x"
<< Qt::hex << int(possibleActions) << "effects=0x" << allowedEffects << Qt::dec;
- // Indicate message handlers we are in DoDragDrop() event loop.
- QWindowsDrag::m_dragging = true;
- const HRESULT r = DoDragDrop(dropDataObject, windowDropSource, allowedEffects, &resultEffect);
- QWindowsDrag::m_dragging = false;
+ const HRESULT r = startDoDragDrop(dropDataObject, windowDropSource, allowedEffects, &resultEffect);
const DWORD reportedPerformedEffect = dropDataObject->reportedPerformedEffect();
if (r == DRAGDROP_S_DROP) {
if (reportedPerformedEffect == DROPEFFECT_MOVE && resultEffect != DROPEFFECT_MOVE) {
diff --git a/src/plugins/platforms/windows/qwindowsdrag.h b/src/plugins/platforms/windows/qwindowsdrag.h
index cf5d43fb84..a2d0e54044 100644
--- a/src/plugins/platforms/windows/qwindowsdrag.h
+++ b/src/plugins/platforms/windows/qwindowsdrag.h
@@ -4,12 +4,12 @@
#ifndef QWINDOWSDRAG_H
#define QWINDOWSDRAG_H
-#include "qwindowscombase.h"
#include "qwindowsinternalmimedata.h"
#include <qpa/qplatformdrag.h>
#include <QtGui/qpixmap.h>
#include <QtGui/qdrag.h>
+#include <QtCore/private/qcomobject_p.h>
struct IDropTargetHelper;
@@ -23,7 +23,7 @@ public:
IDataObject *retrieveDataObject() const override;
};
-class QWindowsOleDropTarget : public QWindowsComBase<IDropTarget>
+class QWindowsOleDropTarget : public QComObject<IDropTarget>
{
public:
explicit QWindowsOleDropTarget(QWindow *w);
@@ -58,7 +58,6 @@ public:
static QWindowsDrag *instance();
void cancelDrag() override { QWindowsDrag::m_canceled = true; }
static bool isCanceled() { return QWindowsDrag::m_canceled; }
- static bool isDragging() { return QWindowsDrag::m_dragging; }
IDataObject *dropDataObject() const { return m_dropDataObject; }
void setDropDataObject(IDataObject *dataObject) { m_dropDataObject = dataObject; }
@@ -69,7 +68,6 @@ public:
private:
static bool m_canceled;
- static bool m_dragging;
QWindowsDropMimeData m_dropData;
IDataObject *m_dropDataObject = nullptr;
diff --git a/src/plugins/platforms/windows/qwindowsdropdataobject.cpp b/src/plugins/platforms/windows/qwindowsdropdataobject.cpp
index 4f5e1f5b85..629291640f 100644
--- a/src/plugins/platforms/windows/qwindowsdropdataobject.cpp
+++ b/src/plugins/platforms/windows/qwindowsdropdataobject.cpp
@@ -5,10 +5,12 @@
#include <QtCore/qurl.h>
#include <QtCore/qmimedata.h>
-#include "qwindowsmime.h"
+#include "qwindowsmimeregistry.h"
QT_BEGIN_NAMESPACE
+using namespace Qt::Literals::StringLiterals;
+
/*!
\class QWindowsDropDataObject
\brief QWindowsOleDataObject subclass specialized for handling Drag&Drop.
@@ -53,11 +55,11 @@ bool QWindowsDropDataObject::shouldIgnore(LPFORMATETC pformatetc) const
QMimeData *dropData = mimeData();
if (dropData && dropData->formats().size() == 1 && dropData->hasUrls()) {
- QString formatName = QWindowsMimeConverter::clipboardFormatName(pformatetc->cfFormat);
+ QString formatName = QWindowsMimeRegistry::clipboardFormatName(pformatetc->cfFormat);
if (pformatetc->cfFormat == CF_UNICODETEXT
|| pformatetc->cfFormat == CF_TEXT
- || formatName == QStringLiteral("UniformResourceLocator")
- || formatName == QStringLiteral("UniformResourceLocatorW")) {
+ || formatName == "UniformResourceLocator"_L1
+ || formatName == "UniformResourceLocatorW"_L1) {
const auto urls = dropData->urls();
return std::all_of(urls.cbegin(), urls.cend(), [] (const QUrl &u) { return u.isLocalFile(); });
}
diff --git a/src/plugins/platforms/windows/qwindowsglcontext.cpp b/src/plugins/platforms/windows/qwindowsglcontext.cpp
index 65740d69da..5ca52c2c19 100644
--- a/src/plugins/platforms/windows/qwindowsglcontext.cpp
+++ b/src/plugins/platforms/windows/qwindowsglcontext.cpp
@@ -544,11 +544,14 @@ static int choosePixelFormat(HDC hdc,
iAttributes[i++] = WGL_NUMBER_OVERLAYS_ARB;
iAttributes[i++] = 1;
}
+ // must be the one before the last one
const int samples = format.samples();
const bool sampleBuffersRequested = samples > 1
&& testFlag(staticContext.extensions, QOpenGLStaticContext::SampleBuffers);
+ int sampleBuffersKeyPosition = 0;
int samplesValuePosition = 0;
if (sampleBuffersRequested) {
+ sampleBuffersKeyPosition = i;
iAttributes[i++] = WGL_SAMPLE_BUFFERS_ARB;
iAttributes[i++] = TRUE;
iAttributes[i++] = WGL_SAMPLES_ARB;
@@ -560,9 +563,9 @@ static int choosePixelFormat(HDC hdc,
}
// must be the last
bool srgbRequested = format.colorSpace() == QColorSpace::SRgb;
- int srgbValuePosition = 0;
+ int srgbCapableKeyPosition = 0;
if (srgbRequested) {
- srgbValuePosition = i;
+ srgbCapableKeyPosition = i;
iAttributes[i++] = WGL_FRAMEBUFFER_SRGB_CAPABLE_EXT;
iAttributes[i++] = TRUE;
}
@@ -576,8 +579,9 @@ static int choosePixelFormat(HDC hdc,
&& numFormats >= 1;
if (valid || (!sampleBuffersRequested && !srgbRequested))
break;
+ // NB reductions must be done in reverse order (disable the last first, then move on to the one before that, etc.)
if (srgbRequested) {
- iAttributes[srgbValuePosition] = 0;
+ iAttributes[srgbCapableKeyPosition] = 0;
srgbRequested = false;
} else if (sampleBuffersRequested) {
if (iAttributes[samplesValuePosition] > 1) {
@@ -585,11 +589,8 @@ static int choosePixelFormat(HDC hdc,
} else if (iAttributes[samplesValuePosition] == 1) {
// Fallback in case it is unable to initialize with any
// samples to avoid falling back to the GDI path
- // NB: The sample attributes needs to be at the end for this
- // to work correctly
- iAttributes[samplesValuePosition - 1] = FALSE;
+ iAttributes[sampleBuffersKeyPosition] = 0;
iAttributes[samplesValuePosition] = 0;
- iAttributes[samplesValuePosition + 1] = 0;
} else {
break;
}
@@ -1261,7 +1262,6 @@ bool QWindowsGLContext::makeCurrent(QPlatformSurface *surface)
// Do we already have a DC entry for that window?
auto *window = static_cast<QWindowsWindow *>(surface);
- window->aboutToMakeCurrent();
const HWND hwnd = window->handle();
if (const QOpenGLContextData *contextData = findByHWND(m_windowContexts, hwnd)) {
// Repeated calls to wglMakeCurrent when vsync is enabled in the driver will
diff --git a/src/plugins/platforms/windows/qwindowsiconengine.cpp b/src/plugins/platforms/windows/qwindowsiconengine.cpp
new file mode 100644
index 0000000000..5e5ca22ec1
--- /dev/null
+++ b/src/plugins/platforms/windows/qwindowsiconengine.cpp
@@ -0,0 +1,394 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "qwindowsiconengine.h"
+
+#include <QtCore/qoperatingsystemversion.h>
+#include <QtGui/qguiapplication.h>
+#include <QtGui/qpainter.h>
+#include <QtGui/qpalette.h>
+
+QT_BEGIN_NAMESPACE
+
+using namespace Qt::StringLiterals;
+
+QString QWindowsIconEngine::glyphs() const
+{
+ if (!QFontInfo(m_iconFont).exactMatch())
+ return {};
+
+ static constexpr std::pair<QLatin1StringView, QStringView> glyphMap[] = {
+ {"address-book-new"_L1, u"\ue780"},
+ {"application-exit"_L1, u"\ue8bb"},
+ {"appointment-new"_L1, u"\ue878"},
+ {"call-start"_L1, u"\uf715"},
+ {"call-stop"_L1, u"\uf405"},
+ {"contact-new"_L1, u"\ue8fa"},
+ {"document-new"_L1, u"\ue8a5"},
+ {"document-open"_L1, u"\ue8e5"},
+ {"document-open-recent"_L1, u"\ue823"},
+ {"document-page-setup"_L1, u"\ue7c3"},
+ {"document-print"_L1, u"\ue749"},
+ {"document-print-preview"_L1, u"\ue956"},
+ {"document-properties"_L1, u"\ue90f"},
+ {"document-revert"_L1, u"\ue7a7"}, // ?
+ {"document-save"_L1, u"\ue74e"}, // or e78c?
+ {"document-save-as"_L1, u"\ue792"},
+ {"document-send"_L1, u"\ue724"},
+ {"edit-clear"_L1, u"\ue894"},
+ {"edit-copy"_L1, u"\ue8c8"},
+ {"edit-cut"_L1, u"\ue8c6"},
+ {"edit-delete"_L1, u"\ue74d"},
+ {"edit-find"_L1, u"\ue721"},
+ //{"edit-find-replace"_L1, u"\u"},
+ {"edit-paste"_L1, u"\ue77f"},
+ {"edit-redo"_L1, u"\ue7a6"},
+ {"edit-select-all"_L1, u"\ue8b3"},
+ {"edit-undo"_L1, u"\ue7a7"},
+ {"folder-new"_L1, u"\ue8f4"},
+ //{"format-indent-less"_L1, u"\u"},
+ //{"format-indent-more"_L1, u"\u"},
+ {"format-justify-center"_L1, u"\ue8e3"},
+ //{"format-justify-fill"_L1, u"\ue235"},
+ {"format-justify-left"_L1, u"\ue8e4"},
+ {"format-justify-right"_L1, u"\ue8e2"},
+ //{"format-text-direction-ltr"_L1, u"\ue247"},
+ //{"format-text-direction-rtl"_L1, u"\ue248"},
+ {"format-text-bold"_L1, u"\ue8dd"},
+ {"format-text-italic"_L1, u"\ue8db"},
+ {"format-text-underline"_L1, u"\ue8dc"},
+ {"format-text-strikethrough"_L1, u"\uede0"},
+ //{"go-bottom"_L1,u"\ue258"},
+ {"go-down"_L1,u"\ue74b"},
+ //{"go-first"_L1, u"\ue5dc"},
+ {"go-home"_L1, u"\ue80f"},
+ // {"go-jump"_L1, u"\uf719"},
+ //{"go-last"_L1, u"\ue5dd"},
+ {"go-next"_L1, u"\ue893"},
+ {"go-previous"_L1, u"\ue892"},
+ //{"go-top"_L1, u"\ue25a"},
+ {"go-up"_L1, u"\ue74a"},
+ {"help-about"_L1, u"\ue946"},
+ //{"help-contents"_L1, u"\ue8de"},
+ {"help-faq"_L1, u"\ue897"},
+ {"insert-image"_L1, u"\ue946"},
+ {"insert-link"_L1, u"\ue71b"},
+ //{"insert-object"_L1, u"\u"},
+ //{"insert-text"_L1, u"\uf827"},
+ {"list-add"_L1, u"\ue710"},
+ {"list-remove"_L1, u"\ue738"},
+ {"mail-forward"_L1, u"\ue89c"},
+ //{"mail-mark-important"_L1, u"\ue937"},
+ //{"mail-mark-junk"_L1, u"\u"},
+ //{"mail-mark-notjunk"_L1, u"\u"},
+ {"mail-mark-read"_L1, u"\ue8c3"},
+ //{"mail-mark-unread"_L1, u"\ue9bc"},
+ {"mail-message-new"_L1, u"\ue70f"},
+ {"mail-reply-all"_L1, u"\ue8c2"},
+ {"mail-reply-sender"_L1, u"\ue8ca"},
+ {"mail-send"_L1, u"\ue724"},
+ //{"mail-send-receive"_L1, u"\u"},
+ {"media-eject"_L1, u"\uf847"},
+ {"media-playback-pause"_L1, u"\ue769"},
+ {"media-playback-start"_L1, u"\ue768"},
+ {"media-playback-stop"_L1, u"\ue71a"},
+ {"media-record"_L1, u"\ue7c8"},
+ {"media-seek-backward"_L1, u"\ueb9e"},
+ {"media-seek-forward"_L1, u"\ueb9d"},
+ {"media-skip-backward"_L1, u"\ue892"},
+ {"media-skip-forward"_L1, u"\ue893"},
+ //{"object-flip-horizontal"_L1, u"\u"},
+ //{"object-flip-vertical"_L1, u"\u"},
+ {"object-rotate-left"_L1, u"\ue80c"},
+ {"object-rotate-right"_L1, u"\ue80d"},
+ //{"process-stop"_L1, u"\ue5c9"},
+ {"system-lock-screen"_L1, u"\uee3f"},
+ {"system-log-out"_L1, u"\uf3b1"},
+ //{"system-run"_L1, u"\u"},
+ {"system-search"_L1, u"\ue721"},
+ {"system-reboot"_L1, u"\ue777"}, // unsure?
+ {"system-shutdown"_L1, u"\ue7e8"},
+ {"tools-check-spelling"_L1, u"\uf87b"},
+ {"view-fullscreen"_L1, u"\ue740"},
+ {"view-refresh"_L1, u"\ue72c"},
+ {"view-restore"_L1, u"\ue777"},
+ //{"view-sort-ascending"_L1, u"\ue25a"},
+ //{"view-sort-descending"_L1, u"\ue258"},
+ {"window-close"_L1, u"\ue8bb"},
+ {"window-new"_L1, u"\ue78b"},
+ {"zoom-fit-best"_L1, u"\ue9a6"},
+ {"zoom-in"_L1, u"\ue8a3"},
+ //{"zoom-original"_L1, u"\u"},
+ {"zoom-out"_L1, u"\ue71f"},
+
+ {"process-working"_L1, u"\ue9f3"},
+
+ {"accessories-calculator"_L1, u"\ue8ef"},
+ {"accessories-character-map"_L1, u"\uf2b7"},
+ {"accessories-dictionary"_L1, u"\ue82d"},
+ //{"accessories-text-editor"_L1, u"\ue262"},
+ {"help-browser"_L1, u"\ue897"},
+ {"multimedia-volume-control"_L1, u"\ue767"},
+ {"preferences-desktop-accessibility"_L1, u"\ue776"},
+ {"preferences-desktop-font"_L1, u"\ue8d2"},
+ {"preferences-desktop-keyboard"_L1, u"\ue765"},
+ {"preferences-desktop-locale"_L1, u"\uf2b7"},
+ //{"preferences-desktop-multimedia"_L1, u"\uea75"},
+ {"preferences-desktop-screensaver"_L1, u"\uf182"},
+ //{"preferences-desktop-theme"_L1, u"\uf560"},
+ //{"preferences-desktop-wallpaper"_L1, u"\ue1bc"},
+ {"system-file-manager"_L1, u"\uec50"},
+ //{"system-software-install"_L1, u"\ueb71"},
+ {"system-software-update"_L1, u"\uecc5"},
+ {"utilities-system-monitor"_L1, u"\ue7f4"},
+ //{"utilities-terminal"_L1, u"\ueb8e"},
+
+ //{"applications-accessories"_L1, u"\u"},
+ {"applications-development"_L1, u"\uec7a"},
+ //{"applications-engineering"_L1, u"\uea3d"},
+ {"applications-games"_L1, u"\ue7fc"},
+ //{"applications-graphics"_L1, u"\u"},
+ {"applications-internet"_L1, u"\ue774"},
+ {"applications-multimedia"_L1, u"\uea69"},
+ //{"applications-office"_L1, u"\u"},
+ //{"applications-other"_L1, u"\u"},
+ //{"applications-science"_L1, u"\uea4b"},
+ {"applications-system"_L1, u"\ue770"},
+ //{"applications-utilities"_L1, u"\u"},
+ //{"preferences-desktop"_L1, u"\ueb97"},
+ //{"preferences-desktop-peripherals"_L1, u"\u"},
+ //{"preferences-desktop-personal"_L1, u"\uf835"},
+ //{"preferences-other"_L1, u"\u"},
+ //{"preferences-system"_L1, u"\ue8b8"},
+ //{"preferences-system-network"_L1, u"\ue894"},
+ {"system-help"_L1, u"\ue946"},
+
+ {"audio-card"_L1, u"\ue8d6"},
+ {"audio-input-microphone"_L1, u"\ue720"},
+ {"battery"_L1, u"\ue83f"},
+ {"camera-photo"_L1, u"\ue722"},
+ {"camera-video"_L1, u"\ue714"},
+ {"camera-web"_L1, u"\ue8b8"},
+ {"computer"_L1, u"\ue7f8"}, // or e7fb?
+ {"drive-harddisk"_L1, u"\ueda2"},
+ {"drive-optical"_L1, u"\ue958"},
+ //{"drive-removable-media"_L1, u"\u"},
+ //{"input-gaming"_L1, u"\u"},
+ {"input-keyboard"_L1, u"\ue92e"},
+ {"input-mouse"_L1, u"\ue962"},
+ {"input-tablet"_L1, u"\ue70a"},
+ {"media-flash"_L1, u"\ue88e"},
+ //{"media-floppy"_L1, u"\u"},
+ {"media-optical"_L1, u"\ue958"},
+ {"media-tape"_L1, u"\ue96a"},
+ //{"modem"_L1, u"\u"},
+ //{"multimedia-player"_L1, u"\u"},
+ {"network-wired"_L1, u"\ue968"},
+ {"network-wireless"_L1, u"\ue701"},
+ //{"pda"_L1, u"\u"},
+ {"phone"_L1, u"\ue717"},
+ {"printer"_L1, u"\ue749"},
+ {"scanner"_L1, u"\ue8fe"},
+ //{"video-display"_L1, u"\uf06a"},
+
+ {"emblem-default"_L1, u"\uf56d"},
+ {"emblem-documents"_L1, u"\ue8a5"},
+ {"emblem-downloads"_L1, u"\ue896"},
+ {"emblem-favorite"_L1, u"\ue734"},
+ {"emblem-important"_L1, u"\ue8c9"},
+ {"emblem-mail"_L1, u"\ue715"},
+ {"emblem-photos"_L1, u"\ue91b"},
+ //{"emblem-readonly"_L1, u"\u"},
+ {"emblem-shared"_L1, u"\ue902"},
+ {"emblem-symbolic-link"_L1, u"\ue71b"},
+ {"emblem-synchronized"_L1, u"\uedab"},
+ {"emblem-system"_L1, u"\ue770"},
+ //{"emblem-unreadable"_L1, u"\u"},
+
+ {"folder"_L1, u"\ue8b7"},
+ //{"folder-remote"_L1, u"\u"},
+ //{"network-server"_L1, u"\ue875"},
+ //{"network-workgroup"_L1, u"\ue1a0"},
+ {"start-here"_L1, u"\ue8fc"}, // unsure
+ {"user-bookmarks"_L1, u"\ue8a4"},
+ //{"user-desktop"_L1, u"\ue30a"},
+ {"user-home"_L1, u"\ue80f"},
+ {"user-trash"_L1, u"\ue74d"},
+
+ //{"appointment-missed"_L1, u"\ue615"},
+ //{"appointment-soon"_L1, u"\uf540"},
+ {"audio-volume-high"_L1, u"\ue995"},
+ {"audio-volume-low"_L1, u"\ue993"},
+ {"audio-volume-medium"_L1, u"\ue994"},
+ {"audio-volume-muted"_L1, u"\ue992"},
+ //{"battery-caution"_L1, u"\ue19c"},
+ {"battery-low"_L1, u"\ue851"}, // ?
+ {"dialog-error"_L1, u"\ue783"},
+ {"dialog-information"_L1, u"\ue946"},
+ //{"dialog-password"_L1, u"\uf042"},
+ {"dialog-question"_L1, u"\uf142"}, // unsure
+ {"dialog-warning"_L1, u"\ue7ba"},
+ //{"folder-drag-accept"_L1, @u"\ue9a3"},
+ {"folder-open"_L1, u"\ue838"},
+ //{"folder-visiting"_L1, u"\ue8a7"},
+ //{"image-loading"_L1, u"\ue41a"},
+ //{"image-missing"_L1, u"\ue3ad"},
+ {"mail-attachment"_L1, u"\ue723"},
+ //{"mail-unread"_L1, u"\uf18a"},
+ //{"mail-read"_L1, u"\uf18c"},
+ {"mail-replied"_L1, u"\ue8ca"},
+ //{"mail-signed"_L1, u"\u"},
+ //{"mail-signed-verified"_L1, u"\u"},
+ {"media-playlist-repeat"_L1, u"\ue8ee"},
+ {"media-playlist-shuffle"_L1, u"\ue8b1"},
+ //{"network-error"_L1, u"\uead9"},
+ //{"network-idle"_L1, u"\ue51f"},
+ {"network-offline"_L1, u"\uf384"},
+ //{"network-receive"_L1, u"\ue2c0"},
+ //{"network-transmit"_L1, u"\ue2c3"},
+ //{"network-transmit-receive"_L1, u"\uca18"},
+ //{"printer-error"_L1, u"\uf7a0"},
+ //{"printer-printing"_L1, u"\uf7a1"},
+ //{"security-high"_L1, u"\ue32a"},
+ //{"security-medium"_L1, u"\ue9e0"},
+ //{"security-low"_L1, u"\uf012"},
+ //{"software-update-available"_L1, u"\ue923"},
+ //{"software-update-urgent"_L1, u"\uf05a"},
+ {"sync-error"_L1, u"\uea6a"},
+ {"sync-synchronizing"_L1, u"\ue895"},
+ //{"task-due"_L1, u"\u"},
+ //{"task-past-due"_L1, u"\u"},
+ {"user-available"_L1, u"\ue8cf"},
+ //{"user-away"_L1, u"\ue510"},
+ //{"user-idle"_L1, u"\u"},
+ //{"user-offline"_L1, u"\uf7b3"},
+ //{"user-trash-full"_L1, u"\ue872"}, //delete
+ //{"user-trash-full"_L1, u"\ue92b"}, //delete_forever
+ {"weather-clear"_L1, u"\ue706"},
+ //{"weather-clear-night"_L1, u"\uf159"},
+ //{"weather-few-clouds"_L1, u"\uf172"},
+ //{"weather-few-clouds-night"_L1, u"\uf174"},
+ //{"weather-fog"_L1, u"\ue818"},
+ {"weather-overcast"_L1, u"\ue753"},
+ //{"weather-severe-alert"_L1, u"\ue002"}, //warning
+ //{"weather-showers"_L1, u"\uf176"},
+ //{"weather-showers-scattered"_L1, u"\u"},
+ //{"weather-snow"_L1, u"\ue80f"}, //snowing
+ //{"weather-storm"_L1, u"\uf070"},
+ };
+
+ const auto it = std::find_if(std::begin(glyphMap), std::end(glyphMap), [this](const auto &c){
+ return c.first == m_iconName;
+ });
+
+ return it != std::end(glyphMap) ? it->second.toString()
+ : (m_iconName.length() == 1 ? m_iconName : QString());
+}
+
+namespace {
+auto iconFontFamily()
+{
+ static const bool isWindows11 = QOperatingSystemVersion::current() >= QOperatingSystemVersion::Windows11;
+ return isWindows11 ? u"Segoe Fluent Icons"_s
+ : u"Segoe MDL2 Assets"_s;
+}
+}
+
+QWindowsIconEngine::QWindowsIconEngine(const QString &iconName)
+ : m_iconName(iconName), m_iconFont(iconFontFamily())
+ , m_glyphs(glyphs())
+{
+}
+
+QWindowsIconEngine::~QWindowsIconEngine()
+{}
+
+QIconEngine *QWindowsIconEngine::clone() const
+{
+ return new QWindowsIconEngine(m_iconName);
+}
+
+QString QWindowsIconEngine::key() const
+{
+ return u"QWindowsIconEngine"_s;
+}
+
+QString QWindowsIconEngine::iconName()
+{
+ return m_iconName;
+}
+
+bool QWindowsIconEngine::isNull()
+{
+ if (m_glyphs.isEmpty())
+ return true;
+
+ const QChar c0 = m_glyphs.at(0);
+ const QFontMetrics fontMetrics(m_iconFont);
+ if (c0.category() == QChar::Other_Surrogate && m_glyphs.size() > 1)
+ return !fontMetrics.inFontUcs4(QChar::surrogateToUcs4(c0, m_glyphs.at(1)));
+ return !fontMetrics.inFont(c0);
+}
+
+QList<QSize> QWindowsIconEngine::availableSizes(QIcon::Mode, QIcon::State)
+{
+ return {{16, 16}, {24, 24}, {48, 48}, {128, 128}};
+}
+
+QSize QWindowsIconEngine::actualSize(const QSize &size, QIcon::Mode mode, QIcon::State state)
+{
+ return QIconEngine::actualSize(size, mode, state);
+}
+
+QPixmap QWindowsIconEngine::pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state)
+{
+ return scaledPixmap(size, mode, state, 1.0);
+}
+
+QPixmap QWindowsIconEngine::scaledPixmap(const QSize &size, QIcon::Mode mode, QIcon::State state, qreal scale)
+{
+ const quint64 cacheKey = calculateCacheKey(mode, state);
+ if (cacheKey != m_cacheKey || m_pixmap.size() != size || m_pixmap.devicePixelRatio() != scale) {
+ m_pixmap = QPixmap(size * scale);
+ m_pixmap.fill(Qt::transparent);
+ m_pixmap.setDevicePixelRatio(scale);
+
+ QPainter painter(&m_pixmap);
+ paint(&painter, QRect(QPoint(), size), mode, state);
+
+ m_cacheKey = cacheKey;
+ }
+
+ return m_pixmap;
+}
+
+void QWindowsIconEngine::paint(QPainter *painter, const QRect &rect, QIcon::Mode mode, QIcon::State state)
+{
+ Q_UNUSED(state);
+
+ painter->save();
+ QFont renderFont(m_iconFont);
+ renderFont.setPixelSize(rect.height());
+ painter->setFont(renderFont);
+
+ QPalette palette;
+ switch (mode) {
+ case QIcon::Active:
+ painter->setPen(palette.color(QPalette::Active, QPalette::Text));
+ break;
+ case QIcon::Normal:
+ painter->setPen(palette.color(QPalette::Active, QPalette::Text));
+ break;
+ case QIcon::Disabled:
+ painter->setPen(palette.color(QPalette::Disabled, QPalette::Text));
+ break;
+ case QIcon::Selected:
+ painter->setPen(palette.color(QPalette::Active, QPalette::HighlightedText));
+ break;
+ }
+
+ painter->drawText(rect, Qt::AlignCenter, m_glyphs);
+ painter->restore();
+}
+
+QT_END_NAMESPACE
diff --git a/src/plugins/platforms/windows/qwindowsiconengine.h b/src/plugins/platforms/windows/qwindowsiconengine.h
new file mode 100644
index 0000000000..3c6cbddb8b
--- /dev/null
+++ b/src/plugins/platforms/windows/qwindowsiconengine.h
@@ -0,0 +1,47 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QWINDOWSICONENGINE_H
+#define QWINDOWSICONENGINE_H
+
+#include <QtCore/qt_windows.h>
+
+#include <QtGui/qfont.h>
+#include <QtGui/qiconengine.h>
+
+QT_BEGIN_NAMESPACE
+
+class QWindowsIconEngine : public QIconEngine
+{
+public:
+ QWindowsIconEngine(const QString &iconName);
+ ~QWindowsIconEngine();
+ QIconEngine *clone() const override;
+ QString key() const override;
+ QString iconName() override;
+ bool isNull() override;
+
+ QList<QSize> availableSizes(QIcon::Mode, QIcon::State) override;
+ QSize actualSize(const QSize &size, QIcon::Mode mode, QIcon::State state) override;
+ QPixmap pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state) override;
+ QPixmap scaledPixmap(const QSize &size, QIcon::Mode mode, QIcon::State state, qreal scale) override;
+ void paint(QPainter *painter, const QRect &rect, QIcon::Mode mode, QIcon::State state) override;
+
+private:
+ static constexpr quint64 calculateCacheKey(QIcon::Mode mode, QIcon::State state)
+ {
+ return (quint64(mode) << 32) | state;
+ }
+
+ QString glyphs() const;
+
+ const QString m_iconName;
+ const QFont m_iconFont;
+ const QString m_glyphs;
+ mutable QPixmap m_pixmap;
+ mutable quint64 m_cacheKey = {};
+};
+
+QT_END_NAMESPACE
+
+#endif // QWINDOWSICONENGINE_H
diff --git a/src/plugins/platforms/windows/qwindowsinputcontext.cpp b/src/plugins/platforms/windows/qwindowsinputcontext.cpp
index 758490ffb7..0281025b5b 100644
--- a/src/plugins/platforms/windows/qwindowsinputcontext.cpp
+++ b/src/plugins/platforms/windows/qwindowsinputcontext.cpp
@@ -676,7 +676,7 @@ int QWindowsInputContext::reconvertString(RECONVERTSTRING *reconv)
reconv->dwTargetStrOffset = reconv->dwCompStrOffset;
auto *pastReconv = reinterpret_cast<ushort *>(reconv + 1);
std::copy(surroundingText.utf16(), surroundingText.utf16() + surroundingText.size(),
- QT_MAKE_UNCHECKED_ARRAY_ITERATOR(pastReconv));
+ pastReconv);
return memSize;
}
diff --git a/src/plugins/platforms/windows/qwindowsintegration.cpp b/src/plugins/platforms/windows/qwindowsintegration.cpp
index 9e284f2366..6415c9ac50 100644
--- a/src/plugins/platforms/windows/qwindowsintegration.cpp
+++ b/src/plugins/platforms/windows/qwindowsintegration.cpp
@@ -48,6 +48,11 @@
#include <QtCore/qdebug.h>
#include <QtCore/qvariant.h>
+#include <QtCore/qoperatingsystemversion.h>
+#include <QtCore/private/qfunctions_win_p.h>
+
+#include <wrl.h>
+
#include <limits.h>
#if !defined(QT_NO_OPENGL)
@@ -56,6 +61,14 @@
#include "qwindowsopengltester.h"
+#if QT_CONFIG(cpp_winrt)
+# include <QtCore/private/qt_winrtbase_p.h>
+# include <winrt/Windows.UI.Notifications.h>
+# include <winrt/Windows.Data.Xml.Dom.h>
+# include <winrt/Windows.Foundation.h>
+# include <winrt/Windows.UI.ViewManagement.h>
+#endif
+
#include <memory>
static inline void initOpenGlBlacklistResources()
@@ -67,31 +80,6 @@ QT_BEGIN_NAMESPACE
using namespace Qt::StringLiterals;
-/*!
- \class QWindowsIntegration
- \brief QPlatformIntegration implementation for Windows.
- \internal
-
- \section1 Programming Considerations
-
- The platform plugin should run on Desktop Windows from Windows XP onwards
- and Windows Embedded.
-
- It should compile with:
- \list
- \li Microsoft Visual Studio 2013 or later (using the Microsoft Windows SDK,
- (\c Q_CC_MSVC).
- \li Stock \l{http://mingw.org/}{MinGW} (\c Q_CC_MINGW).
- This version ships with headers that are missing a lot of WinAPI.
- \li MinGW distributions using GCC 4.7 or higher and a recent MinGW-w64 runtime API,
- such as \l{http://tdm-gcc.tdragon.net/}{TDM-GCC}, or
- \l{http://mingwbuilds.sourceforge.net/}{MinGW-builds}
- (\c Q_CC_MINGW and \c __MINGW64_VERSION_MAJOR indicating the version).
- MinGW-w64 provides more complete headers (compared to stock MinGW from mingw.org),
- including a considerable part of the Windows SDK.
- \endlist
-*/
-
struct QWindowsIntegrationPrivate
{
Q_DISABLE_COPY_MOVE(QWindowsIntegrationPrivate)
@@ -131,7 +119,7 @@ bool parseIntOption(const QString &parameter,const QLatin1StringView &option,
const auto valueRef = QStringView{parameter}.right(valueLength);
const int value = valueRef.toInt(&ok);
if (ok) {
- if (value >= minimumValue && value <= maximumValue)
+ if (value >= int(minimumValue) && value <= int(maximumValue))
*target = static_cast<IntType>(value);
else {
qWarning() << "Value" << value << "for option" << option << "out of range"
@@ -148,14 +136,14 @@ using DarkModeHandling = QNativeInterface::Private::QWindowsApplication::DarkMod
static inline unsigned parseOptions(const QStringList &paramList,
int *tabletAbsoluteRange,
- QtWindows::ProcessDpiAwareness *dpiAwareness,
+ QtWindows::DpiAwareness *dpiAwareness,
DarkModeHandling *darkModeHandling)
{
unsigned options = 0;
for (const QString &param : paramList) {
if (param.startsWith(u"fontengine=")) {
- if (param.endsWith(u"directwrite")) {
- options |= QWindowsIntegration::FontDatabaseDirectWrite;
+ if (param.endsWith(u"gdi")) {
+ options |= QWindowsIntegration::FontDatabaseGDI;
} else if (param.endsWith(u"freetype")) {
options |= QWindowsIntegration::FontDatabaseFreeType;
} else if (param.endsWith(u"native")) {
@@ -179,7 +167,8 @@ static inline unsigned parseOptions(const QStringList &paramList,
options |= QWindowsIntegration::DontPassOsMouseEventsSynthesizedFromTouch;
} else if (parseIntOption(param, "verbose"_L1, 0, INT_MAX, &QWindowsContext::verbose)
|| parseIntOption(param, "tabletabsoluterange"_L1, 0, INT_MAX, tabletAbsoluteRange)
- || parseIntOption(param, "dpiawareness"_L1, QtWindows::ProcessDpiUnaware, QtWindows::ProcessPerMonitorV2DpiAware, dpiAwareness)) {
+ || parseIntOption(param, "dpiawareness"_L1, QtWindows::DpiAwareness::Invalid,
+ QtWindows::DpiAwareness::PerMonitorVersion2, dpiAwareness)) {
} else if (param == u"menus=native") {
options |= QWindowsIntegration::AlwaysUseNativeMenus;
} else if (param == u"menus=none") {
@@ -209,10 +198,11 @@ void QWindowsIntegrationPrivate::parseOptions(QWindowsIntegration *q, const QStr
static bool dpiAwarenessSet = false;
// Default to per-monitor-v2 awareness (if available)
- QtWindows::ProcessDpiAwareness dpiAwareness = QtWindows::ProcessPerMonitorV2DpiAware;
+ QtWindows::DpiAwareness dpiAwareness = QtWindows::DpiAwareness::PerMonitorVersion2;
int tabletAbsoluteRange = -1;
- DarkModeHandling darkModeHandling = DarkModeHandlingFlag::DarkModeWindowFrames;
+ DarkModeHandling darkModeHandling = DarkModeHandlingFlag::DarkModeWindowFrames
+ | DarkModeHandlingFlag::DarkModeStyle;
m_options = ::parseOptions(paramList, &tabletAbsoluteRange, &dpiAwareness, &darkModeHandling);
q->setDarkModeHandling(darkModeHandling);
QWindowsFontDatabase::setFontOptions(m_options);
@@ -223,25 +213,13 @@ void QWindowsIntegrationPrivate::parseOptions(QWindowsIntegration *q, const QStr
QCoreApplication::setAttribute(Qt::AA_CompressHighFrequencyEvents);
else
m_context.initTablet();
+ QWindowSystemInterfacePrivate::TabletEvent::setPlatformSynthesizesMouse(false);
if (!dpiAwarenessSet) { // Set only once in case of repeated instantiations of QGuiApplication.
if (!QCoreApplication::testAttribute(Qt::AA_PluginApplication)) {
- if (dpiAwareness == QtWindows::ProcessPerMonitorV2DpiAware) {
- // DpiAwareV2 requires using new API
- if (m_context.setProcessDpiV2Awareness()) {
- qCDebug(lcQpaWindows, "DpiAwareness: DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2");
- dpiAwarenessSet = true;
- } else {
- // fallback to old API
- dpiAwareness = QtWindows::ProcessPerMonitorDpiAware;
- }
- }
-
- if (!dpiAwarenessSet) {
- m_context.setProcessDpiAwareness(dpiAwareness);
- qCDebug(lcQpaWindows) << "DpiAwareness=" << dpiAwareness
- << "effective process DPI awareness=" << QWindowsContext::processDpiAwareness();
- }
+ m_context.setProcessDpiAwareness(dpiAwareness);
+ qCDebug(lcQpaWindow) << "DpiAwareness=" << dpiAwareness
+ << "effective process DPI awareness=" << QWindowsContext::processDpiAwareness();
}
dpiAwarenessSet = true;
}
@@ -278,9 +256,9 @@ QWindowsIntegration::~QWindowsIntegration()
void QWindowsIntegration::initialize()
{
- QString icStr = QPlatformInputContextFactory::requested();
- icStr.isNull() ? d->m_inputContext.reset(new QWindowsInputContext)
- : d->m_inputContext.reset(QPlatformInputContextFactory::create(icStr));
+ auto icStrs = QPlatformInputContextFactory::requested();
+ icStrs.isEmpty() ? d->m_inputContext.reset(new QWindowsInputContext)
+ : d->m_inputContext.reset(QPlatformInputContextFactory::create(icStrs));
}
bool QWindowsIntegration::hasCapability(QPlatformIntegration::Capability cap) const
@@ -308,6 +286,8 @@ bool QWindowsIntegration::hasCapability(QPlatformIntegration::Capability cap) co
return true;
case SwitchableWidgetComposition:
return false; // QTBUG-68329 QTBUG-53515 QTBUG-54734
+ case BackingStoreStaticContents:
+ return true;
default:
return QPlatformIntegration::hasCapability(cap);
}
@@ -318,7 +298,7 @@ QPlatformWindow *QWindowsIntegration::createPlatformWindow(QWindow *window) cons
{
if (window->type() == Qt::Desktop) {
auto *result = new QWindowsDesktopWindow(window);
- qCDebug(lcQpaWindows) << "Desktop window:" << window
+ qCDebug(lcQpaWindow) << "Desktop window:" << window
<< Qt::showbase << Qt::hex << result->winId() << Qt::noshowbase << Qt::dec << result->geometry();
return result;
}
@@ -328,15 +308,17 @@ QPlatformWindow *QWindowsIntegration::createPlatformWindow(QWindow *window) cons
requested.geometry = window->isTopLevel()
? QHighDpi::toNativePixels(window->geometry(), window)
: QHighDpi::toNativeLocalPosition(window->geometry(), window);
- // Apply custom margins (see QWindowsWindow::setCustomMargins())).
- const QVariant customMarginsV = window->property("_q_windowsCustomMargins");
- if (customMarginsV.isValid())
- requested.customMargins = qvariant_cast<QMargins>(customMarginsV);
+ if (!(requested.flags & Qt::FramelessWindowHint)) {
+ // Apply custom margins (see QWindowsWindow::setCustomMargins())).
+ const QVariant customMarginsV = window->property("_q_windowsCustomMargins");
+ if (customMarginsV.isValid())
+ requested.customMargins = qvariant_cast<QMargins>(customMarginsV);
+ }
QWindowsWindowData obtained =
QWindowsWindowData::create(window, requested,
QWindowsWindow::formatWindowTitle(window->title()));
- qCDebug(lcQpaWindows).nospace()
+ qCDebug(lcQpaWindow).nospace()
<< __FUNCTION__ << ' ' << window
<< "\n Requested: " << requested.geometry << " frame incl.="
<< QWindowsGeometryHint::positionIncludesFrame(window)
@@ -373,7 +355,7 @@ QPlatformWindow *QWindowsIntegration::createForeignWindow(QWindow *window, WId n
screen = pScreen->screen();
if (screen && screen != window->screen())
window->setScreen(screen);
- qCDebug(lcQpaWindows) << "Foreign window:" << window << Qt::showbase << Qt::hex
+ qCDebug(lcQpaWindow) << "Foreign window:" << window << Qt::showbase << Qt::hex
<< result->winId() << Qt::noshowbase << Qt::dec << obtainedGeometry << screen;
return result;
}
@@ -502,17 +484,17 @@ QWindowsStaticOpenGLContext *QWindowsIntegration::staticOpenGLContext()
QPlatformFontDatabase *QWindowsIntegration::fontDatabase() const
{
if (!d->m_fontDatabase) {
-#if QT_CONFIG(directwrite3)
- if (d->m_options & QWindowsIntegration::FontDatabaseDirectWrite)
- d->m_fontDatabase = new QWindowsDirectWriteFontDatabase;
- else
-#endif
#ifndef QT_NO_FREETYPE
if (d->m_options & QWindowsIntegration::FontDatabaseFreeType)
d->m_fontDatabase = new QWindowsFontDatabaseFT;
else
#endif // QT_NO_FREETYPE
- d->m_fontDatabase = new QWindowsFontDatabase();
+#if QT_CONFIG(directwrite3)
+ if (!(d->m_options & (QWindowsIntegration::FontDatabaseGDI | QWindowsIntegration::DontUseDirectWriteFonts)))
+ d->m_fontDatabase = new QWindowsDirectWriteFontDatabase;
+ else
+#endif
+ d->m_fontDatabase = new QWindowsFontDatabase;
}
return d->m_fontDatabase;
}
@@ -560,14 +542,9 @@ QVariant QWindowsIntegration::styleHint(QPlatformIntegration::StyleHint hint) co
return QPlatformIntegration::styleHint(hint);
}
-Qt::KeyboardModifiers QWindowsIntegration::queryKeyboardModifiers() const
-{
- return QWindowsKeyMapper::queryKeyboardModifiers();
-}
-
-QList<int> QWindowsIntegration::possibleKeys(const QKeyEvent *e) const
+QPlatformKeyMapper *QWindowsIntegration::keyMapper() const
{
- return d->m_context.possibleKeys(e);
+ return d->m_context.keyMapper();
}
#if QT_CONFIG(clipboard)
@@ -634,6 +611,156 @@ void QWindowsIntegration::beep() const
MessageBeep(MB_OK); // For QApplication
}
+void QWindowsIntegration::setApplicationBadge(qint64 number)
+{
+ // Clamp to positive numbers, as the Windows API doesn't support negative numbers
+ number = qMax(0, number);
+
+ // Persist, so we can re-apply it on setting changes and Explorer restart
+ m_applicationBadgeNumber = number;
+
+ static const bool isWindows11 = QOperatingSystemVersion::current() >= QOperatingSystemVersion::Windows11;
+
+#if QT_CONFIG(cpp_winrt)
+ // We prefer the native BadgeUpdater API, that allows us to set a number directly,
+ // but it requires that the application has a package identity, and also doesn't
+ // seem to work in all cases on < Windows 11.
+ if (isWindows11 && qt_win_hasPackageIdentity()) {
+ using namespace winrt::Windows::UI::Notifications;
+ auto badgeXml = BadgeUpdateManager::GetTemplateContent(BadgeTemplateType::BadgeNumber);
+ badgeXml.SelectSingleNode(L"//badge/@value").NodeValue(winrt::box_value(winrt::to_hstring(number)));
+ BadgeUpdateManager::CreateBadgeUpdaterForApplication().Update(BadgeNotification(badgeXml));
+ return;
+ }
+#endif
+
+ // Fallback for non-packaged apps, Windows 10, or Qt builds without WinRT/C++ support
+
+ if (!number) {
+ // Clear badge
+ setApplicationBadge(QImage());
+ return;
+ }
+
+ const bool isDarkMode = QWindowsContext::isDarkMode();
+
+ QColor badgeColor;
+ QColor textColor;
+
+#if QT_CONFIG(cpp_winrt)
+ if (isWindows11) {
+ // Match colors used by BadgeUpdater
+ static const auto fromUIColor = [](winrt::Windows::UI::Color &&color) {
+ return QColor(color.R, color.G, color.B, color.A);
+ };
+ using namespace winrt::Windows::UI::ViewManagement;
+ const auto settings = UISettings();
+ badgeColor = fromUIColor(settings.GetColorValue(isDarkMode ?
+ UIColorType::AccentLight2 : UIColorType::Accent));
+ textColor = fromUIColor(settings.GetColorValue(UIColorType::Background));
+ }
+#endif
+
+ if (!badgeColor.isValid()) {
+ // Fall back to basic badge colors, based on Windows 10 look
+ badgeColor = isDarkMode ? Qt::black : QColor(220, 220, 220);
+ badgeColor.setAlphaF(0.5f);
+ textColor = isDarkMode ? Qt::white : Qt::black;
+ }
+
+ const auto devicePixelRatio = qApp->devicePixelRatio();
+
+ static const QSize iconBaseSize(16, 16);
+ QImage image(iconBaseSize * devicePixelRatio,
+ QImage::Format_ARGB32_Premultiplied);
+ image.fill(Qt::transparent);
+
+ QPainter painter(&image);
+
+ QRect badgeRect = image.rect();
+ QPen badgeBorderPen = Qt::NoPen;
+ if (!isWindows11) {
+ QColor badgeBorderColor = textColor;
+ badgeBorderColor.setAlphaF(0.5f);
+ badgeBorderPen = badgeBorderColor;
+ badgeRect.adjust(1, 1, -1, -1);
+ }
+ painter.setBrush(badgeColor);
+ painter.setPen(badgeBorderPen);
+ painter.setRenderHint(QPainter::Antialiasing);
+ painter.drawEllipse(badgeRect);
+
+ auto pixelSize = qCeil(10.5 * devicePixelRatio);
+ // Unlike the BadgeUpdater API we're limited by a square
+ // badge, so adjust the font size when above two digits.
+ const bool textOverflow = number > 99;
+ if (textOverflow)
+ pixelSize *= 0.8;
+
+ QFont font = painter.font();
+ font.setPixelSize(pixelSize);
+ font.setWeight(isWindows11 ? QFont::Medium : QFont::DemiBold);
+ painter.setFont(font);
+
+ painter.setRenderHint(QPainter::TextAntialiasing, devicePixelRatio > 1);
+ painter.setPen(textColor);
+
+ auto text = textOverflow ? u"99+"_s : QString::number(number);
+ painter.translate(textOverflow ? 1 : 0, textOverflow ? 0 : -1);
+ painter.drawText(image.rect(), Qt::AlignCenter, text);
+
+ painter.end();
+
+ setApplicationBadge(image);
+}
+
+void QWindowsIntegration::setApplicationBadge(const QImage &image)
+{
+ QComHelper comHelper;
+
+ using Microsoft::WRL::ComPtr;
+
+ ComPtr<ITaskbarList3> taskbarList;
+ CoCreateInstance(CLSID_TaskbarList, nullptr,
+ CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&taskbarList));
+ if (!taskbarList) {
+ // There may not be any windows with a task bar button yet,
+ // in which case we'll apply the badge once a window with
+ // a button has been created.
+ return;
+ }
+
+ const auto hIcon = image.toHICON();
+
+ // Apply the icon to all top level windows, since the badge is
+ // set on an application level. If one of the windows go away
+ // the other windows will take over in showing the badge.
+ const auto topLevelWindows = QGuiApplication::topLevelWindows();
+ for (auto *topLevelWindow : topLevelWindows) {
+ if (!topLevelWindow->handle())
+ continue;
+ auto hwnd = reinterpret_cast<HWND>(topLevelWindow->winId());
+ taskbarList->SetOverlayIcon(hwnd, hIcon, L"");
+ }
+
+ DestroyIcon(hIcon);
+
+ // FIXME: Update icon when the application scale factor changes.
+ // Doing so in response to screen DPI changes is too soon, as the
+ // task bar is not yet ready for an updated icon, and will just
+ // result in a blurred icon even if our icon is high-DPI.
+}
+
+void QWindowsIntegration::updateApplicationBadge()
+{
+ // The system color settings have changed, or we are reacting
+ // to a task bar button being created for the fist time or after
+ // Explorer had crashed and re-started. In any case, re-apply the
+ // badge so that everything is up to date.
+ if (m_applicationBadgeNumber)
+ setApplicationBadge(m_applicationBadgeNumber);
+}
+
#if QT_CONFIG(vulkan)
QPlatformVulkanInstance *QWindowsIntegration::createPlatformVulkanInstance(QVulkanInstance *instance) const
{
diff --git a/src/plugins/platforms/windows/qwindowsintegration.h b/src/plugins/platforms/windows/qwindowsintegration.h
index 265ef719a6..c271207741 100644
--- a/src/plugins/platforms/windows/qwindowsintegration.h
+++ b/src/plugins/platforms/windows/qwindowsintegration.h
@@ -10,7 +10,9 @@
#include <qpa/qplatformintegration.h>
#include <QtCore/qscopedpointer.h>
#include <QtGui/private/qwindowsfontdatabase_p.h>
+#ifndef QT_NO_OPENGL
#include <QtGui/private/qopenglcontext_p.h>
+#endif
#include <qpa/qplatformopenglcontext.h>
QT_BEGIN_NAMESPACE
@@ -43,7 +45,7 @@ public:
DontUseWMPointer = 0x400,
DetectAltGrModifier = 0x800,
RtlEnabled = 0x1000,
- FontDatabaseDirectWrite = 0x2000
+ FontDatabaseGDI = 0x2000
};
explicit QWindowsIntegration(const QStringList &paramList);
@@ -80,8 +82,7 @@ public:
QPlatformServices *services() const override;
QVariant styleHint(StyleHint hint) const override;
- Qt::KeyboardModifiers queryKeyboardModifiers() const override;
- QList<int> possibleKeys(const QKeyEvent *e) const override;
+ QPlatformKeyMapper *keyMapper() const override;
static QWindowsIntegration *instance() { return m_instance; }
@@ -89,6 +90,10 @@ public:
void beep() const override;
+ void setApplicationBadge(qint64 number) override;
+ void setApplicationBadge(const QImage &image);
+ void updateApplicationBadge();
+
#if QT_CONFIG(sessionmanager)
QPlatformSessionManager *createPlatformSessionManager(const QString &id, const QString &key) const override;
#endif
@@ -104,6 +109,8 @@ private:
QScopedPointer<QWindowsIntegrationPrivate> d;
static QWindowsIntegration *m_instance;
+
+ qint64 m_applicationBadgeNumber = 0;
};
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/windows/qwindowsinternalmimedata.cpp b/src/plugins/platforms/windows/qwindowsinternalmimedata.cpp
index 11d5bc1c36..0542473a4b 100644
--- a/src/plugins/platforms/windows/qwindowsinternalmimedata.cpp
+++ b/src/plugins/platforms/windows/qwindowsinternalmimedata.cpp
@@ -3,7 +3,7 @@
#include "qwindowsinternalmimedata.h"
#include "qwindowscontext.h"
-#include "qwindowsmime.h"
+#include "qwindowsmimeregistry.h"
#include <QtCore/qdebug.h>
#include <QtCore/qvariant.h>
@@ -22,9 +22,9 @@
The base class introduces new virtuals to obtain and release
the instances IDataObject from the clipboard or Drag and Drop and
- does conversion using QWindowsMime classes.
+ does conversion using QWindowsMimeConverter classes.
- \sa QInternalMimeData, QWindowsMime, QWindowsMimeConverter
+ \sa QInternalMimeData, QWindowsMimeConverter, QWindowsMimeRegistry
\internal
*/
@@ -34,7 +34,7 @@ bool QWindowsInternalMimeData::hasFormat_sys(const QString &mime) const
if (!pDataObj)
return false;
- const QWindowsMimeConverter &mc = QWindowsContext::instance()->mimeConverter();
+ const QWindowsMimeRegistry &mc = QWindowsContext::instance()->mimeConverter();
const bool has = mc.converterToMime(mime, pDataObj) != nullptr;
releaseDataObject(pDataObj);
qCDebug(lcQpaMime) << __FUNCTION__ << mime << has;
@@ -47,7 +47,7 @@ QStringList QWindowsInternalMimeData::formats_sys() const
if (!pDataObj)
return QStringList();
- const QWindowsMimeConverter &mc = QWindowsContext::instance()->mimeConverter();
+ const QWindowsMimeRegistry &mc = QWindowsContext::instance()->mimeConverter();
const QStringList fmts = mc.allMimesForFormats(pDataObj);
releaseDataObject(pDataObj);
qCDebug(lcQpaMime) << __FUNCTION__ << fmts;
@@ -61,7 +61,7 @@ QVariant QWindowsInternalMimeData::retrieveData_sys(const QString &mimeType, QMe
return QVariant();
QVariant result;
- const QWindowsMimeConverter &mc = QWindowsContext::instance()->mimeConverter();
+ const QWindowsMimeRegistry &mc = QWindowsContext::instance()->mimeConverter();
if (auto converter = mc.converterToMime(mimeType, pDataObj))
result = converter->convertToMime(mimeType, pDataObj, type);
releaseDataObject(pDataObj);
diff --git a/src/plugins/platforms/windows/qwindowskeymapper.cpp b/src/plugins/platforms/windows/qwindowskeymapper.cpp
index 006396d92a..ba76cda40b 100644
--- a/src/plugins/platforms/windows/qwindowskeymapper.cpp
+++ b/src/plugins/platforms/windows/qwindowskeymapper.cpp
@@ -15,6 +15,7 @@
#include <QtGui/qevent.h>
#include <QtGui/private/qwindowsguieventdispatcher_p.h>
#include <QtCore/private/qdebug_p.h>
+#include <QtCore/private/qtools_p.h>
#if defined(WM_APPCOMMAND)
# ifndef FAPPCOMMAND_MOUSE
@@ -87,9 +88,17 @@ QWindowsKeyMapper::~QWindowsKeyMapper()= default;
#define VK_OEM_3 0xC0
#endif
-// We not only need the scancode itself but also the extended bit of key messages. Thus we need
-// the additional bit when masking the scancode.
-enum { scancodeBitmask = 0x1ff };
+// Get scancode from the given message
+static constexpr quint32 getScancode(const MSG &msg)
+{
+ const auto keyFlags = HIWORD(msg.lParam);
+ quint32 scancode = LOBYTE(keyFlags);
+ // if extended-key flag is on, the scan code consists of a sequence of two bytes,
+ // where the first byte has a value of 0xe0.
+ if ((keyFlags & KF_EXTENDED) != 0)
+ scancode |= 0xE000;
+ return scancode;
+}
// Key recorder ------------------------------------------------------------------------[ start ] --
struct KeyRecord {
@@ -531,33 +540,6 @@ QDebug operator<<(QDebug d, const KeyboardLayoutItem &k)
d << ')';
return d;
}
-
-// Helpers to format a list of int as Qt key sequence
-class formatKeys
-{
-public:
- explicit formatKeys(const QList<int> &keys) : m_keys(keys) {}
-
-private:
- friend QDebug operator<<(QDebug d, const formatKeys &keys);
- const QList<int> &m_keys;
-};
-
-QDebug operator<<(QDebug d, const formatKeys &k)
-{
- QDebugStateSaver saver(d);
- d.nospace();
- d << '(';
- for (int i =0, size = k.m_keys.size(); i < size; ++i) {
- if (i)
- d << ", ";
- d << QKeySequence(k.m_keys.at(i));
- }
- d << ')';
- return d;
-}
-#else // !QT_NO_DEBUG_STREAM
-static int formatKeys(const QList<int> &) { return 0; }
#endif // QT_NO_DEBUG_STREAM
/**
@@ -599,8 +581,7 @@ static inline quint32 toKeyOrUnicode(quint32 vk, quint32 scancode, unsigned char
static inline int asciiToKeycode(char a, int state)
{
- if (a >= 'a' && a <= 'z')
- a = toupper(a);
+ a = QtMiscUtils::toAsciiUpper(a);
if ((state & Qt::ControlModifier) != 0) {
if (a >= 0 && a <= 31) // Ctrl+@..Ctrl+A..CTRL+Z..Ctrl+_
a += '@'; // to @..A..Z.._
@@ -656,7 +637,7 @@ void QWindowsKeyMapper::updateKeyMap(const MSG &msg)
{
unsigned char kbdBuffer[256]; // Will hold the complete keyboard state
GetKeyboardState(kbdBuffer);
- const quint32 scancode = (msg.lParam >> 16) & scancodeBitmask;
+ const quint32 scancode = getScancode(msg);
updatePossibleKeyCodes(kbdBuffer, scancode, quint32(msg.wParam));
}
@@ -778,50 +759,49 @@ static void showSystemMenu(QWindow* w)
if (!menu)
return; // no menu for this window
-#define enabled (MF_BYCOMMAND | MF_ENABLED)
-#define disabled (MF_BYCOMMAND | MF_GRAYED)
+#define enabled (MF_BYCOMMAND | MFS_ENABLED)
+#define disabled (MF_BYCOMMAND | MFS_GRAYED)
- EnableMenuItem(menu, SC_MINIMIZE, (topLevel->flags() & Qt::WindowMinimizeButtonHint)?enabled:disabled);
- bool maximized = IsZoomed(topLevelHwnd);
+ EnableMenuItem(menu, SC_MINIMIZE, (topLevel->flags() & Qt::WindowMinimizeButtonHint) ? enabled : disabled);
+ const bool maximized = IsZoomed(topLevelHwnd);
- EnableMenuItem(menu, SC_MAXIMIZE, ! (topLevel->flags() & Qt::WindowMaximizeButtonHint) || maximized?disabled:enabled);
+ EnableMenuItem(menu, SC_MAXIMIZE, !(topLevel->flags() & Qt::WindowMaximizeButtonHint) || maximized ? disabled : enabled);
// We should _not_ check with the setFixedSize(x,y) case here, since Windows is not able to check
// this and our menu here would be out-of-sync with the menu produced by mouse-click on the
// System Menu, or right-click on the title bar.
- EnableMenuItem(menu, SC_SIZE, (topLevel->flags() & Qt::MSWindowsFixedSizeDialogHint) || maximized?disabled:enabled);
- EnableMenuItem(menu, SC_MOVE, maximized?disabled:enabled);
+ EnableMenuItem(menu, SC_SIZE, (topLevel->flags() & Qt::MSWindowsFixedSizeDialogHint) || maximized ? disabled : enabled);
+ EnableMenuItem(menu, SC_MOVE, maximized ? disabled : enabled);
EnableMenuItem(menu, SC_CLOSE, enabled);
+ EnableMenuItem(menu, SC_RESTORE, maximized ? enabled : disabled);
// Highlight the first entry in the menu, this is what native Win32 applications usually do.
- MENUITEMINFOW restoreItem;
- SecureZeroMemory(&restoreItem, sizeof(restoreItem));
- restoreItem.cbSize = sizeof(restoreItem);
- restoreItem.fMask = MIIM_STATE;
- restoreItem.fState = MFS_HILITE | (maximized ? MFS_ENABLED : MFS_GRAYED);
- SetMenuItemInfoW(menu, SC_RESTORE, FALSE, &restoreItem);
+ HiliteMenuItem(topLevelHwnd, menu, SC_RESTORE, MF_BYCOMMAND | MFS_HILITE);
// Set bold on close menu item
- MENUITEMINFO closeItem;
- closeItem.cbSize = sizeof(MENUITEMINFO);
- closeItem.fMask = MIIM_STATE;
- closeItem.fState = MFS_DEFAULT;
- SetMenuItemInfo(menu, SC_CLOSE, FALSE, &closeItem);
+ SetMenuDefaultItem(menu, SC_CLOSE, FALSE);
#undef enabled
#undef disabled
+
const QPoint pos = QHighDpi::toNativePixels(topLevel->geometry().topLeft(), topLevel);
const int titleBarOffset = isSystemMenuOffsetNeeded(topLevel->flags()) ? getTitleBarHeight(topLevelHwnd) : 0;
const int ret = TrackPopupMenuEx(menu,
- TPM_LEFTALIGN | TPM_TOPALIGN | TPM_NONOTIFY | TPM_RETURNCMD,
+ TPM_LEFTALIGN | TPM_TOPALIGN | TPM_NONOTIFY | TPM_RETURNCMD,
pos.x(), pos.y() + titleBarOffset,
topLevelHwnd,
nullptr);
+
+ // Remove the highlight of the restore menu item, otherwise when the user right-clicks
+ // on the title bar, the popuped system menu will also highlight the restore item, which
+ // is not appropriate, it should only be highlighted if the menu is brought up by keyboard.
+ HiliteMenuItem(topLevelHwnd, menu, SC_RESTORE, MF_BYCOMMAND | MFS_UNHILITE);
+
if (ret)
qWindowsWndProc(topLevelHwnd, WM_SYSCOMMAND, WPARAM(ret), 0);
}
-static inline void sendExtendedPressRelease(QWindow *w, int k,
+static inline void sendExtendedPressRelease(QWindow *w, unsigned long timestamp, int k,
Qt::KeyboardModifiers mods,
quint32 nativeScanCode,
quint32 nativeVirtualKey,
@@ -830,8 +810,8 @@ static inline void sendExtendedPressRelease(QWindow *w, int k,
bool autorep = false,
ushort count = 1)
{
- QWindowSystemInterface::handleExtendedKeyEvent(w, QEvent::KeyPress, k, mods, nativeScanCode, nativeVirtualKey, nativeModifiers, text, autorep, count);
- QWindowSystemInterface::handleExtendedKeyEvent(w, QEvent::KeyRelease, k, mods, nativeScanCode, nativeVirtualKey, nativeModifiers, text, autorep, count);
+ QWindowSystemInterface::handleExtendedKeyEvent(w, timestamp, QEvent::KeyPress, k, mods, nativeScanCode, nativeVirtualKey, nativeModifiers, text, autorep, count);
+ QWindowSystemInterface::handleExtendedKeyEvent(w, timestamp, QEvent::KeyRelease, k, mods, nativeScanCode, nativeVirtualKey, nativeModifiers, text, autorep, count);
}
/*!
@@ -898,7 +878,7 @@ bool QWindowsKeyMapper::translateMultimediaKeyEventInternal(QWindow *window, con
const int qtKey = int(CmdTbl[cmd]);
if (!skipPressRelease)
- sendExtendedPressRelease(receiver, qtKey, Qt::KeyboardModifier(state), 0, 0, 0);
+ sendExtendedPressRelease(receiver, msg.time, qtKey, Qt::KeyboardModifier(state), 0, 0, 0);
// QTBUG-43343: Make sure to return false if Qt does not handle the key, otherwise,
// the keys are not passed to the active media player.
# if QT_CONFIG(shortcut)
@@ -943,7 +923,7 @@ bool QWindowsKeyMapper::translateKeyEventInternal(QWindow *window, MSG msg,
m_seenAltGr = true;
const UINT msgType = msg.message;
- const quint32 scancode = (msg.lParam >> 16) & scancodeBitmask;
+ const quint32 scancode = getScancode(msg);
auto vk_key = quint32(msg.wParam);
quint32 nModifiers = 0;
@@ -978,7 +958,7 @@ bool QWindowsKeyMapper::translateKeyEventInternal(QWindow *window, MSG msg,
// A multi-character key or a Input method character
// not found by our look-ahead
if (msgType == WM_CHAR || msgType == WM_IME_CHAR) {
- sendExtendedPressRelease(receiver, 0, Qt::KeyboardModifier(state), scancode, vk_key, nModifiers, messageKeyText(msg), false);
+ sendExtendedPressRelease(receiver, msg.time, 0, Qt::KeyboardModifier(state), scancode, 0, nModifiers, messageKeyText(msg), false);
return true;
}
@@ -1013,14 +993,14 @@ bool QWindowsKeyMapper::translateKeyEventInternal(QWindow *window, MSG msg,
if (dirStatus == VK_LSHIFT
&& ((msg.wParam == VK_SHIFT && GetKeyState(VK_LCONTROL))
|| (msg.wParam == VK_CONTROL && GetKeyState(VK_LSHIFT)))) {
- sendExtendedPressRelease(receiver, Qt::Key_Direction_L, {},
+ sendExtendedPressRelease(receiver, msg.time, Qt::Key_Direction_L, {},
scancode, vk_key, nModifiers, QString(), false);
result = true;
dirStatus = 0;
} else if (dirStatus == VK_RSHIFT
&& ( (msg.wParam == VK_SHIFT && GetKeyState(VK_RCONTROL))
|| (msg.wParam == VK_CONTROL && GetKeyState(VK_RSHIFT)))) {
- sendExtendedPressRelease(receiver, Qt::Key_Direction_R, {},
+ sendExtendedPressRelease(receiver, msg.time, Qt::Key_Direction_R, {},
scancode, vk_key, nModifiers, QString(), false);
result = true;
dirStatus = 0;
@@ -1233,9 +1213,9 @@ bool QWindowsKeyMapper::translateKeyEventInternal(QWindow *window, MSG msg,
// so, we have an auto-repeating key
if (rec) {
if (code < Qt::Key_Shift || code > Qt::Key_ScrollLock) {
- QWindowSystemInterface::handleExtendedKeyEvent(receiver, QEvent::KeyRelease, code,
+ QWindowSystemInterface::handleExtendedKeyEvent(receiver, msg.time, QEvent::KeyRelease, code,
Qt::KeyboardModifier(state), scancode, quint32(msg.wParam), nModifiers, rec->text, true);
- QWindowSystemInterface::handleExtendedKeyEvent(receiver, QEvent::KeyPress, code,
+ QWindowSystemInterface::handleExtendedKeyEvent(receiver, msg.time, QEvent::KeyPress, code,
Qt::KeyboardModifier(state), scancode, quint32(msg.wParam), nModifiers, rec->text, true);
result = true;
}
@@ -1261,7 +1241,7 @@ bool QWindowsKeyMapper::translateKeyEventInternal(QWindow *window, MSG msg,
if (msg.wParam == VK_PACKET)
code = asciiToKeycode(char(uch.cell()), state);
- QWindowSystemInterface::handleExtendedKeyEvent(receiver, QEvent::KeyPress, code,
+ QWindowSystemInterface::handleExtendedKeyEvent(receiver, msg.time, QEvent::KeyPress, code,
modifiers, scancode, quint32(msg.wParam), nModifiers, text, false);
result =true;
bool store = true;
@@ -1303,10 +1283,10 @@ bool QWindowsKeyMapper::translateKeyEventInternal(QWindow *window, MSG msg,
if ((msg.lParam & 0x40000000) == 0 &&
Qt::KeyboardModifier(state) == Qt::NoModifier &&
((code == Qt::Key_F18) || (code == Qt::Key_F19) || (code == Qt::Key_F20))) {
- QWindowSystemInterface::handleExtendedKeyEvent(receiver, QEvent::KeyPress, code,
+ QWindowSystemInterface::handleExtendedKeyEvent(receiver, msg.time, QEvent::KeyPress, code,
Qt::MetaModifier, scancode,
quint32(msg.wParam), MetaLeft);
- QWindowSystemInterface::handleExtendedKeyEvent(receiver, QEvent::KeyRelease, code,
+ QWindowSystemInterface::handleExtendedKeyEvent(receiver, msg.time, QEvent::KeyRelease, code,
Qt::NoModifier, scancode,
quint32(msg.wParam), 0);
result = true;
@@ -1318,7 +1298,7 @@ bool QWindowsKeyMapper::translateKeyEventInternal(QWindow *window, MSG msg,
// Map SHIFT + Tab to SHIFT + BackTab, QShortcutMap knows about this translation
if (code == Qt::Key_Tab && (state & Qt::ShiftModifier) == Qt::ShiftModifier)
code = Qt::Key_Backtab;
- QWindowSystemInterface::handleExtendedKeyEvent(receiver, QEvent::KeyRelease, code,
+ QWindowSystemInterface::handleExtendedKeyEvent(receiver, msg.time, QEvent::KeyRelease, code,
Qt::KeyboardModifier(state), scancode, quint32(msg.wParam),
nModifiers,
(rec ? rec->text : QString()), false);
@@ -1340,7 +1320,7 @@ bool QWindowsKeyMapper::translateKeyEventInternal(QWindow *window, MSG msg,
return result;
}
-Qt::KeyboardModifiers QWindowsKeyMapper::queryKeyboardModifiers()
+Qt::KeyboardModifiers QWindowsKeyMapper::queryKeyboardModifiers() const
{
Qt::KeyboardModifiers modifiers = Qt::NoModifier;
if (GetKeyState(VK_SHIFT) < 0)
@@ -1354,9 +1334,9 @@ Qt::KeyboardModifiers QWindowsKeyMapper::queryKeyboardModifiers()
return modifiers;
}
-QList<int> QWindowsKeyMapper::possibleKeys(const QKeyEvent *e) const
+QList<QKeyCombination> QWindowsKeyMapper::possibleKeyCombinations(const QKeyEvent *e) const
{
- QList<int> result;
+ QList<QKeyCombination> result;
const quint32 nativeVirtualKey = e->nativeVirtualKey();
@@ -1370,31 +1350,34 @@ QList<int> QWindowsKeyMapper::possibleKeys(const QKeyEvent *e) const
quint32 baseKey = kbItem.qtKey[0];
Qt::KeyboardModifiers keyMods = e->modifiers();
if (baseKey == Qt::Key_Return && (e->nativeModifiers() & ExtendedKey)) {
- result << (Qt::Key_Enter | keyMods).toCombined();
+ result << (Qt::Key_Enter | keyMods);
return result;
}
- result << int(baseKey) + int(keyMods); // The base key is _always_ valid, of course
+
+ // The base key is _always_ valid, of course
+ result << QKeyCombination::fromCombined(int(baseKey) + int(keyMods));
for (size_t i = 1; i < NumMods; ++i) {
Qt::KeyboardModifiers neededMods = ModsTbl[i];
quint32 key = kbItem.qtKey[i];
if (key && key != baseKey && ((keyMods & neededMods) == neededMods)) {
const Qt::KeyboardModifiers missingMods = keyMods & ~neededMods;
- const int matchedKey = int(key) + int(missingMods);
- const auto it =
- std::find_if(result.begin(), result.end(),
- [key] (int k) { return (k & ~Qt::KeyboardModifierMask) == key; });
+ const auto matchedKey = QKeyCombination::fromCombined(int(key) + int(missingMods));
+ const auto it = std::find_if(result.begin(), result.end(),
+ [key](auto keyCombination) {
+ return keyCombination.key() == key;
+ });
// QTBUG-67200: Use the match with the least modifiers (prefer
// Shift+9 over Alt + Shift + 9) resulting in more missing modifiers.
if (it == result.end())
result << matchedKey;
- else if (missingMods > (*it & Qt::KeyboardModifierMask))
+ else if (missingMods > it->keyboardModifiers())
*it = matchedKey;
}
}
qCDebug(lcQpaEvents) << __FUNCTION__ << e << "nativeVirtualKey="
<< Qt::showbase << Qt::hex << e->nativeVirtualKey() << Qt::dec << Qt::noshowbase
- << e->modifiers() << kbItem << "\n returns" << formatKeys(result);
+ << e->modifiers() << kbItem << "\n returns" << result;
return result;
}
diff --git a/src/plugins/platforms/windows/qwindowskeymapper.h b/src/plugins/platforms/windows/qwindowskeymapper.h
index 6e13c47323..72b2536ad7 100644
--- a/src/plugins/platforms/windows/qwindowskeymapper.h
+++ b/src/plugins/platforms/windows/qwindowskeymapper.h
@@ -8,6 +8,8 @@
#include <QtCore/qlocale.h>
+#include <qpa/qplatformkeymapper.h>
+
QT_BEGIN_NAMESPACE
class QKeyEvent;
@@ -33,7 +35,7 @@ struct KeyboardLayoutItem {
quint32 qtKey[NumQtKeys]; // Can by any Qt::Key_<foo>, or unicode character
};
-class QWindowsKeyMapper
+class QWindowsKeyMapper : public QPlatformKeyMapper
{
Q_DISABLE_COPY_MOVE(QWindowsKeyMapper)
public:
@@ -53,8 +55,8 @@ public:
QWindow *keyGrabber() const { return m_keyGrabber; }
void setKeyGrabber(QWindow *w) { m_keyGrabber = w; }
- static Qt::KeyboardModifiers queryKeyboardModifiers();
- QList<int> possibleKeys(const QKeyEvent *e) const;
+ Qt::KeyboardModifiers queryKeyboardModifiers() const override;
+ QList<QKeyCombination> possibleKeyCombinations(const QKeyEvent *e) const override;
private:
bool translateKeyEventInternal(QWindow *receiver, MSG msg, bool grab, LRESULT *lResult);
diff --git a/src/plugins/platforms/windows/qwindowsmenu.cpp b/src/plugins/platforms/windows/qwindowsmenu.cpp
index 403ce52cb0..79deeeaea2 100644
--- a/src/plugins/platforms/windows/qwindowsmenu.cpp
+++ b/src/plugins/platforms/windows/qwindowsmenu.cpp
@@ -6,6 +6,7 @@
#include "qwindowswindow.h"
#include <QtGui/qwindow.h>
+#include <QtGui/private/qpixmap_win_p.h>
#include <QtCore/qdebug.h>
#include <QtCore/qvariant.h>
#include <QtCore/qmetaobject.h>
@@ -229,8 +230,6 @@ void QWindowsMenuItem::setIcon(const QIcon &icon)
updateBitmap();
}
-Q_GUI_EXPORT HBITMAP qt_pixmapToWinHBITMAP(const QPixmap &p, int hbitmapFormat = 0);
-
void QWindowsMenuItem::updateBitmap()
{
freeBitmap();
diff --git a/src/plugins/platforms/windows/qwindowsmenu.h b/src/plugins/platforms/windows/qwindowsmenu.h
index 646ab64894..6f66180d82 100644
--- a/src/plugins/platforms/windows/qwindowsmenu.h
+++ b/src/plugins/platforms/windows/qwindowsmenu.h
@@ -9,7 +9,6 @@
#include <qpa/qplatformmenu.h>
#include <QtCore/qlist.h>
-#include <QtCore/qpair.h>
QT_BEGIN_NAMESPACE
diff --git a/src/plugins/platforms/windows/qwindowsmime.cpp b/src/plugins/platforms/windows/qwindowsmimeregistry.cpp
index 8233e82d8e..8d147e8fa0 100644
--- a/src/plugins/platforms/windows/qwindowsmime.cpp
+++ b/src/plugins/platforms/windows/qwindowsmimeregistry.cpp
@@ -1,7 +1,7 @@
// Copyright (C) 2020 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
-#include "qwindowsmime.h"
+#include "qwindowsmimeregistry.h"
#include "qwindowscontext.h"
#include <QtGui/private/qinternalmimedata_p.h>
@@ -154,7 +154,7 @@ static bool qt_write_dibv5(QDataStream &s, QImage image)
return false;
if (image.format() != QImage::Format_ARGB32)
- image = image.convertToFormat(QImage::Format_ARGB32);
+ image = std::move(image).convertToFormat(QImage::Format_ARGB32);
auto *buf = new uchar[bpl_bmp];
@@ -300,7 +300,7 @@ QDebug operator<<(QDebug d, const FORMATETC &tc)
d << "CF_ENHMETAFILE";
break;
default:
- d << QWindowsMimeConverter::clipboardFormatName(tc.cfFormat);
+ d << QWindowsMimeRegistry::clipboardFormatName(tc.cfFormat);
break;
}
d << ", dwAspect=" << tc.dwAspect << ", lindex=" << tc.lindex
@@ -333,106 +333,7 @@ QDebug operator<<(QDebug d, IDataObject *dataObj)
}
#endif // !QT_NO_DEBUG_STREAM
-/*!
- \class QWindowsMime
- \brief The QWindowsMime class maps open-standard MIME to Window Clipboard formats.
- \internal
-
- Qt's drag-and-drop and clipboard facilities use the MIME standard.
- On X11, this maps trivially to the Xdnd protocol, but on Windows
- although some applications use MIME types to describe clipboard
- formats, others use arbitrary non-standardized naming conventions,
- or unnamed built-in formats of Windows.
-
- By instantiating subclasses of QWindowsMime that provide conversions
- between Windows Clipboard and MIME formats, you can convert
- proprietary clipboard formats to MIME formats.
-
- Qt has predefined support for the following Windows Clipboard formats:
-
- \table
- \header \li Windows Format \li Equivalent MIME type
- \row \li \c CF_UNICODETEXT \li \c text/plain
- \row \li \c CF_TEXT \li \c text/plain
- \row \li \c CF_DIB \li \c{image/xyz}, where \c xyz is
- a \l{QImageWriter::supportedImageFormats()}{Qt image format}
- \row \li \c CF_HDROP \li \c text/uri-list
- \row \li \c CF_INETURL \li \c text/uri-list
- \row \li \c CF_HTML \li \c text/html
- \endtable
-
- An example use of this class would be to map the Windows Metafile
- clipboard format (\c CF_METAFILEPICT) to and from the MIME type
- \c{image/x-wmf}. This conversion might simply be adding or removing
- a header, or even just passing on the data. See \l{Drag and Drop}
- for more information on choosing and definition MIME types.
-
- You can check if a MIME type is convertible using canConvertFromMime() and
- can perform conversions with convertToMime() and convertFromMime().
-
- \sa QWindowsMimeConverter
-*/
-
-
-/*!
-\fn bool QWindowsMime::canConvertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData) const
-
- Returns \c true if the converter can convert from the \a mimeData to
- the format specified in \a formatetc.
-
- All subclasses must reimplement this pure virtual function.
-*/
-
-/*!
- \fn bool QWindowsMime::canConvertToMime(const QString &mimeType, IDataObject *pDataObj) const
-
- Returns \c true if the converter can convert to the \a mimeType from
- the available formats in \a pDataObj.
-
- All subclasses must reimplement this pure virtual function.
-*/
-
-/*!
-\fn QString QWindowsMime::mimeForFormat(const FORMATETC &formatetc) const
-
- Returns the mime type that will be created form the format specified
- in \a formatetc, or an empty string if this converter does not support
- \a formatetc.
-
- All subclasses must reimplement this pure virtual function.
-*/
-
-/*!
-\fn QList<FORMATETC> QWindowsMime::formatsForMime(const QString &mimeType, const QMimeData *mimeData) const
-
- Returns a QList of FORMATETC structures representing the different windows clipboard
- formats that can be provided for the \a mimeType from the \a mimeData.
-
- All subclasses must reimplement this pure virtual function.
-*/
-
-/*!
- \fn QVariant QWindowsMime::convertToMime(const QString &mimeType, IDataObject *pDataObj,
- QMetaType preferredType) const
-
- Returns a QVariant containing the converted data for \a mimeType from \a pDataObj.
- If possible the QVariant should be of the \a preferredType to avoid needless conversions.
-
- All subclasses must reimplement this pure virtual function.
-*/
-
-/*!
-\fn bool QWindowsMime::convertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData, STGMEDIUM * pmedium) const
-
- Convert the \a mimeData to the format specified in \a formatetc.
- The converted data should then be placed in \a pmedium structure.
-
- Return true if the conversion was successful.
-
- All subclasses must reimplement this pure virtual function.
-*/
-
-class QWindowsMimeText : public QNativeInterface::Private::QWindowsMime
+class QWindowsMimeText : public QWindowsMimeConverter
{
public:
bool canConvertToMime(const QString &mimeType, IDataObject *pDataObj) const override;
@@ -446,7 +347,7 @@ public:
bool QWindowsMimeText::canConvertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData) const
{
int cf = getCf(formatetc);
- return (cf == CF_UNICODETEXT || cf == CF_TEXT) && mimeData->hasText();
+ return (cf == CF_UNICODETEXT || (cf == CF_TEXT && GetACP() != CP_UTF8)) && mimeData->hasText();
}
/*
@@ -537,7 +438,7 @@ QString QWindowsMimeText::mimeForFormat(const FORMATETC &formatetc) const
{
int cf = getCf(formatetc);
if (cf == CF_UNICODETEXT || cf == CF_TEXT)
- return QStringLiteral("text/plain");
+ return u"text/plain"_s;
return QString();
}
@@ -547,7 +448,8 @@ QList<FORMATETC> QWindowsMimeText::formatsForMime(const QString &mimeType, const
QList<FORMATETC> formatics;
if (mimeType.startsWith(u"text/plain") && mimeData->hasText()) {
formatics += setCf(CF_UNICODETEXT);
- formatics += setCf(CF_TEXT);
+ if (GetACP() != CP_UTF8)
+ formatics += setCf(CF_TEXT);
}
return formatics;
}
@@ -588,7 +490,7 @@ QVariant QWindowsMimeText::convertToMime(const QString &mime, LPDATAOBJECT pData
return ret;
}
-class QWindowsMimeURI : public QNativeInterface::Private::QWindowsMime
+class QWindowsMimeURI : public QWindowsMimeConverter
{
public:
QWindowsMimeURI();
@@ -605,8 +507,8 @@ private:
QWindowsMimeURI::QWindowsMimeURI()
{
- CF_INETURL_W = QWindowsMimeConverter::registerMimeType(QStringLiteral("UniformResourceLocatorW"));
- CF_INETURL = QWindowsMimeConverter::registerMimeType(QStringLiteral("UniformResourceLocator"));
+ CF_INETURL_W = registerMimeType(u"UniformResourceLocatorW"_s);
+ CF_INETURL = registerMimeType(u"UniformResourceLocator"_s);
}
bool QWindowsMimeURI::canConvertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData) const
@@ -689,7 +591,7 @@ QString QWindowsMimeURI::mimeForFormat(const FORMATETC &formatetc) const
{
QString format;
if (getCf(formatetc) == CF_HDROP || getCf(formatetc) == CF_INETURL_W || getCf(formatetc) == CF_INETURL)
- format = QStringLiteral("text/uri-list");
+ format = u"text/uri-list"_s;
return format;
}
@@ -754,7 +656,7 @@ QVariant QWindowsMimeURI::convertToMime(const QString &mimeType, LPDATAOBJECT pD
return QVariant();
}
-class QWindowsMimeHtml : public QNativeInterface::Private::QWindowsMime
+class QWindowsMimeHtml : public QWindowsMimeConverter
{
public:
QWindowsMimeHtml();
@@ -775,7 +677,7 @@ private:
QWindowsMimeHtml::QWindowsMimeHtml()
{
- CF_HTML = QWindowsMimeConverter::registerMimeType(QStringLiteral("HTML Format"));
+ CF_HTML = registerMimeType(u"HTML Format"_s);
}
QList<FORMATETC> QWindowsMimeHtml::formatsForMime(const QString &mimeType, const QMimeData *mimeData) const
@@ -789,7 +691,7 @@ QList<FORMATETC> QWindowsMimeHtml::formatsForMime(const QString &mimeType, const
QString QWindowsMimeHtml::mimeForFormat(const FORMATETC &formatetc) const
{
if (getCf(formatetc) == CF_HTML)
- return QStringLiteral("text/html");
+ return u"text/html"_s;
return QString();
}
@@ -892,7 +794,7 @@ bool QWindowsMimeHtml::convertFromMime(const FORMATETC &formatetc, const QMimeDa
#ifndef QT_NO_IMAGEFORMAT_BMP
-class QWindowsMimeImage : public QNativeInterface::Private::QWindowsMime
+class QWindowsMimeImage : public QWindowsMimeConverter
{
public:
QWindowsMimeImage();
@@ -906,6 +808,7 @@ public:
QVariant convertToMime(const QString &mime, IDataObject *pDataObj, QMetaType preferredType) const override;
QString mimeForFormat(const FORMATETC &formatetc) const override;
private:
+ bool hasOriginalDIBV5(IDataObject *pDataObj) const;
UINT CF_PNG;
};
@@ -933,7 +836,7 @@ QString QWindowsMimeImage::mimeForFormat(const FORMATETC &formatetc) const
{
int cf = getCf(formatetc);
if (cf == CF_DIB || cf == CF_DIBV5 || cf == int(CF_PNG))
- return QStringLiteral("application/x-qt-image");
+ return u"application/x-qt-image"_s;
return QString();
}
@@ -967,7 +870,7 @@ bool QWindowsMimeImage::convertFromMime(const FORMATETC &formatetc, const QMimeD
QByteArray ba;
if (cf == CF_DIB) {
if (img.format() > QImage::Format_ARGB32)
- img = img.convertToFormat(QImage::Format_RGB32);
+ img = std::move(img).convertToFormat(QImage::Format_RGB32);
const QByteArray ba = writeDib(img);
if (!ba.isEmpty())
return setData(ba, pmedium);
@@ -980,22 +883,48 @@ bool QWindowsMimeImage::convertFromMime(const FORMATETC &formatetc, const QMimeD
} else {
QDataStream s(&ba, QIODevice::WriteOnly);
s.setByteOrder(QDataStream::LittleEndian);// Intel byte order ####
- if (qt_write_dibv5(s, img))
+ if (qt_write_dibv5(s, std::move(img)))
return setData(ba, pmedium);
}
}
return false;
}
+bool QWindowsMimeImage::hasOriginalDIBV5(IDataObject *pDataObj) const
+{
+ bool isSynthesized = true;
+ IEnumFORMATETC *pEnum = nullptr;
+ HRESULT res = pDataObj->EnumFormatEtc(1, &pEnum);
+ if (res == S_OK && pEnum) {
+ FORMATETC fc;
+ while ((res = pEnum->Next(1, &fc, nullptr)) == S_OK) {
+ if (fc.ptd)
+ CoTaskMemFree(fc.ptd);
+ if (fc.cfFormat == CF_DIB)
+ break;
+ if (fc.cfFormat == CF_DIBV5) {
+ isSynthesized = false;
+ break;
+ }
+ }
+ pEnum->Release();
+ }
+ return !isSynthesized;
+}
+
QVariant QWindowsMimeImage::convertToMime(const QString &mimeType, IDataObject *pDataObj, QMetaType preferredType) const
{
Q_UNUSED(preferredType);
QVariant result;
if (mimeType != u"application/x-qt-image")
return result;
- //Try to convert from DIBV5 as it is the most
- //widespread format that support transparency
- if (canGetData(CF_DIBV5, pDataObj)) {
+ // Try to convert from DIBV5 as it is the most widespread format that supports transparency,
+ // but avoid synthesizing it, as that typically loses transparency, e.g. from Office
+ const bool canGetDibV5 = canGetData(CF_DIBV5, pDataObj);
+ const bool hasOrigDibV5 = canGetDibV5 ? hasOriginalDIBV5(pDataObj) : false;
+ qCDebug(lcQpaMime) << "canGetDibV5:" << canGetDibV5 << "hasOrigDibV5:" << hasOrigDibV5;
+ if (hasOrigDibV5) {
+ qCDebug(lcQpaMime) << "Decoding DIBV5";
QImage img;
QByteArray data = getData(CF_DIBV5, pDataObj);
QBuffer buffer(&data);
@@ -1004,6 +933,7 @@ QVariant QWindowsMimeImage::convertToMime(const QString &mimeType, IDataObject *
}
//PNG, MS Office place this (undocumented)
if (canGetData(CF_PNG, pDataObj)) {
+ qCDebug(lcQpaMime) << "Decoding PNG";
QImage img;
QByteArray data = getData(CF_PNG, pDataObj);
if (img.loadFromData(data, "PNG")) {
@@ -1012,6 +942,7 @@ QVariant QWindowsMimeImage::convertToMime(const QString &mimeType, IDataObject *
}
//Fallback to DIB
if (canGetData(CF_DIB, pDataObj)) {
+ qCDebug(lcQpaMime) << "Decoding DIB";
QImage img;
QByteArray data = getData(CF_DIBV5, pDataObj);
QBuffer buffer(&data);
@@ -1023,7 +954,7 @@ QVariant QWindowsMimeImage::convertToMime(const QString &mimeType, IDataObject *
}
#endif
-class QBuiltInMimes : public QNativeInterface::Private::QWindowsMime
+class QBuiltInMimes : public QWindowsMimeConverter
{
public:
QBuiltInMimes();
@@ -1044,10 +975,10 @@ private:
};
QBuiltInMimes::QBuiltInMimes()
-: QWindowsMime()
+: QWindowsMimeConverter()
{
- outFormats.insert(QWindowsMimeConverter::registerMimeType(QStringLiteral("application/x-color")), QStringLiteral("application/x-color"));
- inFormats.insert(QWindowsMimeConverter::registerMimeType(QStringLiteral("application/x-color")), QStringLiteral("application/x-color"));
+ outFormats.insert(registerMimeType(u"application/x-color"_s), u"application/x-color"_s);
+ inFormats.insert(registerMimeType(u"application/x-color"_s), u"application/x-color"_s);
}
bool QBuiltInMimes::canConvertFromMime(const FORMATETC &formatetc, const QMimeData *mimeData) const
@@ -1144,7 +1075,7 @@ QString QBuiltInMimes::mimeForFormat(const FORMATETC &formatetc) const
}
-class QLastResortMimes : public QNativeInterface::Private::QWindowsMime
+class QLastResortMimes : public QWindowsMimeConverter
{
public:
@@ -1172,25 +1103,25 @@ QLastResortMimes::QLastResortMimes()
{
//MIME Media-Types
if (ianaTypes.isEmpty()) {
- ianaTypes.append(QStringLiteral("application/"));
- ianaTypes.append(QStringLiteral("audio/"));
- ianaTypes.append(QStringLiteral("example/"));
- ianaTypes.append(QStringLiteral("image/"));
- ianaTypes.append(QStringLiteral("message/"));
- ianaTypes.append(QStringLiteral("model/"));
- ianaTypes.append(QStringLiteral("multipart/"));
- ianaTypes.append(QStringLiteral("text/"));
- ianaTypes.append(QStringLiteral("video/"));
+ ianaTypes.append(u"application/"_s);
+ ianaTypes.append(u"audio/"_s);
+ ianaTypes.append(u"example/"_s);
+ ianaTypes.append(u"image/"_s);
+ ianaTypes.append(u"message/"_s);
+ ianaTypes.append(u"model/"_s);
+ ianaTypes.append(u"multipart/"_s);
+ ianaTypes.append(u"text/"_s);
+ ianaTypes.append(u"video/"_s);
}
//Types handled by other classes
if (excludeList.isEmpty()) {
- excludeList.append(QStringLiteral("HTML Format"));
- excludeList.append(QStringLiteral("UniformResourceLocator"));
- excludeList.append(QStringLiteral("text/html"));
- excludeList.append(QStringLiteral("text/plain"));
- excludeList.append(QStringLiteral("text/uri-list"));
- excludeList.append(QStringLiteral("application/x-qt-image"));
- excludeList.append(QStringLiteral("application/x-color"));
+ excludeList.append(u"HTML Format"_s);
+ excludeList.append(u"UniformResourceLocator"_s);
+ excludeList.append(u"text/html"_s);
+ excludeList.append(u"text/plain"_s);
+ excludeList.append(u"text/uri-list"_s);
+ excludeList.append(u"application/x-qt-image"_s);
+ excludeList.append(u"application/x-color"_s);
}
}
@@ -1228,7 +1159,7 @@ QList<FORMATETC> QLastResortMimes::formatsForMime(const QString &mimeType, const
auto mit = std::find(formats.begin(), formats.end(), mimeType);
// register any other available formats
if (mit == formats.end() && !excludeList.contains(mimeType, Qt::CaseInsensitive))
- mit = formats.insert(QWindowsMimeConverter::registerMimeType(mimeType), mimeType);
+ mit = formats.insert(registerMimeType(mimeType), mimeType);
if (mit != formats.end())
formatetcs += setCf(mit.key());
@@ -1272,7 +1203,7 @@ bool QLastResortMimes::canConvertToMime(const QString &mimeType, IDataObject *pD
}
// if it is not in there then register it and see if we can get it
const auto mit = std::find(formats.cbegin(), formats.cend(), mimeType);
- const int cf = mit != formats.cend() ? mit.key() : QWindowsMimeConverter::registerMimeType(mimeType);
+ const int cf = mit != formats.cend() ? mit.key() : registerMimeType(mimeType);
return canGetData(cf, pDataObj);
}
@@ -1289,7 +1220,7 @@ QVariant QLastResortMimes::convertToMime(const QString &mimeType, IDataObject *p
data = getData(int(cf), pDataObj, lindex);
} else {
const auto mit = std::find(formats.cbegin(), formats.cend(), mimeType);
- const int cf = mit != formats.cend() ? mit.key() : QWindowsMimeConverter::registerMimeType(mimeType);
+ const int cf = mit != formats.cend() ? mit.key() : registerMimeType(mimeType);
data = getData(cf, pDataObj);
}
if (!data.isEmpty())
@@ -1304,7 +1235,7 @@ QString QLastResortMimes::mimeForFormat(const FORMATETC &formatetc) const
if (!format.isEmpty())
return format;
- const QString clipFormat = QWindowsMimeConverter::clipboardFormatName(getCf(formatetc));
+ const QString clipFormat = QWindowsMimeRegistry::clipboardFormatName(getCf(formatetc));
if (!clipFormat.isEmpty()) {
#if QT_CONFIG(draganddrop)
if (QInternalMimeData::canReadData(clipFormat))
@@ -1334,20 +1265,20 @@ QString QLastResortMimes::mimeForFormat(const FORMATETC &formatetc) const
}
/*!
- \class QWindowsMimeConverter
- \brief Manages the list of QWindowsMime instances.
+ \class QWindowsMimeRegistry
+ \brief Manages the list of QWindowsMimeConverter instances.
\internal
- \sa QWindowsMime
+ \sa QWindowsMimeConverter
*/
-QWindowsMimeConverter::QWindowsMimeConverter() = default;
+QWindowsMimeRegistry::QWindowsMimeRegistry() = default;
-QWindowsMimeConverter::~QWindowsMimeConverter()
+QWindowsMimeRegistry::~QWindowsMimeRegistry()
{
qDeleteAll(m_mimes.begin(), m_mimes.begin() + m_internalMimeCount);
}
-QWindowsMimeConverter::QWindowsMime *QWindowsMimeConverter::converterToMime(const QString &mimeType, IDataObject *pDataObj) const
+QWindowsMimeRegistry::QWindowsMimeConverter *QWindowsMimeRegistry::converterToMime(const QString &mimeType, IDataObject *pDataObj) const
{
ensureInitialized();
for (int i = m_mimes.size()-1; i >= 0; --i) {
@@ -1357,9 +1288,9 @@ QWindowsMimeConverter::QWindowsMime *QWindowsMimeConverter::converterToMime(cons
return nullptr;
}
-QStringList QWindowsMimeConverter::allMimesForFormats(IDataObject *pDataObj) const
+QStringList QWindowsMimeRegistry::allMimesForFormats(IDataObject *pDataObj) const
{
- qCDebug(lcQpaMime) << "QWindowsMime::allMimesForFormats()";
+ qCDebug(lcQpaMime) << "QWindowsMimeConverter::allMimesForFormats()";
ensureInitialized();
QStringList formats;
LPENUMFORMATETC FAR fmtenum;
@@ -1386,7 +1317,7 @@ QStringList QWindowsMimeConverter::allMimesForFormats(IDataObject *pDataObj) con
return formats;
}
-QWindowsMimeConverter::QWindowsMime *QWindowsMimeConverter::converterFromMime(const FORMATETC &formatetc, const QMimeData *mimeData) const
+QWindowsMimeRegistry::QWindowsMimeConverter *QWindowsMimeRegistry::converterFromMime(const FORMATETC &formatetc, const QMimeData *mimeData) const
{
ensureInitialized();
qCDebug(lcQpaMime) << __FUNCTION__ << formatetc;
@@ -1397,7 +1328,7 @@ QWindowsMimeConverter::QWindowsMime *QWindowsMimeConverter::converterFromMime(co
return nullptr;
}
-QList<FORMATETC> QWindowsMimeConverter::allFormatsForMime(const QMimeData *mimeData) const
+QList<FORMATETC> QWindowsMimeRegistry::allFormatsForMime(const QMimeData *mimeData) const
{
ensureInitialized();
QList<FORMATETC> formatics;
@@ -1414,34 +1345,37 @@ QList<FORMATETC> QWindowsMimeConverter::allFormatsForMime(const QMimeData *mimeD
return formatics;
}
-void QWindowsMimeConverter::ensureInitialized() const
+void QWindowsMimeRegistry::ensureInitialized() const
{
- if (m_mimes.isEmpty()) {
- m_mimes
+ if (m_internalMimeCount == 0) {
+ m_internalMimeCount = -1; // prevent reentrancy when types register themselves
#ifndef QT_NO_IMAGEFORMAT_BMP
- << new QWindowsMimeImage
+ (void)new QWindowsMimeImage;
#endif //QT_NO_IMAGEFORMAT_BMP
- << new QLastResortMimes
- << new QWindowsMimeText << new QWindowsMimeURI
- << new QWindowsMimeHtml << new QBuiltInMimes;
+ (void)new QLastResortMimes;
+ (void)new QWindowsMimeText;
+ (void)new QWindowsMimeURI;
+ (void)new QWindowsMimeHtml;
+ (void)new QBuiltInMimes;
m_internalMimeCount = m_mimes.size();
+ Q_ASSERT(m_internalMimeCount > 0);
}
}
-QString QWindowsMimeConverter::clipboardFormatName(int cf)
+QString QWindowsMimeRegistry::clipboardFormatName(int cf)
{
wchar_t buf[256] = {0};
return GetClipboardFormatName(UINT(cf), buf, 255)
? QString::fromWCharArray(buf) : QString();
}
-QVariant QWindowsMimeConverter::convertToMime(const QStringList &mimeTypes,
+QVariant QWindowsMimeRegistry::convertToMime(const QStringList &mimeTypes,
IDataObject *pDataObj,
QMetaType preferredType,
QString *formatIn /* = 0 */) const
{
for (const QString &format : mimeTypes) {
- if (const QWindowsMime *converter = converterToMime(format, pDataObj)) {
+ if (const QWindowsMimeConverter *converter = converterToMime(format, pDataObj)) {
if (converter->canConvertToMime(format, pDataObj)) {
const QVariant dataV = converter->convertToMime(format, pDataObj, preferredType);
if (dataV.isValid()) {
@@ -1458,7 +1392,7 @@ QVariant QWindowsMimeConverter::convertToMime(const QStringList &mimeTypes,
return QVariant();
}
-void QWindowsMimeConverter::registerMime(QWindowsMime *mime)
+void QWindowsMimeRegistry::registerMime(QWindowsMimeConverter *mime)
{
ensureInitialized();
m_mimes.append(mime);
@@ -1467,12 +1401,18 @@ void QWindowsMimeConverter::registerMime(QWindowsMime *mime)
/*!
Registers the MIME type \a mime, and returns an ID number
identifying the format on Windows.
+
+ A mime type \c {application/x-qt-windows-mime;value="WindowsType"} will be
+ registered as the clipboard format for \c WindowsType.
*/
-int QWindowsMimeConverter::registerMimeType(const QString &mime)
+int QWindowsMimeRegistry::registerMimeType(const QString &mime)
{
- const UINT f = RegisterClipboardFormat(reinterpret_cast<const wchar_t *> (mime.utf16()));
- if (!f)
- qErrnoWarning("QWindowsApplication::registerMimeType: Failed to register clipboard format");
+ const QString mimeType = isCustomMimeType(mime) ? customMimeType(mime) : mime;
+ const UINT f = RegisterClipboardFormat(reinterpret_cast<const wchar_t *> (mimeType.utf16()));
+ if (!f) {
+ qErrnoWarning("QWindowsMimeRegistry::registerMimeType: Failed to register clipboard format "
+ "for %s", qPrintable(mime));
+ }
return int(f);
}
diff --git a/src/plugins/platforms/windows/qwindowsmime.h b/src/plugins/platforms/windows/qwindowsmimeregistry.h
index 1f30e991d9..a0f4b4c60a 100644
--- a/src/plugins/platforms/windows/qwindowsmime.h
+++ b/src/plugins/platforms/windows/qwindowsmimeregistry.h
@@ -1,13 +1,13 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
-#ifndef QWINDOWSMIME_H
-#define QWINDOWSMIME_H
+#ifndef QWINDOWSMIMEREGISTRY_H
+#define QWINDOWSMIMEREGISTRY_H
-#include <QtGui/private/qwindowsmime_p.h>
#include <QtCore/qt_windows.h>
+#include <QtGui/qwindowsmimeconverter.h>
#include <QtCore/qlist.h>
#include <QtCore/qvariant.h>
@@ -16,26 +16,26 @@ QT_BEGIN_NAMESPACE
class QDebug;
class QMimeData;
-class QWindowsMimeConverter
+class QWindowsMimeRegistry
{
- Q_DISABLE_COPY_MOVE(QWindowsMimeConverter)
+ Q_DISABLE_COPY_MOVE(QWindowsMimeRegistry)
public:
- using QWindowsMime = QNativeInterface::Private::QWindowsMime;
+ using QWindowsMimeConverter = QWindowsMimeConverter;
- QWindowsMimeConverter();
- ~QWindowsMimeConverter();
+ QWindowsMimeRegistry();
+ ~QWindowsMimeRegistry();
- QWindowsMime *converterToMime(const QString &mimeType, IDataObject *pDataObj) const;
+ QWindowsMimeConverter *converterToMime(const QString &mimeType, IDataObject *pDataObj) const;
QStringList allMimesForFormats(IDataObject *pDataObj) const;
- QWindowsMime *converterFromMime(const FORMATETC &formatetc, const QMimeData *mimeData) const;
+ QWindowsMimeConverter *converterFromMime(const FORMATETC &formatetc, const QMimeData *mimeData) const;
QList<FORMATETC> allFormatsForMime(const QMimeData *mimeData) const;
// Convenience.
QVariant convertToMime(const QStringList &mimeTypes, IDataObject *pDataObj, QMetaType preferredType,
QString *format = nullptr) const;
- void registerMime(QWindowsMime *mime);
- void unregisterMime(QWindowsMime *mime) { m_mimes.removeOne(mime); }
+ void registerMime(QWindowsMimeConverter *mime);
+ void unregisterMime(QWindowsMimeConverter *mime) { m_mimes.removeOne(mime); }
static int registerMimeType(const QString &mime);
@@ -44,7 +44,7 @@ public:
private:
void ensureInitialized() const;
- mutable QList<QWindowsMime *> m_mimes;
+ mutable QList<QWindowsMimeConverter *> m_mimes;
mutable int m_internalMimeCount = 0;
};
@@ -55,4 +55,4 @@ QDebug operator<<(QDebug d, IDataObject *);
QT_END_NAMESPACE
-#endif // QWINDOWSMIME_H
+#endif // QWINDOWSMIMEREGISTRY_H
diff --git a/src/plugins/platforms/windows/qwindowsmousehandler.cpp b/src/plugins/platforms/windows/qwindowsmousehandler.cpp
index 6918f523ce..9af9fba408 100644
--- a/src/plugins/platforms/windows/qwindowsmousehandler.cpp
+++ b/src/plugins/platforms/windows/qwindowsmousehandler.cpp
@@ -16,7 +16,8 @@
#include <QtGui/qcursor.h>
#include <QtCore/qdebug.h>
-#include <QtCore/qscopedpointer.h>
+
+#include <memory>
#include <windowsx.h>
@@ -123,7 +124,7 @@ Qt::MouseButtons QWindowsMouseHandler::queryMouseButtons()
return result;
}
-static QPoint lastMouseMovePos;
+Q_CONSTINIT static QPoint lastMouseMovePos;
namespace {
struct MouseEvent {
@@ -267,7 +268,8 @@ bool QWindowsMouseHandler::translateMouseEvent(QWindow *window, HWND hwnd,
}
}
- const Qt::KeyboardModifiers keyModifiers = QWindowsKeyMapper::queryKeyboardModifiers();
+ const auto *keyMapper = QWindowsContext::instance()->keyMapper();
+ const Qt::KeyboardModifiers keyModifiers = keyMapper->queryKeyboardModifiers();
const MouseEvent mouseEvent = eventFromMsg(msg);
Qt::MouseButtons buttons;
@@ -285,19 +287,16 @@ bool QWindowsMouseHandler::translateMouseEvent(QWindow *window, HWND hwnd,
if (m_lastEventType == QEvent::NonClientAreaMouseButtonPress
&& (mouseEvent.type == QEvent::NonClientAreaMouseMove || mouseEvent.type == QEvent::MouseMove)
&& (m_lastEventButton & buttons) == 0) {
- if (mouseEvent.type == QEvent::NonClientAreaMouseMove) {
- QWindowSystemInterface::handleFrameStrutMouseEvent(window, device, clientPosition, globalPosition, buttons, m_lastEventButton,
- QEvent::NonClientAreaMouseButtonRelease, keyModifiers, source);
- } else {
- QWindowSystemInterface::handleMouseEvent(window, device, clientPosition, globalPosition, buttons, m_lastEventButton,
- QEvent::MouseButtonRelease, keyModifiers, source);
- }
+ auto releaseType = mouseEvent.type == QEvent::NonClientAreaMouseMove ?
+ QEvent::NonClientAreaMouseButtonRelease : QEvent::MouseButtonRelease;
+ QWindowSystemInterface::handleMouseEvent(window, msg.time, device, clientPosition, globalPosition, buttons, m_lastEventButton,
+ releaseType, keyModifiers, source);
}
m_lastEventType = mouseEvent.type;
m_lastEventButton = mouseEvent.button;
if (mouseEvent.type >= QEvent::NonClientAreaMouseMove && mouseEvent.type <= QEvent::NonClientAreaMouseButtonDblClick) {
- QWindowSystemInterface::handleFrameStrutMouseEvent(window, device, clientPosition,
+ QWindowSystemInterface::handleMouseEvent(window, msg.time, device, clientPosition,
globalPosition, buttons,
mouseEvent.button, mouseEvent.type,
keyModifiers, source);
@@ -447,7 +446,7 @@ bool QWindowsMouseHandler::translateMouseEvent(QWindow *window, HWND hwnd,
}
if (!discardEvent && mouseEvent.type != QEvent::None) {
- QWindowSystemInterface::handleMouseEvent(window, device, clientPosition, globalPosition, buttons,
+ QWindowSystemInterface::handleMouseEvent(window, msg.time, device, clientPosition, globalPosition, buttons,
mouseEvent.button, mouseEvent.type,
keyModifiers, source);
}
@@ -471,7 +470,7 @@ static bool isValidWheelReceiver(QWindow *candidate)
return false;
}
-static void redirectWheelEvent(QWindow *window, const QPoint &globalPos, int delta,
+static void redirectWheelEvent(QWindow *window, unsigned long timestamp, const QPoint &globalPos, int delta,
Qt::Orientation orientation, Qt::KeyboardModifiers mods)
{
// Redirect wheel event to one of the following, in order of preference:
@@ -492,6 +491,7 @@ static void redirectWheelEvent(QWindow *window, const QPoint &globalPos, int del
if (handleEvent) {
const QPoint point = (orientation == Qt::Vertical) ? QPoint(0, delta) : QPoint(delta, 0);
QWindowSystemInterface::handleWheelEvent(receiver,
+ timestamp,
QWindowsGeometryHint::mapFromGlobal(receiver, globalPos),
globalPos, QPoint(), point, mods);
}
@@ -520,7 +520,7 @@ bool QWindowsMouseHandler::translateMouseWheelEvent(QWindow *window, HWND,
delta = -delta;
const QPoint globalPos(GET_X_LPARAM(msg.lParam), GET_Y_LPARAM(msg.lParam));
- redirectWheelEvent(window, globalPos, delta, orientation, mods);
+ redirectWheelEvent(window, msg.time, globalPos, delta, orientation, mods);
return true;
}
@@ -551,7 +551,7 @@ bool QWindowsMouseHandler::translateScrollEvent(QWindow *window, HWND,
return false;
}
- redirectWheelEvent(window, QCursor::pos(), delta, Qt::Horizontal, Qt::NoModifier);
+ redirectWheelEvent(window, msg.time, QCursor::pos(), delta, Qt::Horizontal, Qt::NoModifier);
return true;
}
@@ -577,15 +577,14 @@ bool QWindowsMouseHandler::translateTouchEvent(QWindow *window, HWND,
const QRect screenGeometry = screen->geometry();
const int winTouchPointCount = int(msg.wParam);
- QScopedArrayPointer<TOUCHINPUT> winTouchInputs(new TOUCHINPUT[winTouchPointCount]);
- memset(winTouchInputs.data(), 0, sizeof(TOUCHINPUT) * size_t(winTouchPointCount));
+ const auto winTouchInputs = std::make_unique<TOUCHINPUT[]>(winTouchPointCount);
QTouchPointList touchPoints;
touchPoints.reserve(winTouchPointCount);
QEventPoint::States allStates;
GetTouchInputInfo(reinterpret_cast<HTOUCHINPUT>(msg.lParam),
- UINT(msg.wParam), winTouchInputs.data(), sizeof(TOUCHINPUT));
+ UINT(msg.wParam), winTouchInputs.get(), sizeof(TOUCHINPUT));
for (int i = 0; i < winTouchPointCount; ++i) {
const TOUCHINPUT &winTouchInput = winTouchInputs[i];
int id = m_touchInputIDToTouchPointID.value(winTouchInput.dwID, -1);
@@ -632,10 +631,12 @@ bool QWindowsMouseHandler::translateTouchEvent(QWindow *window, HWND,
if (allStates == QEventPoint::State::Released)
m_touchInputIDToTouchPointID.clear();
+ const auto *keyMapper = QWindowsContext::instance()->keyMapper();
QWindowSystemInterface::handleTouchEvent(window,
+ msg.time,
m_touchDevice.data(),
touchPoints,
- QWindowsKeyMapper::queryKeyboardModifiers());
+ keyMapper->queryKeyboardModifiers());
return true;
}
diff --git a/src/plugins/platforms/windows/qwindowsole.cpp b/src/plugins/platforms/windows/qwindowsole.cpp
index 31b851ce17..c01f716054 100644
--- a/src/plugins/platforms/windows/qwindowsole.cpp
+++ b/src/plugins/platforms/windows/qwindowsole.cpp
@@ -2,7 +2,7 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#include "qwindowsole.h"
-#include "qwindowsmime.h"
+#include "qwindowsmimeregistry.h"
#include "qwindowscontext.h"
\
#include <QtGui/qevent.h>
@@ -66,7 +66,7 @@ QWindowsOleDataObject::GetData(LPFORMATETC pformatetc, LPSTGMEDIUM pmedium)
HRESULT hr = ResultFromScode(DATA_E_FORMATETC);
if (data) {
- const QWindowsMimeConverter &mc = QWindowsContext::instance()->mimeConverter();
+ const QWindowsMimeRegistry &mc = QWindowsContext::instance()->mimeConverter();
if (auto converter = mc.converterFromMime(*pformatetc, data))
if (converter->convertFromMime(*pformatetc, data, pmedium))
hr = ResultFromScode(S_OK);
@@ -93,7 +93,7 @@ QWindowsOleDataObject::QueryGetData(LPFORMATETC pformatetc)
qCDebug(lcQpaMime) << __FUNCTION__;
if (data) {
- const QWindowsMimeConverter &mc = QWindowsContext::instance()->mimeConverter();
+ const QWindowsMimeRegistry &mc = QWindowsContext::instance()->mimeConverter();
hr = mc.converterFromMime(*pformatetc, data) ?
ResultFromScode(S_OK) : ResultFromScode(S_FALSE);
}
@@ -144,7 +144,7 @@ QWindowsOleDataObject::EnumFormatEtc(DWORD dwDirection, LPENUMFORMATETC FAR* ppe
QList<FORMATETC> fmtetcs;
if (dwDirection == DATADIR_GET) {
- QWindowsMimeConverter &mc = QWindowsContext::instance()->mimeConverter();
+ QWindowsMimeRegistry &mc = QWindowsContext::instance()->mimeConverter();
fmtetcs = mc.allFormatsForMime(data);
} else {
FORMATETC formatetc;
diff --git a/src/plugins/platforms/windows/qwindowsole.h b/src/plugins/platforms/windows/qwindowsole.h
index b687384684..016f9dd04c 100644
--- a/src/plugins/platforms/windows/qwindowsole.h
+++ b/src/plugins/platforms/windows/qwindowsole.h
@@ -4,12 +4,12 @@
#ifndef QWINDOWSOLE_H
#define QWINDOWSOLE_H
-#include "qwindowscombase.h"
#include <QtCore/qt_windows.h>
#include <QtCore/qlist.h>
#include <QtCore/qmap.h>
#include <QtCore/qpointer.h>
+#include <QtCore/private/qcomobject_p.h>
#include <objidl.h>
@@ -18,7 +18,7 @@ QT_BEGIN_NAMESPACE
class QMimeData;
class QWindow;
-class QWindowsOleDataObject : public QWindowsComBase<IDataObject>
+class QWindowsOleDataObject : public QComObject<IDataObject>
{
public:
explicit QWindowsOleDataObject(QMimeData *mimeData);
@@ -47,7 +47,7 @@ private:
DWORD performedEffect = DROPEFFECT_NONE;
};
-class QWindowsOleEnumFmtEtc : public QWindowsComBase<IEnumFORMATETC>
+class QWindowsOleEnumFmtEtc : public QComObject<IEnumFORMATETC>
{
public:
explicit QWindowsOleEnumFmtEtc(const QList<FORMATETC> &fmtetcs);
diff --git a/src/plugins/platforms/windows/qwindowsopengltester.cpp b/src/plugins/platforms/windows/qwindowsopengltester.cpp
index 19b60a4542..6a790bcc1b 100644
--- a/src/plugins/platforms/windows/qwindowsopengltester.cpp
+++ b/src/plugins/platforms/windows/qwindowsopengltester.cpp
@@ -15,6 +15,7 @@
#include <QtCore/qlibraryinfo.h>
#include <QtCore/qhash.h>
#include <private/qsystemlibrary_p.h>
+#include <QtGui/qtgui-config.h>
#ifndef QT_NO_OPENGL
#include <private/qopengl_p.h>
diff --git a/src/plugins/platforms/windows/qwindowspointerhandler.cpp b/src/plugins/platforms/windows/qwindowspointerhandler.cpp
index 943eaa2b0a..71c7217671 100644
--- a/src/plugins/platforms/windows/qwindowspointerhandler.cpp
+++ b/src/plugins/platforms/windows/qwindowspointerhandler.cpp
@@ -1,9 +1,7 @@
// Copyright (C) 2019 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
-#ifndef WINVER
-# define WINVER 0x0A00 // Enable pointer functions for MinGW
-#endif
+#include <QtCore/qt_windows.h>
#include "qwindowspointerhandler.h"
#include "qwindowsmousehandler.h"
@@ -430,8 +428,9 @@ bool QWindowsPointerHandler::translateTouchEvent(QWindow *window, HWND hwnd,
return false;
if (msg.message == WM_POINTERCAPTURECHANGED) {
- QWindowSystemInterface::handleTouchCancelEvent(window, m_touchDevice.data(),
- QWindowsKeyMapper::queryKeyboardModifiers());
+ const auto *keyMapper = QWindowsContext::instance()->keyMapper();
+ QWindowSystemInterface::handleTouchCancelEvent(window, msg.time, m_touchDevice.data(),
+ keyMapper->queryKeyboardModifiers());
m_lastTouchPoints.clear();
return true;
}
@@ -443,6 +442,8 @@ bool QWindowsPointerHandler::translateTouchEvent(QWindow *window, HWND hwnd,
if (id != -1)
m_lastTouchPoints.remove(id);
}
+ // Send LeaveEvent to reset hover when the last finger leaves the touch screen (QTBUG-62912)
+ QWindowSystemInterface::handleEnterLeaveEvent(nullptr, window);
}
// Only handle down/up/update, ignore others like WM_POINTERENTER, WM_POINTERLEAVE, etc.
@@ -524,7 +525,7 @@ bool QWindowsPointerHandler::translateTouchEvent(QWindow *window, HWND hwnd,
// Some devices send touches for each finger in a different message/frame, instead of consolidating
// them in the same frame as we were expecting. We account for missing unreleased touches here.
- for (auto tp : qAsConst(m_lastTouchPoints)) {
+ for (auto tp : std::as_const(m_lastTouchPoints)) {
if (!inputIds.contains(tp.id)) {
tp.state = QEventPoint::State::Stationary;
allStates |= tp.state;
@@ -539,8 +540,9 @@ bool QWindowsPointerHandler::translateTouchEvent(QWindow *window, HWND hwnd,
if (allStates == QEventPoint::State::Released)
m_touchInputIDToTouchPointID.clear();
- QWindowSystemInterface::handleTouchEvent(window, m_touchDevice.data(), touchPoints,
- QWindowsKeyMapper::queryKeyboardModifiers());
+ const auto *keyMapper = QWindowsContext::instance()->keyMapper();
+ QWindowSystemInterface::handleTouchEvent(window, msg.time, m_touchDevice.data(), touchPoints,
+ keyMapper->queryKeyboardModifiers());
return false; // Allow mouse messages to be generated.
}
@@ -637,7 +639,7 @@ bool QWindowsPointerHandler::translatePenEvent(QWindow *window, HWND hwnd, QtWin
switch (msg.message) {
case WM_POINTERENTER: {
- QWindowSystemInterface::handleTabletEnterLeaveProximityEvent(window, device.data(), true);
+ QWindowSystemInterface::handleTabletEnterLeaveProximityEvent(window, msg.time, device.data(), true);
m_windowUnderPointer = window;
// The local coordinates may fall outside the window.
// Wait until the next update to send the enter event.
@@ -650,7 +652,7 @@ bool QWindowsPointerHandler::translatePenEvent(QWindow *window, HWND hwnd, QtWin
m_windowUnderPointer = nullptr;
m_currentWindow = nullptr;
}
- QWindowSystemInterface::handleTabletEnterLeaveProximityEvent(window, device.data(), false);
+ QWindowSystemInterface::handleTabletEnterLeaveProximityEvent(window, msg.time, device.data(), false);
break;
case WM_POINTERDOWN:
case WM_POINTERUP:
@@ -673,9 +675,10 @@ bool QWindowsPointerHandler::translatePenEvent(QWindow *window, HWND hwnd, QtWin
wumPlatformWindow->applyCursor();
}
}
- const Qt::KeyboardModifiers keyModifiers = QWindowsKeyMapper::queryKeyboardModifiers();
+ const auto *keyMapper = QWindowsContext::instance()->keyMapper();
+ const Qt::KeyboardModifiers keyModifiers = keyMapper->queryKeyboardModifiers();
- QWindowSystemInterface::handleTabletEvent(target, device.data(),
+ QWindowSystemInterface::handleTabletEvent(target, msg.time, device.data(),
localPos, hiResGlobalPos, mouseButtons,
pressure, xTilt, yTilt, tangentialPressure,
rotation, z, keyModifiers);
@@ -726,7 +729,7 @@ bool QWindowsPointerHandler::translateMouseWheelEvent(QWindow *window,
QPoint localPos = QWindowsGeometryHint::mapFromGlobal(receiver, globalPos);
- QWindowSystemInterface::handleWheelEvent(receiver, localPos, globalPos, QPoint(), angleDelta, keyModifiers);
+ QWindowSystemInterface::handleWheelEvent(receiver, msg.time, localPos, globalPos, QPoint(), angleDelta, keyModifiers);
return true;
}
@@ -739,20 +742,20 @@ bool QWindowsPointerHandler::translateMouseEvent(QWindow *window,
{
*result = 0;
- QPoint eventPos(GET_X_LPARAM(msg.lParam), GET_Y_LPARAM(msg.lParam));
- if ((et & QtWindows::NonClientEventFlag) == 0 && QWindowsBaseWindow::isRtlLayout(hwnd)) {
- RECT clientArea;
- GetClientRect(hwnd, &clientArea);
- eventPos.setX(clientArea.right - eventPos.x());
- }
-
QPoint localPos;
QPoint globalPos;
+ QPoint eventPos(GET_X_LPARAM(msg.lParam), GET_Y_LPARAM(msg.lParam));
if ((et == QtWindows::MouseWheelEvent) || (et & QtWindows::NonClientEventFlag)) {
globalPos = eventPos;
localPos = QWindowsGeometryHint::mapFromGlobal(hwnd, eventPos);
} else {
+ if (QWindowsBaseWindow::isRtlLayout(hwnd)) {
+ RECT clientArea;
+ GetClientRect(hwnd, &clientArea);
+ eventPos.setX(clientArea.right - eventPos.x());
+ }
+
globalPos = QWindowsGeometryHint::mapToGlobal(hwnd, eventPos);
auto targetHwnd = hwnd;
if (auto *pw = window->handle())
@@ -762,7 +765,8 @@ bool QWindowsPointerHandler::translateMouseEvent(QWindow *window,
: QWindowsGeometryHint::mapFromGlobal(targetHwnd, globalPos);
}
- const Qt::KeyboardModifiers keyModifiers = QWindowsKeyMapper::queryKeyboardModifiers();
+ const auto *keyMapper = QWindowsContext::instance()->keyMapper();
+ const Qt::KeyboardModifiers keyModifiers = keyMapper->queryKeyboardModifiers();
QWindow *currentWindowUnderPointer = getWindowUnderPointer(window, globalPos);
if (et == QtWindows::MouseWheelEvent)
@@ -774,7 +778,7 @@ bool QWindowsPointerHandler::translateMouseEvent(QWindow *window,
// X11 and macOS.
bool discardEvent = false;
if (msg.message == WM_MOUSEMOVE) {
- static QPoint lastMouseMovePos;
+ Q_CONSTINIT static QPoint lastMouseMovePos;
if (msg.wParam == 0 && (m_windowUnderPointer.isNull() || globalPos == lastMouseMovePos))
discardEvent = true;
lastMouseMovePos = globalPos;
@@ -798,8 +802,8 @@ bool QWindowsPointerHandler::translateMouseEvent(QWindow *window,
break;
case QT_PT_PEN:
#if QT_CONFIG(tabletevent)
- if (!m_activeTabletDevice.isNull())
- device = m_activeTabletDevice.data();
+ qCDebug(lcQpaTablet) << "ignoring synth-mouse event for tablet event from" << device;
+ return false;
#endif
break;
}
@@ -822,19 +826,16 @@ bool QWindowsPointerHandler::translateMouseEvent(QWindow *window,
if (m_lastEventType == QEvent::NonClientAreaMouseButtonPress
&& (mouseEvent.type == QEvent::NonClientAreaMouseMove || mouseEvent.type == QEvent::MouseMove)
&& (m_lastEventButton & mouseButtons) == 0) {
- if (mouseEvent.type == QEvent::NonClientAreaMouseMove) {
- QWindowSystemInterface::handleFrameStrutMouseEvent(window, device, localPos, globalPos, mouseButtons, m_lastEventButton,
- QEvent::NonClientAreaMouseButtonRelease, keyModifiers, source);
- } else {
- QWindowSystemInterface::handleMouseEvent(window, device, localPos, globalPos, mouseButtons, m_lastEventButton,
- QEvent::MouseButtonRelease, keyModifiers, source);
- }
+ auto releaseType = mouseEvent.type == QEvent::NonClientAreaMouseMove ?
+ QEvent::NonClientAreaMouseButtonRelease : QEvent::MouseButtonRelease;
+ QWindowSystemInterface::handleMouseEvent(window, msg.time, device, localPos, globalPos, mouseButtons, m_lastEventButton,
+ releaseType, keyModifiers, source);
}
m_lastEventType = mouseEvent.type;
m_lastEventButton = mouseEvent.button;
if (mouseEvent.type >= QEvent::NonClientAreaMouseMove && mouseEvent.type <= QEvent::NonClientAreaMouseButtonDblClick) {
- QWindowSystemInterface::handleFrameStrutMouseEvent(window, device, localPos, globalPos, mouseButtons,
+ QWindowSystemInterface::handleMouseEvent(window, msg.time, device, localPos, globalPos, mouseButtons,
mouseEvent.button, mouseEvent.type, keyModifiers, source);
return false; // Allow further event processing
}
@@ -854,7 +855,7 @@ bool QWindowsPointerHandler::translateMouseEvent(QWindow *window,
handleEnterLeave(window, currentWindowUnderPointer, globalPos);
if (!discardEvent && mouseEvent.type != QEvent::None) {
- QWindowSystemInterface::handleMouseEvent(window, device, localPos, globalPos, mouseButtons,
+ QWindowSystemInterface::handleMouseEvent(window, msg.time, device, localPos, globalPos, mouseButtons,
mouseEvent.button, mouseEvent.type, keyModifiers, source);
}
diff --git a/src/plugins/platforms/windows/qwindowsscreen.cpp b/src/plugins/platforms/windows/qwindowsscreen.cpp
index 6f0b949b67..a50f9fd4b0 100644
--- a/src/plugins/platforms/windows/qwindowsscreen.cpp
+++ b/src/plugins/platforms/windows/qwindowsscreen.cpp
@@ -14,12 +14,22 @@
#include <QtGui/qpixmap.h>
#include <QtGui/qguiapplication.h>
#include <qpa/qwindowsysteminterface.h>
+#include <QtCore/private/qsystemerror_p.h>
+#include <QtGui/private/qedidparser_p.h>
#include <private/qhighdpiscaling_p.h>
#include <private/qwindowsfontdatabasebase_p.h>
+#include <private/qpixmap_win_p.h>
+#include <private/quniquehandle_p.h>
+
#include <QtGui/qscreen.h>
#include <QtCore/qdebug.h>
+#include <memory>
+#include <type_traits>
+
+#include <cfgmgr32.h>
+#include <setupapi.h>
#include <shellscalingapi.h>
QT_BEGIN_NAMESPACE
@@ -40,7 +50,7 @@ static inline QDpi monitorDPI(HMONITOR hMonitor)
return {0, 0};
}
-static bool getPathInfo(const MONITORINFOEX &viewInfo, DISPLAYCONFIG_PATH_INFO *pathInfo)
+static std::vector<DISPLAYCONFIG_PATH_INFO> getPathInfo(const MONITORINFOEX &viewInfo)
{
// We might want to consider storing adapterId/id from DISPLAYCONFIG_PATH_TARGET_INFO.
std::vector<DISPLAYCONFIG_PATH_INFO> pathInfos;
@@ -56,7 +66,7 @@ static bool getPathInfo(const MONITORINFOEX &viewInfo, DISPLAYCONFIG_PATH_INFO *
// look up the needed buffer sizes.
if (GetDisplayConfigBufferSizes(QDC_ONLY_ACTIVE_PATHS, &numPathArrayElements,
&numModeInfoArrayElements) != ERROR_SUCCESS) {
- return false;
+ return {};
}
pathInfos.resize(numPathArrayElements);
modeInfos.resize(numModeInfoArrayElements);
@@ -65,24 +75,25 @@ static bool getPathInfo(const MONITORINFOEX &viewInfo, DISPLAYCONFIG_PATH_INFO *
} while (result == ERROR_INSUFFICIENT_BUFFER);
if (result != ERROR_SUCCESS)
- return false;
-
- // Find path matching monitor name
- for (uint32_t p = 0; p < numPathArrayElements; p++) {
- DISPLAYCONFIG_SOURCE_DEVICE_NAME deviceName;
- deviceName.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_NAME;
- deviceName.header.size = sizeof(DISPLAYCONFIG_SOURCE_DEVICE_NAME);
- deviceName.header.adapterId = pathInfos[p].sourceInfo.adapterId;
- deviceName.header.id = pathInfos[p].sourceInfo.id;
- if (DisplayConfigGetDeviceInfo(&deviceName.header) == ERROR_SUCCESS) {
- if (wcscmp(viewInfo.szDevice, deviceName.viewGdiDeviceName) == 0) {
- *pathInfo = pathInfos[p];
+ return {};
+
+ // Find paths matching monitor name
+ auto discardThese =
+ std::remove_if(pathInfos.begin(), pathInfos.end(), [&](const auto &path) -> bool {
+ DISPLAYCONFIG_SOURCE_DEVICE_NAME deviceName;
+ deviceName.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_NAME;
+ deviceName.header.size = sizeof(DISPLAYCONFIG_SOURCE_DEVICE_NAME);
+ deviceName.header.adapterId = path.sourceInfo.adapterId;
+ deviceName.header.id = path.sourceInfo.id;
+ if (DisplayConfigGetDeviceInfo(&deviceName.header) == ERROR_SUCCESS) {
+ return wcscmp(viewInfo.szDevice, deviceName.viewGdiDeviceName) != 0;
+ }
return true;
- }
- }
- }
+ });
+
+ pathInfos.erase(discardThese, pathInfos.end());
- return false;
+ return pathInfos;
}
#if 0
@@ -106,6 +117,149 @@ static float getMonitorSDRWhiteLevel(DISPLAYCONFIG_PATH_TARGET_INFO *targetInfo)
using WindowsScreenDataList = QList<QWindowsScreenData>;
+namespace {
+
+struct DiRegKeyHandleTraits
+{
+ using Type = HKEY;
+ static Type invalidValue()
+ {
+ // The setupapi.h functions return INVALID_HANDLE_VALUE when failing to open a registry key
+ return reinterpret_cast<HKEY>(INVALID_HANDLE_VALUE);
+ }
+ static bool close(Type handle) { return RegCloseKey(handle) == ERROR_SUCCESS; }
+};
+
+using DiRegKeyHandle = QUniqueHandle<DiRegKeyHandleTraits>;
+
+}
+
+static void setMonitorDataFromSetupApi(QWindowsScreenData &data,
+ const std::vector<DISPLAYCONFIG_PATH_INFO> &pathGroup)
+{
+ if (pathGroup.empty()) {
+ return;
+ }
+
+ // The only property shared among monitors in a clone group is deviceName
+ {
+ DISPLAYCONFIG_TARGET_DEVICE_NAME deviceName = {};
+ deviceName.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_NAME;
+ deviceName.header.size = sizeof(DISPLAYCONFIG_TARGET_DEVICE_NAME);
+ // The first element in the clone group is the main monitor.
+ deviceName.header.adapterId = pathGroup[0].targetInfo.adapterId;
+ deviceName.header.id = pathGroup[0].targetInfo.id;
+ if (DisplayConfigGetDeviceInfo(&deviceName.header) == ERROR_SUCCESS) {
+ data.devicePath = QString::fromWCharArray(deviceName.monitorDevicePath);
+ } else {
+ qCWarning(lcQpaScreen)
+ << u"Unable to get device information for %1:"_s.arg(pathGroup[0].targetInfo.id)
+ << QSystemError::windowsString();
+ }
+ }
+
+ // The rest must be concatenated into the resulting property
+ QStringList names;
+ QStringList manufacturers;
+ QStringList models;
+ QStringList serialNumbers;
+
+ for (const auto &path : pathGroup) {
+ DISPLAYCONFIG_TARGET_DEVICE_NAME deviceName = {};
+ deviceName.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_NAME;
+ deviceName.header.size = sizeof(DISPLAYCONFIG_TARGET_DEVICE_NAME);
+ deviceName.header.adapterId = path.targetInfo.adapterId;
+ deviceName.header.id = path.targetInfo.id;
+ if (DisplayConfigGetDeviceInfo(&deviceName.header) != ERROR_SUCCESS) {
+ qCWarning(lcQpaScreen)
+ << u"Unable to get device information for %1:"_s.arg(path.targetInfo.id)
+ << QSystemError::windowsString();
+ continue;
+ }
+
+ // https://learn.microsoft.com/en-us/windows-hardware/drivers/install/guid-devinterface-monitor
+ constexpr GUID GUID_DEVINTERFACE_MONITOR = {
+ 0xe6f07b5f, 0xee97, 0x4a90, { 0xb0, 0x76, 0x33, 0xf5, 0x7b, 0xf4, 0xea, 0xa7 }
+ };
+ const HDEVINFO devInfo = SetupDiGetClassDevs(&GUID_DEVINTERFACE_MONITOR, nullptr, nullptr,
+ DIGCF_DEVICEINTERFACE);
+
+ SP_DEVICE_INTERFACE_DATA deviceInterfaceData{};
+ deviceInterfaceData.cbSize = sizeof(deviceInterfaceData);
+
+ if (!SetupDiOpenDeviceInterfaceW(devInfo, deviceName.monitorDevicePath, DIODI_NO_ADD,
+ &deviceInterfaceData)) {
+ qCWarning(lcQpaScreen)
+ << u"Unable to open monitor interface to %1:"_s.arg(data.deviceName)
+ << QSystemError::windowsString();
+ continue;
+ }
+
+ DWORD requiredSize{ 0 };
+ if (SetupDiGetDeviceInterfaceDetailW(devInfo, &deviceInterfaceData, nullptr, 0,
+ &requiredSize, nullptr)
+ || GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
+ continue;
+ }
+
+ const std::unique_ptr<std::byte[]> storage(new std::byte[requiredSize]);
+ auto *devicePath = reinterpret_cast<SP_DEVICE_INTERFACE_DETAIL_DATA_W *>(storage.get());
+ devicePath->cbSize = sizeof(std::remove_pointer_t<decltype(devicePath)>);
+ SP_DEVINFO_DATA deviceInfoData{};
+ deviceInfoData.cbSize = sizeof(deviceInfoData);
+ if (!SetupDiGetDeviceInterfaceDetailW(devInfo, &deviceInterfaceData, devicePath,
+ requiredSize, nullptr, &deviceInfoData)) {
+ qCDebug(lcQpaScreen) << u"Unable to get monitor metadata for %1:"_s.arg(data.deviceName)
+ << QSystemError::windowsString();
+ continue;
+ }
+
+ const DiRegKeyHandle edidRegistryKey{ SetupDiOpenDevRegKey(
+ devInfo, &deviceInfoData, DICS_FLAG_GLOBAL, 0, DIREG_DEV, KEY_READ) };
+
+ if (!edidRegistryKey.isValid())
+ continue;
+
+ DWORD edidDataSize{ 0 };
+ if (RegQueryValueExW(edidRegistryKey.get(), L"EDID", nullptr, nullptr, nullptr,
+ &edidDataSize)
+ != ERROR_SUCCESS) {
+ continue;
+ }
+
+ QByteArray edidData;
+ edidData.resize(edidDataSize);
+
+ if (RegQueryValueExW(edidRegistryKey.get(), L"EDID", nullptr, nullptr,
+ reinterpret_cast<unsigned char *>(edidData.data()), &edidDataSize)
+ != ERROR_SUCCESS) {
+ qCDebug(lcQpaScreen) << u"Unable to get EDID from the Registry for %1:"_s.arg(
+ data.deviceName)
+ << QSystemError::windowsString();
+ continue;
+ }
+
+ QEdidParser edid;
+
+ if (!edid.parse(edidData)) {
+ qCDebug(lcQpaScreen) << "Invalid EDID blob for" << data.deviceName;
+ continue;
+ }
+
+ // We skip edid.identifier because it is unreliable, and a better option
+ // is already available through DisplayConfigGetDeviceInfo (see below).
+ names << QString::fromWCharArray(deviceName.monitorFriendlyDeviceName);
+ manufacturers << edid.manufacturer;
+ models << edid.model;
+ serialNumbers << edid.serialNumber;
+ }
+
+ data.name = names.join(u"|"_s);
+ data.manufacturer = manufacturers.join(u"|"_s);
+ data.model = models.join(u"|"_s);
+ data.serialNumber = serialNumbers.join(u"|"_s);
+}
+
static bool monitorData(HMONITOR hMonitor, QWindowsScreenData *data)
{
MONITORINFOEX info;
@@ -118,16 +272,9 @@ static bool monitorData(HMONITOR hMonitor, QWindowsScreenData *data)
data->geometry = QRect(QPoint(info.rcMonitor.left, info.rcMonitor.top), QPoint(info.rcMonitor.right - 1, info.rcMonitor.bottom - 1));
data->availableGeometry = QRect(QPoint(info.rcWork.left, info.rcWork.top), QPoint(info.rcWork.right - 1, info.rcWork.bottom - 1));
data->deviceName = QString::fromWCharArray(info.szDevice);
- DISPLAYCONFIG_PATH_INFO pathInfo = {};
- const bool hasPathInfo = getPathInfo(info, &pathInfo);
- if (hasPathInfo) {
- DISPLAYCONFIG_TARGET_DEVICE_NAME deviceName = {};
- deviceName.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_NAME;
- deviceName.header.size = sizeof(DISPLAYCONFIG_TARGET_DEVICE_NAME);
- deviceName.header.adapterId = pathInfo.targetInfo.adapterId;
- deviceName.header.id = pathInfo.targetInfo.id;
- if (DisplayConfigGetDeviceInfo(&deviceName.header) == ERROR_SUCCESS)
- data->name = QString::fromWCharArray(deviceName.monitorFriendlyDeviceName);
+ const auto pathGroup = getPathInfo(info);
+ if (!pathGroup.empty()) {
+ setMonitorDataFromSetupApi(*data, pathGroup);
}
if (data->name.isEmpty())
data->name = data->deviceName;
@@ -153,7 +300,9 @@ static bool monitorData(HMONITOR hMonitor, QWindowsScreenData *data)
// ### We might want to consider storing adapterId/id from DISPLAYCONFIG_PATH_TARGET_INFO,
// if we are going to use DISPLAYCONFIG lookups more.
- if (hasPathInfo) {
+ if (!pathGroup.empty()) {
+ // The first element in the clone group is the main monitor.
+ const auto &pathInfo = pathGroup[0];
switch (pathInfo.targetInfo.rotation) {
case DISPLAYCONFIG_ROTATION_IDENTITY:
data->orientation = Qt::LandscapeOrientation;
@@ -182,11 +331,8 @@ static bool monitorData(HMONITOR hMonitor, QWindowsScreenData *data)
// EnumDisplayMonitors (as opposed to EnumDisplayDevices) enumerates only
// virtual desktop screens.
data->flags |= QWindowsScreenData::VirtualDesktop;
- if (info.dwFlags & MONITORINFOF_PRIMARY) {
+ if (info.dwFlags & MONITORINFOF_PRIMARY)
data->flags |= QWindowsScreenData::PrimaryScreen;
- if ((data->flags & QWindowsScreenData::LockScreen) == 0)
- QWindowsFontDatabase::setDefaultVerticalDPI(data->dpi.second);
- }
return true;
}
@@ -232,15 +378,14 @@ static QDebug operator<<(QDebug dbg, const QWindowsScreenData &d)
QDebugStateSaver saver(dbg);
dbg.nospace();
dbg.noquote();
- dbg << "Screen \"" << d.name << "\" "
- << d.geometry.width() << 'x' << d.geometry.height() << '+' << d.geometry.x() << '+' << d.geometry.y()
- << " avail: "
- << d.availableGeometry.width() << 'x' << d.availableGeometry.height() << '+' << d.availableGeometry.x() << '+' << d.availableGeometry.y()
- << " physical: " << d.physicalSizeMM.width() << 'x' << d.physicalSizeMM.height()
- << " DPI: " << d.dpi.first << 'x' << d.dpi.second << " Depth: " << d.depth
- << " Format: " << d.format
- << " hMonitor: " << d.hMonitor
- << " device name: " << d.deviceName;
+ dbg << "Screen \"" << d.name << "\" " << d.geometry.width() << 'x' << d.geometry.height() << '+'
+ << d.geometry.x() << '+' << d.geometry.y() << " avail: " << d.availableGeometry.width()
+ << 'x' << d.availableGeometry.height() << '+' << d.availableGeometry.x() << '+'
+ << d.availableGeometry.y() << " physical: " << d.physicalSizeMM.width() << 'x'
+ << d.physicalSizeMM.height() << " DPI: " << d.dpi.first << 'x' << d.dpi.second
+ << " Depth: " << d.depth << " Format: " << d.format << " hMonitor: " << d.hMonitor
+ << " device name: " << d.deviceName << " manufacturer: " << d.manufacturer
+ << " model: " << d.model << " serial number: " << d.serialNumber;
if (d.flags & QWindowsScreenData::PrimaryScreen)
dbg << " primary";
if (d.flags & QWindowsScreenData::VirtualDesktop)
@@ -273,8 +418,6 @@ QString QWindowsScreen::name() const
: m_data.name;
}
-Q_GUI_EXPORT QPixmap qt_pixmapFromWinHBITMAP(HBITMAP bitmap, int hbitmapFormat = 0);
-
QPixmap QWindowsScreen::grabWindow(WId window, int xIn, int yIn, int width, int height) const
{
QSize windowSize;
@@ -393,10 +536,14 @@ void QWindowsScreen::handleChanges(const QWindowsScreenData &newData)
const bool dpiChanged = !qFuzzyCompare(m_data.dpi.first, newData.dpi.first)
|| !qFuzzyCompare(m_data.dpi.second, newData.dpi.second);
const bool orientationChanged = m_data.orientation != newData.orientation;
+ const bool primaryChanged = (newData.flags & QWindowsScreenData::PrimaryScreen)
+ && !(m_data.flags & QWindowsScreenData::PrimaryScreen);
m_data.dpi = newData.dpi;
m_data.orientation = newData.orientation;
m_data.geometry = newData.geometry;
m_data.availableGeometry = newData.availableGeometry;
+ m_data.flags = (m_data.flags & ~QWindowsScreenData::PrimaryScreen)
+ | (newData.flags & QWindowsScreenData::PrimaryScreen);
if (dpiChanged) {
QWindowSystemInterface::handleScreenLogicalDotsPerInchChange(screen(),
@@ -409,6 +556,8 @@ void QWindowsScreen::handleChanges(const QWindowsScreenData &newData)
QWindowSystemInterface::handleScreenGeometryChange(screen(),
newData.geometry, newData.availableGeometry);
}
+ if (primaryChanged)
+ QWindowSystemInterface::handlePrimaryScreenChanged(this);
}
HMONITOR QWindowsScreen::handle() const
@@ -520,7 +669,8 @@ extern "C" LRESULT QT_WIN_CALLBACK qDisplayChangeObserverWndProc(HWND hwnd, UINT
if (QWindowsTheme *t = QWindowsTheme::instance())
t->displayChanged();
QWindowsWindow::displayChanged();
- QWindowsContext::instance()->screenManager().handleScreenChanges();
+ if (auto *context = QWindowsContext::instance())
+ context->screenManager().handleScreenChanges();
}
return DefWindowProc(hwnd, message, wParam, lParam);
diff --git a/src/plugins/platforms/windows/qwindowsscreen.h b/src/plugins/platforms/windows/qwindowsscreen.h
index b1c94d2204..0467ab2a0c 100644
--- a/src/plugins/platforms/windows/qwindowsscreen.h
+++ b/src/plugins/platforms/windows/qwindowsscreen.h
@@ -7,10 +7,9 @@
#include "qtwindowsglobal.h"
#include <QtCore/qlist.h>
-#include <QtCore/qpair.h>
#include <QtCore/qscopedpointer.h>
#include <qpa/qplatformscreen.h>
-#include <qpa/qplatformscreen_p.h>
+#include <QtGui/qscreen_platform.h>
QT_BEGIN_NAMESPACE
@@ -31,15 +30,18 @@ struct QWindowsScreenData
QImage::Format format = QImage::Format_ARGB32_Premultiplied;
unsigned flags = VirtualDesktop;
QString name;
+ QString manufacturer;
+ QString model;
+ QString serialNumber;
Qt::ScreenOrientation orientation = Qt::LandscapeOrientation;
qreal refreshRateHz = 60;
HMONITOR hMonitor = nullptr;
- QString deviceName = {};
+ QString deviceName;
+ QString devicePath;
std::optional<int> deviceIndex = std::nullopt;
};
-class QWindowsScreen : public QPlatformScreen
- , public QNativeInterface::Private::QWindowsScreen
+class QWindowsScreen : public QPlatformScreen, public QNativeInterface::QWindowsScreen
{
public:
#ifndef QT_NO_CURSOR
@@ -58,6 +60,9 @@ public:
qreal devicePixelRatio() const override { return 1.0; }
qreal refreshRate() const override { return m_data.refreshRateHz; }
QString name() const override;
+ QString manufacturer() const override { return m_data.manufacturer; }
+ QString model() const override { return m_data.model; }
+ QString serialNumber() const override { return m_data.serialNumber; }
Qt::ScreenOrientation orientation() const override { return m_data.orientation; }
QList<QPlatformScreen *> virtualSiblings() const override;
QWindow *topLevelAt(const QPoint &point) const override;
diff --git a/src/plugins/platforms/windows/qwindowsservices.cpp b/src/plugins/platforms/windows/qwindowsservices.cpp
index fbad934a05..89f93fd161 100644
--- a/src/plugins/platforms/windows/qwindowsservices.cpp
+++ b/src/plugins/platforms/windows/qwindowsservices.cpp
@@ -1,7 +1,6 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
-#define QT_NO_URL_CAST_FROM_STRING
#include "qwindowsservices.h"
#include <QtCore/qt_windows.h>
@@ -12,8 +11,10 @@
#include <QtCore/qthread.h>
#include <QtCore/private/qwinregistry_p.h>
+#include <QtCore/private/qfunctions_win_p.h>
#include <shlobj.h>
+#include <shlwapi.h>
#include <intshcut.h>
QT_BEGIN_NAMESPACE
@@ -25,23 +26,73 @@ enum { debug = 0 };
class QWindowsShellExecuteThread : public QThread
{
public:
- explicit QWindowsShellExecuteThread(const wchar_t *path) : m_path(path) { }
+ explicit QWindowsShellExecuteThread(const wchar_t *operation, const wchar_t *file,
+ const wchar_t *parameters)
+ : m_operation(operation)
+ , m_file(file)
+ , m_parameters(parameters) { }
void run() override
{
- if (SUCCEEDED(CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE))) {
- m_result = ShellExecute(nullptr, nullptr, m_path, nullptr, nullptr, SW_SHOWNORMAL);
- CoUninitialize();
- }
+ QComHelper comHelper;
+ if (comHelper.isValid())
+ m_result = ShellExecute(nullptr, m_operation, m_file, m_parameters, nullptr,
+ SW_SHOWNORMAL);
}
HINSTANCE result() const { return m_result; }
private:
HINSTANCE m_result = nullptr;
- const wchar_t *m_path;
+ const wchar_t *m_operation;
+ const wchar_t *m_file;
+ const wchar_t *m_parameters;
};
+static QString msgShellExecuteFailed(const QUrl &url, quintptr code)
+{
+ QString result;
+ QTextStream(&result) <<"ShellExecute '" << url.toString() << "' failed (error " << code << ").";
+ return result;
+}
+
+// Retrieve the web browser and open the URL. This should be used for URLs with
+// fragments which don't work when using ShellExecute() directly (QTBUG-14460,
+// QTBUG-55300).
+static bool openWebBrowser(const QUrl &url)
+{
+ WCHAR browserExecutable[MAX_PATH] = {};
+ const wchar_t operation[] = L"open";
+ DWORD browserExecutableSize = MAX_PATH;
+ if (FAILED(AssocQueryString(0, ASSOCSTR_EXECUTABLE, L"http", operation,
+ browserExecutable, &browserExecutableSize))) {
+ return false;
+ }
+ QString browser = QString::fromWCharArray(browserExecutable, browserExecutableSize - 1);
+ // Workaround for "old" MS Edge entries. Instead of LaunchWinApp.exe we can just use msedge.exe
+ if (browser.contains("LaunchWinApp.exe"_L1, Qt::CaseInsensitive))
+ browser = "msedge.exe"_L1;
+ const QString urlS = url.toString(QUrl::FullyEncoded);
+
+ // Run ShellExecute() in a thread since it may spin the event loop.
+ // Prevent it from interfering with processing of posted events (QTBUG-85676).
+ QWindowsShellExecuteThread thread(operation,
+ reinterpret_cast<const wchar_t *>(browser.utf16()),
+ reinterpret_cast<const wchar_t *>(urlS.utf16()));
+ thread.start();
+ thread.wait();
+
+ const auto result = reinterpret_cast<quintptr>(thread.result());
+ if (debug)
+ qDebug() << __FUNCTION__ << urlS << QString::fromWCharArray(browserExecutable) << result;
+ // ShellExecute returns a value greater than 32 if successful
+ if (result <= 32) {
+ qWarning("%s", qPrintable(msgShellExecuteFailed(url, result)));
+ return false;
+ }
+ return true;
+}
+
static inline bool shellExecute(const QUrl &url)
{
const QString nativeFilePath = url.isLocalFile() && !url.hasFragment() && !url.hasQuery()
@@ -51,7 +102,9 @@ static inline bool shellExecute(const QUrl &url)
// Run ShellExecute() in a thread since it may spin the event loop.
// Prevent it from interfering with processing of posted events (QTBUG-85676).
- QWindowsShellExecuteThread thread(reinterpret_cast<const wchar_t *>(nativeFilePath.utf16()));
+ QWindowsShellExecuteThread thread(nullptr,
+ reinterpret_cast<const wchar_t *>(nativeFilePath.utf16()),
+ nullptr);
thread.start();
thread.wait();
@@ -59,7 +112,7 @@ static inline bool shellExecute(const QUrl &url)
// ShellExecute returns a value greater than 32 if successful
if (result <= 32) {
- qWarning("ShellExecute '%ls' failed (error %zu).", qUtf16Printable(url.toString()), result);
+ qWarning("%s", qPrintable(msgShellExecuteFailed(url, result)));
return false;
}
return true;
@@ -101,10 +154,15 @@ static inline bool launchMail(const QUrl &url)
qWarning("Cannot launch '%ls': There is no mail program installed.", qUtf16Printable(url.toString()));
return false;
}
+ // Fix mail launch if no param is expected in this command.
+ if (command.indexOf("%1"_L1) < 0) {
+ qWarning() << "The mail command lacks the '%1' parameter.";
+ return false;
+ }
//Make sure the path for the process is in quotes
const QChar doubleQuote = u'"';
if (!command.startsWith(doubleQuote)) {
- const int exeIndex = command.indexOf(QStringLiteral(".exe "), 0, Qt::CaseInsensitive);
+ const int exeIndex = command.indexOf(".exe "_L1, 0, Qt::CaseInsensitive);
if (exeIndex != -1) {
command.insert(exeIndex + 4, doubleQuote);
command.prepend(doubleQuote);
@@ -136,7 +194,8 @@ bool QWindowsServices::openUrl(const QUrl &url)
const QString scheme = url.scheme();
if (scheme == u"mailto" && launchMail(url))
return true;
- return shellExecute(url);
+ return url.isLocalFile() && url.hasFragment()
+ ? openWebBrowser(url) : shellExecute(url);
}
bool QWindowsServices::openDocument(const QUrl &url)
diff --git a/src/plugins/platforms/windows/qwindowssystemtrayicon.cpp b/src/plugins/platforms/windows/qwindowssystemtrayicon.cpp
index cf150b5772..6f0680ac23 100644
--- a/src/plugins/platforms/windows/qwindowssystemtrayicon.cpp
+++ b/src/plugins/platforms/windows/qwindowssystemtrayicon.cpp
@@ -1,13 +1,7 @@
// Copyright (C) 2017 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
-#ifndef WINVER
-# define WINVER 0x0A00 // required for NOTIFYICONDATA_V2_SIZE, ChangeWindowMessageFilterEx() (MinGW 5.3)
-#endif
-
-#ifndef NTDDI_VERSION
-# define NTDDI_VERSION 0x0A00000B // required for Shell_NotifyIconGetRect (MinGW 5.3)
-#endif
+#include <QtCore/qt_windows.h>
#include "qwindowssystemtrayicon.h"
#include "qwindowscontext.h"
@@ -23,7 +17,6 @@
#include <QtCore/qsettings.h>
#include <qpa/qwindowsysteminterface.h>
-#include <qt_windows.h>
#include <commctrl.h>
#include <shellapi.h>
#include <shlobj.h>
@@ -31,6 +24,8 @@
QT_BEGIN_NAMESPACE
+using namespace Qt::Literals::StringLiterals;
+
static const UINT q_uNOTIFYICONID = 0;
static uint MYWM_TASKBARCREATED = 0;
@@ -123,7 +118,7 @@ static inline HWND createTrayIconMessageWindow()
return nullptr;
// Register window class in the platform plugin.
const QString className =
- ctx->registerWindowClass(QWindowsContext::classNamePrefix() + QStringLiteral("TrayIconMessageWindowClass"),
+ ctx->registerWindowClass(QWindowsContext::classNamePrefix() + "TrayIconMessageWindowClass"_L1,
qWindowsTrayIconWndProc);
const wchar_t windowName[] = L"QTrayIconMessageWindow";
return CreateWindowEx(0, reinterpret_cast<const wchar_t *>(className.utf16()),
@@ -169,8 +164,7 @@ void QWindowsSystemTrayIcon::cleanup()
void QWindowsSystemTrayIcon::updateIcon(const QIcon &icon)
{
qCDebug(lcQpaTrayIcon) << __FUNCTION__ << '(' << icon << ')' << this;
- if (icon.cacheKey() == m_icon.cacheKey())
- return;
+ m_icon = icon;
const HICON hIconToDestroy = createIcon(icon);
if (ensureInstalled())
sendTrayMessage(NIM_MODIFY);
@@ -190,6 +184,9 @@ void QWindowsSystemTrayIcon::updateToolTip(const QString &tooltip)
QRect QWindowsSystemTrayIcon::geometry() const
{
+ if (!isIconVisible())
+ return QRect();
+
NOTIFYICONIDENTIFIER nid;
memset(&nid, 0, sizeof(nid));
nid.cbSize = sizeof(nid);
@@ -223,25 +220,19 @@ void QWindowsSystemTrayIcon::showMessage(const QString &title, const QString &me
qStringToLimitedWCharArray(title, tnd.szInfoTitle, 64);
tnd.uID = q_uNOTIFYICONID;
- tnd.dwInfoFlags = NIIF_USER;
-
- QSize size(GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON));
- const QSize largeIcon(GetSystemMetrics(SM_CXICON), GetSystemMetrics(SM_CYICON));
- const QSize more = icon.actualSize(largeIcon);
- if (more.height() > (largeIcon.height() * 3/4) || more.width() > (largeIcon.width() * 3/4)) {
- tnd.dwInfoFlags |= NIIF_LARGE_ICON;
- size = largeIcon;
- }
+
+ const auto size = icon.actualSize(QSize(256, 256));
QPixmap pm = icon.pixmap(size);
+ if (m_hMessageIcon) {
+ DestroyIcon(m_hMessageIcon);
+ m_hMessageIcon = nullptr;
+ }
if (pm.isNull()) {
tnd.dwInfoFlags = NIIF_INFO;
} else {
- if (pm.size() != size) {
- qWarning("QSystemTrayIcon::showMessage: Wrong icon size (%dx%d), please add standard one: %dx%d",
- pm.size().width(), pm.size().height(), size.width(), size.height());
- pm = pm.scaled(size, Qt::IgnoreAspectRatio);
- }
- tnd.hBalloonIcon = qt_pixmapToWinHICON(pm);
+ tnd.dwInfoFlags = NIIF_USER | NIIF_LARGE_ICON;
+ m_hMessageIcon = qt_pixmapToWinHICON(pm);
+ tnd.hBalloonIcon = m_hMessageIcon;
}
tnd.hWnd = m_hwnd;
tnd.uTimeout = msecsIn <= 0 ? UINT(10000) : UINT(msecsIn); // 10s default
@@ -299,7 +290,10 @@ void QWindowsSystemTrayIcon::ensureCleanup()
}
if (m_hIcon != nullptr)
DestroyIcon(m_hIcon);
+ if (m_hMessageIcon != nullptr)
+ DestroyIcon(m_hMessageIcon);
m_hIcon = nullptr;
+ m_hMessageIcon = nullptr;
m_menu = nullptr; // externally owned
m_toolTip.clear();
}
@@ -316,6 +310,29 @@ bool QWindowsSystemTrayIcon::setIconVisible(bool visible)
return Shell_NotifyIcon(NIM_MODIFY, &tnd) == TRUE;
}
+bool QWindowsSystemTrayIcon::isIconVisible() const
+{
+ NOTIFYICONIDENTIFIER nid;
+ memset(&nid, 0, sizeof(nid));
+ nid.cbSize = sizeof(nid);
+ nid.hWnd = m_hwnd;
+ nid.uID = q_uNOTIFYICONID;
+ RECT rect;
+ const HRESULT hr = Shell_NotifyIconGetRect(&nid, &rect);
+ // Windows 10 returns S_FALSE if the icon is hidden
+ if (FAILED(hr) || hr == S_FALSE)
+ return false;
+
+ HMONITOR monitor = MonitorFromWindow(m_hwnd, MONITOR_DEFAULTTONEAREST);
+ MONITORINFO info;
+ info.cbSize = sizeof(MONITORINFO);
+ GetMonitorInfo(monitor, &info);
+ // Windows 11 seems to return a geometry outside of the current monitor's geometry in case of
+ // the icon being hidden. As it's impossible to change the alignment of the task bar on Windows
+ // 11 this check should be fine.
+ return rect.bottom <= info.rcMonitor.bottom;
+}
+
bool QWindowsSystemTrayIcon::sendTrayMessage(DWORD msg)
{
NOTIFYICONDATA tnd;
@@ -409,8 +426,15 @@ bool QWindowsSystemTrayIcon::winEvent(const MSG &message, long *result)
QWindowsPopupMenu::notifyTriggered(LOWORD(message.wParam));
break;
default:
- if (message.message == MYWM_TASKBARCREATED) // self-registered message id (tray crashed)
+ if (message.message == MYWM_TASKBARCREATED) {
+ // self-registered message id to handle that
+ // - screen resolution/DPR changed
+ const QIcon oldIcon = m_icon;
+ m_icon = QIcon(); // updateIcon is a no-op if the icon doesn't change
+ updateIcon(oldIcon);
+ // - or tray crashed
sendTrayMessage(NIM_ADD);
+ }
break;
}
return false;
diff --git a/src/plugins/platforms/windows/qwindowssystemtrayicon.h b/src/plugins/platforms/windows/qwindowssystemtrayicon.h
index 2545ae9101..3ad5feb125 100644
--- a/src/plugins/platforms/windows/qwindowssystemtrayicon.h
+++ b/src/plugins/platforms/windows/qwindowssystemtrayicon.h
@@ -49,12 +49,14 @@ private:
void ensureCleanup();
bool sendTrayMessage(DWORD msg);
bool setIconVisible(bool visible);
+ bool isIconVisible() const;
HICON createIcon(const QIcon &icon);
QIcon m_icon;
QString m_toolTip;
HWND m_hwnd = nullptr;
HICON m_hIcon = nullptr;
+ HICON m_hMessageIcon = nullptr;
mutable QPointer<QWindowsPopupMenu> m_menu;
bool m_ignoreNextMouseRelease = false;
bool m_visible = false;
diff --git a/src/plugins/platforms/windows/qwindowstabletsupport.cpp b/src/plugins/platforms/windows/qwindowstabletsupport.cpp
index b7cc2bf77b..ceebb483d2 100644
--- a/src/plugins/platforms/windows/qwindowstabletsupport.cpp
+++ b/src/plugins/platforms/windows/qwindowstabletsupport.cpp
@@ -595,7 +595,8 @@ bool QWindowsTabletSupport::translateTabletPacketEvent()
<< "mode=" << m_mode;
}
- const Qt::KeyboardModifiers keyboardModifiers = QWindowsKeyMapper::queryKeyboardModifiers();
+ const auto *keyMapper = QWindowsContext::instance()->keyMapper();
+ const Qt::KeyboardModifiers keyboardModifiers = keyMapper->queryKeyboardModifiers();
for (int i = 0; i < packetCount ; ++i) {
const PACKET &packet = localPacketBuf[i];
diff --git a/src/plugins/platforms/windows/qwindowstheme.cpp b/src/plugins/platforms/windows/qwindowstheme.cpp
index ef7caa60d9..b0b6672487 100644
--- a/src/plugins/platforms/windows/qwindowstheme.cpp
+++ b/src/plugins/platforms/windows/qwindowstheme.cpp
@@ -1,37 +1,34 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
-// SHSTOCKICONINFO is only available since Vista
-#ifndef _WIN32_WINNT
-# define _WIN32_WINNT 0x0A00
-#endif
+#include <QtCore/qt_windows.h>
#include "qwindowstheme.h"
#include "qwindowsmenu.h"
#include "qwindowsdialoghelpers.h"
#include "qwindowscontext.h"
+#include "qwindowsiconengine.h"
#include "qwindowsintegration.h"
#if QT_CONFIG(systemtrayicon)
# include "qwindowssystemtrayicon.h"
#endif
#include "qwindowsscreen.h"
-#include "qt_windows.h"
#include <commctrl.h>
#include <objbase.h>
-#ifndef Q_CC_MINGW
-# include <commoncontrols.h>
-#endif
+#include <commoncontrols.h>
#include <shellapi.h>
+#include <QtCore/qapplicationstatic.h>
#include <QtCore/qvariant.h>
#include <QtCore/qcoreapplication.h>
#include <QtCore/qdebug.h>
-#include <QtCore/qtextstream.h>
#include <QtCore/qsysinfo.h>
#include <QtCore/qcache.h>
#include <QtCore/qthread.h>
+#include <QtCore/qqueue.h>
#include <QtCore/qmutex.h>
#include <QtCore/qwaitcondition.h>
+#include <QtCore/qoperatingsystemversion.h>
#include <QtGui/qcolor.h>
#include <QtGui/qpalette.h>
#include <QtGui/qguiapplication.h>
@@ -43,46 +40,20 @@
#include <private/qhighdpiscaling_p.h>
#include <private/qsystemlibrary_p.h>
#include <private/qwinregistry_p.h>
+#include <QtCore/private/qfunctions_win_p.h>
#include <algorithm>
-#if QT_CONFIG(cpp_winrt) && !defined(Q_CC_CLANG)
-# include <winrt/base.h>
-// Workaround for Windows SDK bug.
-// See https://github.com/microsoft/Windows.UI.Composition-Win32-Samples/issues/47
-namespace winrt::impl
-{
- template <typename Async>
- auto wait_for(Async const& async, Windows::Foundation::TimeSpan const& timeout);
-}
-# include <winrt/Windows.UI.ViewManagement.h>
-# define HAS_UISETTINGS 1
-#endif
+#if QT_CONFIG(cpp_winrt)
+# include <QtCore/private/qt_winrtbase_p.h>
-#if defined(__IImageList_INTERFACE_DEFINED__) && defined(__IID_DEFINED__)
-# define USE_IIMAGELIST
-#endif
+# include <winrt/Windows.UI.ViewManagement.h>
+#endif // QT_CONFIG(cpp_winrt)
QT_BEGIN_NAMESPACE
using namespace Qt::StringLiterals;
-static inline QColor COLORREFToQColor(COLORREF cr)
-{
- return QColor(GetRValue(cr), GetGValue(cr), GetBValue(cr));
-}
-
-static inline QTextStream& operator<<(QTextStream &str, const QColor &c)
-{
- str.setIntegerBase(16);
- str.setFieldWidth(2);
- str.setPadChar(u'0');
- str << " rgb: #" << c.red() << c.green() << c.blue();
- str.setIntegerBase(10);
- str.setFieldWidth(0);
- return str;
-}
-
static inline bool booleanSystemParametersInfo(UINT what, bool defaultValue)
{
BOOL result;
@@ -106,127 +77,159 @@ static inline QColor mixColors(const QColor &c1, const QColor &c2)
(c1.blue() + c2.blue()) / 2};
}
-static inline QColor getSysColor(int index)
-{
- return COLORREFToQColor(GetSysColor(index));
-}
+enum AccentColorLevel {
+ AccentColorDarkest,
+ AccentColorNormal,
+ AccentColorLightest
+};
-#if defined(HAS_UISETTINGS)
+#if QT_CONFIG(cpp_winrt)
static constexpr QColor getSysColor(winrt::Windows::UI::Color &&color)
{
return QColor(color.R, color.G, color.B, color.A);
}
#endif
-// QTBUG-48823/Windows 10: SHGetFileInfo() (as called by item views on file system
-// models has been observed to trigger a WM_PAINT on the mainwindow. Suppress the
-// behavior by running it in a thread.
+[[maybe_unused]] [[nodiscard]] static inline QColor qt_accentColor(AccentColorLevel level)
+{
+#if QT_CONFIG(cpp_winrt)
+ using namespace winrt::Windows::UI::ViewManagement;
+ const auto settings = UISettings();
+ const QColor accent = getSysColor(settings.GetColorValue(UIColorType::Accent));
+ const QColor accentLight = getSysColor(settings.GetColorValue(UIColorType::AccentLight1));
+ const QColor accentDarkest = getSysColor(settings.GetColorValue(UIColorType::AccentDark3));
+#else
+ const QWinRegistryKey registry(HKEY_CURRENT_USER, LR"(Software\Microsoft\Windows\DWM)");
+ if (!registry.isValid())
+ return {};
+ const QVariant value = registry.value(L"AccentColor");
+ if (!value.isValid())
+ return {};
+ // The retrieved value is in the #AABBGGRR format, we need to
+ // convert it to the #AARRGGBB format which Qt expects.
+ const QColor abgr = QColor::fromRgba(qvariant_cast<DWORD>(value));
+ if (!abgr.isValid())
+ return {};
+ const QColor accent = QColor::fromRgb(abgr.blue(), abgr.green(), abgr.red(), abgr.alpha());
+ const QColor accentLight = accent.lighter(120);
+ const QColor accentDarkest = accent.darker(120 * 120 * 120);
+#endif
+ if (level == AccentColorDarkest)
+ return accentDarkest;
+ else if (level == AccentColorLightest)
+ return accentLight;
+ return accent;
+}
-struct QShGetFileInfoParams
+static inline QColor getSysColor(int index)
{
- QShGetFileInfoParams(const QString &fn, DWORD a, SHFILEINFO *i, UINT f, bool *r)
- : fileName(fn), attributes(a), flags(f), info(i), result(r)
- { }
-
- const QString &fileName;
- const DWORD attributes;
- const UINT flags;
- SHFILEINFO *const info;
- bool *const result;
-};
+ COLORREF cr = GetSysColor(index);
+ return QColor(GetRValue(cr), GetGValue(cr), GetBValue(cr));
+}
+// QTBUG-48823/Windows 10: SHGetFileInfo() (as called by item views on file system
+// models has been observed to trigger a WM_PAINT on the mainwindow. Suppress the
+// behavior by running it in a thread.
class QShGetFileInfoThread : public QThread
{
public:
- explicit QShGetFileInfoThread()
- : QThread(), m_params(nullptr)
+ struct Task
{
- connect(this, &QThread::finished, this, &QObject::deleteLater);
+ Task(const QString &fn, DWORD a, UINT f)
+ : fileName(fn), attributes(a), flags(f)
+ {}
+ Q_DISABLE_COPY(Task)
+ ~Task()
+ {
+ DestroyIcon(hIcon);
+ hIcon = 0;
+ }
+ // Request
+ const QString fileName;
+ const DWORD attributes;
+ const UINT flags;
+ // Result
+ HICON hIcon = 0;
+ int iIcon = -1;
+ bool finished = false;
+ bool resultValid() const { return hIcon != 0 && iIcon >= 0 && finished; }
+ };
+
+ QShGetFileInfoThread()
+ : QThread()
+ {
+ start();
}
- void run() override
+ ~QShGetFileInfoThread()
+ {
+ cancel();
+ wait();
+ }
+
+ QSharedPointer<Task> getNextTask()
{
- m_init = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
+ QMutexLocker l(&m_waitForTaskMutex);
+ while (!isInterruptionRequested()) {
+ if (!m_taskQueue.isEmpty())
+ return m_taskQueue.dequeue();
+ m_waitForTaskCondition.wait(&m_waitForTaskMutex);
+ }
+ return nullptr;
+ }
- QMutexLocker readyLocker(&m_readyMutex);
- while (!m_cancelled.loadRelaxed()) {
- if (!m_params && !m_cancelled.loadRelaxed()
- && !m_readyCondition.wait(&m_readyMutex, QDeadlineTimer(1000ll)))
- continue;
+ void run() override
+ {
+ QComHelper comHelper(COINIT_MULTITHREADED);
- if (m_params) {
- const QString fileName = m_params->fileName;
+ while (!isInterruptionRequested()) {
+ auto task = getNextTask();
+ if (task) {
SHFILEINFO info;
- const bool result = SHGetFileInfo(reinterpret_cast<const wchar_t *>(fileName.utf16()),
- m_params->attributes, &info, sizeof(SHFILEINFO),
- m_params->flags);
- m_doneMutex.lock();
- if (!m_cancelled.loadRelaxed()) {
- *m_params->result = result;
- memcpy(m_params->info, &info, sizeof(SHFILEINFO));
+ const bool result = SHGetFileInfo(reinterpret_cast<const wchar_t *>(task->fileName.utf16()),
+ task->attributes, &info, sizeof(SHFILEINFO),
+ task->flags);
+ if (result) {
+ task->hIcon = info.hIcon;
+ task->iIcon = info.iIcon;
}
- m_params = nullptr;
-
+ task->finished = true;
m_doneCondition.wakeAll();
- m_doneMutex.unlock();
}
}
-
- if (m_init != S_FALSE)
- CoUninitialize();
}
- bool runWithParams(QShGetFileInfoParams *params, qint64 timeOutMSecs)
+ void runWithParams(const QSharedPointer<Task> &task,
+ std::chrono::milliseconds timeout = std::chrono::milliseconds(5000))
{
- QMutexLocker doneLocker(&m_doneMutex);
-
- m_readyMutex.lock();
- m_params = params;
- m_readyCondition.wakeAll();
- m_readyMutex.unlock();
+ {
+ QMutexLocker l(&m_waitForTaskMutex);
+ m_taskQueue.enqueue(task);
+ m_waitForTaskCondition.wakeAll();
+ }
- return m_doneCondition.wait(&m_doneMutex, QDeadlineTimer(timeOutMSecs));
+ QMutexLocker doneLocker(&m_doneMutex);
+ while (!task->finished && !isInterruptionRequested()) {
+ if (!m_doneCondition.wait(&m_doneMutex, QDeadlineTimer(timeout)))
+ return;
+ }
}
void cancel()
{
- QMutexLocker doneLocker(&m_doneMutex);
- m_cancelled.storeRelaxed(1);
- m_readyCondition.wakeAll();
+ requestInterruption();
+ m_doneCondition.wakeAll();
+ m_waitForTaskCondition.wakeAll();
}
private:
- HRESULT m_init;
- QShGetFileInfoParams *m_params;
- QAtomicInt m_cancelled;
- QWaitCondition m_readyCondition;
+ QQueue<QSharedPointer<Task>> m_taskQueue;
QWaitCondition m_doneCondition;
- QMutex m_readyMutex;
+ QWaitCondition m_waitForTaskCondition;
QMutex m_doneMutex;
+ QMutex m_waitForTaskMutex;
};
-
-static bool shGetFileInfoBackground(const QString &fileName, DWORD attributes,
- SHFILEINFO *info, UINT flags,
- qint64 timeOutMSecs = 5000)
-{
- static QShGetFileInfoThread *getFileInfoThread = nullptr;
- if (!getFileInfoThread) {
- getFileInfoThread = new QShGetFileInfoThread;
- getFileInfoThread->start();
- }
-
- bool result = false;
- QShGetFileInfoParams params(fileName, attributes, info, flags, &result);
- if (!getFileInfoThread->runWithParams(&params, timeOutMSecs)) {
- // Cancel and reset getFileInfoThread. It'll
- // be reinitialized the next time we get called.
- getFileInfoThread->cancel();
- getFileInfoThread = nullptr;
- qWarning().noquote() << "SHGetFileInfo() timed out for " << fileName;
- return false;
- }
- return result;
-}
+Q_APPLICATION_STATIC(QShGetFileInfoThread, s_shGetFileInfoThread)
// from QStyle::standardPalette
static inline QPalette standardPalette()
@@ -257,25 +260,17 @@ static QColor placeHolderColor(QColor textColor)
*/
static void populateLightSystemBasePalette(QPalette &result)
{
- QColor background = getSysColor(COLOR_BTNFACE);
- QColor textColor = getSysColor(COLOR_WINDOWTEXT);
- QColor accent = getSysColor(COLOR_HIGHLIGHT);
-
-#if defined(HAS_UISETTINGS)
- if (QWindowsIntegration::instance()->darkModeHandling().testFlag(QWindowsApplication::DarkModeStyle)) {
- using namespace winrt::Windows::UI::ViewManagement;
- const auto settings = UISettings();
-
- background = getSysColor(settings.GetColorValue(UIColorType::Background));
- textColor = getSysColor(settings.GetColorValue(UIColorType::Foreground));
- accent = getSysColor(settings.GetColorValue(UIColorType::Accent));
- }
-#endif
+ const QColor background = getSysColor(COLOR_BTNFACE);
+ const QColor textColor = getSysColor(COLOR_WINDOWTEXT);
+
+ const QColor accent = qt_accentColor(AccentColorNormal);
+ const QColor accentDarkest = qt_accentColor(AccentColorDarkest);
+ const QColor linkColor = accent;
const QColor btnFace = background;
const QColor btnHighlight = getSysColor(COLOR_BTNHIGHLIGHT);
- result.setColor(QPalette::Highlight, accent);
+ result.setColor(QPalette::Highlight, getSysColor(COLOR_HIGHLIGHT));
result.setColor(QPalette::WindowText, getSysColor(COLOR_WINDOWTEXT));
result.setColor(QPalette::Button, btnFace);
result.setColor(QPalette::Light, btnHighlight);
@@ -290,9 +285,10 @@ static void populateLightSystemBasePalette(QPalette &result)
result.setColor(QPalette::Midlight, getSysColor(COLOR_3DLIGHT));
result.setColor(QPalette::Shadow, getSysColor(COLOR_3DDKSHADOW));
result.setColor(QPalette::HighlightedText, getSysColor(COLOR_HIGHLIGHTTEXT));
+ result.setColor(QPalette::Accent, accent);
- result.setColor(QPalette::Link, Qt::blue);
- result.setColor(QPalette::LinkVisited, Qt::magenta);
+ result.setColor(QPalette::Link, linkColor);
+ result.setColor(QPalette::LinkVisited, accentDarkest);
result.setColor(QPalette::Inactive, QPalette::Button, result.button().color());
result.setColor(QPalette::Inactive, QPalette::Window, result.window().color());
result.setColor(QPalette::Inactive, QPalette::Light, result.light().color());
@@ -304,7 +300,7 @@ static void populateLightSystemBasePalette(QPalette &result)
static void populateDarkSystemBasePalette(QPalette &result)
{
-#if defined(HAS_UISETTINGS)
+#if QT_CONFIG(cpp_winrt)
using namespace winrt::Windows::UI::ViewManagement;
const auto settings = UISettings();
@@ -326,19 +322,18 @@ static void populateDarkSystemBasePalette(QPalette &result)
const QColor accentLight = getSysColor(settings.GetColorValue(UIColorType::AccentLight1));
const QColor accentLighter = getSysColor(settings.GetColorValue(UIColorType::AccentLight2));
const QColor accentLightest = getSysColor(settings.GetColorValue(UIColorType::AccentLight3));
- const QColor linkColor = accent;
#else
const QColor foreground = Qt::white;
const QColor background = QColor(0x1E, 0x1E, 0x1E);
- const QColor accent = QColor(0x00, 0x55, 0xff);
+ const QColor accent = qt_accentColor(AccentColorNormal);
const QColor accentDark = accent.darker(120);
const QColor accentDarker = accentDark.darker(120);
const QColor accentDarkest = accentDarker.darker(120);
const QColor accentLight = accent.lighter(120);
const QColor accentLighter = accentLight.lighter(120);
const QColor accentLightest = accentLighter.lighter(120);
- const QColor linkColor = Qt::blue;
#endif
+ const QColor linkColor = accent;
const QColor buttonColor = background.lighter(200);
result.setColor(QPalette::All, QPalette::WindowText, foreground);
@@ -361,49 +356,19 @@ static void populateDarkSystemBasePalette(QPalette &result)
result.setColor(QPalette::All, QPalette::Link, linkColor);
result.setColor(QPalette::All, QPalette::LinkVisited, accentDarkest);
result.setColor(QPalette::All, QPalette::AlternateBase, accentDarkest);
- result.setColor(QPalette::All, QPalette::ToolTipBase, accentDarkest);
- result.setColor(QPalette::All, QPalette::ToolTipText, accentLightest);
+ result.setColor(QPalette::All, QPalette::ToolTipBase, buttonColor);
+ result.setColor(QPalette::All, QPalette::ToolTipText, foreground.darker(120));
result.setColor(QPalette::All, QPalette::PlaceholderText, placeHolderColor(foreground));
-}
-
-static QPalette systemPalette(bool light)
-{
- QPalette result = standardPalette();
- if (light)
- populateLightSystemBasePalette(result);
- else
- populateDarkSystemBasePalette(result);
-
- if (result.window() != result.base()) {
- result.setColor(QPalette::Inactive, QPalette::Highlight,
- result.color(QPalette::Inactive, QPalette::Window));
- result.setColor(QPalette::Inactive, QPalette::HighlightedText,
- result.color(QPalette::Inactive, QPalette::Text));
- }
-
- const QColor disabled = mixColors(result.windowText().color(), result.button().color());
-
- result.setColorGroup(QPalette::Disabled, result.windowText(), result.button(),
- result.light(), result.dark(), result.mid(),
- result.text(), result.brightText(), result.base(),
- result.window());
- result.setColor(QPalette::Disabled, QPalette::WindowText, disabled);
- result.setColor(QPalette::Disabled, QPalette::Text, disabled);
- result.setColor(QPalette::Disabled, QPalette::ButtonText, disabled);
- result.setColor(QPalette::Disabled, QPalette::Highlight, result.color(QPalette::Highlight));
- result.setColor(QPalette::Disabled, QPalette::HighlightedText, result.color(QPalette::HighlightedText));
- result.setColor(QPalette::Disabled, QPalette::Base, result.window().color());
- return result;
+ result.setColor(QPalette::All, QPalette::Accent, accent);
}
static inline QPalette toolTipPalette(const QPalette &systemPalette, bool light)
{
- if (!light)
- return systemPalette;
-
QPalette result(systemPalette);
- const QColor tipBgColor = getSysColor(COLOR_INFOBK);
- const QColor tipTextColor = getSysColor(COLOR_INFOTEXT);
+ const QColor tipBgColor = light ? getSysColor(COLOR_INFOBK)
+ : systemPalette.button().color();
+ const QColor tipTextColor = light ? getSysColor(COLOR_INFOTEXT)
+ : systemPalette.buttonText().color().darker(120);
result.setColor(QPalette::All, QPalette::Button, tipBgColor);
result.setColor(QPalette::All, QPalette::Window, tipBgColor);
@@ -507,7 +472,10 @@ static inline QStringList iconThemeSearchPaths()
static inline QStringList styleNames()
{
- return { QStringLiteral("WindowsVista"), QStringLiteral("Windows") };
+ QStringList styles = { QStringLiteral("WindowsVista"), QStringLiteral("Windows") };
+ if (QOperatingSystemVersion::current() >= QOperatingSystemVersion::Windows11)
+ styles.prepend(QStringLiteral("Windows11"));
+ return styles;
}
static inline int uiEffects()
@@ -562,15 +530,19 @@ QVariant QWindowsTheme::themeHint(ThemeHint hint) const
}
case MouseDoubleClickDistance:
return GetSystemMetrics(SM_CXDOUBLECLK);
+ case MenuBarFocusOnAltPressRelease:
+ return true;
default:
break;
}
return QPlatformTheme::themeHint(hint);
}
-QPlatformTheme::Appearance QWindowsTheme::appearance() const
+Qt::ColorScheme QWindowsTheme::colorScheme() const
{
- return QWindowsContext::isDarkMode() ? Appearance::Dark : Appearance::Light;
+ if (queryHighContrast())
+ return Qt::ColorScheme::Unknown;
+ return QWindowsContext::isDarkMode() ? Qt::ColorScheme::Dark : Qt::ColorScheme::Light;
}
void QWindowsTheme::clearPalettes()
@@ -581,41 +553,66 @@ void QWindowsTheme::clearPalettes()
void QWindowsTheme::refreshPalettes()
{
-
if (!QGuiApplication::desktopSettingsAware())
return;
const bool light =
!QWindowsContext::isDarkMode()
|| !QWindowsIntegration::instance()->darkModeHandling().testFlag(QWindowsApplication::DarkModeStyle);
clearPalettes();
- m_palettes[SystemPalette] = new QPalette(systemPalette(light));
+ m_palettes[SystemPalette] = new QPalette(QWindowsTheme::systemPalette(light ? Qt::ColorScheme::Light : Qt::ColorScheme::Dark));
m_palettes[ToolTipPalette] = new QPalette(toolTipPalette(*m_palettes[SystemPalette], light));
m_palettes[MenuPalette] = new QPalette(menuPalette(*m_palettes[SystemPalette], light));
m_palettes[MenuBarPalette] = menuBarPalette(*m_palettes[MenuPalette], light);
if (!light) {
-#if defined(HAS_UISETTINGS)
- using namespace winrt::Windows::UI::ViewManagement;
- const auto settings = UISettings();
- const QColor accent = getSysColor(settings.GetColorValue(UIColorType::Accent));
- const QColor accentLight = getSysColor(settings.GetColorValue(UIColorType::AccentLight1));
- const QColor accentDarkest = getSysColor(settings.GetColorValue(UIColorType::AccentDark3));
m_palettes[CheckBoxPalette] = new QPalette(*m_palettes[SystemPalette]);
- m_palettes[CheckBoxPalette]->setColor(QPalette::Active, QPalette::Base, accent);
- m_palettes[CheckBoxPalette]->setColor(QPalette::Active, QPalette::Button, accentLight);
- m_palettes[CheckBoxPalette]->setColor(QPalette::Inactive, QPalette::Base, accentDarkest);
-#else
- m_palettes[ButtonPalette] = new QPalette(*m_palettes[SystemPalette]);
- m_palettes[ButtonPalette]->setColor(QPalette::Button, QColor(0x666666u));
- const QColor checkBoxBlue(0x0078d7u);
- m_palettes[CheckBoxPalette] = new QPalette(*m_palettes[SystemPalette]);
- m_palettes[CheckBoxPalette]->setColor(QPalette::Base, checkBoxBlue);
- m_palettes[CheckBoxPalette]->setColor(QPalette::Button, checkBoxBlue);
- m_palettes[CheckBoxPalette]->setColor(QPalette::ButtonText, Qt::white);
-#endif
+ m_palettes[CheckBoxPalette]->setColor(QPalette::Active, QPalette::Base, qt_accentColor(AccentColorNormal));
+ m_palettes[CheckBoxPalette]->setColor(QPalette::Active, QPalette::Button, qt_accentColor(AccentColorLightest));
+ m_palettes[CheckBoxPalette]->setColor(QPalette::Inactive, QPalette::Base, qt_accentColor(AccentColorDarkest));
m_palettes[RadioButtonPalette] = new QPalette(*m_palettes[CheckBoxPalette]);
}
}
+QPalette QWindowsTheme::systemPalette(Qt::ColorScheme colorScheme)
+{
+ QPalette result = standardPalette();
+
+ switch (colorScheme) {
+ case Qt::ColorScheme::Light:
+ populateLightSystemBasePalette(result);
+ break;
+ case Qt::ColorScheme::Dark:
+ populateDarkSystemBasePalette(result);
+ break;
+ default:
+ qFatal("Unknown color scheme");
+ break;
+ }
+
+ if (result.window() != result.base()) {
+ result.setColor(QPalette::Inactive, QPalette::Highlight,
+ result.color(QPalette::Inactive, QPalette::Window));
+ result.setColor(QPalette::Inactive, QPalette::HighlightedText,
+ result.color(QPalette::Inactive, QPalette::Text));
+ result.setColor(QPalette::Inactive, QPalette::Accent,
+ result.color(QPalette::Inactive, QPalette::Window));
+ }
+
+ const QColor disabled = mixColors(result.windowText().color(), result.button().color());
+
+ result.setColorGroup(QPalette::Disabled, result.windowText(), result.button(),
+ result.light(), result.dark(), result.mid(),
+ result.text(), result.brightText(), result.base(),
+ result.window());
+ result.setColor(QPalette::Disabled, QPalette::WindowText, disabled);
+ result.setColor(QPalette::Disabled, QPalette::Text, disabled);
+ result.setColor(QPalette::Disabled, QPalette::ButtonText, disabled);
+ result.setColor(QPalette::Disabled, QPalette::Highlight, result.color(QPalette::Highlight));
+ result.setColor(QPalette::Disabled, QPalette::HighlightedText, result.color(QPalette::HighlightedText));
+ result.setColor(QPalette::Disabled, QPalette::Accent, disabled);
+ result.setColor(QPalette::Disabled, QPalette::Base, result.window().color());
+ return result;
+}
+
void QWindowsTheme::clearFonts()
{
qDeleteAll(m_fonts, m_fonts + NFonts);
@@ -655,20 +652,22 @@ void QWindowsTheme::refreshFonts()
clearFonts();
if (!QGuiApplication::desktopSettingsAware())
return;
+
+ const int dpi = 96;
NONCLIENTMETRICS ncm;
- auto &screenManager = QWindowsContext::instance()->screenManager();
- QWindowsContext::nonClientMetricsForScreen(&ncm, screenManager.screens().value(0));
- qCDebug(lcQpaWindows) << __FUNCTION__ << ncm;
- const QFont menuFont = QWindowsFontDatabase::LOGFONT_to_QFont(ncm.lfMenuFont);
- const QFont messageBoxFont = QWindowsFontDatabase::LOGFONT_to_QFont(ncm.lfMessageFont);
- const QFont statusFont = QWindowsFontDatabase::LOGFONT_to_QFont(ncm.lfStatusFont);
- const QFont titleFont = QWindowsFontDatabase::LOGFONT_to_QFont(ncm.lfCaptionFont);
+ QWindowsContext::nonClientMetrics(&ncm, dpi);
+ qCDebug(lcQpaWindow) << __FUNCTION__ << ncm;
+
+ const QFont menuFont = QWindowsFontDatabase::LOGFONT_to_QFont(ncm.lfMenuFont, dpi);
+ const QFont messageBoxFont = QWindowsFontDatabase::LOGFONT_to_QFont(ncm.lfMessageFont, dpi);
+ const QFont statusFont = QWindowsFontDatabase::LOGFONT_to_QFont(ncm.lfStatusFont, dpi);
+ const QFont titleFont = QWindowsFontDatabase::LOGFONT_to_QFont(ncm.lfCaptionFont, dpi);
QFont fixedFont(QStringLiteral("Courier New"), messageBoxFont.pointSize());
fixedFont.setStyleHint(QFont::TypeWriter);
LOGFONT lfIconTitleFont;
- SystemParametersInfo(SPI_GETICONTITLELOGFONT, sizeof(lfIconTitleFont), &lfIconTitleFont, 0);
- const QFont iconTitleFont = QWindowsFontDatabase::LOGFONT_to_QFont(lfIconTitleFont);
+ SystemParametersInfoForDpi(SPI_GETICONTITLELOGFONT, sizeof(lfIconTitleFont), &lfIconTitleFont, 0, dpi);
+ const QFont iconTitleFont = QWindowsFontDatabase::LOGFONT_to_QFont(lfIconTitleFont, dpi);
m_fonts[SystemFont] = new QFont(QWindowsFontDatabase::systemDefaultFont());
m_fonts[MenuFont] = new QFont(menuFont);
@@ -725,13 +724,9 @@ void QWindowsTheme::refreshIconPixmapSizes()
fileIconSizes[LargeFileIcon] + fileIconSizes[LargeFileIcon] / 2;
fileIconSizes[JumboFileIcon] = 8 * fileIconSizes[LargeFileIcon]; // empirical, has not been observed to work
-#ifdef USE_IIMAGELIST
int *availEnd = fileIconSizes + JumboFileIcon + 1;
-#else
- int *availEnd = fileIconSizes + LargeFileIcon + 1;
-#endif // USE_IIMAGELIST
m_fileIconSizes = QAbstractFileIconEngine::toSizeList(fileIconSizes, availEnd);
- qCDebug(lcQpaWindows) << __FUNCTION__ << m_fileIconSizes;
+ qCDebug(lcQpaWindow) << __FUNCTION__ << m_fileIconSizes;
}
// Defined in qpixmap_win.cpp
@@ -842,15 +837,18 @@ QPixmap QWindowsTheme::standardPixmap(StandardPixmap sp, const QSizeF &pixmapSiz
}
if (stockId != SIID_INVALID) {
- QPixmap pixmap;
SHSTOCKICONINFO iconInfo;
memset(&iconInfo, 0, sizeof(iconInfo));
iconInfo.cbSize = sizeof(iconInfo);
- stockFlags |= (pixmapSize.width() > 16 ? SHGFI_LARGEICON : SHGFI_SMALLICON);
- if (SHGetStockIconInfo(stockId, SHGFI_ICON | stockFlags, &iconInfo) == S_OK) {
- pixmap = qt_pixmapFromWinHICON(iconInfo.hIcon);
- DestroyIcon(iconInfo.hIcon);
- return pixmap;
+ stockFlags |= SHGSI_ICONLOCATION;
+ if (SHGetStockIconInfo(stockId, stockFlags, &iconInfo) == S_OK) {
+ const auto iconSize = pixmapSize.width();
+ HICON icon;
+ if (SHDefExtractIcon(iconInfo.szPath, iconInfo.iIcon, 0, &icon, nullptr, iconSize) == S_OK) {
+ QPixmap pixmap = qt_pixmapFromWinHICON(icon);
+ DestroyIcon(icon);
+ return pixmap;
+ }
}
}
@@ -920,10 +918,9 @@ public:
// Shell image list helper functions.
-static QPixmap pixmapFromShellImageList(int iImageList, const SHFILEINFO &info)
+static QPixmap pixmapFromShellImageList(int iImageList, int iIcon)
{
QPixmap result;
-#ifdef USE_IIMAGELIST
// For MinGW:
static const IID iID_IImageList = {0x46eb5926, 0x582e, 0x4017, {0x9f, 0xdf, 0xe8, 0x99, 0x8d, 0xaa, 0x9, 0x50}};
@@ -932,16 +929,12 @@ static QPixmap pixmapFromShellImageList(int iImageList, const SHFILEINFO &info)
if (hr != S_OK)
return result;
HICON hIcon;
- hr = imageList->GetIcon(info.iIcon, ILD_TRANSPARENT, &hIcon);
+ hr = imageList->GetIcon(iIcon, ILD_TRANSPARENT, &hIcon);
if (hr == S_OK) {
result = qt_pixmapFromWinHICON(hIcon);
DestroyIcon(hIcon);
}
imageList->Release();
-#else
- Q_UNUSED(iImageList);
- Q_UNUSED(info);
-#endif // USE_IIMAGELIST
return result;
}
@@ -981,13 +974,10 @@ QString QWindowsFileIconEngine::cacheKey() const
QPixmap QWindowsFileIconEngine::filePixmap(const QSize &size, QIcon::Mode, QIcon::State)
{
- /* We don't use the variable, but by storing it statically, we
- * ensure CoInitialize is only called once. */
- static HRESULT comInit = CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
- Q_UNUSED(comInit);
+ QComHelper comHelper;
static QCache<QString, FakePointer<int> > dirIconEntryCache(1000);
- static QMutex mx;
+ Q_CONSTINIT static QMutex mx;
static int defaultFolderIIcon = -1;
const bool useDefaultFolderIcon = options() & QPlatformTheme::DontUseCustomDirectoryIcons;
@@ -996,13 +986,9 @@ QPixmap QWindowsFileIconEngine::filePixmap(const QSize &size, QIcon::Mode, QIcon
const int width = int(size.width());
const int iconSize = width > fileIconSizes[SmallFileIcon] ? SHGFI_LARGEICON : SHGFI_SMALLICON;
const int requestedImageListSize =
-#ifdef USE_IIMAGELIST
width > fileIconSizes[ExtraLargeFileIcon]
? sHIL_JUMBO
: (width > fileIconSizes[LargeFileIcon] ? sHIL_EXTRALARGE : 0);
-#else
- 0;
-#endif // !USE_IIMAGELIST
bool cacheableDirIcon = fileInfo().isDir() && !fileInfo().isRoot();
if (cacheableDirIcon) {
QMutexLocker locker(&mx);
@@ -1018,7 +1004,6 @@ QPixmap QWindowsFileIconEngine::filePixmap(const QSize &size, QIcon::Mode, QIcon
}
}
- SHFILEINFO info;
unsigned int flags = SHGFI_ICON | iconSize | SHGFI_SYSICONINDEX | SHGFI_ADDOVERLAYS | SHGFI_OVERLAYINDEX;
DWORD attributes = 0;
QString path = filePath;
@@ -1030,43 +1015,43 @@ QPixmap QWindowsFileIconEngine::filePixmap(const QSize &size, QIcon::Mode, QIcon
flags |= SHGFI_USEFILEATTRIBUTES;
attributes |= FILE_ATTRIBUTE_NORMAL;
}
- const bool val = shGetFileInfoBackground(path, attributes, &info, flags);
-
+ auto task = QSharedPointer<QShGetFileInfoThread::Task>(
+ new QShGetFileInfoThread::Task(path, attributes, flags));
+ s_shGetFileInfoThread()->runWithParams(task);
// Even if GetFileInfo returns a valid result, hIcon can be empty in some cases
- if (val && info.hIcon) {
+ if (task->resultValid()) {
QString key;
if (cacheableDirIcon) {
if (useDefaultFolderIcon && defaultFolderIIcon < 0)
- defaultFolderIIcon = info.iIcon;
+ defaultFolderIIcon = task->iIcon;
//using the unique icon index provided by windows save us from duplicate keys
- key = dirIconPixmapCacheKey(info.iIcon, iconSize, requestedImageListSize);
+ key = dirIconPixmapCacheKey(task->iIcon, iconSize, requestedImageListSize);
QPixmapCache::find(key, &pixmap);
if (!pixmap.isNull()) {
QMutexLocker locker(&mx);
- dirIconEntryCache.insert(filePath, FakePointer<int>::create(info.iIcon));
+ dirIconEntryCache.insert(filePath, FakePointer<int>::create(task->iIcon));
}
}
if (pixmap.isNull()) {
if (requestedImageListSize) {
- pixmap = pixmapFromShellImageList(requestedImageListSize, info);
+ pixmap = pixmapFromShellImageList(requestedImageListSize, task->iIcon);
if (pixmap.isNull() && requestedImageListSize == sHIL_JUMBO)
- pixmap = pixmapFromShellImageList(sHIL_EXTRALARGE, info);
+ pixmap = pixmapFromShellImageList(sHIL_EXTRALARGE, task->iIcon);
}
if (pixmap.isNull())
- pixmap = qt_pixmapFromWinHICON(info.hIcon);
+ pixmap = qt_pixmapFromWinHICON(task->hIcon);
if (!pixmap.isNull()) {
if (cacheableDirIcon) {
QMutexLocker locker(&mx);
QPixmapCache::insert(key, pixmap);
- dirIconEntryCache.insert(filePath, FakePointer<int>::create(info.iIcon));
+ dirIconEntryCache.insert(filePath, FakePointer<int>::create(task->iIcon));
}
} else {
qWarning("QWindowsTheme::fileIconPixmap() no icon found");
}
}
- DestroyIcon(info.hIcon);
}
return pixmap;
@@ -1077,6 +1062,11 @@ QIcon QWindowsTheme::fileIcon(const QFileInfo &fileInfo, QPlatformTheme::IconOpt
return QIcon(new QWindowsFileIconEngine(fileInfo, iconOptions));
}
+QIconEngine *QWindowsTheme::createIconEngine(const QString &iconName) const
+{
+ return new QWindowsIconEngine(iconName);
+}
+
static inline bool doUseNativeMenus()
{
const unsigned options = QWindowsIntegration::instance()->options();
@@ -1113,7 +1103,11 @@ bool QWindowsTheme::queryDarkMode()
bool QWindowsTheme::queryHighContrast()
{
- return booleanSystemParametersInfo(SPI_GETHIGHCONTRAST, false);
+ HIGHCONTRAST hcf = {};
+ hcf.cbSize = static_cast<UINT>(sizeof(HIGHCONTRAST));
+ if (SystemParametersInfo(SPI_GETHIGHCONTRAST, hcf.cbSize, &hcf, FALSE))
+ return hcf.dwFlags & HCF_HIGHCONTRASTON;
+ return false;
}
QPlatformMenuItem *QWindowsTheme::createPlatformMenuItem() const
diff --git a/src/plugins/platforms/windows/qwindowstheme.h b/src/plugins/platforms/windows/qwindowstheme.h
index 85a7607d7f..bc16a9619e 100644
--- a/src/plugins/platforms/windows/qwindowstheme.h
+++ b/src/plugins/platforms/windows/qwindowstheme.h
@@ -31,7 +31,7 @@ public:
#endif
QVariant themeHint(ThemeHint) const override;
- Appearance appearance() const override;
+ Qt::ColorScheme colorScheme() const override;
const QPalette *palette(Palette type = SystemPalette) const override
{ return m_palettes[type]; }
@@ -41,6 +41,7 @@ public:
QPixmap standardPixmap(StandardPixmap sp, const QSizeF &size) const override;
QIcon fileIcon(const QFileInfo &fileInfo, QPlatformTheme::IconOptions iconOptions = {}) const override;
+ QIconEngine *createIconEngine(const QString &iconName) const override;
void windowsThemeChanged(QWindow *window);
void displayChanged() { refreshIconPixmapSizes(); }
@@ -61,6 +62,8 @@ public:
static const char *name;
+ static QPalette systemPalette(Qt::ColorScheme);
+
private:
void clearPalettes();
void refreshPalettes();
diff --git a/src/plugins/platforms/windows/qwindowswindow.cpp b/src/plugins/platforms/windows/qwindowswindow.cpp
index a795328bdf..220c36cc19 100644
--- a/src/plugins/platforms/windows/qwindowswindow.cpp
+++ b/src/plugins/platforms/windows/qwindowswindow.cpp
@@ -1,9 +1,7 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
-#ifndef WINVER
-# define WINVER 0x0A00 // Enable touch functions for MinGW
-#endif
+#include <QtCore/qt_windows.h>
#include "qwindowswindow.h"
#include "qwindowscontext.h"
@@ -29,6 +27,7 @@
#include <QtGui/qwindow.h>
#include <QtGui/qregion.h>
#include <QtGui/qopenglcontext.h>
+#include <QtGui/private/qwindowsthemecache_p.h>
#include <private/qwindow_p.h> // QWINDOWSIZE_MAX
#include <private/qguiapplication_p.h>
#include <private/qhighdpiscaling_p.h>
@@ -431,11 +430,7 @@ static inline bool windowIsAccelerated(const QWindow *w)
{
switch (w->surfaceType()) {
case QSurface::OpenGLSurface:
- return true;
- case QSurface::RasterGLSurface:
- return qt_window_private(const_cast<QWindow *>(w))->compositing;
case QSurface::VulkanSurface:
- return true;
case QSurface::Direct3DSurface:
return true;
default:
@@ -470,14 +465,21 @@ static bool shouldShowMaximizeButton(const QWindow *w, Qt::WindowFlags flags)
w->maximumSize() == QSize(QWINDOWSIZE_MAX, QWINDOWSIZE_MAX);
}
+bool QWindowsWindow::hasNoNativeFrame(HWND hwnd, Qt::WindowFlags flags)
+{
+ const LONG_PTR style = GetWindowLongPtr(hwnd, GWL_STYLE);
+ return (style & WS_CHILD) || (flags & Qt::FramelessWindowHint);
+}
+
// Set the WS_EX_LAYERED flag on a HWND if required. This is required for
// translucent backgrounds, not fully opaque windows and for
// Qt::WindowTransparentForInput (in combination with WS_EX_TRANSPARENT).
bool QWindowsWindow::setWindowLayered(HWND hwnd, Qt::WindowFlags flags, bool hasAlpha, qreal opacity)
{
const LONG_PTR exStyle = GetWindowLongPtr(hwnd, GWL_EXSTYLE);
+ // Native children are frameless by nature, so check for that as well.
const bool needsLayered = (flags & Qt::WindowTransparentForInput)
- || (hasAlpha && (flags & Qt::FramelessWindowHint)) || opacity < 1.0;
+ || (hasAlpha && hasNoNativeFrame(hwnd, flags)) || opacity < 1.0;
const bool isLayered = (exStyle & WS_EX_LAYERED);
if (needsLayered != isLayered) {
if (needsLayered) {
@@ -493,7 +495,7 @@ static void setWindowOpacity(HWND hwnd, Qt::WindowFlags flags, bool hasAlpha, bo
{
if (QWindowsWindow::setWindowLayered(hwnd, flags, hasAlpha, level)) {
const BYTE alpha = BYTE(qRound(255.0 * level));
- if (hasAlpha && !accelerated && (flags & Qt::FramelessWindowHint)) {
+ if (hasAlpha && !accelerated && QWindowsWindow::hasNoNativeFrame(hwnd, flags)) {
// Non-GL windows with alpha: Use blend function to update.
BLENDFUNCTION blend = {AC_SRC_OVER, 0, alpha, AC_SRC_ALPHA};
UpdateLayeredWindow(hwnd, nullptr, nullptr, nullptr, nullptr, nullptr, 0, &blend, ULW_ALPHA);
@@ -795,15 +797,8 @@ void WindowCreationData::fromWindow(const QWindow *w, const Qt::WindowFlags flag
style = WS_CHILD;
}
- // if (!testAttribute(Qt::WA_PaintUnclipped))
- // ### Commented out for now as it causes some problems, but
- // this should be correct anyway, so dig some more into this
-#ifdef Q_FLATTEN_EXPOSE
- if (windowIsOpenGL(w)) // a bit incorrect since the is-opengl status may change from false to true at any time later on
- style |= WS_CLIPSIBLINGS | WS_CLIPCHILDREN; // see SetPixelFormat
-#else
style |= WS_CLIPSIBLINGS | WS_CLIPCHILDREN ;
-#endif
+
if (topLevel) {
if ((type == Qt::Window || dialog || tool)) {
if (!(flags & Qt::FramelessWindowHint)) {
@@ -843,6 +838,10 @@ void WindowCreationData::fromWindow(const QWindow *w, const Qt::WindowFlags flag
// NOTE: WS_EX_TRANSPARENT flag can make mouse inputs fall through a layered window
if (flagsIn & Qt::WindowTransparentForInput)
exStyle |= WS_EX_LAYERED | WS_EX_TRANSPARENT;
+
+ // Currently only compatible with D3D surfaces, use it with care.
+ if (qEnvironmentVariableIntValue("QT_QPA_DISABLE_REDIRECTION_SURFACE"))
+ exStyle |= WS_EX_NOREDIRECTIONBITMAP;
}
}
@@ -850,13 +849,16 @@ static inline bool shouldApplyDarkFrame(const QWindow *w)
{
if (!w->isTopLevel() || w->flags().testFlag(Qt::FramelessWindowHint))
return false;
- if (QWindowsIntegration::instance()->darkModeHandling().testFlag(QWindowsApplication::DarkModeStyle))
- return true;
+ // the application has explicitly opted out of dark frames
+ if (!QWindowsIntegration::instance()->darkModeHandling().testFlag(QWindowsApplication::DarkModeWindowFrames))
+ return false;
+
// if the application supports a dark border, and the palette is dark (window background color
// is darker than the text), then turn dark-border support on, otherwise use a light border.
- const QPalette defaultPalette;
- return defaultPalette.color(QPalette::WindowText).lightness()
- > defaultPalette.color(QPalette::Window).lightness();
+ auto *dWindow = QWindowPrivate::get(const_cast<QWindow*>(w));
+ const QPalette windowPal = dWindow->windowPalette();
+ return windowPal.color(QPalette::WindowText).lightness()
+ > windowPal.color(QPalette::Window).lightness();
}
QWindowsWindowData
@@ -887,11 +889,12 @@ QWindowsWindowData
style, exStyle));
QWindowsContext::instance()->setWindowCreationContext(context);
- const bool hasFrame = (style & (WS_DLGFRAME | WS_THICKFRAME));
+ const bool hasFrame = (style & (WS_DLGFRAME | WS_THICKFRAME))
+ && !(result.flags & Qt::FramelessWindowHint);
QMargins invMargins = topLevel && hasFrame && QWindowsGeometryHint::positionIncludesFrame(w)
? invisibleMargins(QPoint(context->frameX, context->frameY)) : QMargins();
- qCDebug(lcQpaWindows).nospace()
+ qCDebug(lcQpaWindow).nospace()
<< "CreateWindowEx: " << w << " class=" << windowClassName << " title=" << title
<< '\n' << *this << "\nrequested: " << rect << ": "
<< context->frameWidth << 'x' << context->frameHeight
@@ -917,7 +920,7 @@ QWindowsWindowData
pos.x(), pos.y(),
context->frameWidth, context->frameHeight,
parentHandle, nullptr, appinst, nullptr);
- qCDebug(lcQpaWindows).nospace()
+ qCDebug(lcQpaWindow).nospace()
<< "CreateWindowEx: returns " << w << ' ' << result.hwnd << " obtained geometry: "
<< context->obtainedPos << context->obtainedSize << ' ' << context->margins;
@@ -926,11 +929,8 @@ QWindowsWindowData
return result;
}
- if (QWindowsContext::isDarkMode()
- && QWindowsIntegration::instance()->darkModeHandling().testFlag(QWindowsApplication::DarkModeWindowFrames)
- && shouldApplyDarkFrame(w)) {
+ if (QWindowsContext::isDarkMode() && shouldApplyDarkFrame(w))
QWindowsWindow::setDarkBorderToWindow(result.hwnd, true);
- }
if (mirrorParentWidth != 0) {
context->obtainedPos.setX(mirrorParentWidth - context->obtainedSize.width()
@@ -940,6 +940,7 @@ QWindowsWindowData
QRect obtainedGeometry(context->obtainedPos, context->obtainedSize);
result.geometry = obtainedGeometry;
+ result.restoreGeometry = frameGeometry(result.hwnd, topLevel);
result.fullFrameMargins = context->margins;
result.embedded = embedded;
result.hasFrame = hasFrame;
@@ -960,7 +961,7 @@ void WindowCreationData::applyWindowFlags(HWND hwnd) const
const LONG_PTR newExStyle = exStyle;
if (newExStyle != oldExStyle)
SetWindowLongPtr(hwnd, GWL_EXSTYLE, newExStyle);
- qCDebug(lcQpaWindows).nospace() << __FUNCTION__ << hwnd << *this
+ qCDebug(lcQpaWindow).nospace() << __FUNCTION__ << hwnd << *this
<< "\n Style from " << debugWinStyle(DWORD(oldStyle)) << "\n to "
<< debugWinStyle(DWORD(newStyle)) << "\n ExStyle from "
<< debugWinExStyle(DWORD(oldExStyle)) << " to "
@@ -1014,6 +1015,21 @@ static QSize toNativeSizeConstrained(QSize dip, const QScreen *s)
return dip;
}
+// Helper for checking if frame adjustment needs to be skipped
+// NOTE: Unmaximized frameless windows will skip margins calculation
+static bool shouldOmitFrameAdjustment(const Qt::WindowFlags flags, DWORD style)
+{
+ return flags.testFlag(Qt::FramelessWindowHint) && !(style & WS_MAXIMIZE);
+}
+
+// Helper for checking if frame adjustment needs to be skipped
+// NOTE: Unmaximized frameless windows will skip margins calculation
+static bool shouldOmitFrameAdjustment(const Qt::WindowFlags flags, HWND hwnd)
+{
+ DWORD style = hwnd != nullptr ? DWORD(GetWindowLongPtr(hwnd, GWL_STYLE)) : 0;
+ return flags.testFlag(Qt::FramelessWindowHint) && !(style & WS_MAXIMIZE);
+}
+
/*!
\class QWindowsGeometryHint
\brief Stores geometry constraints and provides utility functions.
@@ -1026,7 +1042,7 @@ static QSize toNativeSizeConstrained(QSize dip, const QScreen *s)
QMargins QWindowsGeometryHint::frameOnPrimaryScreen(const QWindow *w, DWORD style, DWORD exStyle)
{
- if (!w->isTopLevel() || w->flags().testFlag(Qt::FramelessWindowHint))
+ if (!w->isTopLevel() || shouldOmitFrameAdjustment(w->flags(), style))
return {};
RECT rect = {0,0,0,0};
style &= ~DWORD(WS_OVERLAPPED); // Not permitted, see docs.
@@ -1034,7 +1050,7 @@ QMargins QWindowsGeometryHint::frameOnPrimaryScreen(const QWindow *w, DWORD styl
qErrnoWarning("%s: AdjustWindowRectEx failed", __FUNCTION__);
const QMargins result(qAbs(rect.left), qAbs(rect.top),
qAbs(rect.right), qAbs(rect.bottom));
- qCDebug(lcQpaWindows).nospace() << __FUNCTION__ << " style="
+ qCDebug(lcQpaWindow).nospace() << __FUNCTION__ << " style="
<< Qt::showbase << Qt::hex << style << " exStyle=" << exStyle << Qt::dec << Qt::noshowbase
<< ' ' << rect << ' ' << result;
return result;
@@ -1042,15 +1058,13 @@ QMargins QWindowsGeometryHint::frameOnPrimaryScreen(const QWindow *w, DWORD styl
QMargins QWindowsGeometryHint::frameOnPrimaryScreen(const QWindow *w, HWND hwnd)
{
- if (!w->isTopLevel() || w->flags().testFlag(Qt::FramelessWindowHint))
- return {};
return frameOnPrimaryScreen(w, DWORD(GetWindowLongPtr(hwnd, GWL_STYLE)),
DWORD(GetWindowLongPtr(hwnd, GWL_EXSTYLE)));
}
QMargins QWindowsGeometryHint::frame(const QWindow *w, DWORD style, DWORD exStyle, qreal dpi)
{
- if (!w->isTopLevel() || w->flags().testFlag(Qt::FramelessWindowHint))
+ if (!w->isTopLevel() || shouldOmitFrameAdjustment(w->flags(), style))
return {};
RECT rect = {0,0,0,0};
style &= ~DWORD(WS_OVERLAPPED); // Not permitted, see docs.
@@ -1059,7 +1073,7 @@ QMargins QWindowsGeometryHint::frame(const QWindow *w, DWORD style, DWORD exStyl
}
const QMargins result(qAbs(rect.left), qAbs(rect.top),
qAbs(rect.right), qAbs(rect.bottom));
- qCDebug(lcQpaWindows).nospace() << __FUNCTION__ << " style="
+ qCDebug(lcQpaWindow).nospace() << __FUNCTION__ << " style="
<< Qt::showbase << Qt::hex << style << " exStyle=" << exStyle << Qt::dec << Qt::noshowbase
<< " dpi=" << dpi
<< ' ' << rect << ' ' << result;
@@ -1068,7 +1082,7 @@ QMargins QWindowsGeometryHint::frame(const QWindow *w, DWORD style, DWORD exStyl
QMargins QWindowsGeometryHint::frame(const QWindow *w, HWND hwnd, DWORD style, DWORD exStyle)
{
- if (!w->isTopLevel() || w->flags().testFlag(Qt::FramelessWindowHint))
+ if (!w->isTopLevel() || shouldOmitFrameAdjustment(w->flags(), style))
return {};
if (QWindowsScreenManager::isSingleScreen())
return frameOnPrimaryScreen(w, style, exStyle);
@@ -1082,8 +1096,6 @@ QMargins QWindowsGeometryHint::frame(const QWindow *w, HWND hwnd, DWORD style, D
QMargins QWindowsGeometryHint::frame(const QWindow *w, HWND hwnd)
{
- if (!w->isTopLevel() || w->flags().testFlag(Qt::FramelessWindowHint))
- return {};
return frame(w, hwnd, DWORD(GetWindowLongPtr(hwnd, GWL_STYLE)),
DWORD(GetWindowLongPtr(hwnd, GWL_EXSTYLE)));
}
@@ -1092,7 +1104,7 @@ QMargins QWindowsGeometryHint::frame(const QWindow *w, HWND hwnd)
QMargins QWindowsGeometryHint::frame(const QWindow *w, const QRect &geometry,
DWORD style, DWORD exStyle)
{
- if (!w->isTopLevel() || w->flags().testFlag(Qt::FramelessWindowHint))
+ if (!w->isTopLevel() || shouldOmitFrameAdjustment(w->flags(), style))
return {};
if (QWindowsScreenManager::isSingleScreen()
|| !QWindowsContext::shouldHaveNonClientDpiScaling(w)) {
@@ -1121,7 +1133,7 @@ bool QWindowsGeometryHint::handleCalculateSize(const QMargins &customMargins, co
ncp->rgrc[0].right -= customMargins.right();
ncp->rgrc[0].bottom -= customMargins.bottom();
result = nullptr;
- qCDebug(lcQpaWindows).nospace() << __FUNCTION__ << oldClientArea << '+' << customMargins << "-->"
+ qCDebug(lcQpaWindow).nospace() << __FUNCTION__ << oldClientArea << '+' << customMargins << "-->"
<< ncp->rgrc[0] << ' ' << ncp->rgrc[1] << ' ' << ncp->rgrc[2]
<< ' ' << ncp->lppos->cx << ',' << ncp->lppos->cy;
return true;
@@ -1157,7 +1169,7 @@ void QWindowsGeometryHint::applyToMinMaxInfo(const QWindow *w,
QSize minimumSize;
QSize maximumSize;
frameSizeConstraints(w, screen, margins, &minimumSize, &maximumSize);
- qCDebug(lcQpaWindows).nospace() << '>' << __FUNCTION__ << '<' << " min="
+ qCDebug(lcQpaWindow).nospace() << '>' << __FUNCTION__ << '<' << " min="
<< minimumSize.width() << ',' << minimumSize.height()
<< " max=" << maximumSize.width() << ',' << maximumSize.height()
<< " margins=" << margins
@@ -1172,7 +1184,7 @@ void QWindowsGeometryHint::applyToMinMaxInfo(const QWindow *w,
mmi->ptMaxTrackSize.x = maximumSize.width();
if (maximumSize.height() < QWINDOWSIZE_MAX)
mmi->ptMaxTrackSize.y = maximumSize.height();
- qCDebug(lcQpaWindows).nospace() << '<' << __FUNCTION__ << " out " << *mmi;
+ qCDebug(lcQpaWindow).nospace() << '<' << __FUNCTION__ << " out " << *mmi;
}
void QWindowsGeometryHint::applyToMinMaxInfo(const QWindow *w,
@@ -1268,7 +1280,7 @@ void QWindowsBaseWindow::hide_sys() // Normal hide, do not activate other window
void QWindowsBaseWindow::raise_sys()
{
- qCDebug(lcQpaWindows) << __FUNCTION__ << this << window();
+ qCDebug(lcQpaWindow) << __FUNCTION__ << this << window();
const Qt::WindowType type = window()->type();
if (type == Qt::Popup
|| type == Qt::SubWindow // Special case for QTBUG-63121: MDI subwindows with WindowStaysOnTopHint
@@ -1279,14 +1291,14 @@ void QWindowsBaseWindow::raise_sys()
void QWindowsBaseWindow::lower_sys()
{
- qCDebug(lcQpaWindows) << __FUNCTION__ << this << window();
+ qCDebug(lcQpaWindow) << __FUNCTION__ << this << window();
if (!(window()->flags() & Qt::WindowStaysOnTopHint))
SetWindowPos(handle(), HWND_BOTTOM, 0, 0, 0, 0, SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE);
}
void QWindowsBaseWindow::setWindowTitle_sys(const QString &title)
{
- qCDebug(lcQpaWindows) << __FUNCTION__ << this << window() << title;
+ qCDebug(lcQpaWindow) << __FUNCTION__ << this << window() << title;
SetWindowText(handle(), reinterpret_cast<const wchar_t *>(title.utf16()));
}
@@ -1353,7 +1365,7 @@ void QWindowsForeignWindow::setParent(const QPlatformWindow *newParentWindow)
const HWND newParent = newParentWindow ? reinterpret_cast<HWND>(newParentWindow->winId()) : HWND(nullptr);
const bool isTopLevel = !newParent;
const DWORD oldStyle = style();
- qCDebug(lcQpaWindows) << __FUNCTION__ << window() << "newParent="
+ qCDebug(lcQpaWindow) << __FUNCTION__ << window() << "newParent="
<< newParentWindow << newParent << "oldStyle=" << debugWinStyle(oldStyle);
SetParent(m_hwnd, newParent);
if (wasTopLevel != isTopLevel) { // Top level window flags need to be set/cleared manually.
@@ -1371,7 +1383,7 @@ void QWindowsForeignWindow::setParent(const QPlatformWindow *newParentWindow)
void QWindowsForeignWindow::setVisible(bool visible)
{
- qCDebug(lcQpaWindows) << __FUNCTION__ << window() << visible;
+ qCDebug(lcQpaWindow) << __FUNCTION__ << window() << visible;
if (visible)
ShowWindow(handle(), SW_SHOWNOACTIVATE);
else
@@ -1408,13 +1420,16 @@ QWindowCreationContext::QWindowCreationContext(const QWindow *w, const QScreen *
requestedGeometry(geometry),
obtainedPos(geometryIn.topLeft()),
obtainedSize(geometryIn.size()),
- margins(QWindowsGeometryHint::frame(w, geometry, style, exStyle)),
- customMargins(cm)
+ margins(QWindowsGeometryHint::frame(w, geometry, style, exStyle))
{
// Geometry of toplevels does not consider window frames.
// TODO: No concept of WA_wasMoved yet that would indicate a
// CW_USEDEFAULT unless set. For now, assume that 0,0 means 'default'
// for toplevels.
+
+ if (!(w->flags() & Qt::FramelessWindowHint))
+ customMargins = cm;
+
if (geometry.isValid()
|| !qt_window_private(const_cast<QWindow *>(w))->resizeAutomatic) {
frameX = geometry.x();
@@ -1433,7 +1448,7 @@ QWindowCreationContext::QWindowCreationContext(const QWindow *w, const QScreen *
}
}
- qCDebug(lcQpaWindows).nospace()
+ qCDebug(lcQpaWindow).nospace()
<< __FUNCTION__ << ' ' << w << ' ' << geometry
<< " pos incl. frame=" << QWindowsGeometryHint::positionIncludesFrame(w)
<< " frame=" << frameWidth << 'x' << frameHeight << '+'
@@ -1517,6 +1532,7 @@ QWindowsWindow::QWindowsWindow(QWindow *aWindow, const QWindowsWindowData &data)
QWindowsWindow::~QWindowsWindow()
{
setFlag(WithinDestroy);
+ QWindowsThemeCache::clearThemeCache(m_data.hwnd);
if (testFlag(TouchRegistered))
UnregisterTouchWindow(m_data.hwnd);
destroyWindow();
@@ -1569,7 +1585,7 @@ void QWindowsWindow::fireFullExpose(bool force)
void QWindowsWindow::destroyWindow()
{
- qCDebug(lcQpaWindows) << __FUNCTION__ << this << window() << m_data.hwnd;
+ qCDebug(lcQpaWindow) << __FUNCTION__ << this << window() << m_data.hwnd;
if (m_data.hwnd) { // Stop event dispatching before Window is destroyed.
setFlag(WithinDestroy);
// Clear any transient child relationships as Windows will otherwise destroy them (QTBUG-35499, QTBUG-36666)
@@ -1718,7 +1734,7 @@ QWindowsWindowData
void QWindowsWindow::setVisible(bool visible)
{
const QWindow *win = window();
- qCDebug(lcQpaWindows) << __FUNCTION__ << this << win << m_data.hwnd << visible;
+ qCDebug(lcQpaWindow) << __FUNCTION__ << this << win << m_data.hwnd << visible;
if (m_data.hwnd) {
if (visible) {
show_sys();
@@ -1898,7 +1914,7 @@ void QWindowsWindow::show_sys() const
void QWindowsWindow::setParent(const QPlatformWindow *newParent)
{
- qCDebug(lcQpaWindows) << __FUNCTION__ << window() << newParent;
+ qCDebug(lcQpaWindow) << __FUNCTION__ << window() << newParent;
if (m_data.hwnd)
setParent_sys(newParent);
@@ -1958,6 +1974,12 @@ void QWindowsWindow::handleCompositionSettingsChanged()
}
}
+qreal QWindowsWindow::dpiRelativeScale(const UINT dpi) const
+{
+ return QHighDpiScaling::roundScaleFactor(qreal(dpi) / QWindowsScreen::baseDpi) /
+ QHighDpiScaling::roundScaleFactor(qreal(savedDpi()) / QWindowsScreen::baseDpi);
+}
+
void QWindowsWindow::handleDpiScaledSize(WPARAM wParam, LPARAM lParam, LRESULT *result)
{
// We want to keep QWindow's device independent size constant across the
@@ -1965,11 +1987,19 @@ void QWindowsWindow::handleDpiScaledSize(WPARAM wParam, LPARAM lParam, LRESULT *
// by the change of DPI (e.g. 120 -> 144 = 1.2), also taking any scale
// factor rounding into account. The win32 window size includes the margins;
// add the margins for the new DPI to the window size.
- const int dpi = int(wParam);
- const qreal scale = QHighDpiScaling::roundScaleFactor(qreal(dpi) / QWindowsScreen::baseDpi) /
- QHighDpiScaling::roundScaleFactor(qreal(savedDpi()) / QWindowsScreen::baseDpi);
- const QMargins margins = QWindowsGeometryHint::frame(window(), style(), exStyle(), dpi);
- const QSize windowSize = (geometry().size() * scale).grownBy(margins);
+ const UINT dpi = UINT(wParam);
+ const qreal scale = dpiRelativeScale(dpi);
+ const QMargins margins = fullFrameMargins();
+ if (!(m_data.flags & Qt::FramelessWindowHint)) {
+ // We need to update the custom margins to match the current DPI, because
+ // we don't want our users manually hook into this message just to set a
+ // new margin, but here we can't call setCustomMargins() directly, that
+ // function will change the window geometry which conflicts with what we
+ // are currently doing.
+ m_data.customMargins *= scale;
+ }
+
+ const QSize windowSize = (geometry().size() * scale).grownBy((margins * scale) + customMargins());
SIZE *size = reinterpret_cast<SIZE *>(lParam);
size->cx = windowSize.width();
size->cy = windowSize.height();
@@ -1979,11 +2009,17 @@ void QWindowsWindow::handleDpiScaledSize(WPARAM wParam, LPARAM lParam, LRESULT *
void QWindowsWindow::handleDpiChanged(HWND hwnd, WPARAM wParam, LPARAM lParam)
{
const UINT dpi = HIWORD(wParam);
+ const qreal scale = dpiRelativeScale(dpi);
setSavedDpi(dpi);
+ QWindowsThemeCache::clearThemeCache(hwnd);
+
// Send screen change first, so that the new screen is set during any following resize
checkForScreenChanged(QWindowsWindow::FromDpiChange);
+ if (!IsZoomed(hwnd))
+ m_data.restoreGeometry.setSize(m_data.restoreGeometry.size() * scale);
+
// We get WM_DPICHANGED in one of two situations:
//
// 1. The DPI change is a "spontaneous" DPI change as a result of e.g.
@@ -2006,13 +2042,22 @@ void QWindowsWindow::handleDpiChanged(HWND hwnd, WPARAM wParam, LPARAM lParam)
SetWindowPos(hwnd, nullptr, prcNewWindow->left, prcNewWindow->top,
prcNewWindow->right - prcNewWindow->left,
prcNewWindow->bottom - prcNewWindow->top, SWP_NOZORDER | SWP_NOACTIVATE);
+ // If the window does not have a frame, WM_MOVE and WM_SIZE won't be
+ // called which prevents the content from being scaled appropriately
+ // after a DPI change.
+ if (shouldOmitFrameAdjustment(m_data.flags, m_data.hwnd))
+ handleGeometryChange();
}
+
+ // Re-apply mask now that we have a new DPI, which have resulted in
+ // a new scale factor.
+ setMask(QHighDpi::toNativeLocalRegion(window()->mask(), window()));
}
void QWindowsWindow::handleDpiChangedAfterParent(HWND hwnd)
{
const UINT dpi = GetDpiForWindow(hwnd);
- const qreal scale = qreal(dpi) / qreal(savedDpi());
+ const qreal scale = dpiRelativeScale(dpi);
setSavedDpi(dpi);
checkForScreenChanged(QWindowsWindow::FromDpiChange);
@@ -2119,7 +2164,7 @@ void QWindowsWindow::setGeometry(const QRect &rectIn)
if (m_data.geometry != rect && (isVisible() || QLibraryInfo::isDebugBuild())) {
const auto warning =
msgUnableToSetGeometry(this, rectIn, m_data.geometry,
- m_data.fullFrameMargins, m_data.customMargins);
+ fullFrameMargins(), customMargins());
qWarning("%s: %s", __FUNCTION__, qPrintable(warning));
}
} else {
@@ -2134,8 +2179,41 @@ void QWindowsWindow::handleMoved()
handleGeometryChange();
}
-void QWindowsWindow::handleResized(int wParam)
+void QWindowsWindow::handleResized(int wParam, LPARAM lParam)
{
+ /* Prevents borderless windows from covering the taskbar when maximized. */
+ if ((m_data.flags.testFlag(Qt::FramelessWindowHint)
+ || (m_data.flags.testFlag(Qt::CustomizeWindowHint) && !m_data.flags.testFlag(Qt::WindowTitleHint)))
+ && IsZoomed(m_data.hwnd)) {
+ const int resizedWidth = LOWORD(lParam);
+ const int resizedHeight = HIWORD(lParam);
+
+ const HMONITOR monitor = MonitorFromWindow(m_data.hwnd, MONITOR_DEFAULTTOPRIMARY);
+ MONITORINFO monitorInfo = {};
+ monitorInfo.cbSize = sizeof(MONITORINFO);
+ GetMonitorInfoW(monitor, &monitorInfo);
+
+ int correctLeft = monitorInfo.rcMonitor.left;
+ int correctTop = monitorInfo.rcMonitor.top;
+ int correctWidth = monitorInfo.rcWork.right - monitorInfo.rcWork.left;
+ int correctHeight = monitorInfo.rcWork.bottom - monitorInfo.rcWork.top;
+
+ if (!m_data.flags.testFlag(Qt::FramelessWindowHint)) {
+ const int borderWidth = invisibleMargins(m_data.hwnd).left();
+ correctLeft -= borderWidth;
+ correctTop -= borderWidth;
+ correctWidth += borderWidth * 2;
+ correctHeight += borderWidth * 2;
+ }
+
+ if (resizedWidth != correctWidth || resizedHeight != correctHeight) {
+ qCDebug(lcQpaWindow) << __FUNCTION__ << "correcting: " << resizedWidth << "x"
+ << resizedHeight << " -> " << correctWidth << "x" << correctHeight;
+ SetWindowPos(m_data.hwnd, nullptr, correctLeft, correctTop, correctWidth, correctHeight,
+ SWP_NOZORDER | SWP_NOACTIVATE);
+ }
+ }
+
switch (wParam) {
case SIZE_MAXHIDE: // Some other window affected.
case SIZE_MAXSHOW:
@@ -2183,11 +2261,11 @@ void QWindowsWindow::checkForScreenChanged(ScreenChangeMode mode)
return;
// For screens with different DPI: postpone until WM_DPICHANGE
// Check on currentScreen as it can be 0 when resuming a session (QTBUG-80436).
- if (mode == FromGeometryChange && currentScreen != nullptr
- && !equalDpi(currentScreen->logicalDpi(), newScreen->logicalDpi())) {
+ const bool changingDpi = !equalDpi(QDpi(savedDpi(), savedDpi()), newScreen->logicalDpi());
+ if (mode == FromGeometryChange && currentScreen != nullptr && changingDpi)
return;
- }
- qCDebug(lcQpaWindows).noquote().nospace() << __FUNCTION__
+
+ qCDebug(lcQpaWindow).noquote().nospace() << __FUNCTION__
<< ' ' << window() << " \"" << (currentScreen ? currentScreen->name() : QString())
<< "\"->\"" << newScreen->name() << '"';
updateFullFrameMargins();
@@ -2198,6 +2276,7 @@ void QWindowsWindow::handleGeometryChange()
{
const QRect previousGeometry = m_data.geometry;
m_data.geometry = geometry_sys();
+ updateFullFrameMargins();
QWindowSystemInterface::handleGeometryChange(window(), m_data.geometry);
// QTBUG-32121: OpenGL/normal windows (with exception of ANGLE
// which we no longer support in Qt 6) do not receive expose
@@ -2215,6 +2294,9 @@ void QWindowsWindow::handleGeometryChange()
if (testFlag(SynchronousGeometryChangeEvent))
QWindowSystemInterface::flushWindowSystemEvents(QEventLoop::ExcludeUserInputEvents);
+ if (!testFlag(ResizeMoveActive))
+ updateRestoreGeometry();
+
if (!wasSync)
clearFlag(SynchronousGeometryChangeEvent);
qCDebug(lcQpaEvents) << __FUNCTION__ << this << window() << m_data.geometry;
@@ -2225,7 +2307,7 @@ void QWindowsBaseWindow::setGeometry_sys(const QRect &rect) const
const QMargins margins = fullFrameMargins();
const QRect frameGeometry = rect + margins;
- qCDebug(lcQpaWindows) << '>' << __FUNCTION__ << window()
+ qCDebug(lcQpaWindow) << '>' << __FUNCTION__ << window()
<< "\n from " << geometry_sys() << " frame: "
<< margins << " to " <<rect
<< " new frame: " << frameGeometry;
@@ -2256,7 +2338,7 @@ void QWindowsBaseWindow::setGeometry_sys(const QRect &rect) const
result = MoveWindow(hwnd, x, frameGeometry.y(),
frameGeometry.width(), frameGeometry.height(), true);
}
- qCDebug(lcQpaWindows) << '<' << __FUNCTION__ << window()
+ qCDebug(lcQpaWindow) << '<' << __FUNCTION__ << window()
<< "\n resulting " << result << geometry_sys();
}
@@ -2342,7 +2424,7 @@ void QWindowsWindow::setWindowTitle(const QString &title)
void QWindowsWindow::setWindowFlags(Qt::WindowFlags flags)
{
- qCDebug(lcQpaWindows) << '>' << __FUNCTION__ << this << window() << "\n from: "
+ qCDebug(lcQpaWindow) << '>' << __FUNCTION__ << this << window() << "\n from: "
<< m_data.flags << "\n to: " << flags;
const QRect oldGeometry = geometry();
if (m_data.flags != flags) {
@@ -2360,7 +2442,7 @@ void QWindowsWindow::setWindowFlags(Qt::WindowFlags flags)
if (oldGeometry != newGeometry)
handleGeometryChange();
- qCDebug(lcQpaWindows) << '<' << __FUNCTION__ << "\n returns: "
+ qCDebug(lcQpaWindow) << '<' << __FUNCTION__ << "\n returns: "
<< m_data.flags << " geometry " << oldGeometry << "->" << newGeometry;
}
@@ -2375,13 +2457,14 @@ QWindowsWindowData QWindowsWindow::setWindowFlags_sys(Qt::WindowFlags wt,
QWindowsWindowData result = m_data;
result.flags = creationData.flags;
result.embedded = creationData.embedded;
- result.hasFrame = (creationData.style & (WS_DLGFRAME | WS_THICKFRAME));
+ result.hasFrame = (creationData.style & (WS_DLGFRAME | WS_THICKFRAME))
+ && !(creationData.flags & Qt::FramelessWindowHint);
return result;
}
void QWindowsWindow::handleWindowStateChange(Qt::WindowStates state)
{
- qCDebug(lcQpaWindows) << __FUNCTION__ << this << window()
+ qCDebug(lcQpaWindow) << __FUNCTION__ << this << window()
<< "\n from " << m_windowState << " to " << state;
m_windowState = state;
QWindowSystemInterface::handleWindowStateChanged(window(), state);
@@ -2389,6 +2472,14 @@ void QWindowsWindow::handleWindowStateChange(Qt::WindowStates state)
handleHidden();
QWindowSystemInterface::flushWindowSystemEvents(QEventLoop::ExcludeUserInputEvents); // Tell QQuickWindow to stop rendering now.
} else {
+ if (state & Qt::WindowMaximized) {
+ WINDOWPLACEMENT windowPlacement{};
+ windowPlacement.length = sizeof(WINDOWPLACEMENT);
+ GetWindowPlacement(m_data.hwnd, &windowPlacement);
+ const RECT geometry = RECTfromQRect(m_data.restoreGeometry);
+ windowPlacement.rcNormalPosition = geometry;
+ SetWindowPlacement(m_data.hwnd, &windowPlacement);
+ }
// QTBUG-17548: We send expose events when receiving WM_Paint, but for
// layered windows and transient children, we won't receive any WM_Paint.
QWindow *w = window();
@@ -2412,6 +2503,11 @@ void QWindowsWindow::handleWindowStateChange(Qt::WindowStates state)
}
}
+void QWindowsWindow::updateRestoreGeometry()
+{
+ m_data.restoreGeometry = normalFrameGeometry(m_data.hwnd);
+}
+
void QWindowsWindow::setWindowState(Qt::WindowStates state)
{
if (m_data.hwnd) {
@@ -2448,7 +2544,7 @@ void QWindowsWindow::setWindowState_sys(Qt::WindowStates newState)
const Qt::WindowStates oldState = m_windowState;
if (oldState == newState)
return;
- qCDebug(lcQpaWindows) << '>' << __FUNCTION__ << this << window()
+ qCDebug(lcQpaWindow) << '>' << __FUNCTION__ << this << window()
<< " from " << oldState << " to " << newState;
const bool visible = isVisible();
@@ -2456,11 +2552,7 @@ void QWindowsWindow::setWindowState_sys(Qt::WindowStates newState)
if (stateChange & Qt::WindowFullScreen) {
if (newState & Qt::WindowFullScreen) {
-#ifndef Q_FLATTEN_EXPOSE
UINT newStyle = WS_CLIPCHILDREN | WS_CLIPSIBLINGS | WS_POPUP;
-#else
- UINT newStyle = WS_POPUP;
-#endif
// Save geometry and style to be restored when fullscreen
// is turned off again, since on Windows, it is not a real
// Window state but emulated by changing geometry and style.
@@ -2483,26 +2575,26 @@ void QWindowsWindow::setWindowState_sys(Qt::WindowStates newState)
if (testFlag(HasBorderInFullScreen))
newStyle |= WS_BORDER;
setStyle(newStyle);
- // Use geometry of QWindow::screen() within creation or the virtual screen the
- // window is in (QTBUG-31166, QTBUG-30724).
- const QScreen *screen = window()->screen();
- if (!screen)
- screen = QGuiApplication::primaryScreen();
- const QRect r = screen ? QHighDpi::toNativePixels(screen->geometry(), window()) : m_savedFrameGeometry;
-
+ const HMONITOR monitor = MonitorFromWindow(m_data.hwnd, MONITOR_DEFAULTTONEAREST);
+ MONITORINFO monitorInfo = {};
+ monitorInfo.cbSize = sizeof(MONITORINFO);
+ GetMonitorInfoW(monitor, &monitorInfo);
+ const QRect screenGeometry(monitorInfo.rcMonitor.left, monitorInfo.rcMonitor.top,
+ monitorInfo.rcMonitor.right - monitorInfo.rcMonitor.left,
+ monitorInfo.rcMonitor.bottom - monitorInfo.rcMonitor.top);
if (newState & Qt::WindowMinimized) {
- setMinimizedGeometry(m_data.hwnd, r);
+ setMinimizedGeometry(m_data.hwnd, screenGeometry);
if (stateChange & Qt::WindowMaximized)
setRestoreMaximizedFlag(m_data.hwnd, newState & Qt::WindowMaximized);
} else {
const UINT swpf = SWP_FRAMECHANGED | SWP_NOACTIVATE;
const bool wasSync = testFlag(SynchronousGeometryChangeEvent);
setFlag(SynchronousGeometryChangeEvent);
- SetWindowPos(m_data.hwnd, HWND_TOP, r.left(), r.top(), r.width(), r.height(), swpf);
+ SetWindowPos(m_data.hwnd, HWND_TOP, screenGeometry.left(), screenGeometry.top(), screenGeometry.width(), screenGeometry.height(), swpf);
if (!wasSync)
clearFlag(SynchronousGeometryChangeEvent);
clearFlag(MaximizeToFullScreen);
- QWindowSystemInterface::handleGeometryChange(window(), r);
+ QWindowSystemInterface::handleGeometryChange(window(), screenGeometry);
QWindowSystemInterface::flushWindowSystemEvents(QEventLoop::ExcludeUserInputEvents);
}
} else {
@@ -2574,12 +2666,12 @@ void QWindowsWindow::setWindowState_sys(Qt::WindowStates newState)
setRestoreMaximizedFlag(m_data.hwnd, newState & Qt::WindowMaximized);
}
}
- qCDebug(lcQpaWindows) << '<' << __FUNCTION__ << this << window() << newState;
+ qCDebug(lcQpaWindow) << '<' << __FUNCTION__ << this << window() << newState;
}
void QWindowsWindow::setStyle(unsigned s) const
{
- qCDebug(lcQpaWindows) << __FUNCTION__ << this << window() << debugWinStyle(s);
+ qCDebug(lcQpaWindow) << __FUNCTION__ << this << window() << debugWinStyle(s);
setFlag(WithinSetStyle);
SetWindowLongPtr(m_data.hwnd, GWL_STYLE, s);
clearFlag(WithinSetStyle);
@@ -2587,13 +2679,16 @@ void QWindowsWindow::setStyle(unsigned s) const
void QWindowsWindow::setExStyle(unsigned s) const
{
- qCDebug(lcQpaWindows) << __FUNCTION__ << this << window() << debugWinExStyle(s);
+ qCDebug(lcQpaWindow) << __FUNCTION__ << this << window() << debugWinExStyle(s);
SetWindowLongPtr(m_data.hwnd, GWL_EXSTYLE, s);
}
bool QWindowsWindow::windowEvent(QEvent *event)
{
switch (event->type()) {
+ case QEvent::ApplicationPaletteChange:
+ setDarkBorder(QWindowsContext::isDarkMode());
+ break;
case QEvent::WindowBlocked: // Blocked by another modal window.
setEnabled(false);
setFlag(BlockedByModal);
@@ -2613,16 +2708,23 @@ bool QWindowsWindow::windowEvent(QEvent *event)
void QWindowsWindow::propagateSizeHints()
{
- qCDebug(lcQpaWindows) << __FUNCTION__ << this << window();
+ qCDebug(lcQpaWindow) << __FUNCTION__ << this << window();
}
bool QWindowsWindow::handleGeometryChangingMessage(MSG *message, const QWindow *qWindow, const QMargins &margins)
{
auto *windowPos = reinterpret_cast<WINDOWPOS *>(message->lParam);
+ const QRect suggestedFrameGeometry(windowPos->x, windowPos->y,
+ windowPos->cx, windowPos->cy);
+ const QRect suggestedGeometry = suggestedFrameGeometry - margins;
// Tell Windows to discard the entire contents of the client area, as re-using
// parts of the client area would lead to jitter during resize.
- windowPos->flags |= SWP_NOCOPYBITS;
+ // Check the suggestedGeometry against the current one to only discard during
+ // resize, and not a plain move. We also look for SWP_NOSIZE since that, too,
+ // implies an identical size, and comparing QRects wouldn't work with null cx/cy
+ if (!(windowPos->flags & SWP_NOSIZE) && suggestedGeometry.size() != qWindow->geometry().size())
+ windowPos->flags |= SWP_NOCOPYBITS;
if ((windowPos->flags & SWP_NOZORDER) == 0) {
if (QWindowsWindow *platformWindow = QWindowsWindow::windowsWindowOf(qWindow)) {
@@ -2638,9 +2740,6 @@ bool QWindowsWindow::handleGeometryChangingMessage(MSG *message, const QWindow *
return false;
if (windowPos->flags & SWP_NOSIZE)
return false;
- const QRect suggestedFrameGeometry(windowPos->x, windowPos->y,
- windowPos->cx, windowPos->cy);
- const QRect suggestedGeometry = suggestedFrameGeometry - margins;
const QRectF correctedGeometryF = QPlatformWindow::closestAcceptableGeometry(qWindow, suggestedGeometry);
if (!correctedGeometryF.isValid())
return false;
@@ -2662,8 +2761,10 @@ bool QWindowsWindow::handleGeometryChanging(MSG *message) const
void QWindowsWindow::setFullFrameMargins(const QMargins &newMargins)
{
+ if (shouldOmitFrameAdjustment(m_data.flags, m_data.hwnd))
+ return;
if (m_data.fullFrameMargins != newMargins) {
- qCDebug(lcQpaWindows) << __FUNCTION__ << window() << m_data.fullFrameMargins << "->" << newMargins;
+ qCDebug(lcQpaWindow) << __FUNCTION__ << window() << m_data.fullFrameMargins << "->" << newMargins;
m_data.fullFrameMargins = newMargins;
}
}
@@ -2679,12 +2780,46 @@ void QWindowsWindow::updateFullFrameMargins()
void QWindowsWindow::calculateFullFrameMargins()
{
+ if (shouldOmitFrameAdjustment(m_data.flags, m_data.hwnd))
+ return;
+
+ // QTBUG-113736: systemMargins depends on AdjustWindowRectExForDpi. This doesn't take into
+ // account possible external modifications to the titlebar, as with ExtendsContentIntoTitleBar()
+ // from the Windows App SDK. We can fix this by comparing the WindowRect (which includes the
+ // frame) to the ClientRect. If a 'typical' frame is detected, i.e. only the titlebar has been
+ // modified, we can safely adjust the frame by deducting the bottom margin to the total Y
+ // difference between the two rects, to get the actual size of the titlebar and prevent
+ // unwanted client area slicing.
+
+ RECT windowRect{};
+ RECT clientRect{};
+ GetWindowRect(handle(), &windowRect);
+ GetClientRect(handle(), &clientRect);
+
+ // QTBUG-117704 It is also possible that the user has manually removed the frame (for example
+ // by handling WM_NCCALCSIZE). If that is the case, i.e., the client area and the window area
+ // have identical sizes, we don't want to override the user-defined margins.
+
+ if (qrectFromRECT(windowRect).size() == qrectFromRECT(clientRect).size())
+ return;
+
// Normally obtained from WM_NCCALCSIZE. This calculation only works
// when no native menu is present.
const auto systemMargins = testFlag(DisableNonClientScaling)
? QWindowsGeometryHint::frameOnPrimaryScreen(window(), m_data.hwnd)
: frameMargins_sys();
- setFullFrameMargins(systemMargins + m_data.customMargins);
+ const QMargins actualMargins = systemMargins + customMargins();
+
+ const int yDiff = (windowRect.bottom - windowRect.top) - (clientRect.bottom - clientRect.top);
+ const bool typicalFrame = (actualMargins.left() == actualMargins.right())
+ && (actualMargins.right() == actualMargins.bottom());
+
+ const QMargins adjustedMargins = typicalFrame ?
+ QMargins(actualMargins.left(), (yDiff - actualMargins.bottom()),
+ actualMargins.right(), actualMargins.bottom())
+ : actualMargins;
+
+ setFullFrameMargins(adjustedMargins);
}
QMargins QWindowsWindow::frameMargins() const
@@ -2697,12 +2832,14 @@ QMargins QWindowsWindow::frameMargins() const
QMargins QWindowsWindow::fullFrameMargins() const
{
+ if (shouldOmitFrameAdjustment(m_data.flags, m_data.hwnd))
+ return {};
return m_data.fullFrameMargins;
}
void QWindowsWindow::setOpacity(qreal level)
{
- qCDebug(lcQpaWindows) << __FUNCTION__ << level;
+ qCDebug(lcQpaWindow) << __FUNCTION__ << level;
if (!qFuzzyCompare(m_opacity, level)) {
m_opacity = level;
if (m_data.hwnd)
@@ -2762,41 +2899,76 @@ void QWindowsWindow::setMask(const QRegion &region)
void QWindowsWindow::requestActivateWindow()
{
- qCDebug(lcQpaWindows) << __FUNCTION__ << this << window();
- // 'Active' state handling is based in focus since it needs to work for
- // child windows as well.
- if (m_data.hwnd) {
- const DWORD currentThread = GetCurrentThreadId();
- bool attached = false;
- DWORD foregroundThread = 0;
-
- // QTBUG-14062, QTBUG-37435: Windows normally only flashes the taskbar entry
- // when activating windows of inactive applications. Attach to the input of the
- // currently active window while setting the foreground window to always activate
- // the window when desired.
- const auto activationBehavior = QWindowsIntegration::instance()->windowActivationBehavior();
- if (QGuiApplication::applicationState() != Qt::ApplicationActive
- && activationBehavior == QWindowsApplication::AlwaysActivateWindow) {
- if (const HWND foregroundWindow = GetForegroundWindow()) {
- foregroundThread = GetWindowThreadProcessId(foregroundWindow, nullptr);
- if (foregroundThread && foregroundThread != currentThread)
- attached = AttachThreadInput(foregroundThread, currentThread, TRUE) == TRUE;
- if (attached) {
- if (!window()->flags().testFlag(Qt::WindowStaysOnBottomHint)
- && !window()->flags().testFlag(Qt::WindowStaysOnTopHint)
- && window()->type() != Qt::ToolTip) {
- const UINT swpFlags = SWP_NOMOVE | SWP_NOSIZE | SWP_NOOWNERZORDER;
- SetWindowPos(m_data.hwnd, HWND_TOPMOST, 0, 0, 0, 0, swpFlags);
- SetWindowPos(m_data.hwnd, HWND_NOTOPMOST, 0, 0, 0, 0, swpFlags);
- }
- }
- }
- }
+ qCDebug(lcQpaWindow) << __FUNCTION__ << this << window();
+
+ if (!m_data.hwnd)
+ return;
+
+ const auto activationBehavior = QWindowsIntegration::instance()->windowActivationBehavior();
+ if (QGuiApplication::applicationState() == Qt::ApplicationActive
+ || activationBehavior != QWindowsApplication::AlwaysActivateWindow) {
SetForegroundWindow(m_data.hwnd);
SetFocus(m_data.hwnd);
- if (attached)
- AttachThreadInput(foregroundThread, currentThread, FALSE);
+ return;
+ }
+
+ // Force activate this window. The following code will bring the window to the
+ // foreground and activate it. If the window is hidden, it will show up. If
+ // the window is minimized, it will restore to the previous position.
+
+ // But first we need some sanity checks.
+ if (m_data.flags & Qt::WindowStaysOnBottomHint) {
+ qCWarning(lcQpaWindow) <<
+ "Windows with Qt::WindowStaysOnBottomHint can't be brought to the foreground.";
+ return;
+ }
+ if (m_data.flags & Qt::WindowStaysOnTopHint) {
+ qCWarning(lcQpaWindow) <<
+ "Windows with Qt::WindowStaysOnTopHint will always be on the foreground.";
+ return;
+ }
+ if (window()->type() == Qt::ToolTip) {
+ qCWarning(lcQpaWindow) << "ToolTip windows should not be activated.";
+ return;
+ }
+
+ // We need to show the window first, otherwise we won't be able to bring it to front.
+ if (!IsWindowVisible(m_data.hwnd))
+ ShowWindow(m_data.hwnd, SW_SHOW);
+
+ if (IsIconic(m_data.hwnd)) {
+ ShowWindow(m_data.hwnd, SW_RESTORE);
+ // When the window is restored, it will always become the foreground window.
+ // So return early here, we don't need the following code to bring it to front.
+ return;
}
+
+ // OK, our window is not minimized, so now we will try to bring it to front manually.
+ const HWND oldForegroundWindow = GetForegroundWindow();
+ if (!oldForegroundWindow) // It may be NULL, according to MS docs.
+ return;
+
+ // First try to send a message to the current foreground window to check whether
+ // it is currently hanging or not.
+ if (SendMessageTimeoutW(oldForegroundWindow, WM_NULL, 0, 0,
+ SMTO_BLOCK | SMTO_ABORTIFHUNG | SMTO_NOTIMEOUTIFNOTHUNG, 1000, nullptr) == 0) {
+ qCWarning(lcQpaWindow) << "The foreground window hangs, can't activate current window.";
+ return;
+ }
+
+ const DWORD windowThreadProcessId = GetWindowThreadProcessId(oldForegroundWindow, nullptr);
+ const DWORD currentThreadId = GetCurrentThreadId();
+
+ AttachThreadInput(windowThreadProcessId, currentThreadId, TRUE);
+ const auto cleanup = qScopeGuard([windowThreadProcessId, currentThreadId](){
+ AttachThreadInput(windowThreadProcessId, currentThreadId, FALSE);
+ });
+
+ BringWindowToTop(m_data.hwnd);
+
+ // Activate the window too. This will force us to the virtual desktop this
+ // window is on, if it's on another virtual desktop.
+ SetActiveWindow(m_data.hwnd);
}
bool QWindowsWindow::setKeyboardGrabEnabled(bool grab)
@@ -2805,7 +2977,7 @@ bool QWindowsWindow::setKeyboardGrabEnabled(bool grab)
qWarning("%s: No handle", __FUNCTION__);
return false;
}
- qCDebug(lcQpaWindows) << __FUNCTION__ << this << window() << grab;
+ qCDebug(lcQpaWindow) << __FUNCTION__ << this << window() << grab;
QWindowsContext *context = QWindowsContext::instance();
if (grab) {
@@ -2819,7 +2991,7 @@ bool QWindowsWindow::setKeyboardGrabEnabled(bool grab)
bool QWindowsWindow::setMouseGrabEnabled(bool grab)
{
- qCDebug(lcQpaWindows) << __FUNCTION__ << window() << grab;
+ qCDebug(lcQpaWindow) << __FUNCTION__ << window() << grab;
if (!m_data.hwnd) {
qWarning("%s: No handle", __FUNCTION__);
return false;
@@ -2894,37 +3066,7 @@ void QWindowsWindow::setFrameStrutEventsEnabled(bool enabled)
void QWindowsWindow::getSizeHints(MINMAXINFO *mmi) const
{
QWindowsGeometryHint::applyToMinMaxInfo(window(), fullFrameMargins(), mmi);
-
- // This block fixes QTBUG-8361, QTBUG-4362: Frameless/title-less windows shouldn't cover the
- // taskbar when maximized
- if (m_data.flags.testFlag(Qt::FramelessWindowHint)
- || (m_data.flags.testFlag(Qt::CustomizeWindowHint) && !m_data.flags.testFlag(Qt::WindowTitleHint))) {
- if (QPlatformScreen *currentScreen = screen()) {
- const QRect geometry = currentScreen->geometry();
- const QRect availableGeometry = currentScreen->availableGeometry();
- mmi->ptMaxSize.y = availableGeometry.height();
-
- // Width, because you can have the taskbar on the sides too.
- mmi->ptMaxSize.x = availableGeometry.width();
-
- // If you have the taskbar on top, or on the left you don't want it at (0,0):
- QPoint availablePositionDiff = availableGeometry.topLeft() - geometry.topLeft();
- mmi->ptMaxPosition.x = availablePositionDiff.x();
- mmi->ptMaxPosition.y = availablePositionDiff.y();
- if (!m_data.flags.testFlag(Qt::FramelessWindowHint)) {
- const int borderWidth = invisibleMargins(m_data.hwnd).left();
- mmi->ptMaxSize.x += borderWidth * 2;
- mmi->ptMaxSize.y += borderWidth * 2;
- mmi->ptMaxTrackSize = mmi->ptMaxSize;
- mmi->ptMaxPosition.x -= borderWidth;
- mmi->ptMaxPosition.y -= borderWidth;
- }
- } else {
- qWarning("screen() returned a null screen");
- }
- }
-
- qCDebug(lcQpaWindows) << __FUNCTION__ << window() << *mmi;
+ qCDebug(lcQpaWindow) << __FUNCTION__ << window() << *mmi;
}
bool QWindowsWindow::handleNonClientHitTest(const QPoint &globalPos, LRESULT *result) const
@@ -3036,7 +3178,7 @@ void QWindowsWindow::setCursor(const CursorHandlePtr &c)
}
if (changed) {
const bool apply = applyNewCursor(window());
- qCDebug(lcQpaWindows) << window() << __FUNCTION__
+ qCDebug(lcQpaWindow) << window() << __FUNCTION__
<< c->handle() << " doApply=" << apply;
m_cursor = c;
if (apply)
@@ -3148,7 +3290,7 @@ static bool queryDarkBorder(HWND hwnd)
SUCCEEDED(DwmGetWindowAttribute(hwnd, DwmwaUseImmersiveDarkMode, &result, sizeof(result)))
|| SUCCEEDED(DwmGetWindowAttribute(hwnd, DwmwaUseImmersiveDarkModeBefore20h1, &result, sizeof(result)));
if (!ok)
- qWarning("%s: Unable to retrieve dark window border setting.", __FUNCTION__);
+ qCWarning(lcQpaWindow, "%s: Unable to retrieve dark window border setting.", __FUNCTION__);
return result == TRUE;
}
@@ -3159,14 +3301,18 @@ bool QWindowsWindow::setDarkBorderToWindow(HWND hwnd, bool d)
SUCCEEDED(DwmSetWindowAttribute(hwnd, DwmwaUseImmersiveDarkMode, &darkBorder, sizeof(darkBorder)))
|| SUCCEEDED(DwmSetWindowAttribute(hwnd, DwmwaUseImmersiveDarkModeBefore20h1, &darkBorder, sizeof(darkBorder)));
if (!ok)
- qWarning("%s: Unable to set dark window border.", __FUNCTION__);
+ qCWarning(lcQpaWindow, "%s: Unable to set %s window border.", __FUNCTION__, d ? "dark" : "light");
return ok;
}
void QWindowsWindow::setDarkBorder(bool d)
{
- if (shouldApplyDarkFrame(window()) && queryDarkBorder(m_data.hwnd) != d)
- setDarkBorderToWindow(m_data.hwnd, d);
+ // respect explicit opt-out and incompatible palettes or styles
+ d = d && shouldApplyDarkFrame(window());
+ if (queryDarkBorder(m_data.hwnd) == d)
+ return;
+
+ setDarkBorderToWindow(m_data.hwnd, d);
}
QWindowsMenuBar *QWindowsWindow::menuBar() const
@@ -3179,6 +3325,13 @@ void QWindowsWindow::setMenuBar(QWindowsMenuBar *mb)
m_menuBar = mb;
}
+QMargins QWindowsWindow::customMargins() const
+{
+ if (m_data.flags & Qt::FramelessWindowHint)
+ return {};
+ return m_data.customMargins;
+}
+
/*!
\brief Sets custom margins to be added to the default margins determined by
the windows style in the handling of the WM_NCCALCSIZE message.
@@ -3191,6 +3344,10 @@ void QWindowsWindow::setMenuBar(QWindowsMenuBar *mb)
void QWindowsWindow::setCustomMargins(const QMargins &newCustomMargins)
{
+ if (m_data.flags & Qt::FramelessWindowHint) {
+ qCWarning(lcQpaWindow) << "You should not set custom margins for a frameless window.";
+ return;
+ }
if (newCustomMargins != m_data.customMargins) {
const QMargins oldCustomMargins = m_data.customMargins;
m_data.customMargins = newCustomMargins;
@@ -3199,7 +3356,7 @@ void QWindowsWindow::setCustomMargins(const QMargins &newCustomMargins)
const QPoint topLeft = currentFrameGeometry.topLeft();
QRect newFrame = currentFrameGeometry.marginsRemoved(oldCustomMargins) + m_data.customMargins;
newFrame.moveTo(topLeft);
- qCDebug(lcQpaWindows) << __FUNCTION__ << oldCustomMargins << "->" << newCustomMargins
+ qCDebug(lcQpaWindow) << __FUNCTION__ << oldCustomMargins << "->" << newCustomMargins
<< currentFrameGeometry << "->" << newFrame;
SetWindowPos(m_data.hwnd, nullptr, newFrame.x(), newFrame.y(), newFrame.width(), newFrame.height(), SWP_NOZORDER | SWP_FRAMECHANGED | SWP_NOACTIVATE);
}
@@ -3282,24 +3439,6 @@ void QWindowsWindow::registerTouchWindow()
qErrnoWarning("RegisterTouchWindow() failed for window '%s'.", qPrintable(window()->objectName()));
}
-void QWindowsWindow::aboutToMakeCurrent()
-{
-#ifndef QT_NO_OPENGL
- // For RasterGLSurface windows, that become OpenGL windows dynamically, it might be
- // time to set up some GL specifics. This is particularly important for layered
- // windows (WS_EX_LAYERED due to alpha > 0).
- const bool isCompositing = qt_window_private(window())->compositing;
- if (isCompositing != testFlag(Compositing)) {
- if (isCompositing)
- setFlag(Compositing);
- else
- clearFlag(Compositing);
-
- updateGLWindowSettings(window(), m_data.hwnd, m_data.flags, m_opacity);
- }
-#endif
-}
-
void QWindowsWindow::setHasBorderInFullScreenStatic(QWindow *window, bool border)
{
if (QPlatformWindow *handle = window->handle())
diff --git a/src/plugins/platforms/windows/qwindowswindow.h b/src/plugins/platforms/windows/qwindowswindow.h
index 29dfbdb856..024711e7f3 100644
--- a/src/plugins/platforms/windows/qwindowswindow.h
+++ b/src/plugins/platforms/windows/qwindowswindow.h
@@ -78,6 +78,7 @@ struct QWindowsWindowData
{
Qt::WindowFlags flags;
QRect geometry;
+ QRect restoreGeometry;
QMargins fullFrameMargins; // Do not use directly for windows, see FrameDirty.
QMargins customMargins; // User-defined, additional frame for NCCALCSIZE
HWND hwnd = nullptr;
@@ -218,6 +219,8 @@ public:
void setGeometry(const QRect &rect) override;
QRect geometry() const override { return m_data.geometry; }
QRect normalGeometry() const override;
+ QRect restoreGeometry() const { return m_data.restoreGeometry; }
+ void updateRestoreGeometry();
void setVisible(bool visible) override;
bool isVisible() const;
@@ -272,7 +275,7 @@ public:
QWindowsMenuBar *menuBar() const;
void setMenuBar(QWindowsMenuBar *mb);
- QMargins customMargins() const override { return m_data.customMargins; }
+ QMargins customMargins() const override;
void setCustomMargins(const QMargins &m) override;
void setStyle(unsigned s) const;
@@ -281,7 +284,7 @@ public:
bool handleWmPaint(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam, LRESULT *result);
void handleMoved();
- void handleResized(int wParam);
+ void handleResized(int wParam, LPARAM lParam);
void handleHidden();
void handleCompositionSettingsChanged();
void handleDpiScaledSize(WPARAM wParam, LPARAM lParam, LRESULT *result);
@@ -296,6 +299,7 @@ public:
static inline void *userDataOf(HWND hwnd);
static inline void setUserDataOf(HWND hwnd, void *ud);
+ static bool hasNoNativeFrame(HWND hwnd, Qt::WindowFlags flags);
static bool setWindowLayered(HWND hwnd, Qt::WindowFlags flags, bool hasAlpha, qreal opacity);
bool isLayered() const;
@@ -320,7 +324,6 @@ public:
void *surface(void *nativeConfig, int *err);
void invalidateSurface() override;
- void aboutToMakeCurrent();
void setAlertState(bool enabled) override;
bool isAlertState() const override { return testFlag(AlertState); }
@@ -342,6 +345,7 @@ public:
void setSavedDpi(int dpi) { m_savedDpi = dpi; }
int savedDpi() const { return m_savedDpi; }
+ qreal dpiRelativeScale(const UINT dpi) const;
private:
inline void show_sys() const;
diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiaaccessibility.cpp b/src/plugins/platforms/windows/uiautomation/qwindowsuiaaccessibility.cpp
index 001cb8505b..1abb412ccd 100644
--- a/src/plugins/platforms/windows/uiautomation/qwindowsuiaaccessibility.cpp
+++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiaaccessibility.cpp
@@ -5,6 +5,7 @@
#if QT_CONFIG(accessibility)
#include "qwindowsuiaaccessibility.h"
+#include "qwindowsuiautomation.h"
#include "qwindowsuiamainprovider.h"
#include "qwindowsuiautils.h"
@@ -14,13 +15,13 @@
#include <QtGui/private/qguiapplication_p.h>
#include <QtCore/qt_windows.h>
#include <qpa/qplatformintegration.h>
-#include <QtGui/private/qwindowsuiawrapper_p.h>
#include <QtCore/private/qwinregistry_p.h>
QT_BEGIN_NAMESPACE
using namespace QWindowsUiAutomation;
+using namespace Qt::Literals::StringLiterals;
bool QWindowsUiaAccessibility::m_accessibleActive = false;
@@ -46,7 +47,7 @@ bool QWindowsUiaAccessibility::handleWmGetObject(HWND hwnd, WPARAM wParam, LPARA
if (QWindow *window = QWindowsContext::instance()->findWindow(hwnd)) {
if (QAccessibleInterface *accessible = window->accessibleRoot()) {
QWindowsUiaMainProvider *provider = QWindowsUiaMainProvider::providerForAccessible(accessible);
- *lResult = QWindowsUiaWrapper::instance()->returnRawElementProvider(hwnd, wParam, lParam, provider);
+ *lResult = UiaReturnRawElementProvider(hwnd, wParam, lParam, provider);
return true;
}
}
@@ -78,8 +79,8 @@ static QString alertSound(const QObject *object)
static QString soundFileName(const QString &soundName)
{
- const QString key = QStringLiteral("AppEvents\\Schemes\\Apps\\.Default\\")
- + soundName + QStringLiteral("\\.Current");
+ const QString key = "AppEvents\\Schemes\\Apps\\.Default\\"_L1
+ + soundName + "\\.Current"_L1;
return QWinRegistryKey(HKEY_CURRENT_USER, key).stringValue(L"");
}
@@ -97,11 +98,7 @@ void QWindowsUiaAccessibility::notifyAccessibilityUpdate(QAccessibleEvent *event
if (!event)
return;
- // Ignore events sent before the first UI Automation
- // request or while QAccessible is being activated.
- if (!m_accessibleActive)
- return;
-
+ // Always handle system sound events
switch (event->type()) {
case QAccessible::PopupMenuStart:
playSystemSound(QStringLiteral("MenuPopup"));
@@ -116,16 +113,17 @@ void QWindowsUiaAccessibility::notifyAccessibilityUpdate(QAccessibleEvent *event
break;
}
- QAccessibleInterface *accessible = event->accessibleInterface();
- if (!isActive() || !accessible || !accessible->isValid())
+ // Ignore events sent before the first UI Automation
+ // request or while QAccessible is being activated.
+ if (!m_accessibleActive)
return;
- // Ensures QWindowsUiaWrapper is properly initialized.
- if (!QWindowsUiaWrapper::instance()->ready())
+ QAccessibleInterface *accessible = event->accessibleInterface();
+ if (!isActive() || !accessible || !accessible->isValid())
return;
// No need to do anything when nobody is listening.
- if (!QWindowsUiaWrapper::instance()->clientsAreListening())
+ if (!UiaClientsAreListening())
return;
switch (event->type()) {
diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiaaccessibility.h b/src/plugins/platforms/windows/uiautomation/qwindowsuiaaccessibility.h
index 1813bb4d89..2e8ee585da 100644
--- a/src/plugins/platforms/windows/uiautomation/qwindowsuiaaccessibility.h
+++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiaaccessibility.h
@@ -7,6 +7,7 @@
#include <QtGui/qtguiglobal.h>
#if QT_CONFIG(accessibility)
+#include <QtCore/qt_windows.h>
#include "qwindowscontext.h"
#include <qpa/qplatformaccessibility.h>
diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiabaseprovider.h b/src/plugins/platforms/windows/uiautomation/qwindowsuiabaseprovider.h
index c899b4096e..032679ab10 100644
--- a/src/plugins/platforms/windows/uiautomation/qwindowsuiabaseprovider.h
+++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiabaseprovider.h
@@ -10,8 +10,8 @@
#include <QtGui/qaccessible.h>
#include <QtCore/qpointer.h>
-#include <qwindowscombase.h>
-#include <QtGui/private/qwindowsuiawrapper_p.h>
+#include "qwindowsuiautomation.h"
+#include <QtCore/private/qcomobject_p.h>
QT_BEGIN_NAMESPACE
diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiaexpandcollapseprovider.h b/src/plugins/platforms/windows/uiautomation/qwindowsuiaexpandcollapseprovider.h
index 49b37ba295..b384eb521c 100644
--- a/src/plugins/platforms/windows/uiautomation/qwindowsuiaexpandcollapseprovider.h
+++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiaexpandcollapseprovider.h
@@ -13,7 +13,7 @@ QT_BEGIN_NAMESPACE
// Implements the Expand/Collapse control pattern provider. Used for menu items with submenus.
class QWindowsUiaExpandCollapseProvider : public QWindowsUiaBaseProvider,
- public QWindowsComBase<IExpandCollapseProvider>
+ public QComObject<IExpandCollapseProvider>
{
Q_DISABLE_COPY_MOVE(QWindowsUiaExpandCollapseProvider)
public:
diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiagriditemprovider.h b/src/plugins/platforms/windows/uiautomation/qwindowsuiagriditemprovider.h
index b501616966..289a867869 100644
--- a/src/plugins/platforms/windows/uiautomation/qwindowsuiagriditemprovider.h
+++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiagriditemprovider.h
@@ -13,7 +13,7 @@ QT_BEGIN_NAMESPACE
// Implements the Grid Item control pattern provider. Used by items within a table/tree.
class QWindowsUiaGridItemProvider : public QWindowsUiaBaseProvider,
- public QWindowsComBase<IGridItemProvider>
+ public QComObject<IGridItemProvider>
{
Q_DISABLE_COPY_MOVE(QWindowsUiaGridItemProvider)
public:
diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiagridprovider.h b/src/plugins/platforms/windows/uiautomation/qwindowsuiagridprovider.h
index b905133f6b..d33bbd0429 100644
--- a/src/plugins/platforms/windows/uiautomation/qwindowsuiagridprovider.h
+++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiagridprovider.h
@@ -12,8 +12,7 @@
QT_BEGIN_NAMESPACE
// Implements the Grid control pattern provider. Used by tables/trees.
-class QWindowsUiaGridProvider : public QWindowsUiaBaseProvider,
- public QWindowsComBase<IGridProvider>
+class QWindowsUiaGridProvider : public QWindowsUiaBaseProvider, public QComObject<IGridProvider>
{
Q_DISABLE_COPY_MOVE(QWindowsUiaGridProvider)
public:
diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiainvokeprovider.h b/src/plugins/platforms/windows/uiautomation/qwindowsuiainvokeprovider.h
index eca7e73039..ec006c673e 100644
--- a/src/plugins/platforms/windows/uiautomation/qwindowsuiainvokeprovider.h
+++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiainvokeprovider.h
@@ -12,8 +12,7 @@
QT_BEGIN_NAMESPACE
// Implements the Invoke control pattern provider.
-class QWindowsUiaInvokeProvider : public QWindowsUiaBaseProvider,
- public QWindowsComBase<IInvokeProvider>
+class QWindowsUiaInvokeProvider : public QWindowsUiaBaseProvider, public QComObject<IInvokeProvider>
{
Q_DISABLE_COPY_MOVE(QWindowsUiaInvokeProvider)
public:
diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiamainprovider.cpp b/src/plugins/platforms/windows/uiautomation/qwindowsuiamainprovider.cpp
index 2d5d91a7ea..29cd1c6237 100644
--- a/src/plugins/platforms/windows/uiautomation/qwindowsuiamainprovider.cpp
+++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiamainprovider.cpp
@@ -27,6 +27,7 @@
#include <QtGui/qaccessible.h>
#include <QtGui/qguiapplication.h>
#include <QtGui/qwindow.h>
+#include <qpa/qplatforminputcontextfactory_p.h>
#if !defined(Q_CC_BOR) && !defined (Q_CC_GNU)
#include <comdef.h>
@@ -38,10 +39,13 @@ QT_BEGIN_NAMESPACE
using namespace QWindowsUiAutomation;
+QMutex QWindowsUiaMainProvider::m_mutex;
// Returns a cached instance of the provider for a specific accessible interface.
QWindowsUiaMainProvider *QWindowsUiaMainProvider::providerForAccessible(QAccessibleInterface *accessible)
{
+ QMutexLocker locker(&m_mutex);
+
if (!accessible)
return nullptr;
@@ -71,12 +75,13 @@ QWindowsUiaMainProvider::~QWindowsUiaMainProvider()
void QWindowsUiaMainProvider::notifyFocusChange(QAccessibleEvent *event)
{
if (QAccessibleInterface *accessible = event->accessibleInterface()) {
- // If this is a table/tree/list, raise event for the focused cell/item instead.
- if (accessible->tableInterface())
+ // If this is a complex element, raise event for the focused child instead.
+ if (accessible->childCount()) {
if (QAccessibleInterface *child = accessible->focusChild())
accessible = child;
+ }
if (QWindowsUiaMainProvider *provider = providerForAccessible(accessible))
- QWindowsUiaWrapper::instance()->raiseAutomationEvent(provider, UIA_AutomationFocusChangedEventId);
+ UiaRaiseAutomationEvent(provider, UIA_AutomationFocusChangedEventId);
}
}
@@ -93,7 +98,7 @@ void QWindowsUiaMainProvider::notifyStateChange(QAccessibleStateChangeEvent *eve
if (accessible->state().checked)
toggleState = accessible->state().checkStateMixed ? ToggleState_Indeterminate : ToggleState_On;
setVariantI4(toggleState, &newVal);
- QWindowsUiaWrapper::instance()->raiseAutomationPropertyChangedEvent(provider, UIA_ToggleToggleStatePropertyId, oldVal, newVal);
+ UiaRaiseAutomationPropertyChangedEvent(provider, UIA_ToggleToggleStatePropertyId, oldVal, newVal);
}
}
}
@@ -102,9 +107,13 @@ void QWindowsUiaMainProvider::notifyStateChange(QAccessibleStateChangeEvent *eve
// Notifies window opened/closed.
if (QWindowsUiaMainProvider *provider = providerForAccessible(accessible)) {
if (accessible->state().active) {
- QWindowsUiaWrapper::instance()->raiseAutomationEvent(provider, UIA_Window_WindowOpenedEventId);
+ UiaRaiseAutomationEvent(provider, UIA_Window_WindowOpenedEventId);
+ if (QAccessibleInterface *focused = accessible->focusChild()) {
+ if (QWindowsUiaMainProvider *focusedProvider = providerForAccessible(focused))
+ UiaRaiseAutomationEvent(focusedProvider, UIA_AutomationFocusChangedEventId);
+ }
} else {
- QWindowsUiaWrapper::instance()->raiseAutomationEvent(provider, UIA_Window_WindowClosedEventId);
+ UiaRaiseAutomationEvent(provider, UIA_Window_WindowClosedEventId);
}
}
}
@@ -133,27 +142,11 @@ void QWindowsUiaMainProvider::notifyValueChange(QAccessibleValueChangeEvent *eve
}
if (event->value().typeId() == QMetaType::QString) {
if (QWindowsUiaMainProvider *provider = providerForAccessible(accessible)) {
-
- // Tries to notify the change using UiaRaiseNotificationEvent(), which is only available on
- // Windows 10 version 1709 or newer. Otherwise uses UiaRaiseAutomationPropertyChangedEvent().
-
- BSTR displayString = bStrFromQString(event->value().toString());
- BSTR activityId = bStrFromQString(QString());
-
- HRESULT hr = QWindowsUiaWrapper::instance()->raiseNotificationEvent(provider, NotificationKind_Other,
- NotificationProcessing_ImportantMostRecent,
- displayString, activityId);
-
- ::SysFreeString(displayString);
- ::SysFreeString(activityId);
-
- if (hr == static_cast<HRESULT>(UIA_E_NOTSUPPORTED)) {
- VARIANT oldVal, newVal;
- clearVariant(&oldVal);
- setVariantString(event->value().toString(), &newVal);
- QWindowsUiaWrapper::instance()->raiseAutomationPropertyChangedEvent(provider, UIA_ValueValuePropertyId, oldVal, newVal);
- ::SysFreeString(newVal.bstrVal);
- }
+ // Notifies changes in string values.
+ VARIANT oldVal, newVal;
+ clearVariant(&oldVal);
+ setVariantString(event->value().toString(), &newVal);
+ UiaRaiseAutomationPropertyChangedEvent(provider, UIA_ValueValuePropertyId, oldVal, newVal);
}
} else if (QAccessibleValueInterface *valueInterface = accessible->valueInterface()) {
if (QWindowsUiaMainProvider *provider = providerForAccessible(accessible)) {
@@ -161,7 +154,7 @@ void QWindowsUiaMainProvider::notifyValueChange(QAccessibleValueChangeEvent *eve
VARIANT oldVal, newVal;
clearVariant(&oldVal);
setVariantDouble(valueInterface->currentValue().toDouble(), &newVal);
- QWindowsUiaWrapper::instance()->raiseAutomationPropertyChangedEvent(provider, UIA_RangeValueValuePropertyId, oldVal, newVal);
+ UiaRaiseAutomationPropertyChangedEvent(provider, UIA_RangeValueValuePropertyId, oldVal, newVal);
}
}
}
@@ -177,7 +170,7 @@ void QWindowsUiaMainProvider::notifyNameChange(QAccessibleEvent *event)
VARIANT oldVal, newVal;
clearVariant(&oldVal);
setVariantString(accessible->text(QAccessible::Name), &newVal);
- QWindowsUiaWrapper::instance()->raiseAutomationPropertyChangedEvent(provider, UIA_NamePropertyId, oldVal, newVal);
+ UiaRaiseAutomationPropertyChangedEvent(provider, UIA_NamePropertyId, oldVal, newVal);
::SysFreeString(newVal.bstrVal);
}
}
@@ -188,7 +181,7 @@ void QWindowsUiaMainProvider::notifySelectionChange(QAccessibleEvent *event)
{
if (QAccessibleInterface *accessible = event->accessibleInterface()) {
if (QWindowsUiaMainProvider *provider = providerForAccessible(accessible)) {
- QWindowsUiaWrapper::instance()->raiseAutomationEvent(provider, UIA_SelectionItem_ElementSelectedEventId);
+ UiaRaiseAutomationEvent(provider, UIA_SelectionItem_ElementSelectedEventId);
}
}
}
@@ -200,13 +193,13 @@ void QWindowsUiaMainProvider::notifyTextChange(QAccessibleEvent *event)
if (accessible->textInterface()) {
if (QWindowsUiaMainProvider *provider = providerForAccessible(accessible)) {
if (event->type() == QAccessible::TextSelectionChanged) {
- QWindowsUiaWrapper::instance()->raiseAutomationEvent(provider, UIA_Text_TextSelectionChangedEventId);
+ UiaRaiseAutomationEvent(provider, UIA_Text_TextSelectionChangedEventId);
} else if (event->type() == QAccessible::TextCaretMoved) {
if (!accessible->state().readOnly) {
- QWindowsUiaWrapper::instance()->raiseAutomationEvent(provider, UIA_Text_TextSelectionChangedEventId);
+ UiaRaiseAutomationEvent(provider, UIA_Text_TextSelectionChangedEventId);
}
} else {
- QWindowsUiaWrapper::instance()->raiseAutomationEvent(provider, UIA_Text_TextChangedEventId);
+ UiaRaiseAutomationEvent(provider, UIA_Text_TextChangedEventId);
}
}
}
@@ -235,6 +228,8 @@ ULONG QWindowsUiaMainProvider::AddRef()
ULONG STDMETHODCALLTYPE QWindowsUiaMainProvider::Release()
{
+ QMutexLocker locker(&m_mutex);
+
if (!--m_ref) {
delete this;
return 0;
@@ -294,15 +289,18 @@ HRESULT QWindowsUiaMainProvider::GetPatternProvider(PATTERNID idPattern, IUnknow
*pRetVal = new QWindowsUiaToggleProvider(id());
break;
case UIA_SelectionPatternId:
- // Lists of items.
- if (accessible->role() == QAccessible::List
+ case UIA_SelectionPattern2Id:
+ // Selections via QAccessibleSelectionInterface or lists of items.
+ if (accessible->selectionInterface()
+ || accessible->role() == QAccessible::List
|| accessible->role() == QAccessible::PageTabList) {
*pRetVal = new QWindowsUiaSelectionProvider(id());
}
break;
case UIA_SelectionItemPatternId:
- // Items within a list and radio buttons.
- if ((accessible->role() == QAccessible::RadioButton)
+ // Parent supports selection interface or items within a list and radio buttons.
+ if ((accessible->parent() && accessible->parent()->selectionInterface())
+ || (accessible->role() == QAccessible::RadioButton)
|| (accessible->role() == QAccessible::ListItem)
|| (accessible->role() == QAccessible::PageTab)) {
*pRetVal = new QWindowsUiaSelectionItemProvider(id());
@@ -359,6 +357,28 @@ HRESULT QWindowsUiaMainProvider::GetPatternProvider(PATTERNID idPattern, IUnknow
return S_OK;
}
+void QWindowsUiaMainProvider::fillVariantArrayForRelation(QAccessibleInterface* accessible,
+ QAccessible::Relation relation, VARIANT *pRetVal)
+{
+ Q_ASSERT(accessible);
+
+ typedef QPair<QAccessibleInterface*, QAccessible::Relation> RelationPair;
+ const QList<RelationPair> relationInterfaces = accessible->relations(relation);
+ if (relationInterfaces.empty())
+ return;
+
+ SAFEARRAY *elements = SafeArrayCreateVector(VT_UNKNOWN, 0, relationInterfaces.size());
+ for (LONG i = 0; i < relationInterfaces.size(); ++i) {
+ if (QWindowsUiaMainProvider *childProvider = QWindowsUiaMainProvider::providerForAccessible(relationInterfaces.at(i).first)) {
+ SafeArrayPutElement(elements, &i, static_cast<IRawElementProviderSimple*>(childProvider));
+ childProvider->Release();
+ }
+ }
+
+ pRetVal->vt = VT_UNKNOWN | VT_ARRAY;
+ pRetVal->parray = elements;
+}
+
HRESULT QWindowsUiaMainProvider::GetPropertyValue(PROPERTYID idProp, VARIANT *pRetVal)
{
qCDebug(lcQpaUiAutomation) << __FUNCTION__ << idProp;
@@ -393,6 +413,15 @@ HRESULT QWindowsUiaMainProvider::GetPropertyValue(PROPERTYID idProp, VARIANT *pR
setVariantString(className, pRetVal);
}
break;
+ case UIA_DescribedByPropertyId:
+ fillVariantArrayForRelation(accessible, QAccessible::DescriptionFor, pRetVal);
+ break;
+ case UIA_FlowsFromPropertyId:
+ fillVariantArrayForRelation(accessible, QAccessible::FlowsTo, pRetVal);
+ break;
+ case UIA_FlowsToPropertyId:
+ fillVariantArrayForRelation(accessible, QAccessible::FlowsFrom, pRetVal);
+ break;
case UIA_FrameworkIdPropertyId:
setVariantString(QStringLiteral("Qt"), pRetVal);
break;
@@ -406,7 +435,7 @@ HRESULT QWindowsUiaMainProvider::GetPropertyValue(PROPERTYID idProp, VARIANT *pR
// The native OSK should be disabled if the Qt OSK is in use,
// or if disabled via application attribute.
- static bool imModuleEmpty = qEnvironmentVariableIsEmpty("QT_IM_MODULE");
+ static bool imModuleEmpty = QPlatformInputContextFactory::requested().isEmpty();
bool nativeVKDisabled = QCoreApplication::testAttribute(Qt::AA_DisableNativeVirtualKeyboard);
// If we want to disable the native OSK auto-showing
@@ -509,7 +538,7 @@ HRESULT QWindowsUiaMainProvider::get_HostRawElementProvider(IRawElementProviderS
// Returns a host provider only for controls associated with a native window handle. Others should return NULL.
if (QAccessibleInterface *accessible = accessibleInterface()) {
if (HWND hwnd = hwndForAccessible(accessible)) {
- return QWindowsUiaWrapper::instance()->hostProviderFromHwnd(hwnd, pRetVal);
+ return UiaHostProviderFromHwnd(hwnd, pRetVal);
}
}
return S_OK;
@@ -699,26 +728,18 @@ HRESULT QWindowsUiaMainProvider::ElementProviderFromPoint(double x, double y, IR
QPoint point;
nativeUiaPointToPoint(uiaPoint, window, &point);
- if (auto targetacc = accessible->childAt(point.x(), point.y())) {
- auto acc = accessible->childAt(point.x(), point.y());
- // Reject the cases where childAt() returns a different instance in each call for the same
- // element (e.g., QAccessibleTree), as it causes an endless loop with Youdao Dictionary installed.
- if (targetacc == acc) {
- // Controls can be embedded within grouping elements. By default returns the innermost control.
- while (acc) {
- targetacc = acc;
- // For accessibility tools it may be better to return the text element instead of its subcomponents.
- if (targetacc->textInterface()) break;
- acc = targetacc->childAt(point.x(), point.y());
- if (acc != targetacc->childAt(point.x(), point.y())) {
- qCDebug(lcQpaUiAutomation) << "Non-unique childAt() for" << targetacc;
- break;
- }
- }
- *pRetVal = providerForAccessible(targetacc);
- } else {
- qCDebug(lcQpaUiAutomation) << "Non-unique childAt() for" << accessible;
+ QAccessibleInterface *targetacc = accessible->childAt(point.x(), point.y());
+
+ if (targetacc) {
+ QAccessibleInterface *acc = targetacc;
+ // Controls can be embedded within grouping elements. By default returns the innermost control.
+ while (acc) {
+ targetacc = acc;
+ // For accessibility tools it may be better to return the text element instead of its subcomponents.
+ if (targetacc->textInterface()) break;
+ acc = acc->childAt(point.x(), point.y());
}
+ *pRetVal = providerForAccessible(targetacc);
}
return S_OK;
}
diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiamainprovider.h b/src/plugins/platforms/windows/uiautomation/qwindowsuiamainprovider.h
index 5659a28e35..24181f9536 100644
--- a/src/plugins/platforms/windows/uiautomation/qwindowsuiamainprovider.h
+++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiamainprovider.h
@@ -11,6 +11,7 @@
#include <QtCore/qpointer.h>
#include <QtCore/qsharedpointer.h>
+#include <QtCore/qmutex.h>
#include <QtCore/qt_windows.h>
#include <QtGui/qaccessible.h>
@@ -61,7 +62,9 @@ public:
private:
QString automationIdForAccessible(const QAccessibleInterface *accessible);
+ static void fillVariantArrayForRelation(QAccessibleInterface *accessible, QAccessible::Relation relation, VARIANT *pRetVal);
ULONG m_ref;
+ static QMutex m_mutex;
};
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiaprovidercache.cpp b/src/plugins/platforms/windows/uiautomation/qwindowsuiaprovidercache.cpp
index c53b130b92..ba2a88bb4e 100644
--- a/src/plugins/platforms/windows/uiautomation/qwindowsuiaprovidercache.cpp
+++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiaprovidercache.cpp
@@ -39,7 +39,7 @@ void QWindowsUiaProviderCache::insert(QAccessible::Id id, QWindowsUiaBaseProvide
m_providerTable[id] = provider;
m_inverseTable[provider] = id;
// Connects the destroyed signal to our slot, to remove deleted objects from the cache.
- QObject::connect(provider, &QObject::destroyed, this, &QWindowsUiaProviderCache::objectDestroyed);
+ QObject::connect(provider, &QObject::destroyed, this, &QWindowsUiaProviderCache::objectDestroyed, Qt::DirectConnection);
}
}
diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiarangevalueprovider.h b/src/plugins/platforms/windows/uiautomation/qwindowsuiarangevalueprovider.h
index 3e06961a82..ffb5ae155b 100644
--- a/src/plugins/platforms/windows/uiautomation/qwindowsuiarangevalueprovider.h
+++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiarangevalueprovider.h
@@ -13,7 +13,7 @@ QT_BEGIN_NAMESPACE
// Implements the Range Value control pattern provider.
class QWindowsUiaRangeValueProvider : public QWindowsUiaBaseProvider,
- public QWindowsComBase<IRangeValueProvider>
+ public QComObject<IRangeValueProvider>
{
Q_DISABLE_COPY_MOVE(QWindowsUiaRangeValueProvider)
public:
diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiaselectionitemprovider.cpp b/src/plugins/platforms/windows/uiautomation/qwindowsuiaselectionitemprovider.cpp
index 7e829b24a7..95bc2f7570 100644
--- a/src/plugins/platforms/windows/uiautomation/qwindowsuiaselectionitemprovider.cpp
+++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiaselectionitemprovider.cpp
@@ -36,6 +36,14 @@ HRESULT STDMETHODCALLTYPE QWindowsUiaSelectionItemProvider::Select()
if (!accessible)
return UIA_E_ELEMENTNOTAVAILABLE;
+ if (QAccessibleInterface *parent = accessible->parent()) {
+ if (QAccessibleSelectionInterface *selectionInterface = parent->selectionInterface()) {
+ selectionInterface->clear();
+ bool ok = selectionInterface->select(accessible);
+ return ok ? S_OK : S_FALSE;
+ }
+ }
+
QAccessibleActionInterface *actionInterface = accessible->actionInterface();
if (!actionInterface)
return UIA_E_ELEMENTNOTAVAILABLE;
@@ -73,6 +81,13 @@ HRESULT STDMETHODCALLTYPE QWindowsUiaSelectionItemProvider::AddToSelection()
if (!accessible)
return UIA_E_ELEMENTNOTAVAILABLE;
+ if (QAccessibleInterface *parent = accessible->parent()) {
+ if (QAccessibleSelectionInterface *selectionInterface = parent->selectionInterface()) {
+ bool ok = selectionInterface->select(accessible);
+ return ok ? S_OK : S_FALSE;
+ }
+ }
+
QAccessibleActionInterface *actionInterface = accessible->actionInterface();
if (!actionInterface)
return UIA_E_ELEMENTNOTAVAILABLE;
@@ -98,6 +113,13 @@ HRESULT STDMETHODCALLTYPE QWindowsUiaSelectionItemProvider::RemoveFromSelection(
if (!accessible)
return UIA_E_ELEMENTNOTAVAILABLE;
+ if (QAccessibleInterface *parent = accessible->parent()) {
+ if (QAccessibleSelectionInterface *selectionInterface = parent->selectionInterface()) {
+ bool ok = selectionInterface->unselect(accessible);
+ return ok ? S_OK : S_FALSE;
+ }
+ }
+
QAccessibleActionInterface *actionInterface = accessible->actionInterface();
if (!actionInterface)
return UIA_E_ELEMENTNOTAVAILABLE;
@@ -124,6 +146,14 @@ HRESULT STDMETHODCALLTYPE QWindowsUiaSelectionItemProvider::get_IsSelected(BOOL
if (!accessible)
return UIA_E_ELEMENTNOTAVAILABLE;
+ if (QAccessibleInterface *parent = accessible->parent()) {
+ if (QAccessibleSelectionInterface *selectionInterface = parent->selectionInterface()) {
+ bool selected = selectionInterface->isSelected(accessible);
+ *pRetVal = selected ? TRUE : FALSE;
+ return S_OK;
+ }
+ }
+
if (accessible->role() == QAccessible::RadioButton)
*pRetVal = accessible->state().checked;
else if (accessible->role() == QAccessible::PageTab)
@@ -146,12 +176,18 @@ HRESULT STDMETHODCALLTYPE QWindowsUiaSelectionItemProvider::get_SelectionContain
if (!accessible)
return UIA_E_ELEMENTNOTAVAILABLE;
+ QAccessibleInterface *parent = accessible->parent();
+ if (parent && parent->selectionInterface()) {
+ *pRetVal = QWindowsUiaMainProvider::providerForAccessible(parent);
+ return S_OK;
+ }
+
QAccessibleActionInterface *actionInterface = accessible->actionInterface();
if (!actionInterface)
return UIA_E_ELEMENTNOTAVAILABLE;
// Radio buttons do not require a container.
- if (QAccessibleInterface *parent = accessible->parent()) {
+ if (parent) {
if ((accessible->role() == QAccessible::ListItem && parent->role() == QAccessible::List)
|| (accessible->role() == QAccessible::PageTab && parent->role() == QAccessible::PageTabList)) {
*pRetVal = QWindowsUiaMainProvider::providerForAccessible(parent);
diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiaselectionitemprovider.h b/src/plugins/platforms/windows/uiautomation/qwindowsuiaselectionitemprovider.h
index b510ae951c..ee34fd9edd 100644
--- a/src/plugins/platforms/windows/uiautomation/qwindowsuiaselectionitemprovider.h
+++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiaselectionitemprovider.h
@@ -13,7 +13,7 @@ QT_BEGIN_NAMESPACE
// Implements the Selection Item control pattern provider. Used for List items and radio buttons.
class QWindowsUiaSelectionItemProvider : public QWindowsUiaBaseProvider,
- public QWindowsComBase<ISelectionItemProvider>
+ public QComObject<ISelectionItemProvider>
{
Q_DISABLE_COPY_MOVE(QWindowsUiaSelectionItemProvider)
public:
diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiaselectionprovider.cpp b/src/plugins/platforms/windows/uiautomation/qwindowsuiaselectionprovider.cpp
index 45f3b20552..37148c655a 100644
--- a/src/plugins/platforms/windows/uiautomation/qwindowsuiaselectionprovider.cpp
+++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiaselectionprovider.cpp
@@ -41,17 +41,23 @@ HRESULT STDMETHODCALLTYPE QWindowsUiaSelectionProvider::GetSelection(SAFEARRAY *
if (!accessible)
return UIA_E_ELEMENTNOTAVAILABLE;
- // First put selected items in a list, then build a safe array with the right size.
+ // First get/create list of selected items, then build a safe array with the right size.
QList<QAccessibleInterface *> selectedList;
- for (int i = 0; i < accessible->childCount(); ++i) {
- if (QAccessibleInterface *child = accessible->child(i)) {
- if (accessible->role() == QAccessible::PageTabList) {
- if (child->role() == QAccessible::PageTab && child->state().focused) {
- selectedList.append(child);
- }
- } else {
- if (child->state().selected) {
- selectedList.append(child);
+ if (QAccessibleSelectionInterface *selectionInterface = accessible->selectionInterface()) {
+ selectedList = selectionInterface->selectedItems();
+ } else {
+ const int childCount = accessible->childCount();
+ selectedList.reserve(childCount);
+ for (int i = 0; i < childCount; ++i) {
+ if (QAccessibleInterface *child = accessible->child(i)) {
+ if (accessible->role() == QAccessible::PageTabList) {
+ if (child->role() == QAccessible::PageTab && child->state().focused) {
+ selectedList.append(child);
+ }
+ } else {
+ if (child->state().selected) {
+ selectedList.append(child);
+ }
}
}
}
@@ -102,11 +108,15 @@ HRESULT STDMETHODCALLTYPE QWindowsUiaSelectionProvider::get_IsSelectionRequired(
// Initially returns false if none are selected. After the first selection, it may be required.
bool anySelected = false;
- for (int i = 0; i < accessible->childCount(); ++i) {
- if (QAccessibleInterface *child = accessible->child(i)) {
- if (child->state().selected) {
- anySelected = true;
- break;
+ if (QAccessibleSelectionInterface *selectionInterface = accessible->selectionInterface()) {
+ anySelected = selectionInterface->selectedItem(0) != nullptr;
+ } else {
+ for (int i = 0; i < accessible->childCount(); ++i) {
+ if (QAccessibleInterface *child = accessible->child(i)) {
+ if (child->state().selected) {
+ anySelected = true;
+ break;
+ }
}
}
}
@@ -116,6 +126,134 @@ HRESULT STDMETHODCALLTYPE QWindowsUiaSelectionProvider::get_IsSelectionRequired(
return S_OK;
}
+HRESULT STDMETHODCALLTYPE QWindowsUiaSelectionProvider::get_FirstSelectedItem(__RPC__deref_out_opt IRawElementProviderSimple **pRetVal)
+{
+ qCDebug(lcQpaUiAutomation) << __FUNCTION__;
+
+ if (!pRetVal)
+ return E_INVALIDARG;
+ *pRetVal = nullptr;
+
+ QAccessibleInterface *accessible = accessibleInterface();
+ if (!accessible)
+ return UIA_E_ELEMENTNOTAVAILABLE;
+
+ QAccessibleInterface *firstSelectedChild = nullptr;
+ if (QAccessibleSelectionInterface *selectionInterface = accessible->selectionInterface()) {
+ firstSelectedChild = selectionInterface->selectedItem(0);
+ if (!firstSelectedChild)
+ return UIA_E_ELEMENTNOTAVAILABLE;
+ } else {
+ int i = 0;
+ while (!firstSelectedChild && i < accessible->childCount()) {
+ if (QAccessibleInterface *child = accessible->child(i)) {
+ if (accessible->role() == QAccessible::PageTabList) {
+ if (child->role() == QAccessible::PageTab && child->state().focused)
+ firstSelectedChild = child;
+ } else if (child->state().selected) {
+ firstSelectedChild = child;
+ }
+ }
+ i++;
+ }
+ }
+
+ if (!firstSelectedChild)
+ return UIA_E_ELEMENTNOTAVAILABLE;
+
+ if (QWindowsUiaMainProvider *childProvider = QWindowsUiaMainProvider::providerForAccessible(firstSelectedChild))
+ {
+ *pRetVal = static_cast<IRawElementProviderSimple *>(childProvider);
+ return S_OK;
+ }
+
+ return S_FALSE;
+}
+
+HRESULT STDMETHODCALLTYPE QWindowsUiaSelectionProvider::get_LastSelectedItem(__RPC__deref_out_opt IRawElementProviderSimple **pRetVal)
+{
+ qCDebug(lcQpaUiAutomation) << __FUNCTION__;
+
+ if (!pRetVal)
+ return E_INVALIDARG;
+ *pRetVal = nullptr;
+
+ QAccessibleInterface *accessible = accessibleInterface();
+ if (!accessible)
+ return UIA_E_ELEMENTNOTAVAILABLE;
+
+ QAccessibleInterface *lastSelectedChild = nullptr;
+ if (QAccessibleSelectionInterface *selectionInterface = accessible->selectionInterface()) {
+ const int selectedItemCount = selectionInterface->selectedItemCount();
+ if (selectedItemCount <= 0)
+ return UIA_E_ELEMENTNOTAVAILABLE;
+ lastSelectedChild = selectionInterface->selectedItem(selectedItemCount - 1);
+ } else {
+ int i = accessible->childCount() - 1;
+ while (!lastSelectedChild && i >= 0) {
+ if (QAccessibleInterface *child = accessible->child(i)) {
+ if (accessible->role() == QAccessible::PageTabList) {
+ if (child->role() == QAccessible::PageTab && child->state().focused)
+ lastSelectedChild = child;
+ } else if (child->state().selected) {
+ lastSelectedChild = child;
+ }
+ }
+ i--;
+ }
+ }
+
+ if (!lastSelectedChild)
+ return UIA_E_ELEMENTNOTAVAILABLE;
+
+ if (QWindowsUiaMainProvider *childProvider = QWindowsUiaMainProvider::providerForAccessible(lastSelectedChild))
+ {
+ *pRetVal = static_cast<IRawElementProviderSimple *>(childProvider);
+ return S_OK;
+ }
+
+ return S_FALSE;
+}
+
+HRESULT STDMETHODCALLTYPE QWindowsUiaSelectionProvider::get_CurrentSelectedItem(__RPC__deref_out_opt IRawElementProviderSimple **pRetVal)
+{
+ qCDebug(lcQpaUiAutomation) << __FUNCTION__;
+ return get_FirstSelectedItem(pRetVal);
+}
+
+HRESULT STDMETHODCALLTYPE QWindowsUiaSelectionProvider::get_ItemCount(__RPC__out int *pRetVal)
+{
+ qCDebug(lcQpaUiAutomation) << __FUNCTION__;
+
+ if (!pRetVal)
+ return E_INVALIDARG;
+ *pRetVal = -1;
+
+ QAccessibleInterface *accessible = accessibleInterface();
+ if (!accessible)
+ return UIA_E_ELEMENTNOTAVAILABLE;
+
+
+ if (QAccessibleSelectionInterface *selectionInterface = accessible->selectionInterface())
+ *pRetVal = selectionInterface->selectedItemCount();
+ else {
+ int selectedCount = 0;
+ for (int i = 0; i < accessible->childCount(); i++) {
+ if (QAccessibleInterface *child = accessible->child(i)) {
+ if (accessible->role() == QAccessible::PageTabList) {
+ if (child->role() == QAccessible::PageTab && child->state().focused)
+ selectedCount++;
+ } else if (child->state().selected) {
+ selectedCount++;
+ }
+ }
+ }
+ *pRetVal = selectedCount;
+ }
+
+ return S_OK;
+}
+
QT_END_NAMESPACE
#endif // QT_CONFIG(accessibility)
diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiaselectionprovider.h b/src/plugins/platforms/windows/uiautomation/qwindowsuiaselectionprovider.h
index 9a187c9413..7a899e4261 100644
--- a/src/plugins/platforms/windows/uiautomation/qwindowsuiaselectionprovider.h
+++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiaselectionprovider.h
@@ -11,9 +11,22 @@
QT_BEGIN_NAMESPACE
+namespace QtPrivate {
+
+template <>
+struct QComObjectTraits<ISelectionProvider2>
+{
+ static constexpr bool isGuidOf(REFIID riid) noexcept
+ {
+ return QComObjectTraits<ISelectionProvider2, ISelectionProvider>::isGuidOf(riid);
+ }
+};
+
+} // namespace QtPrivate
+
// Implements the Selection control pattern provider. Used for Lists.
class QWindowsUiaSelectionProvider : public QWindowsUiaBaseProvider,
- public QWindowsComBase<ISelectionProvider>
+ public QComObject<ISelectionProvider2>
{
Q_DISABLE_COPY_MOVE(QWindowsUiaSelectionProvider)
public:
@@ -24,6 +37,12 @@ public:
HRESULT STDMETHODCALLTYPE GetSelection(SAFEARRAY **pRetVal) override;
HRESULT STDMETHODCALLTYPE get_CanSelectMultiple(BOOL *pRetVal) override;
HRESULT STDMETHODCALLTYPE get_IsSelectionRequired(BOOL *pRetVal) override;
+
+ // ISelectionProvider2
+ HRESULT STDMETHODCALLTYPE get_FirstSelectedItem(__RPC__deref_out_opt IRawElementProviderSimple **pRetVal) override;
+ HRESULT STDMETHODCALLTYPE get_LastSelectedItem(__RPC__deref_out_opt IRawElementProviderSimple **pRetVal) override;
+ HRESULT STDMETHODCALLTYPE get_CurrentSelectedItem(__RPC__deref_out_opt IRawElementProviderSimple **pRetVal) override;
+ HRESULT STDMETHODCALLTYPE get_ItemCount(__RPC__out int *pRetVal) override;
};
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiatableitemprovider.h b/src/plugins/platforms/windows/uiautomation/qwindowsuiatableitemprovider.h
index 8aed180671..3bb0e1027e 100644
--- a/src/plugins/platforms/windows/uiautomation/qwindowsuiatableitemprovider.h
+++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiatableitemprovider.h
@@ -13,7 +13,7 @@ QT_BEGIN_NAMESPACE
// Implements the Table Item control pattern provider. Used by items within a table/tree.
class QWindowsUiaTableItemProvider : public QWindowsUiaBaseProvider,
- public QWindowsComBase<ITableItemProvider>
+ public QComObject<ITableItemProvider>
{
Q_DISABLE_COPY_MOVE(QWindowsUiaTableItemProvider)
public:
diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiatableprovider.h b/src/plugins/platforms/windows/uiautomation/qwindowsuiatableprovider.h
index 6454bb9441..8beb11decf 100644
--- a/src/plugins/platforms/windows/uiautomation/qwindowsuiatableprovider.h
+++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiatableprovider.h
@@ -12,8 +12,7 @@
QT_BEGIN_NAMESPACE
// Implements the Table control pattern provider. Used by tables/trees.
-class QWindowsUiaTableProvider : public QWindowsUiaBaseProvider,
- public QWindowsComBase<ITableProvider>
+class QWindowsUiaTableProvider : public QWindowsUiaBaseProvider, public QComObject<ITableProvider>
{
Q_DISABLE_COPY_MOVE(QWindowsUiaTableProvider)
public:
diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiatextprovider.cpp b/src/plugins/platforms/windows/uiautomation/qwindowsuiatextprovider.cpp
index e3b7c69b60..172836232e 100644
--- a/src/plugins/platforms/windows/uiautomation/qwindowsuiatextprovider.cpp
+++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiatextprovider.cpp
@@ -26,20 +26,6 @@ QWindowsUiaTextProvider::~QWindowsUiaTextProvider()
{
}
-HRESULT STDMETHODCALLTYPE QWindowsUiaTextProvider::QueryInterface(REFIID iid, LPVOID *iface)
-{
- qCDebug(lcQpaUiAutomation) << __FUNCTION__ << this;
-
- if (!iface)
- return E_INVALIDARG;
- *iface = nullptr;
-
- const bool result = qWindowsComQueryUnknownInterfaceMulti<ITextProvider>(this, iid, iface)
- || qWindowsComQueryInterface<ITextProvider>(this, iid, iface)
- || qWindowsComQueryInterface<ITextProvider2>(this, iid, iface);
- return result ? S_OK : E_NOINTERFACE;
-}
-
// Returns an array of providers for the selected text ranges.
HRESULT STDMETHODCALLTYPE QWindowsUiaTextProvider::GetSelection(SAFEARRAY **pRetVal)
{
@@ -146,9 +132,10 @@ HRESULT STDMETHODCALLTYPE QWindowsUiaTextProvider::RangeFromPoint(UiaPoint point
nativeUiaPointToPoint(point, window, &pt);
int offset = textInterface->offsetAtPoint(pt);
- if ((offset >= 0) && (offset < textInterface->characterCount())) {
- *pRetVal = new QWindowsUiaTextRangeProvider(id(), offset, offset);
- }
+ if (offset < 0 || offset >= textInterface->characterCount())
+ return UIA_E_ELEMENTNOTAVAILABLE;
+
+ *pRetVal = new QWindowsUiaTextRangeProvider(id(), offset, offset);
return S_OK;
}
diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiatextprovider.h b/src/plugins/platforms/windows/uiautomation/qwindowsuiatextprovider.h
index 5e6d430f1a..8f886510b4 100644
--- a/src/plugins/platforms/windows/uiautomation/qwindowsuiatextprovider.h
+++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiatextprovider.h
@@ -12,18 +12,27 @@
QT_BEGIN_NAMESPACE
+namespace QtPrivate {
+
+template <>
+struct QComObjectTraits<ITextProvider2>
+{
+ static constexpr bool isGuidOf(REFIID riid) noexcept
+ {
+ return QComObjectTraits<ITextProvider2, ITextProvider>::isGuidOf(riid);
+ }
+};
+
+} // namespace QtPrivate
+
// Implements the Text control pattern provider. Used for text controls.
-class QWindowsUiaTextProvider : public QWindowsUiaBaseProvider,
- public QWindowsComBase<ITextProvider2>
+class QWindowsUiaTextProvider : public QWindowsUiaBaseProvider, public QComObject<ITextProvider2>
{
Q_DISABLE_COPY_MOVE(QWindowsUiaTextProvider)
public:
explicit QWindowsUiaTextProvider(QAccessible::Id id);
~QWindowsUiaTextProvider();
- // IUnknown overrides
- HRESULT STDMETHODCALLTYPE QueryInterface(REFIID id, LPVOID *iface) override;
-
// ITextProvider
HRESULT STDMETHODCALLTYPE GetSelection(SAFEARRAY **pRetVal) override;
HRESULT STDMETHODCALLTYPE GetVisibleRanges(SAFEARRAY **pRetVal) override;
diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiatextrangeprovider.cpp b/src/plugins/platforms/windows/uiautomation/qwindowsuiatextrangeprovider.cpp
index 4d02036196..a62a33cfe2 100644
--- a/src/plugins/platforms/windows/uiautomation/qwindowsuiatextrangeprovider.cpp
+++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiatextrangeprovider.cpp
@@ -167,6 +167,15 @@ HRESULT STDMETHODCALLTYPE QWindowsUiaTextRangeProvider::GetAttributeValue(TEXTAT
else
setVariantI4(CaretPosition_Unknown, pRetVal);
break;
+ case UIA_StrikethroughStyleAttributeId:
+ {
+ const QString value = valueForIA2Attribute(textInterface, QStringLiteral("text-line-through-type"));
+ if (value.isEmpty())
+ break;
+ const TextDecorationLineStyle uiaLineStyle = uiaLineStyleForIA2LineStyle(value);
+ setVariantI4(uiaLineStyle, pRetVal);
+ break;
+ }
default:
break;
}
@@ -307,14 +316,14 @@ HRESULT QWindowsUiaTextRangeProvider::Move(TextUnit unit, int count, int *pRetVa
int len = textInterface->characterCount();
- if (len < 1)
+ if (len < 1 || count == 0) // MSDN: "Zero has no effect."
return S_OK;
if (unit == TextUnit_Character) {
// Moves the start point, ensuring it lies within the bounds.
- int start = qBound(0, m_startOffset + count, len - 1);
+ int start = qBound(0, m_startOffset + count, len);
// If range was initially empty, leaves it as is; otherwise, normalizes it to one char.
- m_endOffset = (m_endOffset > m_startOffset) ? start + 1 : start;
+ m_endOffset = (m_endOffset > m_startOffset) ? qMin(start + 1, len) : start;
*pRetVal = start - m_startOffset; // Returns the actually moved distance.
m_startOffset = start;
} else {
@@ -385,7 +394,7 @@ HRESULT QWindowsUiaTextRangeProvider::MoveEndpointByUnit(TextPatternRangeEndpoin
if (unit == TextUnit_Character) {
if (endpoint == TextPatternRangeEndpoint_Start) {
- int boundedValue = qBound(0, m_startOffset + count, len - 1);
+ int boundedValue = qBound(0, m_startOffset + count, len);
*pRetVal = boundedValue - m_startOffset;
m_startOffset = boundedValue;
m_endOffset = qBound(m_startOffset, m_endOffset, len);
@@ -517,6 +526,42 @@ HRESULT QWindowsUiaTextRangeProvider::unselect()
return S_OK;
}
+// helper method to retrieve the value of the given IAccessible2 text attribute,
+// or an empty string if not set
+QString QWindowsUiaTextRangeProvider::valueForIA2Attribute(QAccessibleTextInterface *textInterface,
+ const QString &key)
+{
+ Q_ASSERT(textInterface);
+
+ int startOffset;
+ int endOffset;
+ const QString attributes = textInterface->attributes(m_startOffset, &startOffset, &endOffset);
+ // don't report if attributes don't apply for the whole range
+ if (startOffset > m_startOffset || endOffset < m_endOffset)
+ return {};
+
+ for (auto attr : QStringTokenizer{attributes, u';'})
+ {
+ const QList<QStringView> items = attr.split(u':', Qt::SkipEmptyParts, Qt::CaseSensitive);
+ if (items.count() == 2 && items[0] == key)
+ return items[1].toString();
+ }
+
+ return {};
+}
+
+TextDecorationLineStyle QWindowsUiaTextRangeProvider::uiaLineStyleForIA2LineStyle(const QString &ia2LineStyle)
+{
+ if (ia2LineStyle == QStringLiteral("none"))
+ return TextDecorationLineStyle_None;
+ if (ia2LineStyle == QStringLiteral("single"))
+ return TextDecorationLineStyle_Single;
+ if (ia2LineStyle == QStringLiteral("double"))
+ return TextDecorationLineStyle_Double;
+
+ return TextDecorationLineStyle_Other;
+}
+
QT_END_NAMESPACE
#endif // QT_CONFIG(accessibility)
diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiatextrangeprovider.h b/src/plugins/platforms/windows/uiautomation/qwindowsuiatextrangeprovider.h
index f7d28a34d2..a37429a603 100644
--- a/src/plugins/platforms/windows/uiautomation/qwindowsuiatextrangeprovider.h
+++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiatextrangeprovider.h
@@ -13,7 +13,7 @@ QT_BEGIN_NAMESPACE
// Implements the Text Range control pattern provider. Used for text controls.
class QWindowsUiaTextRangeProvider : public QWindowsUiaBaseProvider,
- public QWindowsComBase<ITextRangeProvider>
+ public QComObject<ITextRangeProvider>
{
Q_DISABLE_COPY_MOVE(QWindowsUiaTextRangeProvider)
public:
@@ -42,6 +42,8 @@ public:
private:
HRESULT unselect();
+ QString valueForIA2Attribute(QAccessibleTextInterface *textInterface, const QString &key);
+ TextDecorationLineStyle uiaLineStyleForIA2LineStyle(const QString &ia2LineStyle);
int m_startOffset;
int m_endOffset;
};
diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiatoggleprovider.h b/src/plugins/platforms/windows/uiautomation/qwindowsuiatoggleprovider.h
index 742e26f6d6..17150dfbe0 100644
--- a/src/plugins/platforms/windows/uiautomation/qwindowsuiatoggleprovider.h
+++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiatoggleprovider.h
@@ -12,8 +12,7 @@
QT_BEGIN_NAMESPACE
// Implements the Toggle control pattern provider. Used for checkboxes.
-class QWindowsUiaToggleProvider : public QWindowsUiaBaseProvider,
- public QWindowsComBase<IToggleProvider>
+class QWindowsUiaToggleProvider : public QWindowsUiaBaseProvider, public QComObject<IToggleProvider>
{
Q_DISABLE_COPY_MOVE(QWindowsUiaToggleProvider)
public:
diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiautils.cpp b/src/plugins/platforms/windows/uiautomation/qwindowsuiautils.cpp
index 9287e89084..78ab3e890e 100644
--- a/src/plugins/platforms/windows/uiautomation/qwindowsuiautils.cpp
+++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiautils.cpp
@@ -176,6 +176,9 @@ long roleToControlTypeId(QAccessible::Role role)
{QAccessible::PageTabList, UIA_TabControlTypeId},
{QAccessible::Clock, UIA_CustomControlTypeId},
{QAccessible::Splitter, UIA_CustomControlTypeId},
+ {QAccessible::Paragraph, UIA_TextControlTypeId},
+ {QAccessible::WebDocument, UIA_DocumentControlTypeId},
+ {QAccessible::Heading, UIA_TextControlTypeId},
};
return mapping.value(role, UIA_CustomControlTypeId);
diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiautils.h b/src/plugins/platforms/windows/uiautomation/qwindowsuiautils.h
index 8fe8b1c6d7..bf90211cec 100644
--- a/src/plugins/platforms/windows/uiautomation/qwindowsuiautils.h
+++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiautils.h
@@ -12,7 +12,7 @@
#include <QtGui/qaccessible.h>
#include <QtGui/qwindow.h>
#include <QtCore/qrect.h>
-#include <QtGui/private/qwindowsuiawrapper_p.h>
+#include "qwindowsuiautomation.h"
QT_BEGIN_NAMESPACE
diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiautomation.cpp b/src/plugins/platforms/windows/uiautomation/qwindowsuiautomation.cpp
new file mode 100644
index 0000000000..1593a07202
--- /dev/null
+++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiautomation.cpp
@@ -0,0 +1,74 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include <QtGui/qtguiglobal.h>
+#if QT_CONFIG(accessibility)
+
+#include "qwindowsuiautomation.h"
+
+#if defined(__MINGW32__) || defined(__MINGW64__)
+
+template<typename T, typename... TArg>
+struct winapi_func
+{
+ using func_t = T(WINAPI*)(TArg...);
+ const func_t func;
+ const T error_value;
+#ifdef __GNUC__
+# pragma GCC diagnostic ignored "-Wcast-function-type"
+#endif
+ winapi_func(const char *lib_name, const char *func_name, func_t func_proto,
+ T error_value = T(__HRESULT_FROM_WIN32(ERROR_PROC_NOT_FOUND))) :
+ func(reinterpret_cast<func_t>(GetProcAddress(LoadLibraryA(lib_name), func_name))),
+ error_value(error_value)
+ {
+ std::ignore = func_proto;
+ }
+#ifdef __GNUC__
+# pragma GCC diagnostic pop
+#endif
+ T invoke(TArg... arg)
+ {
+ if (!func)
+ return error_value;
+ return func(arg...);
+ }
+};
+
+#define FN(fn) #fn,fn
+
+BOOL WINAPI UiaClientsAreListening()
+{
+ static auto func = winapi_func("uiautomationcore", FN(UiaClientsAreListening), BOOL(false));
+ return func.invoke();
+}
+
+LRESULT WINAPI UiaReturnRawElementProvider(
+ HWND hwnd, WPARAM wParam, LPARAM lParam, IRawElementProviderSimple *el)
+{
+ static auto func = winapi_func("uiautomationcore", FN(UiaReturnRawElementProvider));
+ return func.invoke(hwnd, wParam, lParam, el);
+}
+
+HRESULT WINAPI UiaHostProviderFromHwnd(HWND hwnd, IRawElementProviderSimple **ppProvider)
+{
+ static auto func = winapi_func("uiautomationcore", FN(UiaHostProviderFromHwnd));
+ return func.invoke(hwnd, ppProvider);
+}
+
+HRESULT WINAPI UiaRaiseAutomationPropertyChangedEvent(
+ IRawElementProviderSimple *pProvider, PROPERTYID id, VARIANT oldValue, VARIANT newValue)
+{
+ static auto func = winapi_func("uiautomationcore", FN(UiaRaiseAutomationPropertyChangedEvent));
+ return func.invoke(pProvider, id, oldValue, newValue);
+}
+
+HRESULT WINAPI UiaRaiseAutomationEvent(IRawElementProviderSimple *pProvider, EVENTID id)
+{
+ static auto func = winapi_func("uiautomationcore", FN(UiaRaiseAutomationEvent));
+ return func.invoke(pProvider, id);
+}
+
+#endif // defined(__MINGW32__) || defined(__MINGW64__)
+
+#endif // QT_CONFIG(accessibility)
diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiautomation.h b/src/plugins/platforms/windows/uiautomation/qwindowsuiautomation.h
new file mode 100644
index 0000000000..a192b9b0fb
--- /dev/null
+++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiautomation.h
@@ -0,0 +1,70 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QWINDOWSUIAUTOMATION_H
+#define QWINDOWSUIAUTOMATION_H
+
+#include <QtGui/qtguiglobal.h>
+#if QT_CONFIG(accessibility)
+
+#include <uiautomation.h>
+
+#if defined(__MINGW32__) || defined(__MINGW64__)
+
+#define UIA_SelectionPattern2Id 10034
+#define UIA_IsReadOnlyAttributeId 40015
+#define UIA_StrikethroughStyleAttributeId 40026
+#define UIA_CaretPositionAttributeId 40038
+
+enum CaretPosition {
+ CaretPosition_Unknown = 0,
+ CaretPosition_EndOfLine = 1,
+ CaretPosition_BeginningOfLine = 2
+};
+
+enum TextDecorationLineStyle {
+ TextDecorationLineStyle_None = 0,
+ TextDecorationLineStyle_Single = 1,
+ TextDecorationLineStyle_WordsOnly = 2,
+ TextDecorationLineStyle_Double = 3,
+ TextDecorationLineStyle_Dot = 4,
+ TextDecorationLineStyle_Dash = 5,
+ TextDecorationLineStyle_DashDot = 6,
+ TextDecorationLineStyle_DashDotDot = 7,
+ TextDecorationLineStyle_Wavy = 8,
+ TextDecorationLineStyle_ThickSingle = 9,
+ TextDecorationLineStyle_DoubleWavy = 11,
+ TextDecorationLineStyle_ThickWavy = 12,
+ TextDecorationLineStyle_LongDash = 13,
+ TextDecorationLineStyle_ThickDash = 14,
+ TextDecorationLineStyle_ThickDashDot = 15,
+ TextDecorationLineStyle_ThickDashDotDot = 16,
+ TextDecorationLineStyle_ThickDot = 17,
+ TextDecorationLineStyle_ThickLongDash = 18,
+ TextDecorationLineStyle_Other = -1
+};
+
+BOOL WINAPI UiaClientsAreListening();
+
+#ifndef __ISelectionProvider2_INTERFACE_DEFINED__
+#define __ISelectionProvider2_INTERFACE_DEFINED__
+DEFINE_GUID(IID_ISelectionProvider2, 0x14f68475, 0xee1c, 0x44f6, 0xa8, 0x69, 0xd2, 0x39, 0x38, 0x1f, 0x0f, 0xe7);
+MIDL_INTERFACE("14f68475-ee1c-44f6-a869-d239381f0fe7")
+ISelectionProvider2 : public ISelectionProvider
+{
+public:
+ virtual HRESULT STDMETHODCALLTYPE get_FirstSelectedItem(__RPC__deref_out_opt IRawElementProviderSimple **retVal) = 0;
+ virtual HRESULT STDMETHODCALLTYPE get_LastSelectedItem(__RPC__deref_out_opt IRawElementProviderSimple **retVal) = 0;
+ virtual HRESULT STDMETHODCALLTYPE get_CurrentSelectedItem(__RPC__deref_out_opt IRawElementProviderSimple **retVal) = 0;
+ virtual HRESULT STDMETHODCALLTYPE get_ItemCount(__RPC__out int *retVal) = 0;
+};
+#ifdef __CRT_UUID_DECL
+__CRT_UUID_DECL(ISelectionProvider2, 0x14f68475, 0xee1c, 0x44f6, 0xa8, 0x69, 0xd2, 0x39, 0x38, 0x1f, 0x0f, 0xe7)
+#endif
+#endif // __ISelectionProvider2_INTERFACE_DEFINED__
+
+#endif // defined(__MINGW32__) || defined(__MINGW64__)
+
+#endif // QT_CONFIG(accessibility)
+
+#endif // QWINDOWSUIAUTOMATION_H
diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiavalueprovider.h b/src/plugins/platforms/windows/uiautomation/qwindowsuiavalueprovider.h
index 2f52019d33..8c0a6b8ee7 100644
--- a/src/plugins/platforms/windows/uiautomation/qwindowsuiavalueprovider.h
+++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiavalueprovider.h
@@ -13,8 +13,7 @@ QT_BEGIN_NAMESPACE
// Implements the Value control pattern provider.
// Supported for all controls that can return text(QAccessible::Value).
-class QWindowsUiaValueProvider : public QWindowsUiaBaseProvider,
- public QWindowsComBase<IValueProvider>
+class QWindowsUiaValueProvider : public QWindowsUiaBaseProvider, public QComObject<IValueProvider>
{
Q_DISABLE_COPY_MOVE(QWindowsUiaValueProvider)
public:
diff --git a/src/plugins/platforms/windows/uiautomation/qwindowsuiawindowprovider.h b/src/plugins/platforms/windows/uiautomation/qwindowsuiawindowprovider.h
index e277b3f5c5..89a1655232 100644
--- a/src/plugins/platforms/windows/uiautomation/qwindowsuiawindowprovider.h
+++ b/src/plugins/platforms/windows/uiautomation/qwindowsuiawindowprovider.h
@@ -11,8 +11,7 @@
QT_BEGIN_NAMESPACE
-class QWindowsUiaWindowProvider : public QWindowsUiaBaseProvider,
- public QWindowsComBase<IWindowProvider>
+class QWindowsUiaWindowProvider : public QWindowsUiaBaseProvider, public QComObject<IWindowProvider>
{
Q_DISABLE_COPY(QWindowsUiaWindowProvider)
public:
diff --git a/src/plugins/platforms/xcb/CMakeLists.txt b/src/plugins/platforms/xcb/CMakeLists.txt
index 60029345a7..e8fb442dd4 100644
--- a/src/plugins/platforms/xcb/CMakeLists.txt
+++ b/src/plugins/platforms/xcb/CMakeLists.txt
@@ -1,12 +1,15 @@
# Copyright (C) 2022 The Qt Company Ltd.
-# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-
-# Generated from xcb.pro.
+# SPDX-License-Identifier: BSD-3-Clause
#####################################################################
## XcbQpaPrivate Module:
#####################################################################
+if(GCC)
+ # Work around GCC ABI issues
+ add_compile_options(-Wno-psabi)
+endif()
+
qt_internal_add_module(XcbQpaPrivate
CONFIG_MODULE_NAME xcb_qpa_lib
INTERNAL_MODULE
@@ -47,6 +50,7 @@ qt_internal_add_module(XcbQpaPrivate
PkgConfig::XKB_COMMON_X11
Qt::CorePrivate
Qt::GuiPrivate
+ XCB::CURSOR
XCB::ICCCM
XCB::IMAGE
XCB::KEYSYMS
@@ -58,23 +62,16 @@ qt_internal_add_module(XcbQpaPrivate
XCB::SYNC
XCB::XCB
XCB::XFIXES
- # XCB::XINPUT # special case remove handled below
XCB::XKB
XKB::XKB
+ NO_UNITY_BUILD # X11 define clashes
)
-# special case begin
qt_disable_apple_app_extension_api_only(XcbQpaPrivate)
-# special case end
## Scopes:
#####################################################################
-qt_internal_extend_target(XcbQpaPrivate CONDITION QT_FEATURE_opengl
- PUBLIC_LIBRARIES
- Qt::OpenGLPrivate
-)
-
qt_internal_extend_target(XcbQpaPrivate CONDITION QT_FEATURE_glib
LIBRARIES
GLIB2::GLIB2
@@ -86,21 +83,24 @@ qt_internal_extend_target(XcbQpaPrivate CONDITION QT_FEATURE_draganddrop
)
qt_internal_extend_target(XcbQpaPrivate CONDITION QT_FEATURE_xcb_xlib
+ SOURCES
+ qt_xlib_wrapper.c qt_xlib_wrapper.h
PUBLIC_LIBRARIES
X11::XCB
- # special case begin
- # 'QMAKE_USE += xcb_xlib' in qmake implies also += xlib (aka X11)
- # due to "use": "xcb xlib" in src/gui/configure.json.
- # That's not yet handled by the conversion scripts unfortunately.
X11::X11
- # special case end
+)
+
+qt_internal_extend_target(XcbQpaPrivate CONDITION NOT QT_FEATURE_xcb_xlib
+ SOURCES
+ qxcbcursorfont.h
)
qt_internal_extend_target(XcbQpaPrivate CONDITION QT_FEATURE_xcb_sm
SOURCES
qxcbsessionmanager.cpp qxcbsessionmanager.h
PUBLIC_LIBRARIES
- ${X11_SM_LIB} ${X11_ICE_LIB}
+ X11::SM
+ X11::ICE
)
qt_internal_extend_target(XcbQpaPrivate CONDITION QT_FEATURE_vulkan
@@ -143,7 +143,6 @@ qt_internal_extend_target(XcbQpaPrivate CONDITION QT_FEATURE_fontconfig AND QT_F
WrapFreetype::WrapFreetype
)
-# special case begin
if(QT_FEATURE_system_xcb_xinput)
qt_internal_extend_target(XcbQpaPrivate LIBRARIES XCB::XINPUT)
else()
@@ -157,7 +156,6 @@ else()
"${PROJECT_SOURCE_DIR}/src/3rdparty/xcb/include"
)
endif()
-# special case end
#####################################################################
## QXcbIntegrationPlugin Plugin:
@@ -166,7 +164,7 @@ endif()
qt_internal_add_plugin(QXcbIntegrationPlugin
OUTPUT_NAME qxcb
PLUGIN_TYPE platforms
- DEFAULT_IF ${QT_QPA_DEFAULT_PLATFORM} MATCHES xcb # special case
+ DEFAULT_IF ${QT_QPA_DEFAULT_PLATFORM} MATCHES xcb
SOURCES
qxcbmain.cpp
DEFINES
@@ -177,16 +175,7 @@ qt_internal_add_plugin(QXcbIntegrationPlugin
Qt::XcbQpaPrivate
)
-#### Keys ignored in scope 18:.:.:xcb-plugin.pro:<TRUE>:
-# OTHER_FILES = "xcb.json" "README"
-
-## Scopes:
-#####################################################################
-
-#### Keys ignored in scope 20:.:.:xcb-plugin.pro:NOT TARGET___equals____ss_QT_DEFAULT_QPA_PLUGIN:
-# PLUGIN_EXTENDS = "-"
add_subdirectory(gl_integrations)
if(OFF)
- add_subdirectory(xcb-static) # special case TODO: xcb-static sub folder
+ add_subdirectory(xcb-static)
endif()
-
diff --git a/src/plugins/platforms/xcb/gl_integrations/CMakeLists.txt b/src/plugins/platforms/xcb/gl_integrations/CMakeLists.txt
index 253540455b..957beb9ed4 100644
--- a/src/plugins/platforms/xcb/gl_integrations/CMakeLists.txt
+++ b/src/plugins/platforms/xcb/gl_integrations/CMakeLists.txt
@@ -1,7 +1,5 @@
# Copyright (C) 2022 The Qt Company Ltd.
-# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-
-# Generated from gl_integrations.pro.
+# SPDX-License-Identifier: BSD-3-Clause
if(QT_FEATURE_xcb_egl_plugin)
add_subdirectory(xcb_egl)
diff --git a/src/plugins/platforms/xcb/gl_integrations/xcb_egl/CMakeLists.txt b/src/plugins/platforms/xcb/gl_integrations/xcb_egl/CMakeLists.txt
index c36a8bede5..12938c159a 100644
--- a/src/plugins/platforms/xcb/gl_integrations/xcb_egl/CMakeLists.txt
+++ b/src/plugins/platforms/xcb/gl_integrations/xcb_egl/CMakeLists.txt
@@ -1,13 +1,11 @@
# Copyright (C) 2022 The Qt Company Ltd.
-# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-
-# Generated from xcb_egl.pro.
+# SPDX-License-Identifier: BSD-3-Clause
#####################################################################
## QXcbEglIntegrationPlugin Plugin:
#####################################################################
-qt_find_package(EGL) # special case
+qt_find_package(EGL)
qt_internal_add_plugin(QXcbEglIntegrationPlugin
OUTPUT_NAME qxcb-egl-integration
@@ -29,5 +27,6 @@ qt_internal_add_plugin(QXcbEglIntegrationPlugin
Qt::Gui
Qt::GuiPrivate
Qt::XcbQpaPrivate
- EGL::EGL # special case
+ EGL::EGL
+ NO_UNITY_BUILD # X11 define clashes
)
diff --git a/src/plugins/platforms/xcb/gl_integrations/xcb_egl/qxcbeglinclude.h b/src/plugins/platforms/xcb/gl_integrations/xcb_egl/qxcbeglinclude.h
index 17428f778f..d12501bd90 100644
--- a/src/plugins/platforms/xcb/gl_integrations/xcb_egl/qxcbeglinclude.h
+++ b/src/plugins/platforms/xcb/gl_integrations/xcb_egl/qxcbeglinclude.h
@@ -7,7 +7,6 @@
#include <QtGui/QPalette>
#include <QtCore/QTextStream>
#include <QtGui/private/qmath_p.h>
-#include <QtGui/private/qcssparser_p.h>
#include <QtGui/private/qtextengine_p.h>
#include <QtGui/private/qt_egl_p.h>
diff --git a/src/plugins/platforms/xcb/gl_integrations/xcb_egl/qxcbeglintegration.cpp b/src/plugins/platforms/xcb/gl_integrations/xcb_egl/qxcbeglintegration.cpp
index 1e93ea6805..133b992cd9 100644
--- a/src/plugins/platforms/xcb/gl_integrations/xcb_egl/qxcbeglintegration.cpp
+++ b/src/plugins/platforms/xcb/gl_integrations/xcb_egl/qxcbeglintegration.cpp
@@ -58,6 +58,7 @@ std::optional<VisualInfo> getVisualInfo(xcb_screen_t *screen,
QXcbEglIntegration::QXcbEglIntegration()
: m_connection(nullptr)
, m_egl_display(EGL_NO_DISPLAY)
+ , m_using_platform_display(false)
{
qCDebug(lcQpaGl) << "Xcb EGL gl-integration created";
}
@@ -80,6 +81,7 @@ bool QXcbEglIntegration::initialize(QXcbConnection *connection)
m_egl_display = streamFuncs.get_platform_display(EGL_PLATFORM_X11_KHR,
m_connection->xlib_display(),
nullptr);
+ m_using_platform_display = true;
}
#if QT_CONFIG(egl_x11)
@@ -92,16 +94,19 @@ bool QXcbEglIntegration::initialize(QXcbConnection *connection)
m_egl_display = streamFuncs.get_platform_display(EGL_PLATFORM_XCB_KHR,
reinterpret_cast<void *>(connection->xcb_connection()),
nullptr);
+ m_using_platform_display = true;
}
#endif
EGLint major, minor;
bool success = eglInitialize(m_egl_display, &major, &minor);
+#if QT_CONFIG(egl_x11)
if (!success) {
m_egl_display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
qCDebug(lcQpaGl) << "Xcb EGL gl-integration retrying with display" << m_egl_display;
success = eglInitialize(m_egl_display, &major, &minor);
}
+#endif
m_native_interface_handler.reset(new QXcbEglNativeInterfaceHandler(connection->nativeInterface()));
diff --git a/src/plugins/platforms/xcb/gl_integrations/xcb_egl/qxcbeglintegration.h b/src/plugins/platforms/xcb/gl_integrations/xcb_egl/qxcbeglintegration.h
index ff0804678f..7caf4304bc 100644
--- a/src/plugins/platforms/xcb/gl_integrations/xcb_egl/qxcbeglintegration.h
+++ b/src/plugins/platforms/xcb/gl_integrations/xcb_egl/qxcbeglintegration.h
@@ -39,10 +39,13 @@ public:
EGLDisplay eglDisplay() const { return m_egl_display; }
+ bool usingPlatformDisplay() const { return m_using_platform_display; }
+
xcb_visualid_t getCompatibleVisualId(xcb_screen_t *screen, EGLConfig config) const;
private:
QXcbConnection *m_connection;
EGLDisplay m_egl_display;
+ bool m_using_platform_display;
QScopedPointer<QXcbEglNativeInterfaceHandler> m_native_interface_handler;
};
diff --git a/src/plugins/platforms/xcb/gl_integrations/xcb_egl/qxcbeglwindow.cpp b/src/plugins/platforms/xcb/gl_integrations/xcb_egl/qxcbeglwindow.cpp
index 4d87b08db8..bf2ceb96f4 100644
--- a/src/plugins/platforms/xcb/gl_integrations/xcb_egl/qxcbeglwindow.cpp
+++ b/src/plugins/platforms/xcb/gl_integrations/xcb_egl/qxcbeglwindow.cpp
@@ -7,6 +7,10 @@
#include <QtGui/private/qeglconvenience_p.h>
+#ifndef EGL_EXT_platform_base
+typedef EGLSurface (EGLAPIENTRYP PFNEGLCREATEPLATFORMWINDOWSURFACEEXTPROC) (EGLDisplay dpy, EGLConfig config, void *native_window, const EGLint *attrib_list);
+#endif
+
QT_BEGIN_NAMESPACE
QXcbEglWindow::QXcbEglWindow(QWindow *window, QXcbEglIntegration *glIntegration)
@@ -42,7 +46,20 @@ void QXcbEglWindow::create()
{
QXcbWindow::create();
+ // this is always true without egl_x11
+ if (m_glIntegration->usingPlatformDisplay()) {
+ auto createPlatformWindowSurface = reinterpret_cast<PFNEGLCREATEPLATFORMWINDOWSURFACEEXTPROC>(
+ eglGetProcAddress("eglCreatePlatformWindowSurfaceEXT"));
+ m_surface = createPlatformWindowSurface(m_glIntegration->eglDisplay(),
+ m_config,
+ reinterpret_cast<void *>(&m_window),
+ nullptr);
+ return;
+ }
+
+#if QT_CONFIG(egl_x11)
m_surface = eglCreateWindowSurface(m_glIntegration->eglDisplay(), m_config, m_window, nullptr);
+#endif
}
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/xcb/gl_integrations/xcb_glx/CMakeLists.txt b/src/plugins/platforms/xcb/gl_integrations/xcb_glx/CMakeLists.txt
index f676aeae91..f9f78ad1eb 100644
--- a/src/plugins/platforms/xcb/gl_integrations/xcb_glx/CMakeLists.txt
+++ b/src/plugins/platforms/xcb/gl_integrations/xcb_glx/CMakeLists.txt
@@ -1,7 +1,5 @@
# Copyright (C) 2022 The Qt Company Ltd.
-# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-
-# Generated from xcb_glx.pro.
+# SPDX-License-Identifier: BSD-3-Clause
#####################################################################
## QXcbGlxIntegrationPlugin Plugin:
@@ -27,6 +25,7 @@ qt_internal_add_plugin(QXcbGlxIntegrationPlugin
Qt::Gui
Qt::GuiPrivate
Qt::XcbQpaPrivate
+ NO_UNITY_BUILD # X11 define clashes
)
## Scopes:
diff --git a/src/plugins/platforms/xcb/gl_integrations/xcb_glx/qglxintegration.cpp b/src/plugins/platforms/xcb/gl_integrations/xcb_glx/qglxintegration.cpp
index 5992ebd74e..04eac027cd 100644
--- a/src/plugins/platforms/xcb/gl_integrations/xcb_glx/qglxintegration.cpp
+++ b/src/plugins/platforms/xcb/gl_integrations/xcb_glx/qglxintegration.cpp
@@ -242,9 +242,9 @@ QGLXContext::QGLXContext(Display *display, QXcbScreen *screen, const QSurfaceFor
// Robustness must match that of the shared context.
if (share && share->format().testOption(QSurfaceFormat::ResetNotification))
m_format.setOption(QSurfaceFormat::ResetNotification);
- Q_ASSERT(glVersions.count() > 0);
+ Q_ASSERT(glVersions.size() > 0);
- for (int i = 0; !m_context && i < glVersions.count(); i++) {
+ for (int i = 0; !m_context && i < glVersions.size(); i++) {
const int version = glVersions[i];
if (version > requestedVersion)
continue;
@@ -395,14 +395,19 @@ QGLXContext::QGLXContext(Display *display, GLXContext context, void *visualInfo,
int numConfigs = 0;
static const int attribs[] = { GLX_FBCONFIG_ID, configId, None };
configs = glXChooseFBConfig(m_display, screenNumber, attribs, &numConfigs);
- if (!configs || numConfigs < 1) {
+ if (!configs) {
+ qWarning("QGLXContext: Failed to find config(invalid arguments for glXChooseFBConfig)");
+ return;
+ } else if (numConfigs < 1) {
qWarning("QGLXContext: Failed to find config");
+ XFree(configs);
return;
}
if (configs && numConfigs > 1) // this is suspicious so warn but let it continue
qWarning("QGLXContext: Multiple configs for FBConfig ID %d", configId);
m_config = configs[0];
+ XFree(configs);
}
Q_ASSERT(vinfo || m_config);
diff --git a/src/plugins/platforms/xcb/nativepainting/qbackingstore_x11.cpp b/src/plugins/platforms/xcb/nativepainting/qbackingstore_x11.cpp
index 96a19552a5..d523eecc5a 100644
--- a/src/plugins/platforms/xcb/nativepainting/qbackingstore_x11.cpp
+++ b/src/plugins/platforms/xcb/nativepainting/qbackingstore_x11.cpp
@@ -16,10 +16,6 @@
#include <X11/Xlib.h>
#undef register
-#ifndef None
-#define None 0L
-#endif
-
QT_BEGIN_NAMESPACE
QXcbNativeBackingStore::QXcbNativeBackingStore(QWindow *window)
@@ -78,11 +74,8 @@ void QXcbNativeBackingStore::flush(QWindow *window, const QRegion &region, const
XRenderSetPictureClipRectangles(display(), dstPic, 0, 0, clipRects.constData(), clipRects.size());
- XRenderComposite(display(), PictOpSrc, srcPic, None, dstPic,
- br.x() + offset.x(), br.y() + offset.y(),
- 0, 0,
- br.x(), br.y(),
- br.width(), br.height());
+ XRenderComposite(display(), PictOpSrc, srcPic, 0L /*None*/, dstPic, br.x() + offset.x(),
+ br.y() + offset.y(), 0, 0, br.x(), br.y(), br.width(), br.height());
XRenderFreePicture(display(), dstPic);
}
diff --git a/src/plugins/platforms/xcb/nativepainting/qpaintengine_x11.cpp b/src/plugins/platforms/xcb/nativepainting/qpaintengine_x11.cpp
index f630f538aa..743ff92654 100644
--- a/src/plugins/platforms/xcb/nativepainting/qpaintengine_x11.cpp
+++ b/src/plugins/platforms/xcb/nativepainting/qpaintengine_x11.cpp
@@ -2134,7 +2134,7 @@ void QX11PaintEngine::drawPixmap(const QRectF &r, const QPixmap &px, const QRect
XFillRectangle(d->dpy, d->hd, d->gc, x, y, sw, sh);
restore_clip = true;
} else if (mono_dst && !mono_src) {
- QBitmap bitmap(pixmap);
+ QBitmap bitmap = QBitmap::fromPixmap(pixmap);
XCopyArea(d->dpy, qt_x11PixmapHandle(bitmap), d->hd, d->gc, sx, sy, sw, sh, x, y);
} else {
XCopyArea(d->dpy, qt_x11PixmapHandle(pixmap), d->hd, d->gc, sx, sy, sw, sh, x, y);
diff --git a/src/plugins/platforms/xcb/nativepainting/qpixmap_x11.cpp b/src/plugins/platforms/xcb/nativepainting/qpixmap_x11.cpp
index 186a3cf9d7..a4e820ea92 100644
--- a/src/plugins/platforms/xcb/nativepainting/qpixmap_x11.cpp
+++ b/src/plugins/platforms/xcb/nativepainting/qpixmap_x11.cpp
@@ -1314,7 +1314,7 @@ QBitmap QX11PlatformPixmap::mask() const
#endif
if (d == 1) {
QX11PlatformPixmap *that = const_cast<QX11PlatformPixmap*>(this);
- mask = QPixmap(that);
+ mask = QBitmap::fromPixmap(QPixmap(that));
} else {
mask = mask_to_bitmap(xinfo.screen());
}
diff --git a/src/plugins/platforms/xcb/nativepainting/qtessellator.cpp b/src/plugins/platforms/xcb/nativepainting/qtessellator.cpp
index ecc21eb1f5..7b0094044b 100644
--- a/src/plugins/platforms/xcb/nativepainting/qtessellator.cpp
+++ b/src/plugins/platforms/xcb/nativepainting/qtessellator.cpp
@@ -5,6 +5,7 @@
#include <QRect>
#include <QList>
+#include <QMap>
#include <QDebug>
#include <qmath.h>
diff --git a/src/plugins/platforms/xcb/qt_xlib_wrapper.c b/src/plugins/platforms/xcb/qt_xlib_wrapper.c
new file mode 100644
index 0000000000..f4614e5504
--- /dev/null
+++ b/src/plugins/platforms/xcb/qt_xlib_wrapper.c
@@ -0,0 +1,7 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+#include "qt_xlib_wrapper.h"
+
+#include <X11/Xlib.h>
+
+void qt_XFlush(Display *dpy) { XFlush(dpy); }
diff --git a/src/plugins/platforms/xcb/qt_xlib_wrapper.h b/src/plugins/platforms/xcb/qt_xlib_wrapper.h
new file mode 100644
index 0000000000..642dbdeadd
--- /dev/null
+++ b/src/plugins/platforms/xcb/qt_xlib_wrapper.h
@@ -0,0 +1,17 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+#ifndef QT_XLIB_WRAPPER_H
+#define QT_XLIB_WRAPPER_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+ typedef struct _XDisplay Display;
+ void qt_XFlush(Display *dpy);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // QT_XLIB_WRAPPER_H
diff --git a/src/plugins/platforms/xcb/qxcbatom.cpp b/src/plugins/platforms/xcb/qxcbatom.cpp
index 8f984c7280..dd5596653c 100644
--- a/src/plugins/platforms/xcb/qxcbatom.cpp
+++ b/src/plugins/platforms/xcb/qxcbatom.cpp
@@ -54,6 +54,8 @@ static const char *xcb_atomnames = {
"_QT_CLOSE_CONNECTION\0"
+ "_QT_GET_TIMESTAMP\0"
+
"_MOTIF_WM_HINTS\0"
"DTWM_IS_RUNNING\0"
@@ -109,11 +111,13 @@ static const char *xcb_atomnames = {
"_NET_WM_WINDOW_TYPE_NORMAL\0"
"_KDE_NET_WM_WINDOW_TYPE_OVERRIDE\0"
+ "_KDE_NET_WM_DESKTOP_FILE\0"
"_KDE_NET_WM_FRAME_STRUT\0"
"_NET_FRAME_EXTENTS\0"
"_NET_STARTUP_INFO\0"
"_NET_STARTUP_INFO_BEGIN\0"
+ "_NET_STARTUP_ID\0"
"_NET_SUPPORTING_WM_CHECK\0"
@@ -193,6 +197,7 @@ static const char *xcb_atomnames = {
"_COMPIZ_DECOR_REQUEST\0"
"_COMPIZ_DECOR_DELETE_PIXMAP\0"
"_COMPIZ_TOOLKIT_ACTION\0"
+ "_GTK_APPLICATION_ID\0"
"_GTK_LOAD_ICONTHEMES\0"
"AT_SPI_BUS\0"
"EDID\0"
@@ -212,29 +217,25 @@ void QXcbAtom::initialize(xcb_connection_t *connection)
}
void QXcbAtom::initializeAllAtoms(xcb_connection_t *connection) {
- const char *names[QXcbAtom::NAtoms];
- const char *ptr = xcb_atomnames;
-
+ const char *name = xcb_atomnames;
+ size_t name_len;
int i = 0;
- while (*ptr) {
- names[i++] = ptr;
- while (*ptr)
- ++ptr;
- ++ptr;
- }
-
- Q_ASSERT(i == QXcbAtom::NAtoms);
-
xcb_intern_atom_cookie_t cookies[QXcbAtom::NAtoms];
+ while ((name_len = strlen(name)) != 0) {
+ cookies[i] = xcb_intern_atom(connection, false, name_len, name);
+ ++i;
+ name += name_len + 1; // jump over the \0
+ }
+
Q_ASSERT(i == QXcbAtom::NAtoms);
- for (i = 0; i < QXcbAtom::NAtoms; ++i)
- cookies[i] = xcb_intern_atom(connection, false, strlen(names[i]), names[i]);
for (i = 0; i < QXcbAtom::NAtoms; ++i) {
xcb_intern_atom_reply_t *reply = xcb_intern_atom_reply(connection, cookies[i], nullptr);
- m_allAtoms[i] = reply->atom;
- free(reply);
+ if (reply) {
+ m_allAtoms[i] = reply->atom;
+ free(reply);
+ }
}
}
diff --git a/src/plugins/platforms/xcb/qxcbatom.h b/src/plugins/platforms/xcb/qxcbatom.h
index 48924dd626..bc677eaf3e 100644
--- a/src/plugins/platforms/xcb/qxcbatom.h
+++ b/src/plugins/platforms/xcb/qxcbatom.h
@@ -10,201 +10,206 @@ class QXcbAtom
public:
enum Atom {
// window-manager <-> client protocols
- WM_PROTOCOLS,
- WM_DELETE_WINDOW,
- WM_TAKE_FOCUS,
- _NET_WM_PING,
- _NET_WM_CONTEXT_HELP,
- _NET_WM_SYNC_REQUEST,
- _NET_WM_SYNC_REQUEST_COUNTER,
- MANAGER, // System tray notification
- _NET_SYSTEM_TRAY_OPCODE, // System tray operation
+ AtomWM_PROTOCOLS,
+ AtomWM_DELETE_WINDOW,
+ AtomWM_TAKE_FOCUS,
+ Atom_NET_WM_PING,
+ Atom_NET_WM_CONTEXT_HELP,
+ Atom_NET_WM_SYNC_REQUEST,
+ Atom_NET_WM_SYNC_REQUEST_COUNTER,
+ AtomMANAGER, // System tray notification
+ Atom_NET_SYSTEM_TRAY_OPCODE, // System tray operation
// ICCCM window state
- WM_STATE,
- WM_CHANGE_STATE,
- WM_CLASS,
- WM_NAME,
+ AtomWM_STATE,
+ AtomWM_CHANGE_STATE,
+ AtomWM_CLASS,
+ AtomWM_NAME,
// Session management
- WM_CLIENT_LEADER,
- WM_WINDOW_ROLE,
- SM_CLIENT_ID,
- WM_CLIENT_MACHINE,
+ AtomWM_CLIENT_LEADER,
+ AtomWM_WINDOW_ROLE,
+ AtomSM_CLIENT_ID,
+ AtomWM_CLIENT_MACHINE,
// Clipboard
- CLIPBOARD,
- INCR,
- TARGETS,
- MULTIPLE,
- TIMESTAMP,
- SAVE_TARGETS,
- CLIP_TEMPORARY,
- _QT_SELECTION,
- _QT_CLIPBOARD_SENTINEL,
- _QT_SELECTION_SENTINEL,
- CLIPBOARD_MANAGER,
+ AtomCLIPBOARD,
+ AtomINCR,
+ AtomTARGETS,
+ AtomMULTIPLE,
+ AtomTIMESTAMP,
+ AtomSAVE_TARGETS,
+ AtomCLIP_TEMPORARY,
+ Atom_QT_SELECTION,
+ Atom_QT_CLIPBOARD_SENTINEL,
+ Atom_QT_SELECTION_SENTINEL,
+ AtomCLIPBOARD_MANAGER,
- RESOURCE_MANAGER,
+ AtomRESOURCE_MANAGER,
- _XSETROOT_ID,
+ Atom_XSETROOT_ID,
- _QT_SCROLL_DONE,
- _QT_INPUT_ENCODING,
+ Atom_QT_SCROLL_DONE,
+ Atom_QT_INPUT_ENCODING,
// Qt/XCB specific
- _QT_CLOSE_CONNECTION,
+ Atom_QT_CLOSE_CONNECTION,
- _MOTIF_WM_HINTS,
+ Atom_QT_GET_TIMESTAMP,
- DTWM_IS_RUNNING,
- ENLIGHTENMENT_DESKTOP,
- _DT_SAVE_MODE,
- _SGI_DESKS_MANAGER,
+ Atom_MOTIF_WM_HINTS,
+
+ AtomDTWM_IS_RUNNING,
+ AtomENLIGHTENMENT_DESKTOP,
+ Atom_DT_SAVE_MODE,
+ Atom_SGI_DESKS_MANAGER,
// EWMH (aka NETWM)
- _NET_SUPPORTED,
- _NET_VIRTUAL_ROOTS,
- _NET_WORKAREA,
-
- _NET_MOVERESIZE_WINDOW,
- _NET_WM_MOVERESIZE,
-
- _NET_WM_NAME,
- _NET_WM_ICON_NAME,
- _NET_WM_ICON,
-
- _NET_WM_PID,
-
- _NET_WM_WINDOW_OPACITY,
-
- _NET_WM_STATE,
- _NET_WM_STATE_ABOVE,
- _NET_WM_STATE_BELOW,
- _NET_WM_STATE_FULLSCREEN,
- _NET_WM_STATE_MAXIMIZED_HORZ,
- _NET_WM_STATE_MAXIMIZED_VERT,
- _NET_WM_STATE_MODAL,
- _NET_WM_STATE_STAYS_ON_TOP,
- _NET_WM_STATE_DEMANDS_ATTENTION,
- _NET_WM_STATE_HIDDEN,
-
- _NET_WM_USER_TIME,
- _NET_WM_USER_TIME_WINDOW,
- _NET_WM_FULL_PLACEMENT,
-
- _NET_WM_WINDOW_TYPE,
- _NET_WM_WINDOW_TYPE_DESKTOP,
- _NET_WM_WINDOW_TYPE_DOCK,
- _NET_WM_WINDOW_TYPE_TOOLBAR,
- _NET_WM_WINDOW_TYPE_MENU,
- _NET_WM_WINDOW_TYPE_UTILITY,
- _NET_WM_WINDOW_TYPE_SPLASH,
- _NET_WM_WINDOW_TYPE_DIALOG,
- _NET_WM_WINDOW_TYPE_DROPDOWN_MENU,
- _NET_WM_WINDOW_TYPE_POPUP_MENU,
- _NET_WM_WINDOW_TYPE_TOOLTIP,
- _NET_WM_WINDOW_TYPE_NOTIFICATION,
- _NET_WM_WINDOW_TYPE_COMBO,
- _NET_WM_WINDOW_TYPE_DND,
- _NET_WM_WINDOW_TYPE_NORMAL,
- _KDE_NET_WM_WINDOW_TYPE_OVERRIDE,
-
- _KDE_NET_WM_FRAME_STRUT,
- _NET_FRAME_EXTENTS,
-
- _NET_STARTUP_INFO,
- _NET_STARTUP_INFO_BEGIN,
-
- _NET_SUPPORTING_WM_CHECK,
-
- _NET_WM_CM_S0,
-
- _NET_SYSTEM_TRAY_VISUAL,
-
- _NET_ACTIVE_WINDOW,
+ Atom_NET_SUPPORTED,
+ Atom_NET_VIRTUAL_ROOTS,
+ Atom_NET_WORKAREA,
+
+ Atom_NET_MOVERESIZE_WINDOW,
+ Atom_NET_WM_MOVERESIZE,
+
+ Atom_NET_WM_NAME,
+ Atom_NET_WM_ICON_NAME,
+ Atom_NET_WM_ICON,
+
+ Atom_NET_WM_PID,
+
+ Atom_NET_WM_WINDOW_OPACITY,
+
+ Atom_NET_WM_STATE,
+ Atom_NET_WM_STATE_ABOVE,
+ Atom_NET_WM_STATE_BELOW,
+ Atom_NET_WM_STATE_FULLSCREEN,
+ Atom_NET_WM_STATE_MAXIMIZED_HORZ,
+ Atom_NET_WM_STATE_MAXIMIZED_VERT,
+ Atom_NET_WM_STATE_MODAL,
+ Atom_NET_WM_STATE_STAYS_ON_TOP,
+ Atom_NET_WM_STATE_DEMANDS_ATTENTION,
+ Atom_NET_WM_STATE_HIDDEN,
+
+ Atom_NET_WM_USER_TIME,
+ Atom_NET_WM_USER_TIME_WINDOW,
+ Atom_NET_WM_FULL_PLACEMENT,
+
+ Atom_NET_WM_WINDOW_TYPE,
+ Atom_NET_WM_WINDOW_TYPE_DESKTOP,
+ Atom_NET_WM_WINDOW_TYPE_DOCK,
+ Atom_NET_WM_WINDOW_TYPE_TOOLBAR,
+ Atom_NET_WM_WINDOW_TYPE_MENU,
+ Atom_NET_WM_WINDOW_TYPE_UTILITY,
+ Atom_NET_WM_WINDOW_TYPE_SPLASH,
+ Atom_NET_WM_WINDOW_TYPE_DIALOG,
+ Atom_NET_WM_WINDOW_TYPE_DROPDOWN_MENU,
+ Atom_NET_WM_WINDOW_TYPE_POPUP_MENU,
+ Atom_NET_WM_WINDOW_TYPE_TOOLTIP,
+ Atom_NET_WM_WINDOW_TYPE_NOTIFICATION,
+ Atom_NET_WM_WINDOW_TYPE_COMBO,
+ Atom_NET_WM_WINDOW_TYPE_DND,
+ Atom_NET_WM_WINDOW_TYPE_NORMAL,
+ Atom_KDE_NET_WM_WINDOW_TYPE_OVERRIDE,
+
+ Atom_KDE_NET_WM_DESKTOP_FILE,
+ Atom_KDE_NET_WM_FRAME_STRUT,
+ Atom_NET_FRAME_EXTENTS,
+
+ Atom_NET_STARTUP_INFO,
+ Atom_NET_STARTUP_INFO_BEGIN,
+ Atom_NET_STARTUP_ID,
+
+ Atom_NET_SUPPORTING_WM_CHECK,
+
+ Atom_NET_WM_CM_S0,
+
+ Atom_NET_SYSTEM_TRAY_VISUAL,
+
+ Atom_NET_ACTIVE_WINDOW,
// Property formats
- TEXT,
- UTF8_STRING,
- CARDINAL,
+ AtomTEXT,
+ AtomUTF8_STRING,
+ AtomCARDINAL,
// Xdnd
- XdndEnter,
- XdndPosition,
- XdndStatus,
- XdndLeave,
- XdndDrop,
- XdndFinished,
- XdndTypelist,
- XdndActionList,
-
- XdndSelection,
-
- XdndAware,
- XdndProxy,
-
- XdndActionCopy,
- XdndActionLink,
- XdndActionMove,
- XdndActionAsk,
- XdndActionPrivate,
+ AtomXdndEnter,
+ AtomXdndPosition,
+ AtomXdndStatus,
+ AtomXdndLeave,
+ AtomXdndDrop,
+ AtomXdndFinished,
+ AtomXdndTypelist,
+ AtomXdndActionList,
+
+ AtomXdndSelection,
+
+ AtomXdndAware,
+ AtomXdndProxy,
+
+ AtomXdndActionCopy,
+ AtomXdndActionLink,
+ AtomXdndActionMove,
+ AtomXdndActionAsk,
+ AtomXdndActionPrivate,
// Xkb
- _XKB_RULES_NAMES,
+ Atom_XKB_RULES_NAMES,
// XEMBED
- _XEMBED,
- _XEMBED_INFO,
+ Atom_XEMBED,
+ Atom_XEMBED_INFO,
// XInput2
- ButtonLeft,
- ButtonMiddle,
- ButtonRight,
- ButtonWheelUp,
- ButtonWheelDown,
- ButtonHorizWheelLeft,
- ButtonHorizWheelRight,
- AbsMTPositionX,
- AbsMTPositionY,
- AbsMTTouchMajor,
- AbsMTTouchMinor,
- AbsMTOrientation,
- AbsMTPressure,
- AbsMTTrackingID,
- MaxContacts,
- RelX,
- RelY,
+ AtomButtonLeft,
+ AtomButtonMiddle,
+ AtomButtonRight,
+ AtomButtonWheelUp,
+ AtomButtonWheelDown,
+ AtomButtonHorizWheelLeft,
+ AtomButtonHorizWheelRight,
+ AtomAbsMTPositionX,
+ AtomAbsMTPositionY,
+ AtomAbsMTTouchMajor,
+ AtomAbsMTTouchMinor,
+ AtomAbsMTOrientation,
+ AtomAbsMTPressure,
+ AtomAbsMTTrackingID,
+ AtomMaxContacts,
+ AtomRelX,
+ AtomRelY,
// XInput2 tablet
- AbsX,
- AbsY,
- AbsPressure,
- AbsTiltX,
- AbsTiltY,
- AbsWheel,
- AbsDistance,
- WacomSerialIDs,
- INTEGER,
- RelHorizWheel,
- RelVertWheel,
- RelHorizScroll,
- RelVertScroll,
-
- _XSETTINGS_SETTINGS,
-
- _COMPIZ_DECOR_PENDING,
- _COMPIZ_DECOR_REQUEST,
- _COMPIZ_DECOR_DELETE_PIXMAP,
- _COMPIZ_TOOLKIT_ACTION,
- _GTK_LOAD_ICONTHEMES,
-
- AT_SPI_BUS,
-
- EDID,
- EDID_DATA,
- XFree86_DDC_EDID1_RAWDATA,
-
- _ICC_PROFILE,
+ AtomAbsX,
+ AtomAbsY,
+ AtomAbsPressure,
+ AtomAbsTiltX,
+ AtomAbsTiltY,
+ AtomAbsWheel,
+ AtomAbsDistance,
+ AtomWacomSerialIDs,
+ AtomINTEGER,
+ AtomRelHorizWheel,
+ AtomRelVertWheel,
+ AtomRelHorizScroll,
+ AtomRelVertScroll,
+
+ Atom_XSETTINGS_SETTINGS,
+
+ Atom_COMPIZ_DECOR_PENDING,
+ Atom_COMPIZ_DECOR_REQUEST,
+ Atom_COMPIZ_DECOR_DELETE_PIXMAP,
+ Atom_COMPIZ_TOOLKIT_ACTION,
+ Atom_GTK_APPLICATION_ID,
+ Atom_GTK_LOAD_ICONTHEMES,
+
+ AtomAT_SPI_BUS,
+
+ AtomEDID,
+ AtomEDID_DATA,
+ AtomXFree86_DDC_EDID1_RAWDATA,
+
+ Atom_ICC_PROFILE,
NAtoms
};
diff --git a/src/plugins/platforms/xcb/qxcbbackingstore.cpp b/src/plugins/platforms/xcb/qxcbbackingstore.cpp
index f3905dff81..8353fac6a9 100644
--- a/src/plugins/platforms/xcb/qxcbbackingstore.cpp
+++ b/src/plugins/platforms/xcb/qxcbbackingstore.cpp
@@ -173,7 +173,7 @@ void QXcbBackingStoreImage::init(const QSize &size, uint depth, QImage::Format f
m_qimage_format = format;
m_hasAlpha = QImage::toPixelFormat(m_qimage_format).alphaUsage() == QPixelFormat::UsesAlpha;
if (!m_hasAlpha)
- m_qimage_format = qt_maybeAlphaVersionWithSameDepth(m_qimage_format);
+ m_qimage_format = qt_maybeDataCompatibleAlphaVersion(m_qimage_format);
memset(&m_shm_info, 0, sizeof m_shm_info);
@@ -877,8 +877,10 @@ QPlatformBackingStore::FlushResult QXcbBackingStore::rhiFlush(QWindow *window,
m_image->flushScrolledRegion(true);
- QPlatformBackingStore::rhiFlush(window, sourceDevicePixelRatio, region, offset, textures, translucentBackground);
-
+ auto result = QPlatformBackingStore::rhiFlush(window, sourceDevicePixelRatio, region, offset,
+ textures, translucentBackground);
+ if (result != FlushSuccess)
+ return result;
QXcbWindow *platformWindow = static_cast<QXcbWindow *>(window->handle());
if (platformWindow->needsSync()) {
platformWindow->updateSyncRequestCounter();
diff --git a/src/plugins/platforms/xcb/qxcbclipboard.cpp b/src/plugins/platforms/xcb/qxcbclipboard.cpp
index 6c15d7c142..40e2f47354 100644
--- a/src/plugins/platforms/xcb/qxcbclipboard.cpp
+++ b/src/plugins/platforms/xcb/qxcbclipboard.cpp
@@ -31,7 +31,7 @@ public:
break;
case QClipboard::Clipboard:
- modeAtom = m_clipboard->atom(QXcbAtom::CLIPBOARD);
+ modeAtom = m_clipboard->atom(QXcbAtom::AtomCLIPBOARD);
break;
default:
@@ -56,12 +56,12 @@ protected:
if (isEmpty())
return QStringList();
- if (!formatList.count()) {
+ if (!formatList.size()) {
QXcbClipboardMime *that = const_cast<QXcbClipboardMime *>(this);
// get the list of targets from the current clipboard owner - we do this
// once so that multiple calls to this function don't require multiple
// server round trips...
- that->format_atoms = m_clipboard->getDataInFormat(modeAtom, m_clipboard->atom(QXcbAtom::TARGETS));
+ that->format_atoms = m_clipboard->getDataInFormat(modeAtom, m_clipboard->atom(QXcbAtom::AtomTARGETS));
if (format_atoms.size() > 0) {
const xcb_atom_t *targets = (const xcb_atom_t *) format_atoms.data();
@@ -126,7 +126,7 @@ QXcbClipboardTransaction::QXcbClipboardTransaction(QXcbClipboard *clipboard, xcb
xcb_change_window_attributes(m_clipboard->xcb_connection(), m_window,
XCB_CW_EVENT_MASK, values);
- m_abortTimerId = startTimer(m_clipboard->clipboardTimeout());
+ m_abortTimerId = startTimer(std::chrono::milliseconds{m_clipboard->clipboardTimeout()});
}
QXcbClipboardTransaction::~QXcbClipboardTransaction()
@@ -145,7 +145,7 @@ bool QXcbClipboardTransaction::updateIncrementalProperty(const xcb_property_noti
// restart the timer
if (m_abortTimerId)
killTimer(m_abortTimerId);
- m_abortTimerId = startTimer(m_clipboard->clipboardTimeout());
+ m_abortTimerId = startTimer(std::chrono::milliseconds{m_clipboard->clipboardTimeout()});
uint bytes_left = uint(m_data.size()) - m_offset;
if (bytes_left > 0) {
@@ -203,7 +203,7 @@ QXcbClipboard::QXcbClipboard(QXcbConnection *c)
xcb_xfixes_select_selection_input_checked(xcb_connection(), connection()->qtSelectionOwner(),
XCB_ATOM_PRIMARY, mask);
xcb_xfixes_select_selection_input_checked(xcb_connection(), connection()->qtSelectionOwner(),
- atom(QXcbAtom::CLIPBOARD), mask);
+ atom(QXcbAtom::AtomCLIPBOARD), mask);
}
// xcb_change_property_request_t and xcb_get_property_request_t are the same size
@@ -218,13 +218,13 @@ QXcbClipboard::~QXcbClipboard()
m_timestamp[QClipboard::Selection] != XCB_CURRENT_TIME) {
// First we check if there is a clipboard manager.
- if (connection()->selectionOwner(atom(QXcbAtom::CLIPBOARD_MANAGER)) != XCB_NONE) {
+ if (connection()->selectionOwner(atom(QXcbAtom::AtomCLIPBOARD_MANAGER)) != XCB_NONE) {
// we delete the property so the manager saves all TARGETS.
xcb_delete_property(xcb_connection(), connection()->qtSelectionOwner(),
- atom(QXcbAtom::_QT_SELECTION));
+ atom(QXcbAtom::Atom_QT_SELECTION));
xcb_convert_selection(xcb_connection(), connection()->qtSelectionOwner(),
- atom(QXcbAtom::CLIPBOARD_MANAGER), atom(QXcbAtom::SAVE_TARGETS),
- atom(QXcbAtom::_QT_SELECTION), connection()->time());
+ atom(QXcbAtom::AtomCLIPBOARD_MANAGER), atom(QXcbAtom::AtomSAVE_TARGETS),
+ atom(QXcbAtom::Atom_QT_SELECTION), connection()->time());
connection()->sync();
// waiting until the clipboard manager fetches the content.
@@ -259,7 +259,7 @@ bool QXcbClipboard::handlePropertyNotify(const xcb_generic_event_t *event)
xcb_atom_t QXcbClipboard::atomForMode(QClipboard::Mode mode) const
{
if (mode == QClipboard::Clipboard)
- return atom(QXcbAtom::CLIPBOARD);
+ return atom(QXcbAtom::AtomCLIPBOARD);
if (mode == QClipboard::Selection)
return XCB_ATOM_PRIMARY;
return XCB_NONE;
@@ -269,7 +269,7 @@ QClipboard::Mode QXcbClipboard::modeForAtom(xcb_atom_t a) const
{
if (a == XCB_ATOM_PRIMARY)
return QClipboard::Selection;
- if (a == atom(QXcbAtom::CLIPBOARD))
+ if (a == atom(QXcbAtom::AtomCLIPBOARD))
return QClipboard::Clipboard;
// not supported enum value, used to detect errors
return QClipboard::FindBuffer;
@@ -412,10 +412,10 @@ xcb_atom_t QXcbClipboard::sendTargetsSelection(QMimeData *d, xcb_window_t window
types.append(atoms.at(j));
}
}
- types.append(atom(QXcbAtom::TARGETS));
- types.append(atom(QXcbAtom::MULTIPLE));
- types.append(atom(QXcbAtom::TIMESTAMP));
- types.append(atom(QXcbAtom::SAVE_TARGETS));
+ types.append(atom(QXcbAtom::AtomTARGETS));
+ types.append(atom(QXcbAtom::AtomMULTIPLE));
+ types.append(atom(QXcbAtom::AtomTIMESTAMP));
+ types.append(atom(QXcbAtom::AtomSAVE_TARGETS));
xcb_change_property(xcb_connection(), XCB_PROP_MODE_REPLACE, window, property, XCB_ATOM_ATOM,
32, types.size(), (const void *)types.constData());
@@ -439,7 +439,7 @@ xcb_atom_t QXcbClipboard::sendSelection(QMimeData *d, xcb_atom_t target, xcb_win
// don't allow INCR transfers when using MULTIPLE or to
// Motif clients (since Motif doesn't support INCR)
- static xcb_atom_t motif_clip_temporary = atom(QXcbAtom::CLIP_TEMPORARY);
+ static xcb_atom_t motif_clip_temporary = atom(QXcbAtom::AtomCLIP_TEMPORARY);
bool allow_incr = property != motif_clip_temporary;
// This 'bool' can be removed once there is a proper fix for QTBUG-32853
if (m_clipboard_closing)
@@ -448,7 +448,7 @@ xcb_atom_t QXcbClipboard::sendSelection(QMimeData *d, xcb_atom_t target, xcb_win
if (data.size() > m_maxPropertyRequestDataBytes && allow_incr) {
long bytes = data.size();
xcb_change_property(xcb_connection(), XCB_PROP_MODE_REPLACE, window, property,
- atom(QXcbAtom::INCR), 32, 1, (const void *)&bytes);
+ atom(QXcbAtom::AtomINCR), 32, 1, (const void *)&bytes);
auto transaction = new QXcbClipboardTransaction(this, window, property, data, atomFormat, dataFormat);
m_transactions.insert(window, transaction);
return property;
@@ -532,9 +532,9 @@ void QXcbClipboard::handleSelectionRequest(xcb_selection_request_event_t *req)
return;
}
- xcb_atom_t targetsAtom = atom(QXcbAtom::TARGETS);
- xcb_atom_t multipleAtom = atom(QXcbAtom::MULTIPLE);
- xcb_atom_t timestampAtom = atom(QXcbAtom::TIMESTAMP);
+ xcb_atom_t targetsAtom = atom(QXcbAtom::AtomTARGETS);
+ xcb_atom_t multipleAtom = atom(QXcbAtom::AtomMULTIPLE);
+ xcb_atom_t timestampAtom = atom(QXcbAtom::AtomTIMESTAMP);
struct AtomPair { xcb_atom_t target; xcb_atom_t property; } *multi = nullptr;
xcb_atom_t multi_type = XCB_NONE;
@@ -708,7 +708,7 @@ bool QXcbClipboard::clipboardReadProperty(xcb_window_t win, xcb_atom_t property,
// correct size, not 0-term.
if (size)
*size = buffer_offset;
- if (*type == atom(QXcbAtom::INCR))
+ if (*type == atom(QXcbAtom::AtomINCR))
m_incr_receive_time = connection()->getTimestamp();
if (deleteProperty)
xcb_delete_property(xcb_connection(), win, property);
@@ -747,12 +747,12 @@ xcb_generic_event_t *QXcbClipboard::waitForClipboardEvent(xcb_window_t window, i
const QXcbEventNode *flushedTailNode = queue->flushedTail();
if (checkManager) {
- if (connection()->selectionOwner(atom(QXcbAtom::CLIPBOARD_MANAGER)) == XCB_NONE)
+ if (connection()->selectionOwner(atom(QXcbAtom::AtomCLIPBOARD_MANAGER)) == XCB_NONE)
return nullptr;
}
// process other clipboard events, since someone is probably requesting data from us
- auto clipboardAtom = atom(QXcbAtom::CLIPBOARD);
+ auto clipboardAtom = atom(QXcbAtom::AtomCLIPBOARD);
e = queue->peek([clipboardAtom](xcb_generic_event_t *event, int type) {
xcb_atom_t selection = XCB_ATOM_NONE;
if (type == XCB_SELECTION_REQUEST)
@@ -846,7 +846,7 @@ QByteArray QXcbClipboard::clipboardReadIncrementalProperty(xcb_window_t win, xcb
QByteArray QXcbClipboard::getDataInFormat(xcb_atom_t modeAtom, xcb_atom_t fmtAtom)
{
- return getSelection(modeAtom, fmtAtom, atom(QXcbAtom::_QT_SELECTION));
+ return getSelection(modeAtom, fmtAtom, atom(QXcbAtom::Atom_QT_SELECTION));
}
QByteArray QXcbClipboard::getSelection(xcb_atom_t selection, xcb_atom_t target, xcb_atom_t property, xcb_timestamp_t time)
@@ -870,7 +870,7 @@ QByteArray QXcbClipboard::getSelection(xcb_atom_t selection, xcb_atom_t target,
xcb_atom_t type;
if (clipboardReadProperty(win, property, true, &buf, nullptr, &type, nullptr)) {
- if (type == atom(QXcbAtom::INCR)) {
+ if (type == atom(QXcbAtom::AtomINCR)) {
int nbytes = buf.size() >= 4 ? *((int*)buf.data()) : 0;
buf = clipboardReadIncrementalProperty(win, property, nbytes, false);
}
diff --git a/src/plugins/platforms/xcb/qxcbconnection.cpp b/src/plugins/platforms/xcb/qxcbconnection.cpp
index 51530e0055..1056c6408f 100644
--- a/src/plugins/platforms/xcb/qxcbconnection.cpp
+++ b/src/plugins/platforms/xcb/qxcbconnection.cpp
@@ -35,6 +35,10 @@
#undef explicit
#include <xcb/xinput.h>
+#if QT_CONFIG(xcb_xlib)
+#include "qt_xlib_wrapper.h"
+#endif
+
QT_BEGIN_NAMESPACE
using namespace Qt::StringLiterals;
@@ -81,16 +85,16 @@ QXcbConnection::QXcbConnection(QXcbNativeInterface *nativeInterface, bool canGra
m_drag = new QXcbDrag(this);
#endif
- m_startupId = qgetenv("DESKTOP_STARTUP_ID");
- if (!m_startupId.isNull())
+ setStartupId(qgetenv("DESKTOP_STARTUP_ID"));
+ if (!startupId().isNull())
qunsetenv("DESKTOP_STARTUP_ID");
const int focusInDelay = 100;
m_focusInTimer.setSingleShot(true);
m_focusInTimer.setInterval(focusInDelay);
- m_focusInTimer.callOnTimeout([]() {
+ m_focusInTimer.callOnTimeout(this, []() {
// No FocusIn events for us, proceed with FocusOut normally.
- QWindowSystemInterface::handleWindowActivated(nullptr, Qt::ActiveWindowFocusReason);
+ QWindowSystemInterface::handleFocusWindowChanged(nullptr, Qt::ActiveWindowFocusReason);
});
sync();
@@ -141,12 +145,12 @@ void QXcbConnection::removeWindowEventListener(xcb_window_t id)
QXcbWindowEventListener *QXcbConnection::windowEventListenerFromId(xcb_window_t id)
{
- return m_mapper.value(id, 0);
+ return m_mapper.value(id, nullptr);
}
QXcbWindow *QXcbConnection::platformWindowFromId(xcb_window_t id)
{
- QXcbWindowEventListener *listener = m_mapper.value(id, 0);
+ QXcbWindowEventListener *listener = m_mapper.value(id, nullptr);
if (listener)
return listener->toWindow();
return nullptr;
@@ -598,12 +602,12 @@ void QXcbConnection::handleXcbEvent(xcb_generic_event_t *event)
if (clientMessage->format != 32)
return;
#if QT_CONFIG(draganddrop)
- if (clientMessage->type == atom(QXcbAtom::XdndStatus))
+ if (clientMessage->type == atom(QXcbAtom::AtomXdndStatus))
drag()->handleStatus(clientMessage);
- else if (clientMessage->type == atom(QXcbAtom::XdndFinished))
+ else if (clientMessage->type == atom(QXcbAtom::AtomXdndFinished))
drag()->handleFinished(clientMessage);
#endif
- if (m_systemTrayTracker && clientMessage->type == atom(QXcbAtom::MANAGER))
+ if (m_systemTrayTracker && clientMessage->type == atom(QXcbAtom::AtomMANAGER))
m_systemTrayTracker->notifyManagerClientMessageEvent(clientMessage);
HANDLE_PLATFORM_WINDOW_EVENT(xcb_client_message_event_t, window, handleClientMessageEvent);
}
@@ -654,7 +658,7 @@ void QXcbConnection::handleXcbEvent(xcb_generic_event_t *event)
setTime(selectionRequest->time);
#endif
#if QT_CONFIG(draganddrop)
- if (selectionRequest->selection == atom(QXcbAtom::XdndSelection))
+ if (selectionRequest->selection == atom(QXcbAtom::AtomXdndSelection))
m_drag->handleSelectionRequest(selectionRequest);
else
#endif
@@ -682,11 +686,11 @@ void QXcbConnection::handleXcbEvent(xcb_generic_event_t *event)
if (m_clipboard->handlePropertyNotify(event))
break;
#endif
- if (propertyNotify->atom == atom(QXcbAtom::_NET_WORKAREA)) {
+ if (propertyNotify->atom == atom(QXcbAtom::Atom_NET_WORKAREA)) {
QXcbVirtualDesktop *virtualDesktop = virtualDesktopForRootWindow(propertyNotify->window);
if (virtualDesktop)
virtualDesktop->updateWorkArea();
- } else if (propertyNotify->atom == atom(QXcbAtom::_NET_SUPPORTED)) {
+ } else if (propertyNotify->atom == atom(QXcbAtom::Atom_NET_SUPPORTED)) {
m_wmSupport->updateNetWMAtoms();
} else {
HANDLE_PLATFORM_WINDOW_EVENT(xcb_property_notify_event_t, window, handlePropertyNotifyEvent);
@@ -713,7 +717,7 @@ void QXcbConnection::handleXcbEvent(xcb_generic_event_t *event)
#ifndef QT_NO_CLIPBOARD
m_clipboard->handleXFixesSelectionRequest(notify_event);
#endif
- for (QXcbVirtualDesktop *virtualDesktop : qAsConst(m_virtualDesktops))
+ for (QXcbVirtualDesktop *virtualDesktop : std::as_const(m_virtualDesktops))
virtualDesktop->handleXFixesSelectionNotify(notify_event);
} else if (isXRandrType(response_type, XCB_RANDR_NOTIFY)) {
if (!isAtLeastXRandR15())
@@ -771,6 +775,28 @@ void QXcbConnection::setMousePressWindow(QXcbWindow *w)
m_mousePressWindow = w;
}
+QByteArray QXcbConnection::startupId() const
+{
+ return m_startupId;
+}
+void QXcbConnection::setStartupId(const QByteArray &nextId)
+{
+ m_startupId = nextId;
+ if (m_clientLeader) {
+ if (!nextId.isEmpty())
+ xcb_change_property(xcb_connection(),
+ XCB_PROP_MODE_REPLACE,
+ clientLeader(),
+ atom(QXcbAtom::Atom_NET_STARTUP_ID),
+ atom(QXcbAtom::AtomUTF8_STRING),
+ 8,
+ nextId.size(),
+ nextId.constData());
+ else
+ xcb_delete_property(xcb_connection(), clientLeader(), atom(QXcbAtom::Atom_NET_STARTUP_ID));
+ }
+}
+
void QXcbConnection::grabServer()
{
if (m_canGrabServer)
@@ -796,8 +822,8 @@ xcb_timestamp_t QXcbConnection::getTimestamp()
{
// send a dummy event to myself to get the timestamp from X server.
xcb_window_t window = rootWindow();
- xcb_atom_t dummyAtom = atom(QXcbAtom::CLIP_TEMPORARY);
- xcb_change_property(xcb_connection(), XCB_PROP_MODE_APPEND, window, dummyAtom,
+ xcb_atom_t dummyAtom = atom(QXcbAtom::Atom_QT_GET_TIMESTAMP);
+ xcb_change_property(xcb_connection(), XCB_PROP_MODE_REPLACE, window, dummyAtom,
XCB_ATOM_INTEGER, 32, 0, nullptr);
connection()->flush();
@@ -829,8 +855,6 @@ xcb_timestamp_t QXcbConnection::getTimestamp()
xcb_timestamp_t timestamp = pn->time;
free(event);
- xcb_delete_property(xcb_connection(), window, dummyAtom);
-
return timestamp;
}
@@ -897,7 +921,7 @@ xcb_window_t QXcbConnection::clientLeader()
xcb_change_property(xcb_connection(),
XCB_PROP_MODE_REPLACE,
m_clientLeader,
- atom(QXcbAtom::WM_CLIENT_LEADER),
+ atom(QXcbAtom::AtomWM_CLIENT_LEADER),
XCB_ATOM_WINDOW,
32,
1,
@@ -910,13 +934,15 @@ xcb_window_t QXcbConnection::clientLeader()
xcb_change_property(xcb_connection(),
XCB_PROP_MODE_REPLACE,
m_clientLeader,
- atom(QXcbAtom::SM_CLIENT_ID),
+ atom(QXcbAtom::AtomSM_CLIENT_ID),
XCB_ATOM_STRING,
8,
- session.length(),
+ session.size(),
session.constData());
}
#endif
+
+ setStartupId(startupId());
}
return m_clientLeader;
}
@@ -1029,8 +1055,8 @@ bool QXcbConnection::isUserInputEvent(xcb_generic_event_t *event) const
if (eventType == XCB_CLIENT_MESSAGE) {
auto clientMessage = reinterpret_cast<const xcb_client_message_event_t *>(event);
- if (clientMessage->format == 32 && clientMessage->type == atom(QXcbAtom::WM_PROTOCOLS))
- if (clientMessage->data.data32[0] == atom(QXcbAtom::WM_DELETE_WINDOW))
+ if (clientMessage->format == 32 && clientMessage->type == atom(QXcbAtom::AtomWM_PROTOCOLS))
+ if (clientMessage->data.data32[0] == atom(QXcbAtom::AtomWM_DELETE_WINDOW))
isInputEvent = true;
}
@@ -1066,6 +1092,10 @@ void QXcbConnection::processXcbEvents(QEventLoop::ProcessEventsFlags flags)
m_eventQueue->flushBufferedEvents();
}
+#if QT_CONFIG(xcb_xlib)
+ qt_XFlush(static_cast<Display *>(xlib_display()));
+#endif
+
xcb_flush(xcb_connection());
}
@@ -1111,13 +1141,6 @@ Qt::MouseButtons QXcbConnection::queryMouseButtons() const
return translateMouseButtons(stateMask);
}
-Qt::KeyboardModifiers QXcbConnection::queryKeyboardModifiers() const
-{
- int stateMask = 0;
- QXcbCursor::queryPointer(connection(), nullptr, nullptr, &stateMask);
- return keyboard()->translateModifiers(stateMask);
-}
-
QXcbGlIntegration *QXcbConnection::glIntegration() const
{
if (m_glIntegrationInitialized)
diff --git a/src/plugins/platforms/xcb/qxcbconnection.h b/src/plugins/platforms/xcb/qxcbconnection.h
index 2d55524f6f..527744fe81 100644
--- a/src/plugins/platforms/xcb/qxcbconnection.h
+++ b/src/plugins/platforms/xcb/qxcbconnection.h
@@ -170,9 +170,8 @@ public:
QXcbWindow *mousePressWindow() const { return m_mousePressWindow; }
void setMousePressWindow(QXcbWindow *);
- QByteArray startupId() const { return m_startupId; }
- void setStartupId(const QByteArray &nextId) { m_startupId = nextId; }
- void clearStartupId() { m_startupId.clear(); }
+ QByteArray startupId() const;
+ void setStartupId(const QByteArray &nextId);
void grabServer();
void ungrabServer();
@@ -184,7 +183,6 @@ public:
QXcbSystemTrayTracker *systemTrayTracker() const;
Qt::MouseButtons queryMouseButtons() const;
- Qt::KeyboardModifiers queryKeyboardModifiers() const;
bool isUserInputEvent(xcb_generic_event_t *event) const;
@@ -368,7 +366,7 @@ Q_DECLARE_TYPEINFO(QXcbConnection::TabletData, Q_RELOCATABLE_TYPE);
class QXcbConnectionGrabber
{
public:
- QXcbConnectionGrabber(QXcbConnection *connection);
+ Q_NODISCARD_CTOR QXcbConnectionGrabber(QXcbConnection *connection);
~QXcbConnectionGrabber();
void release();
private:
diff --git a/src/plugins/platforms/xcb/qxcbconnection_basic.cpp b/src/plugins/platforms/xcb/qxcbconnection_basic.cpp
index 5f1b964afa..6416003bea 100644
--- a/src/plugins/platforms/xcb/qxcbconnection_basic.cpp
+++ b/src/plugins/platforms/xcb/qxcbconnection_basic.cpp
@@ -2,6 +2,7 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#include "qxcbconnection_basic.h"
#include "qxcbbackingstore.h" // for createSystemVShmSegment()
+#include "private/qoffsetstringarray_p.h"
#include <xcb/randr.h>
#include <xcb/shm.h>
@@ -27,7 +28,7 @@ QT_BEGIN_NAMESPACE
Q_LOGGING_CATEGORY(lcQpaXcb, "qt.qpa.xcb")
#if QT_CONFIG(xcb_xlib)
-static const char * const xcbConnectionErrors[] = {
+static constexpr auto xcbConnectionErrors = qOffsetStringArray(
"No error", /* Error 0 */
"I/O error", /* XCB_CONN_ERROR */
"Unsupported extension used", /* XCB_CONN_CLOSED_EXT_NOTSUPPORTED */
@@ -36,7 +37,7 @@ static const char * const xcbConnectionErrors[] = {
"Failed to parse display string", /* XCB_CONN_CLOSED_PARSE_ERR */
"No such screen on display", /* XCB_CONN_CLOSED_INVALID_SCREEN */
"Error during FD passing" /* XCB_CONN_CLOSED_FDPASSING_FAILED */
-};
+);
static int nullErrorHandler(Display *dpy, XErrorEvent *err)
{
@@ -60,8 +61,7 @@ static int ioErrorHandler(Display *dpy)
/* Print a message with a textual description of the error */
int code = xcb_connection_has_error(conn);
const char *str = "Unknown error";
- int arrayLength = sizeof(xcbConnectionErrors) / sizeof(xcbConnectionErrors[0]);
- if (code >= 0 && code < arrayLength)
+ if (code >= 0 && code < xcbConnectionErrors.count())
str = xcbConnectionErrors[code];
qWarning("The X11 connection broke: %s (code %d)", str, code);
diff --git a/src/plugins/platforms/xcb/qxcbconnection_screens.cpp b/src/plugins/platforms/xcb/qxcbconnection_screens.cpp
index aa0f2fef65..c31e9b1039 100644
--- a/src/plugins/platforms/xcb/qxcbconnection_screens.cpp
+++ b/src/plugins/platforms/xcb/qxcbconnection_screens.cpp
@@ -155,7 +155,7 @@ void QXcbConnection::updateScreens(const xcb_randr_notify_event_t *event)
}
}
- qCDebug(lcQpaScreen) << "updateScreens: primary output is" << qAsConst(m_screens).first()->name();
+ qCDebug(lcQpaScreen) << "updateScreens: primary output is" << std::as_const(m_screens).first()->name();
}
}
@@ -184,7 +184,7 @@ void QXcbConnection::updateScreen(QXcbScreen *screen, const xcb_randr_output_cha
// If the screen became primary, reshuffle the order in QGuiApplicationPrivate
const int idx = m_screens.indexOf(screen);
if (idx > 0) {
- qAsConst(m_screens).first()->setPrimary(false);
+ std::as_const(m_screens).first()->setPrimary(false);
m_screens.swapItemsAt(0, idx);
}
screen->virtualDesktop()->setPrimaryScreen(screen);
@@ -204,7 +204,7 @@ QXcbScreen *QXcbConnection::createScreen(QXcbVirtualDesktop *virtualDesktop,
if (screen->isPrimary()) {
if (!m_screens.isEmpty())
- qAsConst(m_screens).first()->setPrimary(false);
+ std::as_const(m_screens).first()->setPrimary(false);
m_screens.prepend(screen);
} else {
@@ -219,7 +219,7 @@ QXcbScreen *QXcbConnection::createScreen(QXcbVirtualDesktop *virtualDesktop,
void QXcbConnection::destroyScreen(QXcbScreen *screen)
{
QXcbVirtualDesktop *virtualDesktop = screen->virtualDesktop();
- if (virtualDesktop->screens().count() == 1) {
+ if (virtualDesktop->screens().size() == 1) {
// If there are no other screens on the same virtual desktop,
// then transform the physical screen into a fake screen.
const QString nameWas = screen->name();
@@ -253,7 +253,7 @@ void QXcbConnection::updateScreen_monitor(QXcbScreen *screen, xcb_randr_monitor_
if (screen->isPrimary()) {
const int idx = m_screens.indexOf(screen);
if (idx > 0) {
- qAsConst(m_screens).first()->setPrimary(false);
+ std::as_const(m_screens).first()->setPrimary(false);
m_screens.swapItemsAt(0, idx);
}
screen->virtualDesktop()->setPrimaryScreen(screen);
@@ -268,7 +268,7 @@ QXcbScreen *QXcbConnection::createScreen_monitor(QXcbVirtualDesktop *virtualDesk
if (screen->isPrimary()) {
if (!m_screens.isEmpty())
- qAsConst(m_screens).first()->setPrimary(false);
+ std::as_const(m_screens).first()->setPrimary(false);
m_screens.prepend(screen);
} else {
@@ -326,7 +326,7 @@ void QXcbConnection::initializeScreens(bool initialized)
++xcbScreenNumber;
}
- for (QXcbVirtualDesktop *virtualDesktop : qAsConst(m_virtualDesktops))
+ for (QXcbVirtualDesktop *virtualDesktop : std::as_const(m_virtualDesktops))
virtualDesktop->subscribeToXFixesSelectionNotify();
if (m_virtualDesktops.isEmpty()) {
@@ -334,7 +334,7 @@ void QXcbConnection::initializeScreens(bool initialized)
} else {
// Ensure the primary screen is first on the list
if (primaryScreen) {
- if (qAsConst(m_screens).first() != primaryScreen) {
+ if (std::as_const(m_screens).first() != primaryScreen) {
m_screens.removeOne(primaryScreen);
m_screens.prepend(primaryScreen);
}
@@ -342,14 +342,14 @@ void QXcbConnection::initializeScreens(bool initialized)
// Push the screens to QGuiApplication
if (!initialized) {
- for (QXcbScreen *screen : qAsConst(m_screens)) {
+ for (QXcbScreen *screen : std::as_const(m_screens)) {
qCDebug(lcQpaScreen) << "adding" << screen << "(Primary:" << screen->isPrimary() << ")";
QWindowSystemInterface::handleScreenAdded(screen, screen->isPrimary());
}
}
if (!m_screens.isEmpty())
- qCDebug(lcQpaScreen) << "initializeScreens: primary output is" << qAsConst(m_screens).first()->name();
+ qCDebug(lcQpaScreen) << "initializeScreens: primary output is" << std::as_const(m_screens).first()->name();
}
}
@@ -489,6 +489,10 @@ void QXcbConnection::initializeScreensFromMonitor(xcb_screen_iterator_t *it, int
virtualDesktop = new QXcbVirtualDesktop(this, xcbScreen, xcbScreenNumber);
m_virtualDesktops.append(virtualDesktop);
}
+
+ if (xcbScreenNumber != primaryScreenNumber())
+ return;
+
QList<QPlatformScreen*> old = virtualDesktop->m_screens;
QList<QPlatformScreen *> siblings;
@@ -511,24 +515,22 @@ void QXcbConnection::initializeScreensFromMonitor(xcb_screen_iterator_t *it, int
screen = findScreenForMonitorInfo(old, monitor_info);
if (!screen) {
screen = createScreen_monitor(virtualDesktop, monitor_info, monitors_r->timestamp);
- QHighDpiScaling::updateHighDpiScaling();
} else {
updateScreen_monitor(screen, monitor_info, monitors_r->timestamp);
old.removeAll(screen);
}
}
- m_screens << screen;
+ if (!m_screens.contains(screen))
+ m_screens << screen;
siblings << screen;
// similar logic with QXcbConnection::initializeScreensFromOutput()
- if (primaryScreenNumber() == xcbScreenNumber) {
- if (!(*primaryScreen) || monitor_info->primary) {
- if (*primaryScreen)
- (*primaryScreen)->setPrimary(false);
- *primaryScreen = screen;
- (*primaryScreen)->setPrimary(true);
- siblings.prepend(siblings.takeLast());
- }
+ if (!(*primaryScreen) || monitor_info->primary) {
+ if (*primaryScreen)
+ (*primaryScreen)->setPrimary(false);
+ *primaryScreen = screen;
+ (*primaryScreen)->setPrimary(true);
+ siblings.prepend(siblings.takeLast());
}
xcb_randr_monitor_info_next(&monitor_iter);
@@ -551,10 +553,8 @@ void QXcbConnection::initializeScreensFromMonitor(xcb_screen_iterator_t *it, int
qCDebug(lcQpaScreen) << "create a fake screen: " << screen;
}
- if (primaryScreenNumber() == xcbScreenNumber) {
- *primaryScreen = screen;
- (*primaryScreen)->setPrimary(true);
- }
+ *primaryScreen = screen;
+ (*primaryScreen)->setPrimary(true);
siblings << screen;
m_screens << screen;
diff --git a/src/plugins/platforms/xcb/qxcbconnection_xi2.cpp b/src/plugins/platforms/xcb/qxcbconnection_xi2.cpp
index f528e8b593..4f62a1880b 100644
--- a/src/plugins/platforms/xcb/qxcbconnection_xi2.cpp
+++ b/src/plugins/platforms/xcb/qxcbconnection_xi2.cpp
@@ -15,7 +15,9 @@
#include <xcb/xinput.h>
+#if QT_CONFIG(gestures)
#define QT_XCB_HAS_TOUCHPAD_GESTURES (XCB_INPUT_MINOR_VERSION >= 4)
+#endif
using namespace Qt::StringLiterals;
@@ -226,7 +228,7 @@ void QXcbConnection::xi2SetupSlavePointerDevice(void *info, bool removeExisting,
auto *deviceInfo = reinterpret_cast<xcb_input_xi_device_info_t *>(info);
if (removeExisting) {
#if QT_CONFIG(tabletevent)
- for (int i = 0; i < m_tabletData.count(); ++i) {
+ for (int i = 0; i < m_tabletData.size(); ++i) {
if (m_tabletData.at(i).deviceId == deviceInfo->deviceid) {
m_tabletData.remove(i);
break;
@@ -239,6 +241,7 @@ void QXcbConnection::xi2SetupSlavePointerDevice(void *info, bool removeExisting,
const QByteArray nameRaw = QByteArray(xcb_input_xi_device_info_name(deviceInfo),
xcb_input_xi_device_info_name_length(deviceInfo));
const QString name = QString::fromUtf8(nameRaw);
+ m_xiSlavePointerIds.append(deviceInfo->deviceid);
qCDebug(lcQpaXInputDevices) << "input device " << name << "ID" << deviceInfo->deviceid;
#if QT_CONFIG(tabletevent)
TabletData tabletData;
@@ -270,9 +273,9 @@ void QXcbConnection::xi2SetupSlavePointerDevice(void *info, bool removeExisting,
tabletData.valuatorInfo[valuatorAtom] = info;
}
#endif // QT_CONFIG(tabletevent)
- if (valuatorAtom == QXcbAtom::RelHorizScroll || valuatorAtom == QXcbAtom::RelHorizWheel)
+ if (valuatorAtom == QXcbAtom::AtomRelHorizScroll || valuatorAtom == QXcbAtom::AtomRelHorizWheel)
scrollingDevice()->lastScrollPosition.setX(fixed3232ToReal(vci->value));
- else if (valuatorAtom == QXcbAtom::RelVertScroll || valuatorAtom == QXcbAtom::RelVertWheel)
+ else if (valuatorAtom == QXcbAtom::AtomRelVertScroll || valuatorAtom == QXcbAtom::AtomRelVertWheel)
scrollingDevice()->lastScrollPosition.setY(fixed3232ToReal(vci->value));
break;
}
@@ -300,14 +303,14 @@ void QXcbConnection::xi2SetupSlavePointerDevice(void *info, bool removeExisting,
xcb_atom_t label5 = labels[4];
// Some drivers have no labels on the wheel buttons, some have no label on just one and some have no label on
// button 4 and the wrong one on button 5. So we just check that they are not labelled with unrelated buttons.
- if ((!label4 || qatom(label4) == QXcbAtom::ButtonWheelUp || qatom(label4) == QXcbAtom::ButtonWheelDown) &&
- (!label5 || qatom(label5) == QXcbAtom::ButtonWheelUp || qatom(label5) == QXcbAtom::ButtonWheelDown))
+ if ((!label4 || qatom(label4) == QXcbAtom::AtomButtonWheelUp || qatom(label4) == QXcbAtom::AtomButtonWheelDown) &&
+ (!label5 || qatom(label5) == QXcbAtom::AtomButtonWheelUp || qatom(label5) == QXcbAtom::AtomButtonWheelDown))
scrollingDevice()->legacyOrientations |= Qt::Vertical;
}
if (bci->num_buttons >= 7) {
xcb_atom_t label6 = labels[5];
xcb_atom_t label7 = labels[6];
- if ((!label6 || qatom(label6) == QXcbAtom::ButtonHorizWheelLeft) && (!label7 || qatom(label7) == QXcbAtom::ButtonHorizWheelRight))
+ if ((!label6 || qatom(label6) == QXcbAtom::AtomButtonHorizWheelLeft) && (!label7 || qatom(label7) == QXcbAtom::AtomButtonHorizWheelRight))
scrollingDevice()->legacyOrientations |= Qt::Horizontal;
}
buttonCount = bci->num_buttons;
@@ -331,9 +334,9 @@ void QXcbConnection::xi2SetupSlavePointerDevice(void *info, bool removeExisting,
bool isTablet = false;
#if QT_CONFIG(tabletevent)
// If we have found the valuators which we expect a tablet to have, it might be a tablet.
- if (tabletData.valuatorInfo.contains(QXcbAtom::AbsX) &&
- tabletData.valuatorInfo.contains(QXcbAtom::AbsY) &&
- tabletData.valuatorInfo.contains(QXcbAtom::AbsPressure))
+ if (tabletData.valuatorInfo.contains(QXcbAtom::AtomAbsX) &&
+ tabletData.valuatorInfo.contains(QXcbAtom::AtomAbsY) &&
+ tabletData.valuatorInfo.contains(QXcbAtom::AtomAbsPressure))
isTablet = true;
// But we need to be careful not to take the touch and tablet-button devices as tablets.
@@ -356,7 +359,7 @@ void QXcbConnection::xi2SetupSlavePointerDevice(void *info, bool removeExisting,
// combined device (evdev) rather than separate pen/eraser (wacom driver)
tabletData.pointerType = QPointingDevice::PointerType::Pen;
dbgType = "pen"_L1;
- } else if (nameLower.contains("aiptek") /* && device == QXcbAtom::KEYBOARD */) {
+ } else if (nameLower.contains("aiptek") /* && device == QXcbAtom::AtomKEYBOARD */) {
// some "Genius" tablets
isTablet = true;
tabletData.pointerType = QPointingDevice::PointerType::Pen;
@@ -384,9 +387,9 @@ void QXcbConnection::xi2SetupSlavePointerDevice(void *info, bool removeExisting,
m_tabletData.append(tabletData);
qCDebug(lcQpaXInputDevices) << " it's a tablet with pointer type" << dbgType;
QPointingDevice::Capabilities capsOverride = QInputDevice::Capability::None;
- if (tabletData.valuatorInfo.contains(QXcbAtom::AbsTiltX))
+ if (tabletData.valuatorInfo.contains(QXcbAtom::AtomAbsTiltX))
capsOverride.setFlag(QInputDevice::Capability::XTilt);
- if (tabletData.valuatorInfo.contains(QXcbAtom::AbsTiltY))
+ if (tabletData.valuatorInfo.contains(QXcbAtom::AtomAbsTiltY))
capsOverride.setFlag(QInputDevice::Capability::YTilt);
// TODO can we get USB ID?
Q_ASSERT(deviceInfo->deviceid == tabletData.deviceId);
@@ -442,12 +445,15 @@ void QXcbConnection::xi2SetupSlavePointerDevice(void *info, bool removeExisting,
}
}
+/*!
+ Find all X11 input devices at startup, or react to a device hierarchy event,
+ and create/delete the corresponding QInputDevice instances as necessary.
+ Afterwards, we expect QInputDevice::devices() to contain only the
+ Qt-relevant devices that \c {xinput list} reports. The parent of each master
+ device is this QXcbConnection object; the parent of each slave is its master.
+*/
void QXcbConnection::xi2SetupDevices()
{
-#if QT_CONFIG(tabletevent)
- m_tabletData.clear();
-#endif
- m_touchDevices.clear();
m_xiMasterPointerIds.clear();
auto reply = Q_XCB_REPLY(xcb_input_xi_query_device, xcb_connection(), XCB_INPUT_DEVICE_ALL);
@@ -456,6 +462,18 @@ void QXcbConnection::xi2SetupDevices()
return;
}
+ // Start with all known devices; remove the ones that still exist.
+ // Afterwards, previousDevices will be the list of those that we should delete.
+ QList<const QInputDevice *> previousDevices = QInputDevice::devices();
+ // Return true if the device with the given systemId is new;
+ // otherwise remove it from previousDevices and return false.
+ auto newOrKeep = [&previousDevices](qint64 systemId) {
+ // if nothing is removed from previousDevices, it's a new device
+ return !previousDevices.removeIf([systemId](const QInputDevice *dev) {
+ return dev->systemId() == systemId;
+ });
+ };
+
// XInput doesn't provide a way to identify "seats"; but each device has an attachment to another device.
// So we make up a seatId: master-keyboard-id << 16 | master-pointer-id.
@@ -464,17 +482,21 @@ void QXcbConnection::xi2SetupDevices()
xcb_input_xi_device_info_t *deviceInfo = it.data;
switch (deviceInfo->type) {
case XCB_INPUT_DEVICE_TYPE_MASTER_KEYBOARD: {
- auto dev = new QInputDevice(QString::fromUtf8(xcb_input_xi_device_info_name(deviceInfo)),
- deviceInfo->deviceid, QInputDevice::DeviceType::Keyboard,
- QString::number(deviceInfo->deviceid << 16 | deviceInfo->attachment, 16), this);
- QWindowSystemInterface::registerInputDevice(dev);
+ if (newOrKeep(deviceInfo->deviceid)) {
+ auto dev = new QInputDevice(QString::fromUtf8(xcb_input_xi_device_info_name(deviceInfo)),
+ deviceInfo->deviceid, QInputDevice::DeviceType::Keyboard,
+ QString::number(deviceInfo->deviceid << 16 | deviceInfo->attachment, 16), this);
+ QWindowSystemInterface::registerInputDevice(dev);
+ }
} break;
case XCB_INPUT_DEVICE_TYPE_MASTER_POINTER: {
m_xiMasterPointerIds.append(deviceInfo->deviceid);
- auto dev = new QXcbScrollingDevice(QString::fromUtf8(xcb_input_xi_device_info_name(deviceInfo)), deviceInfo->deviceid,
- QInputDevice::Capability::Position | QInputDevice::Capability::Scroll | QInputDevice::Capability::Hover,
- 32, QString::number(deviceInfo->attachment << 16 | deviceInfo->deviceid, 16), this);
- QWindowSystemInterface::registerInputDevice(dev);
+ if (newOrKeep(deviceInfo->deviceid)) {
+ auto dev = new QXcbScrollingDevice(QString::fromUtf8(xcb_input_xi_device_info_name(deviceInfo)), deviceInfo->deviceid,
+ QInputDevice::Capability::Position | QInputDevice::Capability::Scroll | QInputDevice::Capability::Hover,
+ 32, QString::number(deviceInfo->attachment << 16 | deviceInfo->deviceid, 16), this);
+ QWindowSystemInterface::registerInputDevice(dev);
+ }
continue;
} break;
default:
@@ -491,23 +513,36 @@ void QXcbConnection::xi2SetupDevices()
// already registered
break;
case XCB_INPUT_DEVICE_TYPE_SLAVE_POINTER: {
- m_xiSlavePointerIds.append(deviceInfo->deviceid);
- QInputDevice *master = const_cast<QInputDevice *>(QInputDevicePrivate::fromId(deviceInfo->attachment));
- Q_ASSERT(master);
- xi2SetupSlavePointerDevice(deviceInfo, false, qobject_cast<QPointingDevice *>(master));
+ if (newOrKeep(deviceInfo->deviceid)) {
+ m_xiSlavePointerIds.append(deviceInfo->deviceid);
+ QInputDevice *master = const_cast<QInputDevice *>(QInputDevicePrivate::fromId(deviceInfo->attachment));
+ Q_ASSERT(master);
+ xi2SetupSlavePointerDevice(deviceInfo, false, qobject_cast<QPointingDevice *>(master));
+ }
} break;
case XCB_INPUT_DEVICE_TYPE_SLAVE_KEYBOARD: {
- QInputDevice *master = const_cast<QInputDevice *>(QInputDevicePrivate::fromId(deviceInfo->attachment));
- Q_ASSERT(master);
- QWindowSystemInterface::registerInputDevice(new QInputDevice(
- QString::fromUtf8(xcb_input_xi_device_info_name(deviceInfo)), deviceInfo->deviceid,
- QInputDevice::DeviceType::Keyboard, master->seatName(), master));
+ if (newOrKeep(deviceInfo->deviceid)) {
+ QInputDevice *master = const_cast<QInputDevice *>(QInputDevicePrivate::fromId(deviceInfo->attachment));
+ Q_ASSERT(master);
+ QWindowSystemInterface::registerInputDevice(new QInputDevice(
+ QString::fromUtf8(xcb_input_xi_device_info_name(deviceInfo)), deviceInfo->deviceid,
+ QInputDevice::DeviceType::Keyboard, master->seatName(), master));
+ }
} break;
case XCB_INPUT_DEVICE_TYPE_FLOATING_SLAVE:
break;
}
}
+ // previousDevices is now the list of those that are no longer found
+ qCDebug(lcQpaXInputDevices) << "removed" << previousDevices;
+ for (auto it = previousDevices.constBegin(); it != previousDevices.constEnd(); ++it) {
+ const auto id = (*it)->systemId();
+ m_xiSlavePointerIds.removeAll(id);
+ m_touchDevices.remove(id);
+ }
+ qDeleteAll(previousDevices);
+
if (m_xiMasterPointerIds.size() > 1)
qCDebug(lcQpaXInputDevices) << "multi-pointer X detected";
}
@@ -573,27 +608,27 @@ QXcbConnection::TouchDeviceData *QXcbConnection::populateTouchDevices(void *info
// Some devices (mice) report a resolution of 0; they will be excluded later,
// for now just prevent a division by zero
const int vciResolution = vci->resolution ? vci->resolution : 1;
- if (valuatorAtom == QXcbAtom::AbsMTPositionX)
+ if (valuatorAtom == QXcbAtom::AtomAbsMTPositionX)
caps |= QInputDevice::Capability::Position | QInputDevice::Capability::NormalizedPosition;
- else if (valuatorAtom == QXcbAtom::AbsMTTouchMajor)
+ else if (valuatorAtom == QXcbAtom::AtomAbsMTTouchMajor)
caps |= QInputDevice::Capability::Area;
- else if (valuatorAtom == QXcbAtom::AbsMTOrientation)
+ else if (valuatorAtom == QXcbAtom::AtomAbsMTOrientation)
dev.providesTouchOrientation = true;
- else if (valuatorAtom == QXcbAtom::AbsMTPressure || valuatorAtom == QXcbAtom::AbsPressure)
+ else if (valuatorAtom == QXcbAtom::AtomAbsMTPressure || valuatorAtom == QXcbAtom::AtomAbsPressure)
caps |= QInputDevice::Capability::Pressure;
- else if (valuatorAtom == QXcbAtom::RelX) {
+ else if (valuatorAtom == QXcbAtom::AtomRelX) {
hasRelativeCoords = true;
dev.size.setWidth((fixed3232ToReal(vci->max) - fixed3232ToReal(vci->min)) * 1000.0 / vciResolution);
- } else if (valuatorAtom == QXcbAtom::RelY) {
+ } else if (valuatorAtom == QXcbAtom::AtomRelY) {
hasRelativeCoords = true;
dev.size.setHeight((fixed3232ToReal(vci->max) - fixed3232ToReal(vci->min)) * 1000.0 / vciResolution);
- } else if (valuatorAtom == QXcbAtom::AbsX) {
+ } else if (valuatorAtom == QXcbAtom::AtomAbsX) {
caps |= QInputDevice::Capability::Position;
dev.size.setWidth((fixed3232ToReal(vci->max) - fixed3232ToReal(vci->min)) * 1000.0 / vciResolution);
- } else if (valuatorAtom == QXcbAtom::AbsY) {
+ } else if (valuatorAtom == QXcbAtom::AtomAbsY) {
caps |= QInputDevice::Capability::Position;
dev.size.setHeight((fixed3232ToReal(vci->max) - fixed3232ToReal(vci->min)) * 1000.0 / vciResolution);
- } else if (valuatorAtom == QXcbAtom::RelVertWheel || valuatorAtom == QXcbAtom::RelHorizWheel) {
+ } else if (valuatorAtom == QXcbAtom::AtomRelVertWheel || valuatorAtom == QXcbAtom::AtomRelHorizWheel) {
caps |= QInputDevice::Capability::Scroll;
}
break;
@@ -730,6 +765,8 @@ void QXcbConnection::xi2HandleEvent(xcb_ge_event_t *event)
if (auto device = QPointingDevicePrivate::pointingDeviceById(sourceDeviceId))
xi2HandleScrollEvent(event, device);
+ else
+ qCDebug(lcQpaXInputEvents) << "scroll event from unregistered device" << sourceDeviceId;
if (xiDeviceEvent) {
switch (xiDeviceEvent->event_type) {
@@ -776,7 +813,16 @@ void QXcbConnection::xi2ProcessTouch(void *xiDevEvent, QXcbWindow *platformWindo
{
auto *xiDeviceEvent = reinterpret_cast<xcb_input_touch_begin_event_t *>(xiDevEvent);
TouchDeviceData *dev = touchDeviceForId(xiDeviceEvent->sourceid);
- Q_ASSERT(dev);
+ if (!dev) {
+ qCDebug(lcQpaXInputEvents) << "didn't find the dev for given sourceid - " << xiDeviceEvent->sourceid
+ << ", try to repopulate xi2 devices";
+ xi2SetupDevices();
+ dev = touchDeviceForId(xiDeviceEvent->sourceid);
+ if (!dev) {
+ qCDebug(lcQpaXInputEvents) << "still can't find the dev for it, give up.";
+ return;
+ }
+ }
const bool firstTouch = dev->touchPoints.isEmpty();
if (xiDeviceEvent->event_type == XCB_INPUT_TOUCH_BEGIN) {
QWindowSystemInterface::TouchPoint tp;
@@ -792,7 +838,7 @@ void QXcbConnection::xi2ProcessTouch(void *xiDevEvent, QXcbWindow *platformWindo
qreal nx = -1.0, ny = -1.0;
qreal w = 0.0, h = 0.0;
bool majorAxisIsY = touchPoint.area.height() > touchPoint.area.width();
- for (const TouchDeviceData::ValuatorClassInfo &vci : qAsConst(dev->valuatorInfo)) {
+ for (const TouchDeviceData::ValuatorClassInfo &vci : std::as_const(dev->valuatorInfo)) {
double value;
if (!xi2GetValuatorValueIfSet(xiDeviceEvent, vci.number, &value))
continue;
@@ -804,27 +850,27 @@ void QXcbConnection::xi2ProcessTouch(void *xiDevEvent, QXcbWindow *platformWindo
if (value < vci.min)
value = vci.min;
qreal valuatorNormalized = (value - vci.min) / (vci.max - vci.min);
- if (vci.label == QXcbAtom::RelX) {
+ if (vci.label == QXcbAtom::AtomRelX) {
nx = valuatorNormalized;
- } else if (vci.label == QXcbAtom::RelY) {
+ } else if (vci.label == QXcbAtom::AtomRelY) {
ny = valuatorNormalized;
- } else if (vci.label == QXcbAtom::AbsX) {
+ } else if (vci.label == QXcbAtom::AtomAbsX) {
nx = valuatorNormalized;
- } else if (vci.label == QXcbAtom::AbsY) {
+ } else if (vci.label == QXcbAtom::AtomAbsY) {
ny = valuatorNormalized;
- } else if (vci.label == QXcbAtom::AbsMTPositionX) {
+ } else if (vci.label == QXcbAtom::AtomAbsMTPositionX) {
nx = valuatorNormalized;
- } else if (vci.label == QXcbAtom::AbsMTPositionY) {
+ } else if (vci.label == QXcbAtom::AtomAbsMTPositionY) {
ny = valuatorNormalized;
- } else if (vci.label == QXcbAtom::AbsMTTouchMajor) {
+ } else if (vci.label == QXcbAtom::AtomAbsMTTouchMajor) {
const qreal sw = screen->geometry().width();
const qreal sh = screen->geometry().height();
w = valuatorNormalized * qHypot(sw, sh);
- } else if (vci.label == QXcbAtom::AbsMTTouchMinor) {
+ } else if (vci.label == QXcbAtom::AtomAbsMTTouchMinor) {
const qreal sw = screen->geometry().width();
const qreal sh = screen->geometry().height();
h = valuatorNormalized * qHypot(sw, sh);
- } else if (vci.label == QXcbAtom::AbsMTOrientation) {
+ } else if (vci.label == QXcbAtom::AtomAbsMTOrientation) {
// Find the closest axis.
// 0 corresponds to the Y axis, vci.max to the X axis.
// Flipping over the Y axis and rotating by 180 degrees
@@ -835,7 +881,7 @@ void QXcbConnection::xi2ProcessTouch(void *xiDevEvent, QXcbWindow *platformWindo
value -= 2 * vci.max;
value = qAbs(value);
majorAxisIsY = value < vci.max - value;
- } else if (vci.label == QXcbAtom::AbsMTPressure || vci.label == QXcbAtom::AbsPressure) {
+ } else if (vci.label == QXcbAtom::AtomAbsMTPressure || vci.label == QXcbAtom::AtomAbsPressure) {
touchPoint.pressure = valuatorNormalized;
}
@@ -967,7 +1013,7 @@ void QXcbConnection::abortSystemMoveResize(xcb_window_t window)
qCDebug(lcQpaXInputDevices) << "sending client message NET_WM_MOVERESIZE_CANCEL to window: " << window;
m_startSystemMoveResizeInfo.window = XCB_NONE;
- const xcb_atom_t moveResize = connection()->atom(QXcbAtom::_NET_WM_MOVERESIZE);
+ const xcb_atom_t moveResize = connection()->atom(QXcbAtom::Atom_NET_WM_MOVERESIZE);
xcb_client_message_event_t xev;
xev.response_type = XCB_CLIENT_MESSAGE;
xev.type = moveResize;
@@ -1023,7 +1069,7 @@ bool QXcbConnection::xi2SetMouseGrabEnabled(xcb_window_t w, bool grab)
}
#endif // QT_CONFIG(gestures) && QT_XCB_HAS_TOUCHPAD_GESTURES
- for (int id : qAsConst(m_xiMasterPointerIds)) {
+ for (int id : std::as_const(m_xiMasterPointerIds)) {
xcb_generic_error_t *error = nullptr;
auto cookie = xcb_input_xi_grab_device(xcb_connection(), w, XCB_CURRENT_TIME, XCB_CURSOR_NONE, id,
XCB_INPUT_GRAB_MODE_22_ASYNC, XCB_INPUT_GRAB_MODE_22_ASYNC,
@@ -1041,7 +1087,7 @@ bool QXcbConnection::xi2SetMouseGrabEnabled(xcb_window_t w, bool grab)
free(reply);
}
} else { // ungrab
- for (int id : qAsConst(m_xiMasterPointerIds)) {
+ for (int id : std::as_const(m_xiMasterPointerIds)) {
auto cookie = xcb_input_xi_ungrab_device_checked(xcb_connection(), XCB_CURRENT_TIME, id);
xcb_generic_error_t *error = xcb_request_check(xcb_connection(), cookie);
if (error) {
@@ -1065,11 +1111,15 @@ bool QXcbConnection::xi2SetMouseGrabEnabled(xcb_window_t w, bool grab)
void QXcbConnection::xi2HandleHierarchyEvent(void *event)
{
auto *xiEvent = reinterpret_cast<xcb_input_hierarchy_event_t *>(event);
- // We only care about hotplugged devices
- if (!(xiEvent->flags & (XCB_INPUT_HIERARCHY_MASK_SLAVE_REMOVED | XCB_INPUT_HIERARCHY_MASK_SLAVE_ADDED)))
- return;
-
- xi2SetupDevices();
+ // We care about hotplugged devices (slaves) and master devices.
+ // We don't report anything for DEVICE_ENABLED or DEVICE_DISABLED
+ // (but often that goes with adding or removal anyway).
+ // We don't react to SLAVE_ATTACHED or SLAVE_DETACHED either.
+ if (xiEvent->flags & (XCB_INPUT_HIERARCHY_MASK_MASTER_ADDED |
+ XCB_INPUT_HIERARCHY_MASK_MASTER_REMOVED |
+ XCB_INPUT_HIERARCHY_MASK_SLAVE_REMOVED |
+ XCB_INPUT_HIERARCHY_MASK_SLAVE_ADDED))
+ xi2SetupDevices();
}
#if QT_XCB_HAS_TOUCHPAD_GESTURES
@@ -1234,6 +1284,9 @@ void QXcbConnection::xi2HandleDeviceChangedEvent(void *event)
auto *xiEvent = reinterpret_cast<xcb_input_device_changed_event_t *>(event);
switch (xiEvent->reason) {
case XCB_INPUT_CHANGE_REASON_DEVICE_CHANGE: {
+ // Don't call xi2SetupSlavePointerDevice() again for an already-known device, and never for a master.
+ if (m_xiMasterPointerIds.contains(xiEvent->deviceid) || m_xiSlavePointerIds.contains(xiEvent->deviceid))
+ return;
auto reply = Q_XCB_REPLY(xcb_input_xi_query_device, xcb_connection(), xiEvent->sourceid);
if (!reply || reply->num_infos <= 0)
return;
@@ -1275,9 +1328,9 @@ void QXcbConnection::xi2UpdateScrollingDevice(QInputDevice *dev)
if (classInfo->type == XCB_INPUT_DEVICE_CLASS_TYPE_VALUATOR) {
auto *vci = reinterpret_cast<xcb_input_valuator_class_t *>(classInfo);
const int valuatorAtom = qatom(vci->label);
- if (valuatorAtom == QXcbAtom::RelHorizScroll || valuatorAtom == QXcbAtom::RelHorizWheel)
+ if (valuatorAtom == QXcbAtom::AtomRelHorizScroll || valuatorAtom == QXcbAtom::AtomRelHorizWheel)
scrollingDevice->lastScrollPosition.setX(fixed3232ToReal(vci->value));
- else if (valuatorAtom == QXcbAtom::RelVertScroll || valuatorAtom == QXcbAtom::RelVertWheel)
+ else if (valuatorAtom == QXcbAtom::AtomRelVertScroll || valuatorAtom == QXcbAtom::AtomRelVertWheel)
scrollingDevice->lastScrollPosition.setY(fixed3232ToReal(vci->value));
}
}
@@ -1464,7 +1517,7 @@ bool QXcbConnection::xi2HandleTabletEvent(const void *event, TabletData *tabletD
// The evdev driver doesn't do it this way.
const auto *ev = reinterpret_cast<const xcb_input_property_event_t *>(event);
if (ev->what == XCB_INPUT_PROPERTY_FLAG_MODIFIED) {
- if (ev->property == atom(QXcbAtom::WacomSerialIDs)) {
+ if (ev->property == atom(QXcbAtom::AtomWacomSerialIDs)) {
enum WacomSerialIndex {
_WACSER_USB_ID = 0,
_WACSER_LAST_TOOL_SERIAL,
@@ -1477,7 +1530,7 @@ bool QXcbConnection::xi2HandleTabletEvent(const void *event, TabletData *tabletD
auto reply = Q_XCB_REPLY(xcb_input_xi_get_property, xcb_connection(), tabletData->deviceId, 0,
ev->property, XCB_GET_PROPERTY_TYPE_ANY, 0, 100);
if (reply) {
- if (reply->type == atom(QXcbAtom::INTEGER) && reply->format == 32 && reply->num_items == _WACSER_COUNT) {
+ if (reply->type == atom(QXcbAtom::AtomINTEGER) && reply->format == 32 && reply->num_items == _WACSER_COUNT) {
quint32 *ptr = reinterpret_cast<quint32 *>(xcb_input_xi_get_property_items(reply.get()));
quint32 tool = ptr[_WACSER_TOOL_ID];
// Workaround for http://sourceforge.net/p/linuxwacom/bugs/246/
@@ -1485,6 +1538,7 @@ bool QXcbConnection::xi2HandleTabletEvent(const void *event, TabletData *tabletD
if (!tool && ptr[_WACSER_TOOL_SERIAL])
tool = ptr[_WACSER_TOOL_SERIAL];
+ QWindow *win = nullptr; // TODO QTBUG-111400 get the position somehow, then the window
// The property change event informs us which tool is in proximity or which one left proximity.
if (tool) {
const QPointingDevice *dev = tabletToolInstance(nullptr, tabletData->name,
@@ -1493,22 +1547,19 @@ bool QXcbConnection::xi2HandleTabletEvent(const void *event, TabletData *tabletD
tabletData->inProximity = true;
tabletData->tool = dev->type();
tabletData->serialId = qint64(ptr[_WACSER_TOOL_SERIAL]);
- QWindowSystemInterface::handleTabletEnterProximityEvent(ev->time,
- int(tabletData->tool), int(tabletData->pointerType), tabletData->serialId);
+ QWindowSystemInterface::handleTabletEnterLeaveProximityEvent(win, ev->time, dev, true); // enter
} else {
tool = ptr[_WACSER_LAST_TOOL_ID];
// Workaround for http://sourceforge.net/p/linuxwacom/bugs/246/
// e.g. on Thinkpad Helix, tool ID will be 0 and serial will be 1
if (!tool)
tool = ptr[_WACSER_LAST_TOOL_SERIAL];
- const QInputDevice *dev = QInputDevicePrivate::fromId(tabletData->deviceId);
+ auto *dev = qobject_cast<const QPointingDevice *>(QInputDevicePrivate::fromId(tabletData->deviceId));
Q_ASSERT(dev);
tabletData->tool = dev->type();
tabletData->inProximity = false;
tabletData->serialId = qint64(ptr[_WACSER_LAST_TOOL_SERIAL]);
- // TODO why doesn't it just take QPointingDevice*
- QWindowSystemInterface::handleTabletLeaveProximityEvent(ev->time,
- int(tabletData->tool), int(tabletData->pointerType), tabletData->serialId);
+ QWindowSystemInterface::handleTabletEnterLeaveProximityEvent(win, ev->time, dev, false); // leave
}
// TODO maybe have a hash of tabletData->deviceId to device data so we can
// look up the tablet name here, and distinguish multiple tablets
@@ -1570,30 +1621,30 @@ void QXcbConnection::xi2ReportTabletEvent(const void *event, TabletData *tabletD
xi2GetValuatorValueIfSet(event, classInfo.number, &classInfo.curVal);
double normalizedValue = (classInfo.curVal - classInfo.minVal) / (classInfo.maxVal - classInfo.minVal);
switch (valuator) {
- case QXcbAtom::AbsX:
+ case QXcbAtom::AtomAbsX:
if (Q_LIKELY(useValuators)) {
const qreal value = scaleOneValuator(normalizedValue, physicalScreenArea.x(), physicalScreenArea.width());
global.setX(value);
local.setX(xcbWindow->mapFromGlobalF(global).x());
}
break;
- case QXcbAtom::AbsY:
+ case QXcbAtom::AtomAbsY:
if (Q_LIKELY(useValuators)) {
qreal value = scaleOneValuator(normalizedValue, physicalScreenArea.y(), physicalScreenArea.height());
global.setY(value);
local.setY(xcbWindow->mapFromGlobalF(global).y());
}
break;
- case QXcbAtom::AbsPressure:
+ case QXcbAtom::AtomAbsPressure:
pressure = normalizedValue;
break;
- case QXcbAtom::AbsTiltX:
+ case QXcbAtom::AtomAbsTiltX:
xTilt = classInfo.curVal;
break;
- case QXcbAtom::AbsTiltY:
+ case QXcbAtom::AtomAbsTiltY:
yTilt = classInfo.curVal;
break;
- case QXcbAtom::AbsWheel:
+ case QXcbAtom::AtomAbsWheel:
switch (tabletData->tool) {
case QInputDevice::DeviceType::Airbrush:
tangentialPressure = normalizedValue * 2.0 - 1.0; // Convert 0..1 range to -1..+1 range
@@ -1627,7 +1678,7 @@ void QXcbConnection::xi2ReportTabletEvent(const void *event, TabletData *tabletD
QXcbConnection::TabletData *QXcbConnection::tabletDataForDevice(int id)
{
- for (int i = 0; i < m_tabletData.count(); ++i) {
+ for (int i = 0; i < m_tabletData.size(); ++i) {
if (m_tabletData.at(i).deviceId == id)
return &m_tabletData[i];
}
diff --git a/src/plugins/platforms/xcb/qxcbcursor.cpp b/src/plugins/platforms/xcb/qxcbcursor.cpp
index 2cc39f1e85..dc9ed46956 100644
--- a/src/plugins/platforms/xcb/qxcbcursor.cpp
+++ b/src/plugins/platforms/xcb/qxcbcursor.cpp
@@ -1,4 +1,4 @@
-// Copyright (C) 2016 The Qt Company Ltd.
+// Copyright (C) 2022 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#include "qxcbcursor.h"
@@ -7,13 +7,17 @@
#include "qxcbimage.h"
#include "qxcbxsettings.h"
-#if QT_CONFIG(library)
-#include <QtCore/QLibrary>
-#endif
#include <QtGui/QWindow>
#include <QtGui/QBitmap>
#include <QtGui/private/qguiapplication_p.h>
+#include <qpa/qplatformtheme.h>
+
+#if QT_CONFIG(xcb_xlib)
#include <X11/cursorfont.h>
+#else
+#include "qxcbcursorfont.h"
+#endif
+
#include <xcb/xfixes.h>
#include <xcb/xcb_image.h>
@@ -21,24 +25,6 @@ QT_BEGIN_NAMESPACE
using namespace Qt::StringLiterals;
-typedef int (*PtrXcursorLibraryLoadCursor)(void *, const char *);
-typedef char *(*PtrXcursorLibraryGetTheme)(void *);
-typedef int (*PtrXcursorLibrarySetTheme)(void *, const char *);
-typedef int (*PtrXcursorLibraryGetDefaultSize)(void *);
-
-#if QT_CONFIG(xcb_xlib) && QT_CONFIG(library)
-#include <X11/Xlib.h>
-enum {
- XCursorShape = CursorShape
-};
-#undef CursorShape
-
-static PtrXcursorLibraryLoadCursor ptrXcursorLibraryLoadCursor = nullptr;
-static PtrXcursorLibraryGetTheme ptrXcursorLibraryGetTheme = nullptr;
-static PtrXcursorLibrarySetTheme ptrXcursorLibrarySetTheme = nullptr;
-static PtrXcursorLibraryGetDefaultSize ptrXcursorLibraryGetDefaultSize = nullptr;
-#endif
-
static xcb_font_t cursorFont = 0;
static int cursorCount = 0;
@@ -266,50 +252,28 @@ QXcbCursorCacheKey::QXcbCursorCacheKey(const QCursor &c)
#endif // !QT_NO_CURSOR
QXcbCursor::QXcbCursor(QXcbConnection *conn, QXcbScreen *screen)
- : QXcbObject(conn), m_screen(screen), m_gtkCursorThemeInitialized(false)
+ : QXcbObject(conn), m_screen(screen), m_cursorContext(nullptr), m_callbackForPropertyRegistered(false)
{
#if QT_CONFIG(cursor)
// see NUM_BITMAPS in libXcursor/src/xcursorint.h
m_bitmapCache.setMaxCost(8);
#endif
+ updateContext();
+
if (cursorCount++)
return;
cursorFont = xcb_generate_id(xcb_connection());
const char *cursorStr = "cursor";
xcb_open_font(xcb_connection(), cursorFont, strlen(cursorStr), cursorStr);
-
-#if QT_CONFIG(xcb_xlib) && QT_CONFIG(library)
- static bool function_ptrs_not_initialized = true;
- if (function_ptrs_not_initialized) {
- QLibrary xcursorLib("Xcursor"_L1, 1);
- bool xcursorFound = xcursorLib.load();
- if (!xcursorFound) { // try without the version number
- xcursorLib.setFileName("Xcursor"_L1);
- xcursorFound = xcursorLib.load();
- }
- if (xcursorFound) {
- ptrXcursorLibraryLoadCursor =
- (PtrXcursorLibraryLoadCursor) xcursorLib.resolve("XcursorLibraryLoadCursor");
- ptrXcursorLibraryGetTheme =
- (PtrXcursorLibraryGetTheme) xcursorLib.resolve("XcursorGetTheme");
- ptrXcursorLibrarySetTheme =
- (PtrXcursorLibrarySetTheme) xcursorLib.resolve("XcursorSetTheme");
- ptrXcursorLibraryGetDefaultSize =
- (PtrXcursorLibraryGetDefaultSize) xcursorLib.resolve("XcursorGetDefaultSize");
- }
- function_ptrs_not_initialized = false;
- }
-
-#endif
}
QXcbCursor::~QXcbCursor()
{
xcb_connection_t *conn = xcb_connection();
- if (m_gtkCursorThemeInitialized) {
+ if (m_callbackForPropertyRegistered) {
m_screen->xSettings()->removeCallbackForHandle(this);
}
@@ -317,9 +281,33 @@ QXcbCursor::~QXcbCursor()
xcb_close_font(conn, cursorFont);
#ifndef QT_NO_CURSOR
- for (xcb_cursor_t cursor : qAsConst(m_cursorHash))
+ for (xcb_cursor_t cursor : std::as_const(m_cursorHash))
xcb_free_cursor(conn, cursor);
#endif
+
+ if (m_cursorContext)
+ xcb_cursor_context_free(m_cursorContext);
+}
+
+QSize QXcbCursor::size() const
+{
+ if (const QPlatformTheme *theme = QGuiApplicationPrivate::platformTheme())
+ return theme->themeHint(QPlatformTheme::MouseCursorSize).toSize();
+ return QSize(24, 24);
+}
+
+void QXcbCursor::updateContext()
+{
+ if (m_cursorContext)
+ xcb_cursor_context_free(m_cursorContext);
+
+ m_cursorContext = nullptr;
+
+ xcb_connection_t *conn = xcb_connection();
+ if (xcb_cursor_context_new(conn, m_screen->screen(), &m_cursorContext) < 0) {
+ qWarning() << "xcb: Could not initialize xcb-cursor";
+ m_cursorContext = nullptr;
+ }
}
#ifndef QT_NO_CURSOR
@@ -482,76 +470,39 @@ xcb_cursor_t QXcbCursor::createNonStandardCursor(int cshape)
return cursor;
}
-#if QT_CONFIG(xcb_xlib) && QT_CONFIG(library)
-bool updateCursorTheme(void *dpy, const QByteArray &theme) {
- if (!ptrXcursorLibraryGetTheme
- || !ptrXcursorLibrarySetTheme)
- return false;
- QByteArray oldTheme = ptrXcursorLibraryGetTheme(dpy);
- if (oldTheme == theme)
- return false;
-
- int setTheme = ptrXcursorLibrarySetTheme(dpy,theme.constData());
- return setTheme;
-}
-
- void QXcbCursor::cursorThemePropertyChanged(QXcbVirtualDesktop *screen, const QByteArray &name, const QVariant &property, void *handle)
+void QXcbCursor::cursorThemePropertyChanged(QXcbVirtualDesktop *screen, const QByteArray &name, const QVariant &property, void *handle)
{
Q_UNUSED(screen);
Q_UNUSED(name);
+ Q_UNUSED(property);
QXcbCursor *self = static_cast<QXcbCursor *>(handle);
self->m_cursorHash.clear();
-
- updateCursorTheme(self->connection()->xlib_display(),property.toByteArray());
+ self->updateContext();
}
-static xcb_cursor_t loadCursor(void *dpy, int cshape)
-{
- xcb_cursor_t cursor = XCB_NONE;
- if (!ptrXcursorLibraryLoadCursor || !dpy)
- return cursor;
-
- for (const char *cursorName: cursorNames[cshape]) {
- cursor = ptrXcursorLibraryLoadCursor(dpy, cursorName);
- if (cursor != XCB_NONE)
- break;
- }
-
- return cursor;
-}
-#endif // QT_CONFIG(xcb_xlib) / QT_CONFIG(library)
-
xcb_cursor_t QXcbCursor::createFontCursor(int cshape)
{
+ if (!m_cursorContext)
+ return XCB_NONE;
+
xcb_connection_t *conn = xcb_connection();
int cursorId = cursorIdForShape(cshape);
xcb_cursor_t cursor = XCB_NONE;
-#if QT_CONFIG(xcb_xlib) && QT_CONFIG(library)
- if (m_screen->xSettings()->initialized())
- m_screen->xSettings()->registerCallbackForProperty("Gtk/CursorThemeName",cursorThemePropertyChanged,this);
+ if (!m_callbackForPropertyRegistered && m_screen->xSettings()->initialized()) {
+ m_screen->xSettings()->registerCallbackForProperty("Gtk/CursorThemeName", cursorThemePropertyChanged, this);
+
+ m_callbackForPropertyRegistered = true;
+ }
- // Try Xcursor first
+ // Try xcb-cursor first
if (cshape >= 0 && cshape <= Qt::LastCursor) {
- void *dpy = connection()->xlib_display();
- cursor = loadCursor(dpy, cshape);
- if (!cursor && !m_gtkCursorThemeInitialized && m_screen->xSettings()->initialized()) {
- QByteArray gtkCursorTheme = m_screen->xSettings()->setting("Gtk/CursorThemeName").toByteArray();
- if (updateCursorTheme(dpy,gtkCursorTheme)) {
- cursor = loadCursor(dpy, cshape);
- }
- m_gtkCursorThemeInitialized = true;
+ for (const char *cursorName : cursorNames[cshape]) {
+ cursor = xcb_cursor_load_cursor(m_cursorContext, cursorName);
+ if (cursor != XCB_NONE)
+ return cursor;
}
}
- if (cursor)
- return cursor;
- if (!cursor && cursorId) {
- cursor = XCreateFontCursor(static_cast<Display *>(connection()->xlib_display()), cursorId);
- if (cursor)
- return cursor;
- }
-
-#endif
// Non-standard X11 cursors are created from bitmaps
cursor = createNonStandardCursor(cshape);
diff --git a/src/plugins/platforms/xcb/qxcbcursor.h b/src/plugins/platforms/xcb/qxcbcursor.h
index 83438321ef..bf26861e8f 100644
--- a/src/plugins/platforms/xcb/qxcbcursor.h
+++ b/src/plugins/platforms/xcb/qxcbcursor.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2016 The Qt Company Ltd.
+// Copyright (C) 2022 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#ifndef QXCBCURSOR_H
@@ -6,6 +6,7 @@
#include <qpa/qplatformcursor.h>
#include "qxcbscreen.h"
+#include <xcb/xcb_cursor.h>
#include <QtCore/QCache>
@@ -47,6 +48,10 @@ public:
QPoint pos() const override;
void setPos(const QPoint &pos) override;
+ QSize size() const override;
+
+ void updateContext();
+
static void queryPointer(QXcbConnection *c, QXcbVirtualDesktop **virtualDesktop, QPoint *pos, int *keybMask = nullptr);
#ifndef QT_NO_CURSOR
@@ -75,17 +80,16 @@ private:
#endif
QXcbScreen *m_screen;
+ xcb_cursor_context_t *m_cursorContext;
#ifndef QT_NO_CURSOR
CursorHash m_cursorHash;
BitmapCursorCache m_bitmapCache;
#endif
-#if QT_CONFIG(xcb_xlib) && QT_CONFIG(library)
static void cursorThemePropertyChanged(QXcbVirtualDesktop *screen,
const QByteArray &name,
const QVariant &property,
void *handle);
-#endif
- bool m_gtkCursorThemeInitialized;
+ bool m_callbackForPropertyRegistered;
};
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/xcb/qxcbcursorfont.h b/src/plugins/platforms/xcb/qxcbcursorfont.h
new file mode 100644
index 0000000000..fe74e27691
--- /dev/null
+++ b/src/plugins/platforms/xcb/qxcbcursorfont.h
@@ -0,0 +1,88 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+// copied from <X11/cursorfont.h>
+
+#ifndef QXCBCURSORFONT_H
+#define QXCBCURSORFONT_H
+
+#define XC_num_glyphs 154
+#define XC_X_cursor 0
+#define XC_arrow 2
+#define XC_based_arrow_down 4
+#define XC_based_arrow_up 6
+#define XC_boat 8
+#define XC_bogosity 10
+#define XC_bottom_left_corner 12
+#define XC_bottom_right_corner 14
+#define XC_bottom_side 16
+#define XC_bottom_tee 18
+#define XC_box_spiral 20
+#define XC_center_ptr 22
+#define XC_circle 24
+#define XC_clock 26
+#define XC_coffee_mug 28
+#define XC_cross 30
+#define XC_cross_reverse 32
+#define XC_crosshair 34
+#define XC_diamond_cross 36
+#define XC_dot 38
+#define XC_dotbox 40
+#define XC_double_arrow 42
+#define XC_draft_large 44
+#define XC_draft_small 46
+#define XC_draped_box 48
+#define XC_exchange 50
+#define XC_fleur 52
+#define XC_gobbler 54
+#define XC_gumby 56
+#define XC_hand1 58
+#define XC_hand2 60
+#define XC_heart 62
+#define XC_icon 64
+#define XC_iron_cross 66
+#define XC_left_ptr 68
+#define XC_left_side 70
+#define XC_left_tee 72
+#define XC_leftbutton 74
+#define XC_ll_angle 76
+#define XC_lr_angle 78
+#define XC_man 80
+#define XC_middlebutton 82
+#define XC_mouse 84
+#define XC_pencil 86
+#define XC_pirate 88
+#define XC_plus 90
+#define XC_question_arrow 92
+#define XC_right_ptr 94
+#define XC_right_side 96
+#define XC_right_tee 98
+#define XC_rightbutton 100
+#define XC_rtl_logo 102
+#define XC_sailboat 104
+#define XC_sb_down_arrow 106
+#define XC_sb_h_double_arrow 108
+#define XC_sb_left_arrow 110
+#define XC_sb_right_arrow 112
+#define XC_sb_up_arrow 114
+#define XC_sb_v_double_arrow 116
+#define XC_shuttle 118
+#define XC_sizing 120
+#define XC_spider 122
+#define XC_spraycan 124
+#define XC_star 126
+#define XC_target 128
+#define XC_tcross 130
+#define XC_top_left_arrow 132
+#define XC_top_left_corner 134
+#define XC_top_right_corner 136
+#define XC_top_side 138
+#define XC_top_tee 140
+#define XC_trek 142
+#define XC_ul_angle 144
+#define XC_umbrella 146
+#define XC_ur_angle 148
+#define XC_watch 150
+#define XC_xterm 152
+
+#endif /* QXCBCURSORFONT_H */
diff --git a/src/plugins/platforms/xcb/qxcbdrag.cpp b/src/plugins/platforms/xcb/qxcbdrag.cpp
index f5cb50d9c6..6e5dd770b9 100644
--- a/src/plugins/platforms/xcb/qxcbdrag.cpp
+++ b/src/plugins/platforms/xcb/qxcbdrag.cpp
@@ -5,6 +5,7 @@
#include <xcb/xcb.h>
#include "qxcbconnection.h"
#include "qxcbclipboard.h"
+#include "qxcbkeyboard.h"
#include "qxcbmime.h"
#include "qxcbwindow.h"
#include "qxcbscreen.h"
@@ -27,6 +28,8 @@
QT_BEGIN_NAMESPACE
+using namespace Qt::Literals::StringLiterals;
+
const int xdnd_version = 5;
static inline xcb_window_t xcb_window(QPlatformWindow *w)
@@ -44,7 +47,7 @@ static xcb_window_t xdndProxy(QXcbConnection *c, xcb_window_t w)
xcb_window_t proxy = XCB_NONE;
auto reply = Q_XCB_REPLY(xcb_get_property, c->xcb_connection(),
- false, w, c->atom(QXcbAtom::XdndProxy), XCB_ATOM_WINDOW, 0, 1);
+ false, w, c->atom(QXcbAtom::AtomXdndProxy), XCB_ATOM_WINDOW, 0, 1);
if (reply && reply->type == XCB_ATOM_WINDOW)
proxy = *((xcb_window_t *)xcb_get_property_value(reply.get()));
@@ -54,7 +57,7 @@ static xcb_window_t xdndProxy(QXcbConnection *c, xcb_window_t w)
// exists and is real?
reply = Q_XCB_REPLY(xcb_get_property, c->xcb_connection(),
- false, proxy, c->atom(QXcbAtom::XdndProxy), XCB_ATOM_WINDOW, 0, 1);
+ false, proxy, c->atom(QXcbAtom::AtomXdndProxy), XCB_ATOM_WINDOW, 0, 1);
if (reply && reply->type == XCB_ATOM_WINDOW) {
xcb_window_t p = *((xcb_window_t *)xcb_get_property_value(reply.get()));
@@ -140,7 +143,7 @@ void QXcbDrag::startDrag()
qCDebug(lcQpaXDnd) << "starting drag where source:" << connection()->qtSelectionOwner();
xcb_set_selection_owner(xcb_connection(), connection()->qtSelectionOwner(),
- atom(QXcbAtom::XdndSelection), connection()->time());
+ atom(QXcbAtom::AtomXdndSelection), connection()->time());
QStringList fmts = QXcbMime::formatsHelper(drag()->mimeData());
for (int i = 0; i < fmts.size(); ++i) {
@@ -153,7 +156,7 @@ void QXcbDrag::startDrag()
if (drag_types.size() > 3)
xcb_change_property(xcb_connection(), XCB_PROP_MODE_REPLACE, connection()->qtSelectionOwner(),
- atom(QXcbAtom::XdndTypelist),
+ atom(QXcbAtom::AtomXdndTypelist),
XCB_ATOM_ATOM, 32, drag_types.size(), (const void *)drag_types.constData());
setUseCompositing(current_virtual_desktop->compositingActive());
@@ -187,7 +190,7 @@ Qt::DropAction QXcbDrag::defaultAction(Qt::DropActions possibleActions, Qt::Keyb
void QXcbDrag::handlePropertyNotifyEvent(const xcb_property_notify_event_t *event)
{
- if (event->window != xdnd_dragsource || event->atom != atom(QXcbAtom::XdndActionList))
+ if (event->window != xdnd_dragsource || event->atom != atom(QXcbAtom::AtomXdndActionList))
return;
readActionList();
@@ -233,7 +236,7 @@ xcb_window_t QXcbDrag::findRealWindow(const QPoint & pos, xcb_window_t w, int md
bool windowContainsMouse = !ignoreNonXdndAwareWindows;
{
auto reply = Q_XCB_REPLY(xcb_get_property, xcb_connection(),
- false, w, connection()->atom(QXcbAtom::XdndAware),
+ false, w, connection()->atom(QXcbAtom::AtomXdndAware),
XCB_GET_PROPERTY_TYPE_ANY, 0, 0);
bool isAware = reply && reply->type != XCB_NONE;
if (isAware) {
@@ -306,7 +309,7 @@ bool QXcbDrag::findXdndAwareTarget(const QPoint &globalPos, xcb_window_t *target
xcb_window_t child = translate->child;
auto reply = Q_XCB_REPLY(xcb_get_property, xcb_connection(), false, target,
- atom(QXcbAtom::XdndAware), XCB_GET_PROPERTY_TYPE_ANY, 0, 0);
+ atom(QXcbAtom::AtomXdndAware), XCB_GET_PROPERTY_TYPE_ANY, 0, 0);
bool aware = reply && reply->type != XCB_NONE;
if (aware) {
qCDebug(lcQpaXDnd) << "found XdndAware on" << target;
@@ -379,7 +382,7 @@ void QXcbDrag::move(const QPoint &globalPos, Qt::MouseButtons b, Qt::KeyboardMod
if (proxy_target) {
auto reply = Q_XCB_REPLY(xcb_get_property, xcb_connection(),
false, proxy_target,
- atom(QXcbAtom::XdndAware), XCB_GET_PROPERTY_TYPE_ANY, 0, 1);
+ atom(QXcbAtom::AtomXdndAware), XCB_GET_PROPERTY_TYPE_ANY, 0, 1);
if (!reply || reply->type == XCB_NONE) {
target = 0;
} else {
@@ -404,7 +407,7 @@ void QXcbDrag::move(const QPoint &globalPos, Qt::MouseButtons b, Qt::KeyboardMod
enter.sequence = 0;
enter.window = target;
enter.format = 32;
- enter.type = atom(QXcbAtom::XdndEnter);
+ enter.type = atom(QXcbAtom::AtomXdndEnter);
enter.data.data32[0] = connection()->qtSelectionOwner();
enter.data.data32[1] = flags;
enter.data.data32[2] = drag_types.size() > 0 ? drag_types.at(0) : 0;
@@ -435,7 +438,7 @@ void QXcbDrag::move(const QPoint &globalPos, Qt::MouseButtons b, Qt::KeyboardMod
move.sequence = 0;
move.window = target;
move.format = 32;
- move.type = atom(QXcbAtom::XdndPosition);
+ move.type = atom(QXcbAtom::AtomXdndPosition);
move.data.data32[0] = connection()->qtSelectionOwner();
move.data.data32[1] = 0; // flags
move.data.data32[2] = (globalPos.x() << 16) + globalPos.y();
@@ -459,7 +462,7 @@ void QXcbDrag::move(const QPoint &globalPos, Qt::MouseButtons b, Qt::KeyboardMod
static const bool isUnity = qgetenv("XDG_CURRENT_DESKTOP").toLower() == "unity";
if (isUnity && xdndCollectionWindow == XCB_NONE) {
QString name = QXcbWindow::windowTitle(connection(), target);
- if (name == QStringLiteral("XdndCollectionWindowImp"))
+ if (name == "XdndCollectionWindowImp"_L1)
xdndCollectionWindow = target;
}
if (target == xdndCollectionWindow) {
@@ -481,7 +484,7 @@ void QXcbDrag::drop(const QPoint &globalPos, Qt::MouseButtons b, Qt::KeyboardMod
drop.sequence = 0;
drop.window = current_target;
drop.format = 32;
- drop.type = atom(QXcbAtom::XdndDrop);
+ drop.type = atom(QXcbAtom::AtomXdndDrop);
drop.data.data32[0] = connection()->qtSelectionOwner();
drop.data.data32[1] = 0; // flags
drop.data.data32[2] = connection()->time();
@@ -521,11 +524,11 @@ void QXcbDrag::drop(const QPoint &globalPos, Qt::MouseButtons b, Qt::KeyboardMod
Qt::DropAction QXcbDrag::toDropAction(xcb_atom_t a) const
{
- if (a == atom(QXcbAtom::XdndActionCopy) || a == 0)
+ if (a == atom(QXcbAtom::AtomXdndActionCopy) || a == 0)
return Qt::CopyAction;
- if (a == atom(QXcbAtom::XdndActionLink))
+ if (a == atom(QXcbAtom::AtomXdndActionLink))
return Qt::LinkAction;
- if (a == atom(QXcbAtom::XdndActionMove))
+ if (a == atom(QXcbAtom::AtomXdndActionMove))
return Qt::MoveAction;
return Qt::CopyAction;
}
@@ -534,7 +537,7 @@ Qt::DropActions QXcbDrag::toDropActions(const QList<xcb_atom_t> &atoms) const
{
Qt::DropActions actions;
for (const auto actionAtom : atoms) {
- if (actionAtom != atom(QXcbAtom::XdndActionAsk))
+ if (actionAtom != atom(QXcbAtom::AtomXdndActionAsk))
actions |= toDropAction(actionAtom);
}
return actions;
@@ -544,16 +547,16 @@ xcb_atom_t QXcbDrag::toXdndAction(Qt::DropAction a) const
{
switch (a) {
case Qt::CopyAction:
- return atom(QXcbAtom::XdndActionCopy);
+ return atom(QXcbAtom::AtomXdndActionCopy);
case Qt::LinkAction:
- return atom(QXcbAtom::XdndActionLink);
+ return atom(QXcbAtom::AtomXdndActionLink);
case Qt::MoveAction:
case Qt::TargetMoveAction:
- return atom(QXcbAtom::XdndActionMove);
+ return atom(QXcbAtom::AtomXdndActionMove);
case Qt::IgnoreAction:
return XCB_NONE;
default:
- return atom(QXcbAtom::XdndActionCopy);
+ return atom(QXcbAtom::AtomXdndActionCopy);
}
}
@@ -561,7 +564,7 @@ void QXcbDrag::readActionList()
{
drop_actions.clear();
auto reply = Q_XCB_REPLY(xcb_get_property, xcb_connection(), false, xdnd_dragsource,
- atom(QXcbAtom::XdndActionList), XCB_ATOM_ATOM,
+ atom(QXcbAtom::AtomXdndActionList), XCB_ATOM_ATOM,
0, 1024);
if (reply && reply->type != XCB_NONE && reply->format == 32) {
int length = xcb_get_property_value_length(reply.get()) / 4;
@@ -590,7 +593,7 @@ void QXcbDrag::setActionList(Qt::DropAction requestedAction, Qt::DropActions sup
if (current_actions != actions) {
xcb_change_property(xcb_connection(), XCB_PROP_MODE_REPLACE, connection()->qtSelectionOwner(),
- atom(QXcbAtom::XdndActionList),
+ atom(QXcbAtom::AtomXdndActionList),
XCB_ATOM_ATOM, 32, actions.size(), actions.constData());
current_actions = actions;
}
@@ -614,7 +617,7 @@ void QXcbDrag::stopListeningForActionListChanges()
int QXcbDrag::findTransactionByWindow(xcb_window_t window)
{
int at = -1;
- for (int i = 0; i < transactions.count(); ++i) {
+ for (int i = 0; i < transactions.size(); ++i) {
const Transaction &t = transactions.at(i);
if (t.target == window || t.proxy_target == window) {
at = i;
@@ -627,7 +630,7 @@ int QXcbDrag::findTransactionByWindow(xcb_window_t window)
int QXcbDrag::findTransactionByTime(xcb_timestamp_t timestamp)
{
int at = -1;
- for (int i = 0; i < transactions.count(); ++i) {
+ for (int i = 0; i < transactions.size(); ++i) {
const Transaction &t = transactions.at(i);
if (t.timestamp == timestamp) {
at = i;
@@ -701,7 +704,7 @@ void QXcbDrag::handleEnter(QPlatformWindow *, const xcb_client_message_event_t *
if (event->data.data32[1] & 1) {
// get the types from XdndTypeList
auto reply = Q_XCB_REPLY(xcb_get_property, xcb_connection(), false, xdnd_dragsource,
- atom(QXcbAtom::XdndTypelist), XCB_ATOM_ATOM,
+ atom(QXcbAtom::AtomXdndTypelist), XCB_ATOM_ATOM,
0, xdnd_max_type);
if (reply && reply->type != XCB_NONE && reply->format == 32) {
int length = xcb_get_property_value_length(reply.get()) / 4;
@@ -720,7 +723,7 @@ void QXcbDrag::handleEnter(QPlatformWindow *, const xcb_client_message_event_t *
xdnd_types.append(event->data.data32[i]);
}
}
- for(int i = 0; i < xdnd_types.length(); ++i)
+ for(int i = 0; i < xdnd_types.size(); ++i)
qCDebug(lcQpaXDnd) << " " << connection()->atomName(xdnd_types.at(i));
}
@@ -734,7 +737,7 @@ void QXcbDrag::handle_xdnd_position(QPlatformWindow *w, const xcb_client_message
QPoint p((e->data.data32[2] & 0xffff0000) >> 16, e->data.data32[2] & 0x0000ffff);
Q_ASSERT(w);
QRect geometry = w->geometry();
- p -= geometry.topLeft();
+ p -= w->isEmbedded() ? w->mapToGlobal(geometry.topLeft()) : geometry.topLeft();
if (!w || !w->window() || (w->window()->type() == Qt::Desktop))
return;
@@ -761,12 +764,12 @@ void QXcbDrag::handle_xdnd_position(QPlatformWindow *w, const xcb_client_message
} else {
dropData = m_dropData;
supported_actions = toDropActions(drop_actions);
- if (e->data.data32[4] != atom(QXcbAtom::XdndActionAsk))
+ if (e->data.data32[4] != atom(QXcbAtom::AtomXdndActionAsk))
supported_actions |= Qt::DropActions(toDropAction(e->data.data32[4]));
}
auto buttons = currentDrag() ? b : connection()->queryMouseButtons();
- auto modifiers = currentDrag() ? mods : connection()->queryKeyboardModifiers();
+ auto modifiers = currentDrag() ? mods : connection()->keyboard()->queryKeyboardModifiers();
QPlatformDragQtResponse qt_response = QWindowSystemInterface::handleDrag(
w->window(), dropData, p, supported_actions, buttons, modifiers);
@@ -783,7 +786,7 @@ void QXcbDrag::handle_xdnd_position(QPlatformWindow *w, const xcb_client_message
response.sequence = 0;
response.window = xdnd_dragsource;
response.format = 32;
- response.type = atom(QXcbAtom::XdndStatus);
+ response.type = atom(QXcbAtom::AtomXdndStatus);
response.data.data32[0] = xcb_window(w);
response.data.data32[1] = qt_response.isAccepted(); // flags
response.data.data32[2] = 0; // x, y
@@ -835,7 +838,7 @@ namespace
void QXcbDrag::handlePosition(QPlatformWindow * w, const xcb_client_message_event_t *event)
{
xcb_client_message_event_t *lastEvent = const_cast<xcb_client_message_event_t *>(event);
- ClientMessageScanner scanner(atom(QXcbAtom::XdndPosition));
+ ClientMessageScanner scanner(atom(QXcbAtom::AtomXdndPosition));
while (auto nextEvent = connection()->eventQueue()->peek(scanner)) {
if (lastEvent != event)
free(lastEvent);
@@ -883,7 +886,7 @@ void QXcbDrag::handleStatus(const xcb_client_message_event_t *event)
xcb_client_message_event_t *lastEvent = const_cast<xcb_client_message_event_t *>(event);
xcb_generic_event_t *nextEvent;
- ClientMessageScanner scanner(atom(QXcbAtom::XdndStatus));
+ ClientMessageScanner scanner(atom(QXcbAtom::AtomXdndStatus));
while ((nextEvent = connection()->eventQueue()->peek(scanner))) {
if (lastEvent != event)
free(lastEvent);
@@ -934,7 +937,7 @@ void QXcbDrag::send_leave()
leave.sequence = 0;
leave.window = current_target;
leave.format = 32;
- leave.type = atom(QXcbAtom::XdndLeave);
+ leave.type = atom(QXcbAtom::AtomXdndLeave);
leave.data.data32[0] = connection()->qtSelectionOwner();
leave.data.data32[1] = 0; // flags
leave.data.data32[2] = 0; // x, y
@@ -980,40 +983,54 @@ void QXcbDrag::handleDrop(QPlatformWindow *, const xcb_client_message_event_t *e
Qt::DropActions supported_drop_actions;
QMimeData *dropData = nullptr;
+ // this could be a same-application drop, just proxied due to
+ // some XEMBEDding, so try to find the real QMimeData used
+ // based on the timestamp for this drop.
+ int at = findTransactionByTime(target_time);
+ if (at != -1) {
+ qCDebug(lcQpaXDnd) << "found one transaction via findTransactionByTime()";
+ dropData = transactions.at(at).drag->mimeData();
+ // Can't use the source QMimeData if we need the image conversion code from xdndObtainData
+ if (dropData && dropData->hasImage())
+ dropData = 0;
+ }
+ // if we can't find it, then use the data in the drag manager
if (currentDrag()) {
- dropData = currentDrag()->mimeData();
+ if (!dropData)
+ dropData = currentDrag()->mimeData();
supported_drop_actions = Qt::DropActions(l[4]);
} else {
- dropData = m_dropData;
+ if (!dropData)
+ dropData = m_dropData;
supported_drop_actions = accepted_drop_action | toDropActions(drop_actions);
}
if (!dropData)
return;
- // ###
- // int at = findXdndDropTransactionByTime(target_time);
- // if (at != -1)
- // dropData = QDragManager::dragPrivate(X11->dndDropTransactions.at(at).object)->data;
- // if we can't find it, then use the data in the drag manager
auto buttons = currentDrag() ? b : connection()->queryMouseButtons();
- auto modifiers = currentDrag() ? mods : connection()->queryKeyboardModifiers();
+ auto modifiers = currentDrag() ? mods : connection()->keyboard()->queryKeyboardModifiers();
QPlatformDropQtResponse response = QWindowSystemInterface::handleDrop(
currentWindow.data(), dropData, currentPosition, supported_drop_actions,
buttons, modifiers);
- setExecutedDropAction(response.acceptedAction());
+ Qt::DropAction acceptedAaction = response.acceptedAction();
+ if (!response.isAccepted()) {
+ // Ignore a failed drag
+ acceptedAaction = Qt::IgnoreAction;
+ }
+ setExecutedDropAction(acceptedAaction);
xcb_client_message_event_t finished = {};
finished.response_type = XCB_CLIENT_MESSAGE;
finished.sequence = 0;
finished.window = xdnd_dragsource;
finished.format = 32;
- finished.type = atom(QXcbAtom::XdndFinished);
+ finished.type = atom(QXcbAtom::AtomXdndFinished);
finished.data.data32[0] = currentWindow ? xcb_window(currentWindow.data()) : XCB_NONE;
finished.data.data32[1] = response.isAccepted(); // flags
- finished.data.data32[2] = toXdndAction(response.acceptedAction());
+ finished.data.data32[2] = toXdndAction(acceptedAaction);
qCDebug(lcQpaXDnd) << "sending XdndFinished to source:" << xdnd_dragsource;
@@ -1075,7 +1092,7 @@ void QXcbDrag::timerEvent(QTimerEvent* e)
{
if (e->timerId() == cleanup_timer) {
bool stopTimer = true;
- for (int i = 0; i < transactions.count(); ++i) {
+ for (int i = 0; i < transactions.size(); ++i) {
const Transaction &t = transactions.at(i);
if (t.targetWindow) {
// dnd within the same process, don't delete, these are taken care of
@@ -1083,7 +1100,7 @@ void QXcbDrag::timerEvent(QTimerEvent* e)
continue;
}
QTime currentTime = QTime::currentTime();
- int delta = t.time.msecsTo(currentTime);
+ std::chrono::milliseconds delta{t.time.msecsTo(currentTime)};
if (delta > XdndDropTransactionTimeout) {
/* delete transactions which are older than XdndDropTransactionTimeout. It could mean
one of these:
@@ -1127,7 +1144,7 @@ static xcb_window_t findXdndAwareParent(QXcbConnection *c, xcb_window_t window)
forever {
// check if window has XdndAware
auto gpReply = Q_XCB_REPLY(xcb_get_property, c->xcb_connection(), false, window,
- c->atom(QXcbAtom::XdndAware), XCB_GET_PROPERTY_TYPE_ANY, 0, 0);
+ c->atom(QXcbAtom::AtomXdndAware), XCB_GET_PROPERTY_TYPE_ANY, 0, 0);
bool aware = gpReply && gpReply->type != XCB_NONE;
if (aware) {
target = window;
@@ -1217,6 +1234,7 @@ void QXcbDrag::handleSelectionRequest(const xcb_selection_request_event_t *event
bool QXcbDrag::dndEnable(QXcbWindow *w, bool on)
{
+ qCDebug(lcQpaXDnd) << "dndEnable" << static_cast<QPlatformWindow *>(w) << on;
// Windows announce that they support the XDND protocol by creating a window property XdndAware.
if (on) {
QXcbWindow *window = nullptr;
@@ -1233,7 +1251,7 @@ bool QXcbDrag::dndEnable(QXcbWindow *w, bool on)
desktop_proxy = new QWindow;
window = static_cast<QXcbWindow *>(desktop_proxy->handle());
proxy_id = window->xcb_window();
- xcb_atom_t xdnd_proxy = atom(QXcbAtom::XdndProxy);
+ xcb_atom_t xdnd_proxy = atom(QXcbAtom::AtomXdndProxy);
xcb_change_property(xcb_connection(), XCB_PROP_MODE_REPLACE, w->xcb_window(), xdnd_proxy,
XCB_ATOM_WINDOW, 32, 1, &proxy_id);
xcb_change_property(xcb_connection(), XCB_PROP_MODE_REPLACE, proxy_id, xdnd_proxy,
@@ -1247,14 +1265,14 @@ bool QXcbDrag::dndEnable(QXcbWindow *w, bool on)
qCDebug(lcQpaXDnd) << "setting XdndAware for" << window->xcb_window();
xcb_atom_t atm = xdnd_version;
xcb_change_property(xcb_connection(), XCB_PROP_MODE_REPLACE, window->xcb_window(),
- atom(QXcbAtom::XdndAware), XCB_ATOM_ATOM, 32, 1, &atm);
+ atom(QXcbAtom::AtomXdndAware), XCB_ATOM_ATOM, 32, 1, &atm);
return true;
} else {
return false;
}
} else {
if (w->window()->type() == Qt::Desktop) {
- xcb_delete_property(xcb_connection(), w->xcb_window(), atom(QXcbAtom::XdndProxy));
+ xcb_delete_property(xcb_connection(), w->xcb_window(), atom(QXcbAtom::AtomXdndProxy));
delete desktop_proxy;
desktop_proxy = nullptr;
} else {
@@ -1306,10 +1324,10 @@ QVariant QXcbDropData::xdndObtainData(const QByteArray &format, QMetaType reques
return result;
#ifndef QT_NO_CLIPBOARD
- if (c->selectionOwner(c->atom(QXcbAtom::XdndSelection)) == XCB_NONE)
+ if (c->selectionOwner(c->atom(QXcbAtom::AtomXdndSelection)) == XCB_NONE)
return result; // should never happen?
- xcb_atom_t xdnd_selection = c->atom(QXcbAtom::XdndSelection);
+ xcb_atom_t xdnd_selection = c->atom(QXcbAtom::AtomXdndSelection);
result = c->clipboard()->getSelection(xdnd_selection, a, xdnd_selection, drag->targetTime());
#endif
diff --git a/src/plugins/platforms/xcb/qxcbdrag.h b/src/plugins/platforms/xcb/qxcbdrag.h
index a58b7e850e..ae7cc915c8 100644
--- a/src/plugins/platforms/xcb/qxcbdrag.h
+++ b/src/plugins/platforms/xcb/qxcbdrag.h
@@ -14,7 +14,6 @@
#include <qpoint.h>
#include <qpointer.h>
#include <qrect.h>
-#include <qsharedpointer.h>
#include <qxcbobject.h>
#include <QtCore/QDebug>
@@ -128,7 +127,7 @@ private:
QXcbVirtualDesktop *current_virtual_desktop;
// 10 minute timer used to discard old XdndDrop transactions
- enum { XdndDropTransactionTimeout = 600000 };
+ static constexpr std::chrono::minutes XdndDropTransactionTimeout{10};
int cleanup_timer;
QList<xcb_atom_t> drag_types;
diff --git a/src/plugins/platforms/xcb/qxcbeventqueue.cpp b/src/plugins/platforms/xcb/qxcbeventqueue.cpp
index 5c87cba80d..33795d63cf 100644
--- a/src/plugins/platforms/xcb/qxcbeventqueue.cpp
+++ b/src/plugins/platforms/xcb/qxcbeventqueue.cpp
@@ -345,7 +345,7 @@ void QXcbEventQueue::sendCloseConnectionEvent() const
event.format = 32;
event.sequence = 0;
event.window = window;
- event.type = m_connection->atom(QXcbAtom::_QT_CLOSE_CONNECTION);
+ event.type = m_connection->atom(QXcbAtom::Atom_QT_CLOSE_CONNECTION);
event.data.data32[0] = 0;
xcb_send_event(c, false, window, XCB_EVENT_MASK_NO_EVENT, reinterpret_cast<const char *>(&event));
@@ -357,7 +357,7 @@ bool QXcbEventQueue::isCloseConnectionEvent(const xcb_generic_event_t *event)
{
if (event && (event->response_type & ~0x80) == XCB_CLIENT_MESSAGE) {
auto clientMessage = reinterpret_cast<const xcb_client_message_event_t *>(event);
- if (clientMessage->type == m_connection->atom(QXcbAtom::_QT_CLOSE_CONNECTION))
+ if (clientMessage->type == m_connection->atom(QXcbAtom::Atom_QT_CLOSE_CONNECTION))
m_closeConnectionDetected = true;
}
return m_closeConnectionDetected;
diff --git a/src/plugins/platforms/xcb/qxcbimage.h b/src/plugins/platforms/xcb/qxcbimage.h
index e550102881..c022fae639 100644
--- a/src/plugins/platforms/xcb/qxcbimage.h
+++ b/src/plugins/platforms/xcb/qxcbimage.h
@@ -5,7 +5,6 @@
#define QXCBIMAGE_H
#include "qxcbscreen.h"
-#include <QtCore/QPair>
#include <QtGui/QImage>
#include <QtGui/QPixmap>
#include <xcb/xcb_image.h>
diff --git a/src/plugins/platforms/xcb/qxcbintegration.cpp b/src/plugins/platforms/xcb/qxcbintegration.cpp
index 8b49fb73ca..4dafae31e3 100644
--- a/src/plugins/platforms/xcb/qxcbintegration.cpp
+++ b/src/plugins/platforms/xcb/qxcbintegration.cpp
@@ -93,10 +93,17 @@ static bool runningUnderDebugger()
#endif
}
+class QXcbUnixServices : public QGenericUnixServices
+{
+public:
+ QString portalWindowIdentifier(QWindow *window) override;
+};
+
+
QXcbIntegration *QXcbIntegration::m_instance = nullptr;
QXcbIntegration::QXcbIntegration(const QStringList &parameters, int &argc, char **argv)
- : m_services(new QGenericUnixServices)
+ : m_services(new QXcbUnixServices)
, m_instanceName(nullptr)
, m_canGrab(true)
, m_defaultVisualId(UINT_MAX)
@@ -197,7 +204,7 @@ QPlatformPixmap *QXcbIntegration::createPlatformPixmap(QPlatformPixmap::PixelTyp
QPlatformWindow *QXcbIntegration::createPlatformWindow(QWindow *window) const
{
QXcbGlIntegration *glIntegration = nullptr;
- const bool isTrayIconWindow = QXcbWindow::isTrayIconWindow(window);;
+ const bool isTrayIconWindow = QXcbWindow::isTrayIconWindow(window);
if (window->type() != Qt::Desktop && !isTrayIconWindow) {
if (window->supportsOpenGL()) {
glIntegration = connection()->glIntegration();
@@ -325,19 +332,38 @@ QAbstractEventDispatcher *QXcbIntegration::createEventDispatcher() const
return QXcbEventDispatcher::createEventDispatcher(connection());
}
+using namespace Qt::Literals::StringLiterals;
+static const auto xsNetCursorBlink = "Net/CursorBlink"_ba;
+static const auto xsNetCursorBlinkTime = "Net/CursorBlinkTime"_ba;
+static const auto xsNetDoubleClickTime = "Net/DoubleClickTime"_ba;
+static const auto xsNetDoubleClickDistance = "Net/DoubleClickDistance"_ba;
+static const auto xsNetDndDragThreshold = "Net/DndDragThreshold"_ba;
+
void QXcbIntegration::initialize()
{
const auto defaultInputContext = "compose"_L1;
// Perform everything that may potentially need the event dispatcher (timers, socket
// notifiers) here instead of the constructor.
- QString icStr = QPlatformInputContextFactory::requested();
- if (icStr.isNull())
- icStr = defaultInputContext;
- m_inputContext.reset(QPlatformInputContextFactory::create(icStr));
- if (!m_inputContext && icStr != defaultInputContext && icStr != "none"_L1)
+ auto icStrs = QPlatformInputContextFactory::requested();
+ if (icStrs.isEmpty())
+ icStrs = { defaultInputContext };
+ m_inputContext.reset(QPlatformInputContextFactory::create(icStrs));
+ if (!m_inputContext && !icStrs.contains(defaultInputContext)
+ && icStrs != QStringList{"none"_L1})
m_inputContext.reset(QPlatformInputContextFactory::create(defaultInputContext));
connection()->keyboard()->initialize();
+
+ auto notifyThemeChanged = [](QXcbVirtualDesktop *, const QByteArray &, const QVariant &, void *) {
+ QWindowSystemInterface::handleThemeChange();
+ };
+
+ auto *xsettings = connection()->primaryScreen()->xSettings();
+ xsettings->registerCallbackForProperty(xsNetCursorBlink, notifyThemeChanged, this);
+ xsettings->registerCallbackForProperty(xsNetCursorBlinkTime, notifyThemeChanged, this);
+ xsettings->registerCallbackForProperty(xsNetDoubleClickTime, notifyThemeChanged, this);
+ xsettings->registerCallbackForProperty(xsNetDoubleClickDistance, notifyThemeChanged, this);
+ xsettings->registerCallbackForProperty(xsNetDndDragThreshold, notifyThemeChanged, this);
}
void QXcbIntegration::moveToScreen(QWindow *window, int screen)
@@ -404,14 +430,9 @@ QPlatformServices *QXcbIntegration::services() const
return m_services.data();
}
-Qt::KeyboardModifiers QXcbIntegration::queryKeyboardModifiers() const
+QPlatformKeyMapper *QXcbIntegration::keyMapper() const
{
- return m_connection->queryKeyboardModifiers();
-}
-
-QList<int> QXcbIntegration::possibleKeys(const QKeyEvent *e) const
-{
- return m_connection->keyboard()->possibleKeys(e);
+ return m_connection->keyboard();
}
QStringList QXcbIntegration::themeNames() const
@@ -435,17 +456,17 @@ QVariant QXcbIntegration::styleHint(QPlatformIntegration::StyleHint hint) const
case QPlatformIntegration::CursorFlashTime: {
bool ok = false;
// If cursor blinking is off, returns 0 to keep the cursor awlays display.
- if (connection()->primaryScreen()->xSettings()->setting(QByteArrayLiteral("Net/CursorBlink")).toInt(&ok) == 0 && ok)
+ if (connection()->primaryScreen()->xSettings()->setting(xsNetCursorBlink).toInt(&ok) == 0 && ok)
return 0;
- RETURN_VALID_XSETTINGS(QByteArrayLiteral("Net/CursorBlinkTime"));
+ RETURN_VALID_XSETTINGS(xsNetCursorBlinkTime);
break;
}
case QPlatformIntegration::MouseDoubleClickInterval:
- RETURN_VALID_XSETTINGS(QByteArrayLiteral("Net/DoubleClickTime"));
+ RETURN_VALID_XSETTINGS(xsNetDoubleClickTime);
break;
case QPlatformIntegration::MouseDoubleClickDistance:
- RETURN_VALID_XSETTINGS(QByteArrayLiteral("Net/DoubleClickDistance"));
+ RETURN_VALID_XSETTINGS(xsNetDoubleClickDistance);
break;
case QPlatformIntegration::KeyboardInputInterval:
case QPlatformIntegration::StartDragTime:
@@ -454,21 +475,23 @@ QVariant QXcbIntegration::styleHint(QPlatformIntegration::StyleHint hint) const
case QPlatformIntegration::StartDragVelocity:
case QPlatformIntegration::UseRtlExtensions:
case QPlatformIntegration::PasswordMaskCharacter:
+ case QPlatformIntegration::FlickMaximumVelocity:
+ case QPlatformIntegration::FlickDeceleration:
// TODO using various xcb, gnome or KDE settings
break; // Not implemented, use defaults
+ case QPlatformIntegration::FlickStartDistance:
case QPlatformIntegration::StartDragDistance: {
- RETURN_VALID_XSETTINGS(QByteArrayLiteral("Net/DndDragThreshold"));
-
+ RETURN_VALID_XSETTINGS(xsNetDndDragThreshold);
// The default (in QPlatformTheme::defaultThemeHint) is 10 pixels, but
// on a high-resolution screen it makes sense to increase it.
- qreal dpi = 100.0;
+ qreal dpi = 100;
if (const QXcbScreen *screen = connection()->primaryScreen()) {
if (screen->logicalDpi().first > dpi)
dpi = screen->logicalDpi().first;
if (screen->logicalDpi().second > dpi)
dpi = screen->logicalDpi().second;
}
- return 10.0 * dpi / 100.0;
+ return (hint == QPlatformIntegration::FlickStartDistance ? qreal(15) : qreal(10)) * dpi / qreal(100);
}
case QPlatformIntegration::ShowIsFullScreen:
// X11 always has support for windows, but the
@@ -567,4 +590,15 @@ QPlatformVulkanInstance *QXcbIntegration::createPlatformVulkanInstance(QVulkanIn
}
#endif
+void QXcbIntegration::setApplicationBadge(qint64 number)
+{
+ auto unixServices = dynamic_cast<QGenericUnixServices *>(services());
+ unixServices->setApplicationBadge(number);
+}
+
+QString QXcbUnixServices::portalWindowIdentifier(QWindow *window)
+{
+ return "x11:"_L1 + QString::number(window->winId(), 16);
+}
+
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/xcb/qxcbintegration.h b/src/plugins/platforms/xcb/qxcbintegration.h
index abdfe7112c..a1e0c3f3e1 100644
--- a/src/plugins/platforms/xcb/qxcbintegration.h
+++ b/src/plugins/platforms/xcb/qxcbintegration.h
@@ -74,8 +74,7 @@ public:
QPlatformServices *services() const override;
- Qt::KeyboardModifiers queryKeyboardModifiers() const override;
- QList<int> possibleKeys(const QKeyEvent *e) const override;
+ QPlatformKeyMapper *keyMapper() const override;
QStringList themeNames() const override;
QPlatformTheme *createPlatformTheme(const QString &name) const override;
@@ -102,6 +101,8 @@ public:
static QXcbIntegration *instance() { return m_instance; }
+ void setApplicationBadge(qint64 number) override;
+
private:
QXcbConnection *m_connection = nullptr;
diff --git a/src/plugins/platforms/xcb/qxcbkeyboard.cpp b/src/plugins/platforms/xcb/qxcbkeyboard.cpp
index 0666fac735..17da54bc7a 100644
--- a/src/plugins/platforms/xcb/qxcbkeyboard.cpp
+++ b/src/plugins/platforms/xcb/qxcbkeyboard.cpp
@@ -3,6 +3,7 @@
#include "qxcbkeyboard.h"
#include "qxcbwindow.h"
#include "qxcbscreen.h"
+#include "qxcbcursor.h"
#include <qpa/qwindowsysteminterface.h>
#include <qpa/qplatforminputcontext.h>
@@ -377,9 +378,18 @@ void QXcbKeyboard::updateKeymap()
QXkbCommon::verifyHasLatinLayout(m_xkbKeymap.get());
}
-QList<int> QXcbKeyboard::possibleKeys(const QKeyEvent *event) const
+QList<QKeyCombination> QXcbKeyboard::possibleKeyCombinations(const QKeyEvent *event) const
{
- return QXkbCommon::possibleKeys(m_xkbState.get(), event, m_superAsMeta, m_hyperAsMeta);
+ return QXkbCommon::possibleKeyCombinations(
+ m_xkbState.get(), event, m_superAsMeta, m_hyperAsMeta);
+}
+
+Qt::KeyboardModifiers QXcbKeyboard::queryKeyboardModifiers() const
+{
+ // FIXME: Should we base this on m_xkbState?
+ int stateMask = 0;
+ QXcbCursor::queryPointer(connection(), nullptr, nullptr, &stateMask);
+ return translateModifiers(stateMask);
}
void QXcbKeyboard::updateXKBState(xcb_xkb_state_notify_event_t *state)
diff --git a/src/plugins/platforms/xcb/qxcbkeyboard.h b/src/plugins/platforms/xcb/qxcbkeyboard.h
index 15b08fbead..62d9268c64 100644
--- a/src/plugins/platforms/xcb/qxcbkeyboard.h
+++ b/src/plugins/platforms/xcb/qxcbkeyboard.h
@@ -14,11 +14,13 @@
#include <QtGui/private/qxkbcommon_p.h>
#include <xkbcommon/xkbcommon-x11.h>
+#include <qpa/qplatformkeymapper.h>
+
#include <QEvent>
QT_BEGIN_NAMESPACE
-class QXcbKeyboard : public QXcbObject
+class QXcbKeyboard : public QXcbObject, public QPlatformKeyMapper
{
public:
QXcbKeyboard(QXcbConnection *connection);
@@ -34,7 +36,9 @@ public:
Qt::KeyboardModifiers translateModifiers(int s) const;
void updateKeymap(xcb_mapping_notify_event_t *event);
void updateKeymap();
- QList<int> possibleKeys(const QKeyEvent *event) const;
+
+ QList<QKeyCombination> possibleKeyCombinations(const QKeyEvent *event) const override;
+ Qt::KeyboardModifiers queryKeyboardModifiers() const override;
void updateXKBMods();
xkb_mod_mask_t xkbModMask(quint16 state);
diff --git a/src/plugins/platforms/xcb/qxcbmime.cpp b/src/plugins/platforms/xcb/qxcbmime.cpp
index 7161af279c..860d195d13 100644
--- a/src/plugins/platforms/xcb/qxcbmime.cpp
+++ b/src/plugins/platforms/xcb/qxcbmime.cpp
@@ -27,8 +27,8 @@ QString QXcbMime::mimeAtomToString(QXcbConnection *connection, xcb_atom_t a)
// special cases for string type
if (a == XCB_ATOM_STRING
- || a == connection->atom(QXcbAtom::UTF8_STRING)
- || a == connection->atom(QXcbAtom::TEXT))
+ || a == connection->atom(QXcbAtom::AtomUTF8_STRING)
+ || a == connection->atom(QXcbAtom::AtomTEXT))
return "text/plain"_L1;
// special case for images
@@ -54,15 +54,15 @@ bool QXcbMime::mimeDataForAtom(QXcbConnection *connection, xcb_atom_t a, QMimeDa
*atomFormat = a;
*dataFormat = 8;
- if ((a == connection->atom(QXcbAtom::UTF8_STRING)
+ if ((a == connection->atom(QXcbAtom::AtomUTF8_STRING)
|| a == XCB_ATOM_STRING
- || a == connection->atom(QXcbAtom::TEXT))
+ || a == connection->atom(QXcbAtom::AtomTEXT))
&& QInternalMimeData::hasFormatHelper("text/plain"_L1, mimeData)) {
- if (a == connection->atom(QXcbAtom::UTF8_STRING)) {
+ if (a == connection->atom(QXcbAtom::AtomUTF8_STRING)) {
*data = QInternalMimeData::renderDataHelper("text/plain"_L1, mimeData);
ret = true;
} else if (a == XCB_ATOM_STRING ||
- a == connection->atom(QXcbAtom::TEXT)) {
+ a == connection->atom(QXcbAtom::AtomTEXT)) {
// ICCCM says STRING is latin1
*data = QString::fromUtf8(QInternalMimeData::renderDataHelper(
"text/plain"_L1, mimeData)).toLatin1();
@@ -79,8 +79,7 @@ bool QXcbMime::mimeDataForAtom(QXcbConnection *connection, xcb_atom_t a, QMimeDa
if (atomName == "text/uri-list"_L1
&& connection->atomName(a) == "text/x-moz-url") {
const QString mozUri = QLatin1StringView(data->split('\n').constFirst()) + u'\n';
- *data = QByteArray(reinterpret_cast<const char *>(mozUri.data()),
- mozUri.length() * 2);
+ data->assign({reinterpret_cast<const char *>(mozUri.data()), mozUri.size() * 2});
} else if (atomName == "application/x-color"_L1)
*dataFormat = 16;
ret = true;
@@ -102,9 +101,9 @@ QList<xcb_atom_t> QXcbMime::mimeAtomsForFormat(QXcbConnection *connection, const
// special cases for strings
if (format == "text/plain"_L1) {
- atoms.append(connection->atom(QXcbAtom::UTF8_STRING));
+ atoms.append(connection->atom(QXcbAtom::AtomUTF8_STRING));
atoms.append(XCB_ATOM_STRING);
- atoms.append(connection->atom(QXcbAtom::TEXT));
+ atoms.append(connection->atom(QXcbAtom::AtomTEXT));
}
// special cases for uris
@@ -139,11 +138,11 @@ QVariant QXcbMime::mimeConvertToFormat(QXcbConnection *connection, xcb_atom_t a,
if (format == "text/plain"_L1) {
if (data.endsWith('\0'))
data.chop(1);
- if (a == connection->atom(QXcbAtom::UTF8_STRING)) {
+ if (a == connection->atom(QXcbAtom::AtomUTF8_STRING)) {
return QString::fromUtf8(data);
}
if (a == XCB_ATOM_STRING ||
- a == connection->atom(QXcbAtom::TEXT))
+ a == connection->atom(QXcbAtom::AtomTEXT))
return QString::fromLatin1(data);
}
// If data contains UTF16 text, convert it to a string.
@@ -156,7 +155,7 @@ QVariant QXcbMime::mimeConvertToFormat(QXcbConnection *connection, xcb_atom_t a,
const quint8 byte1 = data.at(1);
if ((byte0 == 0xff && byte1 == 0xfe) || (byte0 == 0xfe && byte1 == 0xff)
|| (byte0 != 0 && byte1 == 0) || (byte0 == 0 && byte1 != 0)) {
- const QString str = QString::fromUtf16(
+ const QStringView str(
reinterpret_cast<const char16_t *>(data.constData()), data.size() / 2);
if (!str.isNull()) {
if (format == "text/uri-list"_L1) {
@@ -175,7 +174,7 @@ QVariant QXcbMime::mimeConvertToFormat(QXcbConnection *connection, xcb_atom_t a,
return list.constFirst();
return list;
} else {
- return str;
+ return str.toString();
}
}
}
@@ -227,12 +226,12 @@ xcb_atom_t QXcbMime::mimeAtomForFormat(QXcbConnection *connection, const QString
// find matches for string types
if (format == "text/plain"_L1) {
- if (atoms.contains(connection->atom(QXcbAtom::UTF8_STRING)))
- return connection->atom(QXcbAtom::UTF8_STRING);
+ if (atoms.contains(connection->atom(QXcbAtom::AtomUTF8_STRING)))
+ return connection->atom(QXcbAtom::AtomUTF8_STRING);
if (atoms.contains(XCB_ATOM_STRING))
return XCB_ATOM_STRING;
- if (atoms.contains(connection->atom(QXcbAtom::TEXT)))
- return connection->atom(QXcbAtom::TEXT);
+ if (atoms.contains(connection->atom(QXcbAtom::AtomTEXT)))
+ return connection->atom(QXcbAtom::AtomTEXT);
}
// find matches for uri types
diff --git a/src/plugins/platforms/xcb/qxcbnativeinterface.cpp b/src/plugins/platforms/xcb/qxcbnativeinterface.cpp
index cd6ff40df9..06f5241d8c 100644
--- a/src/plugins/platforms/xcb/qxcbnativeinterface.cpp
+++ b/src/plugins/platforms/xcb/qxcbnativeinterface.cpp
@@ -350,7 +350,7 @@ void *QXcbNativeInterface::atspiBus()
QXcbIntegration *integration = static_cast<QXcbIntegration *>(QGuiApplicationPrivate::platformIntegration());
QXcbConnection *connection = integration->connection();
if (connection) {
- auto atspiBusAtom = connection->atom(QXcbAtom::AT_SPI_BUS);
+ auto atspiBusAtom = connection->atom(QXcbAtom::AtomAT_SPI_BUS);
auto reply = Q_XCB_REPLY(xcb_get_property, connection->xcb_connection(),
false, connection->rootWindow(),
atspiBusAtom, XCB_ATOM_STRING, 0, 128);
diff --git a/src/plugins/platforms/xcb/qxcbscreen.cpp b/src/plugins/platforms/xcb/qxcbscreen.cpp
index 2a538c9b6e..06f4b66edb 100644
--- a/src/plugins/platforms/xcb/qxcbscreen.cpp
+++ b/src/plugins/platforms/xcb/qxcbscreen.cpp
@@ -50,7 +50,7 @@ QXcbVirtualDesktop::QXcbVirtualDesktop(QXcbConnection *connection, xcb_screen_t
auto reply = Q_XCB_REPLY_UNCHECKED(xcb_get_property, xcb_connection(),
false, screen->root,
- atom(QXcbAtom::_NET_SUPPORTING_WM_CHECK),
+ atom(QXcbAtom::Atom_NET_SUPPORTING_WM_CHECK),
XCB_ATOM_WINDOW, 0, 1024);
if (reply && reply->format == 32 && reply->type == XCB_ATOM_WINDOW) {
xcb_window_t windowManager = *((xcb_window_t *)xcb_get_property_value(reply.get()));
@@ -92,7 +92,7 @@ QXcbVirtualDesktop::~QXcbVirtualDesktop()
{
delete m_xSettings;
- for (auto cmap : qAsConst(m_visualColormaps))
+ for (auto cmap : std::as_const(m_visualColormaps))
xcb_free_colormap(xcb_connection(), cmap);
}
@@ -222,7 +222,7 @@ void QXcbVirtualDesktop::handleScreenChange(xcb_randr_screen_change_notify_event
case XCB_RANDR_ROTATION_REFLECT_Y: break;
}
- for (QPlatformScreen *platformScreen : qAsConst(m_screens)) {
+ for (QPlatformScreen *platformScreen : std::as_const(m_screens)) {
QDpi ldpi = platformScreen->logicalDpi();
QWindowSystemInterface::handleScreenLogicalDotsPerInchChange(platformScreen->screen(), ldpi.first, ldpi.second);
}
@@ -249,7 +249,7 @@ QRect QXcbVirtualDesktop::getWorkArea() const
{
QRect r;
auto workArea = Q_XCB_REPLY_UNCHECKED(xcb_get_property, xcb_connection(), false, screen()->root,
- atom(QXcbAtom::_NET_WORKAREA),
+ atom(QXcbAtom::Atom_NET_WORKAREA),
XCB_ATOM_CARDINAL, 0, 1024);
if (workArea && workArea->type == XCB_ATOM_CARDINAL && workArea->format == 32 && workArea->value_len >= 4) {
// If workArea->value_len > 4, the remaining ones seem to be for WM's virtual desktops
@@ -271,7 +271,7 @@ void QXcbVirtualDesktop::updateWorkArea()
QRect workArea = getWorkArea();
if (m_workArea != workArea) {
m_workArea = workArea;
- for (QPlatformScreen *screen : qAsConst(m_screens))
+ for (QPlatformScreen *screen : std::as_const(m_screens))
((QXcbScreen *)screen)->updateAvailableGeometry();
}
}
@@ -529,7 +529,7 @@ void QXcbScreen::updateColorSpaceAndEdid()
// Read colord ICC data (from GNOME settings)
auto reply = Q_XCB_REPLY_UNCHECKED(xcb_get_property, xcb_connection(),
false, screen()->root,
- connection()->atom(QXcbAtom::_ICC_PROFILE),
+ connection()->atom(QXcbAtom::Atom_ICC_PROFILE),
XCB_ATOM_CARDINAL, 0, 8192);
if (reply->format == 8 && reply->type == XCB_ATOM_CARDINAL) {
QByteArray data(reinterpret_cast<const char *>(xcb_get_property_value(reply.get())), reply->value_len);
@@ -556,11 +556,11 @@ void QXcbScreen::updateColorSpaceAndEdid()
m_edid.greenChromaticity, m_edid.blueChromaticity,
QColorSpace::TransferFunction::Gamma, m_edid.gamma);
} else {
- if (m_edid.tables.length() == 1) {
+ if (m_edid.tables.size() == 1) {
m_colorSpace = QColorSpace(m_edid.whiteChromaticity, m_edid.redChromaticity,
m_edid.greenChromaticity, m_edid.blueChromaticity,
m_edid.tables[0]);
- } else if (m_edid.tables.length() == 3) {
+ } else if (m_edid.tables.size() == 3) {
m_colorSpace = QColorSpace(m_edid.whiteChromaticity, m_edid.redChromaticity,
m_edid.greenChromaticity, m_edid.blueChromaticity,
m_edid.tables[0], m_edid.tables[1], m_edid.tables[2]);
@@ -796,7 +796,7 @@ void QXcbScreen::windowShown(QXcbWindow *window)
// Freedesktop.org Startup Notification
if (!connection()->startupId().isEmpty() && window->window()->isTopLevel()) {
sendStartupMessage(QByteArrayLiteral("remove: ID=") + connection()->startupId());
- connection()->clearStartupId();
+ connection()->setStartupId({});
}
}
@@ -817,15 +817,15 @@ void QXcbScreen::sendStartupMessage(const QByteArray &message) const
xcb_client_message_event_t ev;
ev.response_type = XCB_CLIENT_MESSAGE;
ev.format = 8;
- ev.type = connection()->atom(QXcbAtom::_NET_STARTUP_INFO_BEGIN);
+ ev.type = connection()->atom(QXcbAtom::Atom_NET_STARTUP_INFO_BEGIN);
ev.sequence = 0;
ev.window = rootWindow;
int sent = 0;
- int length = message.length() + 1; // include NUL byte
+ int length = message.size() + 1; // include NUL byte
const char *data = message.constData();
do {
if (sent == 20)
- ev.type = connection()->atom(QXcbAtom::_NET_STARTUP_INFO);
+ ev.type = connection()->atom(QXcbAtom::Atom_NET_STARTUP_INFO);
const int start = sent;
const int numBytes = qMin(length - start, 20);
@@ -839,7 +839,7 @@ void QXcbScreen::sendStartupMessage(const QByteArray &message) const
QRect QXcbScreen::availableGeometry() const
{
static bool enforceNetWorkarea = !qEnvironmentVariableIsEmpty("QT_RELY_ON_NET_WORKAREA_ATOM");
- bool isMultiHeadSystem = virtualSiblings().length() > 1;
+ bool isMultiHeadSystem = virtualSiblings().size() > 1;
bool useScreenGeometry = isMultiHeadSystem && !enforceNetWorkarea;
return useScreenGeometry ? m_geometry : m_availableGeometry;
}
@@ -1095,11 +1095,11 @@ QByteArray QXcbScreen::getEdid() const
return result;
// Try a bunch of atoms
- result = getOutputProperty(atom(QXcbAtom::EDID));
+ result = getOutputProperty(atom(QXcbAtom::AtomEDID));
if (result.isEmpty())
- result = getOutputProperty(atom(QXcbAtom::EDID_DATA));
+ result = getOutputProperty(atom(QXcbAtom::AtomEDID_DATA));
if (result.isEmpty())
- result = getOutputProperty(atom(QXcbAtom::XFree86_DDC_EDID1_RAWDATA));
+ result = getOutputProperty(atom(QXcbAtom::AtomXFree86_DDC_EDID1_RAWDATA));
return result;
}
diff --git a/src/plugins/platforms/xcb/qxcbsessionmanager.cpp b/src/plugins/platforms/xcb/qxcbsessionmanager.cpp
index ec305d5030..b4e28ab831 100644
--- a/src/plugins/platforms/xcb/qxcbsessionmanager.cpp
+++ b/src/plugins/platforms/xcb/qxcbsessionmanager.cpp
@@ -101,19 +101,19 @@ static void sm_setProperty(const QString &name, const QString &value)
{
QByteArray v = value.toUtf8();
SmPropValue prop;
- prop.length = v.length();
+ prop.length = v.size();
prop.value = (SmPointer) const_cast<char *>(v.constData());
sm_setProperty(name.toLatin1().data(), SmARRAY8, 1, &prop);
}
static void sm_setProperty(const QString &name, const QStringList &value)
{
- SmPropValue *prop = new SmPropValue[value.count()];
+ SmPropValue *prop = new SmPropValue[value.size()];
int count = 0;
QList<QByteArray> vl;
vl.reserve(value.size());
for (QStringList::ConstIterator it = value.begin(); it != value.end(); ++it) {
- prop[count].length = (*it).length();
+ prop[count].length = (*it).size();
vl.append((*it).toUtf8());
prop[count].value = (char*)vl.constLast().data();
++count;
diff --git a/src/plugins/platforms/xcb/qxcbsystemtraytracker.cpp b/src/plugins/platforms/xcb/qxcbsystemtraytracker.cpp
index e184c07128..84d2d73f91 100644
--- a/src/plugins/platforms/xcb/qxcbsystemtraytracker.cpp
+++ b/src/plugins/platforms/xcb/qxcbsystemtraytracker.cpp
@@ -26,7 +26,7 @@ enum {
QXcbSystemTrayTracker *QXcbSystemTrayTracker::create(QXcbConnection *connection)
{
// Selection, tray atoms for GNOME, NET WM Specification
- const xcb_atom_t trayAtom = connection->atom(QXcbAtom::_NET_SYSTEM_TRAY_OPCODE);
+ const xcb_atom_t trayAtom = connection->atom(QXcbAtom::Atom_NET_SYSTEM_TRAY_OPCODE);
if (!trayAtom)
return nullptr;
const QByteArray netSysTray = QByteArrayLiteral("_NET_SYSTEM_TRAY_S") + QByteArray::number(connection->primaryScreenNumber());
@@ -113,7 +113,7 @@ xcb_visualid_t QXcbSystemTrayTracker::netSystemTrayVisual()
if (m_trayWindow == XCB_WINDOW_NONE)
return XCB_NONE;
- xcb_atom_t tray_atom = m_connection->atom(QXcbAtom::_NET_SYSTEM_TRAY_VISUAL);
+ xcb_atom_t tray_atom = m_connection->atom(QXcbAtom::Atom_NET_SYSTEM_TRAY_VISUAL);
// Get the xcb property for the _NET_SYSTEM_TRAY_VISUAL atom
auto systray_atom_reply = Q_XCB_REPLY_UNCHECKED(xcb_get_property, m_connection->xcb_connection(),
diff --git a/src/plugins/platforms/xcb/qxcbwindow.cpp b/src/plugins/platforms/xcb/qxcbwindow.cpp
index a37c03cc60..09d4cd9833 100644
--- a/src/plugins/platforms/xcb/qxcbwindow.cpp
+++ b/src/plugins/platforms/xcb/qxcbwindow.cpp
@@ -6,6 +6,7 @@
#include <QtDebug>
#include <QMetaEnum>
#include <QScreen>
+#include <QtCore/QFileInfo>
#include <QtGui/QIcon>
#include <QtGui/QRegion>
#include <QtGui/private/qhighdpiscaling_p.h>
@@ -59,6 +60,7 @@ QT_BEGIN_NAMESPACE
using namespace Qt::StringLiterals;
Q_LOGGING_CATEGORY(lcQpaWindow, "qt.qpa.window");
+Q_LOGGING_CATEGORY(lcQpaXcbWindow, "qt.qpa.xcb.window");
Q_DECLARE_TYPEINFO(xcb_rectangle_t, Q_PRIMITIVE_TYPE);
@@ -94,12 +96,14 @@ const quint32 XEMBED_VERSION = 0;
QXcbScreen *QXcbWindow::parentScreen()
{
- return parent() ? static_cast<QXcbWindow*>(parent())->parentScreen() : xcbScreen();
+ return QPlatformWindow::parent() ? static_cast<QXcbWindow*>(QPlatformWindow::parent())->parentScreen() : xcbScreen();
}
-//QPlatformWindow::screenForGeometry version that uses deviceIndependentGeometry
QXcbScreen *QXcbWindow::initialScreen() const
{
+ // Resolve initial screen via QWindowPrivate::screenForGeometry(),
+ // which works in platform independent coordinates, as opposed to
+ // QPlatformWindow::screenForGeometry() that uses native coordinates.
QWindowPrivate *windowPrivate = qt_window_private(window());
QScreen *screen = windowPrivate->screenForGeometry(window()->geometry());
return static_cast<QXcbScreen*>(screen->handle());
@@ -131,6 +135,7 @@ void QXcbWindow::setImageFormatForVisual(const xcb_visualtype_t *visual)
case 16:
qWarning("Using RGB16 fallback, if this works your X11 server is reporting a bad screen format.");
m_imageFormat = QImage::Format_RGB16;
+ break;
default:
break;
}
@@ -166,7 +171,7 @@ static inline XTextProperty* qstringToXTP(Display *dpy, const QString& s)
tp.value = (uchar*)qcs.data();
tp.encoding = XA_STRING;
tp.format = 8;
- tp.nitems = qcs.length();
+ tp.nitems = qcs.size();
free_prop = false;
}
return &tp;
@@ -221,6 +226,7 @@ enum : quint32 {
void QXcbWindow::create()
{
+ xcb_window_t old_m_window = m_window;
destroy();
m_windowState = Qt::WindowNoState;
@@ -229,8 +235,8 @@ void QXcbWindow::create()
Qt::WindowType type = window()->type();
QXcbScreen *currentScreen = xcbScreen();
- QXcbScreen *platformScreen = parent() ? parentScreen() : initialScreen();
- QRect rect = parent()
+ QXcbScreen *platformScreen = QPlatformWindow::parent() ? parentScreen() : initialScreen();
+ QRect rect = QPlatformWindow::parent()
? QHighDpi::toNativeLocalPosition(window()->geometry(), platformScreen)
: QHighDpi::toNativePixels(window()->geometry(), platformScreen);
@@ -253,11 +259,6 @@ void QXcbWindow::create()
return;
}
- QPlatformWindow::setGeometry(rect);
-
- if (platformScreen != currentScreen)
- QWindowSystemInterface::handleWindowScreenChanged(window(), platformScreen->QPlatformScreen::screen());
-
const QSize minimumSize = windowMinimumSize();
if (rect.width() > 0 || rect.height() > 0) {
rect.setWidth(qBound(1, rect.width(), XCOORD_MAX));
@@ -269,12 +270,17 @@ void QXcbWindow::create()
rect.setHeight(QHighDpi::toNativePixels(int(defaultWindowHeight), platformScreen->QPlatformScreen::screen()));
}
+ QPlatformWindow::setGeometry(rect);
+
+ if (platformScreen != currentScreen)
+ QWindowSystemInterface::handleWindowScreenChanged(window(), platformScreen->QPlatformScreen::screen());
+
xcb_window_t xcb_parent_id = platformScreen->root();
- if (parent()) {
- xcb_parent_id = static_cast<QXcbWindow *>(parent())->xcb_window();
- m_embedded = parent()->isForeignWindow();
+ if (QPlatformWindow::parent()) {
+ xcb_parent_id = static_cast<QXcbWindow *>(QPlatformWindow::parent())->xcb_window();
+ m_embedded = QPlatformWindow::parent()->isForeignWindow();
- QSurfaceFormat parentFormat = parent()->window()->requestedFormat();
+ QSurfaceFormat parentFormat = QPlatformWindow::parent()->window()->requestedFormat();
if (window()->surfaceType() != QSurface::OpenGLSurface && parentFormat.hasAlpha()) {
window()->setFormat(parentFormat);
}
@@ -292,16 +298,16 @@ void QXcbWindow::create()
qWarning() << "Failed to use requested visual id.";
}
- if (parent()) {
+ if (QPlatformWindow::parent()) {
// When using a Vulkan QWindow via QWidget::createWindowContainer() we
// must make sure the visuals are compatible. Now, the parent will be
// of RasterGLSurface which typically chooses a GLX/EGL compatible
// visual which may not be what the Vulkan window would choose.
// Therefore, take the parent's visual.
if (window()->surfaceType() == QSurface::VulkanSurface
- && parent()->window()->surfaceType() != QSurface::VulkanSurface)
+ && QPlatformWindow::parent()->window()->surfaceType() != QSurface::VulkanSurface)
{
- visual = platformScreen->visualForId(static_cast<QXcbWindow *>(parent())->visualId());
+ visual = platformScreen->visualForId(static_cast<QXcbWindow *>(QPlatformWindow::parent())->visualId());
}
}
@@ -358,20 +364,20 @@ void QXcbWindow::create()
xcb_atom_t properties[5];
int propertyCount = 0;
- properties[propertyCount++] = atom(QXcbAtom::WM_DELETE_WINDOW);
- properties[propertyCount++] = atom(QXcbAtom::WM_TAKE_FOCUS);
- properties[propertyCount++] = atom(QXcbAtom::_NET_WM_PING);
+ properties[propertyCount++] = atom(QXcbAtom::AtomWM_DELETE_WINDOW);
+ properties[propertyCount++] = atom(QXcbAtom::AtomWM_TAKE_FOCUS);
+ properties[propertyCount++] = atom(QXcbAtom::Atom_NET_WM_PING);
if (connection()->hasXSync())
- properties[propertyCount++] = atom(QXcbAtom::_NET_WM_SYNC_REQUEST);
+ properties[propertyCount++] = atom(QXcbAtom::Atom_NET_WM_SYNC_REQUEST);
if (window()->flags() & Qt::WindowContextHelpButtonHint)
- properties[propertyCount++] = atom(QXcbAtom::_NET_WM_CONTEXT_HELP);
+ properties[propertyCount++] = atom(QXcbAtom::Atom_NET_WM_CONTEXT_HELP);
xcb_change_property(xcb_connection(),
XCB_PROP_MODE_REPLACE,
m_window,
- atom(QXcbAtom::WM_PROTOCOLS),
+ atom(QXcbAtom::AtomWM_PROTOCOLS),
XCB_ATOM_ATOM,
32,
propertyCount,
@@ -382,10 +388,35 @@ void QXcbWindow::create()
const QByteArray wmClass = QXcbIntegration::instance()->wmClass();
if (!wmClass.isEmpty()) {
xcb_change_property(xcb_connection(), XCB_PROP_MODE_REPLACE,
- m_window, atom(QXcbAtom::WM_CLASS),
+ m_window, atom(QXcbAtom::AtomWM_CLASS),
XCB_ATOM_STRING, 8, wmClass.size(), wmClass.constData());
}
+ QString desktopFileName = QGuiApplication::desktopFileName();
+ if (QGuiApplication::desktopFileName().isEmpty()) {
+ QFileInfo fi = QFileInfo(QCoreApplication::instance()->applicationFilePath());
+ QStringList domainName =
+ QCoreApplication::instance()->organizationDomain().split(QLatin1Char('.'),
+ Qt::SkipEmptyParts);
+
+ if (domainName.isEmpty()) {
+ desktopFileName = fi.baseName();
+ } else {
+ for (int i = 0; i < domainName.size(); ++i)
+ desktopFileName.prepend(QLatin1Char('.')).prepend(domainName.at(i));
+ desktopFileName.append(fi.baseName());
+ }
+ }
+ if (!desktopFileName.isEmpty()) {
+ const QByteArray dfName = desktopFileName.toUtf8();
+ xcb_change_property(xcb_connection(), XCB_PROP_MODE_REPLACE,
+ m_window, atom(QXcbAtom::Atom_KDE_NET_WM_DESKTOP_FILE),
+ atom(QXcbAtom::AtomUTF8_STRING), 8, dfName.size(), dfName.constData());
+ xcb_change_property(xcb_connection(), XCB_PROP_MODE_REPLACE,
+ m_window, atom(QXcbAtom::Atom_GTK_APPLICATION_ID),
+ atom(QXcbAtom::AtomUTF8_STRING), 8, dfName.size(), dfName.constData());
+ }
+
if (connection()->hasXSync()) {
m_syncCounter = xcb_generate_id(xcb_connection());
xcb_sync_create_counter(xcb_connection(), m_syncCounter, m_syncValue);
@@ -393,7 +424,7 @@ void QXcbWindow::create()
xcb_change_property(xcb_connection(),
XCB_PROP_MODE_REPLACE,
m_window,
- atom(QXcbAtom::_NET_WM_SYNC_REQUEST_COUNTER),
+ atom(QXcbAtom::Atom_NET_WM_SYNC_REQUEST_COUNTER),
XCB_ATOM_CARDINAL,
32,
1,
@@ -403,13 +434,13 @@ void QXcbWindow::create()
// set the PID to let the WM kill the application if unresponsive
quint32 pid = getpid();
xcb_change_property(xcb_connection(), XCB_PROP_MODE_REPLACE, m_window,
- atom(QXcbAtom::_NET_WM_PID), XCB_ATOM_CARDINAL, 32,
+ atom(QXcbAtom::Atom_NET_WM_PID), XCB_ATOM_CARDINAL, 32,
1, &pid);
const QByteArray clientMachine = QSysInfo::machineHostName().toLocal8Bit();
if (!clientMachine.isEmpty()) {
xcb_change_property(xcb_connection(), XCB_PROP_MODE_REPLACE, m_window,
- atom(QXcbAtom::WM_CLIENT_MACHINE), XCB_ATOM_STRING, 8,
+ atom(QXcbAtom::AtomWM_CLIENT_MACHINE), XCB_ATOM_STRING, 8,
clientMachine.size(), clientMachine.constData());
}
@@ -423,14 +454,14 @@ void QXcbWindow::create()
xcb_window_t leader = connection()->clientLeader();
xcb_change_property(xcb_connection(), XCB_PROP_MODE_REPLACE, m_window,
- atom(QXcbAtom::WM_CLIENT_LEADER), XCB_ATOM_WINDOW, 32,
+ atom(QXcbAtom::AtomWM_CLIENT_LEADER), XCB_ATOM_WINDOW, 32,
1, &leader);
/* Add XEMBED info; this operation doesn't initiate the embedding. */
quint32 data[] = { XEMBED_VERSION, XEMBED_MAPPED };
xcb_change_property(xcb_connection(), XCB_PROP_MODE_REPLACE, m_window,
- atom(QXcbAtom::_XEMBED_INFO),
- atom(QXcbAtom::_XEMBED_INFO),
+ atom(QXcbAtom::Atom_XEMBED_INFO),
+ atom(QXcbAtom::Atom_XEMBED_INFO),
32, 2, (void *)data);
if (connection()->hasXInput2())
@@ -463,6 +494,17 @@ void QXcbWindow::create()
if (m_trayIconWindow)
m_embedded = requestSystemTrayWindowDock();
+
+ if (m_window != old_m_window) {
+ if (!m_wmTransientForChildren.isEmpty()) {
+ QList<QPointer<QXcbWindow>> transientChildren = m_wmTransientForChildren;
+ m_wmTransientForChildren.clear();
+ for (auto transientChild : transientChildren) {
+ if (transientChild)
+ transientChild->updateWmTransientFor();
+ }
+ }
+ }
}
QXcbWindow::~QXcbWindow()
@@ -470,6 +512,18 @@ QXcbWindow::~QXcbWindow()
destroy();
}
+QXcbForeignWindow::QXcbForeignWindow(QWindow *window, WId nativeHandle)
+ : QXcbWindow(window)
+{
+ m_window = nativeHandle;
+
+ // Reflect the foreign window's geometry as our own
+ if (auto geometry = Q_XCB_REPLY(xcb_get_geometry, xcb_connection(), m_window)) {
+ QRect nativeGeometry(geometry->x, geometry->y, geometry->width, geometry->height);
+ QPlatformWindow::setGeometry(nativeGeometry);
+ }
+}
+
QXcbForeignWindow::~QXcbForeignWindow()
{
// Clear window so that destroy() does not affect it
@@ -487,12 +541,14 @@ void QXcbWindow::destroy()
doFocusOut();
if (connection()->mouseGrabber() == this)
connection()->setMouseGrabber(nullptr);
+ if (connection()->mousePressWindow() == this)
+ connection()->setMousePressWindow(nullptr);
if (m_syncCounter && connection()->hasXSync())
xcb_sync_destroy_counter(xcb_connection(), m_syncCounter);
if (m_window) {
if (m_netWmUserTimeWindow) {
- xcb_delete_property(xcb_connection(), m_window, atom(QXcbAtom::_NET_WM_USER_TIME_WINDOW));
+ xcb_delete_property(xcb_connection(), m_window, atom(QXcbAtom::Atom_NET_WM_USER_TIME_WINDOW));
// Some window managers, like metacity, do XSelectInput on the _NET_WM_USER_TIME_WINDOW window,
// without trapping BadWindow (which crashes when the user time window is destroyed).
connection()->sync();
@@ -520,7 +576,7 @@ void QXcbWindow::setGeometry(const QRect &rect)
propagateSizeHints();
QXcbScreen *currentScreen = xcbScreen();
- QXcbScreen *newScreen = parent() ? parentScreen() : static_cast<QXcbScreen*>(screenForGeometry(rect));
+ QXcbScreen *newScreen = QPlatformWindow::parent() ? parentScreen() : static_cast<QXcbScreen*>(screenForGeometry(rect));
if (!newScreen)
newScreen = xcbScreen();
@@ -561,9 +617,9 @@ void QXcbWindow::setGeometry(const QRect &rect)
QMargins QXcbWindow::frameMargins() const
{
if (m_dirtyFrameMargins) {
- if (connection()->wmSupport()->isSupportedByWM(atom(QXcbAtom::_NET_FRAME_EXTENTS))) {
+ if (connection()->wmSupport()->isSupportedByWM(atom(QXcbAtom::Atom_NET_FRAME_EXTENTS))) {
auto reply = Q_XCB_REPLY(xcb_get_property, xcb_connection(), false, m_window,
- atom(QXcbAtom::_NET_FRAME_EXTENTS), XCB_ATOM_CARDINAL, 0, 4);
+ atom(QXcbAtom::Atom_NET_FRAME_EXTENTS), XCB_ATOM_CARDINAL, 0, 4);
if (reply && reply->type == XCB_ATOM_CARDINAL && reply->format == 32 && reply->value_len == 4) {
quint32 *data = (quint32 *)xcb_get_property_value(reply.get());
// _NET_FRAME_EXTENTS format is left, right, top, bottom
@@ -639,6 +695,44 @@ void QXcbWindow::setVisible(bool visible)
hide();
}
+void QXcbWindow::updateWmTransientFor()
+{
+ xcb_window_t transientXcbParent = XCB_NONE;
+ if (isTransient(window())) {
+ QWindow *tp = window()->transientParent();
+ if (tp && tp->handle()) {
+ QXcbWindow *handle = static_cast<QXcbWindow *>(tp->handle());
+ transientXcbParent = tp->handle()->winId();
+ if (transientXcbParent) {
+ handle->registerWmTransientForChild(this);
+ qCDebug(lcQpaXcbWindow) << Q_FUNC_INFO << static_cast<QPlatformWindow *>(handle)
+ << " registerWmTransientForChild " << static_cast<QPlatformWindow *>(this);
+ }
+ }
+ // Default to client leader if there is no transient parent, else modal dialogs can
+ // be hidden by their parents.
+ if (!transientXcbParent)
+ transientXcbParent = connection()->clientLeader();
+ if (transientXcbParent) { // ICCCM 4.1.2.6
+ xcb_change_property(xcb_connection(), XCB_PROP_MODE_REPLACE, m_window,
+ XCB_ATOM_WM_TRANSIENT_FOR, XCB_ATOM_WINDOW, 32,
+ 1, &transientXcbParent);
+ qCDebug(lcQpaXcbWindow, "0x%x added XCB_ATOM_WM_TRANSIENT_FOR 0x%x", m_window, transientXcbParent);
+ }
+ }
+ if (!transientXcbParent)
+ xcb_delete_property(xcb_connection(), m_window, XCB_ATOM_WM_TRANSIENT_FOR);
+}
+
+void QXcbWindow::registerWmTransientForChild(QXcbWindow *child)
+{
+ if (!child)
+ return;
+
+ if (!m_wmTransientForChildren.contains(child))
+ m_wmTransientForChildren.append(child);
+}
+
void QXcbWindow::show()
{
if (window()->isTopLevel()) {
@@ -652,23 +746,7 @@ void QXcbWindow::show()
propagateSizeHints();
// update WM_TRANSIENT_FOR
- xcb_window_t transientXcbParent = 0;
- if (isTransient(window())) {
- const QWindow *tp = window()->transientParent();
- if (tp && tp->handle())
- transientXcbParent = tp->handle()->winId();
- // Default to client leader if there is no transient parent, else modal dialogs can
- // be hidden by their parents.
- if (!transientXcbParent)
- transientXcbParent = connection()->clientLeader();
- if (transientXcbParent) { // ICCCM 4.1.2.6
- xcb_change_property(xcb_connection(), XCB_PROP_MODE_REPLACE, m_window,
- XCB_ATOM_WM_TRANSIENT_FOR, XCB_ATOM_WINDOW, 32,
- 1, &transientXcbParent);
- }
- }
- if (!transientXcbParent)
- xcb_delete_property(xcb_connection(), m_window, XCB_ATOM_WM_TRANSIENT_FOR);
+ updateWmTransientFor();
// update _NET_WM_STATE
setNetWmStateOnUnmappedWindow();
@@ -779,7 +857,7 @@ void QXcbWindow::doFocusIn()
return;
QWindow *w = static_cast<QWindowPrivate *>(QObjectPrivate::get(window()))->eventReceiver();
connection()->setFocusWindow(w);
- QWindowSystemInterface::handleWindowActivated(w, Qt::ActiveWindowFocusReason);
+ QWindowSystemInterface::handleFocusWindowChanged(w, Qt::ActiveWindowFocusReason);
}
void QXcbWindow::doFocusOut()
@@ -822,29 +900,29 @@ QXcbWindow::NetWmStates QXcbWindow::netWmStates()
NetWmStates result;
auto reply = Q_XCB_REPLY_UNCHECKED(xcb_get_property, xcb_connection(),
- 0, m_window, atom(QXcbAtom::_NET_WM_STATE),
+ 0, m_window, atom(QXcbAtom::Atom_NET_WM_STATE),
XCB_ATOM_ATOM, 0, 1024);
if (reply && reply->format == 32 && reply->type == XCB_ATOM_ATOM) {
const xcb_atom_t *states = static_cast<const xcb_atom_t *>(xcb_get_property_value(reply.get()));
const xcb_atom_t *statesEnd = states + reply->length;
- if (statesEnd != std::find(states, statesEnd, atom(QXcbAtom::_NET_WM_STATE_ABOVE)))
+ if (statesEnd != std::find(states, statesEnd, atom(QXcbAtom::Atom_NET_WM_STATE_ABOVE)))
result |= NetWmStateAbove;
- if (statesEnd != std::find(states, statesEnd, atom(QXcbAtom::_NET_WM_STATE_BELOW)))
+ if (statesEnd != std::find(states, statesEnd, atom(QXcbAtom::Atom_NET_WM_STATE_BELOW)))
result |= NetWmStateBelow;
- if (statesEnd != std::find(states, statesEnd, atom(QXcbAtom::_NET_WM_STATE_FULLSCREEN)))
+ if (statesEnd != std::find(states, statesEnd, atom(QXcbAtom::Atom_NET_WM_STATE_FULLSCREEN)))
result |= NetWmStateFullScreen;
- if (statesEnd != std::find(states, statesEnd, atom(QXcbAtom::_NET_WM_STATE_MAXIMIZED_HORZ)))
+ if (statesEnd != std::find(states, statesEnd, atom(QXcbAtom::Atom_NET_WM_STATE_MAXIMIZED_HORZ)))
result |= NetWmStateMaximizedHorz;
- if (statesEnd != std::find(states, statesEnd, atom(QXcbAtom::_NET_WM_STATE_MAXIMIZED_VERT)))
+ if (statesEnd != std::find(states, statesEnd, atom(QXcbAtom::Atom_NET_WM_STATE_MAXIMIZED_VERT)))
result |= NetWmStateMaximizedVert;
- if (statesEnd != std::find(states, statesEnd, atom(QXcbAtom::_NET_WM_STATE_MODAL)))
+ if (statesEnd != std::find(states, statesEnd, atom(QXcbAtom::Atom_NET_WM_STATE_MODAL)))
result |= NetWmStateModal;
- if (statesEnd != std::find(states, statesEnd, atom(QXcbAtom::_NET_WM_STATE_STAYS_ON_TOP)))
+ if (statesEnd != std::find(states, statesEnd, atom(QXcbAtom::Atom_NET_WM_STATE_STAYS_ON_TOP)))
result |= NetWmStateStaysOnTop;
- if (statesEnd != std::find(states, statesEnd, atom(QXcbAtom::_NET_WM_STATE_DEMANDS_ATTENTION)))
+ if (statesEnd != std::find(states, statesEnd, atom(QXcbAtom::Atom_NET_WM_STATE_DEMANDS_ATTENTION)))
result |= NetWmStateDemandsAttention;
- if (statesEnd != std::find(states, statesEnd, atom(QXcbAtom::_NET_WM_STATE_HIDDEN)))
+ if (statesEnd != std::find(states, statesEnd, atom(QXcbAtom::Atom_NET_WM_STATE_HIDDEN)))
result |= NetWmStateHidden;
} else {
qCDebug(lcQpaXcb, "getting net wm state (%x), empty\n", m_window);
@@ -959,13 +1037,13 @@ void QXcbWindow::setMotifWmHints(Qt::WindowFlags flags)
xcb_change_property(xcb_connection(),
XCB_PROP_MODE_REPLACE,
m_window,
- atom(QXcbAtom::_MOTIF_WM_HINTS),
- atom(QXcbAtom::_MOTIF_WM_HINTS),
+ atom(QXcbAtom::Atom_MOTIF_WM_HINTS),
+ atom(QXcbAtom::Atom_MOTIF_WM_HINTS),
32,
5,
&mwmhints);
} else {
- xcb_delete_property(xcb_connection(), m_window, atom(QXcbAtom::_MOTIF_WM_HINTS));
+ xcb_delete_property(xcb_connection(), m_window, atom(QXcbAtom::Atom_MOTIF_WM_HINTS));
}
}
@@ -977,7 +1055,7 @@ void QXcbWindow::setNetWmState(bool set, xcb_atom_t one, xcb_atom_t two)
event.format = 32;
event.sequence = 0;
event.window = m_window;
- event.type = atom(QXcbAtom::_NET_WM_STATE);
+ event.type = atom(QXcbAtom::Atom_NET_WM_STATE);
event.data.data32[0] = set ? 1 : 0;
event.data.data32[1] = one;
event.data.data32[2] = two;
@@ -993,26 +1071,26 @@ void QXcbWindow::setNetWmState(Qt::WindowStates state)
{
if ((m_windowState ^ state) & Qt::WindowMaximized) {
setNetWmState(state & Qt::WindowMaximized,
- atom(QXcbAtom::_NET_WM_STATE_MAXIMIZED_HORZ),
- atom(QXcbAtom::_NET_WM_STATE_MAXIMIZED_VERT));
+ atom(QXcbAtom::Atom_NET_WM_STATE_MAXIMIZED_HORZ),
+ atom(QXcbAtom::Atom_NET_WM_STATE_MAXIMIZED_VERT));
}
if ((m_windowState ^ state) & Qt::WindowFullScreen)
- setNetWmState(state & Qt::WindowFullScreen, atom(QXcbAtom::_NET_WM_STATE_FULLSCREEN));
+ setNetWmState(state & Qt::WindowFullScreen, atom(QXcbAtom::Atom_NET_WM_STATE_FULLSCREEN));
}
void QXcbWindow::setNetWmState(Qt::WindowFlags flags)
{
setNetWmState(flags & Qt::WindowStaysOnTopHint,
- atom(QXcbAtom::_NET_WM_STATE_ABOVE),
- atom(QXcbAtom::_NET_WM_STATE_STAYS_ON_TOP));
- setNetWmState(flags & Qt::WindowStaysOnBottomHint, atom(QXcbAtom::_NET_WM_STATE_BELOW));
+ atom(QXcbAtom::Atom_NET_WM_STATE_ABOVE),
+ atom(QXcbAtom::Atom_NET_WM_STATE_STAYS_ON_TOP));
+ setNetWmState(flags & Qt::WindowStaysOnBottomHint, atom(QXcbAtom::Atom_NET_WM_STATE_BELOW));
}
void QXcbWindow::setNetWmStateOnUnmappedWindow()
{
if (Q_UNLIKELY(m_mapped))
- qCWarning(lcQpaXcb()) << "internal error: " << Q_FUNC_INFO << "called on mapped window";
+ qCDebug(lcQpaXcb()) << "internal info: " << Q_FUNC_INFO << "called on mapped window";
NetWmStates states;
const Qt::WindowFlags flags = window()->flags();
@@ -1047,7 +1125,7 @@ void QXcbWindow::setNetWmStateOnUnmappedWindow()
QList<xcb_atom_t> atoms;
auto reply = Q_XCB_REPLY_UNCHECKED(xcb_get_property, xcb_connection(),
- 0, m_window, atom(QXcbAtom::_NET_WM_STATE),
+ 0, m_window, atom(QXcbAtom::Atom_NET_WM_STATE),
XCB_ATOM_ATOM, 0, 1024);
if (reply && reply->format == 32 && reply->type == XCB_ATOM_ATOM && reply->value_len > 0) {
const xcb_atom_t *data = static_cast<const xcb_atom_t *>(xcb_get_property_value(reply.get()));
@@ -1055,31 +1133,31 @@ void QXcbWindow::setNetWmStateOnUnmappedWindow()
memcpy((void *)&atoms.first(), (void *)data, reply->value_len * sizeof(xcb_atom_t));
}
- if (states & NetWmStateAbove && !atoms.contains(atom(QXcbAtom::_NET_WM_STATE_ABOVE)))
- atoms.push_back(atom(QXcbAtom::_NET_WM_STATE_ABOVE));
- if (states & NetWmStateBelow && !atoms.contains(atom(QXcbAtom::_NET_WM_STATE_BELOW)))
- atoms.push_back(atom(QXcbAtom::_NET_WM_STATE_BELOW));
- if (states & NetWmStateHidden && !atoms.contains(atom(QXcbAtom::_NET_WM_STATE_HIDDEN)))
- atoms.push_back(atom(QXcbAtom::_NET_WM_STATE_HIDDEN));
- if (states & NetWmStateFullScreen && !atoms.contains(atom(QXcbAtom::_NET_WM_STATE_FULLSCREEN)))
- atoms.push_back(atom(QXcbAtom::_NET_WM_STATE_FULLSCREEN));
- if (states & NetWmStateMaximizedHorz && !atoms.contains(atom(QXcbAtom::_NET_WM_STATE_MAXIMIZED_HORZ)))
- atoms.push_back(atom(QXcbAtom::_NET_WM_STATE_MAXIMIZED_HORZ));
- if (states & NetWmStateMaximizedVert && !atoms.contains(atom(QXcbAtom::_NET_WM_STATE_MAXIMIZED_VERT)))
- atoms.push_back(atom(QXcbAtom::_NET_WM_STATE_MAXIMIZED_VERT));
- if (states & NetWmStateModal && !atoms.contains(atom(QXcbAtom::_NET_WM_STATE_MODAL)))
- atoms.push_back(atom(QXcbAtom::_NET_WM_STATE_MODAL));
- if (states & NetWmStateStaysOnTop && !atoms.contains(atom(QXcbAtom::_NET_WM_STATE_STAYS_ON_TOP)))
- atoms.push_back(atom(QXcbAtom::_NET_WM_STATE_STAYS_ON_TOP));
- if (states & NetWmStateDemandsAttention && !atoms.contains(atom(QXcbAtom::_NET_WM_STATE_DEMANDS_ATTENTION)))
- atoms.push_back(atom(QXcbAtom::_NET_WM_STATE_DEMANDS_ATTENTION));
+ if (states & NetWmStateAbove && !atoms.contains(atom(QXcbAtom::Atom_NET_WM_STATE_ABOVE)))
+ atoms.push_back(atom(QXcbAtom::Atom_NET_WM_STATE_ABOVE));
+ if (states & NetWmStateBelow && !atoms.contains(atom(QXcbAtom::Atom_NET_WM_STATE_BELOW)))
+ atoms.push_back(atom(QXcbAtom::Atom_NET_WM_STATE_BELOW));
+ if (states & NetWmStateHidden && !atoms.contains(atom(QXcbAtom::Atom_NET_WM_STATE_HIDDEN)))
+ atoms.push_back(atom(QXcbAtom::Atom_NET_WM_STATE_HIDDEN));
+ if (states & NetWmStateFullScreen && !atoms.contains(atom(QXcbAtom::Atom_NET_WM_STATE_FULLSCREEN)))
+ atoms.push_back(atom(QXcbAtom::Atom_NET_WM_STATE_FULLSCREEN));
+ if (states & NetWmStateMaximizedHorz && !atoms.contains(atom(QXcbAtom::Atom_NET_WM_STATE_MAXIMIZED_HORZ)))
+ atoms.push_back(atom(QXcbAtom::Atom_NET_WM_STATE_MAXIMIZED_HORZ));
+ if (states & NetWmStateMaximizedVert && !atoms.contains(atom(QXcbAtom::Atom_NET_WM_STATE_MAXIMIZED_VERT)))
+ atoms.push_back(atom(QXcbAtom::Atom_NET_WM_STATE_MAXIMIZED_VERT));
+ if (states & NetWmStateModal && !atoms.contains(atom(QXcbAtom::Atom_NET_WM_STATE_MODAL)))
+ atoms.push_back(atom(QXcbAtom::Atom_NET_WM_STATE_MODAL));
+ if (states & NetWmStateStaysOnTop && !atoms.contains(atom(QXcbAtom::Atom_NET_WM_STATE_STAYS_ON_TOP)))
+ atoms.push_back(atom(QXcbAtom::Atom_NET_WM_STATE_STAYS_ON_TOP));
+ if (states & NetWmStateDemandsAttention && !atoms.contains(atom(QXcbAtom::Atom_NET_WM_STATE_DEMANDS_ATTENTION)))
+ atoms.push_back(atom(QXcbAtom::Atom_NET_WM_STATE_DEMANDS_ATTENTION));
if (atoms.isEmpty()) {
- xcb_delete_property(xcb_connection(), m_window, atom(QXcbAtom::_NET_WM_STATE));
+ xcb_delete_property(xcb_connection(), m_window, atom(QXcbAtom::Atom_NET_WM_STATE));
} else {
xcb_change_property(xcb_connection(), XCB_PROP_MODE_REPLACE, m_window,
- atom(QXcbAtom::_NET_WM_STATE), XCB_ATOM_ATOM, 32,
- atoms.count(), atoms.constData());
+ atom(QXcbAtom::Atom_NET_WM_STATE), XCB_ATOM_ATOM, 32,
+ atoms.size(), atoms.constData());
}
xcb_flush(xcb_connection());
}
@@ -1089,18 +1167,21 @@ void QXcbWindow::setWindowState(Qt::WindowStates state)
if (state == m_windowState)
return;
+ Qt::WindowStates unsetState = m_windowState & ~state;
+ Qt::WindowStates newState = state & ~m_windowState;
+
// unset old state
- if (m_windowState & Qt::WindowMinimized)
+ if (unsetState & Qt::WindowMinimized)
xcb_map_window(xcb_connection(), m_window);
- if (m_windowState & Qt::WindowMaximized)
+ if (unsetState & Qt::WindowMaximized)
setNetWmState(false,
- atom(QXcbAtom::_NET_WM_STATE_MAXIMIZED_HORZ),
- atom(QXcbAtom::_NET_WM_STATE_MAXIMIZED_VERT));
- if (m_windowState & Qt::WindowFullScreen)
- setNetWmState(false, atom(QXcbAtom::_NET_WM_STATE_FULLSCREEN));
+ atom(QXcbAtom::Atom_NET_WM_STATE_MAXIMIZED_HORZ),
+ atom(QXcbAtom::Atom_NET_WM_STATE_MAXIMIZED_VERT));
+ if (unsetState & Qt::WindowFullScreen)
+ setNetWmState(false, atom(QXcbAtom::Atom_NET_WM_STATE_FULLSCREEN));
// set new state
- if (state & Qt::WindowMinimized) {
+ if (newState & Qt::WindowMinimized) {
{
xcb_client_message_event_t event;
@@ -1108,7 +1189,7 @@ void QXcbWindow::setWindowState(Qt::WindowStates state)
event.format = 32;
event.sequence = 0;
event.window = m_window;
- event.type = atom(QXcbAtom::WM_CHANGE_STATE);
+ event.type = atom(QXcbAtom::AtomWM_CHANGE_STATE);
event.data.data32[0] = XCB_ICCCM_WM_STATE_ICONIC;
event.data.data32[1] = 0;
event.data.data32[2] = 0;
@@ -1121,13 +1202,8 @@ void QXcbWindow::setWindowState(Qt::WindowStates state)
}
m_minimized = true;
}
- if (state & Qt::WindowMaximized)
- setNetWmState(true,
- atom(QXcbAtom::_NET_WM_STATE_MAXIMIZED_HORZ),
- atom(QXcbAtom::_NET_WM_STATE_MAXIMIZED_VERT));
- if (state & Qt::WindowFullScreen)
- setNetWmState(true, atom(QXcbAtom::_NET_WM_STATE_FULLSCREEN));
+ // set Maximized && FullScreen state if need
setNetWmState(state);
xcb_get_property_cookie_t cookie = xcb_icccm_get_wm_hints_unchecked(xcb_connection(), m_window);
@@ -1153,7 +1229,7 @@ void QXcbWindow::updateNetWmUserTime(xcb_timestamp_t timestamp)
if (timestamp != 0)
connection()->setNetWmUserTime(timestamp);
- const bool isSupportedByWM = connection()->wmSupport()->isSupportedByWM(atom(QXcbAtom::_NET_WM_USER_TIME_WINDOW));
+ const bool isSupportedByWM = connection()->wmSupport()->isSupportedByWM(atom(QXcbAtom::Atom_NET_WM_USER_TIME_WINDOW));
if (m_netWmUserTimeWindow || isSupportedByWM) {
if (!m_netWmUserTimeWindow) {
m_netWmUserTimeWindow = xcb_generate_id(xcb_connection());
@@ -1168,9 +1244,9 @@ void QXcbWindow::updateNetWmUserTime(xcb_timestamp_t timestamp)
0, // value mask
nullptr); // value list
wid = m_netWmUserTimeWindow;
- xcb_change_property(xcb_connection(), XCB_PROP_MODE_REPLACE, m_window, atom(QXcbAtom::_NET_WM_USER_TIME_WINDOW),
+ xcb_change_property(xcb_connection(), XCB_PROP_MODE_REPLACE, m_window, atom(QXcbAtom::Atom_NET_WM_USER_TIME_WINDOW),
XCB_ATOM_WINDOW, 32, 1, &m_netWmUserTimeWindow);
- xcb_delete_property(xcb_connection(), m_window, atom(QXcbAtom::_NET_WM_USER_TIME));
+ xcb_delete_property(xcb_connection(), m_window, atom(QXcbAtom::Atom_NET_WM_USER_TIME));
QXcbWindow::setWindowTitle(connection(), m_netWmUserTimeWindow,
QStringLiteral("Qt NET_WM User Time Window"));
@@ -1178,14 +1254,14 @@ void QXcbWindow::updateNetWmUserTime(xcb_timestamp_t timestamp)
} else if (!isSupportedByWM) {
// WM no longer supports it, then we should remove the
// _NET_WM_USER_TIME_WINDOW atom.
- xcb_delete_property(xcb_connection(), m_window, atom(QXcbAtom::_NET_WM_USER_TIME_WINDOW));
+ xcb_delete_property(xcb_connection(), m_window, atom(QXcbAtom::Atom_NET_WM_USER_TIME_WINDOW));
xcb_destroy_window(xcb_connection(), m_netWmUserTimeWindow);
m_netWmUserTimeWindow = XCB_NONE;
} else {
wid = m_netWmUserTimeWindow;
}
}
- xcb_change_property(xcb_connection(), XCB_PROP_MODE_REPLACE, wid, atom(QXcbAtom::_NET_WM_USER_TIME),
+ xcb_change_property(xcb_connection(), XCB_PROP_MODE_REPLACE, wid, atom(QXcbAtom::Atom_NET_WM_USER_TIME),
XCB_ATOM_CARDINAL, 32, 1, &timestamp);
}
@@ -1260,10 +1336,10 @@ void QXcbWindow::setWindowIconText(const QString &title)
xcb_change_property(xcb_connection(),
XCB_PROP_MODE_REPLACE,
m_window,
- atom(QXcbAtom::_NET_WM_ICON_NAME),
- atom(QXcbAtom::UTF8_STRING),
+ atom(QXcbAtom::Atom_NET_WM_ICON_NAME),
+ atom(QXcbAtom::AtomUTF8_STRING),
8,
- ba.length(),
+ ba.size(),
ba.constData());
}
@@ -1295,23 +1371,24 @@ void QXcbWindow::setWindowIcon(const QIcon &icon)
if (!icon_data.isEmpty()) {
// Ignore icon exceeding maximum xcb request length
- if (icon_data.size() > xcb_get_maximum_request_length(xcb_connection())) {
- qWarning("Ignoring window icon: Size %llu exceeds maximum xcb request length %u.",
- icon_data.size(), xcb_get_maximum_request_length(xcb_connection()));
+ if (quint64(icon_data.size()) > quint64(xcb_get_maximum_request_length(xcb_connection()))) {
+ qWarning() << "Ignoring window icon" << icon_data.size()
+ << "exceeds maximum xcb request length"
+ << xcb_get_maximum_request_length(xcb_connection());
return;
}
xcb_change_property(xcb_connection(),
XCB_PROP_MODE_REPLACE,
m_window,
- atom(QXcbAtom::_NET_WM_ICON),
- atom(QXcbAtom::CARDINAL),
+ atom(QXcbAtom::Atom_NET_WM_ICON),
+ atom(QXcbAtom::AtomCARDINAL),
32,
icon_data.size(),
(unsigned char *) icon_data.data());
} else {
xcb_delete_property(xcb_connection(),
m_window,
- atom(QXcbAtom::_NET_WM_ICON));
+ atom(QXcbAtom::Atom_NET_WM_ICON));
}
}
@@ -1368,7 +1445,8 @@ void QXcbWindow::propagateSizeHints()
qMin(XCOORD_MAX, maximumSize.height()));
if (sizeIncrement.width() > 0 || sizeIncrement.height() > 0) {
- xcb_icccm_size_hints_set_base_size(&hints, baseSize.width(), baseSize.height());
+ if (!baseSize.isNull() && baseSize.isValid())
+ xcb_icccm_size_hints_set_base_size(&hints, baseSize.width(), baseSize.height());
xcb_icccm_size_hints_set_resize_inc(&hints, sizeIncrement.width(), sizeIncrement.height());
}
@@ -1386,29 +1464,37 @@ void QXcbWindow::requestActivateWindow()
return;
}
- if (!m_mapped) {
- m_deferredActivation = true;
- return;
+ {
+ QMutexLocker locker(&m_mappedMutex);
+ if (!m_mapped) {
+ m_deferredActivation = true;
+ return;
+ }
+ m_deferredActivation = false;
}
- m_deferredActivation = false;
updateNetWmUserTime(connection()->time());
QWindow *focusWindow = QGuiApplication::focusWindow();
+ xcb_window_t current = XCB_NONE;
+ if (focusWindow) {
+ if (QPlatformWindow *pw = focusWindow->handle())
+ current = pw->winId();
+ }
if (window()->isTopLevel()
&& !(window()->flags() & Qt::X11BypassWindowManagerHint)
&& (!focusWindow || !window()->isAncestorOf(focusWindow))
- && connection()->wmSupport()->isSupportedByWM(atom(QXcbAtom::_NET_ACTIVE_WINDOW))) {
+ && connection()->wmSupport()->isSupportedByWM(atom(QXcbAtom::Atom_NET_ACTIVE_WINDOW))) {
xcb_client_message_event_t event;
event.response_type = XCB_CLIENT_MESSAGE;
event.format = 32;
event.sequence = 0;
event.window = m_window;
- event.type = atom(QXcbAtom::_NET_ACTIVE_WINDOW);
+ event.type = atom(QXcbAtom::Atom_NET_ACTIVE_WINDOW);
event.data.data32[0] = 1;
event.data.data32[1] = connection()->time();
- event.data.data32[2] = focusWindow ? focusWindow->winId() : XCB_NONE;
+ event.data.data32[2] = current;
event.data.data32[3] = 0;
event.data.data32[4] = 0;
@@ -1432,7 +1518,7 @@ QXcbWindow::WindowTypes QXcbWindow::wmWindowTypes() const
WindowTypes result;
auto reply = Q_XCB_REPLY_UNCHECKED(xcb_get_property, xcb_connection(),
- 0, m_window, atom(QXcbAtom::_NET_WM_WINDOW_TYPE),
+ 0, m_window, atom(QXcbAtom::Atom_NET_WM_WINDOW_TYPE),
XCB_ATOM_ATOM, 0, 1024);
if (reply && reply->format == 32 && reply->type == XCB_ATOM_ATOM) {
const xcb_atom_t *types = static_cast<const xcb_atom_t *>(xcb_get_property_value(reply.get()));
@@ -1440,49 +1526,49 @@ QXcbWindow::WindowTypes QXcbWindow::wmWindowTypes() const
for (; types != types_end; types++) {
QXcbAtom::Atom type = connection()->qatom(*types);
switch (type) {
- case QXcbAtom::_NET_WM_WINDOW_TYPE_NORMAL:
+ case QXcbAtom::Atom_NET_WM_WINDOW_TYPE_NORMAL:
result |= WindowType::Normal;
break;
- case QXcbAtom::_NET_WM_WINDOW_TYPE_DESKTOP:
+ case QXcbAtom::Atom_NET_WM_WINDOW_TYPE_DESKTOP:
result |= WindowType::Desktop;
break;
- case QXcbAtom::_NET_WM_WINDOW_TYPE_DOCK:
+ case QXcbAtom::Atom_NET_WM_WINDOW_TYPE_DOCK:
result |= WindowType::Dock;
break;
- case QXcbAtom::_NET_WM_WINDOW_TYPE_TOOLBAR:
+ case QXcbAtom::Atom_NET_WM_WINDOW_TYPE_TOOLBAR:
result |= WindowType::Toolbar;
break;
- case QXcbAtom::_NET_WM_WINDOW_TYPE_MENU:
+ case QXcbAtom::Atom_NET_WM_WINDOW_TYPE_MENU:
result |= WindowType::Menu;
break;
- case QXcbAtom::_NET_WM_WINDOW_TYPE_UTILITY:
+ case QXcbAtom::Atom_NET_WM_WINDOW_TYPE_UTILITY:
result |= WindowType::Utility;
break;
- case QXcbAtom::_NET_WM_WINDOW_TYPE_SPLASH:
+ case QXcbAtom::Atom_NET_WM_WINDOW_TYPE_SPLASH:
result |= WindowType::Splash;
break;
- case QXcbAtom::_NET_WM_WINDOW_TYPE_DIALOG:
+ case QXcbAtom::Atom_NET_WM_WINDOW_TYPE_DIALOG:
result |= WindowType::Dialog;
break;
- case QXcbAtom::_NET_WM_WINDOW_TYPE_DROPDOWN_MENU:
+ case QXcbAtom::Atom_NET_WM_WINDOW_TYPE_DROPDOWN_MENU:
result |= WindowType::DropDownMenu;
break;
- case QXcbAtom::_NET_WM_WINDOW_TYPE_POPUP_MENU:
+ case QXcbAtom::Atom_NET_WM_WINDOW_TYPE_POPUP_MENU:
result |= WindowType::PopupMenu;
break;
- case QXcbAtom::_NET_WM_WINDOW_TYPE_TOOLTIP:
+ case QXcbAtom::Atom_NET_WM_WINDOW_TYPE_TOOLTIP:
result |= WindowType::Tooltip;
break;
- case QXcbAtom::_NET_WM_WINDOW_TYPE_NOTIFICATION:
+ case QXcbAtom::Atom_NET_WM_WINDOW_TYPE_NOTIFICATION:
result |= WindowType::Notification;
break;
- case QXcbAtom::_NET_WM_WINDOW_TYPE_COMBO:
+ case QXcbAtom::Atom_NET_WM_WINDOW_TYPE_COMBO:
result |= WindowType::Combo;
break;
- case QXcbAtom::_NET_WM_WINDOW_TYPE_DND:
+ case QXcbAtom::Atom_NET_WM_WINDOW_TYPE_DND:
result |= WindowType::Dnd;
break;
- case QXcbAtom::_KDE_NET_WM_WINDOW_TYPE_OVERRIDE:
+ case QXcbAtom::Atom_KDE_NET_WM_WINDOW_TYPE_OVERRIDE:
result |= WindowType::KdeOverride;
break;
default:
@@ -1499,41 +1585,41 @@ void QXcbWindow::setWmWindowType(WindowTypes types, Qt::WindowFlags flags)
// manual selection 1 (these are never set by Qt and take precedence)
if (types & WindowType::Normal)
- atoms.append(atom(QXcbAtom::_NET_WM_WINDOW_TYPE_NORMAL));
+ atoms.append(atom(QXcbAtom::Atom_NET_WM_WINDOW_TYPE_NORMAL));
if (types & WindowType::Desktop)
- atoms.append(atom(QXcbAtom::_NET_WM_WINDOW_TYPE_DESKTOP));
+ atoms.append(atom(QXcbAtom::Atom_NET_WM_WINDOW_TYPE_DESKTOP));
if (types & WindowType::Dock)
- atoms.append(atom(QXcbAtom::_NET_WM_WINDOW_TYPE_DOCK));
+ atoms.append(atom(QXcbAtom::Atom_NET_WM_WINDOW_TYPE_DOCK));
if (types & WindowType::Notification)
- atoms.append(atom(QXcbAtom::_NET_WM_WINDOW_TYPE_NOTIFICATION));
+ atoms.append(atom(QXcbAtom::Atom_NET_WM_WINDOW_TYPE_NOTIFICATION));
// manual selection 2 (Qt uses these during auto selection);
if (types & WindowType::Utility)
- atoms.append(atom(QXcbAtom::_NET_WM_WINDOW_TYPE_UTILITY));
+ atoms.append(atom(QXcbAtom::Atom_NET_WM_WINDOW_TYPE_UTILITY));
if (types & WindowType::Splash)
- atoms.append(atom(QXcbAtom::_NET_WM_WINDOW_TYPE_SPLASH));
+ atoms.append(atom(QXcbAtom::Atom_NET_WM_WINDOW_TYPE_SPLASH));
if (types & WindowType::Dialog)
- atoms.append(atom(QXcbAtom::_NET_WM_WINDOW_TYPE_DIALOG));
+ atoms.append(atom(QXcbAtom::Atom_NET_WM_WINDOW_TYPE_DIALOG));
if (types & WindowType::Tooltip)
- atoms.append(atom(QXcbAtom::_NET_WM_WINDOW_TYPE_TOOLTIP));
+ atoms.append(atom(QXcbAtom::Atom_NET_WM_WINDOW_TYPE_TOOLTIP));
if (types & WindowType::KdeOverride)
- atoms.append(atom(QXcbAtom::_KDE_NET_WM_WINDOW_TYPE_OVERRIDE));
+ atoms.append(atom(QXcbAtom::Atom_KDE_NET_WM_WINDOW_TYPE_OVERRIDE));
// manual selection 3 (these can be set by Qt, but don't have a
// corresponding Qt::WindowType). note that order of the *MENU
// atoms is important
if (types & WindowType::Menu)
- atoms.append(atom(QXcbAtom::_NET_WM_WINDOW_TYPE_MENU));
+ atoms.append(atom(QXcbAtom::Atom_NET_WM_WINDOW_TYPE_MENU));
if (types & WindowType::DropDownMenu)
- atoms.append(atom(QXcbAtom::_NET_WM_WINDOW_TYPE_DROPDOWN_MENU));
+ atoms.append(atom(QXcbAtom::Atom_NET_WM_WINDOW_TYPE_DROPDOWN_MENU));
if (types & WindowType::PopupMenu)
- atoms.append(atom(QXcbAtom::_NET_WM_WINDOW_TYPE_POPUP_MENU));
+ atoms.append(atom(QXcbAtom::Atom_NET_WM_WINDOW_TYPE_POPUP_MENU));
if (types & WindowType::Toolbar)
- atoms.append(atom(QXcbAtom::_NET_WM_WINDOW_TYPE_TOOLBAR));
+ atoms.append(atom(QXcbAtom::Atom_NET_WM_WINDOW_TYPE_TOOLBAR));
if (types & WindowType::Combo)
- atoms.append(atom(QXcbAtom::_NET_WM_WINDOW_TYPE_COMBO));
+ atoms.append(atom(QXcbAtom::Atom_NET_WM_WINDOW_TYPE_COMBO));
if (types & WindowType::Dnd)
- atoms.append(atom(QXcbAtom::_NET_WM_WINDOW_TYPE_DND));
+ atoms.append(atom(QXcbAtom::Atom_NET_WM_WINDOW_TYPE_DND));
// automatic selection
Qt::WindowType type = static_cast<Qt::WindowType>(int(flags & Qt::WindowType_Mask));
@@ -1541,20 +1627,20 @@ void QXcbWindow::setWmWindowType(WindowTypes types, Qt::WindowFlags flags)
case Qt::Dialog:
case Qt::Sheet:
if (!(types & WindowType::Dialog))
- atoms.append(atom(QXcbAtom::_NET_WM_WINDOW_TYPE_DIALOG));
+ atoms.append(atom(QXcbAtom::Atom_NET_WM_WINDOW_TYPE_DIALOG));
break;
case Qt::Tool:
case Qt::Drawer:
if (!(types & WindowType::Utility))
- atoms.append(atom(QXcbAtom::_NET_WM_WINDOW_TYPE_UTILITY));
+ atoms.append(atom(QXcbAtom::Atom_NET_WM_WINDOW_TYPE_UTILITY));
break;
case Qt::ToolTip:
if (!(types & WindowType::Tooltip))
- atoms.append(atom(QXcbAtom::_NET_WM_WINDOW_TYPE_TOOLTIP));
+ atoms.append(atom(QXcbAtom::Atom_NET_WM_WINDOW_TYPE_TOOLTIP));
break;
case Qt::SplashScreen:
if (!(types & WindowType::Splash))
- atoms.append(atom(QXcbAtom::_NET_WM_WINDOW_TYPE_SPLASH));
+ atoms.append(atom(QXcbAtom::Atom_NET_WM_WINDOW_TYPE_SPLASH));
break;
default:
break;
@@ -1562,20 +1648,20 @@ void QXcbWindow::setWmWindowType(WindowTypes types, Qt::WindowFlags flags)
if ((flags & Qt::FramelessWindowHint) && !(types & WindowType::KdeOverride)) {
// override netwm type - quick and easy for KDE noborder
- atoms.append(atom(QXcbAtom::_KDE_NET_WM_WINDOW_TYPE_OVERRIDE));
+ atoms.append(atom(QXcbAtom::Atom_KDE_NET_WM_WINDOW_TYPE_OVERRIDE));
}
- if (atoms.size() == 1 && atoms.first() == atom(QXcbAtom::_NET_WM_WINDOW_TYPE_NORMAL))
+ if (atoms.size() == 1 && atoms.first() == atom(QXcbAtom::Atom_NET_WM_WINDOW_TYPE_NORMAL))
atoms.clear();
else
- atoms.append(atom(QXcbAtom::_NET_WM_WINDOW_TYPE_NORMAL));
+ atoms.append(atom(QXcbAtom::Atom_NET_WM_WINDOW_TYPE_NORMAL));
if (atoms.isEmpty()) {
- xcb_delete_property(xcb_connection(), m_window, atom(QXcbAtom::_NET_WM_WINDOW_TYPE));
+ xcb_delete_property(xcb_connection(), m_window, atom(QXcbAtom::Atom_NET_WM_WINDOW_TYPE));
} else {
xcb_change_property(xcb_connection(), XCB_PROP_MODE_REPLACE, m_window,
- atom(QXcbAtom::_NET_WM_WINDOW_TYPE), XCB_ATOM_ATOM, 32,
- atoms.count(), atoms.constData());
+ atom(QXcbAtom::Atom_NET_WM_WINDOW_TYPE), XCB_ATOM_ATOM, 32,
+ atoms.size(), atoms.constData());
}
xcb_flush(xcb_connection());
}
@@ -1584,7 +1670,7 @@ void QXcbWindow::setWindowRole(const QString &role)
{
QByteArray roleData = role.toLatin1();
xcb_change_property(xcb_connection(), XCB_PROP_MODE_REPLACE, m_window,
- atom(QXcbAtom::WM_WINDOW_ROLE), XCB_ATOM_STRING, 8,
+ atom(QXcbAtom::AtomWM_WINDOW_ROLE), XCB_ATOM_STRING, 8,
roleData.size(), roleData.constData());
}
@@ -1643,15 +1729,15 @@ void QXcbWindow::handleClientMessageEvent(const xcb_client_message_event_t *even
if (event->format != 32)
return;
- if (event->type == atom(QXcbAtom::WM_PROTOCOLS)) {
+ if (event->type == atom(QXcbAtom::AtomWM_PROTOCOLS)) {
xcb_atom_t protocolAtom = event->data.data32[0];
- if (protocolAtom == atom(QXcbAtom::WM_DELETE_WINDOW)) {
+ if (protocolAtom == atom(QXcbAtom::AtomWM_DELETE_WINDOW)) {
QWindowSystemInterface::handleCloseEvent(window());
- } else if (protocolAtom == atom(QXcbAtom::WM_TAKE_FOCUS)) {
+ } else if (protocolAtom == atom(QXcbAtom::AtomWM_TAKE_FOCUS)) {
connection()->setTime(event->data.data32[1]);
relayFocusToModalWindow();
return;
- } else if (protocolAtom == atom(QXcbAtom::_NET_WM_PING)) {
+ } else if (protocolAtom == atom(QXcbAtom::Atom_NET_WM_PING)) {
if (event->window == xcbScreen()->root())
return;
@@ -1664,14 +1750,14 @@ void QXcbWindow::handleClientMessageEvent(const xcb_client_message_event_t *even
XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT,
(const char *)&reply);
xcb_flush(xcb_connection());
- } else if (protocolAtom == atom(QXcbAtom::_NET_WM_SYNC_REQUEST)) {
+ } else if (protocolAtom == atom(QXcbAtom::Atom_NET_WM_SYNC_REQUEST)) {
connection()->setTime(event->data.data32[1]);
m_syncValue.lo = event->data.data32[2];
m_syncValue.hi = event->data.data32[3];
if (connection()->hasXSync())
m_syncState = SyncReceived;
#ifndef QT_NO_WHATSTHIS
- } else if (protocolAtom == atom(QXcbAtom::_NET_WM_CONTEXT_HELP)) {
+ } else if (protocolAtom == atom(QXcbAtom::Atom_NET_WM_CONTEXT_HELP)) {
QWindowSystemInterface::handleEnterWhatsThisEvent();
#endif
} else {
@@ -1679,29 +1765,29 @@ void QXcbWindow::handleClientMessageEvent(const xcb_client_message_event_t *even
connection()->atomName(protocolAtom).constData());
}
#if QT_CONFIG(draganddrop)
- } else if (event->type == atom(QXcbAtom::XdndEnter)) {
+ } else if (event->type == atom(QXcbAtom::AtomXdndEnter)) {
connection()->drag()->handleEnter(this, event);
- } else if (event->type == atom(QXcbAtom::XdndPosition)) {
+ } else if (event->type == atom(QXcbAtom::AtomXdndPosition)) {
connection()->drag()->handlePosition(this, event);
- } else if (event->type == atom(QXcbAtom::XdndLeave)) {
+ } else if (event->type == atom(QXcbAtom::AtomXdndLeave)) {
connection()->drag()->handleLeave(this, event);
- } else if (event->type == atom(QXcbAtom::XdndDrop)) {
+ } else if (event->type == atom(QXcbAtom::AtomXdndDrop)) {
connection()->drag()->handleDrop(this, event);
#endif
- } else if (event->type == atom(QXcbAtom::_XEMBED)) {
+ } else if (event->type == atom(QXcbAtom::Atom_XEMBED)) {
handleXEmbedMessage(event);
- } else if (event->type == atom(QXcbAtom::_NET_ACTIVE_WINDOW)) {
+ } else if (event->type == atom(QXcbAtom::Atom_NET_ACTIVE_WINDOW)) {
doFocusIn();
- } else if (event->type == atom(QXcbAtom::MANAGER)
- || event->type == atom(QXcbAtom::_NET_WM_STATE)
- || event->type == atom(QXcbAtom::WM_CHANGE_STATE)) {
+ } else if (event->type == atom(QXcbAtom::AtomMANAGER)
+ || event->type == atom(QXcbAtom::Atom_NET_WM_STATE)
+ || event->type == atom(QXcbAtom::AtomWM_CHANGE_STATE)) {
// Ignore _NET_WM_STATE, MANAGER which are relate to tray icons
// and other messages.
- } else if (event->type == atom(QXcbAtom::_COMPIZ_DECOR_PENDING)
- || event->type == atom(QXcbAtom::_COMPIZ_DECOR_REQUEST)
- || event->type == atom(QXcbAtom::_COMPIZ_DECOR_DELETE_PIXMAP)
- || event->type == atom(QXcbAtom::_COMPIZ_TOOLKIT_ACTION)
- || event->type == atom(QXcbAtom::_GTK_LOAD_ICONTHEMES)) {
+ } else if (event->type == atom(QXcbAtom::Atom_COMPIZ_DECOR_PENDING)
+ || event->type == atom(QXcbAtom::Atom_COMPIZ_DECOR_REQUEST)
+ || event->type == atom(QXcbAtom::Atom_COMPIZ_DECOR_DELETE_PIXMAP)
+ || event->type == atom(QXcbAtom::Atom_COMPIZ_TOOLKIT_ACTION)
+ || event->type == atom(QXcbAtom::Atom_GTK_LOAD_ICONTHEMES)) {
//silence the _COMPIZ and _GTK messages for now
} else {
qCWarning(lcQpaXcb) << "Unhandled client message: " << connection()->atomName(event->type);
@@ -1712,7 +1798,7 @@ void QXcbWindow::handleConfigureNotifyEvent(const xcb_configure_notify_event_t *
{
bool fromSendEvent = (event->response_type & 0x80);
QPoint pos(event->x, event->y);
- if (!parent() && !fromSendEvent) {
+ if (!QPlatformWindow::parent() && !fromSendEvent) {
// Do not trust the position, query it instead.
auto reply = Q_XCB_REPLY(xcb_translate_coordinates, xcb_connection(),
xcb_window(), xcbScreen()->root(), 0, 0);
@@ -1723,7 +1809,7 @@ void QXcbWindow::handleConfigureNotifyEvent(const xcb_configure_notify_event_t *
}
const QRect actualGeometry = QRect(pos, QSize(event->width, event->height));
- QPlatformScreen *newScreen = parent() ? parent()->screen() : screenForGeometry(actualGeometry);
+ QPlatformScreen *newScreen = QPlatformWindow::parent() ? QPlatformWindow::parent()->screen() : screenForGeometry(actualGeometry);
if (!newScreen)
return;
@@ -1799,8 +1885,11 @@ QPoint QXcbWindow::mapFromGlobal(const QPoint &pos) const
void QXcbWindow::handleMapNotifyEvent(const xcb_map_notify_event_t *event)
{
if (event->window == m_window) {
+ m_mappedMutex.lock();
m_mapped = true;
- if (m_deferredActivation)
+ const bool deferredActivation = m_deferredActivation;
+ m_mappedMutex.unlock();
+ if (deferredActivation)
requestActivateWindow();
QWindowSystemInterface::handleExposeEvent(window(), QRect(QPoint(), geometry().size()));
@@ -1810,7 +1899,9 @@ void QXcbWindow::handleMapNotifyEvent(const xcb_map_notify_event_t *event)
void QXcbWindow::handleUnmapNotifyEvent(const xcb_unmap_notify_event_t *event)
{
if (event->window == m_window) {
+ m_mappedMutex.lock();
m_mapped = false;
+ m_mappedMutex.unlock();
QWindowSystemInterface::handleExposeEvent(window(), QRegion());
}
}
@@ -1833,7 +1924,7 @@ void QXcbWindow::handleButtonPressEvent(int event_x, int event_y, int root_x, in
if (m_embedded && !m_trayIconWindow) {
if (window() != QGuiApplication::focusWindow()) {
- const QXcbWindow *container = static_cast<const QXcbWindow *>(parent());
+ const QXcbWindow *container = static_cast<const QXcbWindow *>(QPlatformWindow::parent());
Q_ASSERT(container != nullptr);
sendXEmbedMessage(container->xcb_window(), XEMBED_REQUEST_FOCUS);
@@ -1877,8 +1968,10 @@ void QXcbWindow::handleButtonReleaseEvent(int event_x, int event_y, int root_x,
return;
}
- if (connection()->buttonState() == Qt::NoButton)
+ if (connection()->buttonState() == Qt::NoButton) {
connection()->setMousePressWindow(nullptr);
+ m_ignorePressedWindowOnMouseLeave = false;
+ }
handleMouseEvent(timestamp, local, global, modifiers, type, source);
}
@@ -1898,10 +1991,10 @@ static inline bool doCheckUnGrabAncestor(QXcbConnection *conn)
return true;
}
-static bool ignoreLeaveEvent(quint8 mode, quint8 detail, QXcbConnection *conn = nullptr)
+static bool ignoreLeaveEvent(quint8 mode, quint8 detail, QXcbConnection *conn)
{
return ((doCheckUnGrabAncestor(conn)
- && mode == XCB_NOTIFY_MODE_GRAB && detail == XCB_NOTIFY_DETAIL_ANCESTOR)
+ && mode == XCB_NOTIFY_MODE_GRAB && detail == XCB_NOTIFY_DETAIL_ANCESTOR)
|| (mode == XCB_NOTIFY_MODE_UNGRAB && detail == XCB_NOTIFY_DETAIL_INFERIOR)
|| detail == XCB_NOTIFY_DETAIL_VIRTUAL
|| detail == XCB_NOTIFY_DETAIL_NONLINEAR_VIRTUAL);
@@ -1921,14 +2014,18 @@ void QXcbWindow::handleEnterNotifyEvent(int event_x, int event_y, int root_x, in
{
connection()->setTime(timestamp);
- const QPoint global = QPoint(root_x, root_y);
-
- if (ignoreEnterEvent(mode, detail, connection()) || connection()->mousePressWindow())
+ if (ignoreEnterEvent(mode, detail, connection())
+ || (connection()->mousePressWindow() && !m_ignorePressedWindowOnMouseLeave)) {
return;
+ }
// Updates scroll valuators, as user might have done some scrolling outside our X client.
connection()->xi2UpdateScrollingDevices();
+ if (mode == XCB_NOTIFY_MODE_UNGRAB && connection()->queryMouseButtons() != Qt::NoButton)
+ m_ignorePressedWindowOnMouseLeave = true;
+
+ const QPoint global = QPoint(root_x, root_y);
const QPoint local(event_x, event_y);
QWindowSystemInterface::handleEnterEvent(window(), local, global);
}
@@ -1938,8 +2035,11 @@ void QXcbWindow::handleLeaveNotifyEvent(int root_x, int root_y,
{
connection()->setTime(timestamp);
- if (ignoreLeaveEvent(mode, detail, connection()) || connection()->mousePressWindow())
+ QXcbWindow *mousePressWindow = connection()->mousePressWindow();
+ if (ignoreLeaveEvent(mode, detail, connection())
+ || (mousePressWindow && !m_ignorePressedWindowOnMouseLeave)) {
return;
+ }
// check if enter event is buffered
auto event = connection()->eventQueue()->peek([](xcb_generic_event_t *event, int type) {
@@ -1957,6 +2057,8 @@ void QXcbWindow::handleLeaveNotifyEvent(int root_x, int root_y,
QWindowSystemInterface::handleEnterLeaveEvent(enterWindow->window(), window(), local, global);
} else {
QWindowSystemInterface::handleLeaveEvent(window());
+ if (m_ignorePressedWindowOnMouseLeave)
+ connection()->setMousePressWindow(nullptr);
}
free(enter);
@@ -2133,17 +2235,17 @@ void QXcbWindow::handlePropertyNotifyEvent(const xcb_property_notify_event_t *ev
const bool propertyDeleted = event->state == XCB_PROPERTY_DELETE;
- if (event->atom == atom(QXcbAtom::_NET_WM_STATE) || event->atom == atom(QXcbAtom::WM_STATE)) {
+ if (event->atom == atom(QXcbAtom::Atom_NET_WM_STATE) || event->atom == atom(QXcbAtom::AtomWM_STATE)) {
if (propertyDeleted)
return;
Qt::WindowStates newState = Qt::WindowNoState;
- if (event->atom == atom(QXcbAtom::WM_STATE)) { // WM_STATE: Quick check for 'Minimize'.
+ if (event->atom == atom(QXcbAtom::AtomWM_STATE)) { // WM_STATE: Quick check for 'Minimize'.
auto reply = Q_XCB_REPLY(xcb_get_property, xcb_connection(),
- 0, m_window, atom(QXcbAtom::WM_STATE),
+ 0, m_window, atom(QXcbAtom::AtomWM_STATE),
XCB_ATOM_ANY, 0, 1024);
- if (reply && reply->format == 32 && reply->type == atom(QXcbAtom::WM_STATE)) {
+ if (reply && reply->format == 32 && reply->type == atom(QXcbAtom::AtomWM_STATE)) {
const quint32 *data = (const quint32 *)xcb_get_property_value(reply.get());
if (reply->length != 0)
m_minimized = (data[0] == XCB_ICCCM_WM_STATE_ICONIC
@@ -2173,7 +2275,7 @@ void QXcbWindow::handlePropertyNotifyEvent(const xcb_property_notify_event_t *ev
connection()->setMouseGrabber(nullptr);
}
return;
- } else if (event->atom == atom(QXcbAtom::_NET_FRAME_EXTENTS)) {
+ } else if (event->atom == atom(QXcbAtom::Atom_NET_FRAME_EXTENTS)) {
m_dirtyFrameMargins = true;
}
}
@@ -2282,7 +2384,7 @@ bool QXcbWindow::windowEvent(QEvent *event)
case Qt::BacktabFocusReason:
{
const QXcbWindow *container =
- static_cast<const QXcbWindow *>(parent());
+ static_cast<const QXcbWindow *>(QPlatformWindow::parent());
sendXEmbedMessage(container->xcb_window(),
focusEvent->reason() == Qt::TabFocusReason ?
XEMBED_FOCUS_NEXT : XEMBED_FOCUS_PREV);
@@ -2312,7 +2414,7 @@ bool QXcbWindow::startSystemMove()
bool QXcbWindow::startSystemMoveResize(const QPoint &pos, int edges)
{
- const xcb_atom_t moveResize = connection()->atom(QXcbAtom::_NET_WM_MOVERESIZE);
+ const xcb_atom_t moveResize = connection()->atom(QXcbAtom::Atom_NET_WM_MOVERESIZE);
if (!connection()->wmSupport()->isSupportedByWM(moveResize))
return false;
@@ -2360,7 +2462,7 @@ static uint qtEdgesToXcbMoveResizeDirection(Qt::Edges edges)
void QXcbWindow::doStartSystemMoveResize(const QPoint &globalPos, int edges)
{
qCDebug(lcQpaXInputDevices) << "triggered system move or resize via sending _NET_WM_MOVERESIZE client message";
- const xcb_atom_t moveResize = connection()->atom(QXcbAtom::_NET_WM_MOVERESIZE);
+ const xcb_atom_t moveResize = connection()->atom(QXcbAtom::Atom_NET_WM_MOVERESIZE);
xcb_client_message_event_t xev;
xev.response_type = XCB_CLIENT_MESSAGE;
xev.type = moveResize;
@@ -2393,7 +2495,7 @@ void QXcbWindow::sendXEmbedMessage(xcb_window_t window, quint32 message,
event.format = 32;
event.sequence = 0;
event.window = window;
- event.type = atom(QXcbAtom::_XEMBED);
+ event.type = atom(QXcbAtom::Atom_XEMBED);
event.data.data32[0] = connection()->time();
event.data.data32[1] = message;
event.data.data32[2] = detail;
@@ -2402,15 +2504,15 @@ void QXcbWindow::sendXEmbedMessage(xcb_window_t window, quint32 message,
xcb_send_event(xcb_connection(), false, window, XCB_EVENT_MASK_NO_EVENT, (const char *)&event);
}
-static bool activeWindowChangeQueued(const QWindow *window)
+static bool focusWindowChangeQueued(const QWindow *window)
{
/* Check from window system event queue if the next queued activation
* targets a window other than @window.
*/
- QWindowSystemInterfacePrivate::ActivatedWindowEvent *systemEvent =
- static_cast<QWindowSystemInterfacePrivate::ActivatedWindowEvent *>
- (QWindowSystemInterfacePrivate::peekWindowSystemEvent(QWindowSystemInterfacePrivate::ActivatedWindow));
- return systemEvent && systemEvent->activated != window;
+ QWindowSystemInterfacePrivate::FocusWindowEvent *systemEvent =
+ static_cast<QWindowSystemInterfacePrivate::FocusWindowEvent *>
+ (QWindowSystemInterfacePrivate::peekWindowSystemEvent(QWindowSystemInterfacePrivate::FocusWindow));
+ return systemEvent && systemEvent->focused != window;
}
void QXcbWindow::handleXEmbedMessage(const xcb_client_message_event_t *event)
@@ -2440,13 +2542,13 @@ void QXcbWindow::handleXEmbedMessage(const xcb_client_message_event_t *event)
break;
}
connection()->setFocusWindow(window());
- QWindowSystemInterface::handleWindowActivated(window(), reason);
+ QWindowSystemInterface::handleFocusWindowChanged(window(), reason);
break;
case XEMBED_FOCUS_OUT:
if (window() == QGuiApplication::focusWindow()
- && !activeWindowChangeQueued(window())) {
+ && !focusWindowChangeQueued(window())) {
connection()->setFocusWindow(nullptr);
- QWindowSystemInterface::handleWindowActivated(nullptr);
+ QWindowSystemInterface::handleFocusWindowChanged(nullptr);
}
break;
}
@@ -2472,7 +2574,7 @@ void QXcbWindow::setOpacity(qreal level)
xcb_change_property(xcb_connection(),
XCB_PROP_MODE_REPLACE,
m_window,
- atom(QXcbAtom::_NET_WM_WINDOW_OPACITY),
+ atom(QXcbAtom::Atom_NET_WM_WINDOW_OPACITY),
XCB_ATOM_CARDINAL,
32,
1,
@@ -2510,7 +2612,7 @@ void QXcbWindow::setAlertState(bool enabled)
m_alertState = enabled;
- setNetWmState(enabled, atom(QXcbAtom::_NET_WM_STATE_DEMANDS_ATTENTION));
+ setNetWmState(enabled, atom(QXcbAtom::Atom_NET_WM_STATE_DEMANDS_ATTENTION));
}
uint QXcbWindow::visualId() const
@@ -2544,10 +2646,10 @@ void QXcbWindow::setWindowTitle(const QXcbConnection *conn, xcb_window_t window,
xcb_change_property(conn->xcb_connection(),
XCB_PROP_MODE_REPLACE,
window,
- conn->atom(QXcbAtom::_NET_WM_NAME),
- conn->atom(QXcbAtom::UTF8_STRING),
+ conn->atom(QXcbAtom::Atom_NET_WM_NAME),
+ conn->atom(QXcbAtom::AtomUTF8_STRING),
8,
- ba.length(),
+ ba.size(),
ba.constData());
#if QT_CONFIG(xcb_xlib)
@@ -2561,9 +2663,9 @@ void QXcbWindow::setWindowTitle(const QXcbConnection *conn, xcb_window_t window,
QString QXcbWindow::windowTitle(const QXcbConnection *conn, xcb_window_t window)
{
- const xcb_atom_t utf8Atom = conn->atom(QXcbAtom::UTF8_STRING);
+ const xcb_atom_t utf8Atom = conn->atom(QXcbAtom::AtomUTF8_STRING);
auto reply = Q_XCB_REPLY_UNCHECKED(xcb_get_property, conn->xcb_connection(),
- false, window, conn->atom(QXcbAtom::_NET_WM_NAME),
+ false, window, conn->atom(QXcbAtom::Atom_NET_WM_NAME),
utf8Atom, 0, 1024);
if (reply && reply->format == 8 && reply->type == utf8Atom) {
const char *name = reinterpret_cast<const char *>(xcb_get_property_value(reply.get()));
@@ -2571,7 +2673,7 @@ QString QXcbWindow::windowTitle(const QXcbConnection *conn, xcb_window_t window)
}
reply = Q_XCB_REPLY_UNCHECKED(xcb_get_property, conn->xcb_connection(),
- false, window, conn->atom(QXcbAtom::WM_NAME),
+ false, window, conn->atom(QXcbAtom::AtomWM_NAME),
XCB_ATOM_STRING, 0, 1024);
if (reply && reply->format == 8 && reply->type == XCB_ATOM_STRING) {
const char *name = reinterpret_cast<const char *>(xcb_get_property_value(reply.get()));
diff --git a/src/plugins/platforms/xcb/qxcbwindow.h b/src/plugins/platforms/xcb/qxcbwindow.h
index 54a96a7a0a..0c047d569b 100644
--- a/src/plugins/platforms/xcb/qxcbwindow.h
+++ b/src/plugins/platforms/xcb/qxcbwindow.h
@@ -6,6 +6,8 @@
#include <qpa/qplatformwindow.h>
#include <qpa/qplatformwindow_p.h>
+#include <QtCore/QObject>
+#include <QtCore/QPointer>
#include <QtGui/QSurfaceFormat>
#include <QtGui/QImage>
@@ -20,9 +22,10 @@ class QXcbScreen;
class QXcbSyncWindowRequest;
class QIcon;
-class Q_XCB_EXPORT QXcbWindow : public QXcbObject, public QXcbWindowEventListener, public QPlatformWindow
+class Q_XCB_EXPORT QXcbWindow : public QObject, public QXcbObject, public QXcbWindowEventListener, public QPlatformWindow
, public QNativeInterface::Private::QXcbWindow
{
+ Q_OBJECT
public:
enum NetWmState {
NetWmStateAbove = 0x1,
@@ -118,6 +121,8 @@ public:
Qt::KeyboardModifiers modifiers, QEvent::Type type, Qt::MouseEventSource source);
void updateNetWmUserTime(xcb_timestamp_t timestamp);
+ void updateWmTransientFor();
+ void registerWmTransientForChild(QXcbWindow *);
WindowTypes wmWindowTypes() const;
void setWmWindowType(WindowTypes types, Qt::WindowFlags flags);
@@ -217,6 +222,7 @@ protected:
Qt::WindowStates m_windowState = Qt::WindowNoState;
+ QMutex m_mappedMutex;
bool m_mapped = false;
bool m_transparent = false;
bool m_deferredActivation = false;
@@ -224,6 +230,7 @@ protected:
bool m_alertState = false;
bool m_minimized = false;
bool m_trayIconWindow = false;
+ bool m_ignorePressedWindowOnMouseLeave = false;
xcb_window_t m_netWmUserTimeWindow = XCB_NONE;
QSurfaceFormat m_format;
@@ -253,13 +260,14 @@ protected:
qreal m_sizeHintsScaleFactor = 1.0;
RecreationReasons m_recreationReasons = RecreationNotNeeded;
+
+ QList<QPointer<QXcbWindow>> m_wmTransientForChildren;
};
class QXcbForeignWindow : public QXcbWindow
{
public:
- QXcbForeignWindow(QWindow *window, WId nativeHandle)
- : QXcbWindow(window) { m_window = nativeHandle; }
+ QXcbForeignWindow(QWindow *window, WId nativeHandle);
~QXcbForeignWindow();
bool isForeignWindow() const override { return true; }
diff --git a/src/plugins/platforms/xcb/qxcbwmsupport.cpp b/src/plugins/platforms/xcb/qxcbwmsupport.cpp
index 50e85c0aa5..0e3c470c89 100644
--- a/src/plugins/platforms/xcb/qxcbwmsupport.cpp
+++ b/src/plugins/platforms/xcb/qxcbwmsupport.cpp
@@ -30,7 +30,7 @@ void QXcbWMSupport::updateNetWMAtoms()
int offset = 0;
int remaining = 0;
do {
- auto reply = Q_XCB_REPLY(xcb_get_property, xcb_connection(), false, root, atom(QXcbAtom::_NET_SUPPORTED), XCB_ATOM_ATOM, offset, 1024);
+ auto reply = Q_XCB_REPLY(xcb_get_property, xcb_connection(), false, root, atom(QXcbAtom::Atom_NET_SUPPORTED), XCB_ATOM_ATOM, offset, 1024);
if (!reply)
break;
@@ -54,7 +54,7 @@ void QXcbWMSupport::updateVirtualRoots()
{
net_virtual_roots.clear();
- if (!isSupportedByWM(atom(QXcbAtom::_NET_VIRTUAL_ROOTS)))
+ if (!isSupportedByWM(atom(QXcbAtom::Atom_NET_VIRTUAL_ROOTS)))
return;
xcb_window_t root = connection()->primaryScreen()->root();
@@ -62,7 +62,7 @@ void QXcbWMSupport::updateVirtualRoots()
int remaining = 0;
do {
auto reply = Q_XCB_REPLY(xcb_get_property, xcb_connection(),
- false, root, atom(QXcbAtom::_NET_VIRTUAL_ROOTS), XCB_ATOM_WINDOW, offset, 1024);
+ false, root, atom(QXcbAtom::Atom_NET_VIRTUAL_ROOTS), XCB_ATOM_WINDOW, offset, 1024);
if (!reply)
break;
diff --git a/src/plugins/platforms/xcb/qxcbxsettings.cpp b/src/plugins/platforms/xcb/qxcbxsettings.cpp
index 62fd13e5b9..6b62864add 100644
--- a/src/plugins/platforms/xcb/qxcbxsettings.cpp
+++ b/src/plugins/platforms/xcb/qxcbxsettings.cpp
@@ -68,7 +68,7 @@ public:
int offset = 0;
QByteArray settings;
- xcb_atom_t _xsettings_atom = screen->connection()->atom(QXcbAtom::_XSETTINGS_SETTINGS);
+ xcb_atom_t _xsettings_atom = screen->connection()->atom(QXcbAtom::Atom_XSETTINGS_SETTINGS);
while (1) {
auto reply = Q_XCB_REPLY_UNCHECKED(xcb_get_property,
screen->xcb_connection(),
@@ -104,7 +104,7 @@ public:
void populateSettings(const QByteArray &xSettings)
{
- if (xSettings.length() < 12)
+ if (xSettings.size() < 12)
return;
char byteOrder = xSettings.at(0);
if (byteOrder != XCB_IMAGE_ORDER_LSB_FIRST && byteOrder != XCB_IMAGE_ORDER_MSB_FIRST) {
@@ -192,7 +192,7 @@ QXcbXSettings::QXcbXSettings(QXcbVirtualDesktop *screen)
auto atom_reply = Q_XCB_REPLY(xcb_intern_atom,
screen->xcb_connection(),
true,
- settings_atom_for_screen.length(),
+ settings_atom_for_screen.size(),
settings_atom_for_screen.constData());
if (!atom_reply)
return;
diff --git a/src/plugins/platformthemes/CMakeLists.txt b/src/plugins/platformthemes/CMakeLists.txt
index dd8e3bdf54..e5abcd1a11 100644
--- a/src/plugins/platformthemes/CMakeLists.txt
+++ b/src/plugins/platformthemes/CMakeLists.txt
@@ -1,7 +1,6 @@
# Copyright (C) 2022 The Qt Company Ltd.
-# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+# SPDX-License-Identifier: BSD-3-Clause
-# Generated from platformthemes.pro.
if(QT_FEATURE_dbus AND QT_FEATURE_mimetype AND QT_FEATURE_regularexpression AND UNIX AND NOT APPLE)
add_subdirectory(xdgdesktopportal)
diff --git a/src/plugins/platformthemes/gtk3/CMakeLists.txt b/src/plugins/platformthemes/gtk3/CMakeLists.txt
index 218d63b443..becfcccd35 100644
--- a/src/plugins/platformthemes/gtk3/CMakeLists.txt
+++ b/src/plugins/platformthemes/gtk3/CMakeLists.txt
@@ -1,12 +1,10 @@
# Copyright (C) 2022 The Qt Company Ltd.
-# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+# SPDX-License-Identifier: BSD-3-Clause
-# Generated from gtk3.pro.
-
-qt_find_package(GTK3) # special case
+qt_find_package(GTK3)
if(QT_FEATURE_xlib)
- qt_find_package(X11) # special case
+ qt_find_package(X11)
endif()
#####################################################################
@@ -22,9 +20,14 @@ qt_internal_add_plugin(QGtk3ThemePlugin
qgtk3dialoghelpers.cpp qgtk3dialoghelpers.h
qgtk3menu.cpp qgtk3menu.h
qgtk3theme.cpp qgtk3theme.h
+ qgtk3interface.cpp qgtk3interface_p.h
+ qgtk3storage.cpp qgtk3storage_p.h
+ qgtk3json.cpp qgtk3json_p.h
+ NO_PCH_SOURCES
+ qgtk3dialoghelpers.cpp # undef QT_NO_FOREACH
DEFINES
GDK_VERSION_MIN_REQUIRED=GDK_VERSION_3_6
- LIBRARIES # special case
+ LIBRARIES
PkgConfig::GTK3
Qt::Core
Qt::CorePrivate
@@ -34,8 +37,5 @@ qt_internal_add_plugin(QGtk3ThemePlugin
qt_internal_extend_target(QGtk3ThemePlugin CONDITION QT_FEATURE_xlib
LIBRARIES
- X11::X11 # special case
+ X11::X11
)
-
-#### Keys ignored in scope 1:.:.:gtk3.pro:<TRUE>:
-# PLUGIN_EXTENDS = "-"
diff --git a/src/plugins/platformthemes/gtk3/qgtk3dialoghelpers.cpp b/src/plugins/platformthemes/gtk3/qgtk3dialoghelpers.cpp
index 9826d1f4b1..08419ec7dc 100644
--- a/src/plugins/platformthemes/gtk3/qgtk3dialoghelpers.cpp
+++ b/src/plugins/platformthemes/gtk3/qgtk3dialoghelpers.cpp
@@ -1,6 +1,8 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+#undef QT_NO_FOREACH // this file contains unported legacy Q_FOREACH uses
+
#include "qgtk3dialoghelpers.h"
#include "qgtk3theme.h"
@@ -12,6 +14,8 @@
#include <qfileinfo.h>
#include <private/qguiapplication_p.h>
+#include <private/qgenericunixservices_p.h>
+#include <qpa/qplatformintegration.h>
#include <qpa/qplatformfontdatabase.h>
#undef signals
@@ -23,6 +27,10 @@
#include <gdk/gdkx.h>
#endif
+#ifdef GDK_WINDOWING_WAYLAND
+#include <gdk/gdkwayland.h>
+#endif
+
// The size of the preview we display for selected image files. We set height
// larger than width because generally there is more free space vertically
// than horizontally (setting the preview image will always expand the width of
@@ -35,12 +43,10 @@ QT_BEGIN_NAMESPACE
using namespace Qt::StringLiterals;
-class QGtk3Dialog : public QWindow
+class QGtk3Dialog
{
- Q_OBJECT
-
public:
- QGtk3Dialog(GtkWidget *gtkWidget);
+ QGtk3Dialog(GtkWidget *gtkWidget, QPlatformDialogHelper *helper);
~QGtk3Dialog();
GtkDialog *gtkDialog() const;
@@ -49,23 +55,20 @@ public:
bool show(Qt::WindowFlags flags, Qt::WindowModality modality, QWindow *parent);
void hide();
-Q_SIGNALS:
- void accept();
- void reject();
-
protected:
- static void onResponse(QGtk3Dialog *dialog, int response);
-
-private slots:
- void onParentWindowDestroyed();
+ static void onResponse(QPlatformDialogHelper *helper, int response);
private:
GtkWidget *gtkWidget;
+ QPlatformDialogHelper *helper;
+ Qt::WindowModality modality;
};
-QGtk3Dialog::QGtk3Dialog(GtkWidget *gtkWidget) : gtkWidget(gtkWidget)
+QGtk3Dialog::QGtk3Dialog(GtkWidget *gtkWidget, QPlatformDialogHelper *helper)
+ : gtkWidget(gtkWidget)
+ , helper(helper)
{
- g_signal_connect_swapped(G_OBJECT(gtkWidget), "response", G_CALLBACK(onResponse), this);
+ g_signal_connect_swapped(G_OBJECT(gtkWidget), "response", G_CALLBACK(onResponse), helper);
g_signal_connect(G_OBJECT(gtkWidget), "delete-event", G_CALLBACK(gtk_widget_hide_on_delete), NULL);
}
@@ -82,45 +85,52 @@ GtkDialog *QGtk3Dialog::gtkDialog() const
void QGtk3Dialog::exec()
{
- if (modality() == Qt::ApplicationModal) {
+ if (modality == Qt::ApplicationModal) {
// block input to the whole app, including other GTK dialogs
gtk_dialog_run(gtkDialog());
} else {
// block input to the window, allow input to other GTK dialogs
QEventLoop loop;
- connect(this, SIGNAL(accept()), &loop, SLOT(quit()));
- connect(this, SIGNAL(reject()), &loop, SLOT(quit()));
+ loop.connect(helper, SIGNAL(accept()), SLOT(quit()));
+ loop.connect(helper, SIGNAL(reject()), SLOT(quit()));
loop.exec();
}
}
bool QGtk3Dialog::show(Qt::WindowFlags flags, Qt::WindowModality modality, QWindow *parent)
{
- if (parent) {
- connect(parent, &QWindow::destroyed, this, &QGtk3Dialog::onParentWindowDestroyed,
- Qt::UniqueConnection);
- }
- setParent(parent);
- setFlags(flags);
- setModality(modality);
+ Q_UNUSED(flags);
+ this->modality = modality;
gtk_widget_realize(gtkWidget); // creates X window
GdkWindow *gdkWindow = gtk_widget_get_window(gtkWidget);
if (parent) {
+ if (false) {
+#if defined(GDK_WINDOWING_WAYLAND) && GTK_CHECK_VERSION(3, 22, 0)
+ } else if (GDK_IS_WAYLAND_WINDOW(gdkWindow)) {
+ const auto unixServices = dynamic_cast<QGenericUnixServices *>(
+ QGuiApplicationPrivate::platformIntegration()->services());
+ if (unixServices) {
+ const auto handle = unixServices->portalWindowIdentifier(parent);
+ if (handle.startsWith("wayland:"_L1)) {
+ auto handleBa = handle.sliced(8).toUtf8();
+ gdk_wayland_window_set_transient_for_exported(gdkWindow, handleBa.data());
+ }
+ }
+#endif
#if QT_CONFIG(xlib) && defined(GDK_WINDOWING_X11)
- if (GDK_IS_X11_WINDOW(gdkWindow)) {
+ } else if (GDK_IS_X11_WINDOW(gdkWindow)) {
GdkDisplay *gdkDisplay = gdk_window_get_display(gdkWindow);
XSetTransientForHint(gdk_x11_display_get_xdisplay(gdkDisplay),
gdk_x11_window_get_xid(gdkWindow),
parent->winId());
- }
#endif
+ }
}
if (modality != Qt::NonModal) {
gdk_window_set_modal_hint(gdkWindow, true);
- QGuiApplicationPrivate::showModalWindow(this);
}
gtk_widget_show(gtkWidget);
@@ -130,30 +140,20 @@ bool QGtk3Dialog::show(Qt::WindowFlags flags, Qt::WindowModality modality, QWind
void QGtk3Dialog::hide()
{
- QGuiApplicationPrivate::hideModalWindow(this);
gtk_widget_hide(gtkWidget);
}
-void QGtk3Dialog::onResponse(QGtk3Dialog *dialog, int response)
+void QGtk3Dialog::onResponse(QPlatformDialogHelper *helper, int response)
{
if (response == GTK_RESPONSE_OK)
- emit dialog->accept();
+ emit helper->accept();
else
- emit dialog->reject();
-}
-
-void QGtk3Dialog::onParentWindowDestroyed()
-{
- // The QGtk3*DialogHelper classes own this object. Make sure the parent doesn't delete it.
- setParent(nullptr);
+ emit helper->reject();
}
QGtk3ColorDialogHelper::QGtk3ColorDialogHelper()
{
- d.reset(new QGtk3Dialog(gtk_color_chooser_dialog_new("", nullptr)));
- connect(d.data(), SIGNAL(accept()), this, SLOT(onAccepted()));
- connect(d.data(), SIGNAL(reject()), this, SIGNAL(reject()));
-
+ d.reset(new QGtk3Dialog(gtk_color_chooser_dialog_new("", nullptr), this));
g_signal_connect_swapped(d->gtkDialog(), "notify::rgba", G_CALLBACK(onColorChanged), this);
}
@@ -198,11 +198,6 @@ QColor QGtk3ColorDialogHelper::currentColor() const
return QColor::fromRgbF(gdkColor.red, gdkColor.green, gdkColor.blue, gdkColor.alpha);
}
-void QGtk3ColorDialogHelper::onAccepted()
-{
- emit accept();
-}
-
void QGtk3ColorDialogHelper::onColorChanged(QGtk3ColorDialogHelper *dialog)
{
emit dialog->currentColorChanged(dialog->currentColor());
@@ -222,10 +217,7 @@ QGtk3FileDialogHelper::QGtk3FileDialogHelper()
GTK_FILE_CHOOSER_ACTION_OPEN,
qUtf8Printable(QGtk3Theme::defaultStandardButtonText(QPlatformDialogHelper::Cancel)), GTK_RESPONSE_CANCEL,
qUtf8Printable(QGtk3Theme::defaultStandardButtonText(QPlatformDialogHelper::Ok)), GTK_RESPONSE_OK,
- NULL)));
-
- connect(d.data(), SIGNAL(accept()), this, SLOT(onAccepted()));
- connect(d.data(), SIGNAL(reject()), this, SIGNAL(reject()));
+ NULL), this));
g_signal_connect(GTK_FILE_CHOOSER(d->gtkDialog()), "selection-changed", G_CALLBACK(onSelectionChanged), this);
g_signal_connect_swapped(GTK_FILE_CHOOSER(d->gtkDialog()), "current-folder-changed", G_CALLBACK(onCurrentFolderChanged), this);
@@ -348,11 +340,6 @@ QString QGtk3FileDialogHelper::selectedNameFilter() const
return _filterNames.value(gtkFilter);
}
-void QGtk3FileDialogHelper::onAccepted()
-{
- emit accept();
-}
-
void QGtk3FileDialogHelper::onSelectionChanged(GtkDialog *gtkDialog, QGtk3FileDialogHelper *helper)
{
QString selection;
@@ -508,10 +495,7 @@ void QGtk3FileDialogHelper::setNameFilters(const QStringList &filters)
QGtk3FontDialogHelper::QGtk3FontDialogHelper()
{
- d.reset(new QGtk3Dialog(gtk_font_chooser_dialog_new("", nullptr)));
- connect(d.data(), SIGNAL(accept()), this, SLOT(onAccepted()));
- connect(d.data(), SIGNAL(reject()), this, SIGNAL(reject()));
-
+ d.reset(new QGtk3Dialog(gtk_font_chooser_dialog_new("", nullptr), this));
g_signal_connect_swapped(d->gtkDialog(), "notify::font", G_CALLBACK(onFontChanged), this);
}
@@ -615,11 +599,6 @@ QFont QGtk3FontDialogHelper::currentFont() const
return font;
}
-void QGtk3FontDialogHelper::onAccepted()
-{
- emit accept();
-}
-
void QGtk3FontDialogHelper::onFontChanged(QGtk3FontDialogHelper *dialog)
{
emit dialog->currentFontChanged(dialog->currentFont());
@@ -636,5 +615,3 @@ void QGtk3FontDialogHelper::applyOptions()
QT_END_NAMESPACE
#include "moc_qgtk3dialoghelpers.cpp"
-
-#include "qgtk3dialoghelpers.moc"
diff --git a/src/plugins/platformthemes/gtk3/qgtk3dialoghelpers.h b/src/plugins/platformthemes/gtk3/qgtk3dialoghelpers.h
index e5c4c72539..89f48d8b01 100644
--- a/src/plugins/platformthemes/gtk3/qgtk3dialoghelpers.h
+++ b/src/plugins/platformthemes/gtk3/qgtk3dialoghelpers.h
@@ -35,9 +35,6 @@ public:
void setCurrentColor(const QColor &color) override;
QColor currentColor() const override;
-private Q_SLOTS:
- void onAccepted();
-
private:
static void onColorChanged(QGtk3ColorDialogHelper *helper);
void applyOptions();
@@ -66,9 +63,6 @@ public:
void selectNameFilter(const QString &filter) override;
QString selectedNameFilter() const override;
-private Q_SLOTS:
- void onAccepted();
-
private:
static void onSelectionChanged(GtkDialog *dialog, QGtk3FileDialogHelper *helper);
static void onCurrentFolderChanged(QGtk3FileDialogHelper *helper);
@@ -102,9 +96,6 @@ public:
void setCurrentFont(const QFont &font) override;
QFont currentFont() const override;
-private Q_SLOTS:
- void onAccepted();
-
private:
static void onFontChanged(QGtk3FontDialogHelper *helper);
void applyOptions();
diff --git a/src/plugins/platformthemes/gtk3/qgtk3interface.cpp b/src/plugins/platformthemes/gtk3/qgtk3interface.cpp
new file mode 100644
index 0000000000..a35e211fbf
--- /dev/null
+++ b/src/plugins/platformthemes/gtk3/qgtk3interface.cpp
@@ -0,0 +1,702 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+
+#include "qgtk3interface_p.h"
+#include "qgtk3storage_p.h"
+#include <QtCore/QMetaEnum>
+#include <QtCore/QFileInfo>
+#include <QtGui/QFontDatabase>
+
+QT_BEGIN_NAMESPACE
+Q_LOGGING_CATEGORY(lcQGtk3Interface, "qt.qpa.gtk");
+
+
+// Callback for gnome event loop has to be static
+static QGtk3Storage *m_storage = nullptr;
+
+QGtk3Interface::QGtk3Interface(QGtk3Storage *s)
+{
+ initColorMap();
+
+ if (!s) {
+ qCDebug(lcQGtk3Interface) << "QGtk3Interface instantiated without QGtk3Storage."
+ << "No reaction to runtime theme changes.";
+ return;
+ }
+
+ // Connect to the GTK settings changed signal
+ auto handleThemeChange = [] {
+ if (m_storage)
+ m_storage->handleThemeChange();
+ };
+
+ GtkSettings *settings = gtk_settings_get_default();
+ const gboolean success = g_signal_connect(settings, "notify::gtk-theme-name",
+ G_CALLBACK(handleThemeChange), nullptr);
+ if (success == FALSE) {
+ qCDebug(lcQGtk3Interface) << "Connection to theme change signal failed."
+ << "No reaction to runtime theme changes.";
+ } else {
+ m_storage = s;
+ }
+}
+
+QGtk3Interface::~QGtk3Interface()
+{
+ // Ignore theme changes when destructor is reached
+ m_storage = nullptr;
+
+ // QGtkWidgets have to be destroyed manually
+ for (auto v : cache)
+ gtk_widget_destroy(v.second);
+}
+
+/*!
+ \internal
+ \brief Converts a string into the GtkStateFlags enum.
+
+ Converts a string formatted GTK color \param state into an enum value.
+ Returns an integer corresponding to GtkStateFlags.
+ Returns -1 if \param state does not correspond to a valid enum key.
+ */
+int QGtk3Interface::toGtkState(const QString &state)
+{
+#define CASE(x) \
+ if (QLatin1String(QByteArray(state.toLatin1())) == #x ##_L1) \
+ return GTK_STATE_FLAG_ ##x
+
+#define CONVERT\
+ CASE(NORMAL);\
+ CASE(ACTIVE);\
+ CASE(PRELIGHT);\
+ CASE(SELECTED);\
+ CASE(INSENSITIVE);\
+ CASE(INCONSISTENT);\
+ CASE(FOCUSED);\
+ CASE(BACKDROP);\
+ CASE(DIR_LTR);\
+ CASE(DIR_RTL);\
+ CASE(LINK);\
+ CASE(VISITED);\
+ CASE(CHECKED);\
+ CASE(DROP_ACTIVE)
+
+ CONVERT;
+ return -1;
+#undef CASE
+}
+
+/*!
+ \internal
+ \brief Returns \param state converted into a string.
+ */
+const QLatin1String QGtk3Interface::fromGtkState(GtkStateFlags state)
+{
+#define CASE(x) case GTK_STATE_FLAG_ ##x: return QLatin1String(#x)
+ switch (state) {
+ CONVERT;
+ }
+ Q_UNREACHABLE();
+#undef CASE
+#undef CONVERT
+}
+
+/*!
+ \internal
+ \brief Populates the internal map used to find a GTK color's source and fallback generic color.
+ */
+void QGtk3Interface::initColorMap()
+{
+ #define SAVE(src, state, prop, def)\
+ {ColorKey({QGtkColorSource::src, GTK_STATE_FLAG_ ##state}), ColorValue({#prop ##_L1, QGtkColorDefault::def})}
+
+ gtkColorMap = ColorMap {
+ SAVE(Foreground, NORMAL, theme_fg_color, Foreground),
+ SAVE(Foreground, BACKDROP, theme_unfocused_selected_fg_color, Foreground),
+ SAVE(Foreground, INSENSITIVE, insensitive_fg_color, Foreground),
+ SAVE(Foreground, SELECTED, theme_selected_fg_color, Foreground),
+ SAVE(Foreground, ACTIVE, theme_unfocused_fg_color, Foreground),
+ SAVE(Text, NORMAL, theme_text_color, Foreground),
+ SAVE(Text, ACTIVE, theme_unfocused_text_color, Foreground),
+ SAVE(Base, NORMAL, theme_base_color, Background),
+ SAVE(Base, INSENSITIVE, insensitive_base_color, Background),
+ SAVE(Background, NORMAL, theme_bg_color, Background),
+ SAVE(Background, SELECTED, theme_selected_bg_color, Background),
+ SAVE(Background, INSENSITIVE, insensitive_bg_color, Background),
+ SAVE(Background, ACTIVE, theme_unfocused_bg_color, Background),
+ SAVE(Background, BACKDROP, theme_unfocused_selected_bg_color, Background),
+ SAVE(Border, NORMAL, borders, Border),
+ SAVE(Border, ACTIVE, unfocused_borders, Border)
+ };
+#undef SAVE
+
+ qCDebug(lcQGtk3Interface) << "Color map populated from defaults.";
+}
+
+/*!
+ \internal
+ \brief Returns a QImage corresponding to \param standardPixmap.
+
+ A QImage (not a QPixmap) is returned so it can be cached and re-scaled in case the pixmap is
+ requested multiple times with different resolutions.
+
+ \note Rather than defaulting to a QImage(), all QPlatformTheme::StandardPixmap enum values have
+ been mentioned explicitly.
+ That way they can be covered more easily in case additional icons are provided by GTK.
+ */
+QImage QGtk3Interface::standardPixmap(QPlatformTheme::StandardPixmap standardPixmap) const
+{
+ switch (standardPixmap) {
+ case QPlatformTheme::DialogDiscardButton:
+ return qt_gtk_get_icon(GTK_STOCK_DELETE);
+ case QPlatformTheme::DialogOkButton:
+ return qt_gtk_get_icon(GTK_STOCK_OK);
+ case QPlatformTheme::DialogCancelButton:
+ return qt_gtk_get_icon(GTK_STOCK_CANCEL);
+ case QPlatformTheme::DialogYesButton:
+ return qt_gtk_get_icon(GTK_STOCK_YES);
+ case QPlatformTheme::DialogNoButton:
+ return qt_gtk_get_icon(GTK_STOCK_NO);
+ case QPlatformTheme::DialogOpenButton:
+ return qt_gtk_get_icon(GTK_STOCK_OPEN);
+ case QPlatformTheme::DialogCloseButton:
+ return qt_gtk_get_icon(GTK_STOCK_CLOSE);
+ case QPlatformTheme::DialogApplyButton:
+ return qt_gtk_get_icon(GTK_STOCK_APPLY);
+ case QPlatformTheme::DialogSaveButton:
+ return qt_gtk_get_icon(GTK_STOCK_SAVE);
+ case QPlatformTheme::MessageBoxWarning:
+ return qt_gtk_get_icon(GTK_STOCK_DIALOG_WARNING);
+ case QPlatformTheme::MessageBoxQuestion:
+ return qt_gtk_get_icon(GTK_STOCK_DIALOG_QUESTION);
+ case QPlatformTheme::MessageBoxInformation:
+ return qt_gtk_get_icon(GTK_STOCK_DIALOG_INFO);
+ case QPlatformTheme::MessageBoxCritical:
+ return qt_gtk_get_icon(GTK_STOCK_DIALOG_ERROR);
+ case QPlatformTheme::CustomBase:
+ case QPlatformTheme::TitleBarMenuButton:
+ case QPlatformTheme::TitleBarMinButton:
+ case QPlatformTheme::TitleBarMaxButton:
+ case QPlatformTheme::TitleBarCloseButton:
+ case QPlatformTheme::TitleBarNormalButton:
+ case QPlatformTheme::TitleBarShadeButton:
+ case QPlatformTheme::TitleBarUnshadeButton:
+ case QPlatformTheme::TitleBarContextHelpButton:
+ case QPlatformTheme::DockWidgetCloseButton:
+ case QPlatformTheme::DesktopIcon:
+ case QPlatformTheme::TrashIcon:
+ case QPlatformTheme::ComputerIcon:
+ case QPlatformTheme::DriveFDIcon:
+ case QPlatformTheme::DriveHDIcon:
+ case QPlatformTheme::DriveCDIcon:
+ case QPlatformTheme::DriveDVDIcon:
+ case QPlatformTheme::DriveNetIcon:
+ case QPlatformTheme::DirOpenIcon:
+ case QPlatformTheme::DirClosedIcon:
+ case QPlatformTheme::DirLinkIcon:
+ case QPlatformTheme::DirLinkOpenIcon:
+ case QPlatformTheme::FileIcon:
+ case QPlatformTheme::FileLinkIcon:
+ case QPlatformTheme::ToolBarHorizontalExtensionButton:
+ case QPlatformTheme::ToolBarVerticalExtensionButton:
+ case QPlatformTheme::FileDialogStart:
+ case QPlatformTheme::FileDialogEnd:
+ case QPlatformTheme::FileDialogToParent:
+ case QPlatformTheme::FileDialogNewFolder:
+ case QPlatformTheme::FileDialogDetailedView:
+ case QPlatformTheme::FileDialogInfoView:
+ case QPlatformTheme::FileDialogContentsView:
+ case QPlatformTheme::FileDialogListView:
+ case QPlatformTheme::FileDialogBack:
+ case QPlatformTheme::DirIcon:
+ case QPlatformTheme::DialogHelpButton:
+ case QPlatformTheme::DialogResetButton:
+ case QPlatformTheme::ArrowUp:
+ case QPlatformTheme::ArrowDown:
+ case QPlatformTheme::ArrowLeft:
+ case QPlatformTheme::ArrowRight:
+ case QPlatformTheme::ArrowBack:
+ case QPlatformTheme::ArrowForward:
+ case QPlatformTheme::DirHomeIcon:
+ case QPlatformTheme::CommandLink:
+ case QPlatformTheme::VistaShield:
+ case QPlatformTheme::BrowserReload:
+ case QPlatformTheme::BrowserStop:
+ case QPlatformTheme::MediaPlay:
+ case QPlatformTheme::MediaStop:
+ case QPlatformTheme::MediaPause:
+ case QPlatformTheme::MediaSkipForward:
+ case QPlatformTheme::MediaSkipBackward:
+ case QPlatformTheme::MediaSeekForward:
+ case QPlatformTheme::MediaSeekBackward:
+ case QPlatformTheme::MediaVolume:
+ case QPlatformTheme::MediaVolumeMuted:
+ case QPlatformTheme::LineEditClearButton:
+ case QPlatformTheme::DialogYesToAllButton:
+ case QPlatformTheme::DialogNoToAllButton:
+ case QPlatformTheme::DialogSaveAllButton:
+ case QPlatformTheme::DialogAbortButton:
+ case QPlatformTheme::DialogRetryButton:
+ case QPlatformTheme::DialogIgnoreButton:
+ case QPlatformTheme::RestoreDefaultsButton:
+ case QPlatformTheme::TabCloseButton:
+ case QPlatformTheme::NStandardPixmap:
+ return QImage();
+ }
+ Q_UNREACHABLE();
+}
+
+/*!
+ \internal
+ \brief Returns a QImage for a given GTK \param iconName.
+ */
+QImage QGtk3Interface::qt_gtk_get_icon(const char* iconName) const
+{
+ GtkIconSet* iconSet = gtk_icon_factory_lookup_default (iconName);
+ GdkPixbuf* icon = gtk_icon_set_render_icon_pixbuf(iconSet, context(), GTK_ICON_SIZE_DIALOG);
+ return qt_convert_gdk_pixbuf(icon);
+}
+
+/*!
+ \internal
+ \brief Returns a QImage converted from the GDK pixel buffer \param buf.
+
+ The ability to convert GdkPixbuf to QImage relies on the following assumptions:
+ \list
+ \li QImage uses uchar as a data container (unasserted)
+ \li the types guint8 and uchar are identical (statically asserted)
+ \li GDK pixel buffer uses 8 bits per sample (assumed at runtime)
+ \li GDK pixel buffer has 4 channels (assumed at runtime)
+ \endlist
+ */
+QImage QGtk3Interface::qt_convert_gdk_pixbuf(GdkPixbuf *buf) const
+{
+ if (!buf)
+ return QImage();
+
+ const guint8 *gdata = gdk_pixbuf_read_pixels(buf);
+ static_assert(std::is_same<decltype(gdata), const uchar *>::value,
+ "guint8 has diverted from uchar. Code needs fixing.");
+ Q_ASSERT(gdk_pixbuf_get_bits_per_sample(buf) == 8);
+ Q_ASSERT(gdk_pixbuf_get_n_channels(buf) == 4);
+ const uchar *data = static_cast<const uchar *>(gdata);
+
+ const int width = gdk_pixbuf_get_width(buf);
+ const int height = gdk_pixbuf_get_height(buf);
+ const int bpl = gdk_pixbuf_get_rowstride(buf);
+ QImage converted(data, width, height, bpl, QImage::Format_RGBA8888);
+
+ // convert to more optimal format and detach to survive lifetime of buf
+ return converted.convertToFormat(QImage::Format_ARGB32_Premultiplied);
+}
+
+/*!
+ \internal
+ \brief Instantiate a new GTK widget.
+
+ Returns a pointer to a new GTK widget of \param type, allocated on the heap.
+ Returns nullptr of gtk_Default has is passed.
+ */
+GtkWidget *QGtk3Interface::qt_new_gtkWidget(QGtkWidget type) const
+{
+#define CASE(Type)\
+ case QGtkWidget::Type: return Type ##_new();
+#define CASEN(Type)\
+ case QGtkWidget::Type: return Type ##_new(nullptr);
+
+ switch (type) {
+ CASE(gtk_menu_bar)
+ CASE(gtk_menu)
+ CASE(gtk_button)
+ case QGtkWidget::gtk_button_box: return gtk_button_box_new(GtkOrientation::GTK_ORIENTATION_HORIZONTAL);
+ CASE(gtk_check_button)
+ CASEN(gtk_radio_button)
+ CASEN(gtk_frame)
+ CASE(gtk_statusbar)
+ CASE(gtk_entry)
+ case QGtkWidget::gtk_popup: return gtk_window_new(GTK_WINDOW_POPUP);
+ CASE(gtk_notebook)
+ CASE(gtk_toolbar)
+ CASE(gtk_tree_view)
+ CASE(gtk_combo_box)
+ CASE(gtk_combo_box_text)
+ CASE(gtk_progress_bar)
+ CASE(gtk_fixed)
+ CASE(gtk_separator_menu_item)
+ CASE(gtk_offscreen_window)
+ case QGtkWidget::gtk_Default: return nullptr;
+ }
+#undef CASE
+#undef CASEN
+ Q_UNREACHABLE();
+}
+
+/*!
+ \internal
+ \brief Read a GTK widget's color from a generic color getter.
+
+ This method returns a generic color of \param con, a given GTK style context.
+ The requested color is defined by \param def and the GTK color-state \param state.
+ The return type is GDK color in RGBA format.
+ */
+GdkRGBA QGtk3Interface::genericColor(GtkStyleContext *con, GtkStateFlags state, QGtkColorDefault def) const
+{
+ GdkRGBA color;
+
+#define CASE(def, call)\
+ case QGtkColorDefault::def:\
+ gtk_style_context_get_ ##call(con, state, &color);\
+ break;
+
+ switch (def) {
+ CASE(Foreground, color)
+ CASE(Background, background_color)
+ CASE(Border, border_color)
+ }
+ return color;
+#undef CASE
+}
+
+/*!
+ \internal
+ \brief Read a GTK widget's color from a property.
+
+ Returns a color of GTK-widget \param widget, defined by \param source and \param state.
+ The return type is GDK color in RGBA format.
+
+ \note If no corresponding property can be found for \param source, the method falls back to a
+ suitable generic color.
+ */
+QColor QGtk3Interface::color(GtkWidget *widget, QGtkColorSource source, GtkStateFlags state) const
+{
+ GdkRGBA col;
+ GtkStyleContext *con = context(widget);
+
+#define CASE(src, def)\
+ case QGtkColorSource::src: {\
+ const ColorKey key = ColorKey({QGtkColorSource::src, state});\
+ if (gtkColorMap.contains(key)) {\
+ const ColorValue val = gtkColorMap.value(key);\
+ if (!gtk_style_context_lookup_color(con, val.propertyName.toUtf8().constData(), &col)) {\
+ col = genericColor(con, state, val.genericSource);\
+ qCDebug(lcQGtk3Interface) << "Property name" << val.propertyName << "not found.\n"\
+ << "Falling back to " << val.genericSource;\
+ }\
+ } else {\
+ col = genericColor(con, state, QGtkColorDefault::def);\
+ qCDebug(lcQGtk3Interface) << "No color source found for" << QGtkColorSource::src\
+ << fromGtkState(state) << "\n Falling back to"\
+ << QGtkColorDefault::def;\
+ }\
+ }\
+ break;
+
+ switch (source) {
+ CASE(Foreground, Foreground)
+ CASE(Background, Background)
+ CASE(Text, Foreground)
+ CASE(Base, Background)
+ CASE(Border, Border)
+ }
+
+ return fromGdkColor(col);
+#undef CASE
+}
+
+/*!
+ \internal
+ \brief Get pointer to a GTK widget by \param type.
+
+ Returns the pointer to a GTK widget, specified by \param type.
+ GTK widgets are cached, so that only one instance of each type is created.
+ \note
+ The method returns nullptr for the enum value gtk_Default.
+ */
+GtkWidget *QGtk3Interface::widget(QGtkWidget type) const
+{
+ if (type == QGtkWidget::gtk_Default)
+ return nullptr;
+
+ // Return from cache
+ if (GtkWidget *w = cache.value(type))
+ return w;
+
+ // Create new item and cache it
+ GtkWidget *w = qt_new_gtkWidget(type);
+ cache.insert(type, w);
+ return w;
+}
+
+/*!
+ \internal
+ \brief Access a GTK widget's style context.
+
+ Returns the pointer to the style context of GTK widget \param w.
+
+ \note If \param w is nullptr, the GTK default style context (entry style) is returned.
+ */
+GtkStyleContext *QGtk3Interface::context(GtkWidget *w) const
+{
+ if (w)
+ return gtk_widget_get_style_context(w);
+
+ return gtk_widget_get_style_context(widget(QGtkWidget::gtk_entry));
+}
+
+/*!
+ \internal
+ \brief Create a QBrush from a GTK widget.
+
+ Returns a QBrush corresponding to GTK widget type \param wtype, \param source and \param state.
+
+ Brush height and width is ignored in GTK3, because brush assets (e.g. 9-patches)
+ can't be accessed by the GTK3 API. It's therefore unknown, if the brush relates only to colors,
+ or to a pixmap based style.
+
+ */
+QBrush QGtk3Interface::brush(QGtkWidget wtype, QGtkColorSource source, GtkStateFlags state) const
+{
+ // FIXME: When a color's pixmap can be accessed via the GTK API,
+ // read it and set it in the brush.
+ return QBrush(color(widget(wtype), source, state));
+}
+
+/*!
+ \internal
+ \brief Returns the name of the current GTK theme.
+ */
+QString QGtk3Interface::themeName() const
+{
+ QString name;
+
+ if (GtkSettings *settings = gtk_settings_get_default()) {
+ gchar *theme_name;
+ g_object_get(settings, "gtk-theme-name", &theme_name, nullptr);
+ name = QLatin1StringView(theme_name);
+ g_free(theme_name);
+ }
+
+ return name;
+}
+
+/*!
+ \internal
+ \brief Determine color scheme by colors.
+
+ Returns the color scheme of the current GTK theme, heuristically determined by the
+ lightness difference between default background and foreground colors.
+
+ \note Returns Unknown in the unlikely case that both colors have the same lightness.
+ */
+Qt::ColorScheme QGtk3Interface::colorSchemeByColors() const
+{
+ const QColor background = color(widget(QGtkWidget::gtk_Default),
+ QGtkColorSource::Background,
+ GTK_STATE_FLAG_ACTIVE);
+ const QColor foreground = color(widget(QGtkWidget::gtk_Default),
+ QGtkColorSource::Foreground,
+ GTK_STATE_FLAG_ACTIVE);
+
+ if (foreground.lightness() > background.lightness())
+ return Qt::ColorScheme::Dark;
+ if (foreground.lightness() < background.lightness())
+ return Qt::ColorScheme::Light;
+ return Qt::ColorScheme::Unknown;
+}
+
+/*!
+ \internal
+ \brief Map font type to GTK widget type.
+
+ Returns the GTK widget type corresponding to the given QPlatformTheme::Font \param type.
+ */
+inline constexpr QGtk3Interface::QGtkWidget QGtk3Interface::toWidgetType(QPlatformTheme::Font type)
+{
+ switch (type) {
+ case QPlatformTheme::SystemFont: return QGtkWidget::gtk_Default;
+ case QPlatformTheme::MenuFont: return QGtkWidget::gtk_menu;
+ case QPlatformTheme::MenuBarFont: return QGtkWidget::gtk_menu_bar;
+ case QPlatformTheme::MenuItemFont: return QGtkWidget::gtk_menu;
+ case QPlatformTheme::MessageBoxFont: return QGtkWidget::gtk_popup;
+ case QPlatformTheme::LabelFont: return QGtkWidget::gtk_popup;
+ case QPlatformTheme::TipLabelFont: return QGtkWidget::gtk_Default;
+ case QPlatformTheme::StatusBarFont: return QGtkWidget::gtk_statusbar;
+ case QPlatformTheme::TitleBarFont: return QGtkWidget::gtk_Default;
+ case QPlatformTheme::MdiSubWindowTitleFont: return QGtkWidget::gtk_Default;
+ case QPlatformTheme::DockWidgetTitleFont: return QGtkWidget::gtk_Default;
+ case QPlatformTheme::PushButtonFont: return QGtkWidget::gtk_button;
+ case QPlatformTheme::CheckBoxFont: return QGtkWidget::gtk_check_button;
+ case QPlatformTheme::RadioButtonFont: return QGtkWidget::gtk_radio_button;
+ case QPlatformTheme::ToolButtonFont: return QGtkWidget::gtk_button;
+ case QPlatformTheme::ItemViewFont: return QGtkWidget::gtk_entry;
+ case QPlatformTheme::ListViewFont: return QGtkWidget::gtk_tree_view;
+ case QPlatformTheme::HeaderViewFont: return QGtkWidget::gtk_combo_box;
+ case QPlatformTheme::ListBoxFont: return QGtkWidget::gtk_Default;
+ case QPlatformTheme::ComboMenuItemFont: return QGtkWidget::gtk_combo_box;
+ case QPlatformTheme::ComboLineEditFont: return QGtkWidget::gtk_combo_box_text;
+ case QPlatformTheme::SmallFont: return QGtkWidget::gtk_Default;
+ case QPlatformTheme::MiniFont: return QGtkWidget::gtk_Default;
+ case QPlatformTheme::FixedFont: return QGtkWidget::gtk_Default;
+ case QPlatformTheme::GroupBoxTitleFont: return QGtkWidget::gtk_Default;
+ case QPlatformTheme::TabButtonFont: return QGtkWidget::gtk_button;
+ case QPlatformTheme::EditorFont: return QGtkWidget::gtk_entry;
+ case QPlatformTheme::NFonts: return QGtkWidget::gtk_Default;
+ }
+ Q_UNREACHABLE();
+}
+
+/*!
+ \internal
+ \brief Convert pango \param style to QFont::Style.
+ */
+inline constexpr QFont::Style QGtk3Interface::toFontStyle(PangoStyle style)
+{
+ switch (style) {
+ case PANGO_STYLE_ITALIC: return QFont::StyleItalic;
+ case PANGO_STYLE_OBLIQUE: return QFont::StyleOblique;
+ case PANGO_STYLE_NORMAL: return QFont::StyleNormal;
+ }
+ // This is reached when GTK has introduced a new font style
+ Q_UNREACHABLE();
+}
+
+/*!
+ \internal
+ \brief Convert pango font \param weight to an int, representing font weight in Qt.
+
+ Compatibility of PangoWeight is statically asserted.
+ The minimum (1) and maximum (1000) weight in Qt is respeced.
+ */
+inline constexpr int QGtk3Interface::toFontWeight(PangoWeight weight)
+{
+ // GTK PangoWeight can be directly converted to QFont::Weight
+ // unless one of the enums changes.
+ static_assert(PANGO_WEIGHT_THIN == 100 && PANGO_WEIGHT_ULTRAHEAVY == 1000,
+ "Pango font weight enum changed. Fix conversion.");
+
+ static_assert(QFont::Thin == 100 && QFont::Black == 900,
+ "QFont::Weight enum changed. Fix conversion.");
+
+ return qBound(1, static_cast<int>(weight), 1000);
+}
+
+/*!
+ \internal
+ \brief Return a GTK styled font.
+
+ Returns the QFont corresponding to \param type by reading the corresponding
+ GTK widget type's font.
+
+ \note GTK allows to specify a non fixed font as the system's fixed font.
+ If a fixed font is requested, the method fixes the pitch and falls back to monospace,
+ unless a suitable fixed pitch font is found.
+ */
+QFont QGtk3Interface::font(QPlatformTheme::Font type) const
+{
+ GtkStyleContext *con = context(widget(toWidgetType(type)));
+ if (!con)
+ return QFont();
+
+ // explicitly add provider for fixed font
+ GtkCssProvider *cssProvider = nullptr;
+ if (type == QPlatformTheme::FixedFont) {
+ cssProvider = gtk_css_provider_new();
+ gtk_style_context_add_class (con, GTK_STYLE_CLASS_MONOSPACE);
+ const char *fontSpec = "* {font-family: monospace;}";
+ gtk_css_provider_load_from_data(cssProvider, fontSpec, -1, NULL);
+ gtk_style_context_add_provider(con, GTK_STYLE_PROVIDER(cssProvider),
+ GTK_STYLE_PROVIDER_PRIORITY_USER);
+ }
+
+ // remove monospace provider from style context and unref it
+ QScopeGuard guard([&](){
+ if (cssProvider) {
+ gtk_style_context_remove_provider(con, GTK_STYLE_PROVIDER(cssProvider));
+ g_object_unref(cssProvider);
+ }
+ });
+
+ const PangoFontDescription *gtkFont = gtk_style_context_get_font(con, GTK_STATE_FLAG_NORMAL);
+ if (!gtkFont)
+ return QFont();
+
+ const QString family = QString::fromLatin1(pango_font_description_get_family(gtkFont));
+ if (family.isEmpty())
+ return QFont();
+
+ const int weight = toFontWeight(pango_font_description_get_weight(gtkFont));
+
+ // Creating a QFont() creates a futex lockup on a theme change
+ // QFont doesn't have a constructor with float point size
+ // => create a dummy point size and set it later.
+ QFont font(family, 1, weight);
+ font.setPointSizeF(static_cast<float>(pango_font_description_get_size(gtkFont)/PANGO_SCALE));
+ font.setStyle(toFontStyle(pango_font_description_get_style(gtkFont)));
+
+ if (type == QPlatformTheme::FixedFont) {
+ font.setFixedPitch(true);
+ if (!QFontInfo(font).fixedPitch()) {
+ qCDebug(lcQGtk3Interface) << "No fixed pitch font found in font family"
+ << font.family() << ". falling back to a default"
+ << "fixed pitch font";
+ font.setFamily("monospace"_L1);
+ }
+ }
+
+ return font;
+}
+
+/*!
+ \internal
+ \brief Returns a GTK styled file icon for \param fileInfo.
+ */
+QIcon QGtk3Interface::fileIcon(const QFileInfo &fileInfo) const
+{
+ GFile *file = g_file_new_for_path(fileInfo.absoluteFilePath().toLatin1().constData());
+ if (!file)
+ return QIcon();
+
+ GFileInfo *info = g_file_query_info (file, G_FILE_ATTRIBUTE_STANDARD_ICON,
+ G_FILE_QUERY_INFO_NONE, nullptr, nullptr);
+ if (!info) {
+ g_object_unref(file);
+ return QIcon();
+ }
+
+ GIcon *icon = g_file_info_get_icon(info);
+ if (!icon) {
+ g_object_unref(file);
+ g_object_unref(info);
+ return QIcon();
+ }
+
+ GtkIconTheme *theme = gtk_icon_theme_get_default();
+ GtkIconInfo *iconInfo = gtk_icon_theme_lookup_by_gicon(theme, icon, 16,
+ GTK_ICON_LOOKUP_FORCE_SIZE);
+ if (!iconInfo) {
+ g_object_unref(file);
+ g_object_unref(info);
+ return QIcon();
+ }
+
+ GdkPixbuf *buf = gtk_icon_info_load_icon(iconInfo, nullptr);
+ QImage image = qt_convert_gdk_pixbuf(buf);
+ g_object_unref(file);
+ g_object_unref(info);
+ g_object_unref(buf);
+ return QIcon(QPixmap::fromImage(image));
+}
+
+QT_END_NAMESPACE
diff --git a/src/plugins/platformthemes/gtk3/qgtk3interface_p.h b/src/plugins/platformthemes/gtk3/qgtk3interface_p.h
new file mode 100644
index 0000000000..c43932a4fa
--- /dev/null
+++ b/src/plugins/platformthemes/gtk3/qgtk3interface_p.h
@@ -0,0 +1,211 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QGTK3INTERFACE_H
+#define QGTK3INTERFACE_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtCore/QString>
+#include <QtCore/QCache>
+#include <private/qflatmap_p.h>
+#include <QtCore/QObject>
+#include <QtGui/QIcon>
+#include <QtGui/QPalette>
+#include <QtWidgets/QWidget>
+#include <QtCore/QLoggingCategory>
+#include <QtGui/QPixmap>
+#include <qpa/qplatformtheme.h>
+
+#undef signals // Collides with GTK symbols
+#include <gtk/gtk.h>
+#include <gdk/gdk.h>
+#include <glib.h>
+
+QT_BEGIN_NAMESPACE
+
+Q_DECLARE_LOGGING_CATEGORY(lcQGtk3Interface);
+
+using namespace Qt::StringLiterals;
+
+class QGtk3Storage;
+
+/*!
+ \internal
+ \brief The QGtk3Interface class centralizes communication with the GTK3 library.
+
+ By encapsulating all GTK version specific syntax and conversions, it makes Qt's GTK theme
+ independent from GTK versions.
+
+ \note
+ Including GTK3 headers requires #undef signals, which disables Qt signal/slot handling.
+ */
+
+class QGtk3Interface
+{
+ Q_GADGET
+public:
+ QGtk3Interface(QGtk3Storage *);
+ ~QGtk3Interface();
+
+ /*!
+ * \internal
+ \enum QGtk3Interface::QGtkWidget
+ \brief Represents GTK widget types used to obtain color information.
+
+ \note The enum value gtk_Default refers to the GTK default style, rather than to a specific widget.
+ */
+ enum class QGtkWidget {
+ gtk_menu_bar,
+ gtk_menu,
+ gtk_button,
+ gtk_button_box,
+ gtk_check_button,
+ gtk_radio_button,
+ gtk_frame,
+ gtk_statusbar,
+ gtk_entry,
+ gtk_popup,
+ gtk_notebook,
+ gtk_toolbar,
+ gtk_tree_view,
+ gtk_combo_box,
+ gtk_combo_box_text,
+ gtk_progress_bar,
+ gtk_fixed,
+ gtk_separator_menu_item,
+ gtk_Default,
+ gtk_offscreen_window
+ };
+ Q_ENUM(QGtkWidget)
+
+ /*!
+ \internal
+ \enum QGtk3Interface::QGtkColorSource
+ \brief The QGtkColorSource enum represents the source of a color within a GTK widgets style context.
+
+ If the current GTK theme provides such a color for a given widget, the color can be read
+ from the style context by passing the enum's key as a property name to the GTK method
+ gtk_style_context_lookup_color. The method will return false, if no color has been found.
+ */
+ enum class QGtkColorSource {
+ Foreground,
+ Background,
+ Text,
+ Base,
+ Border
+ };
+ Q_ENUM(QGtkColorSource)
+
+ /*!
+ \internal
+ \enum QGtk3Interface::QGtkColorDefault
+ \brief The QGtkColorDefault enum represents generic GTK colors.
+
+ The GTK3 methods gtk_style_context_get_color, gtk_style_context_get_background_color, and
+ gtk_style_context_get_foreground_color always return the respective colors with a widget's
+ style context. Unless set as a property by the current GTK theme, GTK's default colors will
+ be returned.
+ These generic default colors, represented by the GtkColorDefault enum, are used as a
+ back, if a specific color property is requested but not defined in the current GTK theme.
+ */
+ enum class QGtkColorDefault {
+ Foreground,
+ Background,
+ Border
+ };
+ Q_ENUM(QGtkColorDefault)
+
+ // Create a brush from GTK widget type, color source and color state
+ QBrush brush(QGtkWidget wtype, QGtkColorSource source, GtkStateFlags state) const;
+
+ // Font & icon getters
+ QImage standardPixmap(QPlatformTheme::StandardPixmap standardPixmap) const;
+ QFont font(QPlatformTheme::Font type) const;
+ QIcon fileIcon(const QFileInfo &fileInfo) const;
+
+ // Return current GTK theme name
+ QString themeName() const;
+
+ // Derive color scheme from default colors
+ Qt::ColorScheme colorSchemeByColors() const;
+
+ // Convert GTK state to/from string
+ static int toGtkState(const QString &state);
+ static const QLatin1String fromGtkState(GtkStateFlags state);
+
+private:
+
+ // Map colors to GTK property names and default to generic color getters
+ struct ColorKey {
+ QGtkColorSource colorSource = QGtkColorSource::Background;
+ GtkStateFlags state = GTK_STATE_FLAG_NORMAL;
+
+ // struct becomes key of a map, so operator< is needed
+ bool operator<(const ColorKey& other) const {
+ return std::tie(colorSource, state) <
+ std::tie(other.colorSource, other.state);
+ }
+
+ QDebug operator<<(QDebug dbg)
+ {
+ return dbg << "QGtk3Interface::ColorKey(colorSource=" << colorSource << ", GTK state=" << fromGtkState(state) << ")";
+ }
+ };
+
+ struct ColorValue {
+ QString propertyName = QString();
+ QGtkColorDefault genericSource = QGtkColorDefault::Background;
+
+ QDebug operator<<(QDebug dbg)
+ {
+ return dbg << "QGtk3Interface::ColorValue(propertyName=" << propertyName << ", genericSource=" << genericSource << ")";
+ }
+ };
+
+ typedef QFlatMap<ColorKey, ColorValue> ColorMap;
+ ColorMap gtkColorMap;
+ void initColorMap();
+
+ GdkRGBA genericColor(GtkStyleContext *con, GtkStateFlags state, QGtkColorDefault def) const;
+
+ // Cache for GTK widgets
+ mutable QFlatMap<QGtkWidget, GtkWidget *> cache;
+
+ // Converters for GTK icon and GDK pixbuf
+ QImage qt_gtk_get_icon(const char *iconName) const;
+ QImage qt_convert_gdk_pixbuf(GdkPixbuf *buf) const;
+
+ // Create new GTK widget object
+ GtkWidget *qt_new_gtkWidget(QGtkWidget type) const;
+
+ // Deliver GTK Widget from cache or create new
+ GtkWidget *widget(QGtkWidget type) const;
+
+ // Get a GTK widget's style context. Default settings style context if nullptr
+ GtkStyleContext *context(GtkWidget *widget = nullptr) const;
+
+ // Convert GTK color into QColor
+ static inline QColor fromGdkColor (const GdkRGBA &c)
+ { return QColor::fromRgbF(c.red, c.green, c.blue, c.alpha); }
+
+ // get a QColor of a GTK widget (default settings style if nullptr)
+ QColor color (GtkWidget *widget, QGtkColorSource source, GtkStateFlags state) const;
+
+ // Mappings for GTK fonts
+ inline static constexpr QGtkWidget toWidgetType(QPlatformTheme::Font);
+ inline static constexpr QFont::Style toFontStyle(PangoStyle style);
+ inline static constexpr int toFontWeight(PangoWeight weight);
+
+};
+QT_END_NAMESPACE
+#endif // QGTK3INTERFACE_H
diff --git a/src/plugins/platformthemes/gtk3/qgtk3json.cpp b/src/plugins/platformthemes/gtk3/qgtk3json.cpp
new file mode 100644
index 0000000000..eb81e563be
--- /dev/null
+++ b/src/plugins/platformthemes/gtk3/qgtk3json.cpp
@@ -0,0 +1,404 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include "qgtk3json_p.h"
+#include <QtCore/QFile>
+#include <QMetaEnum>
+
+QT_BEGIN_NAMESPACE
+
+QLatin1String QGtk3Json::fromPalette(QPlatformTheme::Palette palette)
+{
+ return QLatin1String(QMetaEnum::fromType<QPlatformTheme::Palette>().valueToKey(static_cast<int>(palette)));
+}
+
+QLatin1String QGtk3Json::fromGtkState(GtkStateFlags state)
+{
+ return QGtk3Interface::fromGtkState(state);
+}
+
+QLatin1String fromColor(const QColor &color)
+{
+ return QLatin1String(QByteArray(color.name(QColor::HexRgb).toLatin1()));
+}
+
+QLatin1String QGtk3Json::fromColorRole(QPalette::ColorRole role)
+{
+ return QLatin1String(QMetaEnum::fromType<QPalette::ColorRole>().valueToKey(static_cast<int>(role)));
+}
+
+QLatin1String QGtk3Json::fromColorGroup(QPalette::ColorGroup group)
+{
+ return QLatin1String(QMetaEnum::fromType<QPalette::ColorGroup>().valueToKey(static_cast<int>(group)));
+}
+
+QLatin1String QGtk3Json::fromGdkSource(QGtk3Interface::QGtkColorSource source)
+{
+ return QLatin1String(QMetaEnum::fromType<QGtk3Interface::QGtkColorSource>().valueToKey(static_cast<int>(source)));
+}
+
+QLatin1String QGtk3Json::fromWidgetType(QGtk3Interface::QGtkWidget widgetType)
+{
+ return QLatin1String(QMetaEnum::fromType<QGtk3Interface::QGtkWidget>().valueToKey(static_cast<int>(widgetType)));
+}
+
+QLatin1String QGtk3Json::fromColorScheme(Qt::ColorScheme app)
+{
+ return QLatin1String(QMetaEnum::fromType<Qt::ColorScheme>().valueToKey(static_cast<int>(app)));
+}
+
+#define CONVERT(type, key, def)\
+ bool ok;\
+ const int intVal = QMetaEnum::fromType<type>().keyToValue(key.toLatin1().constData(), &ok);\
+ return ok ? static_cast<type>(intVal) : type::def
+
+Qt::ColorScheme QGtk3Json::toColorScheme(const QString &colorScheme)
+{
+ CONVERT(Qt::ColorScheme, colorScheme, Unknown);
+}
+
+QPlatformTheme::Palette QGtk3Json::toPalette(const QString &palette)
+{
+ CONVERT(QPlatformTheme::Palette, palette, NPalettes);
+}
+
+GtkStateFlags QGtk3Json::toGtkState(const QString &type)
+{
+ int i = QGtk3Interface::toGtkState(type);
+ if (i < 0)
+ return GTK_STATE_FLAG_NORMAL;
+ return static_cast<GtkStateFlags>(i);
+}
+
+QColor toColor(const QStringView &color)
+{
+ return QColor::fromString(color);
+}
+
+QPalette::ColorRole QGtk3Json::toColorRole(const QString &role)
+{
+ CONVERT(QPalette::ColorRole, role, NColorRoles);
+}
+
+QPalette::ColorGroup QGtk3Json::toColorGroup(const QString &group)
+{
+ CONVERT(QPalette::ColorGroup, group, NColorGroups);
+}
+
+QGtk3Interface::QGtkColorSource QGtk3Json::toGdkSource(const QString &source)
+{
+ CONVERT(QGtk3Interface::QGtkColorSource, source, Background);
+}
+
+QLatin1String QGtk3Json::fromSourceType(QGtk3Storage::SourceType sourceType)
+{
+ return QLatin1String(QMetaEnum::fromType<QGtk3Storage::SourceType>().valueToKey(static_cast<int>(sourceType)));
+}
+
+QGtk3Storage::SourceType QGtk3Json::toSourceType(const QString &sourceType)
+{
+ CONVERT(QGtk3Storage::SourceType, sourceType, Invalid);
+}
+
+QGtk3Interface::QGtkWidget QGtk3Json::toWidgetType(const QString &widgetType)
+{
+ CONVERT(QGtk3Interface::QGtkWidget, widgetType, gtk_offscreen_window);
+}
+
+#undef CONVERT
+
+bool QGtk3Json::save(const QGtk3Storage::PaletteMap &map, const QString &fileName,
+ QJsonDocument::JsonFormat format)
+{
+ QJsonDocument doc = save(map);
+ if (doc.isEmpty()) {
+ qWarning() << "Nothing to save to" << fileName;
+ return false;
+ }
+
+ QFile file(fileName);
+ if (!file.open(QIODevice::WriteOnly)) {
+ qWarning() << "Unable to open file" << fileName << "for writing.";
+ return false;
+ }
+
+ if (!file.write(doc.toJson(format))) {
+ qWarning() << "Unable to serialize Json document.";
+ return false;
+ }
+
+ file.close();
+ qInfo() << "Saved mapping data to" << fileName;
+ return true;
+}
+
+const QJsonDocument QGtk3Json::save(const QGtk3Storage::PaletteMap &map)
+{
+ QJsonObject paletteObject;
+ for (auto paletteIterator = map.constBegin(); paletteIterator != map.constEnd();
+ ++paletteIterator) {
+ const QGtk3Storage::BrushMap &bm = paletteIterator.value();
+ QFlatMap<QPalette::ColorRole, QGtk3Storage::BrushMap> brushMaps;
+ for (auto brushIterator = bm.constBegin(); brushIterator != bm.constEnd();
+ ++brushIterator) {
+ const QPalette::ColorRole role = brushIterator.key().colorRole;
+ if (brushMaps.contains(role)) {
+ brushMaps.value(role).insert(brushIterator.key(), brushIterator.value());
+ } else {
+ QGtk3Storage::BrushMap newMap;
+ newMap.insert(brushIterator.key(), brushIterator.value());
+ brushMaps.insert(role, newMap);
+ }
+ }
+
+ QJsonObject brushArrayObject;
+ for (auto brushMapIterator = brushMaps.constBegin();
+ brushMapIterator != brushMaps.constEnd(); ++brushMapIterator) {
+
+ QJsonArray brushArray;
+ int brushIndex = 0;
+ const QGtk3Storage::BrushMap &bm = brushMapIterator.value();
+ for (auto brushIterator = bm.constBegin(); brushIterator != bm.constEnd();
+ ++brushIterator) {
+ QJsonObject brushObject;
+ const QGtk3Storage::TargetBrush tb = brushIterator.key();
+ QGtk3Storage::Source s = brushIterator.value();
+ brushObject.insert(ceColorGroup, fromColorGroup(tb.colorGroup));
+ brushObject.insert(ceColorScheme, fromColorScheme(tb.colorScheme));
+ brushObject.insert(ceSourceType, fromSourceType(s.sourceType));
+
+ QJsonObject sourceObject;
+ switch (s.sourceType) {
+ case QGtk3Storage::SourceType::Gtk: {
+ sourceObject.insert(ceGtkWidget, fromWidgetType(s.gtk3.gtkWidgetType));
+ sourceObject.insert(ceGdkSource, fromGdkSource(s.gtk3.source));
+ sourceObject.insert(ceGtkState, fromGtkState(s.gtk3.state));
+ sourceObject.insert(ceWidth, s.gtk3.width);
+ sourceObject.insert(ceHeight, s.gtk3.height);
+ }
+ break;
+
+ case QGtk3Storage::SourceType::Fixed: {
+ QJsonObject fixedObject;
+ fixedObject.insert(ceColor, s.fix.fixedBrush.color().name());
+ fixedObject.insert(ceWidth, s.fix.fixedBrush.texture().width());
+ fixedObject.insert(ceHeight, s.fix.fixedBrush.texture().height());
+ sourceObject.insert(ceBrush, fixedObject);
+ }
+ break;
+
+ case QGtk3Storage::SourceType::Modified:{
+ sourceObject.insert(ceColorGroup, fromColorGroup(s.rec.colorGroup));
+ sourceObject.insert(ceColorRole, fromColorRole(s.rec.colorRole));
+ sourceObject.insert(ceColorScheme, fromColorScheme(s.rec.colorScheme));
+ sourceObject.insert(ceRed, s.rec.deltaRed);
+ sourceObject.insert(ceGreen, s.rec.deltaGreen);
+ sourceObject.insert(ceBlue, s.rec.deltaBlue);
+ sourceObject.insert(ceWidth, s.rec.width);
+ sourceObject.insert(ceHeight, s.rec.height);
+ sourceObject.insert(ceLighter, s.rec.lighter);
+ }
+ break;
+
+ case QGtk3Storage::SourceType::Invalid:
+ break;
+ }
+
+ brushObject.insert(ceData, sourceObject);
+ brushArray.insert(brushIndex, brushObject);
+ ++brushIndex;
+ }
+ brushArrayObject.insert(fromColorRole(brushMapIterator.key()), brushArray);
+ }
+ paletteObject.insert(fromPalette(paletteIterator.key()), brushArrayObject);
+ }
+
+ QJsonObject top;
+ top.insert(cePalettes, paletteObject);
+ return paletteObject.keys().isEmpty() ? QJsonDocument() : QJsonDocument(top);
+}
+
+bool QGtk3Json::load(QGtk3Storage::PaletteMap &map, const QString &fileName)
+{
+ QFile file(fileName);
+ if (!file.open(QIODevice::ReadOnly)) {
+ qCWarning(lcQGtk3Interface) << "Unable to open file:" << fileName;
+ return false;
+ }
+
+ QJsonParseError err;
+ QJsonDocument doc = QJsonDocument::fromJson(file.readAll(), &err);
+ if (err.error != QJsonParseError::NoError) {
+ qWarning(lcQGtk3Interface) << "Unable to parse Json document from" << fileName
+ << err.error << err.errorString();
+ return false;
+ }
+
+ if (Q_LIKELY(load(map, doc))) {
+ qInfo() << "GTK mapping successfully imported from" << fileName;
+ return true;
+ }
+
+ qWarning() << "File" << fileName << "could not be loaded.";
+ return false;
+}
+
+bool QGtk3Json::load(QGtk3Storage::PaletteMap &map, const QJsonDocument &doc)
+{
+#define GETSTR(obj, key)\
+ if (!obj.contains(key)) {\
+ qCInfo(lcQGtk3Interface) << key << "missing for palette" << paletteName\
+ << ", Brush" << colorRoleName;\
+ return false;\
+ }\
+ value = obj[key].toString()
+
+#define GETINT(obj, key, var) GETSTR(obj, key);\
+ if (!obj[key].isDouble()) {\
+ qCInfo(lcQGtk3Interface) << key << "type mismatch" << value\
+ << "is not an integer!"\
+ << "(Palette" << paletteName << "), Brush" << colorRoleName;\
+ return false;\
+ }\
+ const int var = obj[key].toInt()
+
+ map.clear();
+ const QJsonObject top(doc.object());
+ if (doc.isEmpty() || top.isEmpty() || !top.contains(cePalettes)) {
+ qCInfo(lcQGtk3Interface) << "Document does not contain Palettes.";
+ return false;
+ }
+
+ const QStringList &paletteList = top[cePalettes].toObject().keys();
+ for (const QString &paletteName : paletteList) {
+ bool ok;
+ const int intVal = QMetaEnum::fromType<QPlatformTheme::Palette>().keyToValue(paletteName
+ .toLatin1().constData(), &ok);
+ if (!ok) {
+ qCInfo(lcQGtk3Interface) << "Invalid Palette name:" << paletteName;
+ return false;
+ }
+ const QJsonObject &paletteObject = top[cePalettes][paletteName].toObject();
+ const QStringList &brushList = paletteObject.keys();
+ if (brushList.isEmpty()) {
+ qCInfo(lcQGtk3Interface) << "Palette" << paletteName << "does not contain brushes";
+ return false;
+ }
+
+ const QPlatformTheme::Palette paletteType = static_cast<QPlatformTheme::Palette>(intVal);
+ QGtk3Storage::BrushMap brushes;
+ const QStringList &colorRoles = paletteObject.keys();
+ for (const QString &colorRoleName : colorRoles) {
+ const int intVal = QMetaEnum::fromType<QPalette::ColorRole>().keyToValue(colorRoleName
+ .toLatin1().constData(), &ok);
+ if (!ok) {
+ qCInfo(lcQGtk3Interface) << "Palette" << paletteName
+ << "contains invalid color role" << colorRoleName;
+ return false;
+ }
+ const QPalette::ColorRole colorRole = static_cast<QPalette::ColorRole>(intVal);
+ const QJsonArray &brushArray = paletteObject[colorRoleName].toArray();
+ for (int brushIndex = 0; brushIndex < brushArray.size(); ++brushIndex) {
+ const QJsonObject brushObject = brushArray.at(brushIndex).toObject();
+ if (brushObject.isEmpty()) {
+ qCInfo(lcQGtk3Interface) << "Brush specification missing at for palette"
+ << paletteName << ", Brush" << colorRoleName;
+ return false;
+ }
+
+ QString value;
+ GETSTR(brushObject, ceSourceType);
+ const QGtk3Storage::SourceType sourceType = toSourceType(value);
+ GETSTR(brushObject, ceColorGroup);
+ const QPalette::ColorGroup colorGroup = toColorGroup(value);
+ GETSTR(brushObject, ceColorScheme);
+ const Qt::ColorScheme colorScheme = toColorScheme(value);
+ QGtk3Storage::TargetBrush tb(colorGroup, colorRole, colorScheme);
+ QGtk3Storage::Source s;
+
+ if (!brushObject.contains(ceData) || !brushObject[ceData].isObject()) {
+ qCInfo(lcQGtk3Interface) << "Source specification missing for palette" << paletteName
+ << "Brush" << colorRoleName;
+ return false;
+ }
+ const QJsonObject &sourceObject = brushObject[ceData].toObject();
+
+ switch (sourceType) {
+ case QGtk3Storage::SourceType::Gtk: {
+ GETSTR(sourceObject, ceGdkSource);
+ const QGtk3Interface::QGtkColorSource gtkSource = toGdkSource(value);
+ GETSTR(sourceObject, ceGtkState);
+ const GtkStateFlags gtkState = toGtkState(value);
+ GETSTR(sourceObject, ceGtkWidget);
+ const QGtk3Interface::QGtkWidget widgetType = toWidgetType(value);
+ GETINT(sourceObject, ceHeight, height);
+ GETINT(sourceObject, ceWidth, width);
+ s = QGtk3Storage::Source(widgetType, gtkSource, gtkState, width, height);
+ }
+ break;
+
+ case QGtk3Storage::SourceType::Fixed: {
+ if (!sourceObject.contains(ceBrush)) {
+ qCInfo(lcQGtk3Interface) << "Fixed brush specification missing for palette" << paletteName
+ << "Brush" << colorRoleName;
+ return false;
+ }
+ const QJsonObject &fixedSource = sourceObject[ceBrush].toObject();
+ GETINT(fixedSource, ceWidth, width);
+ GETINT(fixedSource, ceHeight, height);
+ GETSTR(fixedSource, ceColor);
+ const QColor color(value);
+ if (!color.isValid()) {
+ qCInfo(lcQGtk3Interface) << "Color" << value << "can't be parsed for:" << paletteName
+ << "Brush" << colorRoleName;
+ return false;
+ }
+ const QBrush fixedBrush = (width < 0 && height < 0)
+ ? QBrush(color, QPixmap(width, height))
+ : QBrush(color);
+ s = QGtk3Storage::Source(fixedBrush);
+ }
+ break;
+
+ case QGtk3Storage::SourceType::Modified: {
+ GETSTR(sourceObject, ceColorGroup);
+ const QPalette::ColorGroup colorGroup = toColorGroup(value);
+ GETSTR(sourceObject, ceColorRole);
+ const QPalette::ColorRole colorRole = toColorRole(value);
+ GETSTR(sourceObject, ceColorScheme);
+ const Qt::ColorScheme colorScheme = toColorScheme(value);
+ GETINT(sourceObject, ceLighter, lighter);
+ GETINT(sourceObject, ceRed, red);
+ GETINT(sourceObject, ceBlue, blue);
+ GETINT(sourceObject, ceGreen, green);
+ s = QGtk3Storage::Source(colorGroup, colorRole, colorScheme,
+ lighter, red, green, blue);
+ }
+ break;
+
+ case QGtk3Storage::SourceType::Invalid:
+ qInfo(lcQGtk3Interface) << "Invalid source type for palette" << paletteName
+ << "Brush." << colorRoleName;
+ return false;
+ }
+ brushes.insert(tb, s);
+ }
+ }
+ map.insert(paletteType, brushes);
+ }
+ return true;
+}
+
+QT_END_NAMESPACE
+
diff --git a/src/plugins/platformthemes/gtk3/qgtk3json_p.h b/src/plugins/platformthemes/gtk3/qgtk3json_p.h
new file mode 100644
index 0000000000..daf280612c
--- /dev/null
+++ b/src/plugins/platformthemes/gtk3/qgtk3json_p.h
@@ -0,0 +1,102 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+#ifndef QGTK3JSON_P_H
+#define QGTK3JSON_P_H
+
+#include <QtCore/QCache>
+#include <QtCore/QJsonArray>
+#include <QtCore/QJsonDocument>
+#include <QtCore/QJsonObject>
+#include <QtCore/QMap>
+#include <QtCore/QString>
+#include <QtGui/QGuiApplication>
+#include <QtGui/QPalette>
+
+#include <qpa/qplatformtheme.h>
+#include "qgtk3interface_p.h"
+#include "qgtk3storage_p.h"
+
+#undef signals // Collides with GTK symbols
+#include <gtk/gtk.h>
+#include <gdk/gdk.h>
+#include <glib.h>
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+QT_BEGIN_NAMESPACE
+
+class QGtk3Json
+{
+ Q_GADGET
+private:
+ QGtk3Json(){};
+
+public:
+ // Convert enums to strings
+ static QLatin1String fromPalette(QPlatformTheme::Palette palette);
+ static QLatin1String fromGtkState(GtkStateFlags type);
+ static QLatin1String fromColor(const QColor &Color);
+ static QLatin1String fromColorRole(QPalette::ColorRole role);
+ static QLatin1String fromColorGroup(QPalette::ColorGroup group);
+ static QLatin1String fromGdkSource(QGtk3Interface::QGtkColorSource source);
+ static QLatin1String fromSourceType(QGtk3Storage::SourceType sourceType);
+ static QLatin1String fromWidgetType(QGtk3Interface::QGtkWidget widgetType);
+ static QLatin1String fromColorScheme(Qt::ColorScheme colorScheme);
+
+ // Convert strings to enums
+ static QPlatformTheme::Palette toPalette(const QString &palette);
+ static GtkStateFlags toGtkState(const QString &type);
+ static QColor toColor(const QString &Color);
+ static QPalette::ColorRole toColorRole(const QString &role);
+ static QPalette::ColorGroup toColorGroup(const QString &group);
+ static QGtk3Interface::QGtkColorSource toGdkSource(const QString &source);
+ static QGtk3Storage::SourceType toSourceType(const QString &sourceType);
+ static QGtk3Interface::QGtkWidget toWidgetType(const QString &widgetType);
+ static Qt::ColorScheme toColorScheme(const QString &colorScheme);
+
+ // Json keys
+ static constexpr QLatin1StringView cePalettes = "QtGtk3Palettes"_L1;
+ static constexpr QLatin1StringView cePalette = "PaletteType"_L1;
+ static constexpr QLatin1StringView ceGtkState = "GtkStateType"_L1;
+ static constexpr QLatin1StringView ceGtkWidget = "GtkWidgetType"_L1;
+ static constexpr QLatin1StringView ceColor = "Color"_L1;
+ static constexpr QLatin1StringView ceColorRole = "ColorRole"_L1;
+ static constexpr QLatin1StringView ceColorGroup = "ColorGroup"_L1;
+ static constexpr QLatin1StringView ceGdkSource = "GdkSource"_L1;
+ static constexpr QLatin1StringView ceSourceType = "SourceType"_L1;
+ static constexpr QLatin1StringView ceLighter = "Lighter"_L1;
+ static constexpr QLatin1StringView ceRed = "DeltaRed"_L1;
+ static constexpr QLatin1StringView ceGreen = "DeltaGreen"_L1;
+ static constexpr QLatin1StringView ceBlue = "DeltaBlue"_L1;
+ static constexpr QLatin1StringView ceWidth = "Width"_L1;
+ static constexpr QLatin1StringView ceHeight = "Height"_L1;
+ static constexpr QLatin1StringView ceBrush = "FixedBrush"_L1;
+ static constexpr QLatin1StringView ceData = "SourceData"_L1;
+ static constexpr QLatin1StringView ceBrushes = "Brushes"_L1;
+ static constexpr QLatin1StringView ceColorScheme = "ColorScheme"_L1;
+
+ // Save to a file
+ static bool save(const QGtk3Storage::PaletteMap &map, const QString &fileName,
+ QJsonDocument::JsonFormat format = QJsonDocument::Indented);
+
+ // Save to a Json document
+ static const QJsonDocument save(const QGtk3Storage::PaletteMap &map);
+
+ // Load from a file
+ static bool load(QGtk3Storage::PaletteMap &map, const QString &fileName);
+
+ // Load from a Json document
+ static bool load(QGtk3Storage::PaletteMap &map, const QJsonDocument &doc);
+};
+
+QT_END_NAMESPACE
+#endif // QGTK3JSON_P_H
diff --git a/src/plugins/platformthemes/gtk3/qgtk3menu.cpp b/src/plugins/platformthemes/gtk3/qgtk3menu.cpp
index 5589ff8836..c4ea0e5e33 100644
--- a/src/plugins/platformthemes/gtk3/qgtk3menu.cpp
+++ b/src/plugins/platformthemes/gtk3/qgtk3menu.cpp
@@ -123,13 +123,13 @@ static QString convertMnemonics(QString text, bool *found)
{
*found = false;
- qsizetype i = text.length() - 1;
+ qsizetype i = text.size() - 1;
while (i >= 0) {
const QChar c = text.at(i);
if (c == u'&') {
if (i == 0 || text.at(i - 1) != u'&') {
// convert Qt to GTK mnemonic
- if (i < text.length() - 1 && !text.at(i + 1).isSpace()) {
+ if (i < text.size() - 1 && !text.at(i + 1).isSpace()) {
text.replace(i, 1, u'_');
*found = true;
}
@@ -329,7 +329,7 @@ void QGtk3Menu::insertMenuItem(QPlatformMenuItem *item, QPlatformMenuItem *befor
GtkWidget *handle = gitem->create();
int index = m_items.indexOf(static_cast<QGtk3MenuItem *>(before));
if (index < 0)
- index = m_items.count();
+ index = m_items.size();
m_items.insert(index, gitem);
gtk_menu_shell_insert(GTK_MENU_SHELL(m_menu), handle, index);
}
diff --git a/src/plugins/platformthemes/gtk3/qgtk3storage.cpp b/src/plugins/platformthemes/gtk3/qgtk3storage.cpp
new file mode 100644
index 0000000000..90c0282651
--- /dev/null
+++ b/src/plugins/platformthemes/gtk3/qgtk3storage.cpp
@@ -0,0 +1,673 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include "qgtk3json_p.h"
+#include "qgtk3storage_p.h"
+#include <qpa/qwindowsysteminterface.h>
+
+QT_BEGIN_NAMESPACE
+
+QGtk3Storage::QGtk3Storage()
+{
+ m_interface.reset(new QGtk3Interface(this));
+ populateMap();
+}
+
+/*!
+ \internal
+ \enum QGtk3Storage::SourceType
+ \brief This enum represents the type of a color source.
+
+ \value Gtk Color is read from a GTK widget
+ \value Fixed A fixed brush is specified
+ \value Modified The color is a modification of another color (fixed or read from GTK)
+ \omitvalue Invalid
+ */
+
+/*!
+ \internal
+ \brief Find a brush from a source.
+
+ Returns a QBrush from a given \param source and a \param map of available brushes
+ to search from.
+
+ A null QBrush is returned, if no brush corresponding to the source has been found.
+ */
+QBrush QGtk3Storage::brush(const Source &source, const BrushMap &map) const
+{
+ switch (source.sourceType) {
+ case SourceType::Gtk:
+ return m_interface ? QBrush(m_interface->brush(source.gtk3.gtkWidgetType,
+ source.gtk3.source, source.gtk3.state))
+ : QBrush();
+
+ case SourceType::Modified: {
+ // don't loop through modified sources, break if modified source not found
+ Source recSource = brush(TargetBrush(source.rec.colorGroup, source.rec.colorRole,
+ source.rec.colorScheme), map);
+
+ if (!recSource.isValid() || (recSource.sourceType == SourceType::Modified))
+ return QBrush();
+
+ // Set brush and alter color
+ QBrush b = brush(recSource, map);
+ if (source.rec.width > 0 && source.rec.height > 0)
+ b.setTexture(QPixmap(source.rec.width, source.rec.height));
+ QColor c = b.color().lighter(source.rec.lighter);
+ c = QColor((c.red() + source.rec.deltaRed),
+ (c.green() + source.rec.deltaGreen),
+ (c.blue() + source.rec.deltaBlue));
+ b.setColor(c);
+ return b;
+ }
+
+ case SourceType::Fixed:
+ return source.fix.fixedBrush;
+
+ case SourceType::Invalid:
+ return QBrush();
+ }
+
+ // needed because of the scope after recursive
+ Q_UNREACHABLE();
+}
+
+/*!
+ \internal
+ \brief Recurse to find a source brush for modification.
+
+ Returns the source specified by the target brush \param b in the \param map of brushes.
+ Takes dark/light/unknown into consideration.
+ Returns an empty brush if no suitable one can be found.
+ */
+QGtk3Storage::Source QGtk3Storage::brush(const TargetBrush &b, const BrushMap &map) const
+{
+#define FIND(brush) if (map.contains(brush))\
+ return map.value(brush)
+
+ // Return exact match
+ FIND(b);
+
+ // unknown color scheme can find anything
+ if (b.colorScheme == Qt::ColorScheme::Unknown) {
+ FIND(TargetBrush(b, Qt::ColorScheme::Dark));
+ FIND(TargetBrush(b, Qt::ColorScheme::Light));
+ }
+
+ // Color group All can always be found
+ if (b.colorGroup != QPalette::All)
+ return brush(TargetBrush(QPalette::All, b.colorRole, b.colorScheme), map);
+
+ // Brush not found
+ return Source();
+#undef FIND
+}
+
+/*!
+ \internal
+ \brief Returns a simple, hard coded base palette.
+
+ Create a hard coded palette with default colors as a fallback for any color that can't be
+ obtained from GTK.
+
+ \note This palette will be used as a default baseline for the system palette, which then
+ will be used as a default baseline for any other palette type.
+ */
+QPalette QGtk3Storage::standardPalette()
+{
+ QColor backgroundColor(0xd4, 0xd0, 0xc8);
+ QColor lightColor(backgroundColor.lighter());
+ QColor darkColor(backgroundColor.darker());
+ const QBrush darkBrush(darkColor);
+ QColor midColor(Qt::gray);
+ QPalette palette(Qt::black, backgroundColor, lightColor, darkColor,
+ midColor, Qt::black, Qt::white);
+ palette.setBrush(QPalette::Disabled, QPalette::WindowText, darkBrush);
+ palette.setBrush(QPalette::Disabled, QPalette::Text, darkBrush);
+ palette.setBrush(QPalette::Disabled, QPalette::ButtonText, darkBrush);
+ palette.setBrush(QPalette::Disabled, QPalette::Base, QBrush(backgroundColor));
+ return palette;
+}
+
+/*!
+ \internal
+ \brief Return a GTK styled QPalette.
+
+ Returns the pointer to a (cached) QPalette for \param type, with its brushes
+ populated according to the current GTK theme.
+ */
+const QPalette *QGtk3Storage::palette(QPlatformTheme::Palette type) const
+{
+ if (type >= QPlatformTheme::NPalettes)
+ return nullptr;
+
+ if (m_paletteCache[type].has_value()) {
+ qCDebug(lcQGtk3Interface) << "Returning palette from cache:"
+ << QGtk3Json::fromPalette(type);
+
+ return &m_paletteCache[type].value();
+ }
+
+ // Read system palette as a baseline first
+ if (!m_paletteCache[QPlatformTheme::SystemPalette].has_value() && type != QPlatformTheme::SystemPalette)
+ palette();
+
+ // Fall back to system palette for unknown types
+ if (!m_palettes.contains(type) && type != QPlatformTheme::SystemPalette) {
+ qCDebug(lcQGtk3Interface) << "Returning system palette for unknown type"
+ << QGtk3Json::fromPalette(type);
+ return palette();
+ }
+
+ BrushMap brushes = m_palettes.value(type);
+
+ // Standard palette is base for system palette. System palette is base for all others.
+ QPalette p = QPalette( type == QPlatformTheme::SystemPalette ? standardPalette()
+ : m_paletteCache[QPlatformTheme::SystemPalette].value());
+
+ qCDebug(lcQGtk3Interface) << "Creating palette:" << QGtk3Json::fromPalette(type);
+ for (auto i = brushes.begin(); i != brushes.end(); ++i) {
+ Source source = i.value();
+
+ // Brush is set if
+ // - theme and source color scheme match
+ // - or either of them is unknown
+ const auto appSource = i.key().colorScheme;
+ const auto appTheme = colorScheme();
+ const bool setBrush = (appSource == appTheme) ||
+ (appSource == Qt::ColorScheme::Unknown) ||
+ (appTheme == Qt::ColorScheme::Unknown);
+
+ if (setBrush) {
+ p.setBrush(i.key().colorGroup, i.key().colorRole, brush(source, brushes));
+ }
+ }
+
+ m_paletteCache[type].emplace(p);
+ if (type == QPlatformTheme::SystemPalette)
+ qCDebug(lcQGtk3Interface) << "System Palette defined" << themeName() << colorScheme() << p;
+
+ return &m_paletteCache[type].value();
+}
+
+/*!
+ \internal
+ \brief Return a GTK styled font.
+
+ Returns a QFont of \param type, styled according to the current GTK theme.
+*/
+const QFont *QGtk3Storage::font(QPlatformTheme::Font type) const
+{
+ if (m_fontCache[type].has_value())
+ return &m_fontCache[type].value();
+
+ m_fontCache[type].emplace(m_interface->font(type));
+ return &m_fontCache[type].value();
+}
+
+/*!
+ \internal
+ \brief Return a GTK styled standard pixmap if available.
+
+ Returns a pixmap specified by \param standardPixmap and \param size.
+ Returns an empty pixmap if GTK doesn't support the requested one.
+ */
+QPixmap QGtk3Storage::standardPixmap(QPlatformTheme::StandardPixmap standardPixmap,
+ const QSizeF &size) const
+{
+ if (m_pixmapCache.contains(standardPixmap))
+ return QPixmap::fromImage(m_pixmapCache.object(standardPixmap)->scaled(size.toSize()));
+
+ if (!m_interface)
+ return QPixmap();
+
+ QImage image = m_interface->standardPixmap(standardPixmap);
+ if (image.isNull())
+ return QPixmap();
+
+ m_pixmapCache.insert(standardPixmap, new QImage(image));
+ return QPixmap::fromImage(image.scaled(size.toSize()));
+}
+
+/*!
+ \internal
+ \brief Returns a GTK styled file icon corresponding to \param fileInfo.
+ */
+QIcon QGtk3Storage::fileIcon(const QFileInfo &fileInfo) const
+{
+ return m_interface ? m_interface->fileIcon(fileInfo) : QIcon();
+}
+
+/*!
+ \internal
+ \brief Clears all caches.
+ */
+void QGtk3Storage::clear()
+{
+ m_colorScheme = Qt::ColorScheme::Unknown;
+ m_palettes.clear();
+ for (auto &cache : m_paletteCache)
+ cache.reset();
+
+ for (auto &cache : m_fontCache)
+ cache.reset();
+}
+
+/*!
+ \internal
+ \brief Handles a theme change at runtime.
+
+ Clear all caches, re-populate with current GTK theme and notify the window system interface.
+ This method is a callback for the theme change signal sent from GTK.
+ */
+void QGtk3Storage::handleThemeChange()
+{
+ clear();
+ populateMap();
+ QWindowSystemInterface::handleThemeChange();
+}
+
+/*!
+ \internal
+ \brief Populates a map with information about how to locate colors in GTK.
+
+ This method creates a data structure to locate color information for each brush of a QPalette
+ within GTK. The structure can hold mapping information for each QPlatformTheme::Palette
+ enum value. If no specific mapping is stored for an enum value, the system palette is returned
+ instead of a specific one. If no mapping is stored for the system palette, it will fall back to
+ QGtk3Storage::standardPalette.
+
+ The method will populate the data structure with a standard mapping, covering the following
+ palette types:
+ \list
+ \li QPlatformTheme::SystemPalette
+ \li QPlatformTheme::CheckBoxPalette
+ \li QPlatformTheme::RadioButtonPalette
+ \li QPlatformTheme::ComboBoxPalette
+ \li QPlatformTheme::GroupBoxPalette
+ \li QPlatformTheme::MenuPalette
+ \li QPlatformTheme::TextLineEditPalette
+ \endlist
+
+ The method will check the environment variable {{QT_GUI_GTK_JSON_SAVE}}. If it points to a
+ valid path with write access, it will write the standard mapping into a Json file.
+ That Json file can be modified and/or extended.
+ The Json syntax is
+ - "QGtk3Palettes" (top level value)
+ - QPlatformTheme::Palette
+ - QPalette::ColorRole
+ - Qt::ColorScheme
+ - Qt::ColorGroup
+ - Source data
+ - Source Type
+ - [source data]
+
+ If the environment variable {{QT_GUI_GTK_JSON_HARDCODED}} contains the keyword \c true,
+ all sources are converted to fixed sources. In that case, they contain the hard coded HexRGBA
+ values read from GTK.
+
+ The method will also check the environment variable {{QT_GUI_GTK_JSON}}. If it points to a valid
+ Json file with read access, it will be parsed instead of creating a standard mapping.
+ Parsing errors will be printed out with qCInfo if the logging category {{qt.qpa.gtk}} is activated.
+ In case of a parsing error, the method will fall back to creating a standard mapping.
+
+ \note
+ If a Json file contains only fixed brushes (e.g. exported with {{QT_GUI_GTK_JSON_HARDCODED=true}}),
+ no colors will be imported from GTK.
+ */
+void QGtk3Storage::populateMap()
+{
+ static QString m_themeName;
+
+ // Distiguish initialization, theme change or call without theme change
+ const QString newThemeName = themeName();
+ if (m_themeName == newThemeName)
+ return;
+
+ clear();
+
+ // Derive color scheme from theme name
+ m_colorScheme = newThemeName.contains("dark"_L1, Qt::CaseInsensitive)
+ ? Qt::ColorScheme::Dark : m_interface->colorSchemeByColors();
+
+ if (m_themeName.isEmpty()) {
+ qCDebug(lcQGtk3Interface) << "GTK theme initialized:" << newThemeName << m_colorScheme;
+ } else {
+ qCDebug(lcQGtk3Interface) << "GTK theme changed to:" << newThemeName << m_colorScheme;
+ }
+ m_themeName = newThemeName;
+
+ // create standard mapping or load from Json file?
+ const QString jsonInput = qEnvironmentVariable("QT_GUI_GTK_JSON");
+ if (!jsonInput.isEmpty()) {
+ if (load(jsonInput)) {
+ return;
+ } else {
+ qWarning() << "Falling back to standard GTK mapping.";
+ }
+ }
+
+ createMapping();
+
+ const QString jsonOutput = qEnvironmentVariable("QT_GUI_GTK_JSON_SAVE");
+ if (!jsonOutput.isEmpty() && !save(jsonOutput))
+ qWarning() << "File" << jsonOutput << "could not be saved.\n";
+}
+
+/*!
+ \internal
+ \brief Return a palette map for saving.
+
+ This method returns the existing palette map, if the environment variable
+ {{QT_GUI_GTK_JSON_HARDCODED}} is not set or does not contain the keyword \c true.
+ If it contains the keyword \c true, it returns a palette map with all brush
+ sources converted to fixed sources.
+ */
+const QGtk3Storage::PaletteMap QGtk3Storage::savePalettes() const
+{
+ const QString hard = qEnvironmentVariable("QT_GUI_GTK_JSON_HARDCODED");
+ if (!hard.contains("true"_L1, Qt::CaseInsensitive))
+ return m_palettes;
+
+ // Json output is supposed to be readable without GTK connection
+ // convert palette map into hard coded brushes
+ PaletteMap map = m_palettes;
+ for (auto paletteIterator = map.begin(); paletteIterator != map.end();
+ ++paletteIterator) {
+ QGtk3Storage::BrushMap &bm = paletteIterator.value();
+ for (auto brushIterator = bm.begin(); brushIterator != bm.end();
+ ++brushIterator) {
+ QGtk3Storage::Source &s = brushIterator.value();
+ switch (s.sourceType) {
+
+ // Read the brush and convert it into a fixed brush
+ case SourceType::Gtk: {
+ const QBrush fixedBrush = brush(s, bm);
+ s.fix.fixedBrush = fixedBrush;
+ s.sourceType = SourceType::Fixed;
+ }
+ break;
+ case SourceType::Fixed:
+ case SourceType::Modified:
+ case SourceType::Invalid:
+ break;
+ }
+ }
+ }
+ return map;
+}
+
+/*!
+ \internal
+ \brief Saves current palette mapping to a \param filename with Json format \param f.
+
+ Saves the current palette mapping into a QJson file,
+ taking {{QT_GUI_GTK_JSON_HARDCODED}} into consideration.
+ Returns \c true if saving was successful and \c false otherwise.
+ */
+bool QGtk3Storage::save(const QString &filename, QJsonDocument::JsonFormat f) const
+{
+ return QGtk3Json::save(savePalettes(), filename, f);
+}
+
+/*!
+ \internal
+ \brief Returns a QJsonDocument with current palette mapping.
+
+ Saves the current palette mapping into a QJsonDocument,
+ taking {{QT_GUI_GTK_JSON_HARDCODED}} into consideration.
+ Returns \c true if saving was successful and \c false otherwise.
+ */
+QJsonDocument QGtk3Storage::save() const
+{
+ return QGtk3Json::save(savePalettes());
+}
+
+/*!
+ \internal
+ \brief Loads palette mapping from Json file \param filename.
+
+ Returns \c true if the file was successfully parsed and \c false otherwise.
+ */
+bool QGtk3Storage::load(const QString &filename)
+{
+ return QGtk3Json::load(m_palettes, filename);
+}
+
+/*!
+ \internal
+ \brief Creates a standard palette mapping.
+
+ The method creates a hard coded standard mapping, used if no external Json file
+ containing a valid mapping has been specified in the environment variable {{QT_GUI_GTK_JSON}}.
+ */
+void QGtk3Storage::createMapping()
+{
+ // Hard code standard mapping
+ BrushMap map;
+ Source source;
+
+ // Define a GTK source
+#define GTK(wtype, colorSource, state)\
+ source = Source(QGtk3Interface::QGtkWidget::gtk_ ##wtype,\
+ QGtk3Interface::QGtkColorSource::colorSource, GTK_STATE_FLAG_ ##state)
+
+ // Define a modified source
+#define LIGHTER(group, role, lighter)\
+ source = Source(QPalette::group, QPalette::role,\
+ Qt::ColorScheme::Unknown, lighter)
+#define MODIFY(group, role, red, green, blue)\
+ source = Source(QPalette::group, QPalette::role,\
+ Qt::ColorScheme::Unknown, red, green, blue)
+
+ // Define fixed source
+#define FIX(color) source = FixedSource(color);
+
+ // Add the source to a target brush
+ // Use default Qt::ColorScheme::Unknown, if no color scheme was specified
+#define ADD_2(group, role) map.insert(TargetBrush(QPalette::group, QPalette::role), source);
+#define ADD_3(group, role, app) map.insert(TargetBrush(QPalette::group, QPalette::role,\
+ Qt::ColorScheme::app), source);
+#define ADD_X(x, group, role, app, FUNC, ...) FUNC
+#define ADD(...) ADD_X(,##__VA_ARGS__, ADD_3(__VA_ARGS__), ADD_2(__VA_ARGS__))
+ // Save target brushes to a palette type
+#define SAVE(palette) m_palettes.insert(QPlatformTheme::palette, map)
+ // Clear brushes to start next palette
+#define CLEAR map.clear()
+
+ /*
+ Macro usage:
+
+ 1. Define a source
+ GTK(QGtkWidget, QGtkColorSource, GTK_STATE_FLAG)
+ Fetch the color from a GtkWidget, related to a source and a state.
+
+ LIGHTER(ColorGroup, ColorROle, lighter)
+ Use a color of the same QPalette related to ColorGroup and ColorRole.
+ Make the color lighter (if lighter >100) or darker (if lighter < 100)
+
+ MODIFY(ColorGroup, ColorRole, red, green, blue)
+ Use a color of the same QPalette related to ColorGroup and ColorRole.
+ Modify it by adding red, green, blue.
+
+ FIX(const QBrush &)
+ Use a fixed brush without querying GTK
+
+ 2. Define the target
+ Use ADD(ColorGroup, ColorRole) to use the defined source for the
+ color group / role in the current palette.
+
+ Use ADD(ColorGroup, ColorRole, ColorScheme) to use the defined source
+ only for a specific color scheme
+
+ 3. Save mapping
+ Save the defined mappings for a specific palette.
+ If a mapping entry does not cover all color groups and roles of a palette,
+ the system palette will be used for the remaining values.
+ If the system palette does not have all combination of color groups and roles,
+ the remaining ones will be populated by a hard coded fusion-style like palette.
+
+ 4. Clear mapping
+ Use CLEAR to clear the mapping and begin a new one.
+ */
+
+
+ // System palette
+ {
+ // background color and calculate derivates
+ GTK(Default, Background, INSENSITIVE);
+ ADD(All, Window);
+ ADD(All, Button);
+ ADD(All, Base);
+ LIGHTER(Normal, Window, 125);
+ ADD(Normal, Light);
+ ADD(Inactive, Light);
+ LIGHTER(Normal, Window, 70);
+ ADD(Normal, Shadow);
+ LIGHTER(Normal, Window, 80);
+ ADD(Normal, Dark);
+ ADD(Inactive, Dark)
+
+ GTK(button, Foreground, ACTIVE);
+ ADD(Inactive, WindowText);
+ LIGHTER(Normal, WindowText, 50);
+ ADD(Disabled, Text);
+ ADD(Disabled, WindowText);
+ ADD(Disabled, ButtonText);
+
+ GTK(button, Text, NORMAL);
+ ADD(Inactive, ButtonText);
+
+ // special background colors
+ GTK(Default, Background, SELECTED);
+ ADD(Disabled, Highlight);
+ ADD(Normal, Highlight);
+ ADD(Inactive, Highlight);
+
+ GTK(entry, Foreground, SELECTED);
+ ADD(Normal, HighlightedText);
+ ADD(Inactive, HighlightedText);
+
+ // text color and friends
+ GTK(entry, Text, NORMAL);
+ ADD(Normal, ButtonText);
+ ADD(Normal, WindowText);
+ ADD(Disabled, HighlightedText);
+
+ GTK(Default, Text, NORMAL);
+ ADD(Normal, Text);
+ ADD(Inactive, Text);
+ ADD(Normal, HighlightedText);
+ LIGHTER(Normal, Base, 93);
+ ADD(All, AlternateBase);
+
+ GTK(Default, Foreground, NORMAL);
+ MODIFY(Normal, Text, 100, 100, 100);
+ ADD(All, PlaceholderText, Light);
+ MODIFY(Normal, Text, -100, -100, -100);
+ ADD(All, PlaceholderText, Dark);
+
+ // Light, midlight, dark, mid, shadow colors
+ LIGHTER(Normal, Button, 125);
+ ADD(All, Light)
+ LIGHTER(Normal, Button, 113);
+ ADD(All, Midlight)
+ LIGHTER(Normal, Button, 113);
+ ADD(All, Mid)
+ LIGHTER(Normal, Button, 87);
+ ADD(All, Dark)
+ LIGHTER(Normal, Button, 5);
+ ADD(All, Shadow)
+
+ SAVE(SystemPalette);
+ CLEAR;
+ }
+
+ // Label and TabBar Palette
+ {
+ GTK(entry, Text, NORMAL);
+ ADD(Normal, WindowText);
+ ADD(Inactive, WindowText);
+
+ SAVE(LabelPalette);
+ SAVE(TabBarPalette);
+ CLEAR;
+ }
+
+ // Checkbox and RadioButton Palette
+ {
+ GTK(button, Text, ACTIVE);
+ ADD(Normal, Base, Dark);
+ ADD(Inactive, WindowText, Dark);
+
+ GTK(Default, Foreground, NORMAL);
+ ADD(All, Text);
+
+ GTK(Default, Background, NORMAL);
+ ADD(All, Base);
+
+ GTK(button, Text, NORMAL);
+ ADD(Normal, Base, Light);
+ ADD(Inactive, WindowText, Light);
+
+ SAVE(CheckBoxPalette);
+ SAVE(RadioButtonPalette);
+ CLEAR;
+ }
+
+ // ComboBox, GroupBox & Frame Palette
+ {
+ GTK(combo_box, Text, NORMAL);
+ ADD(Normal, ButtonText, Dark);
+ ADD(Normal, Text, Dark);
+ ADD(Inactive, WindowText, Dark);
+
+ GTK(combo_box, Text, ACTIVE);
+ ADD(Normal, ButtonText, Light);
+ ADD(Normal, Text, Light);
+ ADD(Inactive, WindowText, Light);
+
+ SAVE(ComboBoxPalette);
+ SAVE(GroupBoxPalette);
+ CLEAR;
+ }
+
+ // MenuBar Palette
+ {
+ GTK(Default, Text, ACTIVE);
+ ADD(Normal, ButtonText);
+ SAVE(MenuPalette);
+ CLEAR;
+ }
+
+ // LineEdit Palette
+ {
+ GTK(Default, Background, NORMAL);
+ ADD(All, Base);
+ SAVE(TextLineEditPalette);
+ CLEAR;
+ }
+
+#undef GTK
+#undef REC
+#undef FIX
+#undef ADD
+#undef ADD_2
+#undef ADD_3
+#undef ADD_X
+#undef SAVE
+#undef LOAD
+}
+
+QT_END_NAMESPACE
diff --git a/src/plugins/platformthemes/gtk3/qgtk3storage_p.h b/src/plugins/platformthemes/gtk3/qgtk3storage_p.h
new file mode 100644
index 0000000000..37c5bf57ff
--- /dev/null
+++ b/src/plugins/platformthemes/gtk3/qgtk3storage_p.h
@@ -0,0 +1,235 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QGTK3STORAGE_P_H
+#define QGTK3STORAGE_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include "qgtk3interface_p.h"
+
+#include <QtCore/QJsonDocument>
+#include <QtCore/QCache>
+#include <QtCore/QString>
+#include <QtGui/QGuiApplication>
+#include <QtGui/QPalette>
+
+#include <qpa/qplatformtheme.h>
+#include <private/qflatmap_p.h>
+
+QT_BEGIN_NAMESPACE
+class QGtk3Storage
+{
+ Q_GADGET
+public:
+ QGtk3Storage();
+
+ // Enum documented in cpp file. Please keep it in line with updates made here.
+ enum class SourceType {
+ Gtk,
+ Fixed,
+ Modified,
+ Invalid
+ };
+ Q_ENUM(SourceType)
+
+ // Standard GTK source: Populate a brush from GTK
+ struct Gtk3Source {
+ QGtk3Interface::QGtkWidget gtkWidgetType;
+ QGtk3Interface::QGtkColorSource source;
+ GtkStateFlags state;
+ int width = -1;
+ int height = -1;
+ QDebug operator<<(QDebug dbg)
+ {
+ return dbg << "QGtkStorage::Gtk3Source(gtkwidgetType=" << gtkWidgetType << ", source="
+ << source << ", state=" << state << ", width=" << width << ", height="
+ << height << ")";
+ }
+ };
+
+ // Recursive source: Populate a brush by altering another source
+ struct RecursiveSource {
+ QPalette::ColorGroup colorGroup;
+ QPalette::ColorRole colorRole;
+ Qt::ColorScheme colorScheme;
+ int lighter = 100;
+ int deltaRed = 0;
+ int deltaGreen = 0;
+ int deltaBlue = 0;
+ int width = -1;
+ int height = -1;
+ QDebug operator<<(QDebug dbg)
+ {
+ return dbg << "QGtkStorage::RecursiceSource(colorGroup=" << colorGroup << ", colorRole="
+ << colorRole << ", colorScheme=" << colorScheme << ", lighter=" << lighter
+ << ", deltaRed="<< deltaRed << "deltaBlue =" << deltaBlue << "deltaGreen="
+ << deltaGreen << ", width=" << width << ", height=" << height << ")";
+ }
+ };
+
+ // Fixed source: Populate a brush with fixed values rather than reading GTK
+ struct FixedSource {
+ QBrush fixedBrush;
+ QDebug operator<<(QDebug dbg)
+ {
+ return dbg << "QGtkStorage::FixedSource(" << fixedBrush << ")";
+ }
+ };
+
+ // Data source for brushes
+ struct Source {
+ SourceType sourceType = SourceType::Invalid;
+ Gtk3Source gtk3;
+ RecursiveSource rec;
+ FixedSource fix;
+
+ // GTK constructor
+ Source(QGtk3Interface::QGtkWidget wtype, QGtk3Interface::QGtkColorSource csource,
+ GtkStateFlags cstate, int bwidth = -1, int bheight = -1) : sourceType(SourceType::Gtk)
+ {
+ gtk3.gtkWidgetType = wtype;
+ gtk3.source = csource;
+ gtk3.state = cstate;
+ gtk3.width = bwidth;
+ gtk3.height = bheight;
+ }
+
+ // Recursive constructor for darker/lighter colors
+ Source(QPalette::ColorGroup group, QPalette::ColorRole role,
+ Qt::ColorScheme scheme, int p_lighter = 100)
+ : sourceType(SourceType::Modified)
+ {
+ rec.colorGroup = group;
+ rec.colorRole = role;
+ rec.colorScheme = scheme;
+ rec.lighter = p_lighter;
+ }
+
+ // Recursive ocnstructor for color modification
+ Source(QPalette::ColorGroup group, QPalette::ColorRole role,
+ Qt::ColorScheme scheme, int p_red, int p_green, int p_blue)
+ : sourceType(SourceType::Modified)
+ {
+ rec.colorGroup = group;
+ rec.colorRole = role;
+ rec.colorScheme = scheme;
+ rec.deltaRed = p_red;
+ rec.deltaGreen = p_green;
+ rec.deltaBlue = p_blue;
+ }
+
+ // Recursive constructor for all: color modification and darker/lighter
+ Source(QPalette::ColorGroup group, QPalette::ColorRole role,
+ Qt::ColorScheme scheme, int p_lighter,
+ int p_red, int p_green, int p_blue) : sourceType(SourceType::Modified)
+ {
+ rec.colorGroup = group;
+ rec.colorRole = role;
+ rec.colorScheme = scheme;
+ rec.lighter = p_lighter;
+ rec.deltaRed = p_red;
+ rec.deltaGreen = p_green;
+ rec.deltaBlue = p_blue;
+ }
+
+ // Fixed Source constructor
+ Source(const QBrush &brush) : sourceType(SourceType::Fixed)
+ {
+ fix.fixedBrush = brush;
+ };
+
+ // Invalid constructor and getter
+ Source() : sourceType(SourceType::Invalid) {};
+ bool isValid() const { return sourceType != SourceType::Invalid; }
+
+ // Debug
+ QDebug operator<<(QDebug dbg)
+ {
+ return dbg << "QGtk3Storage::Source(sourceType=" << sourceType << ")";
+ }
+ };
+
+ // Struct with key attributes to identify a brush: color group, color role and color scheme
+ struct TargetBrush {
+ QPalette::ColorGroup colorGroup;
+ QPalette::ColorRole colorRole;
+ Qt::ColorScheme colorScheme;
+
+ // Generic constructor
+ TargetBrush(QPalette::ColorGroup group, QPalette::ColorRole role,
+ Qt::ColorScheme scheme = Qt::ColorScheme::Unknown) :
+ colorGroup(group), colorRole(role), colorScheme(scheme) {};
+
+ // Copy constructor with color scheme modifier for dark/light aware search
+ TargetBrush(const TargetBrush &other, Qt::ColorScheme scheme) :
+ colorGroup(other.colorGroup), colorRole(other.colorRole), colorScheme(scheme) {};
+
+ // struct becomes key of a map, so operator< is needed
+ bool operator<(const TargetBrush& other) const {
+ return std::tie(colorGroup, colorRole, colorScheme) <
+ std::tie(other.colorGroup, other.colorRole, other.colorScheme);
+ }
+ };
+
+ // Mapping a palette's brushes to their GTK sources
+ typedef QFlatMap<TargetBrush, Source> BrushMap;
+
+ // Storage of palettes and their GTK sources
+ typedef QFlatMap<QPlatformTheme::Palette, BrushMap> PaletteMap;
+
+ // Public getters
+ const QPalette *palette(QPlatformTheme::Palette = QPlatformTheme::SystemPalette) const;
+ QPixmap standardPixmap(QPlatformTheme::StandardPixmap standardPixmap, const QSizeF &size) const;
+ Qt::ColorScheme colorScheme() const { return m_colorScheme; };
+ static QPalette standardPalette();
+ const QString themeName() const { return m_interface ? m_interface->themeName() : QString(); };
+ const QFont *font(QPlatformTheme::Font type) const;
+ QIcon fileIcon(const QFileInfo &fileInfo) const;
+
+ // Initialization
+ void populateMap();
+ void handleThemeChange();
+
+private:
+ // Storage for palettes and their brushes
+ PaletteMap m_palettes;
+
+ std::unique_ptr<QGtk3Interface> m_interface;
+
+
+ Qt::ColorScheme m_colorScheme = Qt::ColorScheme::Unknown;
+
+ // Caches for Pixmaps, fonts and palettes
+ mutable QCache<QPlatformTheme::StandardPixmap, QImage> m_pixmapCache;
+ mutable std::array<std::optional<QPalette>, QPlatformTheme::Palette::NPalettes> m_paletteCache;
+ mutable std::array<std::optional<QFont>, QPlatformTheme::NFonts> m_fontCache;
+
+ // Search brush with a given GTK3 source
+ QBrush brush(const Source &source, const BrushMap &map) const;
+
+ // Get GTK3 source for a target brush
+ Source brush (const TargetBrush &brush, const BrushMap &map) const;
+
+ // clear cache, palettes and color scheme
+ void clear();
+
+ // Data creation, import & export
+ void createMapping ();
+ const PaletteMap savePalettes() const;
+ bool save(const QString &filename, const QJsonDocument::JsonFormat f = QJsonDocument::Indented) const;
+ QJsonDocument save() const;
+ bool load(const QString &filename);
+};
+
+QT_END_NAMESPACE
+#endif // QGTK3STORAGE_H
diff --git a/src/plugins/platformthemes/gtk3/qgtk3theme.cpp b/src/plugins/platformthemes/gtk3/qgtk3theme.cpp
index 8551966678..9d23ba7e48 100644
--- a/src/plugins/platformthemes/gtk3/qgtk3theme.cpp
+++ b/src/plugins/platformthemes/gtk3/qgtk3theme.cpp
@@ -5,8 +5,8 @@
#include "qgtk3dialoghelpers.h"
#include "qgtk3menu.h"
#include <QVariant>
-#include <QtCore/qregularexpression.h>
#include <QGuiApplication>
+#include <qpa/qwindowsysteminterface.h>
#undef signals
#include <gtk/gtk.h>
@@ -82,6 +82,29 @@ QGtk3Theme::QGtk3Theme()
/* Use our custom log handler. */
g_log_set_handler("Gtk", G_LOG_LEVEL_MESSAGE, gtkMessageHandler, nullptr);
+
+#define SETTING_CONNECT(setting) g_signal_connect(settings, "notify::" setting, G_CALLBACK(notifyThemeChanged), nullptr)
+ auto notifyThemeChanged = [] {
+ QWindowSystemInterface::handleThemeChange();
+ };
+
+ GtkSettings *settings = gtk_settings_get_default();
+ SETTING_CONNECT("gtk-cursor-blink-time");
+ SETTING_CONNECT("gtk-double-click-distance");
+ SETTING_CONNECT("gtk-double-click-time");
+ SETTING_CONNECT("gtk-long-press-time");
+ SETTING_CONNECT("gtk-entry-password-hint-timeout");
+ SETTING_CONNECT("gtk-dnd-drag-threshold");
+ SETTING_CONNECT("gtk-icon-theme-name");
+ SETTING_CONNECT("gtk-fallback-icon-theme");
+ SETTING_CONNECT("gtk-font-name");
+ SETTING_CONNECT("gtk-application-prefer-dark-theme");
+ SETTING_CONNECT("gtk-theme-name");
+ SETTING_CONNECT("gtk-cursor-theme-name");
+ SETTING_CONNECT("gtk-cursor-theme-size");
+#undef SETTING_CONNECT
+
+ m_storage.reset(new QGtk3Storage);
}
static inline QVariant gtkGetLongPressTime()
@@ -116,8 +139,14 @@ QVariant QGtk3Theme::themeHint(QPlatformTheme::ThemeHint hint) const
return QVariant(gtkSetting("gtk-icon-theme-name"));
case QPlatformTheme::SystemIconFallbackThemeName:
return QVariant(gtkSetting("gtk-fallback-icon-theme"));
- case QPlatformTheme::PreselectFirstFileInDirectory:
- return true;
+ case QPlatformTheme::MouseCursorTheme:
+ return QVariant(gtkSetting("gtk-cursor-theme-name"));
+ case QPlatformTheme::MouseCursorSize: {
+ int s = gtkSetting<gint>("gtk-cursor-theme-size");
+ if (s > 0)
+ return QVariant(QSize(s, s));
+ return QGnomeTheme::themeHint(hint);
+ }
default:
return QGnomeTheme::themeHint(hint);
}
@@ -131,45 +160,10 @@ QString QGtk3Theme::gtkFontName() const
return QGnomeTheme::gtkFontName();
}
-QPlatformTheme::Appearance QGtk3Theme::appearance() const
+Qt::ColorScheme QGtk3Theme::colorScheme() const
{
- /*
- https://docs.gtk.org/gtk3/running.html
-
- It's possible to set a theme variant after the theme name when using GTK_THEME:
-
- GTK_THEME=Adwaita:dark
-
- Some themes also have "-dark" as part of their name.
-
- We test this environment variable first because the documentation says
- it's mainly used for easy debugging, so it should be possible to use it
- to override any other settings.
- */
- QString themeName = qEnvironmentVariable("GTK_THEME");
- const QRegularExpression darkRegex(QStringLiteral("[:-]dark"), QRegularExpression::CaseInsensitiveOption);
- if (!themeName.isEmpty())
- return darkRegex.match(themeName).hasMatch() ? Appearance::Dark : Appearance::Light;
-
- /*
- https://docs.gtk.org/gtk3/property.Settings.gtk-application-prefer-dark-theme.html
-
- This setting controls which theme is used when the theme specified by
- gtk-theme-name provides both light and dark variants. We can save a
- regex check by testing this property first.
- */
- const auto preferDark = gtkSetting<bool>("gtk-application-prefer-dark-theme");
- if (preferDark)
- return Appearance::Dark;
-
- /*
- https://docs.gtk.org/gtk3/property.Settings.gtk-theme-name.html
- */
- themeName = gtkSetting("gtk-theme-name");
- if (!themeName.isEmpty())
- return darkRegex.match(themeName).hasMatch() ? Appearance::Dark : Appearance::Light;
-
- return Appearance::Unknown;
+ Q_ASSERT(m_storage);
+ return m_storage->colorScheme();
}
bool QGtk3Theme::usePlatformNativeDialog(DialogType type) const
@@ -225,4 +219,30 @@ bool QGtk3Theme::useNativeFileDialog()
return gtk_check_version(3, 15, 5) == nullptr;
}
+const QPalette *QGtk3Theme::palette(Palette type) const
+{
+ Q_ASSERT(m_storage);
+ return m_storage->palette(type);
+}
+
+QPixmap QGtk3Theme::standardPixmap(StandardPixmap sp, const QSizeF &size) const
+{
+ Q_ASSERT(m_storage);
+ return m_storage->standardPixmap(sp, size);
+}
+
+const QFont *QGtk3Theme::font(Font type) const
+{
+ Q_ASSERT(m_storage);
+ return m_storage->font(type);
+}
+
+QIcon QGtk3Theme::fileIcon(const QFileInfo &fileInfo,
+ QPlatformTheme::IconOptions iconOptions) const
+{
+ Q_UNUSED(iconOptions);
+ Q_ASSERT(m_storage);
+ return m_storage->fileIcon(fileInfo);
+}
+
QT_END_NAMESPACE
diff --git a/src/plugins/platformthemes/gtk3/qgtk3theme.h b/src/plugins/platformthemes/gtk3/qgtk3theme.h
index 8c7a1da7cc..2828cc56e6 100644
--- a/src/plugins/platformthemes/gtk3/qgtk3theme.h
+++ b/src/plugins/platformthemes/gtk3/qgtk3theme.h
@@ -6,6 +6,7 @@
#include <private/qtguiglobal_p.h>
#include <private/qgenericunixthemes_p.h>
+#include "qgtk3storage_p.h"
QT_BEGIN_NAMESPACE
@@ -17,7 +18,7 @@ public:
virtual QVariant themeHint(ThemeHint hint) const override;
virtual QString gtkFontName() const override;
- Appearance appearance() const override;
+ Qt::ColorScheme colorScheme() const override;
bool usePlatformNativeDialog(DialogType type) const override;
QPlatformDialogHelper *createPlatformDialogHelper(DialogType type) const override;
@@ -25,9 +26,16 @@ public:
QPlatformMenu* createPlatformMenu() const override;
QPlatformMenuItem* createPlatformMenuItem() const override;
+ const QPalette *palette(Palette type = SystemPalette) const override;
+ const QFont *font(Font type = SystemFont) const override;
+ QPixmap standardPixmap(StandardPixmap sp, const QSizeF &size) const override;
+ QIcon fileIcon(const QFileInfo &fileInfo,
+ QPlatformTheme::IconOptions iconOptions = { }) const override;
+
static const char *name;
private:
static bool useNativeFileDialog();
+ std::unique_ptr<QGtk3Storage> m_storage;
};
QT_END_NAMESPACE
diff --git a/src/plugins/platformthemes/xdgdesktopportal/CMakeLists.txt b/src/plugins/platformthemes/xdgdesktopportal/CMakeLists.txt
index 960ea3f0d0..6228e83ec7 100644
--- a/src/plugins/platformthemes/xdgdesktopportal/CMakeLists.txt
+++ b/src/plugins/platformthemes/xdgdesktopportal/CMakeLists.txt
@@ -1,7 +1,5 @@
# Copyright (C) 2022 The Qt Company Ltd.
-# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-
-# Generated from xdgdesktopportal.pro.
+# SPDX-License-Identifier: BSD-3-Clause
#####################################################################
## QXdgDesktopPortalThemePlugin Plugin:
@@ -22,6 +20,3 @@ qt_internal_add_plugin(QXdgDesktopPortalThemePlugin
Qt::Gui
Qt::GuiPrivate
)
-
-#### Keys ignored in scope 1:.:.:xdgdesktopportal.pro:<TRUE>:
-# PLUGIN_EXTENDS = "-"
diff --git a/src/plugins/platformthemes/xdgdesktopportal/qxdgdesktopportalfiledialog.cpp b/src/plugins/platformthemes/xdgdesktopportal/qxdgdesktopportalfiledialog.cpp
index 2ef591a591..1c162be8fc 100644
--- a/src/plugins/platformthemes/xdgdesktopportal/qxdgdesktopportalfiledialog.cpp
+++ b/src/plugins/platformthemes/xdgdesktopportal/qxdgdesktopportalfiledialog.cpp
@@ -169,16 +169,18 @@ void QXdgDesktopPortalFileDialog::openPortal(Qt::WindowFlags windowFlags, Qt::Wi
options.insert("multiple"_L1, d->multipleFiles);
options.insert("directory"_L1, d->directoryMode);
- if (d->saveFile) {
- if (!d->directory.isEmpty())
- options.insert("current_folder"_L1, QFile::encodeName(d->directory).append('\0'));
-
- if (!d->selectedFiles.isEmpty()) {
- // current_file for the file to be pre-selected, current_name for the file name to be pre-filled
- // current_file accepts absolute path while current_name accepts just file name
- options.insert("current_file"_L1, QFile::encodeName(d->selectedFiles.first()).append('\0'));
- options.insert("current_name"_L1, QFileInfo(d->selectedFiles.first()).fileName());
- }
+ if (!d->directory.isEmpty())
+ options.insert("current_folder"_L1, QFile::encodeName(d->directory).append('\0'));
+
+ if (d->saveFile && !d->selectedFiles.isEmpty()) {
+ // current_file for the file to be pre-selected, current_name for the file name to be
+ // pre-filled current_file accepts absolute path and requires the file to exist while
+ // current_name accepts just file name
+ QFileInfo selectedFileInfo(d->selectedFiles.constFirst());
+ if (selectedFileInfo.exists())
+ options.insert("current_file"_L1,
+ QFile::encodeName(d->selectedFiles.constFirst()).append('\0'));
+ options.insert("current_name"_L1, selectedFileInfo.fileName());
}
// Insert filters
@@ -211,6 +213,9 @@ void QXdgDesktopPortalFileDialog::openPortal(Qt::WindowFlags windowFlags, Qt::Wi
filter.name = mimeType.comment();
filter.filterConditions = filterConditions;
+ if (filter.name.isEmpty())
+ filter.name = mimeTypefilter;
+
filterList << filter;
if (!d->selectedMimeTypeFilter.isEmpty() && d->selectedMimeTypeFilter == mimeTypefilter)
diff --git a/src/plugins/platformthemes/xdgdesktopportal/qxdgdesktopportaltheme.cpp b/src/plugins/platformthemes/xdgdesktopportal/qxdgdesktopportaltheme.cpp
index e6a72e4289..7e2a9daacb 100644
--- a/src/plugins/platformthemes/xdgdesktopportal/qxdgdesktopportaltheme.cpp
+++ b/src/plugins/platformthemes/xdgdesktopportal/qxdgdesktopportaltheme.cpp
@@ -40,7 +40,7 @@ public:
/*! \internal
- Converts the given Freedesktop color scheme setting \a colorschemePref to a QPlatformTheme::Appearance value.
+ Converts the given Freedesktop color scheme setting \a colorschemePref to a Qt::ColorScheme value.
Specification: https://github.com/flatpak/xdg-desktop-portal/blob/d7a304a00697d7d608821253cd013f3b97ac0fb6/data/org.freedesktop.impl.portal.Settings.xml#L33-L45
Unfortunately the enum numerical values are not defined identically, so we have to convert them.
@@ -53,18 +53,18 @@ public:
1: Prefer dark appearance | 2: Dark
2: Prefer light appearance | 1: Light
*/
- static QPlatformTheme::Appearance appearanceFromXdgPref(const XdgColorschemePref colorschemePref)
+ static Qt::ColorScheme colorSchemeFromXdgPref(const XdgColorschemePref colorschemePref)
{
switch (colorschemePref) {
- case PreferDark: return QPlatformTheme::Appearance::Dark;
- case PreferLight: return QPlatformTheme::Appearance::Light;
- default: return QPlatformTheme::Appearance::Unknown;
+ case PreferDark: return Qt::ColorScheme::Dark;
+ case PreferLight: return Qt::ColorScheme::Light;
+ default: return Qt::ColorScheme::Unknown;
}
}
QPlatformTheme *baseTheme = nullptr;
uint fileChooserPortalVersion = 0;
- QPlatformTheme::Appearance appearance = QPlatformTheme::Appearance::Unknown;
+ Qt::ColorScheme colorScheme = Qt::ColorScheme::Unknown;
};
QXdgDesktopPortalTheme::QXdgDesktopPortalTheme()
@@ -75,7 +75,7 @@ QXdgDesktopPortalTheme::QXdgDesktopPortalTheme()
QStringList themeNames;
themeNames += QGuiApplicationPrivate::platform_integration->themeNames();
// 1) Look for a theme plugin.
- for (const QString &themeName : qAsConst(themeNames)) {
+ for (const QString &themeName : std::as_const(themeNames)) {
d->baseTheme = QPlatformThemeFactory::create(themeName, nullptr);
if (d->baseTheme)
break;
@@ -84,7 +84,7 @@ QXdgDesktopPortalTheme::QXdgDesktopPortalTheme()
// 2) If no theme plugin was found ask the platform integration to
// create a theme
if (!d->baseTheme) {
- for (const QString &themeName : qAsConst(themeNames)) {
+ for (const QString &themeName : std::as_const(themeNames)) {
d->baseTheme = QGuiApplicationPrivate::platform_integration->createPlatformTheme(themeName);
if (d->baseTheme)
break;
@@ -104,7 +104,7 @@ QXdgDesktopPortalTheme::QXdgDesktopPortalTheme()
message << "org.freedesktop.portal.FileChooser"_L1 << "version"_L1;
QDBusPendingCall pendingCall = QDBusConnection::sessionBus().asyncCall(message);
QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pendingCall);
- QObject::connect(watcher, &QDBusPendingCallWatcher::finished, [d] (QDBusPendingCallWatcher *watcher) {
+ QObject::connect(watcher, &QDBusPendingCallWatcher::finished, watcher, [d] (QDBusPendingCallWatcher *watcher) {
QDBusPendingReply<QVariant> reply = *watcher;
if (reply.isValid()) {
d->fileChooserPortalVersion = reply.value().toUInt();
@@ -124,7 +124,7 @@ QXdgDesktopPortalTheme::QXdgDesktopPortalTheme()
if (reply.isValid()) {
const QDBusVariant dbusVariant = qvariant_cast<QDBusVariant>(reply.value());
const QXdgDesktopPortalThemePrivate::XdgColorschemePref xdgPref = static_cast<QXdgDesktopPortalThemePrivate::XdgColorschemePref>(dbusVariant.variant().toUInt());
- d->appearance = QXdgDesktopPortalThemePrivate::appearanceFromXdgPref(xdgPref);
+ d->colorScheme = QXdgDesktopPortalThemePrivate::colorSchemeFromXdgPref(xdgPref);
}
}
@@ -205,10 +205,12 @@ QVariant QXdgDesktopPortalTheme::themeHint(ThemeHint hint) const
return d->baseTheme->themeHint(hint);
}
-QPlatformTheme::Appearance QXdgDesktopPortalTheme::appearance() const
+Qt::ColorScheme QXdgDesktopPortalTheme::colorScheme() const
{
Q_D(const QXdgDesktopPortalTheme);
- return d->appearance;
+ if (d->colorScheme == Qt::ColorScheme::Unknown)
+ return d->baseTheme->colorScheme();
+ return d->colorScheme;
}
QPixmap QXdgDesktopPortalTheme::standardPixmap(StandardPixmap sp, const QSizeF &size) const
diff --git a/src/plugins/platformthemes/xdgdesktopportal/qxdgdesktopportaltheme.h b/src/plugins/platformthemes/xdgdesktopportal/qxdgdesktopportaltheme.h
index 4390ab73d8..1ac04c45e6 100644
--- a/src/plugins/platformthemes/xdgdesktopportal/qxdgdesktopportaltheme.h
+++ b/src/plugins/platformthemes/xdgdesktopportal/qxdgdesktopportaltheme.h
@@ -34,7 +34,7 @@ public:
QVariant themeHint(ThemeHint hint) const override;
- Appearance appearance() const override;
+ Qt::ColorScheme colorScheme() const override;
QPixmap standardPixmap(StandardPixmap sp, const QSizeF &size) const override;
QIcon fileIcon(const QFileInfo &fileInfo,
diff --git a/src/plugins/printsupport/CMakeLists.txt b/src/plugins/printsupport/CMakeLists.txt
index dcf3c200d7..7736684762 100644
--- a/src/plugins/printsupport/CMakeLists.txt
+++ b/src/plugins/printsupport/CMakeLists.txt
@@ -1,7 +1,5 @@
# Copyright (C) 2022 The Qt Company Ltd.
-# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-
-# Generated from printsupport.pro.
+# SPDX-License-Identifier: BSD-3-Clause
if(QT_FEATURE_cups AND UNIX AND NOT APPLE)
add_subdirectory(cups)
diff --git a/src/plugins/printsupport/cups/CMakeLists.txt b/src/plugins/printsupport/cups/CMakeLists.txt
index 419387b9fd..b6400d2d36 100644
--- a/src/plugins/printsupport/cups/CMakeLists.txt
+++ b/src/plugins/printsupport/cups/CMakeLists.txt
@@ -1,9 +1,7 @@
# Copyright (C) 2022 The Qt Company Ltd.
-# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+# SPDX-License-Identifier: BSD-3-Clause
-# Generated from cups.pro.
-
-qt_find_package(Cups PROVIDED_TARGETS Cups::Cups) # special case
+qt_find_package(Cups PROVIDED_TARGETS Cups::Cups)
#####################################################################
## QCupsPrinterSupportPlugin Plugin:
@@ -28,7 +26,3 @@ qt_internal_add_plugin(QCupsPrinterSupportPlugin
Qt::PrintSupport
Qt::PrintSupportPrivate
)
-
-#### Keys ignored in scope 1:.:.:cups.pro:<TRUE>:
-# MODULE = "cupsprintersupport"
-# OTHER_FILES = "cups.json"
diff --git a/src/plugins/printsupport/cups/qcupsprintengine.cpp b/src/plugins/printsupport/cups/qcupsprintengine.cpp
index b69d36ab8c..6c50c11c0f 100644
--- a/src/plugins/printsupport/cups/qcupsprintengine.cpp
+++ b/src/plugins/printsupport/cups/qcupsprintengine.cpp
@@ -144,7 +144,20 @@ bool QCupsPrintEnginePrivate::openPrintDevice()
}
cupsTempFile = QString::fromLocal8Bit(filename);
outDevice = new QFile();
- static_cast<QFile *>(outDevice)->open(fd, QIODevice::WriteOnly);
+ if (!static_cast<QFile *>(outDevice)->open(fd, QIODevice::WriteOnly)) {
+ qWarning("QPdfPrinter: Could not open CUPS temporary file descriptor: %s",
+ qPrintable(outDevice->errorString()));
+ delete outDevice;
+ outDevice = nullptr;
+
+#if defined(Q_OS_WIN) && defined(Q_CC_MSVC)
+ ::_close(fd);
+#else
+ ::close(fd);
+#endif
+ fd = -1;
+ return false;
+ }
}
return true;
@@ -249,9 +262,12 @@ void QCupsPrintEnginePrivate::changePrinter(const QString &newPrinter)
duplex = m_printDevice.defaultDuplexMode();
duplexRequestedExplicitly = false;
}
- QPrint::ColorMode colorMode = grayscale ? QPrint::GrayScale : QPrint::Color;
- if (!m_printDevice.supportedColorModes().contains(colorMode))
- grayscale = m_printDevice.defaultColorMode() == QPrint::GrayScale;
+ QPrint::ColorMode colorMode = static_cast<QPrint::ColorMode>(printerColorMode());
+ if (!m_printDevice.supportedColorModes().contains(colorMode)) {
+ colorModel = (m_printDevice.defaultColorMode() == QPrint::GrayScale)
+ ? QPdfEngine::ColorModel::Grayscale
+ : QPdfEngine::ColorModel::RGB;
+ }
// Get the equivalent page size for this printer as supported names may be different
if (m_printDevice.supportedPageSize(m_pageLayout.pageSize()).isValid())
diff --git a/src/plugins/printsupport/cups/qcupsprintersupport.cpp b/src/plugins/printsupport/cups/qcupsprintersupport.cpp
index 539cd55c54..6578d8a558 100644
--- a/src/plugins/printsupport/cups/qcupsprintersupport.cpp
+++ b/src/plugins/printsupport/cups/qcupsprintersupport.cpp
@@ -11,14 +11,14 @@
#include <QtPrintSupport/QPrinterInfo>
-#if QT_CONFIG(dialogbuttonbox)
+#if QT_CONFIG(cupspassworddialog)
#include <QGuiApplication>
#include <QDialog>
#include <QDialogButtonBox>
#include <QFormLayout>
#include <QLabel>
#include <QLineEdit>
-#endif // QT_CONFIG(dialogbuttonbox)
+#endif // QT_CONFIG(cupspassworddialog)
#include <cups/ppd.h>
#ifndef QT_LINUXBASE // LSB merges everything into cups.h
@@ -27,7 +27,7 @@
QT_BEGIN_NAMESPACE
-#if QT_CONFIG(dialogbuttonbox)
+#if QT_CONFIG(cupspassworddialog)
static const char *getPasswordCB(const char */*prompt*/, http_t *http, const char */*method*/, const char *resource, void */*user_data*/)
{
// cups doesn't free the const char * we return so keep around
@@ -57,7 +57,7 @@ static const char *getPasswordCB(const char */*prompt*/, http_t *http, const cha
QString resourceString = QString::fromLocal8Bit(resource);
if (resourceString.startsWith(QStringLiteral("/printers/")))
- resourceString = resourceString.mid(QStringLiteral("/printers/").length());
+ resourceString = resourceString.mid(QStringLiteral("/printers/").size());
QLabel *label = new QLabel();
if (hostname == QStringLiteral("localhost")) {
@@ -89,16 +89,16 @@ static const char *getPasswordCB(const char */*prompt*/, http_t *http, const cha
return password.constData();
}
-#endif // QT_CONFIG(dialogbuttonbox)
+#endif // QT_CONFIG(cupspassworddialog)
QCupsPrinterSupport::QCupsPrinterSupport()
: QPlatformPrinterSupport()
{
-#if QT_CONFIG(dialogbuttonbox)
+#if QT_CONFIG(cupspassworddialog)
// Only show password dialog if GUI application
if (qobject_cast<QGuiApplication*>(QCoreApplication::instance()))
cupsSetPasswordCB2(getPasswordCB, nullptr /* user_data */ );
-#endif // QT_CONFIG(dialogbuttonbox)
+#endif // QT_CONFIG(cupspassworddialog)
}
QCupsPrinterSupport::~QCupsPrinterSupport()
diff --git a/src/plugins/printsupport/cups/qppdprintdevice.cpp b/src/plugins/printsupport/cups/qppdprintdevice.cpp
index 487b02784e..95813c90fa 100644
--- a/src/plugins/printsupport/cups/qppdprintdevice.cpp
+++ b/src/plugins/printsupport/cups/qppdprintdevice.cpp
@@ -428,7 +428,7 @@ bool QPpdPrintDevice::setProperty(QPrintDevice::PrintDevicePropertyKey key, cons
{
if (key == PDPK_PpdOption) {
const QStringList values = value.toStringList();
- if (values.count() == 2) {
+ if (values.size() == 2) {
ppdMarkOption(m_ppd, values[0].toLatin1(), values[1].toLatin1());
return true;
}
@@ -441,7 +441,7 @@ bool QPpdPrintDevice::isFeatureAvailable(QPrintDevice::PrintDevicePropertyKey ke
{
if (key == PDPK_PpdChoiceIsInstallableConflict) {
const QStringList values = params.toStringList();
- if (values.count() == 2)
+ if (values.size() == 2)
return ppdInstallableConflict(m_ppd, values[0].toLatin1(), values[1].toLatin1());
}
diff --git a/src/plugins/sqldrivers/.cmake.conf b/src/plugins/sqldrivers/.cmake.conf
index 377be0059e..10bc1fd407 100644
--- a/src/plugins/sqldrivers/.cmake.conf
+++ b/src/plugins/sqldrivers/.cmake.conf
@@ -1 +1 @@
-set(QT_REPO_MODULE_VERSION "6.5.0")
+set(QT_REPO_MODULE_VERSION "6.8.0")
diff --git a/src/plugins/sqldrivers/CMakeLists.txt b/src/plugins/sqldrivers/CMakeLists.txt
index a9348d8de5..43abb00ad1 100644
--- a/src/plugins/sqldrivers/CMakeLists.txt
+++ b/src/plugins/sqldrivers/CMakeLists.txt
@@ -1,12 +1,17 @@
# Copyright (C) 2022 The Qt Company Ltd.
-# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+# SPDX-License-Identifier: BSD-3-Clause
-# Generated from sqldrivers.pro.
-
-# special case begin
cmake_minimum_required(VERSION 3.16)
if (NOT CMAKE_PROJECT_NAME STREQUAL "QtBase" AND NOT CMAKE_PROJECT_NAME STREQUAL "Qt")
include(.cmake.conf)
+ # Store initial build type (if any is specified) to be read by
+ # qt_internal_set_cmake_build_type().
+ # See qt_internal_set_cmake_build_type() for details.
+ if(DEFINED CACHE{CMAKE_BUILD_TYPE})
+ set(__qt_internal_standalone_project_cmake_build_type_before_project_call
+ "${CMAKE_BUILD_TYPE}")
+ endif()
+
project(QSQLiteDriverPlugins
VERSION "${QT_REPO_MODULE_VERSION}"
DESCRIPTION "Qt6 SQL driver plugins"
@@ -51,10 +56,6 @@ if(QT_FEATURE_sql_odbc)
add_subdirectory(odbc)
endif()
-if(QT_FEATURE_sql_tds)
-# TODO add_subdirectory(tds)
-endif()
-
if(QT_FEATURE_sql_oci)
add_subdirectory(oci)
endif()
@@ -67,15 +68,14 @@ if(QT_FEATURE_sql_sqlite)
add_subdirectory(sqlite)
endif()
-if(QT_FEATURE_sql_sqlite2)
-# TODO add_subdirectory(sqlite2)
-endif()
-
if(QT_FEATURE_sql_ibase)
add_subdirectory(ibase)
endif()
+if(QT_FEATURE_sql_mimer)
+ add_subdirectory(mimer)
+endif()
+
if(NOT CMAKE_PROJECT_NAME STREQUAL "QtBase" AND NOT CMAKE_PROJECT_NAME STREQUAL "Qt")
qt_print_feature_summary()
endif()
-# special case end
diff --git a/src/plugins/sqldrivers/configure.cmake b/src/plugins/sqldrivers/configure.cmake
index feebecefa7..534ac020d8 100644
--- a/src/plugins/sqldrivers/configure.cmake
+++ b/src/plugins/sqldrivers/configure.cmake
@@ -1,5 +1,5 @@
# Copyright (C) 2022 The Qt Company Ltd.
-# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+# SPDX-License-Identifier: BSD-3-Clause
@@ -20,6 +20,7 @@ qt_find_package(Oracle PROVIDED_TARGETS Oracle::OCI MODULE_NAME sqldrivers QMAKE
qt_find_package(ODBC PROVIDED_TARGETS ODBC::ODBC MODULE_NAME sqldrivers QMAKE_LIB odbc)
qt_find_package(SQLite3 PROVIDED_TARGETS SQLite::SQLite3 MODULE_NAME sqldrivers QMAKE_LIB sqlite3)
qt_find_package(Interbase PROVIDED_TARGETS Interbase::Interbase MODULE_NAME sqldrivers QMAKE_LIB ibase) # special case
+qt_find_package(Mimer PROVIDED_TARGETS MimerSQL::MimerSQL MODULE_NAME sqldrivers QMAKE_LIB mimer)
if(NOT WIN32 AND QT_FEATURE_system_zlib)
qt_add_qmake_lib_dependency(sqlite3 zlib)
endif()
@@ -64,6 +65,11 @@ qt_feature("system-sqlite" PRIVATE
AUTODETECT OFF
CONDITION QT_FEATURE_sql_sqlite AND SQLite3_FOUND
)
+qt_feature("sql-mimer" PRIVATE
+ LABEL "Mimer"
+ CONDITION Mimer_FOUND
+)
+
qt_configure_add_summary_section(NAME "Qt Sql Drivers")
qt_configure_add_summary_entry(ARGS "sql-db2")
qt_configure_add_summary_entry(ARGS "sql-ibase")
@@ -73,6 +79,7 @@ qt_configure_add_summary_entry(ARGS "sql-odbc")
qt_configure_add_summary_entry(ARGS "sql-psql")
qt_configure_add_summary_entry(ARGS "sql-sqlite")
qt_configure_add_summary_entry(ARGS "system-sqlite")
+qt_configure_add_summary_entry(ARGS "sql-mimer")
qt_configure_end_summary_section() # end of "Qt Sql Drivers" section
qt_configure_add_report_entry(
TYPE WARNING
diff --git a/src/plugins/sqldrivers/db2/CMakeLists.txt b/src/plugins/sqldrivers/db2/CMakeLists.txt
index a3d667245e..119a5b53c9 100644
--- a/src/plugins/sqldrivers/db2/CMakeLists.txt
+++ b/src/plugins/sqldrivers/db2/CMakeLists.txt
@@ -1,7 +1,5 @@
# Copyright (C) 2022 The Qt Company Ltd.
-# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-
-# Generated from db2.pro.
+# SPDX-License-Identifier: BSD-3-Clause
#####################################################################
## QDB2DriverPlugin Plugin:
@@ -23,9 +21,6 @@ qt_internal_add_plugin(QDB2DriverPlugin
Qt::SqlPrivate
)
-#### Keys ignored in scope 1:.:.:db2.pro:<TRUE>:
-# OTHER_FILES = "db2.json"
-
## Scopes:
#####################################################################
diff --git a/src/plugins/sqldrivers/db2/qsql_db2.cpp b/src/plugins/sqldrivers/db2/qsql_db2.cpp
index 2d9293895a..9b6a06c378 100644
--- a/src/plugins/sqldrivers/db2/qsql_db2.cpp
+++ b/src/plugins/sqldrivers/db2/qsql_db2.cpp
@@ -14,6 +14,7 @@
#include <QDebug>
#include <QtSql/private/qsqldriver_p.h>
#include <QtSql/private/qsqlresult_p.h>
+#include "private/qtools_p.h"
#if defined(Q_CC_BOR)
// DB2's sqlsystm.h (included through sqlcli1.h) defines the SQL_BIGINT_TYPE
@@ -43,10 +44,13 @@ class QDB2DriverPrivate : public QSqlDriverPrivate
Q_DECLARE_PUBLIC(QDB2Driver)
public:
- QDB2DriverPrivate() : QSqlDriverPrivate(), hEnv(0), hDbc(0) { dbmsType = QSqlDriver::DB2; }
- SQLHANDLE hEnv;
- SQLHANDLE hDbc;
+ QDB2DriverPrivate() : QSqlDriverPrivate(QSqlDriver::DB2) {}
+ SQLHANDLE hEnv = 0;
+ SQLHANDLE hDbc = 0;
QString user;
+
+ void qSplitTableQualifier(const QString &qualifier, QString &catalog,
+ QString &schema, QString &table) const;
};
class QDB2ResultPrivate;
@@ -301,7 +305,6 @@ static QSqlField qMakeFieldInfo(const QDB2ResultPrivate* d, int i)
// else required is unknown
f.setLength(colSize == 0 ? -1 : int(colSize));
f.setPrecision(colScale == 0 ? -1 : int(colScale));
- f.setSqlType(int(colType));
SQLTCHAR tableName[TABLENAMESIZE];
SQLSMALLINT tableNameLen;
r = SQLColAttribute(d->hStmt, i + 1, SQL_DESC_BASE_TABLE_NAME, tableName,
@@ -461,34 +464,27 @@ static QByteArray qGetBinaryData(SQLHANDLE hStmt, int column, SQLLEN& lengthIndi
return fieldVal;
}
-static void qSplitTableQualifier(const QString & qualifier, QString * catalog,
- QString * schema, QString * table)
+void QDB2DriverPrivate::qSplitTableQualifier(const QString &qualifier, QString &catalog,
+ QString &schema, QString &table) const
{
- if (!catalog || !schema || !table)
- return;
- QStringList l = qualifier.split(u'.');
- if (l.count() > 3)
- return; // can't possibly be a valid table qualifier
- int i = 0, n = l.count();
- if (n == 1) {
- *table = qualifier;
- } else {
- for (QStringList::Iterator it = l.begin(); it != l.end(); ++it) {
- if (n == 3) {
- if (i == 0)
- *catalog = *it;
- else if (i == 1)
- *schema = *it;
- else if (i == 2)
- *table = *it;
- } else if (n == 2) {
- if (i == 0)
- *schema = *it;
- else if (i == 1)
- *table = *it;
- }
- i++;
- }
+ const QList<QStringView> l = QStringView(qualifier).split(u'.');
+ switch (l.count()) {
+ case 1:
+ table = qualifier;
+ break;
+ case 2:
+ schema = l.at(0).toString();
+ table = l.at(1).toString();
+ break;
+ case 3:
+ catalog = l.at(0).toString();
+ schema = l.at(1).toString();
+ table = l.at(2).toString();
+ break;
+ default:
+ qSqlWarning(QString::fromLatin1("QODBCDriver::splitTableQualifier: Unable to split table qualifier '%1'")
+ .arg(qualifier), this);
+ break;
}
}
@@ -508,7 +504,6 @@ static QSqlField qMakeFieldInfo(const SQLHANDLE hStmt)
// else we don't know.
f.setLength(qGetIntData(hStmt, 6, isNull)); // column size
f.setPrecision(qGetIntData(hStmt, 8, isNull)); // precision
- f.setSqlType(type);
return f;
}
@@ -875,7 +870,7 @@ bool QDB2Result::exec()
break; }
case QMetaType::Int:
case QMetaType::Double:
- case QMetaType::ByteArray:
+ case QMetaType::QByteArray:
break;
case QMetaType::QString:
if (bindValueType(i) & QSql::Out)
@@ -1353,7 +1348,7 @@ QSqlRecord QDB2Driver::record(const QString& tableName) const
SQLHANDLE hStmt;
QString catalog, schema, table;
- qSplitTableQualifier(tableName, &catalog, &schema, &table);
+ d->qSplitTableQualifier(tableName, catalog, schema, table);
if (schema.isEmpty())
schema = d->user;
@@ -1509,7 +1504,7 @@ QSqlIndex QDB2Driver::primaryIndex(const QString& tablename) const
return index;
}
QString catalog, schema, table;
- qSplitTableQualifier(tablename, &catalog, &schema, &table);
+ d->qSplitTableQualifier(tablename, catalog, schema, table);
if (isIdentifierEscaped(catalog, QSqlDriver::TableName))
catalog = stripDelimiters(catalog, QSqlDriver::TableName);
@@ -1674,17 +1669,17 @@ QString QDB2Driver::formatValue(const QSqlField &field, bool trimStrings) const
}
}
case QMetaType::QByteArray: {
- QByteArray ba = field.value().toByteArray();
- QString res;
- res += "BLOB(X'"_L1;
- static const char hexchars[] = "0123456789abcdef";
- for (int i = 0; i < ba.size(); ++i) {
- uchar s = (uchar) ba[i];
- res += QLatin1Char(hexchars[s >> 4]);
- res += QLatin1Char(hexchars[s & 0x0f]);
+ const QByteArray ba = field.value().toByteArray();
+ QString r;
+ r.reserve(ba.size() * 2 + 9);
+ r += "BLOB(X'"_L1;
+ for (const char c : ba) {
+ const uchar s = uchar(c);
+ r += QLatin1Char(QtMiscUtils::toHexLower(s >> 4));
+ r += QLatin1Char(QtMiscUtils::toHexLower(s & 0x0f));
}
- res += "')"_L1;
- return res;
+ r += "')"_L1;
+ return r;
}
default:
return QSqlDriver::formatValue(field, trimStrings);
@@ -1702,10 +1697,12 @@ QString QDB2Driver::escapeIdentifier(const QString &identifier, IdentifierType)
QString res = identifier;
if (!identifier.isEmpty() && !identifier.startsWith(u'"') && !identifier.endsWith(u'"') ) {
res.replace(u'"', "\"\""_L1);
- res.prepend(u'"').append(u'"');
res.replace(u'.', "\".\""_L1);
+ res = u'"' + res + u'"';
}
return res;
}
QT_END_NAMESPACE
+
+#include "moc_qsql_db2_p.cpp"
diff --git a/src/plugins/sqldrivers/ibase/CMakeLists.txt b/src/plugins/sqldrivers/ibase/CMakeLists.txt
index 76c5c8d991..b8f2d2561f 100644
--- a/src/plugins/sqldrivers/ibase/CMakeLists.txt
+++ b/src/plugins/sqldrivers/ibase/CMakeLists.txt
@@ -1,5 +1,5 @@
# Copyright (C) 2022 The Qt Company Ltd.
-# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+# SPDX-License-Identifier: BSD-3-Clause
qt_internal_add_plugin(QIBaseDriverPlugin
OUTPUT_NAME qsqlibase
@@ -10,6 +10,7 @@ qt_internal_add_plugin(QIBaseDriverPlugin
DEFINES
QT_NO_CAST_FROM_ASCII
QT_NO_CAST_TO_ASCII
+ QT_NO_CONTEXTLESS_CONNECT
LIBRARIES
Interbase::Interbase
Qt::Core
diff --git a/src/plugins/sqldrivers/ibase/qsql_ibase.cpp b/src/plugins/sqldrivers/ibase/qsql_ibase.cpp
index d228628c08..28361e250d 100644
--- a/src/plugins/sqldrivers/ibase/qsql_ibase.cpp
+++ b/src/plugins/sqldrivers/ibase/qsql_ibase.cpp
@@ -4,9 +4,11 @@
#include "qsql_ibase_p.h"
#include <QtCore/qcoreapplication.h>
#include <QtCore/qdatetime.h>
+#include <QtCore/qtimezone.h>
#include <QtCore/qdeadlinetimer.h>
#include <QtCore/qdebug.h>
#include <QtCore/qlist.h>
+#include <QtCore/qloggingcategory.h>
#include <QtCore/qmap.h>
#include <QtCore/qmutex.h>
#include <QtCore/qvariant.h>
@@ -20,9 +22,12 @@
#include <stdlib.h>
#include <limits.h>
#include <math.h>
+#include <mutex>
QT_BEGIN_NAMESPACE
+static Q_LOGGING_CATEGORY(lcIbase, "qt.sql.ibase")
+
using namespace Qt::StringLiterals;
#define FBVERSION SQL_DIALECT_V6
@@ -36,7 +41,15 @@ using namespace Qt::StringLiterals;
#define blr_boolean_dtype blr_bool
#endif
-enum { QIBaseChunkSize = SHRT_MAX / 2 };
+constexpr qsizetype QIBaseChunkSize = SHRT_MAX / 2;
+
+#if (FB_API_VER >= 40)
+typedef QMap<quint16, QByteArray> QFbTzIdToIanaIdMap;
+typedef QMap<QByteArray, quint16> QIanaIdToFbTzIdMap;
+Q_GLOBAL_STATIC(QFbTzIdToIanaIdMap, qFbTzIdToIanaIdMap)
+Q_GLOBAL_STATIC(QIanaIdToFbTzIdMap, qIanaIdToFbTzIdMap)
+std::once_flag initTZMappingFlag;
+#endif
static bool getIBaseError(QString& msg, const ISC_STATUS* status, ISC_LONG &sqlcode)
{
@@ -85,6 +98,9 @@ static void initDA(XSQLDA *sqlda)
case SQL_FLOAT:
case SQL_DOUBLE:
case SQL_TIMESTAMP:
+#if (FB_API_VER >= 40)
+ case SQL_TIMESTAMP_TZ:
+#endif
case SQL_TYPE_TIME:
case SQL_TYPE_DATE:
case SQL_TEXT:
@@ -122,7 +138,7 @@ static void delDA(XSQLDA *&sqlda)
delete [] sqlda->sqlvar[i].sqldata;
}
free(sqlda);
- sqlda = 0;
+ sqlda = nullptr;
}
static QMetaType::Type qIBaseTypeName(int iType, bool hasScale)
@@ -139,6 +155,9 @@ static QMetaType::Type qIBaseTypeName(int iType, bool hasScale)
case blr_sql_date:
return QMetaType::QDate;
case blr_timestamp:
+#if (FB_API_VER >= 40)
+ case blr_timestamp_tz:
+#endif
return QMetaType::QDateTime;
case blr_blob:
return QMetaType::QByteArray;
@@ -155,7 +174,7 @@ static QMetaType::Type qIBaseTypeName(int iType, bool hasScale)
case blr_boolean_dtype:
return QMetaType::Bool;
}
- qWarning("qIBaseTypeName: unknown datatype: %d", iType);
+ qCWarning(lcIbase, "qIBaseTypeName: unknown datatype: %d", iType);
return QMetaType::UnknownType;
}
@@ -174,6 +193,9 @@ static QMetaType::Type qIBaseTypeName2(int iType, bool hasScale)
case SQL_DOUBLE:
return QMetaType::Double;
case SQL_TIMESTAMP:
+#if (FB_API_VER >= 40)
+ case SQL_TIMESTAMP_TZ:
+#endif
return QMetaType::QDateTime;
case SQL_TYPE_TIME:
return QMetaType::QTime;
@@ -208,12 +230,45 @@ static QDateTime fromTimeStamp(char *buffer)
// have to demangle the structure ourselves because isc_decode_time
// strips the msecs
- t = t.addMSecs(int(((ISC_TIMESTAMP*)buffer)->timestamp_time / 10));
- d = bd.addDays(int(((ISC_TIMESTAMP*)buffer)->timestamp_date));
-
+ auto timebuf = reinterpret_cast<ISC_TIMESTAMP*>(buffer);
+ t = t.addMSecs(static_cast<int>(timebuf->timestamp_time / 10));
+ d = bd.addDays(timebuf->timestamp_date);
return QDateTime(d, t);
}
+#if (FB_API_VER >= 40)
+QDateTime fromTimeStampTz(char *buffer)
+{
+ static const QDate bd(1858, 11, 17);
+ QTime t(0, 0);
+ QDate d;
+
+ // have to demangle the structure ourselves because isc_decode_time
+ // strips the msecs
+ auto timebuf = reinterpret_cast<ISC_TIMESTAMP_TZ*>(buffer);
+ t = t.addMSecs(static_cast<int>(timebuf->utc_timestamp.timestamp_time / 10));
+ d = bd.addDays(timebuf->utc_timestamp.timestamp_date);
+ quint16 fpTzID = timebuf->time_zone;
+
+ QByteArray timeZoneName = qFbTzIdToIanaIdMap()->value(fpTzID);
+ if (!timeZoneName.isEmpty())
+ return QDateTime(d, t, QTimeZone(timeZoneName));
+ else
+ return {};
+}
+
+ISC_TIMESTAMP_TZ toTimeStampTz(const QDateTime &dt)
+{
+ static const QTime midnight(0, 0, 0, 0);
+ static const QDate basedate(1858, 11, 17);
+ ISC_TIMESTAMP_TZ ts;
+ ts.utc_timestamp.timestamp_time = midnight.msecsTo(dt.time()) * 10;
+ ts.utc_timestamp.timestamp_date = basedate.daysTo(dt.date());
+ ts.time_zone = qIanaIdToFbTzIdMap()->value(dt.timeZone().id().simplified(), 0);
+ return ts;
+}
+#endif
+
static ISC_TIME toTime(QTime t)
{
static const QTime midnight(0, 0, 0, 0);
@@ -282,6 +337,34 @@ public:
return true;
}
+#if (FB_API_VER >= 40)
+ void initTZMappingCache()
+ {
+ Q_Q(QIBaseDriver);
+ QSqlQuery qry(q->createResult());
+ qry.setForwardOnly(true);
+ qry.exec(QString("select * from RDB$TIME_ZONES"_L1));
+ if (qry.lastError().type()) {
+ q->setLastError(QSqlError(
+ QCoreApplication::translate("QIBaseDriver",
+ "failed to query time zone mapping from system table"),
+ qry.lastError().databaseText(),
+ QSqlError::StatementError,
+ qry.lastError().nativeErrorCode()));
+
+ return;
+ }
+
+ while (qry.next()) {
+ auto record = qry.record();
+ quint16 fbTzId = record.value(0).value<quint16>();
+ QByteArray ianaId = record.value(1).toByteArray().simplified();
+ qFbTzIdToIanaIdMap()->insert(fbTzId, ianaId);
+ qIanaIdToFbTzIdMap()->insert(ianaId, fbTzId);
+ }
+ }
+#endif
+
public:
isc_db_handle ibase;
isc_tr_handle trans;
@@ -352,9 +435,9 @@ public:
bool isSelect();
QVariant fetchBlob(ISC_QUAD *bId);
- bool writeBlob(int i, const QByteArray &ba);
+ bool writeBlob(qsizetype iPos, const QByteArray &ba);
QVariant fetchArray(int pos, ISC_QUAD *arr);
- bool writeArray(int i, const QList<QVariant> &list);
+ bool writeArray(qsizetype i, const QList<QVariant> &list);
public:
ISC_STATUS status[20];
isc_tr_handle trans;
@@ -374,8 +457,8 @@ QIBaseResultPrivate::QIBaseResultPrivate(QIBaseResult *q, const QIBaseDriver *dr
localTransaction(!drv_d_func()->ibase),
stmt(0),
ibase(drv_d_func()->ibase),
- sqlda(0),
- inda(0),
+ sqlda(nullptr),
+ inda(nullptr),
queryType(-1)
{
}
@@ -399,20 +482,20 @@ void QIBaseResultPrivate::cleanup()
q->cleanup();
}
-bool QIBaseResultPrivate::writeBlob(int i, const QByteArray &ba)
+bool QIBaseResultPrivate::writeBlob(qsizetype iPos, const QByteArray &ba)
{
isc_blob_handle handle = 0;
- ISC_QUAD *bId = (ISC_QUAD*)inda->sqlvar[i].sqldata;
+ ISC_QUAD *bId = (ISC_QUAD*)inda->sqlvar[iPos].sqldata;
isc_create_blob2(status, &ibase, &trans, &handle, bId, 0, 0);
if (!isError(QT_TRANSLATE_NOOP("QIBaseResult", "Unable to create BLOB"),
QSqlError::StatementError)) {
- int i = 0;
+ qsizetype i = 0;
while (i < ba.size()) {
- isc_put_segment(status, &handle, qMin(ba.size() - i, int(QIBaseChunkSize)),
- const_cast<char*>(ba.data()) + i);
+ isc_put_segment(status, &handle, qMin(ba.size() - i, QIBaseChunkSize),
+ ba.data() + i);
if (isError(QT_TRANSLATE_NOOP("QIBaseResult", "Unable to write BLOB")))
return false;
- i += qMin(ba.size() - i, int(QIBaseChunkSize));
+ i += qMin(ba.size() - i, QIBaseChunkSize);
}
}
isc_close_blob(status, &handle);
@@ -432,7 +515,7 @@ QVariant QIBaseResultPrivate::fetchBlob(ISC_QUAD *bId)
QByteArray ba;
int chunkSize = QIBaseChunkSize;
ba.resize(chunkSize);
- int read = 0;
+ qsizetype read = 0;
while (isc_get_segment(status, &handle, &len, chunkSize, ba.data() + read) == 0 || status[1] == isc_segment) {
read += len;
ba.resize(read + chunkSize);
@@ -454,7 +537,7 @@ QVariant QIBaseResultPrivate::fetchBlob(ISC_QUAD *bId)
}
template<typename T>
-static QList<QVariant> toList(char** buf, int count, T* = nullptr)
+static QList<QVariant> toList(char** buf, int count)
{
QList<QVariant> res;
for (int i = 0; i < count; ++i) {
@@ -493,7 +576,7 @@ static char* readArrayBuffer(QList<QVariant>& list, char *buffer, short curDim,
}
break; }
case blr_long:
- valList = toList<int>(&buffer, numElements[dim], static_cast<int *>(0));
+ valList = toList<int>(&buffer, numElements[dim]);
break;
case blr_short:
valList = toList<short>(&buffer, numElements[dim]);
@@ -513,8 +596,16 @@ static char* readArrayBuffer(QList<QVariant>& list, char *buffer, short curDim,
buffer += sizeof(ISC_TIMESTAMP);
}
break;
+#if (FB_API_VER >= 40)
+ case blr_timestamp_tz:
+ for (int i = 0; i < numElements[dim]; ++i) {
+ valList.append(fromTimeStampTz(buffer));
+ buffer += sizeof(ISC_TIMESTAMP_TZ);
+ }
+ break;
+#endif
case blr_sql_time:
- for(int i = 0; i < numElements[dim]; ++i) {
+ for (int i = 0; i < numElements[dim]; ++i) {
valList.append(fromTime(buffer));
buffer += sizeof(ISC_TIME);
}
@@ -593,9 +684,8 @@ QVariant QIBaseResultPrivate::fetchArray(int pos, ISC_QUAD *arr)
template<typename T>
static char* fillList(char *buffer, const QList<QVariant> &list, T* = nullptr)
{
- for (int i = 0; i < list.size(); ++i) {
- T val;
- val = qvariant_cast<T>(list.at(i));
+ for (const auto &elem : list) {
+ T val = qvariant_cast<T>(elem);
memcpy(buffer, &val, sizeof(T));
buffer += sizeof(T);
}
@@ -605,11 +695,9 @@ static char* fillList(char *buffer, const QList<QVariant> &list, T* = nullptr)
template<>
char* fillList<float>(char *buffer, const QList<QVariant> &list, float*)
{
- for (int i = 0; i < list.size(); ++i) {
- double val;
- float val2 = 0;
- val = qvariant_cast<double>(list.at(i));
- val2 = (float)val;
+ for (const auto &elem : list) {
+ double val = qvariant_cast<double>(elem);
+ float val2 = (float)val;
memcpy(buffer, &val2, sizeof(float));
buffer += sizeof(float);
}
@@ -644,7 +732,6 @@ static char* createArrayBuffer(char *buffer, const QList<QVariant> &list,
QMetaType::Type type, short curDim, ISC_ARRAY_DESC *arrayDesc,
QString& error)
{
- int i;
ISC_ARRAY_BOUND *bounds = arrayDesc->array_desc_bounds;
short dim = arrayDesc->array_desc_dimensions - 1;
@@ -659,14 +746,14 @@ static char* createArrayBuffer(char *buffer, const QList<QVariant> &list,
}
if (curDim != dim) {
- for(i = 0; i < list.size(); ++i) {
+ for (const auto &elem : list) {
- if (list.at(i).typeId() != QMetaType::QVariantList) { // dimensions mismatch
+ if (elem.typeId() != QMetaType::QVariantList) { // dimensions mismatch
error = "Array dimensons mismatch. Fieldname: %1"_L1;
return 0;
}
- buffer = createArrayBuffer(buffer, list.at(i).toList(), type, curDim + 1,
+ buffer = createArrayBuffer(buffer, elem.toList(), type, curDim + 1,
arrayDesc, error);
if (!buffer)
return 0;
@@ -693,29 +780,40 @@ static char* createArrayBuffer(char *buffer, const QList<QVariant> &list,
buffer = fillList<quint64>(buffer, list);
break;
case QMetaType::QString:
- for (i = 0; i < list.size(); ++i)
- buffer = qFillBufferWithString(buffer, list.at(i).toString(),
+ for (const auto &elem : list)
+ buffer = qFillBufferWithString(buffer, elem.toString(),
arrayDesc->array_desc_length,
arrayDesc->array_desc_dtype == blr_varying,
true);
break;
case QMetaType::QDate:
- for (i = 0; i < list.size(); ++i) {
- *((ISC_DATE*)buffer) = toDate(list.at(i).toDate());
+ for (const auto &elem : list) {
+ *((ISC_DATE*)buffer) = toDate(elem.toDate());
buffer += sizeof(ISC_DATE);
}
break;
case QMetaType::QTime:
- for (i = 0; i < list.size(); ++i) {
- *((ISC_TIME*)buffer) = toTime(list.at(i).toTime());
+ for (const auto &elem : list) {
+ *((ISC_TIME*)buffer) = toTime(elem.toTime());
buffer += sizeof(ISC_TIME);
}
break;
-
case QMetaType::QDateTime:
- for (i = 0; i < list.size(); ++i) {
- *((ISC_TIMESTAMP*)buffer) = toTimeStamp(list.at(i).toDateTime());
- buffer += sizeof(ISC_TIMESTAMP);
+ for (const auto &elem : list) {
+ switch (arrayDesc->array_desc_dtype) {
+ case blr_timestamp:
+ *((ISC_TIMESTAMP*)buffer) = toTimeStamp(elem.toDateTime());
+ buffer += sizeof(ISC_TIMESTAMP);
+ break;
+#if (FB_API_VER >= 40)
+ case blr_timestamp_tz:
+ *((ISC_TIMESTAMP_TZ*)buffer) = toTimeStampTz(elem.toDateTime());
+ buffer += sizeof(ISC_TIMESTAMP_TZ);
+ break;
+#endif
+ default:
+ break;
+ }
}
break;
case QMetaType::Bool:
@@ -728,7 +826,7 @@ static char* createArrayBuffer(char *buffer, const QList<QVariant> &list,
return buffer;
}
-bool QIBaseResultPrivate::writeArray(int column, const QList<QVariant> &list)
+bool QIBaseResultPrivate::writeArray(qsizetype column, const QList<QVariant> &list)
{
Q_Q(QIBaseResult);
QString error;
@@ -745,7 +843,6 @@ bool QIBaseResultPrivate::writeArray(int column, const QList<QVariant> &list)
short arraySize = 1;
ISC_LONG bufLen;
- QList<QVariant> subList = list;
short dimensions = desc.array_desc_dimensions;
for(int i = 0; i < dimensions; ++i) {
@@ -847,7 +944,7 @@ QIBaseResult::QIBaseResult(const QIBaseDriver *db)
bool QIBaseResult::prepare(const QString& query)
{
Q_D(QIBaseResult);
-// qDebug("prepare: %s", qPrintable(query));
+// qDebug("prepare: %ls", qUtf16Printable(query));
if (!driver() || !driver()->isOpen() || driver()->isOpenError())
return false;
d->cleanup();
@@ -856,13 +953,13 @@ bool QIBaseResult::prepare(const QString& query)
createDA(d->sqlda);
if (d->sqlda == (XSQLDA*)0) {
- qWarning()<<"QIOBaseResult: createDA(): failed to allocate memory";
+ qCWarning(lcIbase) << "QIOBaseResult: createDA(): failed to allocate memory";
return false;
}
createDA(d->inda);
if (d->inda == (XSQLDA*)0){
- qWarning()<<"QIOBaseResult: createDA(): failed to allocate memory";
+ qCWarning(lcIbase) << "QIOBaseResult: createDA(): failed to allocate memory";
return false;
}
@@ -886,7 +983,7 @@ bool QIBaseResult::prepare(const QString& query)
if (d->inda->sqld > d->inda->sqln) {
enlargeDA(d->inda, d->inda->sqld);
if (d->inda == (XSQLDA*)0) {
- qWarning()<<"QIOBaseResult: enlargeDA(): failed to allocate memory";
+ qCWarning(lcIbase) << "QIOBaseResult: enlargeDA(): failed to allocate memory";
return false;
}
@@ -900,7 +997,7 @@ bool QIBaseResult::prepare(const QString& query)
// need more field descriptors
enlargeDA(d->sqlda, d->sqlda->sqld);
if (d->sqlda == (XSQLDA*)0) {
- qWarning()<<"QIOBaseResult: enlargeDA(): failed to allocate memory";
+ qCWarning(lcIbase) << "QIOBaseResult: enlargeDA(): failed to allocate memory";
return false;
}
@@ -920,7 +1017,6 @@ bool QIBaseResult::prepare(const QString& query)
return true;
}
-
bool QIBaseResult::exec()
{
Q_D(QIBaseResult);
@@ -936,20 +1032,17 @@ bool QIBaseResult::exec()
if (d->inda) {
const QList<QVariant> &values = boundValues();
- int i;
if (values.count() > d->inda->sqld) {
- qWarning() << "QIBaseResult::exec: Parameter mismatch, expected"_L1 <<
- d->inda->sqld << ", got"_L1 << values.count() <<
- "parameters"_L1;
+ qCWarning(lcIbase) << "QIBaseResult::exec: Parameter mismatch, expected"_L1
+ << d->inda->sqld << ", got"_L1 << values.count()
+ << "parameters"_L1;
return false;
}
- int para = 0;
- for (i = 0; i < values.count(); ++i) {
- para = i;
+ for (qsizetype para = 0; para < values.count(); ++para) {
if (!d->inda->sqlvar[para].sqldata)
// skip unknown datatypes
continue;
- const QVariant val(values[i]);
+ const QVariant &val = values[para];
if (d->inda->sqlvar[para].sqltype & 1) {
if (QSqlResultPrivate::isVariantNull(val)) {
// set null indicator
@@ -961,6 +1054,12 @@ bool QIBaseResult::exec()
}
// a value of 0 means non-null.
*(d->inda->sqlvar[para].sqlind) = 0;
+ } else {
+ if (QSqlResultPrivate::isVariantNull(val)) {
+ qCWarning(lcIbase) << "QIBaseResult::exec: Null value replaced by default (zero)"_L1
+ << "value for type of column"_L1 << d->inda->sqlvar[para].ownname
+ << ", which is not nullable."_L1;
+ }
}
switch(d->inda->sqlvar[para].sqltype & ~1) {
case SQL_INT64:
@@ -997,6 +1096,11 @@ bool QIBaseResult::exec()
case SQL_TIMESTAMP:
*((ISC_TIMESTAMP*)d->inda->sqlvar[para].sqldata) = toTimeStamp(val.toDateTime());
break;
+#if (FB_API_VER >= 40)
+ case SQL_TIMESTAMP_TZ:
+ *((ISC_TIMESTAMP_TZ*)d->inda->sqlvar[para].sqldata) = toTimeStampTz(val.toDateTime());
+ break;
+#endif
case SQL_TYPE_TIME:
*((ISC_TIME*)d->inda->sqlvar[para].sqldata) = toTime(val.toTime());
break;
@@ -1019,8 +1123,8 @@ bool QIBaseResult::exec()
*((bool*)d->inda->sqlvar[para].sqldata) = val.toBool();
break;
default:
- qWarning("QIBaseResult::exec: Unknown datatype %d",
- d->inda->sqlvar[para].sqltype & ~1);
+ qCWarning(lcIbase, "QIBaseResult::exec: Unknown datatype %d",
+ d->inda->sqlvar[para].sqltype & ~1);
break;
}
}
@@ -1180,6 +1284,11 @@ bool QIBaseResult::gotoNext(QSqlCachedResult::ValueCache& row, int rowIdx)
case SQL_BOOLEAN:
row[idx] = QVariant(bool((*(bool*)buf)));
break;
+#if (FB_API_VER >= 40)
+ case SQL_TIMESTAMP_TZ:
+ row[idx] = fromTimeStampTz(buf);
+ break;
+#endif
default:
// unknown type - don't even try to fetch
row[idx] = QVariant();
@@ -1286,7 +1395,7 @@ int QIBaseResult::numRowsAffected()
bIsProcedure = true; // will sum all changes
break;
default:
- qWarning() << "numRowsAffected: Unknown statement type (" << d->queryType << ")";
+ qCWarning(lcIbase) << "numRowsAffected: Unknown statement type (" << d->queryType << ")";
return -1;
}
@@ -1352,7 +1461,6 @@ QSqlRecord QIBaseResult::record() const
f.setRequiredStatus(q.value(3).toBool() ? QSqlField::Required : QSqlField::Optional);
}
}
- f.setSqlType(v.sqltype);
rec.append(f);
}
return rec;
@@ -1408,26 +1516,26 @@ bool QIBaseDriver::hasFeature(DriverFeature f) const
return false;
}
-bool QIBaseDriver::open(const QString & db,
- const QString & user,
- const QString & password,
- const QString & host,
- int port,
- const QString & connOpts)
+bool QIBaseDriver::open(const QString &db,
+ const QString &user,
+ const QString &password,
+ const QString &host,
+ int port,
+ const QString &connOpts)
{
Q_D(QIBaseDriver);
if (isOpen())
close();
- const QStringList opts(connOpts.split(u';', Qt::SkipEmptyParts));
+ const auto opts(QStringView(connOpts).split(u';', Qt::SkipEmptyParts));
QByteArray role;
- for (int i = 0; i < opts.count(); ++i) {
- QString tmp(opts.at(i).simplified());
+ for (const auto &opt : opts) {
+ const auto tmp(opt.trimmed());
qsizetype idx;
if ((idx = tmp.indexOf(u'=')) != -1) {
- QString val = tmp.mid(idx + 1).simplified();
- QString opt = tmp.left(idx).simplified();
+ const auto val = tmp.mid(idx + 1).trimmed();
+ const auto opt = tmp.left(idx).trimmed().toString();
if (opt.toUpper() == "ISC_DPB_SQL_ROLE_NAME"_L1) {
role = val.toLocal8Bit();
role.truncate(255);
@@ -1478,6 +1586,14 @@ bool QIBaseDriver::open(const QString & db,
setOpen(true);
setOpenError(false);
+#if (FB_API_VER >= 40)
+ std::call_once(initTZMappingFlag, [d](){ d->initTZMappingCache(); });
+ if (lastError().isValid())
+ {
+ setOpen(true);
+ return false;
+ }
+#endif
return true;
}
@@ -1496,13 +1612,6 @@ void QIBaseDriver::close()
qFreeEventBuffer(eBuffer);
}
d->eventBuffers.clear();
-
-#if defined(FB_API_VER)
- // TODO check whether this workaround for Firebird crash is still needed
- QDeadlineTimer timer(500);
- while (!timer.hasExpired())
- QCoreApplication::processEvents();
-#endif
}
isc_detach_database(d->status, &d->ibase);
@@ -1587,8 +1696,8 @@ QStringList QIBaseDriver::tables(QSql::TableType type) const
q.setForwardOnly(true);
if (!q.exec("select rdb$relation_name from rdb$relations "_L1 + typeFilter))
return res;
- while(q.next())
- res << q.value(0).toString().simplified();
+ while (q.next())
+ res << q.value(0).toString().simplified();
return res;
}
@@ -1599,13 +1708,9 @@ QSqlRecord QIBaseDriver::record(const QString& tablename) const
if (!isOpen())
return rec;
+ const QString table = stripDelimiters(tablename, QSqlDriver::TableName);
QSqlQuery q(createResult());
q.setForwardOnly(true);
- QString table = tablename;
- if (isIdentifierEscaped(table, QSqlDriver::TableName))
- table = stripDelimiters(table, QSqlDriver::TableName);
- else
- table = table.toUpper();
q.exec("SELECT a.RDB$FIELD_NAME, b.RDB$FIELD_TYPE, b.RDB$FIELD_LENGTH, "
"b.RDB$FIELD_SCALE, b.RDB$FIELD_PRECISION, a.RDB$NULL_FLAG "
"FROM RDB$RELATION_FIELDS a, RDB$FIELDS b "
@@ -1625,7 +1730,6 @@ QSqlRecord QIBaseDriver::record(const QString& tablename) const
f.setPrecision(0);
}
f.setRequired(q.value(5).toInt() > 0);
- f.setSqlType(type);
rec.append(f);
}
@@ -1638,12 +1742,7 @@ QSqlIndex QIBaseDriver::primaryIndex(const QString &table) const
if (!isOpen())
return index;
- QString tablename = table;
- if (isIdentifierEscaped(tablename, QSqlDriver::TableName))
- tablename = stripDelimiters(tablename, QSqlDriver::TableName);
- else
- tablename = tablename.toUpper();
-
+ const QString tablename = stripDelimiters(table, QSqlDriver::TableName);
QSqlQuery q(createResult());
q.setForwardOnly(true);
q.exec("SELECT a.RDB$INDEX_NAME, b.RDB$FIELD_NAME, d.RDB$FIELD_TYPE, d.RDB$FIELD_SCALE "
@@ -1738,13 +1837,13 @@ bool QIBaseDriver::subscribeToNotification(const QString &name)
{
Q_D(QIBaseDriver);
if (!isOpen()) {
- qWarning("QIBaseDriver::subscribeFromNotificationImplementation: database not open.");
+ qCWarning(lcIbase, "QIBaseDriver::subscribeFromNotificationImplementation: database not open.");
return false;
}
if (d->eventBuffers.contains(name)) {
- qWarning("QIBaseDriver::subscribeToNotificationImplementation: already subscribing to '%s'.",
- qPrintable(name));
+ qCWarning(lcIbase, "QIBaseDriver::subscribeToNotificationImplementation: already subscribing to '%ls'.",
+ qUtf16Printable(name));
return false;
}
@@ -1785,13 +1884,13 @@ bool QIBaseDriver::unsubscribeFromNotification(const QString &name)
{
Q_D(QIBaseDriver);
if (!isOpen()) {
- qWarning("QIBaseDriver::unsubscribeFromNotificationImplementation: database not open.");
+ qCWarning(lcIbase, "QIBaseDriver::unsubscribeFromNotificationImplementation: database not open.");
return false;
}
if (!d->eventBuffers.contains(name)) {
- qWarning("QIBaseDriver::QIBaseSubscriptionState not subscribed to '%s'.",
- qPrintable(name));
+ qCWarning(lcIbase, "QIBaseDriver::QIBaseSubscriptionState not subscribed to '%ls'.",
+ qUtf16Printable(name));
return false;
}
@@ -1846,8 +1945,8 @@ void QIBaseDriver::qHandleEventNotification(void *updatedResultBuffer)
(&qEventCallback)),
eBuffer->resultBuffer);
if (Q_UNLIKELY(status[0] == 1 && status[1])) {
- qCritical("QIBaseDriver::qHandleEventNotification: could not resubscribe to '%s'",
- qPrintable(i.key()));
+ qCritical("QIBaseDriver::qHandleEventNotification: could not resubscribe to '%ls'",
+ qUtf16Printable(i.key()));
}
return;
@@ -1860,8 +1959,8 @@ QString QIBaseDriver::escapeIdentifier(const QString &identifier, IdentifierType
QString res = identifier;
if (!identifier.isEmpty() && !identifier.startsWith(u'"') && !identifier.endsWith(u'"') ) {
res.replace(u'"', "\"\""_L1);
- res.prepend(u'"').append(u'"');
res.replace(u'.', "\".\""_L1);
+ res = u'"' + res + u'"';
}
return res;
}
@@ -1873,3 +1972,5 @@ int QIBaseDriver::maximumIdentifierLength(IdentifierType type) const
}
QT_END_NAMESPACE
+
+#include "moc_qsql_ibase_p.cpp"
diff --git a/src/plugins/sqldrivers/mimer/CMakeLists.txt b/src/plugins/sqldrivers/mimer/CMakeLists.txt
new file mode 100644
index 0000000000..303af7120c
--- /dev/null
+++ b/src/plugins/sqldrivers/mimer/CMakeLists.txt
@@ -0,0 +1,24 @@
+# Generated from mimer.pro.
+
+#####################################################################
+## MIMERSQLDriverPlugin Plugin:
+#####################################################################
+
+qt_internal_add_plugin(QMimerSQLDriverPlugin
+ OUTPUT_NAME qsqlmimer
+ PLUGIN_TYPE sqldrivers
+ SOURCES
+ main.cpp
+ qsql_mimer.cpp qsql_mimer.h
+ DEFINES
+ QT_NO_CAST_FROM_ASCII
+ QT_NO_CAST_TO_ASCII
+ QT_NO_CONTEXTLESS_CONNECT
+ LIBRARIES
+ MimerSQL::MimerSQL
+ Qt::Core
+ Qt::SqlPrivate
+)
+
+#### Keys ignored in scope 1:.:.:mimer.pro:<TRUE>:
+# OTHER_FILES = "mimer.json"
diff --git a/src/plugins/sqldrivers/mimer/README b/src/plugins/sqldrivers/mimer/README
new file mode 100644
index 0000000000..02e4c3e162
--- /dev/null
+++ b/src/plugins/sqldrivers/mimer/README
@@ -0,0 +1,6 @@
+You will need the Mimer SQL development headers and libraries installed before
+compiling this plugin. qsql_mimer.h contains an include to mimerapi.h that is
+needed for the driver to compile.
+
+See the Qt SQL documentation for more information on compiling Qt SQL driver
+plugins.
diff --git a/src/plugins/sqldrivers/mimer/main.cpp b/src/plugins/sqldrivers/mimer/main.cpp
new file mode 100644
index 0000000000..560b7da7c7
--- /dev/null
+++ b/src/plugins/sqldrivers/mimer/main.cpp
@@ -0,0 +1,33 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// Copyright (C) 2022 Mimer Information Technology
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+#include "qsql_mimer.h"
+
+#include <qsqldriverplugin.h>
+#include <qstringlist.h>
+
+QT_BEGIN_NAMESPACE
+
+using namespace Qt::StringLiterals;
+
+class QMimerSQLDriverPlugin : public QSqlDriverPlugin
+{
+ Q_OBJECT
+ Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QSqlDriverFactoryInterface" FILE "mimer.json")
+public:
+ QMimerSQLDriverPlugin();
+ QSqlDriver *create(const QString &) override;
+};
+
+QMimerSQLDriverPlugin::QMimerSQLDriverPlugin() : QSqlDriverPlugin() { }
+
+QSqlDriver *QMimerSQLDriverPlugin::create(const QString &name)
+{
+ if (name == "QMIMER"_L1)
+ return new QMimerSQLDriver;
+ return nullptr;
+}
+
+QT_END_NAMESPACE
+
+#include "main.moc"
diff --git a/src/plugins/sqldrivers/mimer/mimer.json b/src/plugins/sqldrivers/mimer/mimer.json
new file mode 100644
index 0000000000..fba96b765d
--- /dev/null
+++ b/src/plugins/sqldrivers/mimer/mimer.json
@@ -0,0 +1,5 @@
+{
+ "Keys": [
+ "QMIMER"
+ ]
+}
diff --git a/src/plugins/sqldrivers/mimer/qsql_mimer.cpp b/src/plugins/sqldrivers/mimer/qsql_mimer.cpp
new file mode 100644
index 0000000000..7f89e0a0d5
--- /dev/null
+++ b/src/plugins/sqldrivers/mimer/qsql_mimer.cpp
@@ -0,0 +1,1610 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// Copyright (C) 2022 Mimer Information Technology
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+#include <qcoreapplication.h>
+#include <qvariant.h>
+#include <qmetatype.h>
+#include <qdatetime.h>
+#include <qloggingcategory.h>
+#include <qsqlerror.h>
+#include <qsqlfield.h>
+#include <qsqlindex.h>
+#include <qsqlrecord.h>
+#include <qsqlquery.h>
+#include <qsocketnotifier.h>
+#include <qstringlist.h>
+#include <qlocale.h>
+#if defined(Q_OS_WIN32)
+# include <QtCore/qt_windows.h>
+#endif
+#include <QtSql/private/qsqlresult_p.h>
+#include <QtSql/private/qsqldriver_p.h>
+#include "qsql_mimer.h"
+
+#define MIMER_DEFAULT_DATATYPE 1000
+
+Q_DECLARE_OPAQUE_POINTER(MimerSession)
+Q_DECLARE_METATYPE(MimerSession)
+
+Q_DECLARE_OPAQUE_POINTER(MimerStatement)
+Q_DECLARE_METATYPE(MimerStatement)
+
+QT_BEGIN_NAMESPACE
+
+static Q_LOGGING_CATEGORY(lcMimer, "qt.sql.mimer")
+
+enum class MimerColumnTypes {
+ Binary,
+ Clob,
+ Blob,
+ String,
+ Int,
+ Numeric,
+ Long,
+ Float,
+ Double,
+ Boolean,
+ Uuid,
+ Date,
+ Time,
+ Timestamp,
+ Unknown
+};
+
+using namespace Qt::StringLiterals;
+
+class QMimerSQLResultPrivate;
+
+class QMimerSQLResult final : public QSqlResult
+{
+ Q_DECLARE_PRIVATE(QMimerSQLResult)
+public:
+ QMimerSQLResult(const QMimerSQLDriver *db);
+ virtual ~QMimerSQLResult() override;
+ QVariant handle() const override;
+ static constexpr int genericError = -1;
+ static constexpr int lobChunkMaxSizeSet = 1048500;
+ static constexpr int lobChunkMaxSizeFetch = 65536;
+ static constexpr int maxStackStringSize = 200;
+ static constexpr int maxTimeStringSize = 18;
+ static constexpr int maxDateStringSize = 10;
+ static constexpr int maxTimestampStringSize = 29;
+
+private:
+ void cleanup();
+ bool fetch(int i) override;
+ bool fetchFirst() override;
+ bool fetchLast() override;
+ bool fetchNext() override;
+ QVariant data(int i) override;
+ bool isNull(int index) override;
+ bool reset(const QString &query) override;
+ int size() override;
+ int numRowsAffected() override;
+ QSqlRecord record() const override;
+ bool prepare(const QString &query) override;
+ bool execBatch(bool arrayBind = false) override;
+ bool exec() override;
+ qint64 currentRow();
+ QVariant lastInsertId() const override;
+};
+
+class QMimerSQLDriverPrivate final : public QSqlDriverPrivate
+{
+ Q_DECLARE_PUBLIC(QMimerSQLDriver)
+public:
+ QMimerSQLDriverPrivate() : QSqlDriverPrivate(QSqlDriver::MimerSQL), sessionhandle(nullptr) { }
+ MimerSession sessionhandle;
+ QString dbName;
+ QString dbUser;
+ void splitTableQualifier(const QString &qualifier, QString *schema, QString *table) const;
+};
+
+class QMimerSQLResultPrivate : public QSqlResultPrivate
+{
+ Q_DECLARE_PUBLIC(QMimerSQLResult)
+public:
+ Q_DECLARE_SQLDRIVER_PRIVATE(QMimerSQLDriver)
+ QMimerSQLResultPrivate(QMimerSQLResult *q, const QMimerSQLDriver *drv)
+ : QSqlResultPrivate(q, drv),
+ statementhandle(nullptr),
+ lobhandle(nullptr),
+ rowsAffected(0),
+ preparedQuery(false),
+ openCursor(false),
+ openStatement(false),
+ executedStatement(false),
+ callWithOut(false),
+ execBatch(false),
+ currentRow(QSql::BeforeFirstRow)
+ {
+ }
+ MimerStatement statementhandle;
+ MimerLob lobhandle;
+ int rowsAffected;
+ bool preparedQuery;
+ bool openCursor;
+ bool openStatement;
+ bool executedStatement;
+ bool callWithOut;
+ bool execBatch;
+ qint64 currentSize = -1;
+ qint64 currentRow; // Only used when forwardOnly()
+ QVector<QVariant> batch_vector;
+};
+
+static QSqlError qMakeError(const QString &err, const int errCode, QSqlError::ErrorType type,
+ const QMimerSQLDriverPrivate *p)
+{
+ QString msg;
+ if (p) {
+ size_t str_len;
+ int e_code;
+ int rc;
+ str_len = (rc = MimerGetError(p->sessionhandle, &e_code, NULL, 0)) + 1;
+ if (!MIMER_SUCCEEDED(rc)) {
+ msg = QCoreApplication::translate("QMimerSQL", "No Mimer SQL error for code %1")
+ .arg(errCode);
+ } else {
+ QVarLengthArray<wchar_t> tmp_buff((qsizetype)str_len);
+ if (!MIMER_SUCCEEDED(
+ rc = MimerGetError(p->sessionhandle, &e_code, tmp_buff.data(), str_len)))
+ msg = QCoreApplication::translate("QMimerSQL", "No Mimer SQL error for code %1")
+ .arg(errCode);
+ else
+ msg = QString::fromWCharArray(tmp_buff.data());
+ }
+ } else {
+ msg = QCoreApplication::translate("QMimerSQL", "Generic Mimer SQL error");
+ }
+
+ return QSqlError("QMIMER: "_L1 + err, msg, type, QString::number(errCode));
+}
+
+static QString msgCouldNotGet(const char *type, int column)
+{
+ //: Data type, column
+ return QCoreApplication::translate("QMimerSQLResult",
+ "Could not get %1, column %2").arg(QLatin1StringView(type)).arg(column);
+}
+
+static QString msgCouldNotSet(const char *type, int column)
+{
+ //: Data type, parameter
+ return QCoreApplication::translate("QMimerSQLResult",
+ "Could not set %1, parameter %2").arg(QLatin1StringView(type)).arg(column);
+}
+
+QMimerSQLDriver::QMimerSQLDriver(QObject *parent) : QSqlDriver(*new QMimerSQLDriverPrivate, parent)
+{
+}
+
+QMimerSQLDriver::QMimerSQLDriver(MimerSession *conn, QObject *parent)
+ : QSqlDriver(*new QMimerSQLDriverPrivate, parent)
+{
+ Q_D(QMimerSQLDriver);
+ if (conn)
+ d->sessionhandle = *conn;
+}
+
+QMimerSQLDriver::~QMimerSQLDriver()
+{
+ close();
+}
+
+QMimerSQLResult::QMimerSQLResult(const QMimerSQLDriver *db)
+ : QSqlResult(*new QMimerSQLResultPrivate(this, db))
+{
+ Q_D(QMimerSQLResult);
+ d->preparedQuery = db->hasFeature(QSqlDriver::PreparedQueries);
+}
+
+QMimerSQLResult::~QMimerSQLResult()
+{
+ cleanup();
+}
+
+static MimerColumnTypes mimerMapColumnTypes(int32_t t)
+{
+ switch (t) {
+ case MIMER_BINARY:
+ case MIMER_BINARY_VARYING:
+ return MimerColumnTypes::Binary;
+ case MIMER_BLOB:
+ case MIMER_NATIVE_BLOB:
+ return MimerColumnTypes::Blob;
+ case MIMER_CLOB:
+ case MIMER_NCLOB:
+ case MIMER_NATIVE_CLOB:
+ case MIMER_NATIVE_NCLOB:
+ return MimerColumnTypes::Clob;
+ case MIMER_DATE:
+ return MimerColumnTypes::Date;
+ case MIMER_TIME:
+ return MimerColumnTypes::Time;
+ case MIMER_TIMESTAMP:
+ return MimerColumnTypes::Timestamp;
+ case MIMER_INTERVAL_DAY:
+ case MIMER_INTERVAL_DAY_TO_HOUR:
+ case MIMER_INTERVAL_DAY_TO_MINUTE:
+ case MIMER_INTERVAL_DAY_TO_SECOND:
+ case MIMER_INTERVAL_HOUR:
+ case MIMER_INTERVAL_HOUR_TO_MINUTE:
+ case MIMER_INTERVAL_HOUR_TO_SECOND:
+ case MIMER_INTERVAL_MINUTE:
+ case MIMER_INTERVAL_MINUTE_TO_SECOND:
+ case MIMER_INTERVAL_MONTH:
+ case MIMER_INTERVAL_SECOND:
+ case MIMER_INTERVAL_YEAR:
+ case MIMER_INTERVAL_YEAR_TO_MONTH:
+ case MIMER_NCHAR:
+ case MIMER_CHARACTER:
+ case MIMER_CHARACTER_VARYING:
+ case MIMER_NCHAR_VARYING:
+ case MIMER_UTF8:
+ case MIMER_DEFAULT_DATATYPE:
+ return MimerColumnTypes::String;
+ case MIMER_INTEGER:
+ case MIMER_DECIMAL:
+ case MIMER_FLOAT:
+ return MimerColumnTypes::Numeric;
+ case MIMER_BOOLEAN:
+ return MimerColumnTypes::Boolean;
+ case MIMER_T_BIGINT:
+ case MIMER_T_UNSIGNED_BIGINT:
+ case MIMER_NATIVE_BIGINT_NULLABLE:
+ case MIMER_NATIVE_BIGINT:
+ return MimerColumnTypes::Long;
+ case MIMER_NATIVE_REAL_NULLABLE:
+ case MIMER_NATIVE_REAL:
+ case MIMER_T_REAL:
+ return MimerColumnTypes::Float;
+ case MIMER_T_FLOAT:
+ case MIMER_NATIVE_DOUBLE_NULLABLE:
+ case MIMER_NATIVE_DOUBLE:
+ case MIMER_T_DOUBLE:
+ return MimerColumnTypes::Double;
+ case MIMER_NATIVE_INTEGER:
+ case MIMER_NATIVE_INTEGER_NULLABLE:
+ case MIMER_NATIVE_SMALLINT_NULLABLE:
+ case MIMER_NATIVE_SMALLINT:
+ case MIMER_T_INTEGER:
+ case MIMER_T_SMALLINT:
+ return MimerColumnTypes::Int;
+ case MIMER_UUID:
+ return MimerColumnTypes::Uuid;
+ default:
+ qCWarning(lcMimer) << "QMimerSQLDriver::mimerMapColumnTypes: Unknown data type:" << t;
+ }
+ return MimerColumnTypes::Unknown;
+}
+
+static QMetaType::Type qDecodeMSQLType(int32_t t)
+{
+ switch (t) {
+ case MIMER_BINARY:
+ case MIMER_BINARY_VARYING:
+ case MIMER_BLOB:
+ case MIMER_NATIVE_BLOB:
+ return QMetaType::QByteArray;
+ case MIMER_CLOB:
+ case MIMER_NCLOB:
+ case MIMER_NATIVE_CLOB:
+ case MIMER_NATIVE_NCLOB:
+ case MIMER_INTERVAL_DAY:
+ case MIMER_DECIMAL:
+ case MIMER_INTERVAL_DAY_TO_HOUR:
+ case MIMER_INTERVAL_DAY_TO_MINUTE:
+ case MIMER_INTERVAL_DAY_TO_SECOND:
+ case MIMER_INTERVAL_HOUR:
+ case MIMER_INTERVAL_HOUR_TO_MINUTE:
+ case MIMER_INTERVAL_HOUR_TO_SECOND:
+ case MIMER_INTERVAL_MINUTE:
+ case MIMER_INTERVAL_MINUTE_TO_SECOND:
+ case MIMER_INTERVAL_MONTH:
+ case MIMER_INTERVAL_SECOND:
+ case MIMER_INTERVAL_YEAR:
+ case MIMER_INTERVAL_YEAR_TO_MONTH:
+ case MIMER_NCHAR:
+ case MIMER_CHARACTER:
+ case MIMER_CHARACTER_VARYING:
+ case MIMER_NCHAR_VARYING:
+ case MIMER_UTF8:
+ case MIMER_DEFAULT_DATATYPE:
+ case MIMER_INTEGER:
+ case MIMER_FLOAT:
+ return QMetaType::QString;
+ case MIMER_BOOLEAN:
+ return QMetaType::Bool;
+ case MIMER_T_BIGINT:
+ case MIMER_T_UNSIGNED_BIGINT:
+ case MIMER_NATIVE_BIGINT_NULLABLE:
+ case MIMER_NATIVE_BIGINT:
+ return QMetaType::LongLong;
+ case MIMER_NATIVE_REAL_NULLABLE:
+ case MIMER_NATIVE_REAL:
+ case MIMER_T_REAL:
+ return QMetaType::Float;
+ case MIMER_T_FLOAT:
+ case MIMER_NATIVE_DOUBLE_NULLABLE:
+ case MIMER_NATIVE_DOUBLE:
+ case MIMER_T_DOUBLE:
+ return QMetaType::Double;
+ case MIMER_NATIVE_INTEGER_NULLABLE:
+ case MIMER_T_INTEGER:
+ case MIMER_NATIVE_INTEGER:
+ return QMetaType::Int;
+ case MIMER_NATIVE_SMALLINT_NULLABLE:
+ case MIMER_T_SMALLINT:
+ return QMetaType::Int;
+ case MIMER_DATE:
+ return QMetaType::QDate;
+ case MIMER_TIME:
+ return QMetaType::QTime;
+ break;
+ case MIMER_TIMESTAMP:
+ return QMetaType::QDateTime;
+ case MIMER_UUID:
+ return QMetaType::QUuid;
+ default:
+ qCWarning(lcMimer) << "QMimerSQLDriver::qDecodeMSQLType: Unknown data type:" << t;
+ return QMetaType::UnknownType;
+ }
+}
+
+static int32_t qLookupMimDataType(QStringView s)
+{
+ if (s == u"BINARY")
+ return MIMER_BINARY;
+ if (s == u"BINARY VARYING")
+ return MIMER_BINARY_VARYING;
+ if (s == u"BINARY LARGE OBJECT")
+ return MIMER_BLOB;
+ if (s == u"CHARACTER LARGE OBJECT")
+ return MIMER_CLOB;
+ if (s == u"NATIONAL CHAR LARGE OBJECT")
+ return MIMER_NCLOB;
+ if (s == u"INTERVAL DAY")
+ return MIMER_INTERVAL_DAY;
+ if (s == u"DECIMAL")
+ return MIMER_DECIMAL;
+ if (s == u"INTERVAL DAY TO HOUR")
+ return MIMER_INTERVAL_DAY_TO_HOUR;
+ if (s == u"INTERVAL DAY TO MINUTE")
+ return MIMER_INTERVAL_DAY_TO_MINUTE;
+ if (s == u"INTERVAL DAY TO SECOND")
+ return MIMER_INTERVAL_DAY_TO_SECOND;
+ if (s == u"INTERVAL HOUR")
+ return MIMER_INTERVAL_HOUR;
+ if (s == u"INTERVAL HOUR TO MINUTE")
+ return MIMER_INTERVAL_HOUR_TO_MINUTE;
+ if (s == u"INTERVAL HOUR TO SECOND")
+ return MIMER_INTERVAL_HOUR_TO_SECOND;
+ if (s == u"INTERVAL MINUTE")
+ return MIMER_INTERVAL_MINUTE;
+ if (s == u"INTERVAL MINUTE TO SECOND")
+ return MIMER_INTERVAL_MINUTE_TO_SECOND;
+ if (s == u"INTERVAL MONTH")
+ return MIMER_INTERVAL_MONTH;
+ if (s == u"INTERVAL SECOND")
+ return MIMER_INTERVAL_SECOND;
+ if (s == u"INTERVAL YEAR")
+ return MIMER_INTERVAL_YEAR;
+ if (s == u"INTERVAL YEAR TO MONTH")
+ return MIMER_INTERVAL_YEAR_TO_MONTH;
+ if (s == u"NATIONAL CHARACTER")
+ return MIMER_NCHAR;
+ if (s == u"CHARACTER")
+ return MIMER_CHARACTER;
+ if (s == u"CHARACTER VARYING")
+ return MIMER_CHARACTER_VARYING;
+ if (s == u"NATIONAL CHARACTER VARYING")
+ return MIMER_NCHAR_VARYING;
+ if (s == u"UTF-8")
+ return MIMER_UTF8;
+ if (s == u"BOOLEAN")
+ return MIMER_BOOLEAN;
+ if (s == u"BIGINT")
+ return MIMER_T_BIGINT;
+ if (s == u"REAL")
+ return MIMER_T_REAL;
+ if (s == u"FLOAT")
+ return MIMER_T_FLOAT;
+ if (s == u"DOUBLE PRECISION")
+ return MIMER_T_DOUBLE;
+ if (s == u"INTEGER")
+ return MIMER_T_INTEGER;
+ if (s == u"SMALLINT")
+ return MIMER_T_SMALLINT;
+ if (s == u"DATE")
+ return MIMER_DATE;
+ if (s == u"TIME")
+ return MIMER_TIME;
+ if (s == u"TIMESTAMP")
+ return MIMER_TIMESTAMP;
+ if (s == u"BUILTIN.UUID")
+ return MIMER_UUID;
+ if (s == u"USER-DEFINED")
+ return MIMER_DEFAULT_DATATYPE;
+ qCWarning(lcMimer) << "QMimerSQLDriver::qLookupMimDataType: Unhandled data type:" << s;
+ return MIMER_DEFAULT_DATATYPE;
+}
+
+QVariant QMimerSQLResult::handle() const
+{
+ Q_D(const QMimerSQLResult);
+ return QVariant::fromValue(d->statementhandle);
+}
+
+void QMimerSQLResult::cleanup()
+{
+ Q_D(QMimerSQLResult);
+ if (!driver() || !driver()->isOpen()) {
+ d->openCursor = false;
+ d->openStatement = false;
+ return;
+ }
+ if (d->openCursor) {
+ const int32_t err = MimerCloseCursor(d->statementhandle);
+ if (!MIMER_SUCCEEDED(err))
+ setLastError(qMakeError(
+ QCoreApplication::translate("QMimerSQLResult", "Could not close cursor"), err,
+ QSqlError::StatementError, d->drv_d_func()));
+ d->openCursor = false;
+ }
+ if (d->openStatement) {
+ const int32_t err = MimerEndStatement(&d->statementhandle);
+ if (!MIMER_SUCCEEDED(err))
+ setLastError(qMakeError(
+ QCoreApplication::translate("QMimerSQLResult", "Could not close statement"),
+ err, QSqlError::StatementError, d->drv_d_func()));
+ d->openStatement = false;
+ }
+ d->currentSize = -1;
+}
+
+qint64 QMimerSQLResult::currentRow()
+{
+ Q_D(const QMimerSQLResult);
+ return d->currentRow;
+}
+
+bool QMimerSQLResult::fetch(int i)
+{
+ Q_D(const QMimerSQLResult);
+ int32_t err = 0;
+ if (!isActive() || !isSelect())
+ return false;
+ if (i == at())
+ return true;
+ if (i < 0)
+ return false;
+
+ if (isForwardOnly() && i < at())
+ return false;
+
+ if (isForwardOnly()) {
+ bool rc;
+ do {
+ rc = fetchNext();
+ } while (rc && currentRow() < i);
+ return rc;
+ } else {
+ err = MimerFetchScroll(d->statementhandle, MIMER_ABSOLUTE, i + 1);
+ if (err == MIMER_NO_DATA)
+ return false;
+ }
+ if (!MIMER_SUCCEEDED(err)) {
+ setLastError(
+ qMakeError(QCoreApplication::translate("QMimerSQLResult", "Fetch did not succeed"),
+ err, QSqlError::StatementError, d->drv_d_func()));
+ return false;
+ }
+ setAt(MimerCurrentRow(d->statementhandle) - 1);
+ return true;
+}
+
+bool QMimerSQLResult::fetchFirst()
+{
+ Q_D(const QMimerSQLResult);
+ int32_t err = 0;
+ if (!isActive() || !isSelect())
+ return false;
+ if (isForwardOnly()) {
+ if (currentRow() < 0)
+ return fetchNext();
+ else if (currentRow() == 0)
+ setAt(0);
+ else
+ return false;
+ } else {
+ err = MimerFetchScroll(d->statementhandle, MIMER_FIRST, 0);
+ if (MIMER_SUCCEEDED(err) && err != MIMER_NO_DATA)
+ setAt(0);
+ }
+ if (!MIMER_SUCCEEDED(err)) {
+ setLastError(qMakeError(
+ QCoreApplication::translate("QMimerSQLResult", "Fetch first did not succeed"), err,
+ QSqlError::StatementError, d->drv_d_func()));
+ return false;
+ }
+ if (err == MIMER_NO_DATA)
+ return false;
+ return true;
+}
+
+bool QMimerSQLResult::fetchLast()
+{
+ Q_D(const QMimerSQLResult);
+ int32_t err = 0;
+ int row = 0;
+ if (!isActive() || !isSelect())
+ return false;
+ if (isForwardOnly()) {
+ bool rc;
+ do {
+ rc = fetchNext();
+ } while (rc);
+
+ return currentRow() >= 0;
+ } else {
+ err = MimerFetchScroll(d->statementhandle, static_cast<std::int32_t>(MIMER_LAST), 0);
+ if (err == MIMER_NO_DATA)
+ return false;
+ if (MIMER_SUCCEEDED(err)) {
+ row = MimerCurrentRow(d->statementhandle) - 1;
+ } else {
+ setLastError(qMakeError(
+ QCoreApplication::translate("QMimerSQLResult:", "Fetch last did not succeed"),
+ err, QSqlError::StatementError, d->drv_d_func()));
+ return false;
+ }
+ }
+
+ if (row < 0) {
+ setAt(QSql::BeforeFirstRow);
+ return false;
+ } else {
+ setAt(row);
+ return true;
+ }
+}
+
+bool QMimerSQLResult::fetchNext()
+{
+ Q_D(QMimerSQLResult);
+ int32_t err = 0;
+ if (!isActive() || !isSelect())
+ return false;
+ if (isForwardOnly())
+ err = MimerFetch(d->statementhandle);
+ else
+ err = MimerFetchScroll(d->statementhandle, MIMER_NEXT, 0);
+ if (!MIMER_SUCCEEDED(err)) {
+ setLastError(qMakeError(
+ QCoreApplication::translate("QMimerSQLResult", "Could not fetch next row"), err,
+ QSqlError::StatementError, d->drv_d_func()));
+ if (isForwardOnly())
+ d->currentRow = QSql::BeforeFirstRow;
+ return false;
+ }
+ if (err == MIMER_NO_DATA)
+ return false;
+ if (isForwardOnly())
+ setAt(++d->currentRow);
+ else
+ setAt(MimerCurrentRow(d->statementhandle) - 1);
+ return true;
+}
+
+QVariant QMimerSQLResult::data(int i)
+{
+ Q_D(QMimerSQLResult);
+ int32_t err;
+ int32_t mType;
+ if (d->callWithOut) {
+ if (i >= MimerParameterCount(d->statementhandle)) {
+ setLastError(qMakeError(
+ QCoreApplication::translate("QMimerSQLResult:", "Column %1 out of range")
+ .arg(i),
+ genericError, QSqlError::StatementError, nullptr));
+ return QVariant();
+ }
+ mType = MimerParameterType(d->statementhandle, static_cast<std::int16_t>(i + 1));
+ } else {
+ if (i >= MimerColumnCount(d->statementhandle)) {
+ setLastError(qMakeError(
+ QCoreApplication::translate("QMimerSQLResult:", "Column %1 out of range")
+ .arg(i),
+ genericError, QSqlError::StatementError, nullptr));
+ return QVariant();
+ }
+ mType = MimerColumnType(d->statementhandle, static_cast<std::int16_t>(i + 1));
+ }
+ const QMetaType::Type type = qDecodeMSQLType(mType);
+ const MimerColumnTypes mimDataType = mimerMapColumnTypes(mType);
+ err = MimerIsNull(d->statementhandle, static_cast<std::int16_t>(i + 1));
+ if (err > 0) {
+ return QVariant(QMetaType(type), nullptr);
+ } else {
+ switch (mimDataType) {
+ case MimerColumnTypes::Date: {
+ wchar_t dateString_w[maxDateStringSize + 1];
+ err = MimerGetString(d->statementhandle, static_cast<std::int16_t>(i + 1), dateString_w,
+ sizeof(dateString_w) / sizeof(dateString_w[0]));
+ if (!MIMER_SUCCEEDED(err)) {
+ setLastError(qMakeError(msgCouldNotGet("date", i),
+ err, QSqlError::StatementError, d->drv_d_func()));
+ return QVariant(QMetaType(type), nullptr);
+ }
+ return QDate::fromString(QString::fromWCharArray(dateString_w), "yyyy-MM-dd"_L1);
+ }
+ case MimerColumnTypes::Time: {
+ wchar_t timeString_w[maxTimeStringSize + 1];
+ err = MimerGetString(d->statementhandle, static_cast<std::int16_t>(i + 1), timeString_w,
+ sizeof(timeString_w) / sizeof(timeString_w[0]));
+ if (!MIMER_SUCCEEDED(err)) {
+ setLastError(qMakeError(msgCouldNotGet("time", i),
+ err, QSqlError::StatementError, d->drv_d_func()));
+ return QVariant(QMetaType(type), nullptr);
+ }
+ QString timeString = QString::fromWCharArray(timeString_w);
+ QString timeFormatString = "HH:mm:ss"_L1;
+ if (timeString.size() > 8) {
+ timeFormatString.append(".zzz"_L1);
+ timeString = timeString.left(12);
+ }
+ return QTime::fromString(timeString, timeFormatString);
+ }
+ case MimerColumnTypes::Timestamp: {
+ wchar_t dateTimeString_w[maxTimestampStringSize + 1];
+ err = MimerGetString(d->statementhandle, static_cast<std::int16_t>(i + 1),
+ dateTimeString_w,
+ sizeof(dateTimeString_w) / sizeof(dateTimeString_w[0]));
+ if (!MIMER_SUCCEEDED(err)) {
+ setLastError(
+ qMakeError(msgCouldNotGet("date time", i),
+ err, QSqlError::StatementError, d->drv_d_func()));
+ return QVariant(QMetaType(type), nullptr);
+ }
+ QString dateTimeString = QString::fromWCharArray(dateTimeString_w);
+ QString dateTimeFormatString = "yyyy-MM-dd HH:mm:ss"_L1;
+ if (dateTimeString.size() > 19) {
+ dateTimeFormatString.append(".zzz"_L1);
+ dateTimeString = dateTimeString.left(23);
+ }
+ return QDateTime::fromString(dateTimeString, dateTimeFormatString);
+ }
+ case MimerColumnTypes::Int: {
+ int resInt;
+ err = MimerGetInt32(d->statementhandle, static_cast<std::int16_t>(i + 1), &resInt);
+ if (!MIMER_SUCCEEDED(err)) {
+ setLastError(qMakeError(msgCouldNotGet("int32", i),
+ err, QSqlError::StatementError, d->drv_d_func()));
+ return QVariant(QMetaType(type), nullptr);
+ }
+ return resInt;
+ }
+ case MimerColumnTypes::Long: {
+ int64_t resLongLong;
+ err = MimerGetInt64(d->statementhandle, static_cast<std::int16_t>(i + 1), &resLongLong);
+ if (!MIMER_SUCCEEDED(err)) {
+ setLastError(qMakeError(msgCouldNotGet("int64", i),
+ err, QSqlError::StatementError, d->drv_d_func()));
+ return QVariant(QMetaType(type), nullptr);
+ }
+ return (qlonglong)resLongLong;
+ }
+ case MimerColumnTypes::Boolean: {
+ err = MimerGetBoolean(d->statementhandle, static_cast<std::int16_t>(i + 1));
+ if (!MIMER_SUCCEEDED(err)) {
+ setLastError(
+ qMakeError(msgCouldNotGet("boolean", i),
+ err, QSqlError::StatementError, d->drv_d_func()));
+ return QVariant(QMetaType(type), nullptr);
+ }
+ return err == 1;
+ }
+ case MimerColumnTypes::Float: {
+ float resFloat;
+ err = MimerGetFloat(d->statementhandle, static_cast<std::int16_t>(i + 1), &resFloat);
+ if (!MIMER_SUCCEEDED(err)) {
+ setLastError(qMakeError(msgCouldNotGet("float", i),
+ err, QSqlError::StatementError, d->drv_d_func()));
+ return QVariant(QMetaType(type), nullptr);
+ }
+ return resFloat;
+ }
+ case MimerColumnTypes::Double: {
+ double resDouble;
+ err = MimerGetDouble(d->statementhandle, static_cast<std::int16_t>(i + 1), &resDouble);
+ if (!MIMER_SUCCEEDED(err)) {
+ setLastError(
+ qMakeError(msgCouldNotGet("double", i),
+ err, QSqlError::StatementError, d->drv_d_func()));
+ return QVariant(QMetaType(type), nullptr);
+ }
+ switch (numericalPrecisionPolicy()) {
+ case QSql::LowPrecisionInt32:
+ return static_cast<std::int32_t>(resDouble);
+ case QSql::LowPrecisionInt64:
+ return static_cast<qint64>(resDouble);
+ case QSql::LowPrecisionDouble:
+ return static_cast<qreal>(resDouble);
+ case QSql::HighPrecision:
+ return QString::number(resDouble, 'g', 17);
+ }
+ return QVariant(QMetaType(type), nullptr);
+ }
+ case MimerColumnTypes::Binary: {
+ QByteArray byteArray;
+ // Get size
+ err = MimerGetBinary(d->statementhandle, static_cast<std::int16_t>(i + 1), NULL, 0);
+ if (MIMER_SUCCEEDED(err)) {
+ byteArray.resize(err);
+ err = MimerGetBinary(d->statementhandle, static_cast<std::int16_t>(i + 1),
+ byteArray.data(), err);
+ }
+ if (!MIMER_SUCCEEDED(err)) {
+ setLastError(
+ qMakeError(msgCouldNotGet("binary", i),
+ err, QSqlError::StatementError, d->drv_d_func()));
+ return QVariant(QMetaType(type), nullptr);
+ }
+ return byteArray;
+ }
+ case MimerColumnTypes::Blob: {
+ QByteArray byteArray;
+ size_t size;
+ err = MimerGetLob(d->statementhandle, static_cast<std::int16_t>(i + 1), &size,
+ &d->lobhandle);
+ if (MIMER_SUCCEEDED(err)) {
+ constexpr size_t maxSize = lobChunkMaxSizeFetch;
+ QVarLengthArray<char> blobchar(lobChunkMaxSizeFetch);
+ byteArray.reserve(size);
+ size_t left_to_return = size;
+ while (left_to_return > 0) {
+ const size_t bytesToReceive =
+ left_to_return <= maxSize ? left_to_return : maxSize;
+ err = MimerGetBlobData(&d->lobhandle, blobchar.data(), bytesToReceive);
+ byteArray.append(QByteArray::fromRawData(blobchar.data(), bytesToReceive));
+ left_to_return -= bytesToReceive;
+ if (!MIMER_SUCCEEDED(err)) {
+ setLastError(qMakeError(msgCouldNotGet("BLOB", i),
+ err, QSqlError::StatementError, d->drv_d_func()));
+ return QVariant(QMetaType(type), nullptr);
+ }
+ }
+ } else {
+ setLastError(qMakeError(msgCouldNotGet("BLOB", i),
+ err, QSqlError::StatementError, d->drv_d_func()));
+ return QVariant(QMetaType(type), nullptr);
+ }
+ return byteArray;
+ }
+ case MimerColumnTypes::Numeric:
+ case MimerColumnTypes::String: {
+ wchar_t resString_w[maxStackStringSize + 1];
+ // Get size
+ err = MimerGetString(d->statementhandle, static_cast<std::int16_t>(i + 1), resString_w,
+ 0);
+ if (MIMER_SUCCEEDED(err)) {
+ int size = err;
+ if (err <= maxStackStringSize) { // For smaller strings, use a small buffer for
+ // efficiency
+ err = MimerGetString(d->statementhandle, static_cast<std::int16_t>(i + 1),
+ resString_w, maxStackStringSize + 1);
+ if (MIMER_SUCCEEDED(err))
+ return QString::fromWCharArray(resString_w);
+ } else { // For larger strings, dynamically allocate memory
+ QVarLengthArray<wchar_t> largeResString_w(size + 1);
+ err = MimerGetString(d->statementhandle, static_cast<std::int16_t>(i + 1),
+ largeResString_w.data(), size + 1);
+ if (MIMER_SUCCEEDED(err))
+ return QString::fromWCharArray(largeResString_w.data());
+ }
+ }
+ setLastError(qMakeError(msgCouldNotGet(
+ mimDataType == MimerColumnTypes::Numeric ? "numeric" : "string", i),
+ err, QSqlError::StatementError, d->drv_d_func()));
+ return QVariant(QMetaType(type), nullptr);
+ }
+ case MimerColumnTypes::Clob: {
+ size_t size;
+ err = MimerGetLob(d->statementhandle, static_cast<std::int16_t>(i + 1), &size,
+ &d->lobhandle);
+ if (MIMER_SUCCEEDED(err)) {
+ constexpr size_t maxSize = lobChunkMaxSizeFetch;
+ QVarLengthArray<wchar_t> clobstring_w(lobChunkMaxSizeFetch + 1);
+
+ size_t left_to_return = size;
+ QString returnString;
+ while (left_to_return > 0) {
+ const size_t bytesToReceive =
+ left_to_return <= maxSize ? left_to_return : maxSize;
+ err = MimerGetNclobData(&d->lobhandle, clobstring_w.data(), bytesToReceive + 1);
+ returnString.append(QString::fromWCharArray(clobstring_w.data()));
+ left_to_return -= bytesToReceive;
+ if (!MIMER_SUCCEEDED(err)) {
+ setLastError(qMakeError(msgCouldNotGet("CLOB", i),
+ err, QSqlError::StatementError, d->drv_d_func()));
+ return QVariant(QMetaType(type), nullptr);
+ }
+ }
+ return returnString;
+ }
+ setLastError(qMakeError(msgCouldNotGet("CLOB", i),
+ err, QSqlError::StatementError, d->drv_d_func()));
+ return QVariant(QMetaType(type), nullptr);
+ }
+ case MimerColumnTypes::Uuid: {
+ unsigned char uuidChar[16];
+ err = MimerGetUUID(d->statementhandle, static_cast<std::int16_t>(i + 1), uuidChar);
+ if (!MIMER_SUCCEEDED(err)) {
+ setLastError(qMakeError(msgCouldNotGet("UUID", i),
+ err, QSqlError::StatementError, d->drv_d_func()));
+ return QVariant(QMetaType(type), nullptr);
+ }
+ const QByteArray uuidByteArray = QByteArray(reinterpret_cast<char *>(uuidChar), 16);
+ return QUuid::fromRfc4122(uuidByteArray);
+ }
+ case MimerColumnTypes::Unknown:
+ default:
+ setLastError(qMakeError(
+ QCoreApplication::translate("QMimerSQLResult", "Unknown data type %1").arg(i),
+ genericError, QSqlError::StatementError, nullptr));
+ }
+ return QVariant(QMetaType(type), nullptr);
+ }
+}
+
+bool QMimerSQLResult::isNull(int index)
+{
+ Q_D(const QMimerSQLResult);
+ const int32_t rc = MimerIsNull(d->statementhandle, static_cast<std::int16_t>(index + 1));
+ if (!MIMER_SUCCEEDED(rc)) {
+ setLastError(qMakeError(
+ QCoreApplication::translate("QMimerSQLResult", "Could not check null, column %1")
+ .arg(index),
+ rc, QSqlError::StatementError, d->drv_d_func()));
+ return false;
+ }
+ return rc != 0;
+}
+
+bool QMimerSQLResult::reset(const QString &query)
+{
+ if (!prepare(query))
+ return false;
+ return exec();
+}
+
+int QMimerSQLResult::size()
+{
+ Q_D(QMimerSQLResult);
+ if (!isActive() || !isSelect() || isForwardOnly())
+ return -1;
+
+ if (d->currentSize != -1)
+ return d->currentSize;
+
+ const int currentRow = MimerCurrentRow(d->statementhandle);
+ MimerFetchScroll(d->statementhandle, static_cast<std::int32_t>(MIMER_LAST), 0);
+ int size = MimerCurrentRow(d->statementhandle);
+ if (!MIMER_SUCCEEDED(size))
+ size = -1;
+ MimerFetchScroll(d->statementhandle, MIMER_ABSOLUTE, currentRow);
+ d->currentSize = size;
+ return size;
+}
+
+int QMimerSQLResult::numRowsAffected()
+{
+ Q_D(const QMimerSQLResult);
+ return d->rowsAffected;
+}
+
+QSqlRecord QMimerSQLResult::record() const
+{
+ Q_D(const QMimerSQLResult);
+ QSqlRecord rec;
+ if (!isActive() || !isSelect() || !driver())
+ return rec;
+ QSqlField field;
+ const int colSize = MimerColumnCount(d->statementhandle);
+ for (int i = 0; i < colSize; i++) {
+ wchar_t colName_w[100];
+ MimerColumnName(d->statementhandle, static_cast<std::int16_t>(i + 1), colName_w,
+ sizeof(colName_w) / sizeof(colName_w[0]));
+ field.setName(QString::fromWCharArray(colName_w));
+ const int32_t mType = MimerColumnType(d->statementhandle, static_cast<std::int16_t>(i + 1));
+ const QMetaType::Type type = qDecodeMSQLType(mType);
+ field.setMetaType(QMetaType(type));
+ field.setValue(QVariant(field.metaType()));
+ // field.setPrecision(); Should be implemented once the Mimer API can give this
+ // information.
+ // field.setLength(); Should be implemented once the Mimer API can give
+ // this information.
+ rec.insert(i, field);
+ }
+ return rec;
+}
+
+bool QMimerSQLResult::prepare(const QString &query)
+{
+ Q_D(QMimerSQLResult);
+ int32_t err;
+ if (!driver())
+ return false;
+ if (!d->preparedQuery)
+ return QSqlResult::prepare(query);
+ if (query.isEmpty())
+ return false;
+ cleanup();
+ const int option = isForwardOnly() ? MIMER_FORWARD_ONLY : MIMER_SCROLLABLE;
+ err = MimerBeginStatement8(d->drv_d_func()->sessionhandle, query.toUtf8().constData(), option,
+ &d->statementhandle);
+ if (err == MIMER_STATEMENT_CANNOT_BE_PREPARED) {
+ err = MimerExecuteStatement8(d->drv_d_func()->sessionhandle, query.toUtf8().constData());
+ if (MIMER_SUCCEEDED(err)) {
+ d->executedStatement = true;
+ d->openCursor = false;
+ d->openStatement = false;
+ return true;
+ }
+ }
+ if (!MIMER_SUCCEEDED(err)) {
+ setLastError(qMakeError(QCoreApplication::translate("QMimerSQLResult",
+ "Could not prepare/execute statement"),
+ err, QSqlError::StatementError, d->drv_d_func()));
+ return false;
+ }
+ d->openStatement = true;
+ return true;
+}
+
+bool QMimerSQLResult::exec()
+{
+ Q_D(QMimerSQLResult);
+ int32_t err;
+ if (!driver())
+ return false;
+ if (!d->preparedQuery)
+ return QSqlResult::exec();
+ if (d->executedStatement) {
+ d->executedStatement = false;
+ return true;
+ }
+ if (d->openCursor) {
+ setAt(QSql::BeforeFirstRow);
+ err = MimerCloseCursor(d->statementhandle);
+ d->openCursor = false;
+ d->currentSize = -1;
+ }
+ QVector<QVariant> &values = boundValues();
+ if (d->execBatch)
+ values = d->batch_vector;
+ int mimParamCount = MimerParameterCount(d->statementhandle);
+ if (!MIMER_SUCCEEDED(mimParamCount))
+ mimParamCount = 0;
+ if (mimParamCount != values.size()) {
+ setLastError(qMakeError(
+ QCoreApplication::translate("QMimerSQLResult", "Wrong number of parameters"),
+ genericError, QSqlError::StatementError, nullptr));
+ return false;
+ }
+ for (int i = 0; i < mimParamCount; i++) {
+ if (bindValueType(i) == QSql::Out) {
+ d->callWithOut = true;
+ continue;
+ }
+ const QVariant &val = values.at(i);
+ if (QSqlResultPrivate::isVariantNull(val) || val.isNull() || val.toString().isNull()) {
+ err = MimerSetNull(d->statementhandle, i + 1);
+ if (!MIMER_SUCCEEDED(err)) {
+ setLastError(
+ qMakeError(msgCouldNotSet("null", i),
+ err, QSqlError::StatementError, d->drv_d_func()));
+ return false;
+ }
+ continue;
+ }
+
+ const int mimParamType = MimerParameterType(d->statementhandle, i + 1);
+ const MimerColumnTypes mimDataType = mimerMapColumnTypes(mimParamType);
+ switch (mimDataType) {
+ case MimerColumnTypes::Int: {
+ bool convertOk;
+ err = MimerSetInt32(d->statementhandle, i + 1, val.toInt(&convertOk));
+ if (!convertOk || !MIMER_SUCCEEDED(err)) {
+ setLastError(
+ qMakeError(msgCouldNotSet("int32", i),
+ convertOk ? err : genericError, QSqlError::StatementError,
+ convertOk ? d->drv_d_func() : nullptr));
+ return false;
+ }
+ break;
+ }
+ case MimerColumnTypes::Long: {
+ bool convertOk;
+ err = MimerSetInt64(d->statementhandle, i + 1, val.toLongLong(&convertOk));
+ if (!convertOk || !MIMER_SUCCEEDED(err)) {
+ setLastError(
+ qMakeError(msgCouldNotSet("int64", i),
+ convertOk ? err : genericError, QSqlError::StatementError,
+ convertOk ? d->drv_d_func() : nullptr));
+ return false;
+ }
+ break;
+ }
+ case MimerColumnTypes::Float: {
+ bool convertOk;
+ err = MimerSetFloat(d->statementhandle, i + 1, val.toFloat(&convertOk));
+ if (!convertOk || !MIMER_SUCCEEDED(err)) {
+ setLastError(
+ qMakeError(msgCouldNotSet("float", i),
+ convertOk ? err : genericError, QSqlError::StatementError,
+ convertOk ? d->drv_d_func() : nullptr));
+ return false;
+ }
+ break;
+ }
+ case MimerColumnTypes::Double: {
+ bool convertOk;
+ err = MimerSetDouble(d->statementhandle, i + 1, val.toDouble(&convertOk));
+ if (!convertOk || !MIMER_SUCCEEDED(err)) {
+ setLastError(
+ qMakeError(msgCouldNotSet("double", i),
+ convertOk ? err : genericError, QSqlError::StatementError,
+ convertOk ? d->drv_d_func() : nullptr));
+ return false;
+ }
+ break;
+ }
+ case MimerColumnTypes::Binary: {
+ const QByteArray binArr = val.toByteArray();
+ size_t size = static_cast<std::size_t>(binArr.size());
+ err = MimerSetBinary(d->statementhandle, i + 1, binArr.data(), size);
+ if (!MIMER_SUCCEEDED(err)) {
+ setLastError(
+ qMakeError(msgCouldNotSet("binary", i),
+ err, QSqlError::StatementError, d->drv_d_func()));
+ return false;
+ }
+ break;
+ }
+ case MimerColumnTypes::Boolean: {
+ err = MimerSetBoolean(d->statementhandle, i + 1, val.toBool() == true ? 1 : 0);
+ if (!MIMER_SUCCEEDED(err)) {
+ setLastError(
+ qMakeError(msgCouldNotSet("boolean", i),
+ err, QSqlError::StatementError, d->drv_d_func()));
+ return false;
+ }
+ break;
+ }
+ case MimerColumnTypes::Uuid: {
+ const QByteArray uuidArray =
+ QByteArray::fromHex(val.toUuid().toString(QUuid::WithoutBraces).toLatin1());
+ const unsigned char *uuid =
+ reinterpret_cast<const unsigned char *>(uuidArray.constData());
+ err = MimerSetUUID(d->statementhandle, i + 1, uuid);
+ if (!MIMER_SUCCEEDED(err)) {
+ setLastError(
+ qMakeError(msgCouldNotSet("UUID", i),
+ err, QSqlError::StatementError, d->drv_d_func()));
+ return false;
+ }
+ break;
+ }
+ case MimerColumnTypes::Numeric:
+ case MimerColumnTypes::String: {
+ QByteArray string_b = val.toString().trimmed().toUtf8();
+ const char *string_u = string_b.constData();
+ err = MimerSetString8(d->statementhandle, i + 1, string_u);
+ if (!MIMER_SUCCEEDED(err)) {
+ setLastError(
+ qMakeError(msgCouldNotSet(
+ mimDataType == MimerColumnTypes::Numeric ? "numeric" : "string", i),
+ err, QSqlError::StatementError, d->drv_d_func()));
+ return false;
+ }
+ break;
+ }
+ case MimerColumnTypes::Date: {
+ err = MimerSetString8(d->statementhandle, i + 1, val.toString().toUtf8().constData());
+ if (!MIMER_SUCCEEDED(err)) {
+ setLastError(
+ qMakeError(msgCouldNotSet("date", i),
+ err, QSqlError::StatementError, d->drv_d_func()));
+ return false;
+ }
+ break;
+ }
+ case MimerColumnTypes::Time: {
+ QString timeFormatString = "hh:mm:ss"_L1;
+ const QTime timeVal = val.toTime();
+ if (timeVal.msec() > 0)
+ timeFormatString.append(".zzz"_L1);
+ err = MimerSetString8(d->statementhandle, i + 1,
+ timeVal.toString(timeFormatString).toUtf8().constData());
+ if (!MIMER_SUCCEEDED(err)) {
+ setLastError(
+ qMakeError(msgCouldNotSet("time", i),
+ err, QSqlError::StatementError, d->drv_d_func()));
+ return false;
+ }
+ break;
+ }
+ case MimerColumnTypes::Timestamp: {
+ QString dateTimeFormatString = "yyyy-MM-dd hh:mm:ss"_L1;
+ const QDateTime dateTimeVal = val.toDateTime();
+ if (dateTimeVal.time().msec() > 0)
+ dateTimeFormatString.append(".zzz"_L1);
+ err = MimerSetString8(
+ d->statementhandle, i + 1,
+ val.toDateTime().toString(dateTimeFormatString).toUtf8().constData());
+ if (!MIMER_SUCCEEDED(err)) {
+ setLastError(qMakeError(msgCouldNotSet("datetime", i),
+ err, QSqlError::StatementError, d->drv_d_func()));
+ return false;
+ }
+ break;
+ }
+ case MimerColumnTypes::Blob: {
+ QByteArray blobArr = val.toByteArray();
+ const char *blobData = blobArr.constData();
+ qsizetype size = blobArr.size();
+ err = MimerSetLob(d->statementhandle, i + 1, size, &d->lobhandle);
+ if (MIMER_SUCCEEDED(err)) {
+ qsizetype maxSize = lobChunkMaxSizeSet;
+ if (size > maxSize) {
+ qsizetype left_to_send = size;
+ for (qsizetype k = 0; left_to_send > 0; k++) {
+ if (left_to_send <= maxSize) {
+ err = MimerSetBlobData(&d->lobhandle, &blobData[k * maxSize],
+ left_to_send);
+ left_to_send = 0;
+ } else {
+ err = MimerSetBlobData(&d->lobhandle, &blobData[k * maxSize], maxSize);
+ left_to_send = left_to_send - maxSize;
+ }
+ }
+ if (!MIMER_SUCCEEDED(err)) {
+ setLastError(
+ qMakeError(msgCouldNotSet("BLOB byte array", i),
+ err, QSqlError::StatementError, d->drv_d_func()));
+ return false;
+ }
+ } else {
+ err = MimerSetBlobData(&d->lobhandle, blobArr, size);
+ }
+ }
+ if (!MIMER_SUCCEEDED(err)) {
+ setLastError(qMakeError(msgCouldNotSet("BLOB byte array", i),
+ err, QSqlError::StatementError, d->drv_d_func()));
+ return false;
+ }
+ break;
+ }
+ case MimerColumnTypes::Clob: {
+ QByteArray string_b = val.toString().trimmed().toUtf8();
+ const char *string_u = string_b.constData();
+ size_t size_c = 1;
+ size_t size = 0;
+ while (string_u[size++])
+ if ((string_u[size] & 0xc0) != 0x80)
+ size_c++;
+ err = MimerSetLob(d->statementhandle, i + 1, size_c, &d->lobhandle);
+ if (MIMER_SUCCEEDED(err)) {
+ constexpr size_t maxSize = lobChunkMaxSizeSet;
+ if (size > maxSize) {
+ size_t left_to_send = size;
+ size_t pos = 0;
+ uint step_back = 0;
+ while (left_to_send > 0 && step_back < maxSize) {
+ step_back = 0;
+ if (left_to_send <= maxSize) {
+ err = MimerSetNclobData8(&d->lobhandle, &string_u[pos], left_to_send);
+ left_to_send = 0;
+ } else {
+ // Check that we don't split a multi-byte utf-8 characters
+ while (pos + maxSize - step_back > 0
+ && (string_u[pos + maxSize - step_back] & 0xc0) == 0x80)
+ step_back++;
+ err = MimerSetNclobData8(&d->lobhandle, &string_u[pos],
+ maxSize - step_back);
+ left_to_send = left_to_send - maxSize + step_back;
+ pos += maxSize - step_back;
+ }
+ if (!MIMER_SUCCEEDED(err)) {
+ setLastError(qMakeError(msgCouldNotSet("CLOB", i),
+ err, QSqlError::StatementError, d->drv_d_func()));
+ return false;
+ }
+ }
+ } else {
+ err = MimerSetNclobData8(&d->lobhandle, string_u, size);
+ }
+ }
+ if (!MIMER_SUCCEEDED(err)) {
+ setLastError(
+ qMakeError(msgCouldNotSet("CLOB", i),
+ err, QSqlError::StatementError, d->drv_d_func()));
+ return false;
+ }
+ break;
+ }
+ case MimerColumnTypes::Unknown:
+ default:
+ setLastError(qMakeError(
+ QCoreApplication::translate("QMimerSQLResult", "Unknown datatype, parameter %1")
+ .arg(i),
+ genericError, QSqlError::StatementError, nullptr));
+ return false;
+ }
+ }
+ if (d->execBatch)
+ return true;
+ err = MimerExecute(d->statementhandle);
+ if (MIMER_SUCCEEDED(err)) {
+ d->rowsAffected = err;
+ int k = 0;
+ for (qsizetype i = 0; i < values.size(); i++) {
+ if (bindValueType(i) == QSql::Out || bindValueType(i) == QSql::InOut) {
+ bindValue(i, data(k), QSql::In);
+ k++;
+ }
+ }
+ d->callWithOut = false;
+ }
+ setSelect(false);
+ if (MIMER_SEQUENCE_ERROR == err) {
+ err = MimerOpenCursor(d->statementhandle);
+ d->rowsAffected = err;
+ d->openCursor = true;
+ d->currentRow = QSql::BeforeFirstRow;
+ setSelect(true);
+ }
+ if (!MIMER_SUCCEEDED(err)) {
+ setLastError(
+ qMakeError(QCoreApplication::translate("QMimerSQLResult",
+ "Could not execute statement/open cursor"),
+ err, QSqlError::StatementError, d->drv_d_func()));
+ return false;
+ }
+ setActive(true);
+ return true;
+}
+
+bool QMimerSQLResult::execBatch(bool arrayBind)
+{
+ Q_D(QMimerSQLResult);
+ Q_UNUSED(arrayBind);
+ int32_t err;
+ const QVector<QVariant> values = boundValues();
+
+ // Check that we only have input parameters. Currently
+ // we can only handle batch operations without output parameters.
+ for (qsizetype i = 0; i < values.first().toList().size(); i++)
+ if (bindValueType(i) == QSql::Out || bindValueType(i) == QSql::InOut) {
+ setLastError(qMakeError(QCoreApplication::translate(
+ "QMimerSQLResult",
+ "Only input parameters can be used in batch operations"),
+ genericError, QSqlError::StatementError, nullptr));
+ d->execBatch = false;
+ return false;
+ }
+ d->execBatch = true;
+ for (qsizetype i = 0; i < values.first().toList().size(); i++) {
+ for (qsizetype j = 0; j < values.size(); j++)
+ d->batch_vector.append(values.at(j).toList().at(i));
+ exec();
+ if (i != (values.at(0).toList().size() - 1)) {
+ err = MimerAddBatch(d->statementhandle);
+ if (!MIMER_SUCCEEDED(err)) {
+ setLastError(qMakeError(
+ //: %1 is the batch number
+ QCoreApplication::translate("QMimerSQLResult", "Could not add batch %1")
+ .arg(i),
+ err, QSqlError::StatementError, d->drv_d_func()));
+ d->execBatch = false;
+ return false;
+ }
+ }
+ d->batch_vector.clear();
+ }
+ d->execBatch = false;
+ err = MimerExecute(d->statementhandle);
+ if (!MIMER_SUCCEEDED(err)) {
+ setLastError(qMakeError(
+ QCoreApplication::translate("QMimerSQLResult", "Could not execute batch"), err,
+ QSqlError::StatementError, d->drv_d_func()));
+ return false;
+ }
+ return true;
+}
+
+QVariant QMimerSQLResult::lastInsertId() const
+{
+ Q_D(const QMimerSQLResult);
+ int64_t lastSequence;
+ const int32_t err = MimerGetSequenceInt64(d->statementhandle, &lastSequence);
+ if (!MIMER_SUCCEEDED(err))
+ return QVariant(QMetaType(QMetaType::LongLong), nullptr);
+ return QVariant(qint64(lastSequence));
+}
+
+bool QMimerSQLDriver::hasFeature(DriverFeature f) const
+{
+ switch (f) {
+ case NamedPlaceholders: // Is true in reality but Qt parses Sql statement...
+ case EventNotifications:
+ case LowPrecisionNumbers:
+ case MultipleResultSets:
+ case SimpleLocking:
+ case CancelQuery:
+ return false;
+ case FinishQuery:
+ case LastInsertId:
+ case Transactions:
+ case QuerySize:
+ case BLOB:
+ case Unicode:
+ case PreparedQueries:
+ case PositionalPlaceholders:
+ case BatchOperations:
+ return true;
+ }
+ return true;
+}
+
+bool QMimerSQLDriver::open(const QString &db, const QString &user, const QString &password,
+ const QString &host, int port, const QString &connOpts)
+{
+ Q_D(QMimerSQLDriver);
+ Q_UNUSED(host);
+ Q_UNUSED(port);
+ Q_UNUSED(connOpts);
+ if (isOpen())
+ close();
+ const int32_t err = MimerBeginSession8(db.toUtf8().constData(), user.toUtf8().constData(),
+ password.toUtf8().constData(), &d->sessionhandle);
+ if (!MIMER_SUCCEEDED(err)) {
+ setLastError(qMakeError(
+ QCoreApplication::translate("QMimerSQLDriver", "Could not connect to database")
+ + " "_L1 + db,
+ err, QSqlError::ConnectionError, nullptr));
+ setOpenError(true);
+ return false;
+ }
+ d->dbUser = user;
+ d->dbName = db;
+ setOpen(true);
+ setOpenError(false);
+ return true;
+}
+
+void QMimerSQLDriver::close()
+{
+ Q_D(QMimerSQLDriver);
+ if (isOpen()) {
+ const int end_err = MimerEndSession(&d->sessionhandle);
+ if (MIMER_SUCCEEDED(end_err)) {
+ setOpen(false);
+ setOpenError(false);
+ }
+ }
+}
+
+QSqlResult *QMimerSQLDriver::createResult() const
+{
+ return new QMimerSQLResult(this);
+}
+
+QStringList QMimerSQLDriver::tables(QSql::TableType type) const
+{
+ QStringList tl;
+ if (!isOpen())
+ return tl;
+ QSqlQuery t(createResult());
+ QString sql;
+ switch (type) {
+ case QSql::Tables: {
+ sql = "select table_name from information_schema.tables where "
+ "table_type=\'BASE TABLE\' AND table_schema = CURRENT_USER"_L1;
+ break;
+ }
+ case QSql::SystemTables: {
+ sql = "select table_name from information_schema.tables where "
+ "table_type=\'BASE TABLE\' AND table_schema = \'SYSTEM\'"_L1;
+ break;
+ }
+ case QSql::Views: {
+ sql = "select table_name from information_schema.tables where "
+ "table_type=\'VIEW\' AND table_schema = CURRENT_USER"_L1;
+ break;
+ }
+ case QSql::AllTables: {
+ sql = "select table_name from information_schema.tables where "
+ "(table_type=\'VIEW\' or table_type=\'BASE TABLE\')"
+ " AND (table_schema = CURRENT_USER OR table_schema =\'SYSTEM\')"_L1;
+ break;
+ }
+ default:
+ break;
+ }
+ if (sql.length() > 0) {
+ t.exec(sql);
+ while (t.next())
+ tl.append(t.value(0).toString());
+ }
+ return tl;
+}
+
+QSqlIndex QMimerSQLDriver::primaryIndex(const QString &tablename) const
+{
+ Q_D(const QMimerSQLDriver);
+ if (!isOpen())
+ return QSqlIndex();
+ QString table = tablename;
+ if (isIdentifierEscaped(table, QSqlDriver::TableName))
+ table = stripDelimiters(table, QSqlDriver::TableName);
+ QSqlIndex index(tablename);
+ QSqlQuery t(createResult());
+ QString schema;
+ QString qualifiedName = table;
+ d->splitTableQualifier(qualifiedName, &schema, &table);
+ QString sql =
+ "select information_schema.ext_access_paths.column_name,"
+ "case when data_type = 'INTERVAL' then 'INTERVAL '|| interval_type "
+ "when data_type = 'INTEGER' and numeric_precision > 10 then 'BIGINT' "
+ "when data_type = 'INTEGER' and numeric_precision <= 10 AND NUMERIC_PRECISION > 5 "
+ "then 'INTEGER' when data_type = 'INTEGER' and numeric_precision <= 5 then 'SMALLINT' "
+ "else upper(data_type) end as data_type "
+ "from information_schema.ext_access_paths full outer join "
+ "information_schema.columns on information_schema.ext_access_paths.column_name = "
+ "information_schema.columns.column_name and "
+ "information_schema.ext_access_paths.table_name = "
+ "information_schema.columns.table_name where "
+ "information_schema.ext_access_paths.table_name = \'"_L1;
+ sql.append(table)
+ .append("\' and index_type = \'PRIMARY KEY\'"_L1);
+ if (schema.length() == 0)
+ sql.append(" and table_schema = CURRENT_USER"_L1);
+ else
+ sql.append(" and table_schema = \'"_L1).append(schema).append("\'"_L1);
+
+ if (!t.exec(sql))
+ return QSqlIndex();
+ int i = 0;
+ while (t.next()) {
+ QSqlField field(t.value(0).toString(),
+ QMetaType(qDecodeMSQLType(qLookupMimDataType(t.value(1).toString()))),
+ tablename);
+ index.insert(i, field);
+ index.setName(t.value(0).toString());
+ i++;
+ }
+ return index;
+}
+
+QSqlRecord QMimerSQLDriver::record(const QString &tablename) const
+{
+ Q_D(const QMimerSQLDriver);
+ if (!isOpen())
+ return QSqlRecord();
+ QSqlRecord rec;
+ QSqlQuery t(createResult());
+ QString qualifiedName = tablename;
+ if (isIdentifierEscaped(qualifiedName, QSqlDriver::TableName))
+ qualifiedName = stripDelimiters(qualifiedName, QSqlDriver::TableName);
+ QString schema, table;
+ d->splitTableQualifier(qualifiedName, &schema, &table);
+
+ QString sql =
+ "select column_name, case when data_type = 'INTERVAL' then 'INTERVAL '|| interval_type "
+ "when data_type = 'INTEGER' and numeric_precision > 10 then 'BIGINT' "
+ "when data_type = 'INTEGER' and numeric_precision <= 10 AND numeric_precision > 5 "
+ "then 'INTEGER' when data_type = 'INTEGER' and numeric_precision <= 5 then 'SMALLINT' "
+ "else UPPER(data_type) end as data_type, case when is_nullable = 'YES' then false else "
+ "true end as required, "
+ "coalesce(numeric_precision, coalesce(datetime_precision,coalesce(interval_precision, "
+ "-1))) as prec from information_schema.columns where table_name = \'"_L1;
+ if (schema.length() == 0)
+ sql.append(table).append("\' and table_schema = CURRENT_USER"_L1);
+ else
+ sql.append(table).append("\' and table_schema = \'"_L1).append(schema).append("\'"_L1);
+ sql.append(" order by ordinal_position"_L1);
+ if (!t.exec(sql))
+ return QSqlRecord();
+
+ while (t.next()) {
+ QSqlField field(t.value(0).toString(),
+ QMetaType(qDecodeMSQLType(qLookupMimDataType(t.value(1).toString()))),
+ tablename);
+ field.setRequired(t.value(3).toBool());
+ if (t.value(3).toInt() != -1)
+ field.setPrecision(t.value(3).toInt());
+ rec.append(field);
+ }
+
+ return rec;
+}
+
+QVariant QMimerSQLDriver::handle() const
+{
+ Q_D(const QMimerSQLDriver);
+ return QVariant::fromValue(d->sessionhandle);
+}
+
+QString QMimerSQLDriver::escapeIdentifier(const QString &identifier, IdentifierType type) const
+{
+ Q_UNUSED(type);
+ QString res = identifier;
+ if (!identifier.isEmpty() && !identifier.startsWith(u'"') && !identifier.endsWith(u'"')) {
+ res.replace(u'"', "\"\""_L1);
+ res = u'"' + res + u'"';
+ res.replace(u'.', "\".\""_L1);
+ }
+ return res;
+}
+
+bool QMimerSQLDriver::beginTransaction()
+{
+ Q_D(const QMimerSQLDriver);
+ const int32_t err = MimerBeginTransaction(d->sessionhandle, MIMER_TRANS_READWRITE);
+ if (!MIMER_SUCCEEDED(err)) {
+ setLastError(qMakeError(
+ QCoreApplication::translate("QMimerSQLDriver", "Could not start transaction"), err,
+ QSqlError::TransactionError, d));
+ return false;
+ }
+ return true;
+}
+
+bool QMimerSQLDriver::commitTransaction()
+{
+ Q_D(const QMimerSQLDriver);
+ const int32_t err = MimerEndTransaction(d->sessionhandle, MIMER_COMMIT);
+ if (!MIMER_SUCCEEDED(err)) {
+ setLastError(qMakeError(
+ QCoreApplication::translate("QMimerSQLDriver", "Could not commit transaction"), err,
+ QSqlError::TransactionError, d));
+ return false;
+ }
+ return true;
+}
+
+bool QMimerSQLDriver::rollbackTransaction()
+{
+ Q_D(const QMimerSQLDriver);
+ const int32_t err = MimerEndTransaction(d->sessionhandle, MIMER_ROLLBACK);
+ if (!MIMER_SUCCEEDED(err)) {
+ setLastError(qMakeError(
+ QCoreApplication::translate("QMimerSQLDriver", "Could not roll back transaction"),
+ err, QSqlError::TransactionError, d));
+ return false;
+ }
+ return true;
+}
+
+void QMimerSQLDriverPrivate::splitTableQualifier(const QString &qualifiedName, QString *schema,
+ QString *table) const
+{
+ const QList<QStringView> l = QStringView(qualifiedName).split(u'.');
+ int n = l.count();
+ if (n > 2) {
+ return; // can't possibly be a valid table qualifier
+ } else if (n == 1) {
+ *schema = QString();
+ *table = l.at(0).toString();
+ } else {
+ *schema = l.at(0).toString();
+ *table = l.at(1).toString();
+ }
+}
+
+QT_END_NAMESPACE
+
+#include "moc_qsql_mimer.cpp"
diff --git a/src/plugins/sqldrivers/mimer/qsql_mimer.h b/src/plugins/sqldrivers/mimer/qsql_mimer.h
new file mode 100644
index 0000000000..03ad8f21f2
--- /dev/null
+++ b/src/plugins/sqldrivers/mimer/qsql_mimer.h
@@ -0,0 +1,50 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// Copyright (C) 2022 Mimer Information Technology
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+#ifndef QSQL_MIMER_H
+#define QSQL_MIMER_H
+
+#include <QtSql/qsqldriver.h>
+#include <QUuid>
+#include <mimerapi.h>
+
+#ifdef QT_PLUGIN
+# define Q_EXPORT_SQLDRIVER_MIMER
+#else
+# define Q_EXPORT_SQLDRIVER_MIMER Q_SQL_EXPORT
+#endif
+
+QT_BEGIN_NAMESPACE
+
+class QMimerSQLDriverPrivate;
+
+class Q_EXPORT_SQLDRIVER_MIMER QMimerSQLDriver : public QSqlDriver
+{
+ friend class QMimerSQLResultPrivate;
+ Q_DECLARE_PRIVATE(QMimerSQLDriver)
+ Q_OBJECT
+public:
+ explicit QMimerSQLDriver(QObject *parent = nullptr);
+ explicit QMimerSQLDriver(MimerSession *conn, QObject *parent = nullptr);
+ ~QMimerSQLDriver() override;
+ bool hasFeature(DriverFeature f) const override;
+ bool open(const QString &db, const QString &user, const QString &password, const QString &host,
+ int port, const QString &connOpts) override;
+ void close() override;
+ QSqlResult *createResult() const override;
+ QStringList tables(QSql::TableType type) const override;
+ QSqlIndex primaryIndex(const QString &tablename) const override;
+ QSqlRecord record(const QString &tablename) const override;
+ QVariant handle() const override;
+ QString escapeIdentifier(const QString &identifier, IdentifierType type) const override;
+protected:
+ bool beginTransaction() override;
+ bool commitTransaction() override;
+ bool rollbackTransaction() override;
+
+private:
+};
+
+QT_END_NAMESPACE
+
+#endif // QSQL_MIMER
diff --git a/src/plugins/sqldrivers/mysql/CMakeLists.txt b/src/plugins/sqldrivers/mysql/CMakeLists.txt
index b965ab2daa..2e3d028584 100644
--- a/src/plugins/sqldrivers/mysql/CMakeLists.txt
+++ b/src/plugins/sqldrivers/mysql/CMakeLists.txt
@@ -1,7 +1,5 @@
# Copyright (C) 2022 The Qt Company Ltd.
-# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-
-# Generated from mysql.pro.
+# SPDX-License-Identifier: BSD-3-Clause
#####################################################################
## QMYSQLDriverPlugin Plugin:
@@ -16,6 +14,7 @@ qt_internal_add_plugin(QMYSQLDriverPlugin
DEFINES
QT_NO_CAST_FROM_ASCII
QT_NO_CAST_TO_ASCII
+ QT_NO_CONTEXTLESS_CONNECT
LIBRARIES
MySQL::MySQL
Qt::Core
@@ -23,7 +22,4 @@ qt_internal_add_plugin(QMYSQLDriverPlugin
Qt::SqlPrivate
)
-#### Keys ignored in scope 1:.:.:mysql.pro:<TRUE>:
-# OTHER_FILES = "mysql.json"
-
qt_internal_force_macos_intel_arch(QMYSQLDriverPlugin)
diff --git a/src/plugins/sqldrivers/mysql/qsql_mysql.cpp b/src/plugins/sqldrivers/mysql/qsql_mysql.cpp
index 450d551ac5..cfd4931b46 100644
--- a/src/plugins/sqldrivers/mysql/qsql_mysql.cpp
+++ b/src/plugins/sqldrivers/mysql/qsql_mysql.cpp
@@ -10,12 +10,14 @@
#include <qdebug.h>
#include <qfile.h>
#include <qlist.h>
+#include <qloggingcategory.h>
#include <qsqlerror.h>
#include <qsqlfield.h>
#include <qsqlindex.h>
#include <qsqlquery.h>
#include <qsqlrecord.h>
#include <qstringlist.h>
+#include <qtimezone.h>
#include <QtSql/private/qsqldriver_p.h>
#include <QtSql/private/qsqlresult_p.h>
@@ -29,12 +31,30 @@ Q_DECLARE_METATYPE(MYSQL_RES*)
Q_DECLARE_METATYPE(MYSQL*)
Q_DECLARE_METATYPE(MYSQL_STMT*)
+// MYSQL_TYPE_JSON was introduced with MySQL 5.7.9
+#if defined(MYSQL_VERSION_ID) && MYSQL_VERSION_ID < 50709
+#define MYSQL_TYPE_JSON 245
+#endif
+
// MySQL above version 8 removed my_bool typedef while MariaDB kept it,
// by redefining it we can regain source compatibility.
using my_bool = decltype(mysql_stmt_bind_result(nullptr, nullptr));
+// this is a copy of the old MYSQL_TIME before an additional integer was added in
+// 8.0.27.0. This kills the sanity check during retrieving this struct from mysql
+// when another libmysql version is used during runtime than during compile time
+struct QT_MYSQL_TIME
+{
+ unsigned int year, month, day, hour, minute, second;
+ unsigned long second_part; /**< microseconds */
+ my_bool neg;
+ enum enum_mysql_timestamp_type time_type;
+};
+
QT_BEGIN_NAMESPACE
+static Q_LOGGING_CATEGORY(lcMysql, "qt.sql.mysql")
+
using namespace Qt::StringLiterals;
class QMYSQLDriverPrivate : public QSqlDriverPrivate
@@ -45,6 +65,7 @@ public:
QMYSQLDriverPrivate() : QSqlDriverPrivate(QSqlDriver::MySqlServer)
{}
MYSQL *mysql = nullptr;
+ QString dbName;
bool preparedQuerysEnabled = false;
};
@@ -80,9 +101,15 @@ static inline QVariant qDateTimeFromString(QString &val)
#else
if (val.isEmpty())
return QVariant(QDateTime());
- if (val.length() == 14)
- // TIMESTAMPS have the format yyyyMMddhhmmss
+
+ // TIMESTAMPS have either the format "yyyyMMddhhmmss" or "yyyy-MM-dd
+ // hh:mm:ss". QDateTime::fromString() can convert the latter, but not the
+ // former, so adapt it if necessary.
+ if (val.size() == 14)
val.insert(4, u'-').insert(7, u'-').insert(10, u'T').insert(13, u':').insert(16, u':');
+
+ if (!val.endsWith(u'Z'))
+ val.append(u'Z'); // make UTC
return QVariant(QDateTime::fromString(val, Qt::ISODate));
#endif
}
@@ -101,6 +128,18 @@ static inline bool checkPreparedQueries(MYSQL *mysql)
return mysql_stmt_param_count(stmt.get()) == 2;
}
+// used with prepared queries and bound arguments
+static inline void setUtcTimeZone(MYSQL *mysql)
+{
+ std::unique_ptr<MYSQL_STMT, decltype(&mysql_stmt_close)> stmt(mysql_stmt_init(mysql), &mysql_stmt_close);
+ if (!stmt)
+ return;
+
+ static const char query[] = "SET time_zone = '+00:00'";
+ if (mysql_stmt_prepare(stmt.get(), query, sizeof(query) - 1))
+ mysql_stmt_execute(stmt.get());
+}
+
class QMYSQLResultPrivate;
class QMYSQLResult : public QSqlResult
@@ -171,68 +210,70 @@ public:
bool preparedQuery = false;
};
-static QSqlError qMakeError(const QString& err, QSqlError::ErrorType type,
- const QMYSQLDriverPrivate* p)
+static QSqlError qMakeError(const QString &err, QSqlError::ErrorType type,
+ const QMYSQLDriverPrivate *p)
{
- const char *cerr = p->mysql ? mysql_error(p->mysql) : 0;
+ const char *cerr = p->mysql ? mysql_error(p->mysql) : nullptr;
return QSqlError("QMYSQL: "_L1 + err,
QString::fromUtf8(cerr),
type, QString::number(mysql_errno(p->mysql)));
}
-static QMetaType qDecodeMYSQLType(int mysqltype, uint flags)
+static QMetaType qDecodeMYSQLType(enum_field_types mysqltype, uint flags)
{
QMetaType::Type type;
switch (mysqltype) {
- case FIELD_TYPE_TINY :
+ case MYSQL_TYPE_TINY:
type = (flags & UNSIGNED_FLAG) ? QMetaType::UChar : QMetaType::Char;
break;
- case FIELD_TYPE_SHORT :
+ case MYSQL_TYPE_SHORT:
type = (flags & UNSIGNED_FLAG) ? QMetaType::UShort : QMetaType::Short;
break;
- case FIELD_TYPE_LONG :
- case FIELD_TYPE_INT24 :
+ case MYSQL_TYPE_LONG:
+ case MYSQL_TYPE_INT24:
type = (flags & UNSIGNED_FLAG) ? QMetaType::UInt : QMetaType::Int;
break;
- case FIELD_TYPE_YEAR :
+ case MYSQL_TYPE_YEAR:
type = QMetaType::Int;
break;
- case FIELD_TYPE_LONGLONG :
+ case MYSQL_TYPE_BIT:
+ case MYSQL_TYPE_LONGLONG:
type = (flags & UNSIGNED_FLAG) ? QMetaType::ULongLong : QMetaType::LongLong;
break;
- case FIELD_TYPE_FLOAT :
- case FIELD_TYPE_DOUBLE :
- case FIELD_TYPE_DECIMAL :
-#if defined(FIELD_TYPE_NEWDECIMAL)
- case FIELD_TYPE_NEWDECIMAL:
-#endif
+ case MYSQL_TYPE_FLOAT:
+ case MYSQL_TYPE_DOUBLE:
+ case MYSQL_TYPE_DECIMAL:
+ case MYSQL_TYPE_NEWDECIMAL:
type = QMetaType::Double;
break;
- case FIELD_TYPE_DATE :
+ case MYSQL_TYPE_DATE:
type = QMetaType::QDate;
break;
- case FIELD_TYPE_TIME :
+ case MYSQL_TYPE_TIME:
// A time field can be within the range '-838:59:59' to '838:59:59' so
// use QString instead of QTime since QTime is limited to 24 hour clock
type = QMetaType::QString;
break;
- case FIELD_TYPE_DATETIME :
- case FIELD_TYPE_TIMESTAMP :
+ case MYSQL_TYPE_DATETIME:
+ case MYSQL_TYPE_TIMESTAMP:
type = QMetaType::QDateTime;
break;
- case FIELD_TYPE_STRING :
- case FIELD_TYPE_VAR_STRING :
- case FIELD_TYPE_BLOB :
- case FIELD_TYPE_TINY_BLOB :
- case FIELD_TYPE_MEDIUM_BLOB :
- case FIELD_TYPE_LONG_BLOB :
- case FIELD_TYPE_GEOMETRY :
+ case MYSQL_TYPE_STRING:
+ case MYSQL_TYPE_VAR_STRING:
+ case MYSQL_TYPE_BLOB:
+ case MYSQL_TYPE_TINY_BLOB:
+ case MYSQL_TYPE_MEDIUM_BLOB:
+ case MYSQL_TYPE_LONG_BLOB:
+ case MYSQL_TYPE_GEOMETRY:
+ case MYSQL_TYPE_JSON:
type = (flags & BINARY_FLAG) ? QMetaType::QByteArray : QMetaType::QString;
break;
- default:
- case FIELD_TYPE_ENUM :
- case FIELD_TYPE_SET :
+ case MYSQL_TYPE_ENUM:
+ case MYSQL_TYPE_SET:
+ type = QMetaType::QString;
+ break;
+ default: // needed because there are more enum values which are not available in all headers
type = QMetaType::QString;
break;
}
@@ -242,18 +283,17 @@ static QMetaType qDecodeMYSQLType(int mysqltype, uint flags)
static QSqlField qToField(MYSQL_FIELD *field)
{
QSqlField f(QString::fromUtf8(field->name),
- qDecodeMYSQLType(int(field->type), field->flags),
+ qDecodeMYSQLType(field->type, field->flags),
QString::fromUtf8(field->table));
f.setRequired(IS_NOT_NULL(field->flags));
f.setLength(field->length);
f.setPrecision(field->decimals);
- f.setSqlType(field->type);
f.setAutoValue(field->flags & AUTO_INCREMENT_FLAG);
return f;
}
-static QSqlError qMakeStmtError(const QString& err, QSqlError::ErrorType type,
- MYSQL_STMT* stmt)
+static QSqlError qMakeStmtError(const QString &err, QSqlError::ErrorType type,
+ MYSQL_STMT *stmt)
{
const char *cerr = mysql_stmt_error(stmt);
return QSqlError("QMYSQL: "_L1 + err,
@@ -261,15 +301,16 @@ static QSqlError qMakeStmtError(const QString& err, QSqlError::ErrorType type,
type, QString::number(mysql_stmt_errno(stmt)));
}
-static bool qIsBlob(int t)
+static bool qIsBlob(enum_field_types t)
{
return t == MYSQL_TYPE_TINY_BLOB
|| t == MYSQL_TYPE_BLOB
|| t == MYSQL_TYPE_MEDIUM_BLOB
- || t == MYSQL_TYPE_LONG_BLOB;
+ || t == MYSQL_TYPE_LONG_BLOB
+ || t == MYSQL_TYPE_JSON;
}
-static bool qIsTimeOrDate(int t)
+static bool qIsTimeOrDate(enum_field_types t)
{
// *not* MYSQL_TYPE_TIME because its range is bigger than QTime
// (see above)
@@ -284,9 +325,14 @@ static bool qIsInteger(int t)
|| t == QMetaType::LongLong || t == QMetaType::ULongLong;
}
+static inline bool qIsBitfield(enum_field_types type)
+{
+ return type == MYSQL_TYPE_BIT;
+}
+
void QMYSQLResultPrivate::bindBlobs()
{
- for (int i = 0; i < fields.count(); ++i) {
+ for (int i = 0; i < fields.size(); ++i) {
const MYSQL_FIELD *fieldInfo = fields.at(i).myField;
if (qIsBlob(inBinds[i].buffer_type) && meta && fieldInfo) {
MYSQL_BIND *bind = &inBinds[i];
@@ -328,7 +374,7 @@ bool QMYSQLResultPrivate::bindInValues()
bind->buffer_length = f.bufLength = 0;
hasBlobs = true;
} else if (qIsTimeOrDate(fieldInfo->type)) {
- bind->buffer_length = f.bufLength = sizeof(MYSQL_TIME);
+ bind->buffer_length = f.bufLength = sizeof(QT_MYSQL_TIME);
} else if (qIsInteger(f.type.id())) {
bind->buffer_length = f.bufLength = 8;
} else {
@@ -382,7 +428,7 @@ void QMYSQLResult::cleanup()
if (d->stmt) {
if (mysql_stmt_close(d->stmt))
- qWarning("QMYSQLResult::cleanup: unable to free statement handle");
+ qCWarning(lcMysql, "QMYSQLResult::cleanup: unable to free statement handle");
d->stmt = 0;
}
@@ -391,9 +437,8 @@ void QMYSQLResult::cleanup()
d->meta = 0;
}
- int i;
- for (i = 0; i < d->fields.count(); ++i)
- delete[] d->fields[i].outField;
+ for (const QMYSQLResultPrivate::QMyField &f : std::as_const(d->fields))
+ delete[] f.outField;
if (d->outBinds) {
delete[] d->outBinds;
@@ -483,12 +528,7 @@ bool QMYSQLResult::fetchLast()
return success;
}
- my_ulonglong numRows;
- if (d->preparedQuery) {
- numRows = mysql_stmt_num_rows(d->stmt);
- } else {
- numRows = mysql_num_rows(d->result);
- }
+ my_ulonglong numRows = d->preparedQuery ? mysql_stmt_num_rows(d->stmt) : mysql_num_rows(d->result);
if (at() == int(numRows))
return true;
if (!numRows)
@@ -506,11 +546,25 @@ bool QMYSQLResult::fetchFirst()
return fetch(0);
}
+static inline uint64_t
+qDecodeBitfield(const QMYSQLResultPrivate::QMyField &f, const char *outField)
+{
+ // byte-aligned length
+ const auto numBytes = (f.myField->length + 7) / 8;
+ uint64_t val = 0;
+ for (unsigned long i = 0; i < numBytes && outField; ++i) {
+ uint64_t tmp = static_cast<uint8_t>(outField[i]);
+ val <<= 8;
+ val |= tmp;
+ }
+ return val;
+}
+
QVariant QMYSQLResult::data(int field)
{
Q_D(QMYSQLResult);
- if (!isSelect() || field >= d->fields.count()) {
- qWarning("QMYSQLResult::data: column %d out of range", field);
+ if (!isSelect() || field >= d->fields.size()) {
+ qCWarning(lcMysql, "QMYSQLResult::data: column %d out of range", field);
return QVariant();
}
@@ -523,8 +577,9 @@ QVariant QMYSQLResult::data(int field)
if (d->preparedQuery) {
if (f.nullIndicator)
return QVariant(f.type);
-
- if (qIsInteger(f.type.id())) {
+ if (qIsBitfield(f.myField->type)) {
+ return QVariant::fromValue(qDecodeBitfield(f, f.outField));
+ } else if (qIsInteger(f.type.id())) {
QVariant variant(f.type, f.outField);
// we never want to return char variants here, see QTBUG-53397
if (f.type.id() == QMetaType::UChar)
@@ -532,8 +587,8 @@ QVariant QMYSQLResult::data(int field)
else if (f.type.id() == QMetaType::Char)
return variant.toInt();
return variant;
- } else if (qIsTimeOrDate(f.myField->type) && f.bufLength == sizeof(MYSQL_TIME)) {
- auto t = reinterpret_cast<const MYSQL_TIME *>(f.outField);
+ } else if (qIsTimeOrDate(f.myField->type) && f.bufLength >= sizeof(QT_MYSQL_TIME)) {
+ auto t = reinterpret_cast<const QT_MYSQL_TIME *>(f.outField);
QDate date;
QTime time;
if (f.type.id() != QMetaType::QTime)
@@ -541,7 +596,7 @@ QVariant QMYSQLResult::data(int field)
if (f.type.id() != QMetaType::QDate)
time = QTime(t->hour, t->minute, t->second, t->second_part / 1000);
if (f.type.id() == QMetaType::QDateTime)
- return QDateTime(date, time);
+ return QDateTime(date, time, QTimeZone::UTC);
else if (f.type.id() == QMetaType::QDate)
return date;
else
@@ -556,6 +611,9 @@ QVariant QMYSQLResult::data(int field)
return QVariant(f.type);
}
+ if (qIsBitfield(f.myField->type))
+ return QVariant::fromValue(qDecodeBitfield(f, d->row[field]));
+
fieldLength = mysql_fetch_lengths(d->result)[field];
if (f.type.id() != QMetaType::QByteArray)
@@ -625,7 +683,7 @@ QVariant QMYSQLResult::data(int field)
bool QMYSQLResult::isNull(int field)
{
Q_D(const QMYSQLResult);
- if (field < 0 || field >= d->fields.count())
+ if (field < 0 || field >= d->fields.size())
return true;
if (d->preparedQuery)
return d->fields.at(field).nullIndicator;
@@ -644,7 +702,7 @@ bool QMYSQLResult::reset (const QString& query)
cleanup();
const QByteArray encQuery = query.toUtf8();
- if (mysql_real_query(d->drv_d_func()->mysql, encQuery.data(), encQuery.length())) {
+ if (mysql_real_query(d->drv_d_func()->mysql, encQuery.data(), encQuery.size())) {
setLastError(qMakeError(QCoreApplication::translate("QMYSQLResult", "Unable to execute query"),
QSqlError::StatementError, d->drv_d_func()));
return false;
@@ -664,6 +722,7 @@ bool QMYSQLResult::reset (const QString& query)
for(int i = 0; i < numFields; i++) {
MYSQL_FIELD* field = mysql_fetch_field_direct(d->result, i);
d->fields[i].type = qDecodeMYSQLType(field->type, field->flags);
+ d->fields[i].myField = field;
}
setAt(QSql::BeforeFirstRow);
}
@@ -729,7 +788,7 @@ QSqlRecord QMYSQLResult::record() const
if (!mysql_errno(d->drv_d_func()->mysql)) {
mysql_field_seek(res, 0);
MYSQL_FIELD* field = mysql_fetch_field(res);
- while(field) {
+ while (field) {
info.append(qToField(field));
field = mysql_fetch_field(res);
}
@@ -752,8 +811,8 @@ bool QMYSQLResult::nextResult()
d->result = 0;
setSelect(false);
- for (int i = 0; i < d->fields.count(); ++i)
- delete[] d->fields[i].outField;
+ for (const QMYSQLResultPrivate::QMyField &f : std::as_const(d->fields))
+ delete[] f.outField;
d->fields.clear();
int status = mysql_next_result(d->drv_d_func()->mysql);
@@ -766,7 +825,7 @@ bool QMYSQLResult::nextResult()
}
d->result = mysql_store_result(d->drv_d_func()->mysql);
- int numFields = mysql_field_count(d->drv_d_func()->mysql);
+ unsigned int numFields = mysql_field_count(d->drv_d_func()->mysql);
if (!d->result && numFields > 0) {
setLastError(qMakeError(QCoreApplication::translate("QMYSQLResult", "Unable to store next result"),
QSqlError::StatementError, d->drv_d_func()));
@@ -778,9 +837,10 @@ bool QMYSQLResult::nextResult()
d->rowsAffected = mysql_affected_rows(d->drv_d_func()->mysql);
if (isSelect()) {
- for (int i = 0; i < numFields; i++) {
- MYSQL_FIELD* field = mysql_fetch_field_direct(d->result, i);
+ for (unsigned int i = 0; i < numFields; i++) {
+ MYSQL_FIELD *field = mysql_fetch_field_direct(d->result, i);
d->fields[i].type = qDecodeMYSQLType(field->type, field->flags);
+ d->fields[i].myField = field;
}
}
@@ -793,29 +853,6 @@ void QMYSQLResult::virtual_hook(int id, void *data)
QSqlResult::virtual_hook(id, data);
}
-static MYSQL_TIME *toMySqlDate(QDate date, QTime time, int type)
-{
- Q_ASSERT(type == QMetaType::QTime || type == QMetaType::QDate
- || type == QMetaType::QDateTime);
-
- MYSQL_TIME *myTime = new MYSQL_TIME;
- memset(myTime, 0, sizeof(MYSQL_TIME));
-
- if (type == QMetaType::QTime || type == QMetaType::QDateTime) {
- myTime->hour = time.hour();
- myTime->minute = time.minute();
- myTime->second = time.second();
- myTime->second_part = time.msec() * 1000;
- }
- if (type == QMetaType::QDate || type == QMetaType::QDateTime) {
- myTime->year = date.year();
- myTime->month = date.month();
- myTime->day = date.day();
- }
-
- return myTime;
-}
-
bool QMYSQLResult::prepare(const QString& query)
{
Q_D(QMYSQLResult);
@@ -840,7 +877,7 @@ bool QMYSQLResult::prepare(const QString& query)
}
const QByteArray encQuery = query.toUtf8();
- r = mysql_stmt_prepare(d->stmt, encQuery.constData(), encQuery.length());
+ r = mysql_stmt_prepare(d->stmt, encQuery.constData(), encQuery.size());
if (r != 0) {
setLastError(qMakeStmtError(QCoreApplication::translate("QMYSQLResult",
"Unable to prepare statement"), QSqlError::StatementError, d->stmt));
@@ -848,9 +885,9 @@ bool QMYSQLResult::prepare(const QString& query)
return false;
}
- if (mysql_stmt_param_count(d->stmt) > 0) {// allocate memory for outvalues
- d->outBinds = new MYSQL_BIND[mysql_stmt_param_count(d->stmt)];
- }
+ const auto paramCount = mysql_stmt_param_count(d->stmt);
+ if (paramCount > 0) // allocate memory for outvalues
+ d->outBinds = new MYSQL_BIND[paramCount]();
setSelect(d->bindInValues());
d->preparedQuery = true;
@@ -868,7 +905,7 @@ bool QMYSQLResult::exec()
return false;
int r = 0;
- QList<MYSQL_TIME *> timeVector;
+ QList<QT_MYSQL_TIME *> timeVector;
QList<QByteArray> stringVector;
QList<my_bool> nullVector;
@@ -881,11 +918,10 @@ bool QMYSQLResult::exec()
return false;
}
- if (mysql_stmt_param_count(d->stmt) > 0 &&
- mysql_stmt_param_count(d->stmt) == (uint)values.count()) {
-
- nullVector.resize(values.count());
- for (int i = 0; i < values.count(); ++i) {
+ const unsigned long paramCount = mysql_stmt_param_count(d->stmt);
+ if (paramCount > 0 && paramCount == static_cast<size_t>(values.size())) {
+ nullVector.resize(values.size());
+ for (qsizetype i = 0; i < values.size(); ++i) {
const QVariant &val = boundValues().at(i);
void *data = const_cast<void *>(val.constData());
@@ -906,27 +942,41 @@ bool QMYSQLResult::exec()
case QMetaType::QTime:
case QMetaType::QDate:
case QMetaType::QDateTime: {
- MYSQL_TIME *myTime = toMySqlDate(val.toDate(), val.toTime(), val.userType());
+ auto myTime = new QT_MYSQL_TIME{};
timeVector.append(myTime);
-
currBind->buffer = myTime;
- switch (val.userType()) {
- case QMetaType::QTime:
+
+ QDate date;
+ QTime time;
+ int type = val.userType();
+ if (type == QMetaType::QTime) {
+ time = val.toTime();
currBind->buffer_type = MYSQL_TYPE_TIME;
myTime->time_type = MYSQL_TIMESTAMP_TIME;
- break;
- case QMetaType::QDate:
+ } else if (type == QMetaType::QDate) {
+ date = val.toDate();
currBind->buffer_type = MYSQL_TYPE_DATE;
myTime->time_type = MYSQL_TIMESTAMP_DATE;
- break;
- case QMetaType::QDateTime:
+ } else {
+ QDateTime dt = val.toDateTime().toUTC();
+ date = dt.date();
+ time = dt.time();
currBind->buffer_type = MYSQL_TYPE_DATETIME;
myTime->time_type = MYSQL_TIMESTAMP_DATETIME;
- break;
- default:
- break;
}
- currBind->buffer_length = sizeof(MYSQL_TIME);
+
+ if (type == QMetaType::QTime || type == QMetaType::QDateTime) {
+ myTime->hour = time.hour();
+ myTime->minute = time.minute();
+ myTime->second = time.second();
+ myTime->second_part = time.msec() * 1000;
+ }
+ if (type == QMetaType::QDate || type == QMetaType::QDateTime) {
+ myTime->year = date.year();
+ myTime->month = date.month();
+ myTime->day = date.day();
+ }
+ currBind->buffer_length = sizeof(QT_MYSQL_TIME);
currBind->length = 0;
break; }
case QMetaType::UInt:
@@ -960,12 +1010,16 @@ bool QMYSQLResult::exec()
stringVector.append(ba);
currBind->buffer_type = MYSQL_TYPE_STRING;
currBind->buffer = const_cast<char *>(ba.constData());
- currBind->buffer_length = ba.length();
+ currBind->buffer_length = ba.size();
break; }
}
}
+#if defined(MARIADB_VERSION_ID) || MYSQL_VERSION_ID < 80300
r = mysql_stmt_bind_param(d->stmt, d->outBinds);
+#else
+ r = mysql_stmt_bind_named_param(d->stmt, d->outBinds, paramCount, nullptr);
+#endif
if (r != 0) {
setLastError(qMakeStmtError(QCoreApplication::translate("QMYSQLResult",
"Unable to bind value"), QSqlError::StatementError, d->stmt));
@@ -1036,7 +1090,7 @@ static void qLibraryInit()
return;
if (mysql_library_init(0, 0, 0)) {
- qWarning("QMYSQLDriver::qServerInit: unable to start server.");
+ qCWarning(lcMysql, "QMYSQLDriver::qServerInit: unable to start server.");
}
#endif // Q_NO_MYSQL_EMBEDDED
@@ -1128,7 +1182,7 @@ bool QMYSQLDriver::hasFeature(DriverFeature f) const
return false;
}
-static void setOptionFlag(uint &optionFlags, const QString &opt)
+static void setOptionFlag(uint &optionFlags, QStringView opt)
{
if (opt == "CLIENT_COMPRESS"_L1)
optionFlags |= CLIENT_COMPRESS;
@@ -1143,85 +1197,167 @@ static void setOptionFlag(uint &optionFlags, const QString &opt)
else if (opt == "CLIENT_ODBC"_L1)
optionFlags |= CLIENT_ODBC;
else if (opt == "CLIENT_SSL"_L1)
- qWarning("QMYSQLDriver: SSL_KEY, SSL_CERT and SSL_CA should be used instead of CLIENT_SSL.");
+ qCWarning(lcMysql, "QMYSQLDriver: MYSQL_OPT_SSL_KEY, MYSQL_OPT_SSL_CERT "
+ "and MYSQL_OPT_SSL_CA should be used instead of CLIENT_SSL.");
else
- qWarning("QMYSQLDriver::open: Unknown connect option '%s'", opt.toLocal8Bit().constData());
+ qCWarning(lcMysql, "QMYSQLDriver::open: Unknown connect option '%ls'",
+ qUtf16Printable(QString(opt)));
+}
+
+static bool setOptionString(MYSQL *mysql, mysql_option option, QStringView v)
+{
+ return mysql_options(mysql, option, v.toUtf8().constData()) == 0;
}
-bool QMYSQLDriver::open(const QString& db,
- const QString& user,
- const QString& password,
- const QString& host,
- int port,
- const QString& connOpts)
+static bool setOptionInt(MYSQL *mysql, mysql_option option, QStringView v)
+{
+ bool bOk;
+ const auto val = v.toInt(&bOk);
+ return bOk ? mysql_options(mysql, option, &val) == 0 : false;
+}
+
+static bool setOptionBool(MYSQL *mysql, mysql_option option, QStringView v)
+{
+ bool val = (v.isEmpty() || v == "TRUE"_L1 || v == "1"_L1);
+ return mysql_options(mysql, option, &val) == 0;
+}
+
+// MYSQL_OPT_SSL_MODE was introduced with MySQL 5.7.11
+#if defined(MYSQL_VERSION_ID) && MYSQL_VERSION_ID >= 50711 && !defined(MARIADB_VERSION_ID)
+static bool setOptionSslMode(MYSQL *mysql, mysql_option option, QStringView v)
+{
+ mysql_ssl_mode sslMode = SSL_MODE_DISABLED;
+ if (v == "DISABLED"_L1 || v == "SSL_MODE_DISABLED"_L1)
+ sslMode = SSL_MODE_DISABLED;
+ else if (v == "PREFERRED"_L1 || v == "SSL_MODE_PREFERRED"_L1)
+ sslMode = SSL_MODE_PREFERRED;
+ else if (v == "REQUIRED"_L1 || v == "SSL_MODE_REQUIRED"_L1)
+ sslMode = SSL_MODE_REQUIRED;
+ else if (v == "VERIFY_CA"_L1 || v == "SSL_MODE_VERIFY_CA"_L1)
+ sslMode = SSL_MODE_VERIFY_CA;
+ else if (v == "VERIFY_IDENTITY"_L1 || v == "SSL_MODE_VERIFY_IDENTITY"_L1)
+ sslMode = SSL_MODE_VERIFY_IDENTITY;
+ else
+ qCWarning(lcMysql, "Unknown ssl mode '%ls' - using SSL_MODE_DISABLED",
+ qUtf16Printable(QString(v)));
+ return mysql_options(mysql, option, &sslMode) == 0;
+}
+#endif
+
+static bool setOptionProtocol(MYSQL *mysql, mysql_option option, QStringView v)
+{
+ mysql_protocol_type proto = MYSQL_PROTOCOL_DEFAULT;
+ if (v == "TCP"_L1 || v == "MYSQL_PROTOCOL_TCP"_L1)
+ proto = MYSQL_PROTOCOL_TCP;
+ else if (v == "SOCKET"_L1 || v == "MYSQL_PROTOCOL_SOCKET"_L1)
+ proto = MYSQL_PROTOCOL_SOCKET;
+ else if (v == "PIPE"_L1 || v == "MYSQL_PROTOCOL_PIPE"_L1)
+ proto = MYSQL_PROTOCOL_PIPE;
+ else if (v == "MEMORY"_L1 || v == "MYSQL_PROTOCOL_MEMORY"_L1)
+ proto = MYSQL_PROTOCOL_MEMORY;
+ else if (v == "DEFAULT"_L1 || v == "MYSQL_PROTOCOL_DEFAULT"_L1)
+ proto = MYSQL_PROTOCOL_DEFAULT;
+ else
+ qCWarning(lcMysql, "Unknown protocol '%ls' - using MYSQL_PROTOCOL_DEFAULT",
+ qUtf16Printable(QString(v)));
+ return mysql_options(mysql, option, &proto) == 0;
+}
+
+bool QMYSQLDriver::open(const QString &db,
+ const QString &user,
+ const QString &password,
+ const QString &host,
+ int port,
+ const QString &connOpts)
{
Q_D(QMYSQLDriver);
if (isOpen())
close();
+ if (!(d->mysql = mysql_init(nullptr))) {
+ setLastError(qMakeError(tr("Unable to allocate a MYSQL object"),
+ QSqlError::ConnectionError, d));
+ setOpenError(true);
+ return false;
+ }
+
+ typedef bool (*SetOptionFunc)(MYSQL*, mysql_option, QStringView);
+ struct mysqloptions {
+ QLatin1StringView key;
+ mysql_option option;
+ SetOptionFunc func;
+ };
+ const mysqloptions options[] = {
+ {"SSL_KEY"_L1, MYSQL_OPT_SSL_KEY, setOptionString},
+ {"SSL_CERT"_L1, MYSQL_OPT_SSL_CERT, setOptionString},
+ {"SSL_CA"_L1, MYSQL_OPT_SSL_CA, setOptionString},
+ {"SSL_CAPATH"_L1, MYSQL_OPT_SSL_CAPATH, setOptionString},
+ {"SSL_CIPHER"_L1, MYSQL_OPT_SSL_CIPHER, setOptionString},
+ {"MYSQL_OPT_SSL_KEY"_L1, MYSQL_OPT_SSL_KEY, setOptionString},
+ {"MYSQL_OPT_SSL_CERT"_L1, MYSQL_OPT_SSL_CERT, setOptionString},
+ {"MYSQL_OPT_SSL_CA"_L1, MYSQL_OPT_SSL_CA, setOptionString},
+ {"MYSQL_OPT_SSL_CAPATH"_L1, MYSQL_OPT_SSL_CAPATH, setOptionString},
+ {"MYSQL_OPT_SSL_CIPHER"_L1, MYSQL_OPT_SSL_CIPHER, setOptionString},
+ {"MYSQL_OPT_SSL_CRL"_L1, MYSQL_OPT_SSL_CRL, setOptionString},
+ {"MYSQL_OPT_SSL_CRLPATH"_L1, MYSQL_OPT_SSL_CRLPATH, setOptionString},
+#if defined(MYSQL_VERSION_ID) && MYSQL_VERSION_ID >= 50710
+ {"MYSQL_OPT_TLS_VERSION"_L1, MYSQL_OPT_TLS_VERSION, setOptionString},
+#endif
+#if defined(MYSQL_VERSION_ID) && MYSQL_VERSION_ID >= 50711 && !defined(MARIADB_VERSION_ID)
+ {"MYSQL_OPT_SSL_MODE"_L1, MYSQL_OPT_SSL_MODE, setOptionSslMode},
+#endif
+ {"MYSQL_OPT_CONNECT_TIMEOUT"_L1, MYSQL_OPT_CONNECT_TIMEOUT, setOptionInt},
+ {"MYSQL_OPT_READ_TIMEOUT"_L1, MYSQL_OPT_READ_TIMEOUT, setOptionInt},
+ {"MYSQL_OPT_WRITE_TIMEOUT"_L1, MYSQL_OPT_WRITE_TIMEOUT, setOptionInt},
+ {"MYSQL_OPT_RECONNECT"_L1, MYSQL_OPT_RECONNECT, setOptionBool},
+ {"MYSQL_OPT_LOCAL_INFILE"_L1, MYSQL_OPT_LOCAL_INFILE, setOptionInt},
+ {"MYSQL_OPT_PROTOCOL"_L1, MYSQL_OPT_PROTOCOL, setOptionProtocol},
+ {"MYSQL_SHARED_MEMORY_BASE_NAME"_L1, MYSQL_SHARED_MEMORY_BASE_NAME, setOptionString},
+ };
+ auto trySetOption = [&](const QStringView &key, const QStringView &value) -> bool {
+ for (const mysqloptions &opt : options) {
+ if (key == opt.key) {
+ if (!opt.func(d->mysql, opt.option, value)) {
+ qCWarning(lcMysql, "QMYSQLDriver::open: Could not set connect option value "
+ "'%ls' to '%ls'",
+ qUtf16Printable(QString(key)), qUtf16Printable(QString(value)));
+ }
+ return true;
+ }
+ }
+ return false;
+ };
+
/* This is a hack to get MySQL's stored procedure support working.
Since a stored procedure _may_ return multiple result sets,
we have to enable CLIEN_MULTI_STATEMENTS here, otherwise _any_
stored procedure call will fail.
*/
unsigned int optionFlags = CLIENT_MULTI_STATEMENTS;
- const QStringList opts(connOpts.split(u';', Qt::SkipEmptyParts));
+ const QList<QStringView> opts(QStringView(connOpts).split(u';', Qt::SkipEmptyParts));
QString unixSocket;
- QString sslCert;
- QString sslCA;
- QString sslKey;
- QString sslCAPath;
- QString sslCipher;
- my_bool reconnect=false;
- uint connectTimeout = 0;
- uint readTimeout = 0;
- uint writeTimeout = 0;
// extract the real options from the string
- for (int i = 0; i < opts.count(); ++i) {
- QString tmp(opts.at(i).simplified());
+ for (const auto &option : opts) {
+ const QStringView sv = QStringView(option).trimmed();
qsizetype idx;
- if ((idx = tmp.indexOf(u'=')) != -1) {
- QString val = tmp.mid(idx + 1).simplified();
- QString opt = tmp.left(idx).simplified();
- if (opt == "UNIX_SOCKET"_L1)
- unixSocket = val;
- else if (opt == "MYSQL_OPT_RECONNECT"_L1) {
- if (val == "TRUE"_L1 || val == "1"_L1 || val.isEmpty())
- reconnect = true;
- } else if (opt == "MYSQL_OPT_CONNECT_TIMEOUT"_L1)
- connectTimeout = val.toInt();
- else if (opt == "MYSQL_OPT_READ_TIMEOUT"_L1)
- readTimeout = val.toInt();
- else if (opt == "MYSQL_OPT_WRITE_TIMEOUT"_L1)
- writeTimeout = val.toInt();
- else if (opt == "SSL_KEY"_L1)
- sslKey = val;
- else if (opt == "SSL_CERT"_L1)
- sslCert = val;
- else if (opt == "SSL_CA"_L1)
- sslCA = val;
- else if (opt == "SSL_CAPATH"_L1)
- sslCAPath = val;
- else if (opt == "SSL_CIPHER"_L1)
- sslCipher = val;
+ if ((idx = sv.indexOf(u'=')) != -1) {
+ const QStringView key = sv.left(idx).trimmed();
+ const QStringView val = sv.mid(idx + 1).trimmed();
+ if (trySetOption(key, val))
+ continue;
+ else if (key == "UNIX_SOCKET"_L1)
+ unixSocket = val.toString();
else if (val == "TRUE"_L1 || val == "1"_L1)
- setOptionFlag(optionFlags, tmp.left(idx).simplified());
+ setOptionFlag(optionFlags, key);
else
- qWarning("QMYSQLDriver::open: Illegal connect option value '%s'",
- tmp.toLocal8Bit().constData());
+ qCWarning(lcMysql, "QMYSQLDriver::open: Illegal connect option value '%ls'",
+ qUtf16Printable(QString(sv)));
} else {
- setOptionFlag(optionFlags, tmp);
+ setOptionFlag(optionFlags, sv);
}
}
- if (!(d->mysql = mysql_init(nullptr))) {
- setLastError(qMakeError(tr("Unable to allocate a MYSQL object"),
- QSqlError::ConnectionError, d));
- setOpenError(true);
- return false;
- }
-
// try utf8 with non BMP first, utf8 (BMP only) if that fails
static const char wanted_charsets[][8] = { "utf8mb4", "utf8" };
#ifdef MARIADB_VERSION_ID
@@ -1240,23 +1376,6 @@ bool QMYSQLDriver::open(const QString& db,
} *cs = nullptr;
#endif
- if (!sslKey.isNull() || !sslCert.isNull() || !sslCA.isNull() ||
- !sslCAPath.isNull() || !sslCipher.isNull()) {
- mysql_ssl_set(d->mysql,
- sslKey.isNull() ? nullptr : sslKey.toUtf8().constData(),
- sslCert.isNull() ? nullptr : sslCert.toUtf8().constData(),
- sslCA.isNull() ? nullptr : sslCA.toUtf8().constData(),
- sslCAPath.isNull() ? nullptr : sslCAPath.toUtf8().constData(),
- sslCipher.isNull() ? nullptr : sslCipher.toUtf8().constData());
- }
-
- if (connectTimeout != 0)
- mysql_options(d->mysql, MYSQL_OPT_CONNECT_TIMEOUT, &connectTimeout);
- if (readTimeout != 0)
- mysql_options(d->mysql, MYSQL_OPT_READ_TIMEOUT, &readTimeout);
- if (writeTimeout != 0)
- mysql_options(d->mysql, MYSQL_OPT_WRITE_TIMEOUT, &writeTimeout);
-
MYSQL *mysql = mysql_real_connect(d->mysql,
host.isNull() ? nullptr : host.toUtf8().constData(),
user.isNull() ? nullptr : user.toUtf8().constData(),
@@ -1285,9 +1404,10 @@ bool QMYSQLDriver::open(const QString& db,
}
}
if (!ok)
- qWarning("MySQL: Unable to set the client character set to utf8 (\"%s\"). Using '%s' instead.",
- mysql_error(d->mysql),
- mysql_character_set_name(d->mysql));
+ qCWarning(lcMysql, "MySQL: Unable to set the client character set to utf8 (\"%s\"). "
+ "Using '%s' instead.",
+ mysql_error(d->mysql),
+ mysql_character_set_name(d->mysql));
}
if (!db.isEmpty() && mysql_select_db(d->mysql, db.toUtf8().constData())) {
@@ -1297,10 +1417,11 @@ bool QMYSQLDriver::open(const QString& db,
return false;
}
- if (reconnect)
- mysql_options(d->mysql, MYSQL_OPT_RECONNECT, &reconnect);
-
d->preparedQuerysEnabled = checkPreparedQueries(d->mysql);
+ d->dbName = db;
+
+ if (d->preparedQuerysEnabled)
+ setUtcTimeZone(d->mysql);
#if QT_CONFIG(thread)
mysql_thread_init();
@@ -1320,6 +1441,7 @@ void QMYSQLDriver::close()
#endif
mysql_close(d->mysql);
d->mysql = nullptr;
+ d->dbName.clear();
setOpen(false);
setOpenError(false);
}
@@ -1336,14 +1458,14 @@ QStringList QMYSQLDriver::tables(QSql::TableType type) const
QStringList tl;
QSqlQuery q(createResult());
if (type & QSql::Tables) {
- QString sql = "select table_name from information_schema.tables where table_schema = '"_L1 + QLatin1StringView(d->mysql->db) + "' and table_type = 'BASE TABLE'"_L1;
+ QString sql = "select table_name from information_schema.tables where table_schema = '"_L1 + d->dbName + "' and table_type = 'BASE TABLE'"_L1;
q.exec(sql);
while (q.next())
tl.append(q.value(0).toString());
}
if (type & QSql::Views) {
- QString sql = "select table_name from information_schema.tables where table_schema = '"_L1 + QLatin1StringView(d->mysql->db) + "' and table_type = 'VIEW'"_L1;
+ QString sql = "select table_name from information_schema.tables where table_schema = '"_L1 + d->dbName + "' and table_type = 'VIEW'"_L1;
q.exec(sql);
while (q.next())
@@ -1352,7 +1474,7 @@ QStringList QMYSQLDriver::tables(QSql::TableType type) const
return tl;
}
-QSqlIndex QMYSQLDriver::primaryIndex(const QString& tablename) const
+QSqlIndex QMYSQLDriver::primaryIndex(const QString &tablename) const
{
QSqlIndex idx;
if (!isOpen())
@@ -1373,26 +1495,41 @@ QSqlIndex QMYSQLDriver::primaryIndex(const QString& tablename) const
return idx;
}
-QSqlRecord QMYSQLDriver::record(const QString& tablename) const
+QSqlRecord QMYSQLDriver::record(const QString &tablename) const
{
Q_D(const QMYSQLDriver);
- QString table=tablename;
- if (isIdentifierEscaped(table, QSqlDriver::TableName))
- table = stripDelimiters(table, QSqlDriver::TableName);
-
- QSqlRecord info;
if (!isOpen())
- return info;
- MYSQL_RES* r = mysql_list_fields(d->mysql, table.toUtf8().constData(), 0);
- if (!r) {
- return info;
+ return {};
+ QSqlQuery i(createResult());
+ QString stmt("SELECT * FROM %1 LIMIT 0"_L1);
+ i.exec(stmt.arg(escapeIdentifier(tablename, QSqlDriver::TableName)));
+ auto r = i.record();
+ if (r.isEmpty())
+ return r;
+ // no binding of WHERE possible with MySQL
+ // escaping on WHERE clause does not work, so use mysql_real_escape_string()
+ stmt = "SELECT column_name, column_default FROM information_schema.columns WHERE table_name = '%1'"_L1;
+ const auto baTableName = tablename.toUtf8();
+ QVarLengthArray<char> tableNameQuoted(baTableName.size() * 2 + 1);
+#if defined(MARIADB_VERSION_ID)
+ const auto len = mysql_real_escape_string(d->mysql, tableNameQuoted.data(),
+ baTableName.data(), baTableName.size());
+#else
+ const auto len = mysql_real_escape_string_quote(d->mysql, tableNameQuoted.data(),
+ baTableName.data(), baTableName.size(), '\'');
+#endif
+ if (i.exec(stmt.arg(QString::fromUtf8(tableNameQuoted.data(), len)))) {
+ while (i.next()) {
+ const auto colName = i.value(0).toString();
+ const auto recordIdx = r.indexOf(colName);
+ if (recordIdx >= 0) {
+ auto field = r.field(recordIdx);
+ field.setDefaultValue(i.value(1));
+ r.replace(recordIdx, field);
+ }
+ }
}
- MYSQL_FIELD* field;
-
- while ((field = mysql_fetch_field(r)))
- info.append(qToField(field));
- mysql_free_result(r);
- return info;
+ return r;
}
QVariant QMYSQLDriver::handle() const
@@ -1405,7 +1542,7 @@ bool QMYSQLDriver::beginTransaction()
{
Q_D(QMYSQLDriver);
if (!isOpen()) {
- qWarning("QMYSQLDriver::beginTransaction: Database not open");
+ qCWarning(lcMysql, "QMYSQLDriver::beginTransaction: Database not open");
return false;
}
if (mysql_query(d->mysql, "BEGIN WORK")) {
@@ -1420,7 +1557,7 @@ bool QMYSQLDriver::commitTransaction()
{
Q_D(QMYSQLDriver);
if (!isOpen()) {
- qWarning("QMYSQLDriver::commitTransaction: Database not open");
+ qCWarning(lcMysql, "QMYSQLDriver::commitTransaction: Database not open");
return false;
}
if (mysql_query(d->mysql, "COMMIT")) {
@@ -1435,7 +1572,7 @@ bool QMYSQLDriver::rollbackTransaction()
{
Q_D(QMYSQLDriver);
if (!isOpen()) {
- qWarning("QMYSQLDriver::rollbackTransaction: Database not open");
+ qCWarning(lcMysql, "QMYSQLDriver::rollbackTransaction: Database not open");
return false;
}
if (mysql_query(d->mysql, "ROLLBACK")) {
@@ -1472,7 +1609,7 @@ QString QMYSQLDriver::formatValue(const QSqlField &field, bool trimStrings) cons
r = u'\'' + QString::fromUtf8(buffer.data(), escapedSize) + u'\'';
break;
} else {
- qWarning("QMYSQLDriver::formatValue: Database not open");
+ qCWarning(lcMysql, "QMYSQLDriver::formatValue: Database not open");
}
Q_FALLTHROUGH();
case QMetaType::QDateTime:
@@ -1481,7 +1618,6 @@ QString QMYSQLDriver::formatValue(const QSqlField &field, bool trimStrings) cons
// "+00:00" starting in version 8.0.19. However, if we got here,
// it's because the MySQL server is too old for prepared queries
// in the first place, so it won't understand timezones either.
- // Besides, MYSQL_TIME does not support timezones, so match it.
r = u'\'' +
dt.date().toString(Qt::ISODate) +
u'T' +
@@ -1499,9 +1635,9 @@ QString QMYSQLDriver::formatValue(const QSqlField &field, bool trimStrings) cons
QString QMYSQLDriver::escapeIdentifier(const QString &identifier, IdentifierType) const
{
QString res = identifier;
- if (!identifier.isEmpty() && !identifier.startsWith(u'`') && !identifier.endsWith(u'`') ) {
- res.prepend(u'`').append(u'`');
+ if (!identifier.isEmpty() && !identifier.startsWith(u'`') && !identifier.endsWith(u'`')) {
res.replace(u'.', "`.`"_L1);
+ res = u'`' + res + u'`';
}
return res;
}
diff --git a/src/plugins/sqldrivers/oci/CMakeLists.txt b/src/plugins/sqldrivers/oci/CMakeLists.txt
index 9717fb4eaa..66c4219905 100644
--- a/src/plugins/sqldrivers/oci/CMakeLists.txt
+++ b/src/plugins/sqldrivers/oci/CMakeLists.txt
@@ -1,7 +1,5 @@
# Copyright (C) 2022 The Qt Company Ltd.
-# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-
-# Generated from oci.pro.
+# SPDX-License-Identifier: BSD-3-Clause
#####################################################################
## QOCIDriverPlugin Plugin:
@@ -23,9 +21,6 @@ qt_internal_add_plugin(QOCIDriverPlugin
Qt::SqlPrivate
)
-#### Keys ignored in scope 1:.:.:oci.pro:<TRUE>:
-# OTHER_FILES = "oci.json"
-
## Scopes:
#####################################################################
diff --git a/src/plugins/sqldrivers/oci/main.cpp b/src/plugins/sqldrivers/oci/main.cpp
index 933de8f8bf..6cc0062671 100644
--- a/src/plugins/sqldrivers/oci/main.cpp
+++ b/src/plugins/sqldrivers/oci/main.cpp
@@ -17,7 +17,7 @@ class QOCIDriverPlugin : public QSqlDriverPlugin
public:
QOCIDriverPlugin();
- QSqlDriver* create(const QString &);
+ QSqlDriver* create(const QString &) override;
};
QOCIDriverPlugin::QOCIDriverPlugin()
diff --git a/src/plugins/sqldrivers/oci/qsql_oci.cpp b/src/plugins/sqldrivers/oci/qsql_oci.cpp
index 2b844218bf..68e303490d 100644
--- a/src/plugins/sqldrivers/oci/qsql_oci.cpp
+++ b/src/plugins/sqldrivers/oci/qsql_oci.cpp
@@ -7,6 +7,7 @@
#include <qdatetime.h>
#include <qdebug.h>
#include <qlist.h>
+#include <qloggingcategory.h>
#include <qmetatype.h>
#if QT_CONFIG(regularexpression)
#include <qregularexpression.h>
@@ -19,7 +20,9 @@
#include <QtSql/private/qsqlcachedresult_p.h>
#include <QtSql/private/qsqldriver_p.h>
#include <qstringlist.h>
+#if QT_CONFIG(timezone)
#include <qtimezone.h>
+#endif
#include <qvariant.h>
#include <qvarlengtharray.h>
@@ -28,14 +31,7 @@
#define _int64 __int64
#endif
-
#include <oci.h>
-#ifdef max
-#undef max
-#endif
-#ifdef min
-#undef min
-#endif
#include <stdlib.h>
@@ -56,6 +52,8 @@ Q_DECLARE_METATYPE(OCIStmt*)
QT_BEGIN_NAMESPACE
+static Q_LOGGING_CATEGORY(lcOci, "qt.sql.oci")
+
using namespace Qt::StringLiterals;
#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN
@@ -138,8 +136,8 @@ QOCIDateTime::QOCIDateTime(OCIEnv *env, OCIError *err, const QDateTime &dt)
if (dt.isValid()) {
const QDate date = dt.date();
const QTime time = dt.time();
- // Zone in +hh:mm format (stripping UTC prefix from OffsetName)
- QString timeZone = dt.timeZone().displayName(dt, QTimeZone::OffsetName).mid(3);
+ // Zone in +hh:mm format
+ const QString timeZone = dt.toString("ttt"_L1);
const OraText *tz = reinterpret_cast<const OraText *>(timeZone.utf16());
OCIDateTimeConstruct(env, err, dateTime, date.year(), date.month(), date.day(), time.hour(),
time.minute(), time.second(), time.msec() * 1000000,
@@ -168,7 +166,7 @@ QDateTime QOCIDateTime::fromOCIDateTime(OCIEnv *env, OCIError *err, OCIDateTime
secondsOffset = -secondsOffset;
// OCIDateTimeGetTime gives "fractions of second" as nanoseconds
return QDateTime(QDate(year, month, day), QTime(hour, minute, second, nsec / 1000000),
- Qt::OffsetFromUTC, secondsOffset);
+ QTimeZone::fromSecondsAheadOfUtc(secondsOffset));
}
struct TempStorage {
@@ -194,6 +192,7 @@ public:
OCISession *authp = nullptr;
OCITrans *trans = nullptr;
OCIError *err = nullptr;
+ ub4 authMode = OCI_DEFAULT;
bool transaction = false;
int serverVersion = -1;
int prefetchRows = -1;
@@ -277,7 +276,7 @@ public:
0);
#ifdef QOCI_DEBUG
if (r != 0)
- qWarning("QOCIResultPrivate::setCharset: Couldn't set OCI_ATTR_CHARSET_FORM.");
+ qCWarning(lcOci, "QOCIResultPrivate::setCharset: Couldn't set OCI_ATTR_CHARSET_FORM.");
#endif
#endif
@@ -424,6 +423,7 @@ int QOCIResultPrivate::bindValue(OCIStmt *sql, OCIBind **hbnd, OCIError *err, in
break;
}
} // fall through for OUT values
+ Q_FALLTHROUGH();
default: {
if (val.typeId() >= QMetaType::User) {
if (val.canConvert<QOCIRowIdPointer>() && !isOutValue(pos)) {
@@ -436,7 +436,7 @@ int QOCIResultPrivate::bindValue(OCIStmt *sql, OCIBind **hbnd, OCIError *err, in
-1,
SQLT_RDD, indPtr, 0, 0, 0, 0, OCI_DEFAULT);
} else {
- qWarning("Unknown bind variable");
+ qCWarning(lcOci, "Unknown bind variable");
r = OCI_ERROR;
}
} else {
@@ -552,7 +552,7 @@ void QOCIDriverPrivate::allocErrorHandle()
OCI_HTYPE_ERROR,
0, nullptr);
if (r != OCI_SUCCESS)
- qWarning("QOCIDriver: unable to allocate error handle");
+ qCWarning(lcOci, "QOCIDriver: unable to allocate error handle");
}
struct OraFieldInfo
@@ -588,12 +588,7 @@ QString qOraWarn(OCIError *err, int *errorCode)
void qOraWarning(const char* msg, OCIError *err)
{
-#ifdef QOCI_DEBUG
- qWarning("%s %s", msg, qPrintable(qOraWarn(err)));
-#else
- Q_UNUSED(msg);
- Q_UNUSED(err);
-#endif
+ qCWarning(lcOci, "%s %ls", msg, qUtf16Printable(qOraWarn(err)));
}
static int qOraErrorNumber(OCIError *err)
@@ -656,7 +651,7 @@ QMetaType qDecodeOCIType(const QString& ocitype, QSql::NumericalPrecisionPolicy
else if (ocitype == "UNDEFINED"_L1)
type = QMetaType::UnknownType;
if (type == QMetaType::UnknownType)
- qWarning("qDecodeOCIType: unknown type: %s", ocitype.toLocal8Bit().constData());
+ qCWarning(lcOci, "qDecodeOCIType: unknown type: %ls", qUtf16Printable(ocitype));
return QMetaType(type);
}
@@ -724,7 +719,7 @@ QMetaType qDecodeOCIType(int ocitype, QSql::NumericalPrecisionPolicy precisionPo
type = QMetaType::QDateTime;
break;
default:
- qWarning("qDecodeOCIType: unknown OCI datatype: %d", ocitype);
+ qCWarning(lcOci, "qDecodeOCIType: unknown OCI datatype: %d", ocitype);
break;
}
return QMetaType(type);
@@ -741,7 +736,6 @@ static QSqlField qFromOraInf(const OraFieldInfo &ofi)
f.setLength(ofi.oraPrecision == 0 ? 38 : int(ofi.oraPrecision));
f.setPrecision(ofi.oraScale);
- f.setSqlType(int(ofi.oraType));
return f;
}
@@ -840,7 +834,7 @@ QOCICols::OraFieldInf::~OraFieldInf()
if (lob) {
int r = OCIDescriptorFree(lob, OCI_DTYPE_LOB);
if (r != 0)
- qWarning("QOCICols: Cannot free LOB descriptor");
+ qCWarning(lcOci, "QOCICols: Cannot free LOB descriptor");
}
if (dataPtr) {
switch (typ.id()) {
@@ -849,7 +843,7 @@ QOCICols::OraFieldInf::~OraFieldInf()
case QMetaType::QDateTime: {
int r = OCIDescriptorFree(dataPtr, OCI_DTYPE_TIMESTAMP_TZ);
if (r != OCI_SUCCESS)
- qWarning("QOCICols: Cannot free OCIDateTime descriptor");
+ qCWarning(lcOci, "QOCICols: Cannot free OCIDateTime descriptor");
break;
}
default:
@@ -904,7 +898,7 @@ QOCICols::QOCICols(int size, QOCIResultPrivate* dp)
case QMetaType::QDateTime:
r = OCIDescriptorAlloc(d->env, (void **)&fieldInf[idx].dataPtr, OCI_DTYPE_TIMESTAMP_TZ, 0, 0);
if (r != OCI_SUCCESS) {
- qWarning("QOCICols: Unable to allocate the OCIDateTime descriptor");
+ qCWarning(lcOci, "QOCICols: Unable to allocate the OCIDateTime descriptor");
break;
}
r = OCIDefineByPos(d->sql,
@@ -1075,7 +1069,7 @@ OCILobLocator **QOCICols::createLobLocator(int position, OCIEnv* env)
0,
0);
if (r != 0) {
- qWarning("QOCICols: Cannot create LOB locator");
+ qCWarning(lcOci, "QOCICols: Cannot create LOB locator");
lob = 0;
}
return &lob;
@@ -1313,7 +1307,7 @@ bool QOCICols::execBatch(QOCIResultPrivate *d, QVariantList &boundValues, bool a
return false;
#ifdef QOCI_DEBUG
- qDebug() << "columnCount:" << columnCount << boundValues;
+ qCDebug(lcOci) << "columnCount:" << columnCount << boundValues;
#endif
int i;
@@ -1432,7 +1426,7 @@ bool QOCICols::execBatch(QOCIResultPrivate *d, QVariantList &boundValues, bool a
// we may now populate column with data
for (uint row = 0; row < col.recordCount; ++row) {
- const QVariant &val = boundValues.at(i).toList().at(row);
+ const QVariant val = boundValues.at(i).toList().at(row);
if (QSqlResultPrivate::isVariantNull(val) && !d->isOutValue(i)) {
columns[i].indicators[row] = -1;
@@ -1510,13 +1504,13 @@ bool QOCICols::execBatch(QOCIResultPrivate *d, QVariantList &boundValues, bool a
QOCIBatchColumn &bindColumn = columns[i];
#ifdef QOCI_DEBUG
- qDebug("OCIBindByPos(%p, %p, %p, %d, %p, %d, %d, %p, %p, 0, %d, %p, OCI_DEFAULT)",
+ qCDebug(lcOci, "OCIBindByPos(%p, %p, %p, %d, %p, %d, %d, %p, %p, 0, %d, %p, OCI_DEFAULT)",
d->sql, &bindColumn.bindh, d->err, i + 1, bindColumn.data,
bindColumn.maxLen, bindColumn.bindAs, bindColumn.indicators, bindColumn.lengths,
arrayBind ? bindColumn.maxarr_len : 0, arrayBind ? &bindColumn.curelep : 0);
for (int ii = 0; ii < (int)bindColumn.recordCount; ++ii) {
- qDebug(" record %d: indicator %d, length %d", ii, bindColumn.indicators[ii],
+ qCDebug(lcOci, " record %d: indicator %d, length %d", ii, bindColumn.indicators[ii],
bindColumn.lengths[ii]);
}
#endif
@@ -1536,7 +1530,7 @@ bool QOCICols::execBatch(QOCIResultPrivate *d, QVariantList &boundValues, bool a
OCI_DEFAULT);
#ifdef QOCI_DEBUG
- qDebug("After OCIBindByPos: r = %d, bindh = %p", r, bindColumn.bindh);
+ qCDebug(lcOci, "After OCIBindByPos: r = %d, bindh = %p", r, bindColumn.bindh);
#endif
if (r != OCI_SUCCESS && r != OCI_SUCCESS_WITH_INFO) {
@@ -1796,7 +1790,7 @@ void QOCICols::getValues(QVariantList &v, int index)
v[index + i] = QVariant(QMetaType(QMetaType::QByteArray));
break;
default:
- qWarning("QOCICols::value: unknown data type");
+ qCWarning(lcOci, "QOCICols::value: unknown data type");
break;
}
}
@@ -1817,7 +1811,7 @@ QOCIResultPrivate::QOCIResultPrivate(QOCIResult *q, const QOCIDriver *drv)
OCI_HTYPE_ERROR,
0, nullptr);
if (r != OCI_SUCCESS)
- qWarning("QOCIResult: unable to alloc error handle");
+ qCWarning(lcOci, "QOCIResult: unable to alloc error handle");
}
QOCIResultPrivate::~QOCIResultPrivate()
@@ -1825,10 +1819,10 @@ QOCIResultPrivate::~QOCIResultPrivate()
delete cols;
if (sql && OCIHandleFree(sql, OCI_HTYPE_STMT) != OCI_SUCCESS)
- qWarning("~QOCIResult: unable to free statement handle");
+ qCWarning(lcOci, "~QOCIResult: unable to free statement handle");
if (OCIHandleFree(err, OCI_HTYPE_ERROR) != OCI_SUCCESS)
- qWarning("~QOCIResult: unable to free error report handle");
+ qCWarning(lcOci, "~QOCIResult: unable to free error report handle");
}
@@ -1885,7 +1879,7 @@ bool QOCIResult::gotoNext(QSqlCachedResult::ValueCache &values, int index)
break;
case OCI_ERROR:
if (qOraErrorNumber(d->err) == 1406) {
- qWarning("QOCI Warning: data truncated for %s", lastQuery().toLocal8Bit().constData());
+ qCWarning(lcOci, "QOCI Warning: data truncated for %ls", qUtf16Printable(lastQuery()));
r = OCI_SUCCESS; /* ignore it */
break;
}
@@ -1999,7 +1993,7 @@ bool QOCIResult::exec()
setLastError(qMakeError(QCoreApplication::translate("QOCIResult",
"Unable to get statement type"), QSqlError::StatementError, d->err));
#ifdef QOCI_DEBUG
- qDebug() << "lastQuery()" << lastQuery();
+ qCDebug(lcOci) << "lastQuery()" << lastQuery();
#endif
return false;
}
@@ -2014,7 +2008,7 @@ bool QOCIResult::exec()
setLastError(qMakeError(QCoreApplication::translate("QOCIResult", "Unable to bind value"),
QSqlError::StatementError, d->err));
#ifdef QOCI_DEBUG
- qDebug() << "lastQuery()" << lastQuery();
+ qCDebug(lcOci) << "lastQuery()" << lastQuery();
#endif
return false;
}
@@ -2033,7 +2027,7 @@ bool QOCIResult::exec()
setLastError(qMakeError(QCoreApplication::translate("QOCIResult",
"Unable to execute statement"), QSqlError::StatementError, d->err));
#ifdef QOCI_DEBUG
- qDebug() << "lastQuery()" << lastQuery();
+ qCDebug(lcOci) << "lastQuery()" << lastQuery();
#endif
return false;
}
@@ -2125,7 +2119,7 @@ QOCIDriver::QOCIDriver(QObject* parent)
0,
NULL);
if (r != 0) {
- qWarning("QOCIDriver: unable to create environment");
+ qCWarning(lcOci, "QOCIDriver: unable to create environment");
setLastError(qMakeError(tr("Unable to initialize", "QOCIDriver"),
QSqlError::ConnectionError, d->err));
return;
@@ -2156,10 +2150,10 @@ QOCIDriver::~QOCIDriver()
close();
int r = OCIHandleFree(d->err, OCI_HTYPE_ERROR);
if (r != OCI_SUCCESS)
- qWarning("Unable to free Error handle: %d", r);
+ qCWarning(lcOci, "Unable to free Error handle: %d", r);
r = OCIHandleFree(d->env, OCI_HTYPE_ENV);
if (r != OCI_SUCCESS)
- qWarning("Unable to free Environment handle: %d", r);
+ qCWarning(lcOci, "Unable to free Environment handle: %d", r);
}
bool QOCIDriver::hasFeature(DriverFeature f) const
@@ -2190,17 +2184,16 @@ bool QOCIDriver::hasFeature(DriverFeature f) const
static void qParseOpts(const QString &options, QOCIDriverPrivate *d)
{
- const QStringList opts(options.split(u';', Qt::SkipEmptyParts));
- for (int i = 0; i < opts.count(); ++i) {
- const QString tmp(opts.at(i));
+ const QVector<QStringView> opts(QStringView(options).split(u';', Qt::SkipEmptyParts));
+ for (const auto tmp : opts) {
qsizetype idx;
if ((idx = tmp.indexOf(u'=')) == -1) {
- qWarning("QOCIDriver::parseArgs: Invalid parameter: '%s'",
- tmp.toLocal8Bit().constData());
+ qCWarning(lcOci, "QOCIDriver::parseArgs: Invalid parameter: '%ls'",
+ qUtf16Printable(tmp.toString()));
continue;
}
- const QString opt = tmp.left(idx);
- const QString val = tmp.mid(idx + 1).simplified();
+ const QStringView opt = tmp.left(idx);
+ const QStringView val = tmp.mid(idx + 1).trimmed();
bool ok;
if (opt == "OCI_ATTR_PREFETCH_ROWS"_L1) {
d->prefetchRows = val.toInt(&ok);
@@ -2210,9 +2203,18 @@ static void qParseOpts(const QString &options, QOCIDriverPrivate *d)
d->prefetchMem = val.toInt(&ok);
if (!ok)
d->prefetchMem = -1;
+ } else if (opt == "OCI_AUTH_MODE"_L1) {
+ if (val == "OCI_SYSDBA"_L1) {
+ d->authMode = OCI_SYSDBA;
+ } else if (val == "OCI_SYSOPER"_L1) {
+ d->authMode = OCI_SYSOPER;
+ } else if (val != "OCI_DEFAULT"_L1) {
+ qCWarning(lcOci, "QOCIDriver::parseArgs: Unsupported value for OCI_AUTH_MODE: '%ls'",
+ qUtf16Printable(val.toString()));
+ }
} else {
- qWarning ("QOCIDriver::parseArgs: Invalid parameter: '%s'",
- opt.toLocal8Bit().constData());
+ qCWarning(lcOci, "QOCIDriver::parseArgs: Invalid parameter: '%ls'",
+ qUtf16Printable(opt.toString()));
}
}
}
@@ -2276,9 +2278,9 @@ bool QOCIDriver::open(const QString & db,
if (r == OCI_SUCCESS) {
if (user.isEmpty() && password.isEmpty())
- r = OCISessionBegin(d->svc, d->err, d->authp, OCI_CRED_EXT, OCI_DEFAULT);
+ r = OCISessionBegin(d->svc, d->err, d->authp, OCI_CRED_EXT, d->authMode);
else
- r = OCISessionBegin(d->svc, d->err, d->authp, OCI_CRED_RDBMS, OCI_DEFAULT);
+ r = OCISessionBegin(d->svc, d->err, d->authp, OCI_CRED_RDBMS, d->authMode);
}
if (r == OCI_SUCCESS || r == OCI_SUCCESS_WITH_INFO)
r = OCIAttrSet(d->svc, OCI_HTYPE_SVCCTX, d->authp, 0, OCI_ATTR_SESSION, d->err);
@@ -2309,7 +2311,7 @@ bool QOCIDriver::open(const QString & db,
sizeof(vertxt),
OCI_HTYPE_SVCCTX);
if (r != 0) {
- qWarning("QOCIDriver::open: could not get Oracle server version.");
+ qCWarning(lcOci, "QOCIDriver::open: could not get Oracle server version.");
} else {
QString versionStr;
versionStr = QString(reinterpret_cast<const QChar *>(vertxt));
@@ -2358,7 +2360,7 @@ bool QOCIDriver::beginTransaction()
{
Q_D(QOCIDriver);
if (!isOpen()) {
- qWarning("QOCIDriver::beginTransaction: Database not open");
+ qCWarning(lcOci, "QOCIDriver::beginTransaction: Database not open");
return false;
}
int r = OCITransStart(d->svc,
@@ -2379,7 +2381,7 @@ bool QOCIDriver::commitTransaction()
{
Q_D(QOCIDriver);
if (!isOpen()) {
- qWarning("QOCIDriver::commitTransaction: Database not open");
+ qCWarning(lcOci, "QOCIDriver::commitTransaction: Database not open");
return false;
}
int r = OCITransCommit(d->svc,
@@ -2399,7 +2401,7 @@ bool QOCIDriver::rollbackTransaction()
{
Q_D(QOCIDriver);
if (!isOpen()) {
- qWarning("QOCIDriver::rollbackTransaction: Database not open");
+ qCWarning(lcOci, "QOCIDriver::rollbackTransaction: Database not open");
return false;
}
int r = OCITransRollback(d->svc,
@@ -2432,7 +2434,7 @@ static QString make_where_clause(const QString &user, Expression e)
"WMSYS",
};
static const char joinC[][4] = { "or" , "and" };
- static constexpr QLatin1Char bang[] = { u' ', u'!' };
+ static constexpr char16_t bang[] = { u' ', u'!' };
const QLatin1StringView join(joinC[e]);
@@ -2551,8 +2553,7 @@ QSqlRecord QOCIDriver::record(const QString& tablename) const
// eg. a sub-query on the sys.synonyms table
QString stmt("select column_name, data_type, data_length, "
"data_precision, data_scale, nullable, data_default%1"
- "from all_tab_columns a "
- "where a.table_name=%2"_L1);
+ "from all_tab_columns a "_L1);
if (d->serverVersion >= 9)
stmt = stmt.arg(", char_length "_L1);
else
@@ -2566,7 +2567,7 @@ QSqlRecord QOCIDriver::record(const QString& tablename) const
else
table = table.toUpper();
- tmpStmt = stmt.arg(u'\'' + table + u'\'');
+ tmpStmt = stmt + "where a.table_name='"_L1 + table + u'\'';
if (owner.isEmpty()) {
owner = d->user;
}
@@ -2680,7 +2681,7 @@ QSqlIndex QOCIDriver::primaryIndex(const QString& tablename) const
QString QOCIDriver::formatValue(const QSqlField &field, bool trimStrings) const
{
- switch (field.typeID()) {
+ switch (field.metaType().id()) {
case QMetaType::QDateTime: {
QDateTime datetime = field.value().toDateTime();
QString datestring;
@@ -2742,10 +2743,19 @@ QString QOCIDriver::escapeIdentifier(const QString &identifier, IdentifierType t
QString res = identifier;
if (!identifier.isEmpty() && !isIdentifierEscaped(identifier, type)) {
res.replace(u'"', "\"\""_L1);
- res.prepend(u'"').append(u'"');
res.replace(u'.', "\".\""_L1);
+ res = u'"' + res + u'"';
}
return res;
}
+int QOCIDriver::maximumIdentifierLength(IdentifierType type) const
+{
+ Q_D(const QOCIDriver);
+ Q_UNUSED(type);
+ return d->serverVersion > 12 ? 128 : 30;
+}
+
QT_END_NAMESPACE
+
+#include "moc_qsql_oci_p.cpp"
diff --git a/src/plugins/sqldrivers/oci/qsql_oci_p.h b/src/plugins/sqldrivers/oci/qsql_oci_p.h
index fd173e21e8..91fef9de7b 100644
--- a/src/plugins/sqldrivers/oci/qsql_oci_p.h
+++ b/src/plugins/sqldrivers/oci/qsql_oci_p.h
@@ -42,7 +42,7 @@ public:
explicit QOCIDriver(QObject *parent = nullptr);
QOCIDriver(OCIEnv *env, OCISvcCtx *ctx, QObject *parent = nullptr);
~QOCIDriver();
- bool hasFeature(DriverFeature f) const;
+ bool hasFeature(DriverFeature f) const override;
bool open(const QString &db,
const QString &user,
const QString &password,
@@ -58,6 +58,7 @@ public:
bool trimStrings) const override;
QVariant handle() const override;
QString escapeIdentifier(const QString &identifier, IdentifierType) const override;
+ int maximumIdentifierLength(IdentifierType type) const override;
protected:
bool beginTransaction() override;
diff --git a/src/plugins/sqldrivers/odbc/CMakeLists.txt b/src/plugins/sqldrivers/odbc/CMakeLists.txt
index c4ebd5fc4b..7812aced2c 100644
--- a/src/plugins/sqldrivers/odbc/CMakeLists.txt
+++ b/src/plugins/sqldrivers/odbc/CMakeLists.txt
@@ -1,13 +1,11 @@
# Copyright (C) 2022 The Qt Company Ltd.
-# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-
-# Generated from odbc.pro.
+# SPDX-License-Identifier: BSD-3-Clause
#####################################################################
## QODBCDriverPlugin Plugin:
#####################################################################
-qt_find_package(ODBC) # special case
+qt_find_package(ODBC)
qt_internal_add_plugin(QODBCDriverPlugin
OUTPUT_NAME qsqlodbc
@@ -18,6 +16,7 @@ qt_internal_add_plugin(QODBCDriverPlugin
DEFINES
QT_NO_CAST_FROM_ASCII
QT_NO_CAST_TO_ASCII
+ QT_NO_CONTEXTLESS_CONNECT
LIBRARIES
ODBC::ODBC
Qt::Core
@@ -25,9 +24,6 @@ qt_internal_add_plugin(QODBCDriverPlugin
Qt::SqlPrivate
)
-#### Keys ignored in scope 1:.:.:odbc.pro:<TRUE>:
-# OTHER_FILES = "odbc.json"
-
## Scopes:
#####################################################################
diff --git a/src/plugins/sqldrivers/odbc/qsql_odbc.cpp b/src/plugins/sqldrivers/odbc/qsql_odbc.cpp
index bfcff3ad47..976911d458 100644
--- a/src/plugins/sqldrivers/odbc/qsql_odbc.cpp
+++ b/src/plugins/sqldrivers/odbc/qsql_odbc.cpp
@@ -10,10 +10,12 @@
#include <qcoreapplication.h>
#include <qdatetime.h>
#include <qlist.h>
+#include <qloggingcategory.h>
#include <qmath.h>
#include <qsqlerror.h>
#include <qsqlfield.h>
#include <qsqlindex.h>
+#include <qstringconverter.h>
#include <qstringlist.h>
#include <qvariant.h>
#include <qvarlengtharray.h>
@@ -21,61 +23,89 @@
#include <QSqlQuery>
#include <QtSql/private/qsqldriver_p.h>
#include <QtSql/private/qsqlresult_p.h>
+#include "private/qtools_p.h"
QT_BEGIN_NAMESPACE
+static Q_LOGGING_CATEGORY(lcOdbc, "qt.sql.odbc")
+
using namespace Qt::StringLiterals;
+// non-standard ODBC SQL data type from SQL Server sometimes used instead of SQL_TIME
+#ifndef SQL_SS_TIME2
+#define SQL_SS_TIME2 (-154)
+#endif
+
// undefine this to prevent initial check of the ODBC driver
#define ODBC_CHECK_DRIVER
-static const int COLNAMESIZE = 256;
-static const SQLSMALLINT TABLENAMESIZE = 128;
+static constexpr int COLNAMESIZE = 256;
+static constexpr SQLSMALLINT TABLENAMESIZE = 128;
//Map Qt parameter types to ODBC types
-static const SQLSMALLINT qParamType[4] = { SQL_PARAM_INPUT, SQL_PARAM_INPUT, SQL_PARAM_OUTPUT, SQL_PARAM_INPUT_OUTPUT };
+static constexpr SQLSMALLINT qParamType[4] = { SQL_PARAM_INPUT, SQL_PARAM_INPUT, SQL_PARAM_OUTPUT, SQL_PARAM_INPUT_OUTPUT };
-inline static QString fromSQLTCHAR(const QVarLengthArray<SQLTCHAR>& input, qsizetype size=-1)
+class SqlStmtHandle
{
- QString result;
+public:
+ SqlStmtHandle(SQLHANDLE hDbc)
+ {
+ SQLAllocHandle(SQL_HANDLE_STMT, hDbc, &stmtHandle);
+ }
+ ~SqlStmtHandle()
+ {
+ if (stmtHandle != SQL_NULL_HSTMT)
+ SQLFreeHandle(SQL_HANDLE_STMT, stmtHandle);
+ }
+ SQLHANDLE handle() const
+ {
+ return stmtHandle;
+ }
+ bool isValid() const
+ {
+ return stmtHandle != SQL_NULL_HSTMT;
+ }
+ SQLHANDLE stmtHandle = SQL_NULL_HSTMT;
+};
+template<typename C, int SIZE = sizeof(SQLTCHAR)>
+inline static QString fromSQLTCHAR(const C &input, qsizetype size = -1)
+{
// Remove any trailing \0 as some drivers misguidedly append one
- int realsize = qMin(size, input.size());
- if (realsize > 0 && input[realsize-1] == 0)
+ qsizetype realsize = qMin(size, input.size());
+ if (realsize > 0 && input[realsize - 1] == 0)
realsize--;
- switch(sizeof(SQLTCHAR)) {
- case 1:
- result=QString::fromUtf8((const char *)input.constData(), realsize);
- break;
- case 2:
- result = QString::fromUtf16(reinterpret_cast<const char16_t *>(input.constData()), realsize);
- break;
- case 4:
- result = QString::fromUcs4(reinterpret_cast<const char32_t *>(input.constData()), realsize);
- break;
- default:
- qCritical("sizeof(SQLTCHAR) is %d. Don't know how to handle this.", int(sizeof(SQLTCHAR)));
- }
- return result;
+ if constexpr (SIZE == 1)
+ return QString::fromUtf8(reinterpret_cast<const char *>(input.constData()), realsize);
+ else if constexpr (SIZE == 2)
+ return QString::fromUtf16(reinterpret_cast<const char16_t *>(input.constData()), realsize);
+ else if constexpr (SIZE == 4)
+ return QString::fromUcs4(reinterpret_cast<const char32_t *>(input.constData()), realsize);
+ else
+ static_assert(QtPrivate::value_dependent_false<SIZE>(),
+ "Don't know how to handle sizeof(SQLTCHAR) != 1/2/4");
+}
+
+template<int SIZE = sizeof(SQLTCHAR)>
+QStringConverter::Encoding encodingForSqlTChar()
+{
+ if constexpr (SIZE == 1)
+ return QStringConverter::Utf8;
+ else if constexpr (SIZE == 2)
+ return QStringConverter::Utf16;
+ else if constexpr (SIZE == 4)
+ return QStringConverter::Utf32;
+ else
+ static_assert(QtPrivate::value_dependent_false<SIZE>(),
+ "Don't know how to handle sizeof(SQLTCHAR) != 1/2/4");
}
-inline static QVarLengthArray<SQLTCHAR> toSQLTCHAR(const QString &input)
+inline static QVarLengthArray<SQLTCHAR> toSQLTCHAR(QStringView input)
{
QVarLengthArray<SQLTCHAR> result;
- result.resize(input.size());
- switch(sizeof(SQLTCHAR)) {
- case 1:
- memcpy(result.data(), input.toUtf8().data(), input.size());
- break;
- case 2:
- memcpy(result.data(), input.unicode(), input.size() * 2);
- break;
- case 4:
- memcpy(result.data(), input.toUcs4().data(), input.size() * 4);
- break;
- default:
- qCritical("sizeof(SQLTCHAR) is %d. Don't know how to handle this.", int(sizeof(SQLTCHAR)));
- }
- result.append(0); // make sure it's null terminated, doesn't matter if it already is, it does if it isn't.
+ QStringEncoder enc(encodingForSqlTChar());
+ result.resize(enc.requiredSpace(input.size()));
+ const auto end = enc.appendToBuffer(reinterpret_cast<char *>(result.data()), input);
+ result.resize((end - reinterpret_cast<char *>(result.data())) / sizeof(SQLTCHAR));
return result;
}
@@ -84,7 +114,7 @@ class QODBCDriverPrivate : public QSqlDriverPrivate
Q_DECLARE_PUBLIC(QODBCDriver)
public:
- enum DefaultCase {Lower, Mixed, Upper, Sensitive};
+ enum class DefaultCase {Lower, Mixed, Upper, Sensitive};
using QSqlDriverPrivate::QSqlDriverPrivate;
SQLHANDLE hEnv = nullptr;
@@ -105,15 +135,18 @@ public:
void checkHasMultiResults();
void checkSchemaUsage();
void checkDateTimePrecision();
+ void checkDefaultCase();
bool setConnectionOptions(const QString& connOpts);
void splitTableQualifier(const QString &qualifier, QString &catalog,
- QString &schema, QString &table);
- DefaultCase defaultCase() const;
+ QString &schema, QString &table) const;
QString adjustCase(const QString&) const;
QChar quoteChar();
+ SQLRETURN sqlFetchNext(const SqlStmtHandle &hStmt) const;
+ SQLRETURN sqlFetchNext(SQLHANDLE hStmt) const;
private:
bool isQuoteInitialized = false;
QChar quote = u'"';
+ DefaultCase m_defaultCase = DefaultCase::Mixed;
};
class QODBCResultPrivate;
@@ -193,118 +226,121 @@ void QODBCResultPrivate::updateStmtHandleState()
disconnectCount = drv_d_func() ? drv_d_func()->disconnectCount : 0;
}
-static QString qWarnODBCHandle(int handleType, SQLHANDLE handle, int *nativeCode = nullptr)
+struct DiagRecord
+{
+ QString description;
+ QString sqlState;
+ QString errorCode;
+};
+static QList<DiagRecord> qWarnODBCHandle(int handleType, SQLHANDLE handle)
{
- SQLINTEGER nativeCode_ = 0;
+ SQLINTEGER nativeCode = 0;
SQLSMALLINT msgLen = 0;
+ SQLSMALLINT i = 1;
SQLRETURN r = SQL_NO_DATA;
- SQLTCHAR state_[SQL_SQLSTATE_SIZE+1];
- QVarLengthArray<SQLTCHAR> description_(SQL_MAX_MESSAGE_LENGTH);
- QString result;
- int i = 1;
+ QVarLengthArray<SQLTCHAR, SQL_SQLSTATE_SIZE + 1> state(SQL_SQLSTATE_SIZE + 1);
+ QVarLengthArray<SQLTCHAR, SQL_MAX_MESSAGE_LENGTH + 1> description(SQL_MAX_MESSAGE_LENGTH + 1);
+ QList<DiagRecord> result;
- description_[0] = 0;
+ if (!handle)
+ return result;
do {
r = SQLGetDiagRec(handleType,
handle,
i,
- state_,
- &nativeCode_,
- 0,
- 0,
+ state.data(),
+ &nativeCode,
+ description.data(),
+ description.size(),
&msgLen);
- if ((r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) && msgLen > 0)
- description_.resize(msgLen+1);
- r = SQLGetDiagRec(handleType,
- handle,
- i,
- state_,
- &nativeCode_,
- description_.data(),
- description_.size(),
- &msgLen);
- if (r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) {
- if (nativeCode)
- *nativeCode = nativeCode_;
- const QString tmpstore = fromSQLTCHAR(description_, msgLen);
- if (result != tmpstore) {
- if (!result.isEmpty())
- result += u' ';
- result += tmpstore;
- }
+ if (msgLen >= description.size()) {
+ description.resize(msgLen + 1); // incl. \0 termination
+ continue;
+ }
+ if (SQL_SUCCEEDED(r)) {
+ result.push_back({fromSQLTCHAR(description, msgLen),
+ fromSQLTCHAR(state),
+ QString::number(nativeCode)});
} else if (r == SQL_ERROR || r == SQL_INVALID_HANDLE) {
- return result;
+ break;
}
++i;
} while (r != SQL_NO_DATA);
return result;
}
-static QString qODBCWarn(const SQLHANDLE hStmt, const SQLHANDLE envHandle = 0,
- const SQLHANDLE pDbC = 0, int *nativeCode = nullptr)
+static QList<DiagRecord> qODBCWarn(const SQLHANDLE hStmt,
+ const SQLHANDLE envHandle = nullptr,
+ const SQLHANDLE pDbC = nullptr)
{
- QString result;
- if (envHandle)
- result += qWarnODBCHandle(SQL_HANDLE_ENV, envHandle, nativeCode);
- if (pDbC) {
- const QString dMessage = qWarnODBCHandle(SQL_HANDLE_DBC, pDbC, nativeCode);
- if (!dMessage.isEmpty()) {
- if (!result.isEmpty())
- result += u' ';
- result += dMessage;
- }
- }
- if (hStmt) {
- const QString hMessage = qWarnODBCHandle(SQL_HANDLE_STMT, hStmt, nativeCode);
- if (!hMessage.isEmpty()) {
- if (!result.isEmpty())
- result += u' ';
- result += hMessage;
- }
- }
+ QList<DiagRecord> result;
+ result.append(qWarnODBCHandle(SQL_HANDLE_ENV, envHandle));
+ result.append(qWarnODBCHandle(SQL_HANDLE_DBC, pDbC));
+ result.append(qWarnODBCHandle(SQL_HANDLE_STMT, hStmt));
return result;
}
-static QString qODBCWarn(const QODBCResultPrivate* odbc, int *nativeCode = nullptr)
+static QList<DiagRecord> qODBCWarn(const QODBCResultPrivate *odbc)
{
- return qODBCWarn(odbc->hStmt, odbc->dpEnv(), odbc->dpDbc(), nativeCode);
+ return qODBCWarn(odbc->hStmt, odbc->dpEnv(), odbc->dpDbc());
}
-static QString qODBCWarn(const QODBCDriverPrivate* odbc, int *nativeCode = nullptr)
+static QList<DiagRecord> qODBCWarn(const QODBCDriverPrivate *odbc)
{
- return qODBCWarn(0, odbc->hEnv, odbc->hDbc, nativeCode);
+ return qODBCWarn(nullptr, odbc->hEnv, odbc->hDbc);
}
-static void qSqlWarning(const QString& message, const QODBCResultPrivate* odbc)
+static DiagRecord combineRecords(const QList<DiagRecord> &records)
{
- qWarning() << message << "\tError:" << qODBCWarn(odbc);
+ const auto add = [](const DiagRecord &a, const DiagRecord &b) {
+ return DiagRecord{a.description + u' ' + b.description,
+ a.sqlState + u';' + b.sqlState,
+ a.errorCode + u';' + b.errorCode};
+ };
+ if (records.isEmpty())
+ return {};
+ return std::accumulate(std::next(records.begin()), records.end(), records.front(), add);
}
-static void qSqlWarning(const QString &message, const QODBCDriverPrivate *odbc)
+static QSqlError errorFromDiagRecords(const QString &err,
+ QSqlError::ErrorType type,
+ const QList<DiagRecord> &records)
{
- qWarning() << message << "\tError:" << qODBCWarn(odbc);
+ if (records.empty())
+ return QSqlError("QODBC: unknown error"_L1, {}, type, {});
+ const auto combined = combineRecords(records);
+ return QSqlError("QODBC: "_L1 + err, combined.description + ", "_L1 + combined.sqlState, type,
+ combined.errorCode);
}
-static void qSqlWarning(const QString &message, const SQLHANDLE hStmt)
+static QString errorStringFromDiagRecords(const QList<DiagRecord>& records)
{
- qWarning() << message << "\tError:" << qODBCWarn(hStmt);
+ const auto combined = combineRecords(records);
+ return combined.description;
}
-static QSqlError qMakeError(const QString& err, QSqlError::ErrorType type, const QODBCResultPrivate* p)
+template<class T>
+static void qSqlWarning(const QString &message, T &&val)
{
- int nativeCode = -1;
- QString message = qODBCWarn(p, &nativeCode);
- return QSqlError("QODBC: "_L1 + err, message, type,
- nativeCode != -1 ? QString::number(nativeCode) : QString());
+ const auto addMsg = errorStringFromDiagRecords(qODBCWarn(val));
+ if (addMsg.isEmpty())
+ qCWarning(lcOdbc) << message;
+ else
+ qCWarning(lcOdbc) << message << "\tError:" << addMsg;
+}
+
+static QSqlError qMakeError(const QString &err,
+ QSqlError::ErrorType type,
+ const QODBCResultPrivate *p)
+{
+ return errorFromDiagRecords(err, type, qODBCWarn(p));
}
-static QSqlError qMakeError(const QString& err, QSqlError::ErrorType type,
- const QODBCDriverPrivate* p)
+static QSqlError qMakeError(const QString &err,
+ QSqlError::ErrorType type,
+ const QODBCDriverPrivate *p)
{
- int nativeCode = -1;
- QString message = qODBCWarn(p, &nativeCode);
- return QSqlError("QODBC: "_L1 + err, message, type,
- nativeCode != -1 ? QString::number(nativeCode) : QString());
+ return errorFromDiagRecords(err, type, qODBCWarn(p));
}
static QMetaType qDecodeODBCType(SQLSMALLINT sqltype, bool isSigned = true)
@@ -342,6 +378,7 @@ static QMetaType qDecodeODBCType(SQLSMALLINT sqltype, bool isSigned = true)
case SQL_TYPE_DATE:
type = QMetaType::QDate;
break;
+ case SQL_SS_TIME2:
case SQL_TIME:
case SQL_TYPE_TIME:
type = QMetaType::QTime;
@@ -370,119 +407,69 @@ static QMetaType qDecodeODBCType(SQLSMALLINT sqltype, bool isSigned = true)
return QMetaType(type);
}
-static QString qGetStringData(SQLHANDLE hStmt, int column, int colSize, bool unicode = false)
+template <typename CT>
+static QVariant getStringDataImpl(SQLHANDLE hStmt, SQLUSMALLINT column, qsizetype colSize, SQLSMALLINT targetType)
{
QString fieldVal;
SQLRETURN r = SQL_ERROR;
SQLLEN lengthIndicator = 0;
-
- // NB! colSize must be a multiple of 2 for unicode enabled DBs
- if (colSize <= 0) {
- colSize = 256;
- } else if (colSize > 65536) { // limit buffer size to 64 KB
- colSize = 65536;
- } else {
- colSize++; // make sure there is room for more than the 0 termination
- }
- if (unicode) {
+ QVarLengthArray<CT> buf(colSize);
+ while (true) {
r = SQLGetData(hStmt,
- column+1,
- SQL_C_TCHAR,
- NULL,
- 0,
- &lengthIndicator);
- if ((r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) && lengthIndicator > 0)
- colSize = int(lengthIndicator / sizeof(SQLTCHAR) + 1);
- QVarLengthArray<SQLTCHAR> buf(colSize);
- memset(buf.data(), 0, colSize*sizeof(SQLTCHAR));
- while (true) {
- r = SQLGetData(hStmt,
- column+1,
- SQL_C_TCHAR,
- (SQLPOINTER)buf.data(),
- colSize*sizeof(SQLTCHAR),
- &lengthIndicator);
- if (r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) {
- if (lengthIndicator == SQL_NULL_DATA) {
- fieldVal.clear();
- break;
- }
- // starting with ODBC Native Client 2012, SQL_NO_TOTAL is returned
- // instead of the length (which sometimes was wrong in older versions)
- // see link for more info: http://msdn.microsoft.com/en-us/library/jj219209.aspx
- // if length indicator equals SQL_NO_TOTAL, indicating that
- // more data can be fetched, but size not known, collect data
- // and fetch next block
- if (lengthIndicator == SQL_NO_TOTAL) {
- fieldVal += fromSQLTCHAR(buf, colSize);
- continue;
- }
- // if SQL_SUCCESS_WITH_INFO is returned, indicating that
- // more data can be fetched, the length indicator does NOT
- // contain the number of bytes returned - it contains the
- // total number of bytes that CAN be fetched
- int rSize = (r == SQL_SUCCESS_WITH_INFO) ? colSize : int(lengthIndicator / sizeof(SQLTCHAR));
- fieldVal += fromSQLTCHAR(buf, rSize);
- if (lengthIndicator < SQLLEN(colSize*sizeof(SQLTCHAR))) {
- // workaround for Drivermanagers that don't return SQL_NO_DATA
- break;
- }
- } else if (r == SQL_NO_DATA) {
- break;
- } else {
- qWarning() << "qGetStringData: Error while fetching data (" << qWarnODBCHandle(SQL_HANDLE_STMT, hStmt) << ')';
- fieldVal.clear();
- break;
+ column + 1,
+ targetType,
+ SQLPOINTER(buf.data()), SQLINTEGER(buf.size() * sizeof(CT)),
+ &lengthIndicator);
+ if (SQL_SUCCEEDED(r)) {
+ if (lengthIndicator == SQL_NULL_DATA) {
+ return {};
}
- }
- } else {
- r = SQLGetData(hStmt,
- column+1,
- SQL_C_CHAR,
- NULL,
- 0,
- &lengthIndicator);
- if ((r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) && lengthIndicator > 0)
- colSize = lengthIndicator + 1;
- QVarLengthArray<SQLCHAR> buf(colSize);
- while (true) {
- r = SQLGetData(hStmt,
- column+1,
- SQL_C_CHAR,
- (SQLPOINTER)buf.data(),
- colSize,
- &lengthIndicator);
- if (r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) {
- if (lengthIndicator == SQL_NULL_DATA || lengthIndicator == SQL_NO_TOTAL) {
- fieldVal.clear();
- break;
- }
- // if SQL_SUCCESS_WITH_INFO is returned, indicating that
- // more data can be fetched, the length indicator does NOT
- // contain the number of bytes returned - it contains the
- // total number of bytes that CAN be fetched
- qsizetype rSize = (r == SQL_SUCCESS_WITH_INFO) ? colSize : lengthIndicator;
- // Remove any trailing \0 as some drivers misguidedly append one
- int realsize = qMin(rSize, buf.size());
- if (realsize > 0 && buf[realsize - 1] == 0)
- realsize--;
- fieldVal += QString::fromUtf8(reinterpret_cast<const char *>(buf.constData()), realsize);
- if (lengthIndicator < SQLLEN(colSize)) {
- // workaround for Drivermanagers that don't return SQL_NO_DATA
- break;
- }
- } else if (r == SQL_NO_DATA) {
- break;
- } else {
- qWarning() << "qGetStringData: Error while fetching data (" << qWarnODBCHandle(SQL_HANDLE_STMT, hStmt) << ')';
- fieldVal.clear();
+ // starting with ODBC Native Client 2012, SQL_NO_TOTAL is returned
+ // instead of the length (which sometimes was wrong in older versions)
+ // see link for more info: http://msdn.microsoft.com/en-us/library/jj219209.aspx
+ // if length indicator equals SQL_NO_TOTAL, indicating that
+ // more data can be fetched, but size not known, collect data
+ // and fetch next block
+ if (lengthIndicator == SQL_NO_TOTAL) {
+ fieldVal += fromSQLTCHAR<QVarLengthArray<CT>, sizeof(CT)>(buf, buf.size());
+ continue;
+ }
+ // if SQL_SUCCESS_WITH_INFO is returned, indicating that
+ // more data can be fetched, the length indicator does NOT
+ // contain the number of bytes returned - it contains the
+ // total number of bytes that CAN be fetched
+ const qsizetype rSize = (r == SQL_SUCCESS_WITH_INFO)
+ ? buf.size()
+ : qsizetype(lengthIndicator / sizeof(CT));
+ fieldVal += fromSQLTCHAR<QVarLengthArray<CT>, sizeof(CT)>(buf, rSize);
+ // lengthIndicator does not contain the termination character
+ if (lengthIndicator < SQLLEN((buf.size() - 1) * sizeof(CT))) {
+ // workaround for Drivermanagers that don't return SQL_NO_DATA
break;
}
+ } else if (r == SQL_NO_DATA) {
+ break;
+ } else {
+ qSqlWarning("QODBC::getStringData: Error while fetching data"_L1, hStmt);
+ return {};
}
}
return fieldVal;
}
+static QVariant qGetStringData(SQLHANDLE hStmt, SQLUSMALLINT column, int colSize, bool unicode)
+{
+ if (colSize <= 0) {
+ colSize = 256; // default Prealloc size of QVarLengthArray
+ } else if (colSize > 65536) { // limit buffer size to 64 KB
+ colSize = 65536;
+ } else {
+ colSize++; // make sure there is room for more than the 0 termination
+ }
+ return unicode ? getStringDataImpl<SQLTCHAR>(hStmt, column, colSize, SQL_C_TCHAR)
+ : getStringDataImpl<SQLCHAR>(hStmt, column, colSize, SQL_C_CHAR);
+}
+
static QVariant qGetBinaryData(SQLHANDLE hStmt, int column)
{
QByteArray fieldVal;
@@ -494,19 +481,19 @@ static QVariant qGetBinaryData(SQLHANDLE hStmt, int column)
SQLLEN lengthIndicator = 0;
SQLRETURN r = SQL_ERROR;
- QVarLengthArray<SQLTCHAR> colName(COLNAMESIZE);
+ QVarLengthArray<SQLTCHAR, COLNAMESIZE> colName(COLNAMESIZE);
r = SQLDescribeCol(hStmt,
column + 1,
- colName.data(),
- COLNAMESIZE,
+ colName.data(), SQLSMALLINT(colName.size()),
&colNameLen,
&colType,
&colSize,
&colScale,
&nullable);
if (r != SQL_SUCCESS)
- qWarning() << "qGetBinaryData: Unable to describe column" << column;
+ qSqlWarning(("QODBC::qGetBinaryData: Unable to describe column %1"_L1)
+ .arg(QString::number(column)), hStmt);
// SQLDescribeCol may return 0 if size cannot be determined
if (!colSize)
colSize = 255;
@@ -521,7 +508,7 @@ static QVariant qGetBinaryData(SQLHANDLE hStmt, int column)
const_cast<char *>(fieldVal.constData() + read),
colSize,
&lengthIndicator);
- if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO)
+ if (!SQL_SUCCEEDED(r))
break;
if (lengthIndicator == SQL_NULL_DATA)
return QVariant(QMetaType(QMetaType::QByteArray));
@@ -550,7 +537,7 @@ static QVariant qGetIntData(SQLHANDLE hStmt, int column, bool isSigned = true)
(SQLPOINTER)&intbuf,
sizeof(intbuf),
&lengthIndicator);
- if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO)
+ if (!SQL_SUCCEEDED(r))
return QVariant();
if (lengthIndicator == SQL_NULL_DATA)
return QVariant(QMetaType::fromType<int>());
@@ -570,7 +557,7 @@ static QVariant qGetDoubleData(SQLHANDLE hStmt, int column)
(SQLPOINTER) &dblbuf,
0,
&lengthIndicator);
- if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {
+ if (!SQL_SUCCEEDED(r)) {
return QVariant();
}
if (lengthIndicator == SQL_NULL_DATA)
@@ -590,7 +577,7 @@ static QVariant qGetBigIntData(SQLHANDLE hStmt, int column, bool isSigned = true
(SQLPOINTER) &lngbuf,
sizeof(lngbuf),
&lengthIndicator);
- if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO)
+ if (!SQL_SUCCEEDED(r))
return QVariant();
if (lengthIndicator == SQL_NULL_DATA)
return QVariant(QMetaType::fromType<qlonglong>());
@@ -606,28 +593,25 @@ static bool isAutoValue(const SQLHANDLE hStmt, int column)
SQLLEN nNumericAttribute = 0; // Check for auto-increment
const SQLRETURN r = ::SQLColAttribute(hStmt, column + 1, SQL_DESC_AUTO_UNIQUE_VALUE,
0, 0, 0, &nNumericAttribute);
- if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {
- qSqlWarning(QStringLiteral("qMakeField: Unable to get autovalue attribute for column ")
- + QString::number(column), hStmt);
+ if (!SQL_SUCCEEDED(r)) {
+ qSqlWarning(("QODBC::isAutoValue: Unable to get autovalue attribute for column %1"_L1)
+ .arg(QString::number(column)), hStmt);
return false;
}
return nNumericAttribute != SQL_FALSE;
}
-static QSqlField qMakeFieldInfo(const SQLHANDLE hStmt, int i, QString *errorMessage);
-
// creates a QSqlField from a valid hStmt generated
// by SQLColumns. The hStmt has to point to a valid position.
static QSqlField qMakeFieldInfo(const SQLHANDLE hStmt, const QODBCDriverPrivate* p)
{
- QString fname = qGetStringData(hStmt, 3, -1, p->unicode);
+ QString fname = qGetStringData(hStmt, 3, -1, p->unicode).toString();
int type = qGetIntData(hStmt, 4).toInt(); // column type
QSqlField f(fname, qDecodeODBCType(type, p));
QVariant var = qGetIntData(hStmt, 6);
f.setLength(var.isNull() ? -1 : var.toInt()); // column size
var = qGetIntData(hStmt, 8).toInt();
f.setPrecision(var.isNull() ? -1 : var.toInt()); // precision
- f.setSqlType(type);
int required = qGetIntData(hStmt, 10).toInt(); // nullable-flag
// required can be SQL_NO_NULLS, SQL_NULLABLE or SQL_NULLABLE_UNKNOWN
if (required == SQL_NO_NULLS)
@@ -638,16 +622,7 @@ static QSqlField qMakeFieldInfo(const SQLHANDLE hStmt, const QODBCDriverPrivate*
return f;
}
-static QSqlField qMakeFieldInfo(const QODBCResultPrivate* p, int i )
-{
- QString errorMessage;
- const QSqlField result = qMakeFieldInfo(p->hStmt, i, &errorMessage);
- if (!errorMessage.isEmpty())
- qSqlWarning(errorMessage, p);
- return result;
-}
-
-static QSqlField qMakeFieldInfo(const SQLHANDLE hStmt, int i, QString *errorMessage)
+static QSqlField qMakeFieldInfo(const QODBCResultPrivate *p, int i)
{
SQLSMALLINT colNameLen;
SQLSMALLINT colType;
@@ -655,12 +630,10 @@ static QSqlField qMakeFieldInfo(const SQLHANDLE hStmt, int i, QString *errorMess
SQLSMALLINT colScale;
SQLSMALLINT nullable;
SQLRETURN r = SQL_ERROR;
- QVarLengthArray<SQLTCHAR> colName(COLNAMESIZE);
- errorMessage->clear();
- r = SQLDescribeCol(hStmt,
+ QVarLengthArray<SQLTCHAR, COLNAMESIZE> colName(COLNAMESIZE);
+ r = SQLDescribeCol(p->hStmt,
i+1,
- colName.data(),
- (SQLSMALLINT)COLNAMESIZE,
+ colName.data(), SQLSMALLINT(colName.size()),
&colNameLen,
&colType,
&colSize,
@@ -668,12 +641,13 @@ static QSqlField qMakeFieldInfo(const SQLHANDLE hStmt, int i, QString *errorMess
&nullable);
if (r != SQL_SUCCESS) {
- *errorMessage = QStringLiteral("qMakeField: Unable to describe column ") + QString::number(i);
+ qSqlWarning(("QODBC::qMakeFieldInfo: Unable to describe column %1"_L1)
+ .arg(QString::number(i)), p);
return QSqlField();
}
SQLLEN unsignedFlag = SQL_FALSE;
- r = SQLColAttribute (hStmt,
+ r = SQLColAttribute (p->hStmt,
i + 1,
SQL_DESC_UNSIGNED,
0,
@@ -681,15 +655,14 @@ static QSqlField qMakeFieldInfo(const SQLHANDLE hStmt, int i, QString *errorMess
0,
&unsignedFlag);
if (r != SQL_SUCCESS) {
- qSqlWarning(QStringLiteral("qMakeField: Unable to get column attributes for column ")
- + QString::number(i), hStmt);
+ qSqlWarning(("QODBC::qMakeFieldInfo: Unable to get column attributes for column %1"_L1)
+ .arg(QString::number(i)), p);
}
const QString qColName(fromSQLTCHAR(colName, colNameLen));
// nullable can be SQL_NO_NULLS, SQL_NULLABLE or SQL_NULLABLE_UNKNOWN
QMetaType type = qDecodeODBCType(colType, unsignedFlag == SQL_FALSE);
QSqlField f(qColName, type);
- f.setSqlType(colType);
f.setLength(colSize == 0 ? -1 : int(colSize));
f.setPrecision(colScale == 0 ? -1 : int(colScale));
if (nullable == SQL_NO_NULLS)
@@ -697,13 +670,18 @@ static QSqlField qMakeFieldInfo(const SQLHANDLE hStmt, int i, QString *errorMess
else if (nullable == SQL_NULLABLE)
f.setRequired(false);
// else we don't know
- f.setAutoValue(isAutoValue(hStmt, i));
- QVarLengthArray<SQLTCHAR> tableName(TABLENAMESIZE);
+ f.setAutoValue(isAutoValue(p->hStmt, i));
+ QVarLengthArray<SQLTCHAR, TABLENAMESIZE> tableName(TABLENAMESIZE);
SQLSMALLINT tableNameLen;
- r = SQLColAttribute(hStmt, i + 1, SQL_DESC_BASE_TABLE_NAME, tableName.data(),
- TABLENAMESIZE, &tableNameLen, 0);
+ r = SQLColAttribute(p->hStmt,
+ i + 1,
+ SQL_DESC_BASE_TABLE_NAME,
+ tableName.data(),
+ SQLSMALLINT(tableName.size() * sizeof(SQLTCHAR)), // SQLColAttribute needs/returns size in bytes
+ &tableNameLen,
+ 0);
if (r == SQL_SUCCESS)
- f.setTableName(fromSQLTCHAR(tableName, tableNameLen));
+ f.setTableName(fromSQLTCHAR(tableName, tableNameLen / sizeof(SQLTCHAR)));
return f;
}
@@ -724,7 +702,7 @@ QChar QODBCDriverPrivate::quoteChar()
&driverResponse,
sizeof(driverResponse),
&length);
- if (r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO)
+ if (SQL_SUCCEEDED(r))
quote = QChar(driverResponse[0]);
else
quote = u'"';
@@ -733,176 +711,193 @@ QChar QODBCDriverPrivate::quoteChar()
return quote;
}
+SQLRETURN QODBCDriverPrivate::sqlFetchNext(const SqlStmtHandle &hStmt) const
+{
+ return sqlFetchNext(hStmt.handle());
+}
-bool QODBCDriverPrivate::setConnectionOptions(const QString& connOpts)
+SQLRETURN QODBCDriverPrivate::sqlFetchNext(SQLHANDLE hStmt) const
+{
+ if (hasSQLFetchScroll)
+ return SQLFetchScroll(hStmt, SQL_FETCH_NEXT, 0);
+ return SQLFetch(hStmt);
+}
+
+static SQLRETURN qt_string_SQLSetConnectAttr(SQLHDBC handle, SQLINTEGER attr, QStringView val)
+{
+ auto encoded = toSQLTCHAR(val);
+ return SQLSetConnectAttr(handle, attr,
+ encoded.data(),
+ SQLINTEGER(encoded.size() * sizeof(SQLTCHAR))); // size in bytes
+}
+
+
+bool QODBCDriverPrivate::setConnectionOptions(const QString &connOpts)
{
// Set any connection attributes
- const QStringList opts(connOpts.split(u';', Qt::SkipEmptyParts));
SQLRETURN r = SQL_SUCCESS;
- for (int i = 0; i < opts.count(); ++i) {
- const QString tmp(opts.at(i));
+ for (const auto connOpt : QStringTokenizer{connOpts, u';'}) {
int idx;
- if ((idx = tmp.indexOf(u'=')) == -1) {
- qWarning() << "QODBCDriver::open: Illegal connect option value '" << tmp << '\'';
+ if ((idx = connOpt.indexOf(u'=')) == -1) {
+ qSqlWarning(("QODBCDriver::open: Illegal connect option value '%1'"_L1)
+ .arg(connOpt), this);
continue;
}
- const QString opt(tmp.left(idx));
- const QString val(tmp.mid(idx + 1).simplified());
+ const auto opt(connOpt.left(idx));
+ const auto val(connOpt.mid(idx + 1).trimmed());
SQLUINTEGER v = 0;
r = SQL_SUCCESS;
- if (opt.toUpper() == "SQL_ATTR_ACCESS_MODE"_L1) {
- if (val.toUpper() == "SQL_MODE_READ_ONLY"_L1) {
+ if (opt == "SQL_ATTR_ACCESS_MODE"_L1) {
+ if (val == "SQL_MODE_READ_ONLY"_L1) {
v = SQL_MODE_READ_ONLY;
- } else if (val.toUpper() == "SQL_MODE_READ_WRITE"_L1) {
+ } else if (val == "SQL_MODE_READ_WRITE"_L1) {
v = SQL_MODE_READ_WRITE;
} else {
- qWarning() << "QODBCDriver::open: Unknown option value '" << val << '\'';
+ qSqlWarning(("QODBCDriver::open: Unknown option value '%1'"_L1)
+ .arg(val), this);
continue;
}
r = SQLSetConnectAttr(hDbc, SQL_ATTR_ACCESS_MODE, (SQLPOINTER) size_t(v), 0);
- } else if (opt.toUpper() == "SQL_ATTR_CONNECTION_TIMEOUT"_L1) {
+ } else if (opt == "SQL_ATTR_CONNECTION_TIMEOUT"_L1) {
v = val.toUInt();
r = SQLSetConnectAttr(hDbc, SQL_ATTR_CONNECTION_TIMEOUT, (SQLPOINTER) size_t(v), 0);
- } else if (opt.toUpper() == "SQL_ATTR_LOGIN_TIMEOUT"_L1) {
+ } else if (opt == "SQL_ATTR_LOGIN_TIMEOUT"_L1) {
v = val.toUInt();
r = SQLSetConnectAttr(hDbc, SQL_ATTR_LOGIN_TIMEOUT, (SQLPOINTER) size_t(v), 0);
- } else if (opt.toUpper() == "SQL_ATTR_CURRENT_CATALOG"_L1) {
- val.utf16(); // 0 terminate
- r = SQLSetConnectAttr(hDbc, SQL_ATTR_CURRENT_CATALOG,
- toSQLTCHAR(val).data(),
- SQLINTEGER(val.length() * sizeof(SQLTCHAR)));
- } else if (opt.toUpper() == "SQL_ATTR_METADATA_ID"_L1) {
- if (val.toUpper() == "SQL_TRUE"_L1) {
+ } else if (opt == "SQL_ATTR_CURRENT_CATALOG"_L1) {
+ r = qt_string_SQLSetConnectAttr(hDbc, SQL_ATTR_CURRENT_CATALOG, val);
+ } else if (opt == "SQL_ATTR_METADATA_ID"_L1) {
+ if (val == "SQL_TRUE"_L1) {
v = SQL_TRUE;
- } else if (val.toUpper() == "SQL_FALSE"_L1) {
+ } else if (val == "SQL_FALSE"_L1) {
v = SQL_FALSE;
} else {
- qWarning() << "QODBCDriver::open: Unknown option value '" << val << '\'';
+ qSqlWarning(("QODBCDriver::open: Unknown option value '%1'"_L1)
+ .arg(val), this);
continue;
}
r = SQLSetConnectAttr(hDbc, SQL_ATTR_METADATA_ID, (SQLPOINTER) size_t(v), 0);
- } else if (opt.toUpper() == "SQL_ATTR_PACKET_SIZE"_L1) {
+ } else if (opt == "SQL_ATTR_PACKET_SIZE"_L1) {
v = val.toUInt();
r = SQLSetConnectAttr(hDbc, SQL_ATTR_PACKET_SIZE, (SQLPOINTER) size_t(v), 0);
- } else if (opt.toUpper() == "SQL_ATTR_TRACEFILE"_L1) {
- val.utf16(); // 0 terminate
- r = SQLSetConnectAttr(hDbc, SQL_ATTR_TRACEFILE,
- toSQLTCHAR(val).data(),
- SQLINTEGER(val.length() * sizeof(SQLTCHAR)));
- } else if (opt.toUpper() == "SQL_ATTR_TRACE"_L1) {
- if (val.toUpper() == "SQL_OPT_TRACE_OFF"_L1) {
+ } else if (opt == "SQL_ATTR_TRACEFILE"_L1) {
+ r = qt_string_SQLSetConnectAttr(hDbc, SQL_ATTR_TRACEFILE, val);
+ } else if (opt == "SQL_ATTR_TRACE"_L1) {
+ if (val == "SQL_OPT_TRACE_OFF"_L1) {
v = SQL_OPT_TRACE_OFF;
- } else if (val.toUpper() == "SQL_OPT_TRACE_ON"_L1) {
+ } else if (val == "SQL_OPT_TRACE_ON"_L1) {
v = SQL_OPT_TRACE_ON;
} else {
- qWarning() << "QODBCDriver::open: Unknown option value '" << val << '\'';
+ qSqlWarning(("QODBCDriver::open: Unknown option value '%1'"_L1)
+ .arg(val), this);
continue;
}
r = SQLSetConnectAttr(hDbc, SQL_ATTR_TRACE, (SQLPOINTER) size_t(v), 0);
- } else if (opt.toUpper() == "SQL_ATTR_CONNECTION_POOLING"_L1) {
+ } else if (opt == "SQL_ATTR_CONNECTION_POOLING"_L1) {
if (val == "SQL_CP_OFF"_L1)
v = SQL_CP_OFF;
- else if (val.toUpper() == "SQL_CP_ONE_PER_DRIVER"_L1)
+ else if (val == "SQL_CP_ONE_PER_DRIVER"_L1)
v = SQL_CP_ONE_PER_DRIVER;
- else if (val.toUpper() == "SQL_CP_ONE_PER_HENV"_L1)
+ else if (val == "SQL_CP_ONE_PER_HENV"_L1)
v = SQL_CP_ONE_PER_HENV;
- else if (val.toUpper() == "SQL_CP_DEFAULT"_L1)
+ else if (val == "SQL_CP_DEFAULT"_L1)
v = SQL_CP_DEFAULT;
else {
- qWarning() << "QODBCDriver::open: Unknown option value '" << val << '\'';
+ qSqlWarning(("QODBCDriver::open: Unknown option value '%1'"_L1)
+ .arg(val), this);
continue;
}
r = SQLSetConnectAttr(hDbc, SQL_ATTR_CONNECTION_POOLING, (SQLPOINTER) size_t(v), 0);
- } else if (opt.toUpper() == "SQL_ATTR_CP_MATCH"_L1) {
- if (val.toUpper() == "SQL_CP_STRICT_MATCH"_L1)
+ } else if (opt == "SQL_ATTR_CP_MATCH"_L1) {
+ if (val == "SQL_CP_STRICT_MATCH"_L1)
v = SQL_CP_STRICT_MATCH;
- else if (val.toUpper() == "SQL_CP_RELAXED_MATCH"_L1)
+ else if (val == "SQL_CP_RELAXED_MATCH"_L1)
v = SQL_CP_RELAXED_MATCH;
- else if (val.toUpper() == "SQL_CP_MATCH_DEFAULT"_L1)
+ else if (val == "SQL_CP_MATCH_DEFAULT"_L1)
v = SQL_CP_MATCH_DEFAULT;
else {
- qWarning() << "QODBCDriver::open: Unknown option value '" << val << '\'';
+ qSqlWarning(("QODBCDriver::open: Unknown option value '%1'"_L1)
+ .arg(val), this);
continue;
}
r = SQLSetConnectAttr(hDbc, SQL_ATTR_CP_MATCH, (SQLPOINTER) size_t(v), 0);
- } else if (opt.toUpper() == "SQL_ATTR_ODBC_VERSION"_L1) {
+ } else if (opt == "SQL_ATTR_ODBC_VERSION"_L1) {
// Already handled in QODBCDriver::open()
continue;
} else {
- qWarning() << "QODBCDriver::open: Unknown connection attribute '" << opt << '\'';
+ qSqlWarning(("QODBCDriver::open: Unknown connection attribute '%1'"_L1)
+ .arg(opt), this);
}
- if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO)
- qSqlWarning(QString::fromLatin1("QODBCDriver::open: Unable to set connection attribute'%1'").arg(
- opt), this);
+ if (!SQL_SUCCEEDED(r))
+ qSqlWarning(("QODBCDriver::open: Unable to set connection attribute '%1'"_L1)
+ .arg(opt), this);
}
return true;
}
-void QODBCDriverPrivate::splitTableQualifier(const QString & qualifier, QString &catalog,
- QString &schema, QString &table)
+void QODBCDriverPrivate::splitTableQualifier(const QString &qualifier, QString &catalog,
+ QString &schema, QString &table) const
{
+ Q_Q(const QODBCDriver);
+ const auto adjustName = [&](const QString &name) {
+ if (q->isIdentifierEscaped(name, QSqlDriver::TableName))
+ return q->stripDelimiters(name, QSqlDriver::TableName);
+ return adjustCase(name);
+ };
+ catalog.clear();
+ schema.clear();
+ table.clear();
if (!useSchema) {
- table = qualifier;
+ table = adjustName(qualifier);
return;
}
- QStringList l = qualifier.split(u'.');
- if (l.count() > 3)
- return; // can't possibly be a valid table qualifier
- int i = 0, n = l.count();
- if (n == 1) {
- table = qualifier;
- } else {
- for (QStringList::Iterator it = l.begin(); it != l.end(); ++it) {
- if (n == 3) {
- if (i == 0) {
- catalog = *it;
- } else if (i == 1) {
- schema = *it;
- } else if (i == 2) {
- table = *it;
- }
- } else if (n == 2) {
- if (i == 0) {
- schema = *it;
- } else if (i == 1) {
- table = *it;
- }
- }
- i++;
- }
+ const QList<QStringView> l = QStringView(qualifier).split(u'.');
+ switch (l.count()) {
+ case 1:
+ table = adjustName(qualifier);
+ break;
+ case 2:
+ schema = adjustName(l.at(0).toString());
+ table = adjustName(l.at(1).toString());
+ break;
+ case 3:
+ catalog = adjustName(l.at(0).toString());
+ schema = adjustName(l.at(1).toString());
+ table = adjustName(l.at(2).toString());
+ break;
+ default:
+ qSqlWarning(("QODBCDriver::splitTableQualifier: Unable to split table qualifier '%1'"_L1)
+ .arg(qualifier), this);
+ break;
}
}
-QODBCDriverPrivate::DefaultCase QODBCDriverPrivate::defaultCase() const
+void QODBCDriverPrivate::checkDefaultCase()
{
- DefaultCase ret;
+ m_defaultCase = DefaultCase::Mixed; //arbitrary case if driver cannot be queried
SQLUSMALLINT casing;
- int r = SQLGetInfo(hDbc,
- SQL_IDENTIFIER_CASE,
- &casing,
- sizeof(casing),
- NULL);
- if ( r != SQL_SUCCESS)
- ret = Mixed;//arbitrary case if driver cannot be queried
- else {
+ SQLRETURN r = SQLGetInfo(hDbc,
+ SQL_IDENTIFIER_CASE,
+ &casing,
+ sizeof(casing),
+ NULL);
+ if (r == SQL_SUCCESS) {
switch (casing) {
- case (SQL_IC_UPPER):
- ret = Upper;
- break;
- case (SQL_IC_LOWER):
- ret = Lower;
- break;
- case (SQL_IC_SENSITIVE):
- ret = Sensitive;
- break;
- case (SQL_IC_MIXED):
- default:
- ret = Mixed;
- break;
+ case SQL_IC_UPPER:
+ m_defaultCase = DefaultCase::Upper;
+ break;
+ case SQL_IC_LOWER:
+ m_defaultCase = DefaultCase::Lower;
+ break;
+ case SQL_IC_SENSITIVE:
+ m_defaultCase = DefaultCase::Sensitive;
+ break;
+ case SQL_IC_MIXED:
+ m_defaultCase = DefaultCase::Mixed;
+ break;
}
}
- return ret;
}
/*
@@ -911,20 +906,16 @@ QODBCDriverPrivate::DefaultCase QODBCDriverPrivate::defaultCase() const
*/
QString QODBCDriverPrivate::adjustCase(const QString &identifier) const
{
- QString ret = identifier;
- switch(defaultCase()) {
- case (Lower):
- ret = identifier.toLower();
- break;
- case (Upper):
- ret = identifier.toUpper();
- break;
- case(Mixed):
- case(Sensitive):
- default:
- ret = identifier;
+ switch (m_defaultCase) {
+ case DefaultCase::Lower:
+ return identifier.toLower();
+ case DefaultCase::Upper:
+ return identifier.toUpper();
+ case DefaultCase::Mixed:
+ case DefaultCase::Sensitive:
+ break;
}
- return ret;
+ return identifier;
}
////////////////////////////////////////////////////////////////////////////
@@ -940,8 +931,7 @@ QODBCResult::~QODBCResult()
if (d->hStmt && d->isStmtHandleValid() && driver() && driver()->isOpen()) {
SQLRETURN r = SQLFreeHandle(SQL_HANDLE_STMT, d->hStmt);
if (r != SQL_SUCCESS)
- qSqlWarning("QODBCDriver: Unable to free statement handle "_L1
- + QString::number(r), d);
+ qSqlWarning(("QODBCResult: Unable to free statement handle "_L1), d);
}
}
@@ -985,17 +975,20 @@ bool QODBCResult::reset (const QString& query)
(SQLPOINTER)SQL_CURSOR_STATIC,
SQL_IS_UINTEGER);
}
- if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {
+ if (!SQL_SUCCEEDED(r)) {
setLastError(qMakeError(QCoreApplication::translate("QODBCResult",
"QODBCResult::reset: Unable to set 'SQL_CURSOR_STATIC' as statement attribute. "
"Please check your ODBC driver configuration"), QSqlError::StatementError, d));
return false;
}
- r = SQLExecDirect(d->hStmt,
- toSQLTCHAR(query).data(),
- (SQLINTEGER) query.length());
- if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO && r!= SQL_NO_DATA) {
+ {
+ auto encoded = toSQLTCHAR(query);
+ r = SQLExecDirect(d->hStmt,
+ encoded.data(),
+ SQLINTEGER(encoded.size()));
+ }
+ if (!SQL_SUCCEEDED(r) && r!= SQL_NO_DATA) {
setLastError(qMakeError(QCoreApplication::translate("QODBCResult",
"Unable to execute statement"), QSqlError::StatementError, d));
return false;
@@ -1003,14 +996,14 @@ bool QODBCResult::reset (const QString& query)
SQLULEN isScrollable = 0;
r = SQLGetStmtAttr(d->hStmt, SQL_ATTR_CURSOR_SCROLLABLE, &isScrollable, SQL_IS_INTEGER, 0);
- if (r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO)
+ if (SQL_SUCCEEDED(r))
setForwardOnly(isScrollable == SQL_NONSCROLLABLE);
SQLSMALLINT count = 0;
SQLNumResultCols(d->hStmt, &count);
if (count) {
setSelect(true);
- for (int i = 0; i < count; ++i) {
+ for (SQLSMALLINT i = 0; i < count; ++i) {
d->rInf.append(qMakeFieldInfo(d, i));
}
d->fieldCache.resize(count);
@@ -1072,7 +1065,7 @@ bool QODBCResult::fetchNext()
else
r = SQLFetch(d->hStmt);
- if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {
+ if (!SQL_SUCCEEDED(r)) {
if (r != SQL_NO_DATA)
setLastError(qMakeError(QCoreApplication::translate("QODBCResult",
"Unable to fetch next"), QSqlError::ConnectionError, d));
@@ -1169,7 +1162,8 @@ QVariant QODBCResult::data(int field)
{
Q_D(QODBCResult);
if (field >= d->rInf.count() || field < 0) {
- qWarning() << "QODBCResult::data: column" << field << "out of range";
+ qSqlWarning(("QODBCResult::data: column %1 out of range"_L1)
+ .arg(QString::number(field)), d);
return QVariant();
}
if (field < d->fieldCacheIdx)
@@ -1205,7 +1199,7 @@ QVariant QODBCResult::data(int field)
(SQLPOINTER)&dbuf,
0,
&lengthIndicator);
- if ((r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) && (lengthIndicator != SQL_NULL_DATA))
+ if (SQL_SUCCEEDED(r) && (lengthIndicator != SQL_NULL_DATA))
d->fieldCache[i] = QVariant(QDate(dbuf.year, dbuf.month, dbuf.day));
else
d->fieldCache[i] = QVariant(QMetaType::fromType<QDate>());
@@ -1218,7 +1212,7 @@ QVariant QODBCResult::data(int field)
(SQLPOINTER)&tbuf,
0,
&lengthIndicator);
- if ((r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) && (lengthIndicator != SQL_NULL_DATA))
+ if (SQL_SUCCEEDED(r) && (lengthIndicator != SQL_NULL_DATA))
d->fieldCache[i] = QVariant(QTime(tbuf.hour, tbuf.minute, tbuf.second));
else
d->fieldCache[i] = QVariant(QMetaType::fromType<QTime>());
@@ -1231,7 +1225,7 @@ QVariant QODBCResult::data(int field)
(SQLPOINTER)&dtbuf,
0,
&lengthIndicator);
- if ((r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) && (lengthIndicator != SQL_NULL_DATA))
+ if (SQL_SUCCEEDED(r) && (lengthIndicator != SQL_NULL_DATA))
d->fieldCache[i] = QVariant(QDateTime(QDate(dtbuf.year, dtbuf.month, dtbuf.day),
QTime(dtbuf.hour, dtbuf.minute, dtbuf.second, dtbuf.fraction / 1000000)));
else
@@ -1261,7 +1255,7 @@ QVariant QODBCResult::data(int field)
}
break;
default:
- d->fieldCache[i] = QVariant(qGetStringData(d->hStmt, i, info.length(), false));
+ d->fieldCache[i] = qGetStringData(d->hStmt, i, info.length(), false);
break;
}
d->fieldCacheIdx = field + 1;
@@ -1274,7 +1268,7 @@ bool QODBCResult::isNull(int field)
Q_D(const QODBCResult);
if (field < 0 || field >= d->fieldCache.size())
return true;
- if (field <= d->fieldCacheIdx) {
+ if (field >= d->fieldCacheIdx) {
// since there is no good way to find out whether the value is NULL
// without fetching the field we'll fetch it here.
// (data() also sets the NULL flag)
@@ -1295,8 +1289,7 @@ int QODBCResult::numRowsAffected()
SQLRETURN r = SQLRowCount(d->hStmt, &affectedRowCount);
if (r == SQL_SUCCESS)
return affectedRowCount;
- else
- qSqlWarning("QODBCResult::numRowsAffected: Unable to count affected rows"_L1, d);
+ qSqlWarning("QODBCResult::numRowsAffected: Unable to count affected rows"_L1, d);
return -1;
}
@@ -1336,16 +1329,19 @@ bool QODBCResult::prepare(const QString& query)
(SQLPOINTER)SQL_CURSOR_STATIC,
SQL_IS_UINTEGER);
}
- if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {
+ if (!SQL_SUCCEEDED(r)) {
setLastError(qMakeError(QCoreApplication::translate("QODBCResult",
"QODBCResult::reset: Unable to set 'SQL_CURSOR_STATIC' as statement attribute. "
"Please check your ODBC driver configuration"), QSqlError::StatementError, d));
return false;
}
- r = SQLPrepare(d->hStmt,
- toSQLTCHAR(query).data(),
- (SQLINTEGER) query.length());
+ {
+ auto encoded = toSQLTCHAR(query);
+ r = SQLPrepare(d->hStmt,
+ encoded.data(),
+ SQLINTEGER(encoded.size()));
+ }
if (r != SQL_SUCCESS) {
setLastError(qMakeError(QCoreApplication::translate("QODBCResult",
@@ -1373,14 +1369,12 @@ bool QODBCResult::exec()
SQLCloseCursor(d->hStmt);
QVariantList &values = boundValues();
- QByteArrayList tmpStorage(values.count(), QByteArray()); // holds temporary buffers
- QVarLengthArray<SQLLEN, 32> indicators(values.count());
- memset(indicators.data(), 0, indicators.size() * sizeof(SQLLEN));
+ QByteArrayList tmpStorage(values.count(), QByteArray()); // targets for SQLBindParameter()
+ QVarLengthArray<SQLLEN, 32> indicators(values.count(), 0);
// bind parameters - only positional binding allowed
- int i;
SQLRETURN r;
- for (i = 0; i < values.count(); ++i) {
+ for (qsizetype i = 0; i < values.count(); ++i) {
if (bindValueType(i) & QSql::Out)
values[i].detach();
const QVariant &val = values.at(i);
@@ -1588,36 +1582,36 @@ bool QODBCResult::exec()
case QMetaType::QString:
if (d->unicode) {
QByteArray &ba = tmpStorage[i];
- QString str = val.toString();
+ {
+ const auto encoded = toSQLTCHAR(val.toString());
+ ba = QByteArray(reinterpret_cast<const char *>(encoded.data()),
+ encoded.size() * sizeof(SQLTCHAR));
+ }
+
if (*ind != SQL_NULL_DATA)
- *ind = str.length() * sizeof(SQLTCHAR);
- const qsizetype strSize = str.length() * sizeof(SQLTCHAR);
+ *ind = ba.size();
if (bindValueType(i) & QSql::Out) {
- const QVarLengthArray<SQLTCHAR> a(toSQLTCHAR(str));
- ba = QByteArray((const char *)a.constData(), int(a.size() * sizeof(SQLTCHAR)));
r = SQLBindParameter(d->hStmt,
i + 1,
qParamType[bindValueType(i) & QSql::InOut],
SQL_C_TCHAR,
- strSize > 254 ? SQL_WLONGVARCHAR : SQL_WVARCHAR,
+ ba.size() > 254 ? SQL_WLONGVARCHAR : SQL_WVARCHAR,
0, // god knows... don't change this!
0,
- ba.data(),
+ const_cast<char *>(ba.constData()), // don't detach
ba.size(),
ind);
break;
}
- ba = QByteArray(reinterpret_cast<const char *>(toSQLTCHAR(str).constData()),
- int(strSize));
r = SQLBindParameter(d->hStmt,
i + 1,
qParamType[bindValueType(i) & QSql::InOut],
SQL_C_TCHAR,
- strSize > 254 ? SQL_WLONGVARCHAR : SQL_WVARCHAR,
- strSize,
+ ba.size() > 254 ? SQL_WLONGVARCHAR : SQL_WVARCHAR,
+ ba.size(),
0,
- const_cast<char *>(ba.constData()),
+ const_cast<char *>(ba.constData()), // don't detach
ba.size(),
ind);
break;
@@ -1660,15 +1654,15 @@ bool QODBCResult::exec()
break; }
}
if (r != SQL_SUCCESS) {
- qWarning() << "QODBCResult::exec: unable to bind variable:" << qODBCWarn(d);
+ qSqlWarning("QODBCResult::exec: unable to bind variable:"_L1, d);
setLastError(qMakeError(QCoreApplication::translate("QODBCResult",
"Unable to bind variable"), QSqlError::StatementError, d));
return false;
}
}
r = SQLExecute(d->hStmt);
- if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO && r != SQL_NO_DATA) {
- qWarning() << "QODBCResult::exec: Unable to execute statement:" << qODBCWarn(d);
+ if (!SQL_SUCCEEDED(r) && r != SQL_NO_DATA) {
+ qSqlWarning("QODBCResult::exec: Unable to execute statement:"_L1, d);
setLastError(qMakeError(QCoreApplication::translate("QODBCResult",
"Unable to execute statement"), QSqlError::StatementError, d));
return false;
@@ -1676,14 +1670,14 @@ bool QODBCResult::exec()
SQLULEN isScrollable = 0;
r = SQLGetStmtAttr(d->hStmt, SQL_ATTR_CURSOR_SCROLLABLE, &isScrollable, SQL_IS_INTEGER, 0);
- if (r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO)
+ if (SQL_SUCCEEDED(r))
setForwardOnly(isScrollable == SQL_NONSCROLLABLE);
SQLSMALLINT count = 0;
SQLNumResultCols(d->hStmt, &count);
if (count) {
setSelect(true);
- for (int i = 0; i < count; ++i) {
+ for (SQLSMALLINT i = 0; i < count; ++i) {
d->rInf.append(qMakeFieldInfo(d, i));
}
d->fieldCache.resize(count);
@@ -1697,7 +1691,7 @@ bool QODBCResult::exec()
if (!hasOutValues())
return true;
- for (i = 0; i < values.count(); ++i) {
+ for (qsizetype i = 0; i < values.count(); ++i) {
switch (values.at(i).typeId()) {
case QMetaType::QDate: {
DATE_STRUCT ds = *((DATE_STRUCT *)const_cast<char *>(tmpStorage.at(i).constData()));
@@ -1728,10 +1722,11 @@ bool QODBCResult::exec()
case QMetaType::QString:
if (d->unicode) {
if (bindValueType(i) & QSql::Out) {
- const QByteArray &first = tmpStorage.at(i);
- QVarLengthArray<SQLTCHAR> array;
- array.append((const SQLTCHAR *)first.constData(), first.size());
- values[i] = fromSQLTCHAR(array, first.size()/sizeof(SQLTCHAR));
+ const QByteArray &bytes = tmpStorage.at(i);
+ const auto strSize = bytes.size() / sizeof(SQLTCHAR);
+ QVarLengthArray<SQLTCHAR> string(strSize);
+ memcpy(string.data(), bytes.data(), strSize * sizeof(SQLTCHAR));
+ values[i] = fromSQLTCHAR(string);
}
break;
}
@@ -1807,9 +1802,7 @@ bool QODBCResult::nextResult()
SQLRETURN r = SQLMoreResults(d->hStmt);
if (r != SQL_SUCCESS) {
if (r == SQL_SUCCESS_WITH_INFO) {
- int nativeCode = -1;
- QString message = qODBCWarn(d, &nativeCode);
- qWarning() << "QODBCResult::nextResult():" << message;
+ qSqlWarning("QODBCResult::nextResult:"_L1, d);
} else {
if (r != SQL_NO_DATA)
setLastError(qMakeError(QCoreApplication::translate("QODBCResult",
@@ -1822,7 +1815,7 @@ bool QODBCResult::nextResult()
SQLNumResultCols(d->hStmt, &count);
if (count) {
setSelect(true);
- for (int i = 0; i < count; ++i) {
+ for (SQLSMALLINT i = 0; i < count; ++i) {
d->rInf.append(qMakeFieldInfo(d, i));
}
d->fieldCache.resize(count);
@@ -1928,6 +1921,18 @@ bool QODBCDriver::open(const QString & db,
int,
const QString& connOpts)
{
+ const auto ensureEscaped = [](QString arg) -> QString {
+ QChar quoteChar;
+ if (arg.startsWith(u'"'))
+ quoteChar = u'\'';
+ else if (arg.startsWith(u'\''))
+ quoteChar = u'"';
+ else if (arg.contains(u';'))
+ quoteChar = u'"';
+ else
+ return arg;
+ return quoteChar + arg + quoteChar;
+ };
Q_D(QODBCDriver);
if (isOpen())
close();
@@ -1935,7 +1940,7 @@ bool QODBCDriver::open(const QString & db,
r = SQLAllocHandle(SQL_HANDLE_ENV,
SQL_NULL_HANDLE,
&d->hEnv);
- if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {
+ if (!SQL_SUCCEEDED(r)) {
qSqlWarning("QODBCDriver::open: Unable to allocate environment"_L1, d);
setOpenError(true);
return false;
@@ -1947,7 +1952,7 @@ bool QODBCDriver::open(const QString & db,
r = SQLAllocHandle(SQL_HANDLE_DBC,
d->hEnv,
&d->hDbc);
- if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {
+ if (!SQL_SUCCEEDED(r)) {
qSqlWarning("QODBCDriver::open: Unable to allocate connection"_L1, d);
setOpenError(true);
cleanup();
@@ -1963,31 +1968,31 @@ bool QODBCDriver::open(const QString & db,
QString connQStr;
// support the "DRIVER={SQL SERVER};SERVER=blah" syntax
if (db.contains(".dsn"_L1, Qt::CaseInsensitive))
- connQStr = "FILEDSN="_L1 + db;
+ connQStr = "FILEDSN="_L1 + ensureEscaped(db);
else if (db.contains("DRIVER="_L1, Qt::CaseInsensitive)
|| db.contains("SERVER="_L1, Qt::CaseInsensitive))
connQStr = db;
else
- connQStr = "DSN="_L1 + db;
+ connQStr = "DSN="_L1 + ensureEscaped(db);
if (!user.isEmpty())
- connQStr += ";UID="_L1 + user;
+ connQStr += ";UID="_L1 + ensureEscaped(user);
if (!password.isEmpty())
- connQStr += ";PWD="_L1 + password;
+ connQStr += ";PWD="_L1 + ensureEscaped(password);
SQLSMALLINT cb;
- QVarLengthArray<SQLTCHAR> connOut(1024);
- memset(connOut.data(), 0, connOut.size() * sizeof(SQLTCHAR));
- r = SQLDriverConnect(d->hDbc,
- NULL,
- toSQLTCHAR(connQStr).data(),
- (SQLSMALLINT)connQStr.length(),
- connOut.data(),
- 1024,
- &cb,
- /*SQL_DRIVER_NOPROMPT*/0);
-
- if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) {
+ QVarLengthArray<SQLTCHAR, 1024> connOut(1024);
+ {
+ auto encoded = toSQLTCHAR(connQStr);
+ r = SQLDriverConnect(d->hDbc,
+ nullptr,
+ encoded.data(), SQLSMALLINT(encoded.size()),
+ connOut.data(), SQLSMALLINT(connOut.size()),
+ &cb,
+ /*SQL_DRIVER_NOPROMPT*/0);
+ }
+
+ if (!SQL_SUCCEEDED(r)) {
setLastError(qMakeError(tr("Unable to connect"), QSqlError::ConnectionError, d));
setOpenError(true);
cleanup();
@@ -2008,6 +2013,7 @@ bool QODBCDriver::open(const QString & db,
d->checkHasSQLFetchScroll();
d->checkHasMultiResults();
d->checkDateTimePrecision();
+ d->checkDefaultCase();
setOpen(true);
setOpenError(false);
if (d->dbmsType == MSSqlServer) {
@@ -2066,7 +2072,7 @@ void QODBCDriverPrivate::checkUnicode()
(SQLPOINTER)&fFunc,
sizeof(fFunc),
NULL);
- if ((r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) && (fFunc & SQL_CVT_WCHAR)) {
+ if (SQL_SUCCEEDED(r) && (fFunc & SQL_CVT_WCHAR)) {
unicode = true;
return;
}
@@ -2076,7 +2082,7 @@ void QODBCDriverPrivate::checkUnicode()
(SQLPOINTER)&fFunc,
sizeof(fFunc),
NULL);
- if ((r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) && (fFunc & SQL_CVT_WVARCHAR)) {
+ if (SQL_SUCCEEDED(r) && (fFunc & SQL_CVT_WVARCHAR)) {
unicode = true;
return;
}
@@ -2086,74 +2092,86 @@ void QODBCDriverPrivate::checkUnicode()
(SQLPOINTER)&fFunc,
sizeof(fFunc),
NULL);
- if ((r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) && (fFunc & SQL_CVT_WLONGVARCHAR)) {
+ if (SQL_SUCCEEDED(r) && (fFunc & SQL_CVT_WLONGVARCHAR)) {
unicode = true;
return;
}
- SQLHANDLE hStmt;
- r = SQLAllocHandle(SQL_HANDLE_STMT,
- hDbc,
- &hStmt);
- r = SQLExecDirect(hStmt, toSQLTCHAR("select 'test'"_L1).data(), SQL_NTS);
+ SqlStmtHandle hStmt(hDbc);
+ // for databases which do not return something useful in SQLGetInfo and are picky about a
+ // 'SELECT' statement without 'FROM' but support VALUE(foo) statement like e.g. DB2 or Oracle
+ const std::array<QStringView, 3> statements = {
+ u"select 'test'",
+ u"values('test')",
+ u"select 'test' from dual",
+ };
+ for (const auto &statement : statements) {
+ auto encoded = toSQLTCHAR(statement);
+ r = SQLExecDirect(hStmt.handle(), encoded.data(), SQLINTEGER(encoded.size()));
+ if (r == SQL_SUCCESS)
+ break;
+ }
if (r == SQL_SUCCESS) {
- r = SQLFetch(hStmt);
+ r = SQLFetch(hStmt.handle());
if (r == SQL_SUCCESS) {
- QVarLengthArray<SQLWCHAR> buffer(10);
- r = SQLGetData(hStmt, 1, SQL_C_WCHAR, buffer.data(), buffer.size() * sizeof(SQLWCHAR), NULL);
+ QVarLengthArray<SQLWCHAR, 10> buffer(10);
+ r = SQLGetData(hStmt.handle(), 1, SQL_C_WCHAR, buffer.data(),
+ buffer.size() * sizeof(SQLWCHAR), NULL);
if (r == SQL_SUCCESS && fromSQLTCHAR(buffer) == "test"_L1) {
unicode = true;
}
}
}
- r = SQLFreeHandle(SQL_HANDLE_STMT, hStmt);
}
bool QODBCDriverPrivate::checkDriver() const
{
#ifdef ODBC_CHECK_DRIVER
- static const SQLUSMALLINT reqFunc[] = {
+ static constexpr SQLUSMALLINT reqFunc[] = {
SQL_API_SQLDESCRIBECOL, SQL_API_SQLGETDATA, SQL_API_SQLCOLUMNS,
SQL_API_SQLGETSTMTATTR, SQL_API_SQLGETDIAGREC, SQL_API_SQLEXECDIRECT,
- SQL_API_SQLGETINFO, SQL_API_SQLTABLES, 0
+ SQL_API_SQLGETINFO, SQL_API_SQLTABLES
};
// these functions are optional
- static const SQLUSMALLINT optFunc[] = {
- SQL_API_SQLNUMRESULTCOLS, SQL_API_SQLROWCOUNT, 0
+ static constexpr SQLUSMALLINT optFunc[] = {
+ SQL_API_SQLNUMRESULTCOLS, SQL_API_SQLROWCOUNT
};
SQLRETURN r;
SQLUSMALLINT sup;
- int i;
// check the required functions
- for (i = 0; reqFunc[i] != 0; ++i) {
+ for (const SQLUSMALLINT func : reqFunc) {
- r = SQLGetFunctions(hDbc, reqFunc[i], &sup);
+ r = SQLGetFunctions(hDbc, func, &sup);
if (r != SQL_SUCCESS) {
qSqlWarning("QODBCDriver::checkDriver: Cannot get list of supported functions"_L1, this);
return false;
}
if (sup == SQL_FALSE) {
- qWarning () << "QODBCDriver::open: Warning - Driver doesn't support all needed functionality (" << reqFunc[i] <<
- ").\nPlease look at the Qt SQL Module Driver documentation for more information.";
+ qSqlWarning(("QODBCDriver::checkDriver: Driver doesn't support all needed "
+ "functionality (func id %1).\nPlease look at the Qt SQL Module "
+ "Driver documentation for more information."_L1)
+ .arg(QString::number(func)), this);
return false;
}
}
// these functions are optional and just generate a warning
- for (i = 0; optFunc[i] != 0; ++i) {
+ for (const SQLUSMALLINT func : optFunc) {
- r = SQLGetFunctions(hDbc, optFunc[i], &sup);
+ r = SQLGetFunctions(hDbc, func, &sup);
if (r != SQL_SUCCESS) {
qSqlWarning("QODBCDriver::checkDriver: Cannot get list of supported functions"_L1, this);
return false;
}
if (sup == SQL_FALSE) {
- qWarning() << "QODBCDriver::checkDriver: Warning - Driver doesn't support some non-critical functions (" << optFunc[i] << ')';
+ qSqlWarning(("QODBCDriver::checkDriver: Driver doesn't support some "
+ "non-critical functions (func id %1)."_L1)
+ .arg(QString::number(func)), this);
return true;
}
}
@@ -2172,23 +2190,22 @@ void QODBCDriverPrivate::checkSchemaUsage()
(SQLPOINTER) &val,
sizeof(val),
NULL);
- if (r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO)
+ if (SQL_SUCCEEDED(r))
useSchema = (val != 0);
}
void QODBCDriverPrivate::checkDBMS()
{
SQLRETURN r;
- QVarLengthArray<SQLTCHAR> serverString(200);
+ QVarLengthArray<SQLTCHAR, 200> serverString(200);
SQLSMALLINT t;
- memset(serverString.data(), 0, serverString.size() * sizeof(SQLTCHAR));
r = SQLGetInfo(hDbc,
SQL_DBMS_NAME,
serverString.data(),
SQLSMALLINT(serverString.size() * sizeof(SQLTCHAR)),
&t);
- if (r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) {
+ if (SQL_SUCCEEDED(r)) {
const QString serverType = fromSQLTCHAR(serverString, t / sizeof(SQLTCHAR));
if (serverType.contains("PostgreSQL"_L1, Qt::CaseInsensitive))
dbmsType = QSqlDriver::PostgreSQL;
@@ -2206,7 +2223,7 @@ void QODBCDriverPrivate::checkDBMS()
serverString.data(),
SQLSMALLINT(serverString.size() * sizeof(SQLTCHAR)),
&t);
- if (r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) {
+ if (SQL_SUCCEEDED(r)) {
const QString serverType = fromSQLTCHAR(serverString, t / sizeof(SQLTCHAR));
isFreeTDSDriver = serverType.contains("tdsodbc"_L1, Qt::CaseInsensitive);
unicode = unicode && !isFreeTDSDriver;
@@ -2217,46 +2234,42 @@ void QODBCDriverPrivate::checkHasSQLFetchScroll()
{
SQLUSMALLINT sup;
SQLRETURN r = SQLGetFunctions(hDbc, SQL_API_SQLFETCHSCROLL, &sup);
- if ((r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO) || sup != SQL_TRUE) {
+ if ((!SQL_SUCCEEDED(r)) || sup != SQL_TRUE) {
hasSQLFetchScroll = false;
- qWarning("QODBCDriver::checkHasSQLFetchScroll: Warning - Driver doesn't support scrollable result sets, use forward only mode for queries");
+ qSqlWarning("QODBCDriver::checkHasSQLFetchScroll: Driver doesn't support "
+ "scrollable result sets, use forward only mode for queries"_L1, this);
}
}
void QODBCDriverPrivate::checkHasMultiResults()
{
- QVarLengthArray<SQLTCHAR> driverResponse(2);
+ QVarLengthArray<SQLTCHAR, 2> driverResponse(2);
SQLSMALLINT length;
SQLRETURN r = SQLGetInfo(hDbc,
SQL_MULT_RESULT_SETS,
driverResponse.data(),
SQLSMALLINT(driverResponse.size() * sizeof(SQLTCHAR)),
&length);
- if (r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO)
- hasMultiResultSets = fromSQLTCHAR(driverResponse, length/sizeof(SQLTCHAR)).startsWith(u'Y');
+ if (SQL_SUCCEEDED(r))
+ hasMultiResultSets = fromSQLTCHAR(driverResponse, length / sizeof(SQLTCHAR)).startsWith(u'Y');
}
void QODBCDriverPrivate::checkDateTimePrecision()
{
SQLINTEGER columnSize;
- SQLHANDLE hStmt;
+ SqlStmtHandle hStmt(hDbc);
- SQLRETURN r = SQLAllocHandle(SQL_HANDLE_STMT, hDbc, &hStmt);
- if (r != SQL_SUCCESS) {
+ if (!hStmt.isValid())
return;
- }
- r = SQLGetTypeInfo(hStmt, SQL_TIMESTAMP);
- if (r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) {
- r = SQLFetch(hStmt);
- if ( r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO )
- {
- if (SQLGetData(hStmt, 3, SQL_INTEGER, &columnSize, sizeof(columnSize), 0) == SQL_SUCCESS) {
+ SQLRETURN r = SQLGetTypeInfo(hStmt.handle(), SQL_TIMESTAMP);
+ if (SQL_SUCCEEDED(r)) {
+ r = SQLFetch(hStmt.handle());
+ if (SQL_SUCCEEDED(r)) {
+ if (SQLGetData(hStmt.handle(), 3, SQL_INTEGER, &columnSize, sizeof(columnSize), 0) == SQL_SUCCESS)
datetimePrecision = (int)columnSize;
- }
}
}
- SQLFreeHandle(SQL_HANDLE_STMT, hStmt);
}
QSqlResult *QODBCDriver::createResult() const
@@ -2268,7 +2281,7 @@ bool QODBCDriver::beginTransaction()
{
Q_D(QODBCDriver);
if (!isOpen()) {
- qWarning("QODBCDriver::beginTransaction: Database not open");
+ qSqlWarning("QODBCDriver::beginTransaction: Database not open"_L1, d);
return false;
}
SQLUINTEGER ac(SQL_AUTOCOMMIT_OFF);
@@ -2288,7 +2301,7 @@ bool QODBCDriver::commitTransaction()
{
Q_D(QODBCDriver);
if (!isOpen()) {
- qWarning("QODBCDriver::commitTransaction: Database not open");
+ qSqlWarning("QODBCDriver::commitTransaction: Database not open"_L1, d);
return false;
}
SQLRETURN r = SQLEndTran(SQL_HANDLE_DBC,
@@ -2306,7 +2319,7 @@ bool QODBCDriver::rollbackTransaction()
{
Q_D(QODBCDriver);
if (!isOpen()) {
- qWarning("QODBCDriver::rollbackTransaction: Database not open");
+ qSqlWarning("QODBCDriver::rollbackTransaction: Database not open"_L1, d);
return false;
}
SQLRETURN r = SQLEndTran(SQL_HANDLE_DBC,
@@ -2341,19 +2354,16 @@ QStringList QODBCDriver::tables(QSql::TableType type) const
QStringList tl;
if (!isOpen())
return tl;
- SQLHANDLE hStmt;
- SQLRETURN r = SQLAllocHandle(SQL_HANDLE_STMT,
- d->hDbc,
- &hStmt);
- if (r != SQL_SUCCESS) {
+ SqlStmtHandle hStmt(d->hDbc);
+ if (!hStmt.isValid()) {
qSqlWarning("QODBCDriver::tables: Unable to allocate handle"_L1, d);
return tl;
}
- r = SQLSetStmtAttr(hStmt,
- SQL_ATTR_CURSOR_TYPE,
- (SQLPOINTER)SQL_CURSOR_FORWARD_ONLY,
- SQL_IS_UINTEGER);
+ SQLRETURN r = SQLSetStmtAttr(hStmt.handle(),
+ SQL_ATTR_CURSOR_TYPE,
+ (SQLPOINTER)SQL_CURSOR_FORWARD_ONLY,
+ SQL_IS_UINTEGER);
QStringList tableType;
if (type & QSql::Tables)
tableType += "TABLE"_L1;
@@ -2364,48 +2374,32 @@ QStringList QODBCDriver::tables(QSql::TableType type) const
if (tableType.isEmpty())
return tl;
- QString joinedTableTypeString = tableType.join(u',');
+ {
+ auto joinedTableTypeString = toSQLTCHAR(tableType.join(u','));
- r = SQLTables(hStmt,
- NULL,
- 0,
- NULL,
- 0,
- NULL,
- 0,
- toSQLTCHAR(joinedTableTypeString).data(),
- joinedTableTypeString.length() /* characters, not bytes */);
+ r = SQLTables(hStmt.handle(),
+ nullptr, 0,
+ nullptr, 0,
+ nullptr, 0,
+ joinedTableTypeString.data(), joinedTableTypeString.size());
+ }
if (r != SQL_SUCCESS)
- qSqlWarning("QODBCDriver::tables Unable to execute table list"_L1, d);
+ qSqlWarning("QODBCDriver::tables Unable to execute table list"_L1,
+ hStmt.handle());
- if (d->hasSQLFetchScroll)
- r = SQLFetchScroll(hStmt,
- SQL_FETCH_NEXT,
- 0);
- else
- r = SQLFetch(hStmt);
-
- if (r != SQL_SUCCESS && r != SQL_SUCCESS_WITH_INFO && r != SQL_NO_DATA) {
- qWarning() << "QODBCDriver::tables failed to retrieve table/view list: (" << r << "," << qWarnODBCHandle(SQL_HANDLE_STMT, hStmt) << ")";
+ r = d->sqlFetchNext(hStmt);
+ if (!SQL_SUCCEEDED(r) && r != SQL_NO_DATA) {
+ qSqlWarning("QODBCDriver::tables failed to retrieve table/view list"_L1,
+ hStmt.handle());
return QStringList();
}
while (r == SQL_SUCCESS) {
- QString fieldVal = qGetStringData(hStmt, 2, -1, d->unicode);
- tl.append(fieldVal);
-
- if (d->hasSQLFetchScroll)
- r = SQLFetchScroll(hStmt,
- SQL_FETCH_NEXT,
- 0);
- else
- r = SQLFetch(hStmt);
+ tl.append(qGetStringData(hStmt.handle(), 2, -1, d->unicode).toString());
+ r = d->sqlFetchNext(hStmt);
}
- r = SQLFreeHandle(SQL_HANDLE_STMT, hStmt);
- if (r!= SQL_SUCCESS)
- qSqlWarning("QODBCDriver: Unable to free statement handle"_L1 + QString::number(r), d);
return tl;
}
@@ -2418,98 +2412,69 @@ QSqlIndex QODBCDriver::primaryIndex(const QString& tablename) const
bool usingSpecialColumns = false;
QSqlRecord rec = record(tablename);
- SQLHANDLE hStmt;
- SQLRETURN r = SQLAllocHandle(SQL_HANDLE_STMT,
- d->hDbc,
- &hStmt);
- if (r != SQL_SUCCESS) {
- qSqlWarning("QODBCDriver::primaryIndex: Unable to list primary key"_L1, d);
+ SqlStmtHandle hStmt(d->hDbc);
+ if (!hStmt.isValid()) {
+ qSqlWarning("QODBCDriver::primaryIndex: Unable to allocate handle"_L1, d);
return index;
}
QString catalog, schema, table;
- const_cast<QODBCDriverPrivate*>(d)->splitTableQualifier(tablename, catalog, schema, table);
+ d->splitTableQualifier(tablename, catalog, schema, table);
- if (isIdentifierEscaped(catalog, QSqlDriver::TableName))
- catalog = stripDelimiters(catalog, QSqlDriver::TableName);
- else
- catalog = d->adjustCase(catalog);
-
- if (isIdentifierEscaped(schema, QSqlDriver::TableName))
- schema = stripDelimiters(schema, QSqlDriver::TableName);
- else
- schema = d->adjustCase(schema);
-
- if (isIdentifierEscaped(table, QSqlDriver::TableName))
- table = stripDelimiters(table, QSqlDriver::TableName);
- else
- table = d->adjustCase(table);
-
- r = SQLSetStmtAttr(hStmt,
- SQL_ATTR_CURSOR_TYPE,
- (SQLPOINTER)SQL_CURSOR_FORWARD_ONLY,
- SQL_IS_UINTEGER);
- r = SQLPrimaryKeys(hStmt,
- catalog.length() == 0 ? NULL : toSQLTCHAR(catalog).data(),
- catalog.length(),
- schema.length() == 0 ? NULL : toSQLTCHAR(schema).data(),
- schema.length(),
- toSQLTCHAR(table).data(),
- table.length() /* in characters, not in bytes */);
+ SQLRETURN r = SQLSetStmtAttr(hStmt.handle(),
+ SQL_ATTR_CURSOR_TYPE,
+ (SQLPOINTER)SQL_CURSOR_FORWARD_ONLY,
+ SQL_IS_UINTEGER);
+ {
+ auto c = toSQLTCHAR(catalog);
+ auto s = toSQLTCHAR(schema);
+ auto t = toSQLTCHAR(table);
+ r = SQLPrimaryKeys(hStmt.handle(),
+ catalog.isEmpty() ? nullptr : c.data(), c.size(),
+ schema.isEmpty() ? nullptr : s.data(), s.size(),
+ t.data(), t.size());
+ }
// if the SQLPrimaryKeys() call does not succeed (e.g the driver
// does not support it) - try an alternative method to get hold of
// the primary index (e.g MS Access and FoxPro)
if (r != SQL_SUCCESS) {
- r = SQLSpecialColumns(hStmt,
- SQL_BEST_ROWID,
- catalog.length() == 0 ? NULL : toSQLTCHAR(catalog).data(),
- catalog.length(),
- schema.length() == 0 ? NULL : toSQLTCHAR(schema).data(),
- schema.length(),
- toSQLTCHAR(table).data(),
- table.length(),
- SQL_SCOPE_CURROW,
- SQL_NULLABLE);
+ auto c = toSQLTCHAR(catalog);
+ auto s = toSQLTCHAR(schema);
+ auto t = toSQLTCHAR(table);
+ r = SQLSpecialColumns(hStmt.handle(),
+ SQL_BEST_ROWID,
+ catalog.isEmpty() ? nullptr : c.data(), c.size(),
+ schema.isEmpty() ? nullptr : s.data(), s.size(),
+ t.data(), t.size(),
+ SQL_SCOPE_CURROW,
+ SQL_NULLABLE);
if (r != SQL_SUCCESS) {
- qSqlWarning("QODBCDriver::primaryIndex: Unable to execute primary key list"_L1, d);
+ qSqlWarning("QODBCDriver::primaryIndex: Unable to execute primary key list"_L1,
+ hStmt.handle());
} else {
usingSpecialColumns = true;
}
}
- if (d->hasSQLFetchScroll)
- r = SQLFetchScroll(hStmt,
- SQL_FETCH_NEXT,
- 0);
- else
- r = SQLFetch(hStmt);
+ r = d->sqlFetchNext(hStmt);
int fakeId = 0;
QString cName, idxName;
// Store all fields in a StringList because some drivers can't detail fields in this FETCH loop
while (r == SQL_SUCCESS) {
if (usingSpecialColumns) {
- cName = qGetStringData(hStmt, 1, -1, d->unicode); // column name
+ cName = qGetStringData(hStmt.handle(), 1, -1, d->unicode).toString(); // column name
idxName = QString::number(fakeId++); // invent a fake index name
} else {
- cName = qGetStringData(hStmt, 3, -1, d->unicode); // column name
- idxName = qGetStringData(hStmt, 5, -1, d->unicode); // pk index name
+ cName = qGetStringData(hStmt.handle(), 3, -1, d->unicode).toString(); // column name
+ idxName = qGetStringData(hStmt.handle(), 5, -1, d->unicode).toString(); // pk index name
}
index.append(rec.field(cName));
index.setName(idxName);
- if (d->hasSQLFetchScroll)
- r = SQLFetchScroll(hStmt,
- SQL_FETCH_NEXT,
- 0);
- else
- r = SQLFetch(hStmt);
-
+ r = d->sqlFetchNext(hStmt);
}
- r = SQLFreeHandle(SQL_HANDLE_STMT, hStmt);
- if (r!= SQL_SUCCESS)
- qSqlWarning("QODBCDriver: Unable to free statement handle"_L1 + QString::number(r), d);
return index;
}
@@ -2520,72 +2485,39 @@ QSqlRecord QODBCDriver::record(const QString& tablename) const
if (!isOpen())
return fil;
- SQLHANDLE hStmt;
- QString catalog, schema, table;
- const_cast<QODBCDriverPrivate*>(d)->splitTableQualifier(tablename, catalog, schema, table);
-
- if (isIdentifierEscaped(catalog, QSqlDriver::TableName))
- catalog = stripDelimiters(catalog, QSqlDriver::TableName);
- else
- catalog = d->adjustCase(catalog);
-
- if (isIdentifierEscaped(schema, QSqlDriver::TableName))
- schema = stripDelimiters(schema, QSqlDriver::TableName);
- else
- schema = d->adjustCase(schema);
-
- if (isIdentifierEscaped(table, QSqlDriver::TableName))
- table = stripDelimiters(table, QSqlDriver::TableName);
- else
- table = d->adjustCase(table);
-
- SQLRETURN r = SQLAllocHandle(SQL_HANDLE_STMT,
- d->hDbc,
- &hStmt);
- if (r != SQL_SUCCESS) {
+ SqlStmtHandle hStmt(d->hDbc);
+ if (!hStmt.isValid()) {
qSqlWarning("QODBCDriver::record: Unable to allocate handle"_L1, d);
return fil;
}
- r = SQLSetStmtAttr(hStmt,
- SQL_ATTR_CURSOR_TYPE,
- (SQLPOINTER)SQL_CURSOR_FORWARD_ONLY,
- SQL_IS_UINTEGER);
- r = SQLColumns(hStmt,
- catalog.length() == 0 ? NULL : toSQLTCHAR(catalog).data(),
- catalog.length(),
- schema.length() == 0 ? NULL : toSQLTCHAR(schema).data(),
- schema.length(),
- toSQLTCHAR(table).data(),
- table.length(),
- NULL,
- 0);
- if (r != SQL_SUCCESS)
- qSqlWarning("QODBCDriver::record: Unable to execute column list"_L1, d);
- if (d->hasSQLFetchScroll)
- r = SQLFetchScroll(hStmt,
- SQL_FETCH_NEXT,
- 0);
- else
- r = SQLFetch(hStmt);
+ QString catalog, schema, table;
+ d->splitTableQualifier(tablename, catalog, schema, table);
+
+ SQLRETURN r = SQLSetStmtAttr(hStmt.handle(),
+ SQL_ATTR_CURSOR_TYPE,
+ (SQLPOINTER)SQL_CURSOR_FORWARD_ONLY,
+ SQL_IS_UINTEGER);
+ {
+ auto c = toSQLTCHAR(catalog);
+ auto s = toSQLTCHAR(schema);
+ auto t = toSQLTCHAR(table);
+ r = SQLColumns(hStmt.handle(),
+ catalog.isEmpty() ? nullptr : c.data(), c.size(),
+ schema.isEmpty() ? nullptr : s.data(), s.size(),
+ t.data(), t.size(),
+ nullptr,
+ 0);
+ }
+ if (r != SQL_SUCCESS)
+ qSqlWarning("QODBCDriver::record: Unable to execute column list"_L1, hStmt.handle());
+ r = d->sqlFetchNext(hStmt);
// Store all fields in a StringList because some drivers can't detail fields in this FETCH loop
while (r == SQL_SUCCESS) {
-
- fil.append(qMakeFieldInfo(hStmt, d));
-
- if (d->hasSQLFetchScroll)
- r = SQLFetchScroll(hStmt,
- SQL_FETCH_NEXT,
- 0);
- else
- r = SQLFetch(hStmt);
+ fil.append(qMakeFieldInfo(hStmt.handle(), d));
+ r = d->sqlFetchNext(hStmt);
}
-
- r = SQLFreeHandle(SQL_HANDLE_STMT, hStmt);
- if (r!= SQL_SUCCESS)
- qSqlWarning("QODBCDriver: Unable to free statement handle "_L1 + QString::number(r), d);
-
return fil;
}
@@ -2597,9 +2529,10 @@ QString QODBCDriver::formatValue(const QSqlField &field,
r = "NULL"_L1;
} else if (field.metaType().id() == QMetaType::QDateTime) {
// Use an escape sequence for the datetime fields
- if (field.value().toDateTime().isValid()){
- QDate dt = field.value().toDateTime().date();
- QTime tm = field.value().toDateTime().time();
+ const QDateTime dateTime = field.value().toDateTime();
+ if (dateTime.isValid()) {
+ const QDate dt = dateTime.date();
+ const QTime tm = dateTime.time();
// Dateformat has to be "yyyy-MM-dd hh:mm:ss", with leading zeroes if month or day < 10
r = "{ ts '"_L1 +
QString::number(dt.year()) + u'-' +
@@ -2612,15 +2545,14 @@ QString QODBCDriver::formatValue(const QSqlField &field,
} else
r = "NULL"_L1;
} else if (field.metaType().id() == QMetaType::QByteArray) {
- QByteArray ba = field.value().toByteArray();
- QString res;
- static const char hexchars[] = "0123456789abcdef";
- for (int i = 0; i < ba.size(); ++i) {
- uchar s = (uchar) ba[i];
- res += QLatin1Char(hexchars[s >> 4]);
- res += QLatin1Char(hexchars[s & 0x0f]);
+ const QByteArray ba = field.value().toByteArray();
+ r.reserve((ba.size() + 1) * 2);
+ r = "0x"_L1;
+ for (const char c : ba) {
+ const uchar s = uchar(c);
+ r += QLatin1Char(QtMiscUtils::toHexLower(s >> 4));
+ r += QLatin1Char(QtMiscUtils::toHexLower(s & 0x0f));
}
- r = "0x"_L1 + res;
} else {
r = QSqlDriver::formatValue(field, trimStrings);
}
@@ -2639,9 +2571,10 @@ QString QODBCDriver::escapeIdentifier(const QString &identifier, IdentifierType)
QChar quote = const_cast<QODBCDriverPrivate*>(d)->quoteChar();
QString res = identifier;
if (!identifier.isEmpty() && !identifier.startsWith(quote) && !identifier.endsWith(quote) ) {
- res.replace(quote, QString(quote)+QString(quote));
- res.prepend(quote).append(quote);
- res.replace(u'.', QString(quote) + u'.' +QString(quote));
+ const QString quoteStr(quote);
+ res.replace(quote, quoteStr + quoteStr);
+ res.replace(u'.', quoteStr + u'.' + quoteStr);
+ res = quote + res + quote;
}
return res;
}
diff --git a/src/plugins/sqldrivers/psql/CMakeLists.txt b/src/plugins/sqldrivers/psql/CMakeLists.txt
index 59d5882479..2f55ab4950 100644
--- a/src/plugins/sqldrivers/psql/CMakeLists.txt
+++ b/src/plugins/sqldrivers/psql/CMakeLists.txt
@@ -1,9 +1,7 @@
# Copyright (C) 2022 The Qt Company Ltd.
-# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+# SPDX-License-Identifier: BSD-3-Clause
-# Generated from psql.pro.
-
-qt_find_package(PostgreSQL) # special case
+qt_find_package(PostgreSQL)
#####################################################################
## QPSQLDriverPlugin Plugin:
@@ -18,6 +16,7 @@ qt_internal_add_plugin(QPSQLDriverPlugin
DEFINES
QT_NO_CAST_FROM_ASCII
QT_NO_CAST_TO_ASCII
+ QT_NO_CONTEXTLESS_CONNECT
LIBRARIES
PostgreSQL::PostgreSQL
Qt::Core
@@ -25,9 +24,6 @@ qt_internal_add_plugin(QPSQLDriverPlugin
Qt::SqlPrivate
)
-#### Keys ignored in scope 1:.:.:psql.pro:<TRUE>:
-# OTHER_FILES = "psql.json"
-
# PostgreSQL delivers header files that are not a part of PostgreSQL itself. When precompiled
# headers are processed, MinGW uses 'pthread.h' from the PostgreSQL installation directory.
# As result, we disable precompile headers for the plugin.
diff --git a/src/plugins/sqldrivers/psql/qsql_psql.cpp b/src/plugins/sqldrivers/psql/qsql_psql.cpp
index 7365d71695..7b9521fec6 100644
--- a/src/plugins/sqldrivers/psql/qsql_psql.cpp
+++ b/src/plugins/sqldrivers/psql/qsql_psql.cpp
@@ -6,6 +6,7 @@
#include <qcoreapplication.h>
#include <qvariant.h>
#include <qdatetime.h>
+#include <qloggingcategory.h>
#include <qregularexpression.h>
#include <qsqlerror.h>
#include <qsqlfield.h>
@@ -65,6 +66,8 @@ Q_DECLARE_METATYPE(PGresult*)
QT_BEGIN_NAMESPACE
+static Q_LOGGING_CATEGORY(lcPsql, "qt.sql.postgresql")
+
using namespace Qt::StringLiterals;
inline void qPQfreemem(void *buffer)
@@ -74,11 +77,11 @@ inline void qPQfreemem(void *buffer)
/* Missing declaration of PGRES_SINGLE_TUPLE for PSQL below 9.2 */
#if !defined PG_VERSION_NUM || PG_VERSION_NUM-0 < 90200
-static const int PGRES_SINGLE_TUPLE = 9;
+static constexpr int PGRES_SINGLE_TUPLE = 9;
#endif
typedef int StatementId;
-static const StatementId InvalidStatementId = 0;
+static constexpr StatementId InvalidStatementId = 0;
class QPSQLResultPrivate;
@@ -122,10 +125,9 @@ public:
QSocketNotifier *sn = nullptr;
QPSQLDriver::Protocol pro = QPSQLDriver::Version6;
StatementId currentStmtId = InvalidStatementId;
- int stmtCount = 0;
+ StatementId stmtCount = InvalidStatementId;
mutable bool pendingNotifyCheck = false;
bool hasBackslashEscape = false;
- bool isUtf8 = false;
void appendTables(QStringList &tl, QSqlQuery &t, QChar type);
PGresult *exec(const char *stmt);
@@ -141,6 +143,7 @@ public:
bool setEncodingUtf8();
void setDatestyle();
void setByteaOutput();
+ void setUtcTimeZone();
void detectBackslashEscape();
mutable QHash<int, QString> oidToTable;
};
@@ -175,7 +178,7 @@ PGresult *QPSQLDriverPrivate::exec(const char *stmt)
PGresult *QPSQLDriverPrivate::exec(const QString &stmt)
{
- return exec((isUtf8 ? stmt.toUtf8() : stmt.toLocal8Bit()).constData());
+ return exec(stmt.toUtf8().constData());
}
StatementId QPSQLDriverPrivate::sendQuery(const QString &stmt)
@@ -183,8 +186,7 @@ StatementId QPSQLDriverPrivate::sendQuery(const QString &stmt)
// Discard any prior query results that the application didn't eat.
// This is required for PQsendQuery()
discardResults();
- const int result = PQsendQuery(connection,
- (isUtf8 ? stmt.toUtf8() : stmt.toLocal8Bit()).constData());
+ const int result = PQsendQuery(connection, stmt.toUtf8().constData());
currentStmtId = result ? generateStatementId() : InvalidStatementId;
return currentStmtId;
}
@@ -209,8 +211,8 @@ PGresult *QPSQLDriverPrivate::getResult(StatementId stmtId) const
if (stmtId != currentStmtId) {
// If you change the following warning, remember to update it
// on sql-driver.html page too.
- qWarning("QPSQLDriver::getResult: Query results lost - "
- "probably discarded on executing another SQL query.");
+ qCWarning(lcPsql, "QPSQLDriver::getResult: Query results lost - "
+ "probably discarded on executing another SQL query.");
return nullptr;
}
PGresult *result = PQgetResult(connection);
@@ -234,7 +236,7 @@ void QPSQLDriverPrivate::discardResults() const
StatementId QPSQLDriverPrivate::generateStatementId()
{
- int stmtId = ++stmtCount;
+ StatementId stmtId = ++stmtCount;
if (stmtId <= 0)
stmtId = stmtCount = 1;
return stmtId;
@@ -245,18 +247,18 @@ void QPSQLDriverPrivate::checkPendingNotifications() const
Q_Q(const QPSQLDriver);
if (seid.size() && !pendingNotifyCheck) {
pendingNotifyCheck = true;
- QMetaObject::invokeMethod(const_cast<QPSQLDriver*>(q), "_q_handleNotification", Qt::QueuedConnection);
+ QMetaObject::invokeMethod(const_cast<QPSQLDriver*>(q), &QPSQLDriver::_q_handleNotification, Qt::QueuedConnection);
}
}
-class QPSQLResultPrivate : public QSqlResultPrivate
+class QPSQLResultPrivate final : public QSqlResultPrivate
{
Q_DECLARE_PUBLIC(QPSQLResult)
public:
Q_DECLARE_SQLDRIVER_PRIVATE(QPSQLDriver)
using QSqlResultPrivate::QSqlResultPrivate;
- QString fieldSerial(int i) const override { return u'$' + QString::number(i + 1); }
+ QString fieldSerial(qsizetype i) const override { return QString("$%1"_L1).arg(i + 1); }
void deallocatePreparedStmt();
std::queue<PGresult*> nextResultSets;
@@ -274,7 +276,7 @@ static QSqlError qMakeError(const QString &err, QSqlError::ErrorType type,
const QPSQLDriverPrivate *p, PGresult *result = nullptr)
{
const char *s = PQerrorMessage(p->connection);
- QString msg = p->isUtf8 ? QString::fromUtf8(s) : QString::fromLocal8Bit(s);
+ QString msg = QString::fromUtf8(s);
QString errorCode;
if (result) {
errorCode = QString::fromLatin1(PQresultErrorField(result, PG_DIAG_SQLSTATE));
@@ -382,8 +384,10 @@ void QPSQLResultPrivate::deallocatePreparedStmt()
const QString stmt = QStringLiteral("DEALLOCATE ") + preparedStmtId;
PGresult *result = drv_d_func()->exec(stmt);
- if (PQresultStatus(result) != PGRES_COMMAND_OK)
- qWarning("Unable to free statement: %s", PQerrorMessage(drv_d_func()->connection));
+ if (PQresultStatus(result) != PGRES_COMMAND_OK) {
+ const QString msg = QString::fromUtf8(PQerrorMessage(drv_d_func()->connection));
+ qCWarning(lcPsql, "Unable to free statement: %ls.", qUtf16Printable(msg));
+ }
PQclear(result);
}
preparedStmtId.clear();
@@ -593,7 +597,7 @@ QVariant QPSQLResult::data(int i)
{
Q_D(const QPSQLResult);
if (i >= PQnfields(d->result)) {
- qWarning("QPSQLResult::data: column %d out of range", i);
+ qCWarning(lcPsql, "QPSQLResult::data: column %d out of range.", i);
return QVariant();
}
const int currentRow = isForwardOnly() ? 0 : at();
@@ -606,7 +610,7 @@ QVariant QPSQLResult::data(int i)
case QMetaType::Bool:
return QVariant((bool)(val[0] == 't'));
case QMetaType::QString:
- return d->drv_d_func()->isUtf8 ? QString::fromUtf8(val) : QString::fromLatin1(val);
+ return QString::fromUtf8(val);
case QMetaType::LongLong:
if (val[0] == '-')
return QByteArray::fromRawData(val, qstrlen(val)).toLongLong();
@@ -641,23 +645,21 @@ QVariant QPSQLResult::data(int i)
}
return dbl;
}
- case QMetaType::QDate:
#if QT_CONFIG(datestring)
+ case QMetaType::QDate:
return QVariant(QDate::fromString(QString::fromLatin1(val), Qt::ISODate));
-#else
- return QVariant(QString::fromLatin1(val));
-#endif
case QMetaType::QTime:
-#if QT_CONFIG(datestring)
return QVariant(QTime::fromString(QString::fromLatin1(val), Qt::ISODate));
+ case QMetaType::QDateTime: {
+ QString tzString(QString::fromLatin1(val));
+ if (!tzString.endsWith(u'Z'))
+ tzString.append(u'Z'); // make UTC
+ return QVariant(QDateTime::fromString(tzString, Qt::ISODate));
+ }
#else
- return QVariant(QString::fromLatin1(val));
-#endif
+ case QMetaType::QDate:
+ case QMetaType::QTime:
case QMetaType::QDateTime:
-#if QT_CONFIG(datestring)
- return QVariant(QDateTime::fromString(QString::fromLatin1(val),
- Qt::ISODate).toLocalTime());
-#else
return QVariant(QString::fromLatin1(val));
#endif
case QMetaType::QByteArray: {
@@ -668,7 +670,7 @@ QVariant QPSQLResult::data(int i)
return QVariant(ba);
}
default:
- qWarning("QPSQLResult::data: unknown data type");
+ qCWarning(lcPsql, "QPSQLResult::data: unhandled data type %d.", type.id());
}
return QVariant();
}
@@ -747,10 +749,7 @@ QSqlRecord QPSQLResult::record() const
int count = PQnfields(d->result);
QSqlField f;
for (int i = 0; i < count; ++i) {
- if (d->drv_d_func()->isUtf8)
- f.setName(QString::fromUtf8(PQfname(d->result, i)));
- else
- f.setName(QString::fromLocal8Bit(PQfname(d->result, i)));
+ f.setName(QString::fromUtf8(PQfname(d->result, i)));
const int tableOid = PQftable(d->result, i);
// WARNING: We cannot execute any other SQL queries on
// the same db connection while forward-only mode is active
@@ -801,7 +800,6 @@ QSqlRecord QPSQLResult::record() const
f.setLength(len);
f.setPrecision(precision);
- f.setSqlType(ptype);
info.append(f);
}
return info;
@@ -835,7 +833,7 @@ static QString qCreateParamString(const QList<QVariant> &boundValues, const QSql
QString qMakePreparedStmtId()
{
- static QBasicAtomicInt qPreparedStmtCount = Q_BASIC_ATOMIC_INITIALIZER(0);
+ Q_CONSTINIT static QBasicAtomicInt qPreparedStmtCount = Q_BASIC_ATOMIC_INITIALIZER(0);
QString id = QStringLiteral("qpsqlpstmt_") + QString::number(qPreparedStmtCount.fetchAndAddRelaxed(1) + 1, 16);
return id;
}
@@ -918,7 +916,7 @@ void QPSQLDriverPrivate::setDatestyle()
PGresult *result = exec("SET DATESTYLE TO 'ISO'");
int status = PQresultStatus(result);
if (status != PGRES_COMMAND_OK)
- qWarning("%s", PQerrorMessage(connection));
+ qCWarning(lcPsql) << QString::fromUtf8(PQerrorMessage(connection));
PQclear(result);
}
@@ -931,11 +929,20 @@ void QPSQLDriverPrivate::setByteaOutput()
PGresult *result = exec("SET bytea_output TO escape");
int status = PQresultStatus(result);
if (status != PGRES_COMMAND_OK)
- qWarning("%s", PQerrorMessage(connection));
+ qCWarning(lcPsql) << QString::fromUtf8(PQerrorMessage(connection));
PQclear(result);
}
}
+void QPSQLDriverPrivate::setUtcTimeZone()
+{
+ PGresult *result = exec("SET TIME ZONE 'UTC'");
+ int status = PQresultStatus(result);
+ if (status != PGRES_COMMAND_OK)
+ qCWarning(lcPsql) << QString::fromUtf8(PQerrorMessage(connection));
+ PQclear(result);
+}
+
void QPSQLDriverPrivate::detectBackslashEscape()
{
// standard_conforming_strings option introduced in 8.2
@@ -1069,16 +1076,16 @@ QPSQLDriver::Protocol QPSQLDriverPrivate::getPSQLVersion()
if (serverVersion == QPSQLDriver::VersionUnknown) {
serverVersion = clientVersion;
if (serverVersion != QPSQLDriver::VersionUnknown)
- qWarning("The server version of this PostgreSQL is unknown, falling back to the client version.");
+ qCWarning(lcPsql, "The server version of this PostgreSQL is unknown, "
+ "falling back to the client version.");
}
// Keep the old behavior unchanged
if (serverVersion == QPSQLDriver::VersionUnknown)
serverVersion = QPSQLDriver::Version6;
- if (serverVersion < QPSQLDriver::Version7_3) {
- qWarning("This version of PostgreSQL is not supported and may not work.");
- }
+ if (serverVersion < QPSQLDriver::Version7_3)
+ qCWarning(lcPsql, "This version of PostgreSQL is not supported and may not work.");
return serverVersion;
}
@@ -1104,8 +1111,7 @@ QPSQLDriver::QPSQLDriver(PGconn *conn, QObject *parent)
QPSQLDriver::~QPSQLDriver()
{
Q_D(QPSQLDriver);
- if (d->connection)
- PQfinish(d->connection);
+ PQfinish(d->connection);
}
QVariant QPSQLDriver::handle() const
@@ -1125,6 +1131,7 @@ bool QPSQLDriver::hasFeature(DriverFeature f) const
case EventNotifications:
case MultipleResultSets:
case BLOB:
+ case Unicode:
return true;
case PreparedQueries:
case PositionalPlaceholders:
@@ -1135,8 +1142,6 @@ bool QPSQLDriver::hasFeature(DriverFeature f) const
case FinishQuery:
case CancelQuery:
return false;
- case Unicode:
- return d->isUtf8;
}
return false;
}
@@ -1194,9 +1199,16 @@ bool QPSQLDriver::open(const QString &db,
d->pro = d->getPSQLVersion();
d->detectBackslashEscape();
- d->isUtf8 = d->setEncodingUtf8();
+ if (!d->setEncodingUtf8()) {
+ setLastError(qMakeError(tr("Unable to set client encoding to 'UNICODE'"), QSqlError::ConnectionError, d));
+ setOpenError(true);
+ PQfinish(d->connection);
+ d->connection = nullptr;
+ return false;
+ }
d->setDatestyle();
d->setByteaOutput();
+ d->setUtcTimeZone();
setOpen(true);
setOpenError(false);
@@ -1209,13 +1221,12 @@ void QPSQLDriver::close()
d->seid.clear();
if (d->sn) {
- disconnect(d->sn, SIGNAL(activated(QSocketDescriptor)), this, SLOT(_q_handleNotification()));
+ disconnect(d->sn, &QSocketNotifier::activated, this, &QPSQLDriver::_q_handleNotification);
delete d->sn;
d->sn = nullptr;
}
- if (d->connection)
- PQfinish(d->connection);
+ PQfinish(d->connection);
d->connection = nullptr;
setOpen(false);
setOpenError(false);
@@ -1230,7 +1241,7 @@ bool QPSQLDriver::beginTransaction()
{
Q_D(QPSQLDriver);
if (!isOpen()) {
- qWarning("QPSQLDriver::beginTransaction: Database not open");
+ qCWarning(lcPsql, "QPSQLDriver::beginTransaction: Database not open.");
return false;
}
PGresult *res = d->exec("BEGIN");
@@ -1248,7 +1259,7 @@ bool QPSQLDriver::commitTransaction()
{
Q_D(QPSQLDriver);
if (!isOpen()) {
- qWarning("QPSQLDriver::commitTransaction: Database not open");
+ qCWarning(lcPsql, "QPSQLDriver::commitTransaction: Database not open.");
return false;
}
PGresult *res = d->exec("COMMIT");
@@ -1277,7 +1288,7 @@ bool QPSQLDriver::rollbackTransaction()
{
Q_D(QPSQLDriver);
if (!isOpen()) {
- qWarning("QPSQLDriver::rollbackTransaction: Database not open");
+ qCWarning(lcPsql, "QPSQLDriver::rollbackTransaction: Database not open.");
return false;
}
PGresult *res = d->exec("ROLLBACK");
@@ -1414,7 +1425,6 @@ QSqlRecord QPSQLDriver::record(const QString &tablename) const
f.setLength(len);
f.setPrecision(precision);
f.setDefaultValue(defVal);
- f.setSqlType(query.value(1).toInt());
info.append(f);
}
@@ -1439,32 +1449,28 @@ QString QPSQLDriver::formatValue(const QSqlField &field, bool trimStrings) const
r = nullStr();
} else {
switch (field.metaType().id()) {
- case QMetaType::QDateTime:
-#if QT_CONFIG(datestring)
- if (field.value().toDateTime().isValid()) {
+ case QMetaType::QDateTime: {
+ const auto dt = field.value().toDateTime();
+ if (dt.isValid()) {
// we force the value to be considered with a timezone information, and we force it to be UTC
// this is safe since postgresql stores only the UTC value and not the timezone offset (only used
// while parsing), so we have correct behavior in both case of with timezone and without tz
r = QStringLiteral("TIMESTAMP WITH TIME ZONE ") + u'\'' +
- QLocale::c().toString(field.value().toDateTime().toUTC(), u"yyyy-MM-ddThh:mm:ss.zzz") +
+ QLocale::c().toString(dt.toUTC(), u"yyyy-MM-ddThh:mm:ss.zzz") +
u'Z' + u'\'';
} else {
r = nullStr();
}
-#else
- r = nullStr();
-#endif // datestring
break;
- case QMetaType::QTime:
-#if QT_CONFIG(datestring)
- if (field.value().toTime().isValid()) {
- r = u'\'' + field.value().toTime().toString(u"hh:mm:ss.zzz") + u'\'';
- } else
-#endif
- {
+ }
+ case QMetaType::QTime: {
+ const auto t = field.value().toTime();
+ if (t.isValid())
+ r = u'\'' + QLocale::c().toString(t, u"hh:mm:ss.zzz") + u'\'';
+ else
r = nullStr();
- }
break;
+ }
case QMetaType::QString:
r = QSqlDriver::formatValue(field, trimStrings);
if (d->hasBackslashEscape)
@@ -1516,8 +1522,8 @@ QString QPSQLDriver::escapeIdentifier(const QString &identifier, IdentifierType)
QString res = identifier;
if (!identifier.isEmpty() && !identifier.startsWith(u'"') && !identifier.endsWith(u'"') ) {
res.replace(u'"', "\"\""_L1);
- res.prepend(u'"').append(u'"');
res.replace(u'.', "\".\""_L1);
+ res = u'"' + res + u'"';
}
return res;
}
@@ -1538,7 +1544,7 @@ bool QPSQLDriver::subscribeToNotification(const QString &name)
{
Q_D(QPSQLDriver);
if (!isOpen()) {
- qWarning("QPSQLDriver::subscribeToNotificationImplementation: database not open.");
+ qCWarning(lcPsql, "QPSQLDriver::subscribeToNotification: Database not open.");
return false;
}
@@ -1563,11 +1569,12 @@ bool QPSQLDriver::subscribeToNotification(const QString &name)
PQclear(result);
if (!d->sn) {
- d->sn = new QSocketNotifier(socket, QSocketNotifier::Read);
- connect(d->sn, SIGNAL(activated(QSocketDescriptor)), this, SLOT(_q_handleNotification()));
+ d->sn = new QSocketNotifier(socket, QSocketNotifier::Read, this);
+ connect(d->sn, &QSocketNotifier::activated, this, &QPSQLDriver::_q_handleNotification);
}
} else {
- qWarning("QPSQLDriver::subscribeToNotificationImplementation: PQsocket didn't return a valid socket to listen on");
+ qCWarning(lcPsql, "QPSQLDriver::subscribeToNotificationImplementation: "
+ "PQsocket didn't return a valid socket to listen on.");
return false;
}
@@ -1578,13 +1585,13 @@ bool QPSQLDriver::unsubscribeFromNotification(const QString &name)
{
Q_D(QPSQLDriver);
if (!isOpen()) {
- qWarning("QPSQLDriver::unsubscribeFromNotificationImplementation: database not open.");
+ qCWarning(lcPsql, "QPSQLDriver::unsubscribeFromNotification: Database not open.");
return false;
}
if (!d->seid.contains(name)) {
- qWarning("QPSQLDriver::unsubscribeFromNotificationImplementation: not subscribed to '%s'.",
- qPrintable(name));
+ qCWarning(lcPsql, "QPSQLDriver::unsubscribeFromNotification: not subscribed to '%ls'.",
+ qUtf16Printable(name));
return false;
}
@@ -1600,7 +1607,7 @@ bool QPSQLDriver::unsubscribeFromNotification(const QString &name)
d->seid.removeAll(name);
if (d->seid.isEmpty()) {
- disconnect(d->sn, SIGNAL(activated(QSocketDescriptor)), this, SLOT(_q_handleNotification()));
+ disconnect(d->sn, &QSocketNotifier::activated, this, &QPSQLDriver::_q_handleNotification);
delete d->sn;
d->sn = nullptr;
}
@@ -1627,17 +1634,19 @@ void QPSQLDriver::_q_handleNotification()
QString payload;
#if defined PG_VERSION_NUM && PG_VERSION_NUM-0 >= 70400
if (notify->extra)
- payload = d->isUtf8 ? QString::fromUtf8(notify->extra) : QString::fromLatin1(notify->extra);
+ payload = QString::fromUtf8(notify->extra);
#endif
QSqlDriver::NotificationSource source = (notify->be_pid == PQbackendPID(d->connection)) ? QSqlDriver::SelfSource : QSqlDriver::OtherSource;
emit notification(name, source, payload);
}
else
- qWarning("QPSQLDriver: received notification for '%s' which isn't subscribed to.",
- qPrintable(name));
+ qCWarning(lcPsql, "QPSQLDriver: received notification for '%ls' which isn't subscribed to.",
+ qUtf16Printable(name));
qPQfreemem(notify);
}
}
QT_END_NAMESPACE
+
+#include "moc_qsql_psql_p.cpp"
diff --git a/src/plugins/sqldrivers/qt_cmdline.cmake b/src/plugins/sqldrivers/qt_cmdline.cmake
index 5a1a1c9ba3..2bb1fc64eb 100644
--- a/src/plugins/sqldrivers/qt_cmdline.cmake
+++ b/src/plugins/sqldrivers/qt_cmdline.cmake
@@ -1,9 +1,9 @@
# Copyright (C) 2022 The Qt Company Ltd.
-# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+# SPDX-License-Identifier: BSD-3-Clause
qt_commandline_option(mysql_config TYPE string)
qt_commandline_option(psql_config TYPE string)
-qt_commandline_option(sqlite TYPE enum NAME system-sqlite MAPPING qt no system yes)
+qt_commandline_option(sqlite CONTROLS_FEATURE TYPE enum NAME system-sqlite MAPPING qt no system yes)
qt_commandline_option(sql-db2 TYPE boolean)
qt_commandline_option(sql-ibase TYPE boolean)
qt_commandline_option(sql-mysql TYPE boolean)
@@ -11,6 +11,7 @@ qt_commandline_option(sql-oci TYPE boolean)
qt_commandline_option(sql-odbc TYPE boolean)
qt_commandline_option(sql-psql TYPE boolean)
qt_commandline_option(sql-sqlite TYPE boolean)
+qt_commandline_option(sql-mimer TYPE boolean)
qt_commandline_option(plugin-sql-db2 TYPE void NAME sql-db2)
qt_commandline_option(plugin-sql-ibase TYPE void NAME sql-ibase)
qt_commandline_option(plugin-sql-mysql TYPE void NAME sql-mysql)
@@ -18,3 +19,4 @@ qt_commandline_option(plugin-sql-oci TYPE void NAME sql-oci)
qt_commandline_option(plugin-sql-odbc TYPE void NAME sql-odbc)
qt_commandline_option(plugin-sql-psql TYPE void NAME sql-psql)
qt_commandline_option(plugin-sql-sqlite TYPE void NAME sql-sqlite)
+qt_commandline_option(plugin-sql-mimer TYPE void NAME sql-mimer)
diff --git a/src/plugins/sqldrivers/sqlite/CMakeLists.txt b/src/plugins/sqldrivers/sqlite/CMakeLists.txt
index 4caa518679..4203a5c437 100644
--- a/src/plugins/sqldrivers/sqlite/CMakeLists.txt
+++ b/src/plugins/sqldrivers/sqlite/CMakeLists.txt
@@ -1,7 +1,5 @@
# Copyright (C) 2022 The Qt Company Ltd.
-# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-
-# Generated from sqlite.pro.
+# SPDX-License-Identifier: BSD-3-Clause
#####################################################################
## QSQLiteDriverPlugin Plugin:
@@ -12,20 +10,18 @@ qt_internal_add_plugin(QSQLiteDriverPlugin
PLUGIN_TYPE sqldrivers
SOURCES
qsql_sqlite.cpp qsql_sqlite_p.h
+ qsql_sqlite_vfs.cpp qsql_sqlite_vfs_p.h
smain.cpp
DEFINES
QT_NO_CAST_FROM_ASCII
QT_NO_CAST_TO_ASCII
+ QT_NO_CONTEXTLESS_CONNECT
LIBRARIES
Qt::Core
Qt::CorePrivate
Qt::SqlPrivate
)
-#### Keys ignored in scope 1:.:.:sqlite.pro:<TRUE>:
-# OTHER_FILES = "sqlite.json"
-# QT_FOR_CONFIG = "sqldrivers-private"
-
## Scopes:
#####################################################################
@@ -34,7 +30,6 @@ qt_internal_extend_target(QSQLiteDriverPlugin CONDITION QT_FEATURE_system_sqlite
SQLite::SQLite3
)
-# special case begin
if (NOT QT_FEATURE_system_sqlite)
# On newer compilers compiling sqlite.c produces warnings
qt_disable_warnings(QSQLiteDriverPlugin)
@@ -43,7 +38,11 @@ endif()
if(QT_FEATURE_system_sqlite)
qt_internal_force_macos_intel_arch(QSQLiteDriverPlugin)
endif()
-# special case end
+
+qt_internal_extend_target(QSQLiteDriverPlugin CONDITION NOT QT_FEATURE_system_sqlite AND VXWORKS
+ DEFINES
+ SQLITE_OS_UNIX=1
+)
qt_internal_extend_target(QSQLiteDriverPlugin CONDITION NOT QT_FEATURE_system_sqlite
SOURCES
@@ -52,8 +51,11 @@ qt_internal_extend_target(QSQLiteDriverPlugin CONDITION NOT QT_FEATURE_system_sq
SQLITE_ENABLE_COLUMN_METADATA
SQLITE_ENABLE_FTS3
SQLITE_ENABLE_FTS3_PARENTHESIS
+ SQLITE_ENABLE_FTS4
SQLITE_ENABLE_FTS5
+ SQLITE_ENABLE_GEOPOLY
SQLITE_ENABLE_JSON1
+ SQLITE_ENABLE_MATH_FUNCTIONS
SQLITE_ENABLE_RTREE
SQLITE_OMIT_COMPLETE
INCLUDE_DIRECTORIES
diff --git a/src/plugins/sqldrivers/sqlite/qsql_sqlite.cpp b/src/plugins/sqldrivers/sqlite/qsql_sqlite.cpp
index 51a3908867..c574772fd7 100644
--- a/src/plugins/sqldrivers/sqlite/qsql_sqlite.cpp
+++ b/src/plugins/sqldrivers/sqlite/qsql_sqlite.cpp
@@ -7,6 +7,7 @@
#include <qdatetime.h>
#include <qdebug.h>
#include <qlist.h>
+#include <qloggingcategory.h>
#include <qsqlerror.h>
#include <qsqlfield.h>
#include <qsqlindex.h>
@@ -38,23 +39,9 @@ Q_DECLARE_METATYPE(sqlite3_stmt*)
QT_BEGIN_NAMESPACE
-using namespace Qt::StringLiterals;
+static Q_LOGGING_CATEGORY(lcSqlite, "qt.sql.sqlite")
-static QString _q_escapeIdentifier(const QString &identifier, QSqlDriver::IdentifierType type)
-{
- QString res = identifier;
- // If it contains [ and ] then we assume it to be escaped properly already as this indicates
- // the syntax is exactly how it should be
- if (identifier.contains(u'[') && identifier.contains(u']'))
- return res;
- if (!identifier.isEmpty() && !identifier.startsWith(u'"') && !identifier.endsWith(u'"')) {
- res.replace(u'"', "\"\""_L1);
- res.prepend(u'"').append(u'"');
- if (type == QSqlDriver::TableName)
- res.replace(u'.', "\".\""_L1);
- }
- return res;
-}
+using namespace Qt::StringLiterals;
static int qGetColumnType(const QString &tpName)
{
@@ -114,11 +101,64 @@ class QSQLiteDriverPrivate : public QSqlDriverPrivate
public:
inline QSQLiteDriverPrivate() : QSqlDriverPrivate(QSqlDriver::SQLite) {}
+ bool isIdentifierEscaped(QStringView identifier) const;
+ QSqlIndex getTableInfo(QSqlQuery &query, const QString &tableName,
+ bool onlyPIndex = false) const;
+
sqlite3 *access = nullptr;
QList<QSQLiteResult *> results;
QStringList notificationid;
};
+bool QSQLiteDriverPrivate::isIdentifierEscaped(QStringView identifier) const
+{
+ return identifier.size() > 2
+ && ((identifier.startsWith(u'"') && identifier.endsWith(u'"'))
+ || (identifier.startsWith(u'`') && identifier.endsWith(u'`'))
+ || (identifier.startsWith(u'[') && identifier.endsWith(u']')));
+}
+
+QSqlIndex QSQLiteDriverPrivate::getTableInfo(QSqlQuery &query, const QString &tableName,
+ bool onlyPIndex) const
+{
+ Q_Q(const QSQLiteDriver);
+ QString schema;
+ QString table = q->escapeIdentifier(tableName, QSqlDriver::TableName);
+ const auto indexOfSeparator = table.indexOf(u'.');
+ if (indexOfSeparator > -1) {
+ auto leftName = QStringView{table}.first(indexOfSeparator);
+ auto rightName = QStringView{table}.sliced(indexOfSeparator + 1);
+ if (isIdentifierEscaped(leftName) && isIdentifierEscaped(rightName)) {
+ schema = leftName.toString() + u'.';
+ table = rightName.toString();
+ }
+ }
+
+ query.exec("PRAGMA "_L1 + schema + "table_info ("_L1 + table + u')');
+ QSqlIndex ind;
+ while (query.next()) {
+ bool isPk = query.value(5).toInt();
+ if (onlyPIndex && !isPk)
+ continue;
+ QString typeName = query.value(2).toString().toLower();
+ QString defVal = query.value(4).toString();
+ if (!defVal.isEmpty() && defVal.at(0) == u'\'') {
+ const int end = defVal.lastIndexOf(u'\'');
+ if (end > 0)
+ defVal = defVal.mid(1, end - 1);
+ }
+
+ QSqlField fld(query.value(1).toString(), QMetaType(qGetColumnType(typeName)), tableName);
+ if (isPk && (typeName == "integer"_L1))
+ // INTEGER PRIMARY KEY fields are auto-generated in sqlite
+ // INT PRIMARY KEY is not the same as INTEGER PRIMARY KEY!
+ fld.setAutoValue(true);
+ fld.setRequired(query.value(3).toInt() != 0);
+ fld.setDefaultValue(defVal);
+ ind.append(fld);
+ }
+ return ind;
+}
class QSQLiteResultPrivate : public QSqlCachedResultPrivate
{
@@ -158,7 +198,7 @@ void QSQLiteResultPrivate::finalize()
return;
sqlite3_finalize(stmt);
- stmt = 0;
+ stmt = nullptr;
}
void QSQLiteResultPrivate::initColumns(bool emptyResultset)
@@ -210,7 +250,6 @@ void QSQLiteResultPrivate::initColumns(bool emptyResultset)
}
QSqlField fld(colName, QMetaType(fieldType), tableName);
- fld.setSqlType(stp);
rInf.append(fld);
}
}
@@ -223,7 +262,7 @@ bool QSQLiteResultPrivate::fetchNext(QSqlCachedResult::ValueCache &values, int i
// already fetched
Q_ASSERT(!initialFetch);
skipRow = false;
- for(int i=0;i<firstRow.count();i++)
+ for(int i=0;i<firstRow.size();i++)
values[i]=firstRow[i];
return skippedStatus;
}
@@ -382,10 +421,10 @@ bool QSQLiteResult::execBatch(bool arrayBind)
Q_D(QSqlResult);
QScopedValueRollback<QList<QVariant>> valuesScope(d->values);
QList<QVariant> values = d->values;
- if (values.count() == 0)
+ if (values.size() == 0)
return false;
- for (int i = 0; i < values.at(0).toList().count(); ++i) {
+ for (int i = 0; i < values.at(0).toList().size(); ++i) {
d->values.clear();
QScopedValueRollback<QHash<QString, QList<int>>> indexesScope(d->indexes);
auto it = d->indexes.constBegin();
@@ -419,16 +458,16 @@ bool QSQLiteResult::exec()
}
int paramCount = sqlite3_bind_parameter_count(d->stmt);
- bool paramCountIsValid = paramCount == values.count();
+ bool paramCountIsValid = paramCount == values.size();
#if (SQLITE_VERSION_NUMBER >= 3003011)
// In the case of the reuse of a named placeholder
// We need to check explicitly that paramCount is greater than or equal to 1, as sqlite
// can end up in a case where for virtual tables it returns 0 even though it
// has parameters
- if (paramCount >= 1 && paramCount < values.count()) {
+ if (paramCount >= 1 && paramCount < values.size()) {
const auto countIndexes = [](int counter, const QList<int> &indexList) {
- return counter + indexList.length();
+ return counter + indexList.size();
};
const int bindParamCount = std::accumulate(d->indexes.cbegin(),
@@ -436,7 +475,7 @@ bool QSQLiteResult::exec()
0,
countIndexes);
- paramCountIsValid = bindParamCount == values.count();
+ paramCountIsValid = bindParamCount == values.size();
// When using named placeholders, it will reuse the index for duplicated
// placeholders. So we need to ensure the QList has only one instance of
// each value as SQLite will do the rest for us.
@@ -625,6 +664,30 @@ static void _q_regexp_cleanup(void *cache)
}
#endif
+static void _q_lower(sqlite3_context* context, int argc, sqlite3_value** argv)
+{
+ if (Q_UNLIKELY(argc != 1)) {
+ sqlite3_result_text(context, nullptr, 0, nullptr);
+ return;
+ }
+ const QString lower = QString::fromUtf8(
+ reinterpret_cast<const char*>(sqlite3_value_text(argv[0]))).toLower();
+ const QByteArray ba = lower.toUtf8();
+ sqlite3_result_text(context, ba.data(), ba.size(), SQLITE_TRANSIENT);
+}
+
+static void _q_upper(sqlite3_context* context, int argc, sqlite3_value** argv)
+{
+ if (Q_UNLIKELY(argc != 1)) {
+ sqlite3_result_text(context, nullptr, 0, nullptr);
+ return;
+ }
+ const QString upper = QString::fromUtf8(
+ reinterpret_cast<const char*>(sqlite3_value_text(argv[0]))).toUpper();
+ const QByteArray ba = upper.toUtf8();
+ sqlite3_result_text(context, ba.data(), ba.size(), SQLITE_TRANSIENT);
+}
+
QSQLiteDriver::QSQLiteDriver(QObject * parent)
: QSqlDriver(*new QSQLiteDriverPrivate, parent)
{
@@ -691,13 +754,16 @@ bool QSQLiteDriver::open(const QString & db, const QString &, const QString &, c
bool openReadOnlyOption = false;
bool openUriOption = false;
bool useExtendedResultCodes = true;
+ bool useQtVfs = false;
+ bool useQtCaseFolding = false;
+ bool openNoFollow = false;
#if QT_CONFIG(regularexpression)
static const auto regexpConnectOption = "QSQLITE_ENABLE_REGEXP"_L1;
bool defineRegexp = false;
int regexpCacheSize = 25;
#endif
- const auto opts = QStringView{conOpts}.split(u';');
+ const auto opts = QStringView{conOpts}.split(u';', Qt::SkipEmptyParts);
for (auto option : opts) {
option = option.trimmed();
if (option.startsWith("QSQLITE_BUSY_TIMEOUT"_L1)) {
@@ -708,6 +774,8 @@ bool QSQLiteDriver::open(const QString & db, const QString &, const QString &, c
if (ok)
timeOut = nt;
}
+ } else if (option == "QSQLITE_USE_QT_VFS"_L1) {
+ useQtVfs = true;
} else if (option == "QSQLITE_OPEN_READONLY"_L1) {
openReadOnlyOption = true;
} else if (option == "QSQLITE_OPEN_URI"_L1) {
@@ -716,6 +784,10 @@ bool QSQLiteDriver::open(const QString & db, const QString &, const QString &, c
sharedCache = true;
} else if (option == "QSQLITE_NO_USE_EXTENDED_RESULT_CODES"_L1) {
useExtendedResultCodes = false;
+ } else if (option == "QSQLITE_ENABLE_NON_ASCII_CASE_FOLDING"_L1) {
+ useQtCaseFolding = true;
+ } else if (option == "QSQLITE_OPEN_NOFOLLOW"_L1) {
+ openNoFollow = true;
}
#if QT_CONFIG(regularexpression)
else if (option.startsWith(regexpConnectOption)) {
@@ -733,16 +805,25 @@ bool QSQLiteDriver::open(const QString & db, const QString &, const QString &, c
}
}
#endif
+ else
+ qCWarning(lcSqlite, "Unsupported option '%ls'", qUtf16Printable(option.toString()));
}
int openMode = (openReadOnlyOption ? SQLITE_OPEN_READONLY : (SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE));
openMode |= (sharedCache ? SQLITE_OPEN_SHAREDCACHE : SQLITE_OPEN_PRIVATECACHE);
if (openUriOption)
openMode |= SQLITE_OPEN_URI;
+ if (openNoFollow) {
+#if defined(SQLITE_OPEN_NOFOLLOW)
+ openMode |= SQLITE_OPEN_NOFOLLOW;
+#else
+ qCWarning(lcSqlite, "SQLITE_OPEN_NOFOLLOW not supported with the SQLite version %s", sqlite3_libversion());
+#endif
+ }
openMode |= SQLITE_OPEN_NOMUTEX;
- const int res = sqlite3_open_v2(db.toUtf8().constData(), &d->access, openMode, nullptr);
+ const int res = sqlite3_open_v2(db.toUtf8().constData(), &d->access, openMode, useQtVfs ? "QtVFS" : nullptr);
if (res == SQLITE_OK) {
sqlite3_busy_timeout(d->access, timeOut);
@@ -757,6 +838,12 @@ bool QSQLiteDriver::open(const QString & db, const QString &, const QString &, c
nullptr, &_q_regexp_cleanup);
}
#endif
+ if (useQtCaseFolding) {
+ sqlite3_create_function_v2(d->access, "lower", 1, SQLITE_UTF8, nullptr,
+ &_q_lower, nullptr, nullptr, nullptr);
+ sqlite3_create_function_v2(d->access, "upper", 1, SQLITE_UTF8, nullptr,
+ &_q_upper, nullptr, nullptr, nullptr);
+ }
return true;
} else {
setLastError(qMakeError(d->access, tr("Error opening database"),
@@ -765,7 +852,7 @@ bool QSQLiteDriver::open(const QString & db, const QString &, const QString &, c
if (d->access) {
sqlite3_close(d->access);
- d->access = 0;
+ d->access = nullptr;
}
return false;
@@ -776,10 +863,10 @@ void QSQLiteDriver::close()
{
Q_D(QSQLiteDriver);
if (isOpen()) {
- for (QSQLiteResult *result : qAsConst(d->results))
+ for (QSQLiteResult *result : std::as_const(d->results))
result->d_func()->finalize();
- if (d->access && (d->notificationid.count() > 0)) {
+ if (d->access && (d->notificationid.size() > 0)) {
d->notificationid.clear();
sqlite3_update_hook(d->access, nullptr, nullptr);
}
@@ -788,7 +875,7 @@ void QSQLiteDriver::close()
if (res != SQLITE_OK)
setLastError(qMakeError(d->access, tr("Error closing database"), QSqlError::ConnectionError, res));
- d->access = 0;
+ d->access = nullptr;
setOpen(false);
setOpenError(false);
}
@@ -877,79 +964,26 @@ QStringList QSQLiteDriver::tables(QSql::TableType type) const
return res;
}
-static QSqlIndex qGetTableInfo(QSqlQuery &q, const QString &tableName, bool onlyPIndex = false)
-{
- QString schema;
- QString table(tableName);
- const qsizetype indexOfSeparator = tableName.indexOf(u'.');
- if (indexOfSeparator > -1) {
- const qsizetype indexOfCloseBracket = tableName.indexOf(u']');
- if (indexOfCloseBracket != tableName.size() - 1) {
- // Handles a case like databaseName.tableName
- schema = tableName.left(indexOfSeparator + 1);
- table = tableName.mid(indexOfSeparator + 1);
- } else {
- const qsizetype indexOfOpenBracket = tableName.lastIndexOf(u'[', indexOfCloseBracket);
- if (indexOfOpenBracket > 0) {
- // Handles a case like databaseName.[tableName]
- schema = tableName.left(indexOfOpenBracket);
- table = tableName.mid(indexOfOpenBracket);
- }
- }
- }
- q.exec("PRAGMA "_L1 + schema + "table_info ("_L1 +
- _q_escapeIdentifier(table, QSqlDriver::TableName) + u')');
- QSqlIndex ind;
- while (q.next()) {
- bool isPk = q.value(5).toInt();
- if (onlyPIndex && !isPk)
- continue;
- QString typeName = q.value(2).toString().toLower();
- QString defVal = q.value(4).toString();
- if (!defVal.isEmpty() && defVal.at(0) == u'\'') {
- const int end = defVal.lastIndexOf(u'\'');
- if (end > 0)
- defVal = defVal.mid(1, end - 1);
- }
-
- QSqlField fld(q.value(1).toString(), QMetaType(qGetColumnType(typeName)), tableName);
- if (isPk && (typeName == "integer"_L1))
- // INTEGER PRIMARY KEY fields are auto-generated in sqlite
- // INT PRIMARY KEY is not the same as INTEGER PRIMARY KEY!
- fld.setAutoValue(true);
- fld.setRequired(q.value(3).toInt() != 0);
- fld.setDefaultValue(defVal);
- ind.append(fld);
- }
- return ind;
-}
-
-QSqlIndex QSQLiteDriver::primaryIndex(const QString &tblname) const
+QSqlIndex QSQLiteDriver::primaryIndex(const QString &tablename) const
{
+ Q_D(const QSQLiteDriver);
if (!isOpen())
return QSqlIndex();
- QString table = tblname;
- if (isIdentifierEscaped(table, QSqlDriver::TableName))
- table = stripDelimiters(table, QSqlDriver::TableName);
-
QSqlQuery q(createResult());
q.setForwardOnly(true);
- return qGetTableInfo(q, table, true);
+ return d->getTableInfo(q, tablename, true);
}
-QSqlRecord QSQLiteDriver::record(const QString &tbl) const
+QSqlRecord QSQLiteDriver::record(const QString &tablename) const
{
+ Q_D(const QSQLiteDriver);
if (!isOpen())
return QSqlRecord();
- QString table = tbl;
- if (isIdentifierEscaped(table, QSqlDriver::TableName))
- table = stripDelimiters(table, QSqlDriver::TableName);
-
QSqlQuery q(createResult());
q.setForwardOnly(true);
- return qGetTableInfo(q, table);
+ return d->getTableInfo(q, tablename);
}
QVariant QSQLiteDriver::handle() const
@@ -960,7 +994,52 @@ QVariant QSQLiteDriver::handle() const
QString QSQLiteDriver::escapeIdentifier(const QString &identifier, IdentifierType type) const
{
- return _q_escapeIdentifier(identifier, type);
+ Q_D(const QSQLiteDriver);
+ if (identifier.isEmpty() || isIdentifierEscaped(identifier, type))
+ return identifier;
+
+ const auto indexOfSeparator = identifier.indexOf(u'.');
+ if (indexOfSeparator > -1) {
+ auto leftName = QStringView{identifier}.first(indexOfSeparator);
+ auto rightName = QStringView{identifier}.sliced(indexOfSeparator + 1);
+ const QStringView leftEnclose = d->isIdentifierEscaped(leftName) ? u"" : u"\"";
+ const QStringView rightEnclose = d->isIdentifierEscaped(rightName) ? u"" : u"\"";
+ if (leftEnclose.isEmpty() || rightEnclose.isEmpty())
+ return (leftEnclose + leftName + leftEnclose + u'.' + rightEnclose + rightName
+ + rightEnclose);
+ }
+ return u'"' + identifier + u'"';
+}
+
+bool QSQLiteDriver::isIdentifierEscaped(const QString &identifier, IdentifierType type) const
+{
+ Q_D(const QSQLiteDriver);
+ Q_UNUSED(type);
+ return d->isIdentifierEscaped(QStringView{identifier});
+}
+
+QString QSQLiteDriver::stripDelimiters(const QString &identifier, IdentifierType type) const
+{
+ Q_D(const QSQLiteDriver);
+ const auto indexOfSeparator = identifier.indexOf(u'.');
+ if (indexOfSeparator > -1) {
+ auto leftName = QStringView{identifier}.first(indexOfSeparator);
+ auto rightName = QStringView{identifier}.sliced(indexOfSeparator + 1);
+ const auto leftEscaped = d->isIdentifierEscaped(leftName);
+ const auto rightEscaped = d->isIdentifierEscaped(rightName);
+ if (leftEscaped || rightEscaped) {
+ if (leftEscaped)
+ leftName = leftName.sliced(1).chopped(1);
+ if (rightEscaped)
+ rightName = rightName.sliced(1).chopped(1);
+ return leftName + u'.' + rightName;
+ }
+ }
+
+ if (isIdentifierEscaped(identifier, type))
+ return identifier.mid(1, identifier.size() - 2);
+
+ return identifier;
}
static void handle_sqlite_callback(void *qobj,int aoperation, char const *adbname, char const *atablename,
@@ -979,18 +1058,19 @@ bool QSQLiteDriver::subscribeToNotification(const QString &name)
{
Q_D(QSQLiteDriver);
if (!isOpen()) {
- qWarning("Database not open.");
+ qCWarning(lcSqlite, "QSQLiteDriver::subscribeToNotification: Database not open.");
return false;
}
if (d->notificationid.contains(name)) {
- qWarning("Already subscribing to '%s'.", qPrintable(name));
+ qCWarning(lcSqlite, "QSQLiteDriver::subscribeToNotification: Already subscribing to '%ls'.",
+ qUtf16Printable(name));
return false;
}
//sqlite supports only one notification callback, so only the first is registered
d->notificationid << name;
- if (d->notificationid.count() == 1)
+ if (d->notificationid.size() == 1)
sqlite3_update_hook(d->access, &handle_sqlite_callback, reinterpret_cast<void *> (this));
return true;
@@ -1000,12 +1080,13 @@ bool QSQLiteDriver::unsubscribeFromNotification(const QString &name)
{
Q_D(QSQLiteDriver);
if (!isOpen()) {
- qWarning("Database not open.");
+ qCWarning(lcSqlite, "QSQLiteDriver::unsubscribeFromNotification: Database not open.");
return false;
}
if (!d->notificationid.contains(name)) {
- qWarning("Not subscribed to '%s'.", qPrintable(name));
+ qCWarning(lcSqlite, "QSQLiteDriver::unsubscribeFromNotification: Not subscribed to '%ls'.",
+ qUtf16Printable(name));
return false;
}
diff --git a/src/plugins/sqldrivers/sqlite/qsql_sqlite_p.h b/src/plugins/sqldrivers/sqlite/qsql_sqlite_p.h
index 53ffb45f96..db6b76cb69 100644
--- a/src/plugins/sqldrivers/sqlite/qsql_sqlite_p.h
+++ b/src/plugins/sqldrivers/sqlite/qsql_sqlite_p.h
@@ -54,9 +54,12 @@ public:
QStringList tables(QSql::TableType) const override;
QSqlRecord record(const QString& tablename) const override;
- QSqlIndex primaryIndex(const QString &table) const override;
+ QSqlIndex primaryIndex(const QString &tablename) const override;
QVariant handle() const override;
- QString escapeIdentifier(const QString &identifier, IdentifierType) const override;
+
+ QString escapeIdentifier(const QString &identifier, IdentifierType type) const override;
+ bool isIdentifierEscaped(const QString &identifier, IdentifierType type) const override;
+ QString stripDelimiters(const QString &identifier, IdentifierType type) const override;
bool subscribeToNotification(const QString &name) override;
bool unsubscribeFromNotification(const QString &name) override;
diff --git a/src/plugins/sqldrivers/sqlite/qsql_sqlite_vfs.cpp b/src/plugins/sqldrivers/sqlite/qsql_sqlite_vfs.cpp
new file mode 100644
index 0000000000..bbba3cd14f
--- /dev/null
+++ b/src/plugins/sqldrivers/sqlite/qsql_sqlite_vfs.cpp
@@ -0,0 +1,258 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "qsql_sqlite_vfs_p.h"
+
+#include <QFile>
+
+#include <limits.h> // defines PATH_MAX on unix
+#include <sqlite3.h>
+#include <stdio.h> // defines FILENAME_MAX everywhere
+
+#ifndef PATH_MAX
+# define PATH_MAX FILENAME_MAX
+#endif
+#if SQLITE_VERSION_NUMBER < 3040000
+typedef const char *sqlite3_filename;
+#endif
+
+namespace {
+struct Vfs : sqlite3_vfs {
+ sqlite3_vfs *pVfs;
+ sqlite3_io_methods ioMethods;
+};
+
+struct File : sqlite3_file {
+ class QtFile : public QFile {
+ public:
+ QtFile(const QString &name, bool removeOnClose)
+ : QFile(name)
+ , removeOnClose(removeOnClose)
+ {}
+
+ ~QtFile() override
+ {
+ if (removeOnClose)
+ remove();
+ }
+ private:
+ bool removeOnClose;
+ };
+ QtFile *pFile;
+};
+
+
+int xClose(sqlite3_file *sfile)
+{
+ auto file = static_cast<File *>(sfile);
+ delete file->pFile;
+ file->pFile = nullptr;
+ return SQLITE_OK;
+}
+
+int xRead(sqlite3_file *sfile, void *ptr, int iAmt, sqlite3_int64 iOfst)
+{
+ auto file = static_cast<File *>(sfile);
+ if (!file->pFile->seek(iOfst))
+ return SQLITE_IOERR_READ;
+
+ auto sz = file->pFile->read(static_cast<char *>(ptr), iAmt);
+ if (sz < iAmt) {
+ memset(static_cast<char *>(ptr) + sz, 0, size_t(iAmt - sz));
+ return SQLITE_IOERR_SHORT_READ;
+ }
+ return SQLITE_OK;
+}
+
+int xWrite(sqlite3_file *sfile, const void *data, int iAmt, sqlite3_int64 iOfst)
+{
+ auto file = static_cast<File *>(sfile);
+ if (!file->pFile->seek(iOfst))
+ return SQLITE_IOERR_SEEK;
+ return file->pFile->write(reinterpret_cast<const char*>(data), iAmt) == iAmt ? SQLITE_OK : SQLITE_IOERR_WRITE;
+}
+
+int xTruncate(sqlite3_file *sfile, sqlite3_int64 size)
+{
+ auto file = static_cast<File *>(sfile);
+ return file->pFile->resize(size) ? SQLITE_OK : SQLITE_IOERR_TRUNCATE;
+}
+
+int xSync(sqlite3_file *sfile, int /*flags*/)
+{
+ static_cast<File *>(sfile)->pFile->flush();
+ return SQLITE_OK;
+}
+
+int xFileSize(sqlite3_file *sfile, sqlite3_int64 *pSize)
+{
+ auto file = static_cast<File *>(sfile);
+ *pSize = file->pFile->size();
+ return SQLITE_OK;
+}
+
+// No lock/unlock for QFile, QLockFile doesn't work for me
+
+int xLock(sqlite3_file *, int) { return SQLITE_OK; }
+
+int xUnlock(sqlite3_file *, int) { return SQLITE_OK; }
+
+int xCheckReservedLock(sqlite3_file *, int *pResOut)
+{
+ *pResOut = 0;
+ return SQLITE_OK;
+}
+
+int xFileControl(sqlite3_file *, int, void *) { return SQLITE_NOTFOUND; }
+
+int xSectorSize(sqlite3_file *)
+{
+ return 4096;
+}
+
+int xDeviceCharacteristics(sqlite3_file *)
+{
+ return 0; // no SQLITE_IOCAP_XXX
+}
+
+int xOpen(sqlite3_vfs *svfs, sqlite3_filename zName, sqlite3_file *sfile,
+ int flags, int *pOutFlags)
+{
+ auto vfs = static_cast<Vfs *>(svfs);
+ auto file = static_cast<File *>(sfile);
+ memset(file, 0, sizeof(File));
+ QIODeviceBase::OpenMode mode = QIODeviceBase::NotOpen;
+ if (!zName || (flags & SQLITE_OPEN_MEMORY))
+ return SQLITE_PERM;
+ if ((flags & SQLITE_OPEN_READONLY) &&
+ !(flags & SQLITE_OPEN_READWRITE) &&
+ !(flags & SQLITE_OPEN_CREATE) &&
+ !(flags & SQLITE_OPEN_DELETEONCLOSE)) {
+ mode |= QIODeviceBase::OpenModeFlag::ReadOnly;
+ } else {
+ /*
+ ** ^The [SQLITE_OPEN_EXCLUSIVE] flag is always used in conjunction
+ ** with the [SQLITE_OPEN_CREATE] flag, which are both directly
+ ** analogous to the O_EXCL and O_CREAT flags of the POSIX open()
+ ** API. The SQLITE_OPEN_EXCLUSIVE flag, when paired with the
+ ** SQLITE_OPEN_CREATE, is used to indicate that file should always
+ ** be created, and that it is an error if it already exists.
+ ** It is <i>not</i> used to indicate the file should be opened
+ ** for exclusive access.
+ */
+ if ((flags & SQLITE_OPEN_CREATE) && (flags & SQLITE_OPEN_EXCLUSIVE))
+ mode |= QIODeviceBase::OpenModeFlag::NewOnly;
+
+ if (flags & SQLITE_OPEN_READWRITE)
+ mode |= QIODeviceBase::OpenModeFlag::ReadWrite;
+ }
+
+ file->pMethods = &vfs->ioMethods;
+ file->pFile = new File::QtFile(QString::fromUtf8(zName), bool(flags & SQLITE_OPEN_DELETEONCLOSE));
+ if (!file->pFile->open(mode))
+ return SQLITE_CANTOPEN;
+ if (pOutFlags)
+ *pOutFlags = flags;
+
+ return SQLITE_OK;
+}
+
+int xDelete(sqlite3_vfs *, const char *zName, int)
+{
+ return QFile::remove(QString::fromUtf8(zName)) ? SQLITE_OK : SQLITE_ERROR;
+}
+
+int xAccess(sqlite3_vfs */*svfs*/, const char *zName, int flags, int *pResOut)
+{
+ *pResOut = 0;
+ switch (flags) {
+ case SQLITE_ACCESS_EXISTS:
+ case SQLITE_ACCESS_READ:
+ *pResOut = QFile::exists(QString::fromUtf8(zName));
+ break;
+ default:
+ break;
+ }
+ return SQLITE_OK;
+}
+
+int xFullPathname(sqlite3_vfs *, const char *zName, int nOut, char *zOut)
+{
+ if (!zName)
+ return SQLITE_ERROR;
+
+ int i = 0;
+ for (;zName[i] && i < nOut; ++i)
+ zOut[i] = zName[i];
+
+ if (i >= nOut)
+ return SQLITE_ERROR;
+
+ zOut[i] = '\0';
+ return SQLITE_OK;
+}
+
+int xRandomness(sqlite3_vfs *svfs, int nByte, char *zOut)
+{
+ auto vfs = static_cast<Vfs *>(svfs)->pVfs;
+ return vfs->xRandomness(vfs, nByte, zOut);
+}
+
+int xSleep(sqlite3_vfs *svfs, int microseconds)
+{
+ auto vfs = static_cast<Vfs *>(svfs)->pVfs;
+ return vfs->xSleep(vfs, microseconds);
+}
+
+int xCurrentTime(sqlite3_vfs *svfs, double *zOut)
+{
+ auto vfs = static_cast<Vfs *>(svfs)->pVfs;
+ return vfs->xCurrentTime(vfs, zOut);
+}
+
+int xGetLastError(sqlite3_vfs *, int, char *)
+{
+ return 0;
+}
+
+int xCurrentTimeInt64(sqlite3_vfs *svfs, sqlite3_int64 *zOut)
+{
+ auto vfs = static_cast<Vfs *>(svfs)->pVfs;
+ return vfs->xCurrentTimeInt64(vfs, zOut);
+}
+} // namespace {
+
+void register_qt_vfs()
+{
+ static Vfs vfs;
+ memset(&vfs, 0, sizeof(Vfs));
+ vfs.iVersion = 1;
+ vfs.szOsFile = sizeof(File);
+ vfs.mxPathname = PATH_MAX;
+ vfs.zName = "QtVFS";
+ vfs.xOpen = &xOpen;
+ vfs.xDelete = &xDelete;
+ vfs.xAccess = &xAccess;
+ vfs.xFullPathname = &xFullPathname;
+ vfs.xRandomness = &xRandomness;
+ vfs.xSleep = &xSleep;
+ vfs.xCurrentTime = &xCurrentTime;
+ vfs.xGetLastError = &xGetLastError;
+ vfs.xCurrentTimeInt64 = &xCurrentTimeInt64;
+ vfs.pVfs = sqlite3_vfs_find(nullptr);
+ vfs.ioMethods.iVersion = 1;
+ vfs.ioMethods.xClose = &xClose;
+ vfs.ioMethods.xRead = &xRead;
+ vfs.ioMethods.xWrite = &xWrite;
+ vfs.ioMethods.xTruncate = &xTruncate;
+ vfs.ioMethods.xSync = &xSync;
+ vfs.ioMethods.xFileSize = &xFileSize;
+ vfs.ioMethods.xLock = &xLock;
+ vfs.ioMethods.xUnlock = &xUnlock;
+ vfs.ioMethods.xCheckReservedLock = &xCheckReservedLock;
+ vfs.ioMethods.xFileControl = &xFileControl;
+ vfs.ioMethods.xSectorSize = &xSectorSize;
+ vfs.ioMethods.xDeviceCharacteristics = &xDeviceCharacteristics;
+
+ sqlite3_vfs_register(&vfs, 0);
+}
diff --git a/src/plugins/sqldrivers/sqlite/qsql_sqlite_vfs_p.h b/src/plugins/sqldrivers/sqlite/qsql_sqlite_vfs_p.h
new file mode 100644
index 0000000000..56024b3ecb
--- /dev/null
+++ b/src/plugins/sqldrivers/sqlite/qsql_sqlite_vfs_p.h
@@ -0,0 +1,21 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QSQL_SQLITE_VFS_H
+#define QSQL_SQLITE_VFS_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+void register_qt_vfs();
+
+
+#endif // QSQL_SQLITE_VFS_H
diff --git a/src/plugins/sqldrivers/sqlite/smain.cpp b/src/plugins/sqldrivers/sqlite/smain.cpp
index 9223cca850..0d201c38d3 100644
--- a/src/plugins/sqldrivers/sqlite/smain.cpp
+++ b/src/plugins/sqldrivers/sqlite/smain.cpp
@@ -4,6 +4,7 @@
#include <qsqldriverplugin.h>
#include <qstringlist.h>
#include "qsql_sqlite_p.h"
+#include "qsql_sqlite_vfs_p.h"
QT_BEGIN_NAMESPACE
@@ -23,6 +24,7 @@ public:
QSQLiteDriverPlugin::QSQLiteDriverPlugin()
: QSqlDriverPlugin()
{
+ register_qt_vfs();
}
QSqlDriver* QSQLiteDriverPlugin::create(const QString &name)
@@ -31,7 +33,8 @@ QSqlDriver* QSQLiteDriverPlugin::create(const QString &name)
QSQLiteDriver* driver = new QSQLiteDriver();
return driver;
}
- return 0;
+
+ return nullptr;
}
QT_END_NAMESPACE
diff --git a/src/plugins/styles/CMakeLists.txt b/src/plugins/styles/CMakeLists.txt
index db89d0aca5..b809042c14 100644
--- a/src/plugins/styles/CMakeLists.txt
+++ b/src/plugins/styles/CMakeLists.txt
@@ -1,7 +1,5 @@
# Copyright (C) 2022 The Qt Company Ltd.
-# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-
-# Generated from styles.pro.
+# SPDX-License-Identifier: BSD-3-Clause
if(QT_FEATURE_style_android)
add_subdirectory(android)
@@ -10,5 +8,5 @@ if(QT_FEATURE_style_mac)
add_subdirectory(mac)
endif()
if(QT_FEATURE_style_windowsvista)
- add_subdirectory(windowsvista)
+ add_subdirectory(modernwindows)
endif()
diff --git a/src/plugins/styles/android/CMakeLists.txt b/src/plugins/styles/android/CMakeLists.txt
index b89693c122..b8bda6fd5f 100644
--- a/src/plugins/styles/android/CMakeLists.txt
+++ b/src/plugins/styles/android/CMakeLists.txt
@@ -1,7 +1,5 @@
# Copyright (C) 2022 The Qt Company Ltd.
-# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-
-# Generated from android.pro.
+# SPDX-License-Identifier: BSD-3-Clause
#####################################################################
## QAndroidStylePlugin Plugin:
@@ -18,6 +16,3 @@ qt_internal_add_plugin(QAndroidStylePlugin
Qt::Gui
Qt::WidgetsPrivate
)
-
-#### Keys ignored in scope 1:.:.:android.pro:<TRUE>:
-# DISTFILES = "androidstyle.json"
diff --git a/src/plugins/styles/android/qandroidstyle.cpp b/src/plugins/styles/android/qandroidstyle.cpp
index 5acdb07520..64940ed4d8 100644
--- a/src/plugins/styles/android/qandroidstyle.cpp
+++ b/src/plugins/styles/android/qandroidstyle.cpp
@@ -1134,7 +1134,7 @@ QAndroidStyle::AndroidStateDrawable::AndroidStateDrawable(const QVariantMap &dra
QAndroidStyle::AndroidStateDrawable::~AndroidStateDrawable()
{
- for (const StateType &type : qAsConst(m_states))
+ for (const StateType &type : std::as_const(m_states))
delete type.second;
}
@@ -1209,7 +1209,7 @@ const QAndroidStyle::AndroidDrawable * QAndroidStyle::AndroidStateDrawable::best
int QAndroidStyle::AndroidStateDrawable::extractState(const QVariantMap &value)
{
- QStyle::State state = QStyle::State_Enabled | QStyle::State_Active;;
+ QStyle::State state = QStyle::State_Enabled | QStyle::State_Active;
for (auto it = value.cbegin(), end = value.cend(); it != end; ++it) {
const QString &key = it.key();
bool val = it.value().toString() == QLatin1String("true");
@@ -1259,7 +1259,7 @@ int QAndroidStyle::AndroidStateDrawable::extractState(const QVariantMap &value)
void QAndroidStyle::AndroidStateDrawable::setPaddingLeftToSizeWidth()
{
- for (const StateType &type : qAsConst(m_states))
+ for (const StateType &type : std::as_const(m_states))
const_cast<AndroidDrawable *>(type.second)->setPaddingLeftToSizeWidth();
}
@@ -1285,7 +1285,7 @@ QAndroidStyle::AndroidLayerDrawable::AndroidLayerDrawable(const QVariantMap &dra
QAndroidStyle::AndroidLayerDrawable::~AndroidLayerDrawable()
{
- for (const LayerType &layer : qAsConst(m_layers))
+ for (const LayerType &layer : std::as_const(m_layers))
delete layer.second;
}
diff --git a/src/plugins/styles/mac/CMakeLists.txt b/src/plugins/styles/mac/CMakeLists.txt
index 5fafc6d94f..9dc8d01cc7 100644
--- a/src/plugins/styles/mac/CMakeLists.txt
+++ b/src/plugins/styles/mac/CMakeLists.txt
@@ -1,7 +1,5 @@
# Copyright (C) 2022 The Qt Company Ltd.
-# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-
-# Generated from mac.pro.
+# SPDX-License-Identifier: BSD-3-Clause
#####################################################################
## QMacStylePlugin Plugin:
@@ -21,12 +19,7 @@ qt_internal_add_plugin(QMacStylePlugin
Qt::WidgetsPrivate
)
-#### Keys ignored in scope 1:.:.:mac.pro:<TRUE>:
-# DISTFILES = "macstyle.json"
-
-# special case begin
set_target_properties(QMacStylePlugin
PROPERTIES
DISABLE_PRECOMPILE_HEADERS ON
)
-# special case end
diff --git a/src/plugins/styles/mac/main.mm b/src/plugins/styles/mac/main.mm
index 15808754ac..5f4fbd9eb8 100644
--- a/src/plugins/styles/mac/main.mm
+++ b/src/plugins/styles/mac/main.mm
@@ -4,6 +4,8 @@
#include <QtWidgets/qstyleplugin.h>
#include "qmacstyle_mac_p.h"
+#include <QtCore/private/qcore_mac_p.h>
+
QT_BEGIN_NAMESPACE
class QMacStylePlugin : public QStylePlugin
diff --git a/src/plugins/styles/mac/qmacstyle_mac.mm b/src/plugins/styles/mac/qmacstyle_mac.mm
index 3646e16c8a..247e62bc2a 100644
--- a/src/plugins/styles/mac/qmacstyle_mac.mm
+++ b/src/plugins/styles/mac/qmacstyle_mac.mm
@@ -160,46 +160,7 @@ const int pushButtonBevelRectOffsets[3] = {
QVector<QPointer<QObject> > QMacStylePrivate::scrollBars;
-bool isDarkMode() { return QGuiApplicationPrivate::platformTheme()->appearance() == QPlatformTheme::Appearance::Dark; }
-
-// Title bar gradient colors for Lion were determined by inspecting PSDs exported
-// using CoreUI's CoreThemeDocument; there is no public API to retrieve them
-
-static QLinearGradient titlebarGradientActive()
-{
- static QLinearGradient darkGradient = [](){
- QLinearGradient gradient;
- // FIXME: colors are chosen somewhat arbitrarily and could be fine-tuned,
- // or ideally determined by calling a native API.
- gradient.setColorAt(0, QColor(47, 47, 47));
- return gradient;
- }();
- static QLinearGradient lightGradient = [](){
- QLinearGradient gradient;
- gradient.setColorAt(0, QColor(235, 235, 235));
- gradient.setColorAt(0.5, QColor(210, 210, 210));
- gradient.setColorAt(0.75, QColor(195, 195, 195));
- gradient.setColorAt(1, QColor(180, 180, 180));
- return gradient;
- }();
- return isDarkMode() ? darkGradient : lightGradient;
-}
-
-static QLinearGradient titlebarGradientInactive()
-{
- static QLinearGradient darkGradient = [](){
- QLinearGradient gradient;
- gradient.setColorAt(1, QColor(42, 42, 42));
- return gradient;
- }();
- static QLinearGradient lightGradient = [](){
- QLinearGradient gradient;
- gradient.setColorAt(0, QColor(250, 250, 250));
- gradient.setColorAt(1, QColor(225, 225, 225));
- return gradient;
- }();
- return isDarkMode() ? darkGradient : lightGradient;
-}
+bool isDarkMode() { return QGuiApplicationPrivate::platformTheme()->colorScheme() == Qt::ColorScheme::Dark; }
#if QT_CONFIG(tabwidget)
/*
@@ -429,10 +390,7 @@ static bool setupSlider(NSSlider *slider, const QStyleOptionSlider *sl)
if (sl->minimum >= sl->maximum)
return false;
- // NSSlider seems to cache values based on tracking and the last layout of the
- // NSView, resulting in incorrect knob rects that break the interaction with
- // multiple sliders. So completely reinitialize the slider.
- [slider initWithFrame:sl->rect.toCGRect()];
+ slider.frame = sl->rect.toCGRect();
slider.minValue = sl->minimum;
slider.maxValue = sl->maximum;
@@ -463,14 +421,6 @@ static bool setupSlider(NSSlider *slider, const QStyleOptionSlider *sl)
// the cell for its metrics and to draw itself.
[slider layoutSubtreeIfNeeded];
- if (sl->state & QStyle::State_Sunken) {
- const CGRect knobRect = [slider.cell knobRectFlipped:slider.isFlipped];
- CGPoint pressPoint;
- pressPoint.x = CGRectGetMidX(knobRect);
- pressPoint.y = CGRectGetMidY(knobRect);
- [slider.cell startTrackingAt:pressPoint inView:slider];
- }
-
return true;
}
@@ -1792,10 +1742,6 @@ QRectF QMacStylePrivate::comboboxEditBounds(const QRectF &outerBounds, const Coc
QMacStylePrivate::QMacStylePrivate()
: backingStoreNSView(nil)
{
- if (auto *ssf = QGuiApplicationPrivate::platformTheme()->font(QPlatformTheme::SmallFont))
- smallSystemFont = *ssf;
- if (auto *msf = QGuiApplicationPrivate::platformTheme()->font(QPlatformTheme::MiniFont))
- miniSystemFont = *msf;
}
QMacStylePrivate::~QMacStylePrivate()
@@ -2090,6 +2036,14 @@ void QMacStyle::unpolish(QApplication *)
void QMacStyle::polish(QWidget* w)
{
+ Q_D(QMacStyle);
+ if (!d->smallSystemFont && QGuiApplicationPrivate::platformTheme()) {
+ if (auto *ssf = QGuiApplicationPrivate::platformTheme()->font(QPlatformTheme::SmallFont))
+ d->smallSystemFont = *ssf;
+ else
+ d->smallSystemFont = QFont();
+ }
+
if (false
#if QT_CONFIG(menu)
|| qobject_cast<QMenu*>(w)
@@ -2547,10 +2501,13 @@ int QMacStyle::pixelMetric(PixelMetric metric, const QStyleOption *opt, const QW
case PM_ToolBarFrameWidth:
ret = 1;
break;
- case PM_ScrollView_ScrollBarOverlap:
- ret = [NSScroller preferredScrollerStyle] == NSScrollerStyleOverlay ?
- pixelMetric(PM_ScrollBarExtent, opt, widget) : 0;
+ case PM_ScrollView_ScrollBarOverlap: {
+ const QStyle *realStyle = widget ? widget->style() : proxy();
+ ret = realStyle->styleHint(SH_ScrollBar_Transient, opt, widget)
+ ? realStyle->pixelMetric(PM_ScrollBarExtent, opt, widget)
+ : 0;
break;
+ }
default:
ret = QCommonStyle::pixelMetric(metric, opt, widget);
break;
@@ -3278,7 +3235,7 @@ void QMacStyle::drawPrimitive(PrimitiveElement pe, const QStyleOption *opt, QPai
NSButtonCell *triangleCell = static_cast<NSButtonCell *>(d->cocoaCell(cw));
[triangleCell setState:(opt->state & State_Open) ? NSControlStateValueOn : NSControlStateValueOff];
bool viewHasFocus = (w && w->hasFocus()) || (opt->state & State_HasFocus);
- [triangleCell setBackgroundStyle:((opt->state & State_Selected) && viewHasFocus) ? NSBackgroundStyleDark : NSBackgroundStyleLight];
+ [triangleCell setBackgroundStyle:((opt->state & State_Selected) && viewHasFocus) ? NSBackgroundStyleEmphasized : NSBackgroundStyleNormal];
d->setupNSGraphicsContext(cg, NO);
@@ -3415,17 +3372,7 @@ void QMacStyle::drawPrimitive(PrimitiveElement pe, const QStyleOption *opt, QPai
} break;
#endif // QT_CONFIG(tabbar)
case PE_PanelStatusBar: {
- // Fill the status bar with the titlebar gradient.
- QLinearGradient linearGrad;
- if (w ? qt_macWindowMainWindow(w->window()) : (opt->state & QStyle::State_Active)) {
- linearGrad = titlebarGradientActive();
- } else {
- linearGrad = titlebarGradientInactive();
- }
-
- linearGrad.setStart(0, opt->rect.top());
- linearGrad.setFinalStop(0, opt->rect.bottom());
- p->fillRect(opt->rect, linearGrad);
+ p->fillRect(opt->rect, opt->palette.window());
// Draw the black separator line at the top of the status bar.
if (w ? qt_macWindowMainWindow(w->window()) : (opt->state & QStyle::State_Active))
@@ -3906,6 +3853,7 @@ void QMacStyle::drawControl(ControlElement ce, const QStyleOption *opt, QPainter
frameRect = frameRect.adjusted(-1, 0, 1, 0);
}
break;
+ case QStyleOptionTab::Moving: // Moving tab treated like End
case QStyleOptionTab::End:
// Pressed state hack: tweak adjustments in preparation for flip below
if (isSelected || tabDirection == QMacStylePrivate::West)
@@ -3933,8 +3881,13 @@ void QMacStyle::drawControl(ControlElement ce, const QStyleOption *opt, QPainter
pb.enabled = isEnabled;
[pb highlight:isPressed];
+
// Set off state when inactive. See needsInactiveHack for when it's selected
- pb.state = (isActive && isSelected && !isPressed) ? NSControlStateValueOn : NSControlStateValueOff;
+ // On macOS 12, don't set the Off state for selected tabs as it draws a gray backgorund even when highlighted
+ if (QOperatingSystemVersion::current() > QOperatingSystemVersion::MacOSBigSur)
+ pb.state = (isActive && isSelected) ? NSControlStateValueOn : NSControlStateValueOff;
+ else
+ pb.state = (isActive && isSelected && !isPressed) ? NSControlStateValueOn : NSControlStateValueOff;
const auto drawBezelBlock = ^(CGContextRef ctx, const CGRect &r) {
CGContextClipToRect(ctx, opt->rect.toCGRect());
@@ -4101,12 +4054,7 @@ void QMacStyle::drawControl(ControlElement ce, const QStyleOption *opt, QPainter
}
// fill title bar background
- QLinearGradient linearGrad;
- linearGrad.setStart(QPointF(0, 0));
- linearGrad.setFinalStop(QPointF(0, 2 * effectiveRect.height()));
- linearGrad.setColorAt(0, opt->palette.button().color());
- linearGrad.setColorAt(1, opt->palette.dark().color());
- p->fillRect(effectiveRect, linearGrad);
+ p->fillRect(effectiveRect, opt->palette.window());
// draw horizontal line at bottom
p->setPen(opt->palette.dark().color());
@@ -4121,7 +4069,7 @@ void QMacStyle::drawControl(ControlElement ce, const QStyleOption *opt, QPainter
titleRect.width());
const auto text = p->fontMetrics().elidedText(dwOpt->title, Qt::ElideRight, titleRect.width());
- proxy()->drawItemText(p, titleRect, Qt::AlignCenter, dwOpt->palette,
+ proxy()->drawItemText(p, titleRect, Qt::AlignCenter | Qt::TextHideMnemonic, dwOpt->palette,
dwOpt->state & State_Enabled, text, QPalette::WindowText);
}
p->restore();
@@ -5181,7 +5129,8 @@ void QMacStyle::drawComplexControl(ComplexControl cc, const QStyleOptionComplex
const auto cocoaSize = d->effectiveAquaSizeConstrain(opt, widget);
const CGFloat maxExpandScale = expandedKnobWidths[cocoaSize] / knobWidths[cocoaSize];
- const bool isTransient = proxy()->styleHint(SH_ScrollBar_Transient, opt, widget);
+ const QStyle *realStyle = widget ? widget->style() : proxy();
+ const bool isTransient = realStyle->styleHint(SH_ScrollBar_Transient, opt, widget);
if (!isTransient)
d->stopAnimation(opt->styleObject);
bool wasActive = false;
@@ -5370,6 +5319,15 @@ void QMacStyle::drawComplexControl(ComplexControl cc, const QStyleOptionComplex
const CGRect knobRect = [slider.cell knobRectFlipped:slider.isFlipped];
pressPoint.x = CGRectGetMidX(knobRect);
pressPoint.y = CGRectGetMidY(knobRect);
+
+ // The only way to tell a NSSlider/NSSliderCell to render as pressed
+ // is to start tracking. But this API has some weird behaviors that
+ // we have to account for. First of all, the pressed state will not
+ // be visually reflected unless we start tracking twice. And secondly
+ // if we don't track twice, the state of one render-pass will affect
+ // the render pass of other sliders, even if we set up the shared
+ // NSSlider with a new slider value.
+ [slider.cell startTrackingAt:pressPoint inView:slider];
[slider.cell startTrackingAt:pressPoint inView:slider];
}
@@ -5474,8 +5432,12 @@ void QMacStyle::drawComplexControl(ComplexControl cc, const QStyleOptionComplex
}
});
- if (isPressed)
+ if (isPressed) {
+ // We stop twice to be on the safe side, even if one seems to be enough.
+ // See startTracking above for why we do this.
[slider.cell stopTracking:pressPoint at:pressPoint inView:slider mouseIsUp:NO];
+ [slider.cell stopTracking:pressPoint at:pressPoint inView:slider mouseIsUp:NO];
+ }
}
break;
#if QT_CONFIG(spinbox)
@@ -5537,7 +5499,7 @@ void QMacStyle::drawComplexControl(ComplexControl cc, const QStyleOptionComplex
const auto cw = QMacStylePrivate::CocoaControl(ct, cs);
auto *cc = static_cast<NSControl *>(d->cocoaControl(cw));
cc.enabled = isEnabled;
- QRectF frameRect = cw.adjustedControlFrame(combo->rect);;
+ QRectF frameRect = cw.adjustedControlFrame(combo->rect);
if (cw.type == QMacStylePrivate::Button_PopupButton) {
// Non-editable QComboBox
auto *pb = static_cast<NSPopUpButton *>(cc);
@@ -5610,16 +5572,7 @@ void QMacStyle::drawComplexControl(ComplexControl cc, const QStyleOptionComplex
const auto frameAdjust = 1.0 / p->device()->devicePixelRatio();
const auto innerFrameRect = outerFrameRect.adjusted(frameAdjust, frameAdjust, -frameAdjust, 0);
QPainterPath innerFramePath = d->windowPanelPath(innerFrameRect);
- if (isActive) {
- QLinearGradient g;
- g.setStart(QPointF(0, 0));
- g.setFinalStop(QPointF(0, 2 * opt->rect.height()));
- g.setColorAt(0, opt->palette.button().color());
- g.setColorAt(1, opt->palette.dark().color());
- p->fillPath(innerFramePath, g);
- } else {
- p->fillPath(innerFramePath, opt->palette.button());
- }
+ p->fillPath(innerFramePath, opt->palette.button());
if (titlebar->subControls & (SC_TitleBarCloseButton
| SC_TitleBarMaxButton
@@ -5682,8 +5635,8 @@ void QMacStyle::drawComplexControl(ComplexControl cc, const QStyleOptionComplex
const bool rtl = groupBox.direction == Qt::RightToLeft;
const int alignment = Qt::TextHideMnemonic | (rtl ? Qt::AlignRight : Qt::AlignLeft);
const QFont savedFont = p->font();
- if (!flat)
- p->setFont(d->smallSystemFont);
+ if (!flat && d->smallSystemFont)
+ p->setFont(*d->smallSystemFont);
proxy()->drawItemText(p, rect, alignment, groupBox.palette, groupBox.state & State_Enabled, groupBox.text, QPalette::WindowText);
if (!flat)
p->setFont(savedFont);
@@ -5966,7 +5919,7 @@ QRect QMacStyle::subControlRect(ComplexControl cc, const QStyleOptionComplex *op
qreal controlsSpacing = lastButtonRect.right() + titleBarButtonSpacing;
if (!titlebar->icon.isNull()) {
const auto iconSize = proxy()->pixelMetric(PM_SmallIconSize);
- const auto actualIconSize = titlebar->icon.actualSize(QSize(iconSize, iconSize)).width();;
+ const auto actualIconSize = titlebar->icon.actualSize(QSize(iconSize, iconSize)).width();
controlsSpacing += actualIconSize + titleBarIconTitleSpacing;
}
@@ -6047,7 +6000,9 @@ QRect QMacStyle::subControlRect(ComplexControl cc, const QStyleOptionComplex *op
const int margin = flat || hasNoText ? 0 : 9;
ret = groupBox->rect.adjusted(margin, 0, -margin, 0);
- const QFontMetricsF fm = flat || fontIsSet ? QFontMetricsF(groupBox->fontMetrics) : QFontMetricsF(d->smallSystemFont);
+ const QFontMetricsF fm = flat || fontIsSet || !d->smallSystemFont
+ ? QFontMetricsF(groupBox->fontMetrics)
+ : QFontMetricsF(*d->smallSystemFont);
const QSizeF s = fm.size(Qt::AlignHCenter | Qt::AlignVCenter, qt_mac_removeMnemonics(groupBox->text), 0, nullptr);
const int tw = qCeil(s.width());
const int h = qCeil(fm.height());
diff --git a/src/plugins/styles/mac/qmacstyle_mac_p_p.h b/src/plugins/styles/mac/qmacstyle_mac_p_p.h
index 8dc8d659e7..0dcfce2f0c 100644
--- a/src/plugins/styles/mac/qmacstyle_mac_p_p.h
+++ b/src/plugins/styles/mac/qmacstyle_mac_p_p.h
@@ -12,7 +12,6 @@
#include <QtCore/qlist.h>
#include <QtCore/qmap.h>
#include <QtCore/qmath.h>
-#include <QtCore/qpair.h>
#include <QtCore/qpointer.h>
#include <QtCore/qtextstream.h>
@@ -262,8 +261,7 @@ public:
mutable QHash<CocoaControl, NSView *> cocoaControls;
mutable QHash<CocoaControl, NSCell *> cocoaCells;
- QFont smallSystemFont;
- QFont miniSystemFont;
+ std::optional<QFont> smallSystemFont;
QMacKeyValueObserver appearanceObserver;
};
diff --git a/src/plugins/styles/windowsvista/CMakeLists.txt b/src/plugins/styles/modernwindows/CMakeLists.txt
index cd23ef81b8..985bce3a2d 100644
--- a/src/plugins/styles/windowsvista/CMakeLists.txt
+++ b/src/plugins/styles/modernwindows/CMakeLists.txt
@@ -1,29 +1,27 @@
# Copyright (C) 2022 The Qt Company Ltd.
-# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-
-# Generated from windowsvista.pro.
+# SPDX-License-Identifier: BSD-3-Clause
#####################################################################
## QWindowsVistaStylePlugin Plugin:
#####################################################################
-qt_internal_add_plugin(QWindowsVistaStylePlugin
- OUTPUT_NAME qwindowsvistastyle
+qt_internal_add_plugin(QModernWindowsStylePlugin
+ OUTPUT_NAME qmodernwindowsstyle
PLUGIN_TYPE styles
SOURCES
main.cpp
qwindowsvistastyle.cpp qwindowsvistastyle_p.h
+ qwindows11style.cpp qwindows11style_p.h
qwindowsvistastyle_p_p.h
- qwindowsxpstyle.cpp qwindowsxpstyle_p.h
- qwindowsxpstyle_p_p.h
+ qwindowsvistaanimation.cpp qwindowsvistaanimation_p.h
+ qwindowsthemedata.cpp qwindowsthemedata_p.h
+
LIBRARIES
gdi32
user32
uxtheme
Qt::Core
Qt::Gui
+ Qt::GuiPrivate
Qt::WidgetsPrivate
)
-
-#### Keys ignored in scope 1:.:.:windowsvista.pro:<TRUE>:
-# DISTFILES = "windowsvistastyle.json"
diff --git a/src/plugins/styles/modernwindows/main.cpp b/src/plugins/styles/modernwindows/main.cpp
new file mode 100644
index 0000000000..a4d8e60385
--- /dev/null
+++ b/src/plugins/styles/modernwindows/main.cpp
@@ -0,0 +1,36 @@
+// Copyright (C) 2017 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include <QtWidgets/private/qtwidgetsglobal_p.h>
+#include <QtWidgets/qstyleplugin.h>
+#include <QtCore/qoperatingsystemversion.h>
+#include "qwindowsvistastyle_p.h"
+#include "qwindows11style_p.h"
+
+QT_BEGIN_NAMESPACE
+
+class QModernWindowsStylePlugin : public QStylePlugin
+{
+ Q_OBJECT
+ Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QStyleFactoryInterface" FILE "modernwindowsstyles.json")
+public:
+ QStyle *create(const QString &key) override;
+};
+
+QStyle *QModernWindowsStylePlugin::create(const QString &key)
+{
+ bool isWin11OrAbove = QOperatingSystemVersion::current() >= QOperatingSystemVersion::Windows11;
+ if (isWin11OrAbove && key.compare(QLatin1String("windows11"), Qt::CaseInsensitive) == 0) {
+ return new QWindows11Style();
+ } else if (!isWin11OrAbove && key.compare(QLatin1String("windows11"), Qt::CaseInsensitive) == 0) {
+ qWarning("QWindows11Style: Style is only supported on Windows11 and above");
+ return new QWindowsVistaStyle();
+ } else if (key.compare(QLatin1String("windowsvista"), Qt::CaseInsensitive) == 0) {
+ return new QWindowsVistaStyle();
+ }
+ return nullptr;
+}
+
+QT_END_NAMESPACE
+
+#include "main.moc"
diff --git a/src/plugins/styles/modernwindows/modernwindowsstyles.json b/src/plugins/styles/modernwindows/modernwindowsstyles.json
new file mode 100644
index 0000000000..e9cef558e8
--- /dev/null
+++ b/src/plugins/styles/modernwindows/modernwindowsstyles.json
@@ -0,0 +1,3 @@
+{
+ "Keys": [ "windowsvista", "windows11" ]
+}
diff --git a/src/plugins/styles/modernwindows/qwindows11style.cpp b/src/plugins/styles/modernwindows/qwindows11style.cpp
new file mode 100644
index 0000000000..f352b7e1af
--- /dev/null
+++ b/src/plugins/styles/modernwindows/qwindows11style.cpp
@@ -0,0 +1,2116 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "qwindows11style_p.h"
+#include <qstylehints.h>
+#include <private/qstyleanimation_p.h>
+#include <private/qstylehelper_p.h>
+#include <private/qapplication_p.h>
+#include <qstyleoption.h>
+#include <qpainter.h>
+#include <QGraphicsDropShadowEffect>
+
+#include "qdrawutil.h"
+#include <chrono>
+
+QT_BEGIN_NAMESPACE
+
+const static int topLevelRoundingRadius = 8; //Radius for toplevel items like popups for round corners
+const static int secondLevelRoundingRadius = 4; //Radius for second level items like hovered menu item round corners
+
+enum WINUI3Color {
+ subtleHighlightColor, //Subtle highlight based on alpha used for hovered elements
+ subtlePressedColor, //Subtle highlight based on alpha used for pressed elements
+ frameColorLight, //Color of frame around flyouts and controls except for Checkbox and Radiobutton
+ frameColorStrong, //Color of frame around Checkbox and Radiobuttons
+ controlStrongFill, //Color of controls with strong filling such as the right side of a slider
+ controlStrokeSecondary,
+ controlStrokePrimary,
+ controlFillTertiary, //Color of filled sunken controls
+ controlFillSecondary, //Color of filled hovered controls
+ menuPanelFill, //Color of menu panel
+ textOnAccentPrimary, //Color of text on controls filled in accent color
+ textOnAccentSecondary, //Color of text of sunken controls in accent color
+ controlTextSecondary, //Color of text of sunken controls
+ controlStrokeOnAccentSecondary, //Color of frame around Buttons in accent color
+ controlFillSolid, //Color for solid fill
+ surfaceStroke, //Color of MDI window frames
+};
+
+const static QColor WINUI3ColorsLight [] {
+ QColor(0x00,0x00,0x00,0x09), //subtleHighlightColor
+ QColor(0x00,0x00,0x00,0x06), //subtlePressedColor
+ QColor(0x00,0x00,0x00,0x0F), //frameColorLight
+ QColor(0x00,0x00,0x00,0x9c), //frameColorStrong
+ QColor(0x00,0x00,0x00,0x72), //controlStrongFill
+ QColor(0x00,0x00,0x00,0x29), //controlStrokeSecondary
+ QColor(0x00,0x00,0x00,0x14), //controlStrokePrimary
+ QColor(0xF9,0xF9,0xF9,0x00), //controlFillTertiary
+ QColor(0xF9,0xF9,0xF9,0x80), //controlFillSecondary
+ QColor(0xFF,0xFF,0xFF,0xFF), //menuPanelFill
+ QColor(0xFF,0xFF,0xFF,0xFF), //textOnAccentPrimary
+ QColor(0xFF,0xFF,0xFF,0x7F), //textOnAccentSecondary
+ QColor(0x00,0x00,0x00,0x7F), //controlTextSecondary
+ QColor(0x00,0x00,0x00,0x66), //controlStrokeOnAccentSecondary
+ QColor(0xFF,0xFF,0xFF,0xFF), //controlFillSolid
+ QColor(0x75,0x75,0x75,0x66), //surfaceStroke
+};
+
+const static QColor WINUI3ColorsDark[] {
+ QColor(0xFF,0xFF,0xFF,0x0F), //subtleHighlightColor
+ QColor(0xFF,0xFF,0xFF,0x0A), //subtlePressedColor
+ QColor(0xFF,0xFF,0xFF,0x12), //frameColorLight
+ QColor(0xFF,0xFF,0xFF,0x8B), //frameColorStrong
+ QColor(0xFF,0xFF,0xFF,0x8B), //controlStrongFill
+ QColor(0xFF,0xFF,0xFF,0x18), //controlStrokeSecondary
+ QColor(0xFF,0xFF,0xFF,0x12), //controlStrokePrimary
+ QColor(0xF9,0xF9,0xF9,0x00), //controlFillTertiary
+ QColor(0xF9,0xF9,0xF9,0x80), //controlFillSecondary
+ QColor(0x0F,0x0F,0x0F,0xFF), //menuPanelFill
+ QColor(0x00,0x00,0x00,0xFF), //textOnAccentPrimary
+ QColor(0x00,0x00,0x00,0x80), //textOnAccentSecondary
+ QColor(0xFF,0xFF,0xFF,0x87), //controlTextSecondary
+ QColor(0xFF,0xFF,0xFF,0x14), //controlStrokeOnAccentSecondary
+ QColor(0x45,0x45,0x45,0xFF), //controlFillSolid
+ QColor(0x75,0x75,0x75,0x66), //surfaceStroke
+};
+
+const static QColor* WINUI3Colors[] {
+ WINUI3ColorsLight,
+ WINUI3ColorsDark
+};
+
+const QColor shellCloseButtonColor(0xC4,0x2B,0x1C,0xFF); //Color of close Button in Titlebar
+
+#if QT_CONFIG(toolbutton)
+static void drawArrow(const QStyle *style, const QStyleOptionToolButton *toolbutton,
+ const QRect &rect, QPainter *painter, const QWidget *widget = nullptr)
+{
+ QStyle::PrimitiveElement pe;
+ switch (toolbutton->arrowType) {
+ case Qt::LeftArrow:
+ pe = QStyle::PE_IndicatorArrowLeft;
+ break;
+ case Qt::RightArrow:
+ pe = QStyle::PE_IndicatorArrowRight;
+ break;
+ case Qt::UpArrow:
+ pe = QStyle::PE_IndicatorArrowUp;
+ break;
+ case Qt::DownArrow:
+ pe = QStyle::PE_IndicatorArrowDown;
+ break;
+ default:
+ return;
+ }
+ QStyleOption arrowOpt = *toolbutton;
+ arrowOpt.rect = rect;
+ style->drawPrimitive(pe, &arrowOpt, painter, widget);
+}
+#endif // QT_CONFIG(toolbutton)
+/*!
+ \class QWindows11Style
+ \brief The QWindows11Style class provides a look and feel suitable for applications on Microsoft Windows 11.
+ \since 6.6
+ \ingroup appearance
+ \inmodule QtWidgets
+ \internal
+
+ \warning This style is only available on the Windows 11 platform and above.
+
+ \sa QWindows11Style QWindowsVistaStyle, QMacStyle, QFusionStyle
+*/
+
+/*!
+ Constructs a QWindows11Style object.
+*/
+QWindows11Style::QWindows11Style() : QWindowsVistaStyle(*new QWindows11StylePrivate)
+{
+}
+
+/*!
+ \internal
+ Constructs a QWindows11Style object.
+*/
+QWindows11Style::QWindows11Style(QWindows11StylePrivate &dd) : QWindowsVistaStyle(dd)
+{
+}
+
+/*!
+ Destructor.
+*/
+QWindows11Style::~QWindows11Style() = default;
+
+/*!
+ \internal
+ see drawPrimitive for comments on the animation support
+
+ */
+void QWindows11Style::drawComplexControl(ComplexControl control, const QStyleOptionComplex *option,
+ QPainter *painter, const QWidget *widget) const
+{
+ QWindows11StylePrivate *d = const_cast<QWindows11StylePrivate*>(d_func());
+
+ State state = option->state;
+ SubControls sub = option->subControls;
+ State flags = option->state;
+ if (widget && widget->testAttribute(Qt::WA_UnderMouse) && widget->isActiveWindow())
+ flags |= State_MouseOver;
+
+ painter->save();
+ painter->setRenderHint(QPainter::Antialiasing);
+ if (d->transitionsEnabled()) {
+ if (control == CC_Slider) {
+ if (const auto *slider = qstyleoption_cast<const QStyleOptionSlider *>(option)) {
+ QObject *styleObject = option->styleObject; // Can be widget or qquickitem
+
+ QRectF thumbRect = proxy()->subControlRect(CC_Slider, option, SC_SliderHandle, widget);
+ auto center = thumbRect.center();
+ const qreal outerRadius = qMin(8.0, (slider->orientation == Qt::Horizontal ? thumbRect.height() / 2.0 : thumbRect.width() / 2.0) - 1);
+
+ thumbRect.setWidth(outerRadius);
+ thumbRect.setHeight(outerRadius);
+ thumbRect.moveCenter(center);
+ QPointF cursorPos = widget ? widget->mapFromGlobal(QCursor::pos()) : QPointF();
+ bool isInsideHandle = thumbRect.contains(cursorPos);
+
+ bool oldIsInsideHandle = styleObject->property("_q_insidehandle").toBool();
+ int oldState = styleObject->property("_q_stylestate").toInt();
+ int oldActiveControls = styleObject->property("_q_stylecontrols").toInt();
+
+ QRectF oldRect = styleObject->property("_q_stylerect").toRect();
+ styleObject->setProperty("_q_insidehandle", isInsideHandle);
+ styleObject->setProperty("_q_stylestate", int(option->state));
+ styleObject->setProperty("_q_stylecontrols", int(option->activeSubControls));
+ styleObject->setProperty("_q_stylerect", option->rect);
+ if (option->styleObject->property("_q_end_radius").isNull())
+ option->styleObject->setProperty("_q_end_radius", outerRadius * 0.43);
+
+ bool doTransition = (((state & State_Sunken) != (oldState & State_Sunken)
+ || ((oldIsInsideHandle) != (isInsideHandle))
+ || oldActiveControls != int(option->activeSubControls))
+ && state & State_Enabled);
+
+ if (oldRect != option->rect) {
+ doTransition = false;
+ d->stopAnimation(styleObject);
+ styleObject->setProperty("_q_inner_radius", outerRadius * 0.43);
+ }
+
+ if (doTransition) {
+ QNumberStyleAnimation *t = new QNumberStyleAnimation(styleObject);
+ t->setStartValue(styleObject->property("_q_inner_radius").toFloat());
+ if (state & State_Sunken)
+ t->setEndValue(outerRadius * 0.29);
+ else if (isInsideHandle)
+ t->setEndValue(outerRadius * 0.71);
+ else
+ t->setEndValue(outerRadius * 0.43);
+
+ styleObject->setProperty("_q_end_radius", t->endValue());
+
+ t->setStartTime(d->animationTime());
+ t->setDuration(150);
+ d->startAnimation(t);
+ }
+ }
+ }
+ }
+
+ switch (control) {
+#if QT_CONFIG(spinbox)
+ case CC_SpinBox:
+ if (const QStyleOptionSpinBox *sb = qstyleoption_cast<const QStyleOptionSpinBox *>(option)) {
+ if (sb->frame && (sub & SC_SpinBoxFrame)) {
+ painter->save();
+ QRegion clipRegion = option->rect;
+ clipRegion -= option->rect.adjusted(2, 2, -2, -2);
+ painter->setClipRegion(clipRegion);
+ QColor lineColor = state & State_HasFocus ? option->palette.accent().color() : QColor(0,0,0);
+ painter->setPen(QPen(lineColor));
+ painter->drawLine(option->rect.bottomLeft() + QPointF(7,-0.5), option->rect.bottomRight() + QPointF(-7,-0.5));
+ painter->restore();
+ }
+ QRectF frameRect = option->rect;
+ frameRect.adjust(0.5,0.5,-0.5,-0.5);
+ QBrush fillColor = option->palette.brush(QPalette::Base);
+ painter->setBrush(fillColor);
+ painter->setPen(QPen(highContrastTheme == true ? sb->palette.buttonText().color() : WINUI3Colors[colorSchemeIndex][frameColorLight]));
+ painter->drawRoundedRect(frameRect.adjusted(2,2,-2,-2), secondLevelRoundingRadius, secondLevelRoundingRadius);
+ QPoint mousePos = widget ? widget->mapFromGlobal(QCursor::pos()) : QPoint();
+ QColor hoverColor = WINUI3Colors[colorSchemeIndex][subtleHighlightColor];
+ if (sub & SC_SpinBoxEditField) {
+ QRect rect = proxy()->subControlRect(CC_SpinBox, option, SC_SpinBoxEditField, widget).adjusted(0, 0, 0, 1);
+ if (rect.contains(mousePos) && !(state & State_HasFocus)) {
+ QBrush fillColor = QBrush(WINUI3Colors[colorSchemeIndex][subtleHighlightColor]);
+ painter->setBrush(fillColor);
+ painter->setPen(Qt::NoPen);
+ painter->drawRoundedRect(option->rect.adjusted(2,2,-2,-2), secondLevelRoundingRadius, secondLevelRoundingRadius);
+ }
+ }
+ if (sub & SC_SpinBoxUp) {
+ QRect rect = proxy()->subControlRect(CC_SpinBox, option, SC_SpinBoxUp, widget).adjusted(0, 0, 0, 1);
+ float scale = rect.width() >= 16 ? 1.0 : rect.width()/16.0;
+ if (rect.contains(mousePos)) {
+ painter->setPen(Qt::NoPen);
+ painter->setBrush(QBrush(hoverColor));
+ painter->drawRoundedRect(rect.adjusted(1,1,-1,-1),secondLevelRoundingRadius, secondLevelRoundingRadius);
+ }
+ painter->save();
+ painter->translate(rect.center());
+ painter->scale(scale,scale);
+ painter->translate(-rect.center());
+ painter->setFont(assetFont);
+ painter->setPen(sb->palette.buttonText().color());
+ painter->setBrush(Qt::NoBrush);
+ painter->drawText(rect,"\uE018", Qt::AlignVCenter | Qt::AlignHCenter);
+ painter->restore();
+ }
+ if (sub & SC_SpinBoxDown) {
+ QRect rect = proxy()->subControlRect(CC_SpinBox, option, SC_SpinBoxDown, widget);
+ float scale = rect.width() >= 16 ? 1.0 : rect.width()/16.0;
+ if (rect.contains(mousePos)) {
+ painter->setPen(Qt::NoPen);
+ painter->setBrush(QBrush(hoverColor));
+ painter->drawRoundedRect(rect.adjusted(1,1,-1,-1), secondLevelRoundingRadius, secondLevelRoundingRadius);
+ }
+ painter->save();
+ painter->translate(rect.center());
+ painter->scale(scale,scale);
+ painter->translate(-rect.center());
+ painter->setFont(assetFont);
+ painter->setPen(sb->palette.buttonText().color());
+ painter->setBrush(Qt::NoBrush);
+ painter->drawText(rect,"\uE019", Qt::AlignVCenter | Qt::AlignHCenter);
+ painter->restore();
+ }
+ }
+ break;
+#endif // QT_CONFIG(spinbox)
+#if QT_CONFIG(slider)
+ case CC_Slider:
+ if (const auto *slider = qstyleoption_cast<const QStyleOptionSlider *>(option)) {
+ QRectF slrect = slider->rect;
+ QRegion tickreg = slrect.toRect();
+
+ if (sub & SC_SliderGroove) {
+ QRectF rect = proxy()->subControlRect(CC_Slider, option, SC_SliderGroove, widget);
+ QRectF handleRect = proxy()->subControlRect(CC_Slider, option, SC_SliderHandle, widget);
+ QPointF handlePos = handleRect.center();
+ QRectF leftRect;
+ QRectF rightRect;
+
+ if (slider->orientation == Qt::Horizontal) {
+ rect = QRect(slrect.left(), rect.center().y() - 2, slrect.width() - 5, 4);
+ leftRect = QRect(rect.left(), rect.top(), (handlePos.x() - rect.left()), rect.height());
+ rightRect = QRect(handlePos.x(), rect.top(), (rect.width() - handlePos.x()), rect.height());
+ } else {
+ rect = QRect(rect.center().x() - 2, slrect.top(), 4, slrect.height() - 5);
+ rightRect = QRect(rect.left(), rect.top(), rect.width(), (handlePos.y() - rect.top()));
+ leftRect = QRect(rect.left(), handlePos.y(), rect.width(), (rect.height() - handlePos.y()));
+ }
+
+ painter->setPen(Qt::NoPen);
+ painter->setBrush(option->palette.accent());
+ painter->drawRoundedRect(leftRect,1,1);
+ painter->setBrush(QBrush(WINUI3Colors[colorSchemeIndex][controlStrongFill]));
+ painter->drawRoundedRect(rightRect,1,1);
+
+ painter->setPen(QPen(highContrastTheme == true ? slider->palette.buttonText().color() : WINUI3Colors[colorSchemeIndex][frameColorLight]));
+ painter->setBrush(Qt::NoBrush);
+ painter->drawRoundedRect(leftRect,1.5,1.5);
+ painter->drawRoundedRect(rightRect,1.5,1.5);
+
+ tickreg -= rect.toRect();
+ }
+ if (sub & SC_SliderTickmarks) {
+ int tickOffset = proxy()->pixelMetric(PM_SliderTickmarkOffset, slider, widget);
+ int ticks = slider->tickPosition;
+ int thickness = proxy()->pixelMetric(PM_SliderControlThickness, slider, widget);
+ int len = proxy()->pixelMetric(PM_SliderLength, slider, widget);
+ int available = proxy()->pixelMetric(PM_SliderSpaceAvailable, slider, widget);
+ int interval = slider->tickInterval;
+ if (interval <= 0) {
+ interval = slider->singleStep;
+ if (QStyle::sliderPositionFromValue(slider->minimum, slider->maximum, interval,
+ available)
+ - QStyle::sliderPositionFromValue(slider->minimum, slider->maximum,
+ 0, available) < 3)
+ interval = slider->pageStep;
+ }
+ if (!interval)
+ interval = 1;
+ int fudge = len / 2;
+ int pos;
+ int bothOffset = (ticks & QSlider::TicksAbove && ticks & QSlider::TicksBelow) ? 1 : 0;
+ painter->setPen(slider->palette.text().color());
+ QVarLengthArray<QLine, 32> lines;
+ int v = slider->minimum;
+ while (v <= slider->maximum + 1) {
+ if (v == slider->maximum + 1 && interval == 1)
+ break;
+ const int v_ = qMin(v, slider->maximum);
+ int tickLength = (v_ == slider->minimum || v_ >= slider->maximum) ? 4 : 3;
+ pos = QStyle::sliderPositionFromValue(slider->minimum, slider->maximum,
+ v_, available) + fudge;
+ if (slider->orientation == Qt::Horizontal) {
+ if (ticks & QSlider::TicksAbove) {
+ lines.append(QLine(pos, tickOffset - 1 - bothOffset,
+ pos, tickOffset - 1 - bothOffset - tickLength));
+ }
+
+ if (ticks & QSlider::TicksBelow) {
+ lines.append(QLine(pos, tickOffset + thickness + bothOffset,
+ pos, tickOffset + thickness + bothOffset + tickLength));
+ }
+ } else {
+ if (ticks & QSlider::TicksAbove) {
+ lines.append(QLine(tickOffset - 1 - bothOffset, pos,
+ tickOffset - 1 - bothOffset - tickLength, pos));
+ }
+
+ if (ticks & QSlider::TicksBelow) {
+ lines.append(QLine(tickOffset + thickness + bothOffset, pos,
+ tickOffset + thickness + bothOffset + tickLength, pos));
+ }
+ }
+ // in the case where maximum is max int
+ int nextInterval = v + interval;
+ if (nextInterval < v)
+ break;
+ v = nextInterval;
+ }
+ if (!lines.isEmpty()) {
+ painter->save();
+ painter->translate(slrect.topLeft());
+ painter->drawLines(lines.constData(), lines.size());
+ painter->restore();
+ }
+ }
+ if (sub & SC_SliderHandle) {
+ if (const auto *slider = qstyleoption_cast<const QStyleOptionSlider *>(option)) {
+ const QRectF rect = proxy()->subControlRect(CC_Slider, option, SC_SliderHandle, widget);
+ const QPointF center = rect.center();
+
+ const QNumberStyleAnimation* animation = qobject_cast<QNumberStyleAnimation*>(d->animation(option->styleObject));
+
+ if (animation != nullptr)
+ option->styleObject->setProperty("_q_inner_radius", animation->currentValue());
+ else
+ option->styleObject->setProperty("_q_inner_radius", option->styleObject->property("_q_end_radius"));
+
+ const qreal outerRadius = qMin(8.0,(slider->orientation == Qt::Horizontal ? rect.height() / 2.0 : rect.width() / 2.0) - 1);
+ const float innerRadius = option->styleObject->property("_q_inner_radius").toFloat();
+ painter->setRenderHint(QPainter::Antialiasing, true);
+ painter->setPen(Qt::NoPen);
+ painter->setBrush(QBrush(WINUI3Colors[colorSchemeIndex][controlFillSolid]));
+ painter->drawEllipse(center, outerRadius, outerRadius);
+ painter->setBrush(option->palette.accent());
+ painter->drawEllipse(center, innerRadius, innerRadius);
+
+ painter->setPen(QPen(WINUI3Colors[colorSchemeIndex][controlStrokeSecondary]));
+ painter->setBrush(Qt::NoBrush);
+ painter->drawEllipse(center, outerRadius + 0.5, outerRadius + 0.5);
+ painter->drawEllipse(center, innerRadius + 0.5, innerRadius + 0.5);
+ }
+ }
+ if (slider->state & State_HasFocus) {
+ QStyleOptionFocusRect fropt;
+ fropt.QStyleOption::operator=(*slider);
+ fropt.rect = subElementRect(SE_SliderFocusRect, slider, widget);
+ proxy()->drawPrimitive(PE_FrameFocusRect, &fropt, painter, widget);
+ }
+ }
+ break;
+#endif
+#if QT_CONFIG(combobox)
+ case CC_ComboBox:
+ if (const QStyleOptionComboBox *combobox = qstyleoption_cast<const QStyleOptionComboBox *>(option)) {
+ QBrush fillColor = state & State_MouseOver && !(state & State_HasFocus) ? QBrush(WINUI3Colors[colorSchemeIndex][subtleHighlightColor]) : option->palette.brush(QPalette::Base);
+ QRectF rect = option->rect.adjusted(2,2,-2,-2);
+ painter->setBrush(fillColor);
+ painter->setPen(Qt::NoPen);
+ painter->drawRoundedRect(rect, secondLevelRoundingRadius, secondLevelRoundingRadius);
+
+ rect.adjust(0.5,0.5,-0.5,-0.5);
+ painter->setBrush(Qt::NoBrush);
+ painter->setPen(highContrastTheme == true ? combobox->palette.buttonText().color() : WINUI3Colors[colorSchemeIndex][frameColorLight]);
+ painter->drawRoundedRect(rect, secondLevelRoundingRadius, secondLevelRoundingRadius);
+ if (sub & SC_ComboBoxArrow) {
+ QRectF rect = proxy()->subControlRect(CC_ComboBox, option, SC_ComboBoxArrow, widget).adjusted(-4, 0, -4, 1);
+ painter->setFont(assetFont);
+ painter->setPen(combobox->palette.text().color());
+ painter->drawText(rect,"\uE019", Qt::AlignVCenter | Qt::AlignHCenter);
+ }
+ if (combobox->editable) {
+ QColor lineColor = state & State_HasFocus ? option->palette.accent().color() : QColor(0,0,0);
+ painter->setPen(QPen(lineColor));
+ painter->drawLine(rect.bottomLeft() + QPoint(2,1), rect.bottomRight() + QPoint(-2,1));
+ if (state & State_HasFocus)
+ painter->drawLine(rect.bottomLeft() + QPoint(3,2), rect.bottomRight() + QPoint(-3,2));
+ }
+ }
+ break;
+#endif // QT_CONFIG(combobox)
+ case QStyle::CC_ScrollBar:
+ if (const QStyleOptionSlider *scrollbar = qstyleoption_cast<const QStyleOptionSlider *>(option)) {
+ QRectF rect = scrollbar->rect;
+ QPointF center = rect.center();
+
+ if (scrollbar->orientation == Qt::Vertical && rect.width()>24)
+ rect.marginsRemoved(QMargins(0,2,2,2));
+ else if (scrollbar->orientation == Qt::Horizontal && rect.height()>24)
+ rect.marginsRemoved(QMargins(2,0,2,2));
+
+ if (state & State_MouseOver) {
+ if (scrollbar->orientation == Qt::Vertical && rect.width()>24)
+ rect.setWidth(rect.width()/2);
+ else if (scrollbar->orientation == Qt::Horizontal && rect.height()>24)
+ rect.setHeight(rect.height()/2);
+ rect.moveCenter(center);
+ painter->setBrush(scrollbar->palette.base());
+ painter->setPen(Qt::NoPen);
+ painter->drawRoundedRect(rect, topLevelRoundingRadius, topLevelRoundingRadius);
+
+ painter->setBrush(Qt::NoBrush);
+ painter->setPen(WINUI3Colors[colorSchemeIndex][frameColorLight]);
+ painter->drawRoundedRect(rect.marginsRemoved(QMarginsF(0.5,0.5,0.5,0.5)), topLevelRoundingRadius + 0.5, topLevelRoundingRadius + 0.5);
+ }
+ if (sub & SC_ScrollBarSlider) {
+ QRectF rect = proxy()->subControlRect(CC_ScrollBar, option, SC_ScrollBarSlider, widget);
+ QPointF center = rect.center();
+ if (flags & State_MouseOver) {
+ if (scrollbar->orientation == Qt::Vertical)
+ rect.setWidth(rect.width()/2);
+ else
+ rect.setHeight(rect.height()/2);
+ }
+ else {
+ if (scrollbar->orientation == Qt::Vertical)
+ rect.setWidth(1);
+ else
+ rect.setHeight(1);
+
+ }
+ rect.moveCenter(center);
+ painter->setBrush(Qt::gray);
+ painter->setPen(Qt::NoPen);
+ painter->drawRoundedRect(rect, secondLevelRoundingRadius, secondLevelRoundingRadius);
+ }
+ if (sub & SC_ScrollBarAddLine) {
+ QRectF rect = proxy()->subControlRect(CC_ScrollBar, option, SC_ScrollBarAddLine, widget);
+ if (flags & State_MouseOver) {
+ painter->setFont(QFont("Segoe Fluent Icons",6));
+ painter->setPen(Qt::gray);
+ if (scrollbar->orientation == Qt::Vertical)
+ painter->drawText(rect,"\uEDDC", Qt::AlignVCenter | Qt::AlignHCenter);
+ else
+ painter->drawText(rect,"\uEDDA", Qt::AlignVCenter | Qt::AlignHCenter);
+ }
+ }
+ if (sub & SC_ScrollBarSubLine) {
+ QRectF rect = proxy()->subControlRect(CC_ScrollBar, option, SC_ScrollBarSubLine, widget);
+ if (flags & State_MouseOver) {
+ painter->setPen(Qt::gray);
+ if (scrollbar->orientation == Qt::Vertical)
+ painter->drawText(rect,"\uEDDB", Qt::AlignVCenter | Qt::AlignHCenter);
+ else
+ painter->drawText(rect,"\uEDD9", Qt::AlignVCenter | Qt::AlignHCenter);
+ }
+ }
+ }
+ break;
+ case CC_TitleBar:
+ if (const auto* titlebar = qstyleoption_cast<const QStyleOptionTitleBar*>(option)) {
+ painter->setPen(Qt::NoPen);
+ painter->setPen(QPen(WINUI3Colors[colorSchemeIndex][surfaceStroke]));
+ painter->setBrush(titlebar->palette.button());
+ painter->drawRect(titlebar->rect);
+
+ // draw title
+ QRect textRect = proxy()->subControlRect(CC_TitleBar, titlebar, SC_TitleBarLabel, widget);
+ painter->setPen(titlebar->palette.text().color());
+ // Note workspace also does elliding but it does not use the correct font
+ QString title = painter->fontMetrics().elidedText(titlebar->text, Qt::ElideRight, textRect.width() - 14);
+ painter->drawText(textRect.adjusted(1, 1, 1, 1), title, QTextOption(Qt::AlignHCenter | Qt::AlignVCenter));
+
+ QFont buttonFont = QFont(assetFont);
+ buttonFont.setPointSize(8);
+ // min button
+ if ((titlebar->subControls & SC_TitleBarMinButton) && (titlebar->titleBarFlags & Qt::WindowMinimizeButtonHint) &&
+ !(titlebar->titleBarState& Qt::WindowMinimized)) {
+ const QRect minButtonRect = proxy()->subControlRect(CC_TitleBar, titlebar, SC_TitleBarMinButton, widget);
+ if (minButtonRect.isValid()) {
+ bool hover = (titlebar->activeSubControls & SC_TitleBarMinButton) && (titlebar->state & State_MouseOver);
+ if (hover)
+ painter->fillRect(minButtonRect,WINUI3Colors[colorSchemeIndex][subtleHighlightColor]);
+ const QString textToDraw("\uE921");
+ painter->setPen(QPen(titlebar->palette.text().color()));
+ painter->setFont(buttonFont);
+ painter->drawText(minButtonRect, Qt::AlignVCenter | Qt::AlignHCenter, textToDraw);
+ }
+ }
+ // max button
+ if ((titlebar->subControls & SC_TitleBarMaxButton) && (titlebar->titleBarFlags & Qt::WindowMaximizeButtonHint) &&
+ !(titlebar->titleBarState & Qt::WindowMaximized)) {
+ const QRectF maxButtonRect = proxy()->subControlRect(CC_TitleBar, titlebar, SC_TitleBarMaxButton, widget);
+ if (maxButtonRect.isValid()) {
+ bool hover = (titlebar->activeSubControls & SC_TitleBarMaxButton) && (titlebar->state & State_MouseOver);
+ if (hover)
+ painter->fillRect(maxButtonRect,WINUI3Colors[colorSchemeIndex][subtleHighlightColor]);
+ const QString textToDraw("\uE922");
+ painter->setPen(QPen(titlebar->palette.text().color()));
+ painter->setFont(buttonFont);
+ painter->drawText(maxButtonRect, Qt::AlignVCenter | Qt::AlignHCenter, textToDraw);
+ }
+ }
+
+ // close button
+ if ((titlebar->subControls & SC_TitleBarCloseButton) && (titlebar->titleBarFlags & Qt::WindowSystemMenuHint)) {
+ const QRect closeButtonRect = proxy()->subControlRect(CC_TitleBar, titlebar, SC_TitleBarCloseButton, widget);
+ if (closeButtonRect.isValid()) {
+ bool hover = (titlebar->activeSubControls & SC_TitleBarCloseButton) && (titlebar->state & State_MouseOver);
+ if (hover)
+ painter->fillRect(closeButtonRect,shellCloseButtonColor);
+ const QString textToDraw("\uE8BB");
+ painter->setPen(QPen(hover ? titlebar->palette.highlightedText().color() : titlebar->palette.text().color()));
+ painter->setFont(buttonFont);
+ painter->drawText(closeButtonRect, Qt::AlignVCenter | Qt::AlignHCenter, textToDraw);
+ }
+ }
+
+ // normalize button
+ if ((titlebar->subControls & SC_TitleBarNormalButton) &&
+ (((titlebar->titleBarFlags & Qt::WindowMinimizeButtonHint) &&
+ (titlebar->titleBarState & Qt::WindowMinimized)) ||
+ ((titlebar->titleBarFlags & Qt::WindowMaximizeButtonHint) &&
+ (titlebar->titleBarState & Qt::WindowMaximized)))) {
+ const QRect normalButtonRect = proxy()->subControlRect(CC_TitleBar, titlebar, SC_TitleBarNormalButton, widget);
+ if (normalButtonRect.isValid()) {
+ bool hover = (titlebar->activeSubControls & SC_TitleBarNormalButton) && (titlebar->state & State_MouseOver);
+ if (hover)
+ painter->fillRect(normalButtonRect,WINUI3Colors[colorSchemeIndex][subtleHighlightColor]);
+ const QString textToDraw("\uE923");
+ painter->setPen(QPen(titlebar->palette.text().color()));
+ painter->setFont(buttonFont);
+ painter->drawText(normalButtonRect, Qt::AlignVCenter | Qt::AlignHCenter, textToDraw);
+ }
+ }
+
+ // context help button
+ if (titlebar->subControls & SC_TitleBarContextHelpButton
+ && (titlebar->titleBarFlags & Qt::WindowContextHelpButtonHint)) {
+ const QRect contextHelpButtonRect = proxy()->subControlRect(CC_TitleBar, titlebar, SC_TitleBarContextHelpButton, widget);
+ if (contextHelpButtonRect.isValid()) {
+ bool hover = (titlebar->activeSubControls & SC_TitleBarCloseButton) && (titlebar->state & State_MouseOver);
+ if (hover)
+ painter->fillRect(contextHelpButtonRect,WINUI3Colors[colorSchemeIndex][subtleHighlightColor]);
+ const QString textToDraw("\uE897");
+ painter->setPen(QPen(titlebar->palette.text().color()));
+ painter->setFont(buttonFont);
+ painter->drawText(contextHelpButtonRect, Qt::AlignVCenter | Qt::AlignHCenter, textToDraw);
+ }
+ }
+
+ // shade button
+ if (titlebar->subControls & SC_TitleBarShadeButton && (titlebar->titleBarFlags & Qt::WindowShadeButtonHint)) {
+ const QRect shadeButtonRect = proxy()->subControlRect(CC_TitleBar, titlebar, SC_TitleBarShadeButton, widget);
+ if (shadeButtonRect.isValid()) {
+ bool hover = (titlebar->activeSubControls & SC_TitleBarShadeButton) && (titlebar->state & State_MouseOver);
+ if (hover)
+ painter->fillRect(shadeButtonRect,WINUI3Colors[colorSchemeIndex][subtleHighlightColor]);
+ const QString textToDraw("\uE010");
+ painter->setPen(QPen(titlebar->palette.text().color()));
+ painter->setFont(buttonFont);
+ painter->drawText(shadeButtonRect, Qt::AlignVCenter | Qt::AlignHCenter, textToDraw);
+ }
+ }
+
+ // unshade button
+ if (titlebar->subControls & SC_TitleBarUnshadeButton && (titlebar->titleBarFlags & Qt::WindowShadeButtonHint)) {
+ const QRect unshadeButtonRect = proxy()->subControlRect(CC_TitleBar, titlebar, SC_TitleBarUnshadeButton, widget);
+ if (unshadeButtonRect.isValid()) {
+ bool hover = (titlebar->activeSubControls & SC_TitleBarUnshadeButton) && (titlebar->state & State_MouseOver);
+ if (hover)
+ painter->fillRect(unshadeButtonRect,WINUI3Colors[colorSchemeIndex][subtleHighlightColor]);
+ const QString textToDraw("\uE011");
+ painter->setPen(QPen(titlebar->palette.text().color()));
+ painter->setFont(buttonFont);
+ painter->drawText(unshadeButtonRect, Qt::AlignVCenter | Qt::AlignHCenter, textToDraw);
+ }
+ }
+
+ // window icon for system menu
+ if ((titlebar->subControls & SC_TitleBarSysMenu) && (titlebar->titleBarFlags & Qt::WindowSystemMenuHint)) {
+ const QRect iconRect = proxy()->subControlRect(CC_TitleBar, titlebar, SC_TitleBarSysMenu, widget);
+ if (iconRect.isValid()) {
+ if (!titlebar->icon.isNull()) {
+ titlebar->icon.paint(painter, iconRect);
+ } else {
+ QStyleOption tool = *titlebar;
+ QPixmap pm = proxy()->standardIcon(SP_TitleBarMenuButton, &tool, widget).pixmap(16, 16);
+ tool.rect = iconRect;
+ painter->save();
+ proxy()->drawItemPixmap(painter, iconRect, Qt::AlignCenter, pm);
+ painter->restore();
+ }
+ }
+ }
+ }
+ break;
+ default:
+ QWindowsVistaStyle::drawComplexControl(control, option, painter, widget);
+ }
+ painter->restore();
+}
+
+void QWindows11Style::drawPrimitive(PrimitiveElement element, const QStyleOption *option,
+ QPainter *painter,
+ const QWidget *widget) const {
+ QWindows11StylePrivate *d = const_cast<QWindows11StylePrivate*>(d_func());
+
+ int state = option->state;
+ painter->save();
+ painter->setRenderHint(QPainter::Antialiasing);
+ if (d->transitionsEnabled() && (element == PE_IndicatorCheckBox || element == PE_IndicatorRadioButton)) {
+ QObject *styleObject = option->styleObject; // Can be widget or qquickitem
+ if (styleObject) {
+ int oldState = styleObject->property("_q_stylestate").toInt();
+ styleObject->setProperty("_q_stylestate", int(option->state));
+ styleObject->setProperty("_q_stylerect", option->rect);
+ bool doTransition = (((state & State_Sunken) != (oldState & State_Sunken)
+ || ((state & State_MouseOver) != (oldState & State_MouseOver))
+ || (state & State_On) != (oldState & State_On))
+ && state & State_Enabled);
+ if (doTransition) {
+ if (element == PE_IndicatorRadioButton) {
+ QNumberStyleAnimation *t = new QNumberStyleAnimation(styleObject);
+ t->setStartValue(styleObject->property("_q_inner_radius").toFloat());
+ t->setEndValue(7.0f);
+ if (option->state & State_Sunken)
+ t->setEndValue(2.0f);
+ else if (option->state & State_MouseOver && !(option->state & State_On))
+ t->setEndValue(7.0f);
+ else if (option->state & State_MouseOver && (option->state & State_On))
+ t->setEndValue(5.0f);
+ else if (option->state & State_On)
+ t->setEndValue(4.0f);
+ styleObject->setProperty("_q_end_radius", t->endValue());
+ t->setStartTime(d->animationTime());
+ t->setDuration(150);
+ d->startAnimation(t);
+ }
+ else if (element == PE_IndicatorCheckBox) {
+ if ((oldState & State_Off && state & State_On) || (oldState & State_NoChange && state & State_On)) {
+ QNumberStyleAnimation *t = new QNumberStyleAnimation(styleObject);
+ t->setStartValue(0.0f);
+ t->setEndValue(1.0f);
+ t->setStartTime(d->animationTime());
+ t->setDuration(150);
+ d->startAnimation(t);
+ }
+ }
+ }
+ }
+ } else if (!d->transitionsEnabled() && element == PE_IndicatorRadioButton) {
+ QObject *styleObject = option->styleObject; // Can be widget or qquickitem
+ if (styleObject) {
+ styleObject->setProperty("_q_end_radius",7.0);
+ if (option->state & State_Sunken)
+ styleObject->setProperty("_q_end_radius",2.0);
+ else if (option->state & State_MouseOver && !(option->state & State_On))
+ styleObject->setProperty("_q_end_radius",7.0);
+ else if (option->state & State_MouseOver && (option->state & State_On))
+ styleObject->setProperty("_q_end_radius",5.0);
+ else if (option->state & State_On)
+ styleObject->setProperty("_q_end_radius",4.0);
+ }
+ }
+
+ switch (element) {
+ case PE_PanelTipLabel: {
+ QRectF tipRect = option->rect.marginsRemoved(QMargins(1,1,1,1));
+ painter->setPen(Qt::NoPen);
+ painter->setBrush(option->palette.toolTipBase());
+ painter->drawRoundedRect(tipRect, secondLevelRoundingRadius, secondLevelRoundingRadius);
+
+ painter->setPen(highContrastTheme == true ? option->palette.buttonText().color() : WINUI3Colors[colorSchemeIndex][frameColorLight]);
+ painter->setBrush(Qt::NoBrush);
+ painter->drawRoundedRect(tipRect.marginsAdded(QMarginsF(0.5,0.5,0.5,0.5)), secondLevelRoundingRadius, secondLevelRoundingRadius);
+ break;
+ }
+ case PE_FrameTabWidget:
+ if (const QStyleOptionTabWidgetFrame *frame = qstyleoption_cast<const QStyleOptionTabWidgetFrame *>(option)) {
+ QRectF frameRect = frame->rect.marginsRemoved(QMargins(0,0,0,0));
+ painter->setPen(Qt::NoPen);
+ painter->setBrush(frame->palette.base());
+ painter->drawRoundedRect(frameRect, secondLevelRoundingRadius, secondLevelRoundingRadius);
+
+ painter->setPen(highContrastTheme == true ? frame->palette.buttonText().color() : WINUI3Colors[colorSchemeIndex][frameColorLight]);
+ painter->setBrush(Qt::NoBrush);
+ painter->drawRoundedRect(frameRect.marginsRemoved(QMarginsF(0.5,0.5,0.5,0.5)), secondLevelRoundingRadius, secondLevelRoundingRadius);
+ }
+ break;
+ case PE_FrameGroupBox:
+ if (const QStyleOptionFrame *frame = qstyleoption_cast<const QStyleOptionFrame *>(option)) {
+ QRectF frameRect = frame->rect;
+ frameRect.adjust(0.5,0.5,-0.5,-0.5);
+ painter->setPen(highContrastTheme == true ? frame->palette.buttonText().color() : WINUI3Colors[colorSchemeIndex][frameColorStrong]);
+ painter->setBrush(Qt::NoBrush);
+ if (frame->features & QStyleOptionFrame::Flat) {
+ QRect fr = frame->rect;
+ QPoint p1(fr.x(), fr.y() + 1);
+ QPoint p2(fr.x() + fr.width(), p1.y());
+ painter->drawLine(p1,p2);
+ } else {
+ painter->drawRoundedRect(frameRect.marginsRemoved(QMargins(1,1,1,1)), secondLevelRoundingRadius, secondLevelRoundingRadius);
+ }
+ }
+ break;
+ case PE_IndicatorCheckBox:
+ {
+ QNumberStyleAnimation* animation = qobject_cast<QNumberStyleAnimation*>(d->animation(option->styleObject));
+ QFontMetrics fm(assetFont);
+
+ QRectF rect = option->rect;
+ QPointF center = QPointF(rect.x() + rect.width() / 2, rect.y() + rect.height() / 2);
+ rect.setWidth(15);
+ rect.setHeight(15);
+ rect.moveCenter(center);
+
+ float clipWidth = animation != nullptr ? animation->currentValue() : 1.0f;
+ QRectF clipRect = fm.boundingRect("\uE001");
+ clipRect.moveCenter(center);
+ clipRect.setLeft(rect.x() + (rect.width() - clipRect.width()) / 2.0);
+ clipRect.setWidth(clipWidth * clipRect.width());
+
+
+ QBrush fillBrush = (option->state & State_On || option->state & State_NoChange) ? option->palette.accent() : option->palette.window();
+ if (state & State_MouseOver && (option->state & State_On || option->state & State_NoChange))
+ fillBrush.setColor(fillBrush.color().lighter(107));
+ else if (state & State_MouseOver && !(option->state & State_On || option->state & State_NoChange))
+ fillBrush.setColor(fillBrush.color().darker(107));
+ painter->setPen(Qt::NoPen);
+ painter->setBrush(fillBrush);
+ painter->drawRoundedRect(rect, secondLevelRoundingRadius, secondLevelRoundingRadius, Qt::AbsoluteSize);
+
+ painter->setPen(QPen(highContrastTheme == true ? option->palette.buttonText().color() : WINUI3Colors[colorSchemeIndex][frameColorStrong]));
+ painter->setBrush(Qt::NoBrush);
+ painter->drawRoundedRect(rect, secondLevelRoundingRadius + 0.5, secondLevelRoundingRadius + 0.5, Qt::AbsoluteSize);
+
+ painter->setFont(assetFont);
+ painter->setPen(option->palette.highlightedText().color());
+ painter->setBrush(option->palette.highlightedText().color());
+ if (option->state & State_On)
+ painter->drawText(clipRect, Qt::AlignVCenter | Qt::AlignLeft,"\uE001");
+ else if (option->state & State_NoChange)
+ painter->drawText(rect, Qt::AlignVCenter | Qt::AlignHCenter,"\uE108");
+ }
+ break;
+
+ case PE_IndicatorRadioButton:
+ {
+ if (option->styleObject->property("_q_end_radius").isNull())
+ option->styleObject->setProperty("_q_end_radius", option->state & State_On ? 4.0f :7.0f);
+ QNumberStyleAnimation* animation = qobject_cast<QNumberStyleAnimation*>(d->animation(option->styleObject));
+ if (animation != nullptr)
+ option->styleObject->setProperty("_q_inner_radius", animation->currentValue());
+ else
+ option->styleObject->setProperty("_q_inner_radius", option->styleObject->property("_q_end_radius"));
+ int innerRadius = option->styleObject->property("_q_inner_radius").toFloat();
+
+ QRectF rect = option->rect;
+ QPointF center = QPoint(rect.x() + rect.width() / 2, rect.y() + rect.height() / 2);
+ rect.setWidth(15);
+ rect.setHeight(15);
+ rect.moveCenter(center);
+ QRectF innerRect = rect;
+ innerRect.setWidth(8);
+ innerRect.setHeight(8);
+ innerRect.moveCenter(center);
+
+ painter->setPen(Qt::NoPen);
+ painter->setBrush(option->palette.accent());
+ if (option->state & State_MouseOver && option->state & State_Enabled)
+ painter->setBrush(QBrush(option->palette.accent().color().lighter(107)));
+ painter->drawEllipse(center, 7, 7);
+
+ painter->setPen(QPen(WINUI3Colors[colorSchemeIndex][frameColorStrong]));
+ painter->setBrush(Qt::NoBrush);
+ painter->drawEllipse(center, 7.5, 7.5);
+
+ painter->setPen(Qt::NoPen);
+ painter->setBrush(QBrush(option->palette.window()));
+ painter->drawEllipse(center,innerRadius, innerRadius);
+
+ painter->setPen(QPen(WINUI3Colors[colorSchemeIndex][frameColorStrong]));
+ painter->setBrush(Qt::NoBrush);
+ painter->drawEllipse(center,innerRadius + 0.5, innerRadius + 0.5);
+
+ painter->setPen(Qt::NoPen);
+ painter->setBrush(QBrush(option->palette.window()));
+ if (option->state & State_MouseOver && option->state & State_Enabled)
+ painter->setBrush(QBrush(option->palette.window().color().darker(107)));
+ painter->drawEllipse(center,innerRadius, innerRadius);
+ }
+ break;
+ case PE_PanelButtonBevel:{
+ QRectF rect = option->rect.marginsRemoved(QMargins(2,2,2,2));
+ rect.adjust(-0.5,-0.5,0.5,0.5);
+ painter->setBrush(Qt::NoBrush);
+ painter->setPen(QPen(WINUI3Colors[colorSchemeIndex][controlStrokePrimary]));
+ painter->drawRoundedRect(rect, secondLevelRoundingRadius, secondLevelRoundingRadius);
+
+ rect = option->rect.marginsRemoved(QMargins(2,2,2,2));
+ painter->setPen(Qt::NoPen);
+ if (!(state & (State_Raised)))
+ painter->setBrush(WINUI3Colors[colorSchemeIndex][controlFillTertiary]);
+ else if (state & State_MouseOver)
+ painter->setBrush(WINUI3Colors[colorSchemeIndex][controlFillSecondary]);
+ else
+ painter->setBrush(option->palette.button());
+ painter->drawRoundedRect(rect, secondLevelRoundingRadius, secondLevelRoundingRadius);
+ painter->setPen(QPen(WINUI3Colors[colorSchemeIndex][controlStrokeSecondary]));
+ if (state & State_Raised)
+ painter->drawLine(rect.bottomLeft() + QPoint(2,1), rect.bottomRight() + QPoint(-2,1));
+ }
+ break;
+ case PE_FrameDefaultButton:
+ painter->setPen(option->palette.accent().color());
+ painter->setBrush(Qt::NoBrush);
+ painter->drawRoundedRect(option->rect, secondLevelRoundingRadius, secondLevelRoundingRadius);
+ break;
+ case QStyle::PE_FrameMenu:
+ break;
+ case QStyle::PE_PanelMenu: {
+ QRect rect = option->rect;
+ QPen pen(WINUI3Colors[colorSchemeIndex][frameColorLight]);
+ painter->save();
+ painter->setPen(pen);
+ painter->setBrush(QBrush(WINUI3Colors[colorSchemeIndex][menuPanelFill]));
+ painter->setRenderHint(QPainter::Antialiasing);
+ painter->drawRoundedRect(rect.marginsRemoved(QMargins(2,2,12,2)), topLevelRoundingRadius, topLevelRoundingRadius);
+ painter->restore();
+ break;
+ }
+ case PE_PanelLineEdit:
+ if (widget && widget->objectName() == "qt_spinbox_lineedit")
+ break;
+ if (const auto *panel = qstyleoption_cast<const QStyleOptionFrame *>(option)) {
+ QBrush fillColor = state & State_MouseOver && !(state & State_HasFocus) ? QBrush(WINUI3Colors[colorSchemeIndex][subtleHighlightColor]) : option->palette.brush(QPalette::Base);
+ painter->setBrush(fillColor);
+ painter->setPen(Qt::NoPen);
+ QRectF frameRect = option->rect;
+ frameRect.adjust(0.5,0.5,-0.5,-0.5);
+ painter->drawRoundedRect(frameRect, secondLevelRoundingRadius, secondLevelRoundingRadius);
+ if (panel->lineWidth > 0)
+ proxy()->drawPrimitive(PE_FrameLineEdit, panel, painter, widget);
+ }
+ break;
+ case PE_FrameLineEdit: {
+ painter->setBrush(Qt::NoBrush);
+ painter->setPen(highContrastTheme == true ? option->palette.buttonText().color() : QPen(WINUI3Colors[colorSchemeIndex][frameColorLight]));
+ painter->drawRoundedRect(option->rect, secondLevelRoundingRadius, secondLevelRoundingRadius);
+ QRegion clipRegion = option->rect;
+ clipRegion -= option->rect.adjusted(2, 2, -2, -2);
+ painter->setClipRegion(clipRegion);
+ QColor lineColor = state & State_HasFocus ? option->palette.accent().color() : QColor(0,0,0);
+ painter->setPen(QPen(lineColor));
+ painter->drawLine(option->rect.bottomLeft() + QPointF(1,0.5), option->rect.bottomRight() + QPointF(-1,0.5));
+ }
+ break;
+ case PE_Frame: {
+ if (const auto *frame = qstyleoption_cast<const QStyleOptionFrame *>(option)) {
+ if (frame->frameShape == QFrame::NoFrame)
+ break;
+ QRectF rect = option->rect.adjusted(1,1,-1,-1);
+ if (widget && widget->inherits("QComboBoxPrivateContainer")) {
+ painter->setPen(Qt::NoPen);
+ painter->setBrush(WINUI3Colors[colorSchemeIndex][menuPanelFill]);
+ painter->drawRoundedRect(rect, secondLevelRoundingRadius, secondLevelRoundingRadius);
+
+ }
+ painter->setBrush(option->palette.base());
+ painter->setPen(Qt::NoPen);
+ painter->drawRoundedRect(rect, secondLevelRoundingRadius, secondLevelRoundingRadius);
+
+ painter->setBrush(Qt::NoBrush);
+ painter->setPen(QPen(WINUI3Colors[colorSchemeIndex][frameColorLight]));
+ painter->drawRoundedRect(rect.marginsRemoved(QMarginsF(0.5,0.5,0.5,0.5)), secondLevelRoundingRadius, secondLevelRoundingRadius);
+
+ if (widget && widget->inherits("QTextEdit")) {
+ QRegion clipRegion = option->rect;
+ QColor lineColor = state & State_HasFocus ? option->palette.accent().color() : QColor(0,0,0,255);
+ painter->setPen(QPen(lineColor));
+ painter->drawLine(option->rect.bottomLeft() + QPoint(1,-1), option->rect.bottomRight() + QPoint(-1,-1));
+ }
+ }
+ break;
+ }
+ case QStyle::PE_PanelItemViewRow:
+ if (const QStyleOptionViewItem *vopt = qstyleoption_cast<const QStyleOptionViewItem *>(option)) {
+ if ((vopt->state & State_Selected || vopt->state & State_MouseOver) && vopt->showDecorationSelected) {
+ painter->setBrush(WINUI3Colors[colorSchemeIndex][subtleHighlightColor]);
+ painter->setPen(Qt::NoPen);
+ painter->drawRoundedRect(vopt->rect.marginsRemoved(QMargins(0,2,-2,2)),2,2);
+ int offset = (widget && widget->inherits("QTreeView")) ? 2 : 0;
+ if (vopt->viewItemPosition == QStyleOptionViewItem::Beginning && option->state & State_Selected) {
+ painter->setPen(QPen(option->palette.accent().color()));
+ painter->drawLine(option->rect.x(),option->rect.y()+offset,option->rect.x(),option->rect.y() + option->rect.height()-2);
+ painter->drawLine(option->rect.x()+1,option->rect.y()+2,option->rect.x()+1,option->rect.y() + option->rect.height()-2);
+ }
+ }
+ }
+ break;
+ case QStyle::PE_Widget: {
+#if QT_CONFIG(dialogbuttonbox)
+ const QDialogButtonBox *buttonBox = nullptr;
+ if (qobject_cast<const QMessageBox *> (widget))
+ buttonBox = widget->findChild<const QDialogButtonBox *>(QLatin1String("qt_msgbox_buttonbox"));
+#if QT_CONFIG(inputdialog)
+ else if (qobject_cast<const QInputDialog *> (widget))
+ buttonBox = widget->findChild<const QDialogButtonBox *>(QLatin1String("qt_inputdlg_buttonbox"));
+#endif // QT_CONFIG(inputdialog)
+ if (buttonBox) {
+ painter->fillRect(option->rect,option->palette.window());
+ }
+#endif
+ break;
+ }
+ case QStyle::PE_FrameWindow:
+ if (const auto *frm = qstyleoption_cast<const QStyleOptionFrame *>(option)) {
+
+ QRectF rect= option->rect;
+ int fwidth = int((frm->lineWidth + frm->midLineWidth) / QWindowsStylePrivate::nativeMetricScaleFactor(widget));
+
+ QRectF bottomLeftCorner = QRectF(rect.left() + 1.0,
+ rect.bottom() - 1.0 - secondLevelRoundingRadius,
+ secondLevelRoundingRadius,
+ secondLevelRoundingRadius);
+ QRectF bottomRightCorner = QRectF(rect.right() - 1.0 - secondLevelRoundingRadius,
+ rect.bottom() - 1.0 - secondLevelRoundingRadius,
+ secondLevelRoundingRadius,
+ secondLevelRoundingRadius);
+
+ //Draw Mask
+ if (widget != nullptr) {
+ QBitmap mask(widget->width(), widget->height());
+ mask.clear();
+
+ QPainter maskPainter(&mask);
+ maskPainter.setRenderHint(QPainter::Antialiasing);
+ maskPainter.setBrush(Qt::color1);
+ maskPainter.setPen(Qt::NoPen);
+ maskPainter.drawRoundedRect(option->rect,secondLevelRoundingRadius,secondLevelRoundingRadius);
+ const_cast<QWidget*>(widget)->setMask(mask);
+ }
+
+ //Draw Window
+ painter->setPen(QPen(frm->palette.base(), fwidth));
+ painter->drawLine(QPointF(rect.left(), rect.top()),
+ QPointF(rect.left(), rect.bottom() - fwidth));
+ painter->drawLine(QPointF(rect.left() + fwidth, rect.bottom()),
+ QPointF(rect.right() - fwidth, rect.bottom()));
+ painter->drawLine(QPointF(rect.right(), rect.top()),
+ QPointF(rect.right(), rect.bottom() - fwidth));
+
+ painter->setPen(QPen(WINUI3Colors[colorSchemeIndex][surfaceStroke]));
+ painter->drawLine(QPointF(rect.left() + 0.5, rect.top() + 0.5),
+ QPointF(rect.left() + 0.5, rect.bottom() - 0.5 - secondLevelRoundingRadius));
+ painter->drawLine(QPointF(rect.left() + 0.5 + secondLevelRoundingRadius, rect.bottom() - 0.5),
+ QPointF(rect.right() - 0.5 - secondLevelRoundingRadius, rect.bottom() - 0.5));
+ painter->drawLine(QPointF(rect.right() - 0.5, rect.top() + 1.5),
+ QPointF(rect.right() - 0.5, rect.bottom() - 0.5 - secondLevelRoundingRadius));
+
+ painter->setPen(Qt::NoPen);
+ painter->setBrush(frm->palette.base());
+ painter->drawPie(bottomRightCorner.marginsAdded(QMarginsF(2.5,2.5,0.0,0.0)),
+ 270 * 16,90 * 16);
+ painter->drawPie(bottomLeftCorner.marginsAdded(QMarginsF(0.0,2.5,2.5,0.0)),
+ -90 * 16,-90 * 16);
+
+ painter->setPen(QPen(WINUI3Colors[colorSchemeIndex][surfaceStroke]));
+ painter->setBrush(Qt::NoBrush);
+ painter->drawArc(bottomRightCorner,
+ 0 * 16,-90 * 16);
+ painter->drawArc(bottomLeftCorner,
+ -90 * 16,-90 * 16);
+ }
+ break;
+ default:
+ QWindowsVistaStyle::drawPrimitive(element, option, painter, widget);
+ }
+ painter->restore();
+}
+
+/*!
+ \internal
+*/
+void QWindows11Style::drawControl(ControlElement element, const QStyleOption *option,
+ QPainter *painter, const QWidget *widget) const
+{
+ Q_D(const QWindows11Style);
+ QRect rect(option->rect);
+ State flags = option->state;
+
+ painter->save();
+ painter->setRenderHint(QPainter::Antialiasing);
+ switch (element) {
+ case QStyle::CE_TabBarTabShape:
+ if (const QStyleOptionTab *tab = qstyleoption_cast<const QStyleOptionTab *>(option)) {
+ QRectF tabRect = tab->rect.marginsRemoved(QMargins(2,2,0,0));
+ painter->setPen(Qt::NoPen);
+ painter->setBrush(tab->palette.base());
+ if (tab->state & State_MouseOver){
+ painter->setBrush(WINUI3Colors[colorSchemeIndex][subtleHighlightColor]);
+ } else if (tab->state & State_Selected) {
+ painter->setBrush(tab->palette.base());
+ } else {
+ painter->setBrush(tab->palette.window());
+ }
+ painter->drawRoundedRect(tabRect,2,2);
+
+ painter->setBrush(Qt::NoBrush);
+ painter->setPen(highContrastTheme == true ? tab->palette.buttonText().color() : WINUI3Colors[colorSchemeIndex][frameColorLight]);
+ painter->drawRoundedRect(tabRect.adjusted(0.5,0.5,-0.5,-0.5),2,2);
+
+ }
+ break;
+ case CE_ToolButtonLabel:
+ if (const QStyleOptionToolButton *toolbutton
+ = qstyleoption_cast<const QStyleOptionToolButton *>(option)) {
+ QRect rect = toolbutton->rect;
+ int shiftX = 0;
+ int shiftY = 0;
+ if (toolbutton->state & (State_Sunken | State_On)) {
+ shiftX = proxy()->pixelMetric(PM_ButtonShiftHorizontal, toolbutton, widget);
+ shiftY = proxy()->pixelMetric(PM_ButtonShiftVertical, toolbutton, widget);
+ }
+ // Arrow type always overrules and is always shown
+ bool hasArrow = toolbutton->features & QStyleOptionToolButton::Arrow;
+ if (((!hasArrow && toolbutton->icon.isNull()) && !toolbutton->text.isEmpty())
+ || toolbutton->toolButtonStyle == Qt::ToolButtonTextOnly) {
+ int alignment = Qt::AlignCenter | Qt::TextShowMnemonic;
+ if (!proxy()->styleHint(SH_UnderlineShortcut, toolbutton, widget))
+ alignment |= Qt::TextHideMnemonic;
+ rect.translate(shiftX, shiftY);
+ painter->setFont(toolbutton->font);
+ const QString text = d->toolButtonElideText(toolbutton, rect, alignment);
+ if (toolbutton->state & State_Raised)
+ painter->setPen(QPen(toolbutton->palette.buttonText().color()));
+ else
+ painter->setPen(QPen(WINUI3Colors[colorSchemeIndex][controlTextSecondary]));
+ proxy()->drawItemText(painter, rect, alignment, toolbutton->palette,
+ toolbutton->state & State_Enabled, text);
+ } else {
+ QPixmap pm;
+ QSize pmSize = toolbutton->iconSize;
+ if (!toolbutton->icon.isNull()) {
+ QIcon::State state = toolbutton->state & State_On ? QIcon::On : QIcon::Off;
+ QIcon::Mode mode;
+ if (!(toolbutton->state & State_Enabled))
+ mode = QIcon::Disabled;
+ else if ((toolbutton->state & State_MouseOver) && (toolbutton->state & State_AutoRaise))
+ mode = QIcon::Active;
+ else
+ mode = QIcon::Normal;
+ pm = toolbutton->icon.pixmap(toolbutton->rect.size().boundedTo(toolbutton->iconSize), painter->device()->devicePixelRatio(),
+ mode, state);
+ pmSize = pm.size() / pm.devicePixelRatio();
+ }
+
+ if (toolbutton->toolButtonStyle != Qt::ToolButtonIconOnly) {
+ painter->setFont(toolbutton->font);
+ QRect pr = rect,
+ tr = rect;
+ int alignment = Qt::TextShowMnemonic;
+ if (!proxy()->styleHint(SH_UnderlineShortcut, toolbutton, widget))
+ alignment |= Qt::TextHideMnemonic;
+
+ if (toolbutton->toolButtonStyle == Qt::ToolButtonTextUnderIcon) {
+ pr.setHeight(pmSize.height() + 4); //### 4 is currently hardcoded in QToolButton::sizeHint()
+ tr.adjust(0, pr.height() - 1, 0, -1);
+ pr.translate(shiftX, shiftY);
+ if (!hasArrow) {
+ proxy()->drawItemPixmap(painter, pr, Qt::AlignCenter, pm);
+ } else {
+ drawArrow(proxy(), toolbutton, pr, painter, widget);
+ }
+ alignment |= Qt::AlignCenter;
+ } else {
+ pr.setWidth(pmSize.width() + 4); //### 4 is currently hardcoded in QToolButton::sizeHint()
+ tr.adjust(pr.width(), 0, 0, 0);
+ pr.translate(shiftX, shiftY);
+ if (!hasArrow) {
+ proxy()->drawItemPixmap(painter, QStyle::visualRect(toolbutton->direction, rect, pr), Qt::AlignCenter, pm);
+ } else {
+ drawArrow(proxy(), toolbutton, pr, painter, widget);
+ }
+ alignment |= Qt::AlignLeft | Qt::AlignVCenter;
+ }
+ tr.translate(shiftX, shiftY);
+ const QString text = d->toolButtonElideText(toolbutton, tr, alignment);
+ if (toolbutton->state & State_Raised)
+ painter->setPen(QPen(toolbutton->palette.buttonText().color()));
+ else
+ painter->setPen(QPen(WINUI3Colors[colorSchemeIndex][controlTextSecondary]));
+ proxy()->drawItemText(painter, QStyle::visualRect(toolbutton->direction, rect, tr), alignment, toolbutton->palette,
+ toolbutton->state & State_Enabled, text);
+ } else {
+ rect.translate(shiftX, shiftY);
+ if (hasArrow) {
+ drawArrow(proxy(), toolbutton, rect, painter, widget);
+ } else {
+ proxy()->drawItemPixmap(painter, rect, Qt::AlignCenter, pm);
+ }
+ }
+ }
+ }
+ break;
+ case QStyle::CE_ShapedFrame:
+ if (const QStyleOptionFrame *f = qstyleoption_cast<const QStyleOptionFrame *>(option)) {
+ int frameShape = f->frameShape;
+ int frameShadow = QFrame::Plain;
+ if (f->state & QStyle::State_Sunken)
+ frameShadow = QFrame::Sunken;
+ else if (f->state & QStyle::State_Raised)
+ frameShadow = QFrame::Raised;
+
+ int lw = f->lineWidth;
+ int mlw = f->midLineWidth;
+
+ switch (frameShape) {
+ case QFrame::Box:
+ if (frameShadow == QFrame::Plain)
+ qDrawPlainRoundedRect(painter, f->rect, secondLevelRoundingRadius, secondLevelRoundingRadius, highContrastTheme == true ? f->palette.buttonText().color() : WINUI3Colors[colorSchemeIndex][frameColorStrong], lw);
+ else
+ qDrawShadeRect(painter, f->rect, f->palette, frameShadow == QFrame::Sunken, lw, mlw);
+ break;
+ case QFrame::Panel:
+ if (frameShadow == QFrame::Plain)
+ qDrawPlainRoundedRect(painter, f->rect, secondLevelRoundingRadius, secondLevelRoundingRadius, highContrastTheme == true ? f->palette.buttonText().color() : WINUI3Colors[colorSchemeIndex][frameColorStrong], lw);
+ else
+ qDrawShadePanel(painter, f->rect, f->palette, frameShadow == QFrame::Sunken, lw);
+ break;
+ default:
+ QWindowsVistaStyle::drawControl(element, option, painter, widget);
+ }
+ }
+ break;
+ case QStyle::CE_ProgressBarGroove:{
+ if (const QStyleOptionProgressBar* progbaropt = qstyleoption_cast<const QStyleOptionProgressBar*>(option)) {
+ if (const QProgressBar* bar = qobject_cast<const QProgressBar*>(widget)) {
+ QRect rect = subElementRect(SE_ProgressBarContents, progbaropt, widget);
+ QPointF center = rect.center();
+ if (bar->orientation() & Qt::Horizontal) {
+ rect.setHeight(1);
+ rect.moveTop(center.y());
+ } else {
+ rect.setWidth(1);
+ rect.moveLeft(center.x());
+ }
+ painter->setPen(Qt::NoPen);
+ painter->setBrush(Qt::gray);
+ painter->drawRect(rect);
+ }
+ }
+ break;
+ }
+ case QStyle::CE_ProgressBarContents:
+ if (const QStyleOptionProgressBar* progbaropt = qstyleoption_cast<const QStyleOptionProgressBar*>(option)) {
+ if (const QProgressBar* bar = qobject_cast<const QProgressBar*>(widget)) {
+ const qreal progressBarThickness = 3;
+ const qreal progressBarHalfThickness = progressBarThickness / 2.0;
+ QRectF rect = subElementRect(SE_ProgressBarContents, progbaropt, widget);
+ QRectF originalRect = rect;
+ QPointF center = rect.center();
+ bool isIndeterminate = progbaropt->maximum == 0 && progbaropt->minimum == 0;
+ float fillPercentage = 0;
+ const qreal offset = (bar->orientation() == Qt::Horizontal && int(rect.height()) % 2 == 0)
+ || (bar->orientation() == Qt::Vertical && int(rect.width()) % 2 == 0) ? 0.5 : 0.0;
+
+ if (!isIndeterminate) {
+ fillPercentage = ((float(progbaropt->progress) - float(progbaropt->minimum)) / (float(progbaropt->maximum) - float(progbaropt->minimum)));
+ if (bar->orientation() == Qt::Horizontal) {
+ rect.setHeight(progressBarThickness);
+ rect.moveTop(center.y() - progressBarHalfThickness - offset);
+ rect.setWidth(rect.width() * fillPercentage);
+ } else {
+ float oldHeight = rect.height();
+ rect.setWidth(progressBarThickness);
+ rect.moveLeft(center.x() - progressBarHalfThickness - offset);
+ rect.moveTop(oldHeight * (1.0f - fillPercentage));
+ rect.setHeight(oldHeight * fillPercentage);
+ }
+ } else {
+ auto elapsedTime = std::chrono::time_point_cast<std::chrono::milliseconds>(std::chrono::system_clock::now());
+ fillPercentage = (elapsedTime.time_since_epoch().count() % 5000)/(5000.0f*0.75);
+ if (bar->orientation() == Qt::Horizontal) {
+ float barBegin = qMin(qMax(fillPercentage-0.25,0.0) * rect.width(), float(rect.width()));
+ float barEnd = qMin(fillPercentage * rect.width(), float(rect.width()));
+ rect = QRect(QPoint(rect.left() + barBegin, rect.top()), QPoint(rect.left() + barEnd, rect.bottom()));
+ rect.setHeight(progressBarThickness);
+ rect.moveTop(center.y() - progressBarHalfThickness - offset);
+ } else {
+ float barBegin = qMin(qMax(fillPercentage-0.25,0.0) * rect.height(), float(rect.height()));
+ float barEnd = qMin(fillPercentage * rect.height(), float(rect.height()));
+ rect = QRect(QPoint(rect.left(), rect.bottom() - barEnd), QPoint(rect.right(), rect.bottom() - barBegin));
+ rect.setWidth(progressBarThickness);
+ rect.moveLeft(center.x() - progressBarHalfThickness - offset);
+ }
+ const_cast<QWidget*>(widget)->update();
+ }
+ if (progbaropt->invertedAppearance && bar->orientation() == Qt::Horizontal)
+ rect.moveLeft(originalRect.width() * (1.0 - fillPercentage));
+ else if (progbaropt->invertedAppearance && bar->orientation() == Qt::Vertical)
+ rect.moveBottom(originalRect.height() * fillPercentage);
+ painter->setPen(Qt::NoPen);
+ painter->setBrush(progbaropt->palette.accent());
+ painter->drawRoundedRect(rect, secondLevelRoundingRadius, secondLevelRoundingRadius);
+ }
+ }
+ break;
+ case QStyle::CE_ProgressBarLabel:
+ if (const QStyleOptionProgressBar* progbaropt = qstyleoption_cast<const QStyleOptionProgressBar*>(option)) {
+ QRect rect = subElementRect(SE_ProgressBarLabel, progbaropt, widget);
+ painter->setPen(progbaropt->palette.text().color());
+ painter->drawText(rect, progbaropt->text,Qt::AlignVCenter|Qt::AlignLeft);
+ }
+ break;
+ case CE_PushButtonLabel:
+ if (const QStyleOptionButton *btn = qstyleoption_cast<const QStyleOptionButton *>(option)) {
+ QRect textRect = btn->rect;
+
+ int tf = Qt::AlignVCenter|Qt::TextShowMnemonic;
+ if (!proxy()->styleHint(SH_UnderlineShortcut, btn, widget))
+ tf |= Qt::TextHideMnemonic;
+
+ if (btn->features & QStyleOptionButton::HasMenu) {
+ int indicatorSize = proxy()->pixelMetric(PM_MenuButtonIndicator, btn, widget);
+ if (btn->direction == Qt::LeftToRight)
+ textRect = textRect.adjusted(0, 0, -indicatorSize, 0);
+ else
+ textRect = textRect.adjusted(indicatorSize, 0, 0, 0);
+ }
+ if (!btn->icon.isNull()) {
+ //Center both icon and text
+ QIcon::Mode mode = btn->state & State_Enabled ? QIcon::Normal : QIcon::Disabled;
+ if (mode == QIcon::Normal && btn->state & State_HasFocus)
+ mode = QIcon::Active;
+ QIcon::State state = QIcon::Off;
+ if (btn->state & State_On)
+ state = QIcon::On;
+
+ QPixmap pixmap = btn->icon.pixmap(btn->iconSize, painter->device()->devicePixelRatio(), mode, state);
+ int pixmapWidth = pixmap.width() / pixmap.devicePixelRatio();
+ int pixmapHeight = pixmap.height() / pixmap.devicePixelRatio();
+ int labelWidth = pixmapWidth;
+ int labelHeight = pixmapHeight;
+ int iconSpacing = 4;//### 4 is currently hardcoded in QPushButton::sizeHint()
+ if (!btn->text.isEmpty()) {
+ int textWidth = btn->fontMetrics.boundingRect(option->rect, tf, btn->text).width();
+ labelWidth += (textWidth + iconSpacing);
+ }
+
+ QRect iconRect = QRect(textRect.x() + (textRect.width() - labelWidth) / 2,
+ textRect.y() + (textRect.height() - labelHeight) / 2,
+ pixmapWidth, pixmapHeight);
+
+ iconRect = visualRect(btn->direction, textRect, iconRect);
+
+ if (btn->direction == Qt::RightToLeft) {
+ tf |= Qt::AlignRight;
+ textRect.setRight(iconRect.left() - iconSpacing / 2);
+ } else {
+ tf |= Qt::AlignLeft; //left align, we adjust the text-rect instead
+ textRect.setLeft(iconRect.left() + iconRect.width() + iconSpacing / 2);
+ }
+
+ if (btn->state & (State_On | State_Sunken))
+ iconRect.translate(proxy()->pixelMetric(PM_ButtonShiftHorizontal, option, widget),
+ proxy()->pixelMetric(PM_ButtonShiftVertical, option, widget));
+ painter->drawPixmap(iconRect, pixmap);
+ } else {
+ tf |= Qt::AlignHCenter;
+ }
+
+
+ if (btn->state & State_Sunken)
+ painter->setPen(flags & State_On ? QPen(WINUI3Colors[colorSchemeIndex][textOnAccentSecondary]) : QPen(WINUI3Colors[colorSchemeIndex][controlTextSecondary]));
+ else
+ painter->setPen(flags & State_On ? QPen(WINUI3Colors[colorSchemeIndex][textOnAccentPrimary]) : QPen(btn->palette.buttonText().color()));
+ proxy()->drawItemText(painter, textRect, tf, option->palette,btn->state & State_Enabled, btn->text);
+ }
+ break;
+ case CE_PushButtonBevel:
+ if (const QStyleOptionButton *btn = qstyleoption_cast<const QStyleOptionButton *>(option)) {
+ if (btn->features.testFlag(QStyleOptionButton::Flat)) {
+ painter->setPen(Qt::NoPen);
+ if (flags & (State_Sunken | State_On)) {
+ painter->setBrush(WINUI3Colors[colorSchemeIndex][subtlePressedColor]);
+ }
+ else if (flags & State_MouseOver) {
+ painter->setBrush(WINUI3Colors[colorSchemeIndex][subtleHighlightColor]);
+ }
+ painter->drawRoundedRect(rect, secondLevelRoundingRadius, secondLevelRoundingRadius);
+ } else {
+ QRectF rect = btn->rect.marginsRemoved(QMargins(2,2,2,2));
+ painter->setPen(Qt::NoPen);
+ if (flags & (State_Sunken))
+ painter->setBrush(flags & State_On ? option->palette.accent().color().lighter(120) : WINUI3Colors[colorSchemeIndex][controlFillTertiary]);
+ else if (flags & State_MouseOver)
+ painter->setBrush(flags & State_On ? option->palette.accent().color().lighter(110) : WINUI3Colors[colorSchemeIndex][controlFillSecondary]);
+ else
+ painter->setBrush(flags & State_On ? option->palette.accent() : option->palette.button());
+ painter->drawRoundedRect(rect, secondLevelRoundingRadius, secondLevelRoundingRadius);
+
+ rect.adjust(0.5,0.5,-0.5,-0.5);
+ painter->setBrush(Qt::NoBrush);
+ painter->setPen(btn->features.testFlag(QStyleOptionButton::DefaultButton) ? QPen(option->palette.accent().color()) : QPen(WINUI3Colors[colorSchemeIndex][controlStrokePrimary]));
+ painter->drawRoundedRect(rect, secondLevelRoundingRadius, secondLevelRoundingRadius);
+
+ painter->setPen(btn->features.testFlag(QStyleOptionButton::DefaultButton) ? QPen(WINUI3Colors[colorSchemeIndex][controlStrokeOnAccentSecondary]) : QPen(WINUI3Colors[colorSchemeIndex][controlStrokeSecondary]));
+ if (flags & State_Raised)
+ painter->drawLine(rect.bottomLeft() + QPointF(4.0,0.0), rect.bottomRight() + QPointF(-4,0.0));
+ }
+ }
+ break;
+ case CE_MenuBarItem:
+ if (const auto *mbi = qstyleoption_cast<const QStyleOptionMenuItem *>(option)) {
+ constexpr int hPadding = 11;
+ constexpr int topPadding = 4;
+ constexpr int bottomPadding = 6;
+ bool active = mbi->state & State_Selected;
+ bool hasFocus = mbi->state & State_HasFocus;
+ bool down = mbi->state & State_Sunken;
+ bool enabled = mbi->state & State_Enabled;
+ QStyleOptionMenuItem newMbi = *mbi;
+ newMbi.font.setPointSize(10);
+ if (enabled && (active || hasFocus)) {
+ if (active && down)
+ painter->setBrushOrigin(painter->brushOrigin() + QPoint(1, 1));
+ if (active && hasFocus) {
+ painter->setBrush(WINUI3Colors[colorSchemeIndex][subtleHighlightColor]);
+ painter->setPen(Qt::NoPen);
+ QRect rect = mbi->rect.marginsRemoved(QMargins(5,0,5,0));
+ painter->drawRoundedRect(rect,secondLevelRoundingRadius,secondLevelRoundingRadius,Qt::AbsoluteSize);
+ }
+ }
+ newMbi.rect.adjust(hPadding,topPadding,-hPadding,-bottomPadding);
+ painter->setFont(newMbi.font);
+ QCommonStyle::drawControl(element, &newMbi, painter, widget);
+ }
+ break;
+
+#if QT_CONFIG(menu)
+ case CE_MenuEmptyArea:
+ break;
+
+ case CE_MenuItem:
+ if (const auto *menuitem = qstyleoption_cast<const QStyleOptionMenuItem *>(option)) {
+ int x, y, w, h;
+ menuitem->rect.getRect(&x, &y, &w, &h);
+ int tab = menuitem->reservedShortcutWidth;
+ bool dis = !(menuitem->state & State_Enabled);
+ bool checked = menuitem->checkType != QStyleOptionMenuItem::NotCheckable
+ ? menuitem->checked : false;
+ bool act = menuitem->state & State_Selected;
+
+ // windows always has a check column, regardless whether we have an icon or not
+ int checkcol = qMax<int>(menuitem->maxIconWidth, 32);
+
+ QBrush fill = (act == true && dis == false) ? QBrush(WINUI3Colors[colorSchemeIndex][subtleHighlightColor]) : menuitem->palette.brush(QPalette::Button);
+ painter->setBrush(fill);
+ painter->setPen(Qt::NoPen);
+ QRect rect = menuitem->rect;
+ rect = rect.marginsRemoved(QMargins(2,2,2,2));
+ if (act && dis == false)
+ painter->drawRoundedRect(rect,secondLevelRoundingRadius,secondLevelRoundingRadius,Qt::AbsoluteSize);
+
+ if (menuitem->menuItemType == QStyleOptionMenuItem::Separator){
+ int yoff = 4;
+ painter->setPen(highContrastTheme == true ? menuitem->palette.buttonText().color() : WINUI3Colors[colorSchemeIndex][frameColorLight]);
+ painter->drawLine(x, y + yoff, x + w, y + yoff );
+ break;
+ }
+
+ QRect vCheckRect = visualRect(option->direction, menuitem->rect, QRect(menuitem->rect.x(), menuitem->rect.y(), checkcol, menuitem->rect.height()));
+ if (!menuitem->icon.isNull() && checked) {
+ if (act) {
+ qDrawShadePanel(painter, vCheckRect,
+ menuitem->palette, true, 1,
+ &menuitem->palette.brush(QPalette::Button));
+ } else {
+ QBrush fill(menuitem->palette.light().color(), Qt::Dense4Pattern);
+ qDrawShadePanel(painter, vCheckRect, menuitem->palette, true, 1, &fill);
+ }
+ }
+ // On Windows Style, if we have a checkable item and an icon we
+ // draw the icon recessed to indicate an item is checked. If we
+ // have no icon, we draw a checkmark instead.
+ if (!menuitem->icon.isNull()) {
+ QIcon::Mode mode = dis ? QIcon::Disabled : QIcon::Normal;
+ if (act && !dis)
+ mode = QIcon::Active;
+ QPixmap pixmap;
+ if (checked)
+ pixmap = menuitem->icon.pixmap(proxy()->pixelMetric(PM_SmallIconSize, option, widget), mode, QIcon::On);
+ else
+ pixmap = menuitem->icon.pixmap(proxy()->pixelMetric(PM_SmallIconSize, option, widget), mode);
+ QRect pmr(QPoint(0, 0), pixmap.deviceIndependentSize().toSize());
+ pmr.moveCenter(vCheckRect.center());
+ painter->setPen(menuitem->palette.text().color());
+ painter->drawPixmap(pmr.topLeft(), pixmap);
+ } else if (checked) {
+ QStyleOptionMenuItem newMi = *menuitem;
+ newMi.state = State_None;
+ if (!dis)
+ newMi.state |= State_Enabled;
+ if (act)
+ newMi.state |= State_On | State_Selected;
+ newMi.rect = visualRect(option->direction, menuitem->rect, QRect(menuitem->rect.x() + QWindowsStylePrivate::windowsItemFrame,
+ menuitem->rect.y() + QWindowsStylePrivate::windowsItemFrame,
+ checkcol - 2 * QWindowsStylePrivate::windowsItemFrame,
+ menuitem->rect.height() - 2 * QWindowsStylePrivate::windowsItemFrame));
+
+ QColor discol;
+ if (dis) {
+ discol = menuitem->palette.text().color();
+ painter->setPen(discol);
+ }
+ int xm = int(QWindowsStylePrivate::windowsItemFrame) + checkcol / 4 + int(QWindowsStylePrivate::windowsItemHMargin);
+ int xpos = menuitem->rect.x() + xm;
+ QRect textRect(xpos, y + QWindowsStylePrivate::windowsItemVMargin,
+ w - xm - QWindowsStylePrivate::windowsRightBorder - tab + 1, h - 2 * QWindowsStylePrivate::windowsItemVMargin);
+ QRect vTextRect = visualRect(option->direction, menuitem->rect, textRect);
+
+ painter->save();
+ painter->setFont(assetFont);
+ int text_flags = Qt::AlignVCenter | Qt::TextShowMnemonic | Qt::TextDontClip | Qt::TextSingleLine;
+ if (!proxy()->styleHint(SH_UnderlineShortcut, menuitem, widget))
+ text_flags |= Qt::TextHideMnemonic;
+ text_flags |= Qt::AlignLeft;
+
+ const QString textToDraw("\uE001");
+ painter->setPen(option->palette.text().color());
+ painter->drawText(vTextRect, text_flags, textToDraw);
+ painter->restore();
+ }
+ painter->setPen(act ? menuitem->palette.highlightedText().color() : menuitem->palette.buttonText().color());
+
+ QColor discol = menuitem->palette.text().color();
+ if (dis) {
+ discol = menuitem->palette.color(QPalette::Disabled, QPalette::WindowText);
+ painter->setPen(discol);
+ }
+
+ int xm = int(QWindowsStylePrivate::windowsItemFrame) + checkcol + int(QWindowsStylePrivate::windowsItemHMargin);
+ int xpos = menuitem->rect.x() + xm;
+ QRect textRect(xpos, y + QWindowsStylePrivate::windowsItemVMargin,
+ w - xm - QWindowsStylePrivate::windowsRightBorder - tab + 1, h - 2 * QWindowsStylePrivate::windowsItemVMargin);
+ QRect vTextRect = visualRect(option->direction, menuitem->rect, textRect);
+ QStringView s(menuitem->text);
+ if (!s.isEmpty()) { // draw text
+ painter->save();
+ qsizetype t = s.indexOf(u'\t');
+ int text_flags = Qt::AlignVCenter | Qt::TextShowMnemonic | Qt::TextDontClip | Qt::TextSingleLine;
+ if (!proxy()->styleHint(SH_UnderlineShortcut, menuitem, widget))
+ text_flags |= Qt::TextHideMnemonic;
+ text_flags |= Qt::AlignLeft;
+ if (t >= 0) {
+ QRect vShortcutRect = visualRect(option->direction, menuitem->rect,
+ QRect(textRect.topRight(), QPoint(menuitem->rect.right(), textRect.bottom())));
+ const QString textToDraw = s.mid(t + 1).toString();
+ if (dis && !act && proxy()->styleHint(SH_EtchDisabledText, option, widget)) {
+ painter->setPen(menuitem->palette.light().color());
+ painter->drawText(vShortcutRect.adjusted(1, 1, 1, 1), text_flags, textToDraw);
+ painter->setPen(discol);
+ }
+ painter->setPen(menuitem->palette.color(QPalette::Disabled, QPalette::Text));
+ painter->drawText(vShortcutRect, text_flags, textToDraw);
+ s = s.left(t);
+ }
+ QFont font = menuitem->font;
+ if (menuitem->menuItemType == QStyleOptionMenuItem::DefaultItem)
+ font.setBold(true);
+ painter->setFont(font);
+ const QString textToDraw = s.left(t).toString();
+ painter->setPen(discol);
+ painter->drawText(vTextRect, text_flags, textToDraw);
+ painter->restore();
+ }
+ if (menuitem->menuItemType == QStyleOptionMenuItem::SubMenu) {// draw sub menu arrow
+ int dim = (h - 2 * QWindowsStylePrivate::windowsItemFrame) / 2;
+ xpos = x + w - QWindowsStylePrivate::windowsArrowHMargin - QWindowsStylePrivate::windowsItemFrame - dim;
+ QRect vSubMenuRect = visualRect(option->direction, menuitem->rect, QRect(xpos, y + h / 2 - dim / 2, dim, dim));
+ QStyleOptionMenuItem newMI = *menuitem;
+ newMI.rect = vSubMenuRect;
+ newMI.state = dis ? State_None : State_Enabled;
+ if (act)
+ newMI.palette.setColor(QPalette::ButtonText,
+ newMI.palette.highlightedText().color());
+ painter->save();
+ painter->setFont(assetFont);
+ int text_flags = Qt::AlignVCenter | Qt::TextShowMnemonic | Qt::TextDontClip | Qt::TextSingleLine;
+ if (!proxy()->styleHint(SH_UnderlineShortcut, menuitem, widget))
+ text_flags |= Qt::TextHideMnemonic;
+ text_flags |= Qt::AlignLeft;
+ const QString textToDraw("\uE013");
+ painter->setPen(option->palette.text().color());
+ painter->drawText(vSubMenuRect, text_flags, textToDraw);
+ painter->restore();
+ }
+ }
+ break;
+#endif // QT_CONFIG(menu)
+ case CE_MenuBarEmptyArea: {
+ break;
+ }
+ case CE_HeaderEmptyArea:
+ break;
+ case CE_HeaderSection: {
+ if (const QStyleOptionHeader *header = qstyleoption_cast<const QStyleOptionHeader *>(option)) {
+ painter->setPen(highContrastTheme == true ? header->palette.buttonText().color() : WINUI3Colors[colorSchemeIndex][frameColorLight]);
+ if (header->position == QStyleOptionHeader::OnlyOneSection) {
+ break;
+ }
+ else if (header->position == QStyleOptionHeader::Beginning) {
+ painter->drawLine(QPointF(option->rect.topRight()) + QPointF(0.5,0.0),
+ QPointF(option->rect.bottomRight()) + QPointF(0.5,0.0));
+ }
+ else if (header->position == QStyleOptionHeader::End) {
+ painter->drawLine(QPointF(option->rect.topLeft()) - QPointF(0.5,0.0),
+ QPointF(option->rect.bottomLeft()) - QPointF(0.5,0.0));
+ } else {
+ painter->drawLine(QPointF(option->rect.topRight()) + QPointF(0.5,0.0),
+ QPointF(option->rect.bottomRight()) + QPointF(0.5,0.0));
+ painter->drawLine(QPointF(option->rect.topLeft()) - QPointF(0.5,0.0),
+ QPointF(option->rect.bottomLeft()) - QPointF(0.5,0.0));
+ }
+ painter->drawLine(QPointF(option->rect.bottomLeft()) + QPointF(0.0,0.5),
+ QPointF(option->rect.bottomRight()) + QPointF(0.0,0.5));
+ }
+ break;
+ }
+ case QStyle::CE_ItemViewItem: {
+ if (const QStyleOptionViewItem *vopt = qstyleoption_cast<const QStyleOptionViewItem *>(option)) {
+ if (const QAbstractItemView *view = qobject_cast<const QAbstractItemView *>(widget)) {
+ QRect checkRect = proxy()->subElementRect(SE_ItemViewItemCheckIndicator, vopt, widget);
+ QRect iconRect = proxy()->subElementRect(SE_ItemViewItemDecoration, vopt, widget);
+ QRect textRect = proxy()->subElementRect(SE_ItemViewItemText, vopt, widget);
+
+ QRect rect = vopt->rect;
+
+ painter->setPen(highContrastTheme == true ? vopt->palette.buttonText().color() : WINUI3Colors[colorSchemeIndex][frameColorLight]);
+ if (vopt->viewItemPosition == QStyleOptionViewItem::OnlyOne || vopt->viewItemPosition == QStyleOptionViewItem::Invalid) {
+ } else if (vopt->viewItemPosition == QStyleOptionViewItem::Beginning) {
+ painter->drawLine(QPointF(option->rect.topRight()) + QPointF(0.5,0.0),
+ QPointF(option->rect.bottomRight()) + QPointF(0.5,0.0));
+ } else if (vopt->viewItemPosition == QStyleOptionViewItem::End) {
+ painter->drawLine(QPointF(option->rect.topLeft()) - QPointF(0.5,0.0),
+ QPointF(option->rect.bottomLeft()) - QPointF(0.5,0.0));
+ } else {
+ painter->drawLine(QPointF(option->rect.topRight()) + QPointF(0.5,0.0),
+ QPointF(option->rect.bottomRight()) + QPointF(0.5,0.0));
+ painter->drawLine(QPointF(option->rect.topLeft()) - QPointF(0.5,0.0),
+ QPointF(option->rect.bottomLeft()) - QPointF(0.5,0.0));
+ }
+ painter->setPen(QPen(option->palette.buttonText().color()));
+
+ bool isTreeView = widget && widget->inherits("QTreeView");
+
+ if ((vopt->state & State_Selected || vopt->state & State_MouseOver) && !(isTreeView && vopt->state & State_MouseOver) && vopt->showDecorationSelected) {
+ painter->setBrush(WINUI3Colors[colorSchemeIndex][subtleHighlightColor]);
+ QWidget *editorWidget = view->indexWidget(view->currentIndex());
+ if (editorWidget) {
+ QPalette pal = editorWidget->palette();
+ QColor editorBgColor = vopt->backgroundBrush == Qt::NoBrush ? vopt->palette.color(widget->backgroundRole()) : vopt->backgroundBrush.color();
+ editorBgColor.setAlpha(255);
+ pal.setColor(editorWidget->backgroundRole(),editorBgColor);
+ editorWidget->setPalette(pal);
+ }
+ } else {
+ painter->setBrush(vopt->backgroundBrush);
+ }
+ painter->setPen(Qt::NoPen);
+
+ if (vopt->viewItemPosition == QStyleOptionViewItem::OnlyOne || vopt->viewItemPosition == QStyleOptionViewItem::Invalid) {
+ painter->drawRoundedRect(vopt->rect.marginsRemoved(QMargins(2,2,2,2)),secondLevelRoundingRadius,secondLevelRoundingRadius);
+ } else if (vopt->viewItemPosition == QStyleOptionViewItem::Beginning) {
+ painter->drawRoundedRect(rect.marginsRemoved(QMargins(2,2,0,2)),secondLevelRoundingRadius,secondLevelRoundingRadius);
+ } else if (vopt->viewItemPosition == QStyleOptionViewItem::End) {
+ painter->drawRoundedRect(vopt->rect.marginsRemoved(QMargins(0,2,2,2)),secondLevelRoundingRadius,secondLevelRoundingRadius);
+ } else {
+ painter->drawRect(vopt->rect.marginsRemoved(QMargins(0,2,0,2)));
+ }
+
+ // draw the check mark
+ if (vopt->features & QStyleOptionViewItem::HasCheckIndicator) {
+ QStyleOptionViewItem option(*vopt);
+ option.rect = checkRect;
+ option.state = option.state & ~QStyle::State_HasFocus;
+
+ switch (vopt->checkState) {
+ case Qt::Unchecked:
+ option.state |= QStyle::State_Off;
+ break;
+ case Qt::PartiallyChecked:
+ option.state |= QStyle::State_NoChange;
+ break;
+ case Qt::Checked:
+ option.state |= QStyle::State_On;
+ break;
+ }
+ proxy()->drawPrimitive(QStyle::PE_IndicatorItemViewItemCheck, &option, painter, widget);
+ }
+
+ // draw the icon
+ QIcon::Mode mode = QIcon::Normal;
+ if (!(vopt->state & QStyle::State_Enabled))
+ mode = QIcon::Disabled;
+ else if (vopt->state & QStyle::State_Selected)
+ mode = QIcon::Selected;
+ QIcon::State state = vopt->state & QStyle::State_Open ? QIcon::On : QIcon::Off;
+ vopt->icon.paint(painter, iconRect, vopt->decorationAlignment, mode, state);
+
+ painter->setPen(QPen(option->palette.buttonText().color()));
+ if (!view->isPersistentEditorOpen(vopt->index))
+ d->viewItemDrawText(painter, vopt, textRect);
+ if (vopt->state & State_Selected && (vopt->viewItemPosition == QStyleOptionViewItem::Beginning || vopt->viewItemPosition == QStyleOptionViewItem::OnlyOne || vopt->viewItemPosition == QStyleOptionViewItem::Invalid)) {
+ if (widget && widget->inherits("QListView") && qobject_cast<const QListView*>(widget)->viewMode() != QListView::IconMode) {
+ painter->setPen(QPen(vopt->palette.accent().color()));
+ painter->drawLine(option->rect.x(),option->rect.y()+2,option->rect.x(),option->rect.y() + option->rect.height()-2);
+ painter->drawLine(option->rect.x()+1,option->rect.y()+2,option->rect.x()+1,option->rect.y() + option->rect.height()-2);
+ }
+ }
+ }
+ }
+ break;
+ }
+ default:
+ QWindowsVistaStyle::drawControl(element, option, painter, widget);
+ }
+ painter->restore();
+}
+
+int QWindows11Style::styleHint(StyleHint hint, const QStyleOption *opt,
+ const QWidget *widget, QStyleHintReturn *returnData) const {
+ switch (hint) {
+ case SH_GroupBox_TextLabelColor:
+ if (opt!=nullptr && widget!=nullptr)
+ return opt->palette.text().color().rgba();
+ return 0;
+ case QStyle::SH_ItemView_ShowDecorationSelected:
+ return 1;
+ default:
+ return QWindowsVistaStyle::styleHint(hint, opt, widget, returnData);
+ }
+}
+
+QRect QWindows11Style::subElementRect(QStyle::SubElement element, const QStyleOption *option,
+ const QWidget *widget) const
+{
+ QRect ret;
+ switch (element) {
+ case QStyle::SE_LineEditContents:
+ ret = option->rect.adjusted(8,0,-8,0);
+ break;
+ case QStyle::SE_ItemViewItemText:
+ if (const auto *item = qstyleoption_cast<const QStyleOptionViewItem *>(option)) {
+ const int decorationOffset = item->features.testFlag(QStyleOptionViewItem::HasDecoration) ? item->decorationSize.width() : 0;
+ if (widget && widget->parentWidget() &&
+ widget->parentWidget()->inherits("QComboBoxPrivateContainer")) {
+ ret = option->rect.adjusted(decorationOffset + 5, 0, -5, 0);
+ } else {
+ ret = QWindowsVistaStyle::subElementRect(element, option, widget);
+ }
+ } else {
+ ret = QWindowsVistaStyle::subElementRect(element, option, widget);
+ }
+ break;
+ default:
+ ret = QWindowsVistaStyle::subElementRect(element, option, widget);
+ }
+ return ret;
+}
+
+/*!
+ \internal
+ */
+QRect QWindows11Style::subControlRect(ComplexControl control, const QStyleOptionComplex *option,
+ SubControl subControl, const QWidget *widget) const
+{
+ QRect ret;
+
+ switch (control) {
+#if QT_CONFIG(spinbox)
+ case CC_SpinBox:
+ if (const QStyleOptionSpinBox *spinbox = qstyleoption_cast<const QStyleOptionSpinBox *>(option)) {
+ QSize bs;
+ int fw = spinbox->frame ? proxy()->pixelMetric(PM_SpinBoxFrameWidth, spinbox, widget) : 0;
+ bs.setHeight(qMax(8, spinbox->rect.height() - fw));
+ bs.setWidth(qMin(24.0, spinbox->rect.width()*(1.0/4.0)));
+ int y = fw + spinbox->rect.y();
+ int x, lx, rx;
+ x = spinbox->rect.x() + spinbox->rect.width() - fw - 2 * bs.width();
+ lx = fw;
+ rx = x - fw;
+ switch (subControl) {
+ case SC_SpinBoxUp:
+ if (spinbox->buttonSymbols == QAbstractSpinBox::NoButtons)
+ return QRect();
+ ret = QRect(x, y, bs.width(), bs.height());
+ break;
+ case SC_SpinBoxDown:
+ if (spinbox->buttonSymbols == QAbstractSpinBox::NoButtons)
+ return QRect();
+ ret = QRect(x + bs.width(), y, bs.width(), bs.height());
+ break;
+ case SC_SpinBoxEditField:
+ if (spinbox->buttonSymbols == QAbstractSpinBox::NoButtons) {
+ ret = QRect(lx, fw, spinbox->rect.width() - 2*fw, spinbox->rect.height() - 2*fw);
+ } else {
+ ret = QRect(lx, fw, rx, spinbox->rect.height() - 2*fw);
+ }
+ break;
+ case SC_SpinBoxFrame:
+ ret = spinbox->rect;
+ default:
+ break;
+ }
+ ret = visualRect(spinbox->direction, spinbox->rect, ret);
+ }
+ break;
+ case CC_TitleBar:
+ if (const QStyleOptionTitleBar *titlebar = qstyleoption_cast<const QStyleOptionTitleBar *>(option)) {
+ SubControl sc = subControl;
+ ret = QCommonStyle::subControlRect(control, option, subControl, widget);
+ static constexpr int indent = 3;
+ static constexpr int controlWidthMargin = 2;
+ const int controlHeight = titlebar->rect.height();
+ const int controlWidth = 46;
+ const int iconSize = proxy()->pixelMetric(QStyle::PM_TitleBarButtonIconSize, option, widget);
+ int offset = -(controlWidthMargin + indent);
+
+ bool isMinimized = titlebar->titleBarState & Qt::WindowMinimized;
+ bool isMaximized = titlebar->titleBarState & Qt::WindowMaximized;
+
+ switch (sc) {
+ case SC_TitleBarLabel:
+ if (titlebar->titleBarFlags & (Qt::WindowTitleHint | Qt::WindowSystemMenuHint)) {
+ ret = titlebar->rect;
+ if (titlebar->titleBarFlags & Qt::WindowSystemMenuHint)
+ ret.adjust(iconSize + controlWidthMargin + indent, 0, -controlWidth, 0);
+ if (titlebar->titleBarFlags & Qt::WindowMinimizeButtonHint)
+ ret.adjust(0, 0, -controlWidth, 0);
+ if (titlebar->titleBarFlags & Qt::WindowMaximizeButtonHint)
+ ret.adjust(0, 0, -controlWidth, 0);
+ if (titlebar->titleBarFlags & Qt::WindowShadeButtonHint)
+ ret.adjust(0, 0, -controlWidth, 0);
+ if (titlebar->titleBarFlags & Qt::WindowContextHelpButtonHint)
+ ret.adjust(0, 0, -controlWidth, 0);
+ }
+ break;
+ case SC_TitleBarContextHelpButton:
+ if (titlebar->titleBarFlags & Qt::WindowContextHelpButtonHint)
+ offset += controlWidth;
+ Q_FALLTHROUGH();
+ case SC_TitleBarMinButton:
+ if (!isMinimized && (titlebar->titleBarFlags & Qt::WindowMinimizeButtonHint))
+ offset += controlWidth;
+ else if (sc == SC_TitleBarMinButton)
+ break;
+ Q_FALLTHROUGH();
+ case SC_TitleBarNormalButton:
+ if (isMinimized && (titlebar->titleBarFlags & Qt::WindowMinimizeButtonHint))
+ offset += controlWidth;
+ else if (isMaximized && (titlebar->titleBarFlags & Qt::WindowMaximizeButtonHint))
+ offset += controlWidth;
+ else if (sc == SC_TitleBarNormalButton)
+ break;
+ Q_FALLTHROUGH();
+ case SC_TitleBarMaxButton:
+ if (!isMaximized && (titlebar->titleBarFlags & Qt::WindowMaximizeButtonHint))
+ offset += controlWidth;
+ else if (sc == SC_TitleBarMaxButton)
+ break;
+ Q_FALLTHROUGH();
+ case SC_TitleBarShadeButton:
+ if (!isMinimized && (titlebar->titleBarFlags & Qt::WindowShadeButtonHint))
+ offset += controlWidth;
+ else if (sc == SC_TitleBarShadeButton)
+ break;
+ Q_FALLTHROUGH();
+ case SC_TitleBarUnshadeButton:
+ if (isMinimized && (titlebar->titleBarFlags & Qt::WindowShadeButtonHint))
+ offset += controlWidth;
+ else if (sc == SC_TitleBarUnshadeButton)
+ break;
+ Q_FALLTHROUGH();
+ case SC_TitleBarCloseButton:
+ if (titlebar->titleBarFlags & Qt::WindowSystemMenuHint)
+ offset += controlWidth;
+ else if (sc == SC_TitleBarCloseButton)
+ break;
+ ret.setRect(titlebar->rect.right() - offset, titlebar->rect.top(),
+ controlWidth, controlHeight);
+ break;
+ case SC_TitleBarSysMenu:
+ if (titlebar->titleBarFlags & Qt::WindowSystemMenuHint) {
+ ret.setRect(titlebar->rect.left() + controlWidthMargin + indent, titlebar->rect.top() + iconSize/2,
+ iconSize, iconSize);
+ }
+ break;
+ default:
+ break;
+ }
+ if (widget && isMinimized && titlebar->rect.width() < offset)
+ const_cast<QWidget*>(widget)->resize(controlWidthMargin + indent + offset + iconSize + controlWidthMargin, controlWidth);
+ ret = visualRect(titlebar->direction, titlebar->rect, ret);
+ }
+ break;
+#endif // Qt_NO_SPINBOX
+ case CC_ScrollBar:
+ {
+ ret = QCommonStyle::subControlRect(control, option, subControl, widget);
+
+ switch (subControl) {
+ case QStyle::SC_ScrollBarAddLine:
+ if (const QStyleOptionSlider *scrollbar = qstyleoption_cast<const QStyleOptionSlider *>(option)) {
+ if (scrollbar->orientation == Qt::Vertical) {
+ ret = ret.adjusted(2,2,-2,-3);
+ } else {
+ ret = ret.adjusted(3,2,-2,-2);
+ }
+ }
+ break;
+ case QStyle::SC_ScrollBarSubLine:
+ if (const QStyleOptionSlider *scrollbar = qstyleoption_cast<const QStyleOptionSlider *>(option)) {
+ if (scrollbar->orientation == Qt::Vertical) {
+ ret = ret.adjusted(2,2,-2,-3);
+ } else {
+ ret = ret.adjusted(3,2,-2,-2);
+ }
+ }
+ break;
+ default:
+ break;
+ }
+ break;
+ }
+ default:
+ ret = QWindowsVistaStyle::subControlRect(control, option, subControl, widget);
+ }
+ return ret;
+}
+
+/*!
+ \internal
+ */
+QSize QWindows11Style::sizeFromContents(ContentsType type, const QStyleOption *option,
+ const QSize &size, const QWidget *widget) const
+{
+ QSize contentSize(size);
+
+ switch (type) {
+
+ case CT_Menu:
+ contentSize += QSize(10, 0);
+ break;
+
+#if QT_CONFIG(menubar)
+ case CT_MenuBarItem:
+ if (!contentSize.isEmpty()) {
+ constexpr int hMargin = 2 * 6;
+ constexpr int hPadding = 2 * 11;
+ constexpr int itemHeight = 32;
+ contentSize.setWidth(contentSize.width() + hMargin + hPadding);
+ contentSize.setHeight(itemHeight);
+ }
+ break;
+#endif
+
+ default:
+ contentSize = QWindowsVistaStyle::sizeFromContents(type, option, size, widget);
+ break;
+ }
+
+ return contentSize;
+}
+
+
+/*!
+ \internal
+ */
+int QWindows11Style::pixelMetric(PixelMetric metric, const QStyleOption *option, const QWidget *widget) const
+{
+ int res = 0;
+
+ switch (metric) {
+ case QStyle::PM_IndicatorWidth:
+ case QStyle::PM_IndicatorHeight:
+ case QStyle::PM_ExclusiveIndicatorWidth:
+ case QStyle::PM_ExclusiveIndicatorHeight:
+ res = 16;
+ break;
+ case QStyle::PM_SliderLength:
+ res = int(QStyleHelper::dpiScaled(16, option));
+ break;
+ case QStyle::PM_TitleBarButtonIconSize:
+ res = 16;
+ break;
+ case QStyle::PM_TitleBarButtonSize:
+ res = 32;
+ break;
+ case QStyle::PM_ScrollBarExtent:
+ res = 12;
+ break;
+ default:
+ res = QWindowsVistaStyle::pixelMetric(metric, option, widget);
+ }
+
+ return res;
+}
+
+void QWindows11Style::polish(QWidget* widget)
+{
+ QWindowsVistaStyle::polish(widget);
+ if (widget->inherits("QScrollBar") || widget->inherits("QComboBoxPrivateContainer") || widget->inherits("QMenu")) {
+ bool wasCreated = widget->testAttribute(Qt::WA_WState_Created);
+ bool layoutDirection = widget->testAttribute(Qt::WA_RightToLeft);
+ widget->setAttribute(Qt::WA_OpaquePaintEvent,false);
+ widget->setAttribute(Qt::WA_TranslucentBackground);
+ widget->setWindowFlag(Qt::FramelessWindowHint);
+ widget->setWindowFlag(Qt::NoDropShadowWindowHint);
+ widget->setAttribute(Qt::WA_RightToLeft, layoutDirection);
+ widget->setAttribute(Qt::WA_WState_Created, wasCreated);
+ auto pal = widget->palette();
+ pal.setColor(widget->backgroundRole(), Qt::transparent);
+ widget->setPalette(pal);
+ }
+ if (widget->inherits("QComboBoxPrivateContainer") || widget->inherits("QMenu")) {
+ QGraphicsDropShadowEffect* dropshadow = new QGraphicsDropShadowEffect(widget);
+ dropshadow->setBlurRadius(3);
+ dropshadow->setXOffset(3);
+ dropshadow->setYOffset(3);
+ widget->setGraphicsEffect(dropshadow);
+ }
+ if (widget->inherits("QComboBox")) {
+
+ QComboBox* cb = qobject_cast<QComboBox*>(widget);
+ if (cb->isEditable()) {
+ QLineEdit *le = cb->lineEdit();
+ le->setFrame(false);
+ }
+ }
+ if (widget->inherits("QGraphicsView") && !widget->inherits("QTextEdit")) {
+ QPalette pal = widget->palette();
+ pal.setColor(QPalette::Base, pal.window().color());
+ widget->setPalette(pal);
+ }
+ else if (widget->inherits("QAbstractScrollArea") && !widget->inherits("QMdiArea")) {
+ if (const auto *scrollarea = qobject_cast<QAbstractScrollArea *>(widget)) {
+ QPalette pal = widget->palette();
+ QColor backgroundColor = widget->palette().base().color();
+ backgroundColor.setAlpha(255);
+ pal.setColor(scrollarea->viewport()->backgroundRole(), backgroundColor);
+ scrollarea->viewport()->setPalette(pal);
+ }
+ }
+ if (widget->inherits("QCommandLinkButton")) {
+ widget->setProperty("_qt_usingVistaStyle",false);
+ QPalette pal = widget->palette();
+ pal.setColor(QPalette::ButtonText, pal.text().color());
+ pal.setColor(QPalette::BrightText, pal.text().color());
+ widget->setPalette(pal);
+ }
+}
+
+
+/*
+The colors for Windows 11 are taken from the official WinUI3 Figma style at
+https://www.figma.com/community/file/1159947337437047524
+*/
+static void populateLightSystemBasePalette(QPalette &result)
+{
+ static QString oldStyleSheet;
+ const bool styleSheetChanged = oldStyleSheet != qApp->styleSheet();
+
+ QPalette standardPalette = QApplication::palette();
+ const QColor textColor = QColor(0x00,0x00,0x00,0xE4);
+
+ const QColor btnFace = QColor(0xFF,0xFF,0xFF,0xB3);
+ const QColor btnHighlight = result.accent().color();
+ const QColor btnColor = result.button().color();
+
+ if (standardPalette.color(QPalette::Highlight) == result.color(QPalette::Highlight) || styleSheetChanged)
+ result.setColor(QPalette::Highlight, btnHighlight);
+ if (standardPalette.color(QPalette::WindowText) == result.color(QPalette::WindowText) || styleSheetChanged)
+ result.setColor(QPalette::WindowText, textColor);
+ if (standardPalette.color(QPalette::Button) == result.color(QPalette::Button) || styleSheetChanged)
+ result.setColor(QPalette::Button, btnFace);
+ if (standardPalette.color(QPalette::Light) == result.color(QPalette::Light) || styleSheetChanged)
+ result.setColor(QPalette::Light, btnColor.lighter(150));
+ if (standardPalette.color(QPalette::Dark) == result.color(QPalette::Dark) || styleSheetChanged)
+ result.setColor(QPalette::Dark, btnColor.darker(200));
+ if (standardPalette.color(QPalette::Mid) == result.color(QPalette::Mid) || styleSheetChanged)
+ result.setColor(QPalette::Mid, btnColor.darker(150));
+ if (standardPalette.color(QPalette::Text) == result.color(QPalette::Text) || styleSheetChanged)
+ result.setColor(QPalette::Text, textColor);
+ if (standardPalette.color(QPalette::BrightText) != result.color(QPalette::BrightText) || styleSheetChanged)
+ result.setColor(QPalette::BrightText, btnHighlight);
+ if (standardPalette.color(QPalette::Base) == result.color(QPalette::Base) || styleSheetChanged)
+ result.setColor(QPalette::Base, btnFace);
+ if (standardPalette.color(QPalette::Window) == result.color(QPalette::Window) || styleSheetChanged)
+ result.setColor(QPalette::Window, QColor(0xF3,0xF3,0xF3,0xFF));
+ if (standardPalette.color(QPalette::ButtonText) == result.color(QPalette::ButtonText) || styleSheetChanged)
+ result.setColor(QPalette::ButtonText, textColor);
+ if (standardPalette.color(QPalette::Midlight) == result.color(QPalette::Midlight) || styleSheetChanged)
+ result.setColor(QPalette::Midlight, btnColor.lighter(125));
+ if (standardPalette.color(QPalette::Shadow) == result.color(QPalette::Shadow) || styleSheetChanged)
+ result.setColor(QPalette::Shadow, Qt::black);
+ if (standardPalette.color(QPalette::ToolTipBase) == result.color(QPalette::ToolTipBase) || styleSheetChanged)
+ result.setColor(QPalette::ToolTipBase, result.window().color());
+ if (standardPalette.color(QPalette::ToolTipText) == result.color(QPalette::ToolTipText) || styleSheetChanged)
+ result.setColor(QPalette::ToolTipText, result.windowText().color());
+
+ if (result.midlight() == result.button())
+ result.setColor(QPalette::Midlight, btnColor.lighter(110));
+ oldStyleSheet = qApp->styleSheet();
+}
+
+/*!
+ \internal
+ */
+void QWindows11Style::polish(QPalette& pal)
+{
+ highContrastTheme = QGuiApplicationPrivate::colorScheme() == Qt::ColorScheme::Unknown;
+ colorSchemeIndex = QGuiApplicationPrivate::colorScheme() == Qt::ColorScheme::Light ? 0 : 1;
+
+ if (!highContrastTheme && colorSchemeIndex == 0)
+ populateLightSystemBasePalette(pal);
+
+ if (standardPalette().color(QPalette::Inactive, QPalette::Button) == pal.color(QPalette::Inactive, QPalette::Button))
+ pal.setColor(QPalette::Inactive, QPalette::Button, pal.button().color());
+ if (standardPalette().color(QPalette::Inactive, QPalette::Window) == pal.color(QPalette::Inactive, QPalette::Window))
+ pal.setColor(QPalette::Inactive, QPalette::Window, pal.window().color());
+ if (standardPalette().color(QPalette::Inactive, QPalette::Light) == pal.color(QPalette::Inactive, QPalette::Light))
+ pal.setColor(QPalette::Inactive, QPalette::Light, pal.light().color());
+ if (standardPalette().color(QPalette::Inactive, QPalette::Dark) == pal.color(QPalette::Inactive, QPalette::Dark))
+ pal.setColor(QPalette::Inactive, QPalette::Dark, pal.dark().color());
+ if (standardPalette().color(QPalette::Inactive, QPalette::Accent) == pal.color(QPalette::Inactive, QPalette::Accent))
+ pal.setColor(QPalette::Inactive, QPalette::Accent, pal.accent().color());
+ if (standardPalette().color(QPalette::Inactive, QPalette::Highlight) == pal.color(QPalette::Inactive, QPalette::Highlight))
+ pal.setColor(QPalette::Inactive, QPalette::Highlight, pal.highlight().color());
+ if (standardPalette().color(QPalette::Inactive, QPalette::HighlightedText) == pal.color(QPalette::Inactive, QPalette::HighlightedText))
+ pal.setColor(QPalette::Inactive, QPalette::HighlightedText, pal.highlightedText().color());
+ if (standardPalette().color(QPalette::Inactive, QPalette::Text) == pal.color(QPalette::Inactive, QPalette::Text))
+ pal.setColor(QPalette::Inactive, QPalette::Text, pal.text().color());
+ if (standardPalette().color(QPalette::Inactive, QPalette::WindowText) == pal.color(QPalette::Inactive, QPalette::WindowText))
+ pal.setColor(QPalette::Inactive, QPalette::WindowText, pal.windowText().color());
+
+ if (highContrastTheme)
+ pal.setColor(QPalette::Active, QPalette::HighlightedText, pal.windowText().color());
+}
+
+QT_END_NAMESPACE
diff --git a/src/plugins/styles/modernwindows/qwindows11style_p.h b/src/plugins/styles/modernwindows/qwindows11style_p.h
new file mode 100644
index 0000000000..90e368f1ea
--- /dev/null
+++ b/src/plugins/styles/modernwindows/qwindows11style_p.h
@@ -0,0 +1,69 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QWINDOWS11STYLE_P_H
+#define QWINDOWS11STYLE_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtWidgets/private/qtwidgetsglobal_p.h>
+#include <qwindowsvistastyle_p_p.h>
+
+QT_BEGIN_NAMESPACE
+
+class QWindows11StylePrivate;
+class QWindows11Style;
+
+class QWindows11Style : public QWindowsVistaStyle
+{
+ Q_OBJECT
+public:
+ QWindows11Style();
+ ~QWindows11Style() override;
+ void drawComplexControl(ComplexControl control, const QStyleOptionComplex *option,
+ QPainter *painter, const QWidget *widget) const override;
+ void drawPrimitive(PrimitiveElement element, const QStyleOption *option,
+ QPainter *painter, const QWidget *widget) const override;
+ QRect subElementRect(QStyle::SubElement element, const QStyleOption *option,
+ const QWidget *widget = nullptr) const override;
+ QRect subControlRect(ComplexControl control, const QStyleOptionComplex *option,
+ SubControl subControl, const QWidget *widget) const override;
+ void drawControl(ControlElement element, const QStyleOption *option,
+ QPainter *painter, const QWidget *widget) const override;
+ int styleHint(StyleHint hint, const QStyleOption *opt = nullptr,
+ const QWidget *widget = nullptr, QStyleHintReturn *returnData = nullptr) const override;
+ void polish(QWidget* widget) override;
+
+ QSize sizeFromContents(ContentsType type, const QStyleOption *option,
+ const QSize &size, const QWidget *widget) const override;
+ int pixelMetric(PixelMetric metric, const QStyleOption *option = nullptr,
+ const QWidget *widget = nullptr) const override;
+ void polish(QPalette &pal) override;
+protected:
+ QWindows11Style(QWindows11StylePrivate &dd);
+private:
+ Q_DISABLE_COPY_MOVE(QWindows11Style)
+ Q_DECLARE_PRIVATE(QWindows11Style)
+ friend class QStyleFactory;
+
+ bool highContrastTheme = false;
+ int colorSchemeIndex = 0;
+ const QFont assetFont = QFont("Segoe Fluent Icons"); //Font to load icons from
+};
+
+class QWindows11StylePrivate : public QWindowsVistaStylePrivate {
+ Q_DECLARE_PUBLIC(QWindows11Style)
+};
+
+QT_END_NAMESPACE
+
+#endif // QWINDOWS11STYLE_P_H
diff --git a/src/plugins/styles/modernwindows/qwindowsthemedata.cpp b/src/plugins/styles/modernwindows/qwindowsthemedata.cpp
new file mode 100644
index 0000000000..44569e054d
--- /dev/null
+++ b/src/plugins/styles/modernwindows/qwindowsthemedata.cpp
@@ -0,0 +1,59 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "qwindowsthemedata_p.h"
+#include "qwindowsvistastyle_p_p.h"
+
+/* \internal
+ Returns \c true if the QWindowsThemeData is valid for use.
+*/
+bool QWindowsThemeData::isValid()
+{
+ return QWindowsVistaStylePrivate::useVista() && theme >= 0 && handle();
+}
+
+/* \internal
+ Returns the theme engine handle to the specific class.
+ If the handle hasn't been opened before, it opens the data, and
+ adds it to a static map, for caching.
+*/
+HTHEME QWindowsThemeData::handle()
+{
+ if (!QWindowsVistaStylePrivate::useVista())
+ return nullptr;
+
+ if (!htheme)
+ htheme = QWindowsVistaStylePrivate::createTheme(theme, QWindowsVistaStylePrivate::winId(widget));
+ return htheme;
+}
+
+/* \internal
+ Converts a QRect to the native RECT structure.
+*/
+RECT QWindowsThemeData::toRECT(const QRect &qr)
+{
+ RECT r;
+ r.left = qr.x();
+ r.right = qr.x() + qr.width();
+ r.top = qr.y();
+ r.bottom = qr.y() + qr.height();
+ return r;
+}
+
+/* \internal
+ Returns the native region of a part, if the part is considered
+ transparent. The region is scaled to the parts size (rect).
+*/
+HRGN QWindowsThemeData::mask(QWidget *widget)
+{
+ if (!IsThemeBackgroundPartiallyTransparent(handle(), partId, stateId))
+ return nullptr;
+
+ HRGN hrgn;
+ HDC dc = nullptr;
+ if (widget)
+ dc = QWindowsVistaStylePrivate::hdcForWidgetBackingStore(widget);
+ RECT nativeRect = toRECT(rect);
+ GetThemeBackgroundRegion(handle(), dc, partId, stateId, &nativeRect, &hrgn);
+ return hrgn;
+}
diff --git a/src/plugins/styles/modernwindows/qwindowsthemedata_p.h b/src/plugins/styles/modernwindows/qwindowsthemedata_p.h
new file mode 100644
index 0000000000..5de2bcbdea
--- /dev/null
+++ b/src/plugins/styles/modernwindows/qwindowsthemedata_p.h
@@ -0,0 +1,183 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QWINDOWSTHEMEDATA_P_H
+#define QWINDOWSTHEMEDATA_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <qwidget.h>
+#include <qt_windows.h>
+#include <uxtheme.h>
+#include <vssym32.h>
+#include <limits.h>
+
+
+// TMT_TEXTSHADOWCOLOR is wrongly defined in mingw
+#if TMT_TEXTSHADOWCOLOR != 3818
+#undef TMT_TEXTSHADOWCOLOR
+#define TMT_TEXTSHADOWCOLOR 3818
+#endif
+#ifndef TST_NONE
+# define TST_NONE 0
+#endif
+
+// These defines are missing from the tmschema, but still exist as
+// states for their parts
+#ifndef MINBS_INACTIVE
+#define MINBS_INACTIVE 5
+#endif
+#ifndef MAXBS_INACTIVE
+#define MAXBS_INACTIVE 5
+#endif
+#ifndef RBS_INACTIVE
+#define RBS_INACTIVE 5
+#endif
+#ifndef HBS_INACTIVE
+#define HBS_INACTIVE 5
+#endif
+#ifndef CBS_INACTIVE
+#define CBS_INACTIVE 5
+#endif
+
+// Declarations -----------------------------------------------------------------------------------
+class QWindowsThemeData
+{
+public:
+ explicit QWindowsThemeData(const QWidget *w = nullptr, QPainter *p = nullptr, int themeIn = -1,
+ int part = 0, int state = 0, const QRect &r = QRect())
+ : widget(w), painter(p), theme(themeIn), partId(part), stateId(state),
+ mirrorHorizontally(false), mirrorVertically(false), noBorder(false),
+ noContent(false), invertPixels(false), rect(r)
+ {}
+
+ HRGN mask(QWidget *widget);
+ HTHEME handle();
+ bool isValid();
+ QSizeF size();
+
+ static QSizeF themeSize(const QWidget *w = nullptr, QPainter *p = nullptr,
+ int themeIn = -1, int part = 0, int state = 0);
+ static RECT toRECT(const QRect &qr);
+
+ QMarginsF margins(const QRect &rect, int propId = TMT_CONTENTMARGINS);
+ QMarginsF margins(int propId = TMT_CONTENTMARGINS);
+
+ const QWidget *widget;
+ QPainter *painter;
+
+ int theme;
+ HTHEME htheme = nullptr;
+ int partId;
+ int stateId;
+
+ uint mirrorHorizontally : 1;
+ uint mirrorVertically : 1;
+ uint noBorder : 1;
+ uint noContent : 1;
+ uint invertPixels : 1;
+ uint rotate = 0;
+ QRect rect;
+};
+
+struct ThemeMapKey {
+ int theme = 0;
+ int partId = -1;
+ int stateId = -1;
+ bool noBorder = false;
+ bool noContent = false;
+
+ ThemeMapKey() = default;
+ ThemeMapKey(const QWindowsThemeData &data)
+ : theme(data.theme), partId(data.partId), stateId(data.stateId),
+ noBorder(data.noBorder), noContent(data.noContent) {}
+
+};
+
+inline size_t qHash(const ThemeMapKey &key)
+{ return key.theme ^ key.partId ^ key.stateId; }
+
+inline bool operator==(const ThemeMapKey &k1, const ThemeMapKey &k2)
+{
+ return k1.theme == k2.theme
+ && k1.partId == k2.partId
+ && k1.stateId == k2.stateId;
+}
+
+enum AlphaChannelType {
+ UnknownAlpha = -1, // Alpha of part & state not yet known
+ NoAlpha, // Totally opaque, no need to touch alpha (RGB)
+ MaskAlpha, // Alpha channel must be fixed (ARGB)
+ RealAlpha // Proper alpha values from Windows (ARGB_Premultiplied)
+};
+
+struct ThemeMapData {
+ AlphaChannelType alphaType = UnknownAlpha; // Which type of alpha on part & state
+
+ bool dataValid : 1; // Only used to detect if hash value is ok
+ bool partIsTransparent : 1;
+ bool hasAlphaChannel : 1; // True = part & state has real Alpha
+ bool wasAlphaSwapped : 1; // True = alpha channel needs to be swapped
+ bool hadInvalidAlpha : 1; // True = alpha channel contained invalid alpha values
+
+ ThemeMapData() : dataValid(false), partIsTransparent(false),
+ hasAlphaChannel(false), wasAlphaSwapped(false), hadInvalidAlpha(false) {}
+};
+
+
+#ifndef QT_NO_DEBUG_STREAM
+QDebug operator<<(QDebug d, const QWindowsThemeData &t);
+QDebug operator<<(QDebug d, const ThemeMapKey &k);
+QDebug operator<<(QDebug d, const ThemeMapData &td);
+#endif
+
+inline QSizeF QWindowsThemeData::size()
+{
+ QSizeF result(0, 0);
+ if (isValid()) {
+ SIZE size;
+ if (SUCCEEDED(GetThemePartSize(handle(), nullptr, partId, stateId, nullptr, TS_TRUE, &size)))
+ result = QSize(size.cx, size.cy);
+ }
+ return result;
+}
+
+inline QSizeF QWindowsThemeData::themeSize(const QWidget *w, QPainter *p, int themeIn, int part, int state)
+{
+ QWindowsThemeData theme(w, p, themeIn, part, state);
+ return theme.size();
+}
+
+inline QMarginsF QWindowsThemeData::margins(const QRect &qRect, int propId)
+{
+ QMarginsF result(0, 0, 0 ,0);
+ if (isValid()) {
+ MARGINS margins;
+ RECT rect = QWindowsThemeData::toRECT(qRect);
+ if (SUCCEEDED(GetThemeMargins(handle(), nullptr, partId, stateId, propId, &rect, &margins)))
+ result = QMargins(margins.cxLeftWidth, margins.cyTopHeight, margins.cxRightWidth, margins.cyBottomHeight);
+ }
+ return result;
+}
+
+inline QMarginsF QWindowsThemeData::margins(int propId)
+{
+ QMarginsF result(0, 0, 0 ,0);
+ if (isValid()) {
+ MARGINS margins;
+ if (SUCCEEDED(GetThemeMargins(handle(), nullptr, partId, stateId, propId, nullptr, &margins)))
+ result = QMargins(margins.cxLeftWidth, margins.cyTopHeight, margins.cxRightWidth, margins.cyBottomHeight);
+ }
+ return result;
+}
+
+#endif // QWINDOWSTHEMEDATA_P_H
diff --git a/src/plugins/styles/modernwindows/qwindowsvistaanimation.cpp b/src/plugins/styles/modernwindows/qwindowsvistaanimation.cpp
new file mode 100644
index 0000000000..ac9a5ad8c0
--- /dev/null
+++ b/src/plugins/styles/modernwindows/qwindowsvistaanimation.cpp
@@ -0,0 +1,15 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "qwindowsvistaanimation_p.h"
+#include "qwindowsvistastyle_p_p.h"
+
+bool QWindowsVistaAnimation::isUpdateNeeded() const
+{
+ return QWindowsVistaStylePrivate::useVista();
+}
+
+void QWindowsVistaAnimation::paint(QPainter *painter, const QStyleOption *option)
+{
+ painter->drawImage(option->rect, currentImage());
+}
diff --git a/src/plugins/styles/modernwindows/qwindowsvistaanimation_p.h b/src/plugins/styles/modernwindows/qwindowsvistaanimation_p.h
new file mode 100644
index 0000000000..817fa5f4b5
--- /dev/null
+++ b/src/plugins/styles/modernwindows/qwindowsvistaanimation_p.h
@@ -0,0 +1,49 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QWINDOWSVISTAANIMATION_P_H
+#define QWINDOWSVISTAANIMATION_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <private/qstyleanimation_p.h>
+#include <QtWidgets/private/qwindowsstyle_p.h>
+
+class QWindowsVistaAnimation : public QBlendStyleAnimation
+{
+ Q_OBJECT
+public:
+ QWindowsVistaAnimation(Type type, QObject *target) : QBlendStyleAnimation(type, target) { }
+
+ bool isUpdateNeeded() const override;
+ void paint(QPainter *painter, const QStyleOption *option);
+};
+
+
+// Handles state transition animations
+class QWindowsVistaTransition : public QWindowsVistaAnimation
+{
+ Q_OBJECT
+public:
+ QWindowsVistaTransition(QObject *target) : QWindowsVistaAnimation(Transition, target) {}
+};
+
+
+// Handles pulse animations (default buttons)
+class QWindowsVistaPulse: public QWindowsVistaAnimation
+{
+ Q_OBJECT
+public:
+ QWindowsVistaPulse(QObject *target) : QWindowsVistaAnimation(Pulse, target) {}
+};
+
+#endif // QWINDOWSVISTAANIMATION_P_H
diff --git a/src/plugins/styles/modernwindows/qwindowsvistastyle.cpp b/src/plugins/styles/modernwindows/qwindowsvistastyle.cpp
new file mode 100644
index 0000000000..1df134b629
--- /dev/null
+++ b/src/plugins/styles/modernwindows/qwindowsvistastyle.cpp
@@ -0,0 +1,5012 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "qwindowsvistastyle_p.h"
+#include "qwindowsvistastyle_p_p.h"
+#include "qwindowsvistaanimation_p.h"
+#include <qoperatingsystemversion.h>
+#include <qscreen.h>
+#include <qstylehints.h>
+#include <qwindow.h>
+#include <private/qstyleanimation_p.h>
+#include <private/qstylehelper_p.h>
+#include <qpa/qplatformnativeinterface.h>
+#include <private/qapplication_p.h>
+#include <private/qsystemlibrary_p.h>
+#include <private/qwindowsthemecache_p.h>
+
+#include "qdrawutil.h" // for now
+#include <qbackingstore.h>
+
+
+QT_BEGIN_NAMESPACE
+
+using namespace Qt::StringLiterals;
+
+static const int windowsItemFrame = 2; // menu item frame width
+static const int windowsItemHMargin = 3; // menu item hor text margin
+static const int windowsItemVMargin = 4; // menu item ver text margin
+static const int windowsArrowHMargin = 6; // arrow horizontal margin
+static const int windowsRightBorder = 15; // right border on windows
+
+#ifndef TMT_CONTENTMARGINS
+# define TMT_CONTENTMARGINS 3602
+#endif
+#ifndef TMT_SIZINGMARGINS
+# define TMT_SIZINGMARGINS 3601
+#endif
+#ifndef LISS_NORMAL
+# define LISS_NORMAL 1
+# define LISS_HOT 2
+# define LISS_SELECTED 3
+# define LISS_DISABLED 4
+# define LISS_SELECTEDNOTFOCUS 5
+# define LISS_HOTSELECTED 6
+#endif
+#ifndef BP_COMMANDLINK
+# define BP_COMMANDLINK 6
+# define BP_COMMANDLINKGLYPH 7
+# define CMDLGS_NORMAL 1
+# define CMDLGS_HOT 2
+# define CMDLGS_PRESSED 3
+# define CMDLGS_DISABLED 4
+#endif
+
+// QWindowsVistaStylePrivate -------------------------------------------------------------------------
+// Static initializations
+HWND QWindowsVistaStylePrivate::m_vistaTreeViewHelper = nullptr;
+bool QWindowsVistaStylePrivate::useVistaTheme = false;
+Q_CONSTINIT QBasicAtomicInt QWindowsVistaStylePrivate::ref = Q_BASIC_ATOMIC_INITIALIZER(-1); // -1 based refcounting
+
+static void qt_add_rect(HRGN &winRegion, QRect r)
+{
+ HRGN rgn = CreateRectRgn(r.left(), r.top(), r.x() + r.width(), r.y() + r.height());
+ if (rgn) {
+ HRGN dest = CreateRectRgn(0,0,0,0);
+ int result = CombineRgn(dest, winRegion, rgn, RGN_OR);
+ if (result) {
+ DeleteObject(winRegion);
+ winRegion = dest;
+ }
+ DeleteObject(rgn);
+ }
+}
+
+static HRGN qt_hrgn_from_qregion(const QRegion &region)
+{
+ HRGN hRegion = CreateRectRgn(0,0,0,0);
+ if (region.rectCount() == 1) {
+ qt_add_rect(hRegion, region.boundingRect());
+ return hRegion;
+ }
+ for (const QRect &rect : region)
+ qt_add_rect(hRegion, rect);
+ return hRegion;
+}
+
+static inline Qt::Orientation progressBarOrientation(const QStyleOption *option = nullptr)
+{
+ if (const auto *pb = qstyleoption_cast<const QStyleOptionProgressBar *>(option))
+ return pb->state & QStyle::State_Horizontal ? Qt::Horizontal : Qt::Vertical;
+ return Qt::Horizontal;
+}
+
+/* In order to obtain the correct VistaTreeViewTheme (arrows for PE_IndicatorBranch),
+ * we need to set the windows "explorer" theme explicitly on a native
+ * window and open the "TREEVIEW" theme handle passing its window handle
+ * in order to get Vista-style item view themes (particularly drawBackground()
+ * for selected items needs this).
+ * We invoke a service of the native Windows interface to create
+ * a non-visible window handle, open the theme on it and insert it into
+ * the cache so that it is found by QWindowsThemeData::handle() first.
+ */
+static inline HWND createTreeViewHelperWindow()
+{
+ using QWindowsApplication = QNativeInterface::Private::QWindowsApplication;
+
+ HWND result = nullptr;
+ if (auto nativeWindowsApp = dynamic_cast<QWindowsApplication *>(QGuiApplicationPrivate::platformIntegration()))
+ result = nativeWindowsApp->createMessageWindow(QStringLiteral("QTreeViewThemeHelperWindowClass"),
+ QStringLiteral("QTreeViewThemeHelperWindow"));
+ return result;
+}
+
+enum TransformType { SimpleTransform, HighDpiScalingTransform, ComplexTransform };
+
+static inline TransformType transformType(const QTransform &transform, qreal devicePixelRatio)
+{
+ if (transform.type() <= QTransform::TxTranslate)
+ return SimpleTransform;
+ if (transform.type() > QTransform::TxScale)
+ return ComplexTransform;
+ return qFuzzyCompare(transform.m11(), devicePixelRatio)
+ && qFuzzyCompare(transform.m22(), devicePixelRatio)
+ ? HighDpiScalingTransform : ComplexTransform;
+}
+
+// QTBUG-60571: Exclude known fully opaque theme parts which produce values
+// invalid in ARGB32_Premultiplied (for example, 0x00ffffff).
+static inline bool isFullyOpaque(const QWindowsThemeData &themeData)
+{
+ return themeData.theme == QWindowsVistaStylePrivate::TaskDialogTheme && themeData.partId == TDLG_PRIMARYPANEL;
+}
+
+static inline QRectF scaleRect(const QRectF &r, qreal factor)
+{
+ return r.isValid() && factor > 1
+ ? QRectF(r.topLeft() * factor, r.size() * factor) : r;
+}
+
+static QRegion scaleRegion(const QRegion &region, qreal factor)
+{
+ if (region.isEmpty() || qFuzzyCompare(factor, qreal(1)))
+ return region;
+ QRegion result;
+ for (const QRect &rect : region)
+ result += QRectF(QPointF(rect.topLeft()) * factor, QSizeF(rect.size() * factor)).toRect();
+ return result;
+}
+
+
+/* \internal
+ Checks if the theme engine can/should be used, or if we should fall back
+ to Windows style. For Windows 10, this will still return false for the
+ High Contrast themes.
+*/
+bool QWindowsVistaStylePrivate::useVista(bool update)
+{
+ if (update)
+ useVistaTheme = IsThemeActive() && (IsAppThemed() || !QCoreApplication::instance());
+ return useVistaTheme;
+}
+
+/* \internal
+ Handles refcounting, and queries the theme engine for usage.
+*/
+void QWindowsVistaStylePrivate::init(bool force)
+{
+ if (ref.ref() && !force)
+ return;
+ if (!force) // -1 based atomic refcounting
+ ref.ref();
+
+ useVista(true);
+}
+
+/* \internal
+ Cleans up all static data.
+*/
+void QWindowsVistaStylePrivate::cleanup(bool force)
+{
+ if (bufferBitmap) {
+ if (bufferDC && nullBitmap)
+ SelectObject(bufferDC, nullBitmap);
+ DeleteObject(bufferBitmap);
+ bufferBitmap = nullptr;
+ }
+
+ if (bufferDC)
+ DeleteDC(bufferDC);
+ bufferDC = nullptr;
+
+ if (ref.deref() && !force)
+ return;
+ if (!force) // -1 based atomic refcounting
+ ref.deref();
+
+ useVistaTheme = false;
+ cleanupHandleMap();
+}
+
+bool QWindowsVistaStylePrivate::transitionsEnabled() const
+{
+ BOOL animEnabled = false;
+ if (SystemParametersInfo(SPI_GETCLIENTAREAANIMATION, 0, &animEnabled, 0))
+ {
+ if (animEnabled)
+ return true;
+ }
+ return false;
+}
+
+int QWindowsVistaStylePrivate::pixelMetricFromSystemDp(QStyle::PixelMetric pm, const QStyleOption *option, const QWidget *widget)
+{
+ switch (pm) {
+ case QStyle::PM_IndicatorWidth:
+ return QWindowsThemeData::themeSize(widget, nullptr, QWindowsVistaStylePrivate::ButtonTheme, BP_CHECKBOX, CBS_UNCHECKEDNORMAL).width();
+ case QStyle::PM_IndicatorHeight:
+ return QWindowsThemeData::themeSize(widget, nullptr, QWindowsVistaStylePrivate::ButtonTheme, BP_CHECKBOX, CBS_UNCHECKEDNORMAL).height();
+ case QStyle::PM_ExclusiveIndicatorWidth:
+ return QWindowsThemeData::themeSize(widget, nullptr, QWindowsVistaStylePrivate::ButtonTheme, BP_RADIOBUTTON, RBS_UNCHECKEDNORMAL).width();
+ case QStyle::PM_ExclusiveIndicatorHeight:
+ return QWindowsThemeData::themeSize(widget, nullptr, QWindowsVistaStylePrivate::ButtonTheme, BP_RADIOBUTTON, RBS_UNCHECKEDNORMAL).height();
+ case QStyle::PM_ProgressBarChunkWidth:
+ return progressBarOrientation(option) == Qt::Horizontal
+ ? QWindowsThemeData::themeSize(widget, nullptr, QWindowsVistaStylePrivate::ProgressTheme, PP_CHUNK).width()
+ : QWindowsThemeData::themeSize(widget, nullptr, QWindowsVistaStylePrivate::ProgressTheme, PP_CHUNKVERT).height();
+ case QStyle::PM_SliderThickness:
+ return QWindowsThemeData::themeSize(widget, nullptr, QWindowsVistaStylePrivate::TrackBarTheme, TKP_THUMB).height();
+ case QStyle::PM_TitleBarHeight:
+ return QWindowsStylePrivate::pixelMetricFromSystemDp(QStyle::PM_TitleBarHeight, option, widget);
+ case QStyle::PM_MdiSubWindowFrameWidth:
+ return QWindowsThemeData::themeSize(widget, nullptr, QWindowsVistaStylePrivate::WindowTheme, WP_FRAMELEFT, FS_ACTIVE).width();
+ case QStyle::PM_DockWidgetFrameWidth:
+ return QWindowsThemeData::themeSize(widget, nullptr, QWindowsVistaStylePrivate::WindowTheme, WP_SMALLFRAMERIGHT, FS_ACTIVE).width();
+ default:
+ break;
+ }
+ return QWindowsVistaStylePrivate::InvalidMetric;
+}
+
+int QWindowsVistaStylePrivate::fixedPixelMetric(QStyle::PixelMetric pm)
+{
+ switch (pm) {
+ case QStyle::PM_DockWidgetTitleBarButtonMargin:
+ return 5;
+ case QStyle::PM_ScrollBarSliderMin:
+ return 18;
+ case QStyle::PM_MenuHMargin:
+ case QStyle::PM_MenuVMargin:
+ return 0;
+ case QStyle::PM_MenuPanelWidth:
+ return 3;
+ default:
+ break;
+ }
+
+ return QWindowsVistaStylePrivate::InvalidMetric;
+}
+
+bool QWindowsVistaStylePrivate::initVistaTreeViewTheming()
+{
+ if (m_vistaTreeViewHelper)
+ return true;
+
+ m_vistaTreeViewHelper = createTreeViewHelperWindow();
+ if (!m_vistaTreeViewHelper) {
+ qWarning("Unable to create the treeview helper window.");
+ return false;
+ }
+ if (FAILED(SetWindowTheme(m_vistaTreeViewHelper, L"explorer", nullptr))) {
+ qErrnoWarning("SetWindowTheme() failed.");
+ cleanupVistaTreeViewTheming();
+ return false;
+ }
+ return true;
+}
+
+void QWindowsVistaStylePrivate::cleanupVistaTreeViewTheming()
+{
+ if (m_vistaTreeViewHelper) {
+ DestroyWindow(m_vistaTreeViewHelper);
+ m_vistaTreeViewHelper = nullptr;
+ }
+}
+
+/* \internal
+ Closes all open theme data handles to ensure that we don't leak
+ resources, and that we don't refer to old handles when for
+ example the user changes the theme style.
+*/
+void QWindowsVistaStylePrivate::cleanupHandleMap()
+{
+ QWindowsThemeCache::clearAllThemeCaches();
+ QWindowsVistaStylePrivate::cleanupVistaTreeViewTheming();
+}
+
+HTHEME QWindowsVistaStylePrivate::createTheme(int theme, HWND hwnd)
+{
+ if (theme == VistaTreeViewTheme && QWindowsVistaStylePrivate::initVistaTreeViewTheming())
+ hwnd = QWindowsVistaStylePrivate::m_vistaTreeViewHelper;
+ return QWindowsThemeCache::createTheme(theme, hwnd);
+}
+
+QBackingStore *QWindowsVistaStylePrivate::backingStoreForWidget(const QWidget *widget)
+{
+ if (QBackingStore *backingStore = widget->backingStore())
+ return backingStore;
+ if (const QWidget *topLevel = widget->nativeParentWidget())
+ if (QBackingStore *topLevelBackingStore = topLevel->backingStore())
+ return topLevelBackingStore;
+ return nullptr;
+}
+
+HDC QWindowsVistaStylePrivate::hdcForWidgetBackingStore(const QWidget *widget)
+{
+ if (QBackingStore *backingStore = backingStoreForWidget(widget)) {
+ QPlatformNativeInterface *nativeInterface = QGuiApplication::platformNativeInterface();
+ if (nativeInterface)
+ return static_cast<HDC>(nativeInterface->nativeResourceForBackingStore(QByteArrayLiteral("getDC"), backingStore));
+ }
+ return nullptr;
+}
+
+QString QWindowsVistaStylePrivate::themeName(int theme)
+{
+ return QWindowsThemeCache::themeName(theme);
+}
+
+bool QWindowsVistaStylePrivate::isItemViewDelegateLineEdit(const QWidget *widget)
+{
+ if (!widget)
+ return false;
+ const QWidget *parent1 = widget->parentWidget();
+ // Exclude dialogs or other toplevels parented on item views.
+ if (!parent1 || parent1->isWindow())
+ return false;
+ const QWidget *parent2 = parent1->parentWidget();
+ return parent2 && widget->inherits("QLineEdit")
+ && parent2->inherits("QAbstractItemView");
+}
+
+// Returns whether base color is set for this widget
+bool QWindowsVistaStylePrivate::isLineEditBaseColorSet(const QStyleOption *option, const QWidget *widget)
+{
+ uint resolveMask = option->palette.resolveMask();
+ if (widget) {
+ // Since spin box includes a line edit we need to resolve the palette mask also from
+ // the parent, as while the color is always correct on the palette supplied by panel,
+ // the mask can still be empty. If either mask specifies custom base color, use that.
+#if QT_CONFIG(spinbox)
+ if (const QAbstractSpinBox *spinbox = qobject_cast<QAbstractSpinBox*>(widget->parentWidget()))
+ resolveMask |= spinbox->palette().resolveMask();
+#endif // QT_CONFIG(spinbox)
+ }
+ return (resolveMask & (1 << QPalette::Base)) != 0;
+}
+
+/*! \internal
+ This function will always return a valid window handle, and might
+ create a limbo widget to do so.
+ We often need a window handle to for example open theme data, so
+ this function ensures that we get one.
+*/
+HWND QWindowsVistaStylePrivate::winId(const QWidget *widget)
+{
+ if (widget) {
+ if (const HWND hwnd = QApplicationPrivate::getHWNDForWidget(const_cast<QWidget *>(widget)))
+ return hwnd;
+ }
+
+ // Find top level with native window (there might be dialogs that do not have one).
+ const auto allWindows = QGuiApplication::allWindows();
+ for (const QWindow *window : allWindows) {
+ if (window->isTopLevel() && window->type() != Qt::Desktop && window->handle() != nullptr)
+ return reinterpret_cast<HWND>(window->winId());
+ }
+
+ return GetDesktopWindow();
+}
+
+/*! \internal
+ Returns a native buffer (DIB section) of at least the size of
+ ( \a x , \a y ). The buffer has a 32 bit depth, to not lose
+ the alpha values on proper alpha-pixmaps.
+*/
+HBITMAP QWindowsVistaStylePrivate::buffer(int w, int h)
+{
+ // If we already have a HBITMAP which is of adequate size, just return that
+ if (bufferBitmap) {
+ if (bufferW >= w && bufferH >= h)
+ return bufferBitmap;
+ // Not big enough, discard the old one
+ if (bufferDC && nullBitmap)
+ SelectObject(bufferDC, nullBitmap);
+ DeleteObject(bufferBitmap);
+ bufferBitmap = nullptr;
+ }
+
+ w = qMax(bufferW, w);
+ h = qMax(bufferH, h);
+
+ if (!bufferDC) {
+ HDC displayDC = GetDC(nullptr);
+ bufferDC = CreateCompatibleDC(displayDC);
+ ReleaseDC(nullptr, displayDC);
+ }
+
+ // Define the header
+ BITMAPINFO bmi;
+ memset(&bmi, 0, sizeof(bmi));
+ bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
+ bmi.bmiHeader.biWidth = w;
+ bmi.bmiHeader.biHeight = -h;
+ bmi.bmiHeader.biPlanes = 1;
+ bmi.bmiHeader.biBitCount = 32;
+ bmi.bmiHeader.biCompression = BI_RGB;
+
+ // Create the pixmap
+ bufferPixels = nullptr;
+ bufferBitmap = CreateDIBSection(bufferDC, &bmi, DIB_RGB_COLORS, reinterpret_cast<void **>(&bufferPixels), nullptr, 0);
+ GdiFlush();
+ nullBitmap = static_cast<HBITMAP>(SelectObject(bufferDC, bufferBitmap));
+
+ if (Q_UNLIKELY(!bufferBitmap)) {
+ qErrnoWarning("QWindowsVistaStylePrivate::buffer(%dx%d), CreateDIBSection() failed.", w, h);
+ bufferW = 0;
+ bufferH = 0;
+ return nullptr;
+ }
+ if (Q_UNLIKELY(!bufferPixels)) {
+ qErrnoWarning("QWindowsVistaStylePrivate::buffer(%dx%d), CreateDIBSection() did not allocate pixel data.", w, h);
+ bufferW = 0;
+ bufferH = 0;
+ return nullptr;
+ }
+ bufferW = w;
+ bufferH = h;
+#ifdef DEBUG_XP_STYLE
+ qDebug("Creating new dib section (%d, %d)", w, h);
+#endif
+ return bufferBitmap;
+}
+
+/*! \internal
+ Returns \c true if the part contains any transparency at all. This does
+ not indicate what kind of transparency we're dealing with. It can be
+ - Alpha transparency
+ - Masked transparency
+*/
+bool QWindowsVistaStylePrivate::isTransparent(QWindowsThemeData &themeData)
+{
+ return IsThemeBackgroundPartiallyTransparent(themeData.handle(), themeData.partId,
+ themeData.stateId);
+}
+
+
+/*! \internal
+ Returns a QRegion of the region of the part
+*/
+QRegion QWindowsVistaStylePrivate::region(QWindowsThemeData &themeData)
+{
+ HRGN hRgn = nullptr;
+ const qreal factor = QWindowsStylePrivate::nativeMetricScaleFactor(themeData.widget);
+ RECT rect = themeData.toRECT(QRect(themeData.rect.topLeft() / factor, themeData.rect.size() / factor));
+ if (!SUCCEEDED(GetThemeBackgroundRegion(themeData.handle(), bufferHDC(), themeData.partId,
+ themeData.stateId, &rect, &hRgn))) {
+ return QRegion();
+ }
+
+ HRGN dest = CreateRectRgn(0, 0, 0, 0);
+ const bool success = CombineRgn(dest, hRgn, nullptr, RGN_COPY) != ERROR;
+
+ QRegion region;
+
+ if (success) {
+ QVarLengthArray<char> buf(256);
+ RGNDATA *rd = reinterpret_cast<RGNDATA *>(buf.data());
+ if (GetRegionData(dest, buf.size(), rd) == 0) {
+ const auto numBytes = GetRegionData(dest, 0, nullptr);
+ if (numBytes > 0) {
+ buf.resize(numBytes);
+ rd = reinterpret_cast<RGNDATA *>(buf.data());
+ if (GetRegionData(dest, numBytes, rd) == 0)
+ rd = nullptr;
+ } else {
+ rd = nullptr;
+ }
+ }
+ if (rd) {
+ RECT *r = reinterpret_cast<RECT *>(rd->Buffer);
+ for (uint i = 0; i < rd->rdh.nCount; ++i) {
+ QRect rect;
+ rect.setCoords(int(r->left * factor), int(r->top * factor),
+ int((r->right - 1) * factor), int((r->bottom - 1) * factor));
+ ++r;
+ region |= rect;
+ }
+ }
+ }
+
+ DeleteObject(hRgn);
+ DeleteObject(dest);
+
+ return region;
+}
+
+/*! \internal
+ Returns \c true if the native doublebuffer contains pixels with
+ varying alpha value.
+*/
+bool QWindowsVistaStylePrivate::hasAlphaChannel(const QRect &rect)
+{
+ const int startX = rect.left();
+ const int startY = rect.top();
+ const int w = rect.width();
+ const int h = rect.height();
+
+ int firstAlpha = -1;
+ for (int y = startY; y < h/2; ++y) {
+ auto buffer = reinterpret_cast<const DWORD *>(bufferPixels) + (y * bufferW);
+ for (int x = startX; x < w; ++x, ++buffer) {
+ int alpha = (*buffer) >> 24;
+ if (firstAlpha == -1)
+ firstAlpha = alpha;
+ else if (alpha != firstAlpha)
+ return true;
+ }
+ }
+ return false;
+}
+
+/*! \internal
+ When the theme engine paints both a true alpha pixmap and a glyph
+ into our buffer, the glyph might not contain a proper alpha value.
+ The rule of thumb for premultiplied pixmaps is that the color
+ values of a pixel can never be higher than the alpha values, so
+ we use this to our advantage here, and fix all instances where
+ this occurs.
+*/
+bool QWindowsVistaStylePrivate::fixAlphaChannel(const QRect &rect)
+{
+ const int startX = rect.left();
+ const int startY = rect.top();
+ const int w = rect.width();
+ const int h = rect.height();
+ bool hasFixedAlphaValue = false;
+
+ for (int y = startY; y < h; ++y) {
+ auto buffer = reinterpret_cast<DWORD *>(bufferPixels) + (y * bufferW);
+ for (int x = startX; x < w; ++x, ++buffer) {
+ uint pixel = *buffer;
+ int alpha = qAlpha(pixel);
+ if (qRed(pixel) > alpha || qGreen(pixel) > alpha || qBlue(pixel) > alpha) {
+ *buffer |= 0xff000000;
+ hasFixedAlphaValue = true;
+ }
+ }
+ }
+ return hasFixedAlphaValue;
+}
+
+/*! \internal
+ Swaps the alpha values on certain pixels:
+ 0xFF?????? -> 0x00??????
+ 0x00?????? -> 0xFF??????
+ Used to determine the mask of a non-alpha transparent pixmap in
+ the native doublebuffer, and swap the alphas so we may paint
+ the image as a Premultiplied QImage with drawImage(), and obtain
+ the mask transparency.
+*/
+bool QWindowsVistaStylePrivate::swapAlphaChannel(const QRect &rect, bool allPixels)
+{
+ const int startX = rect.left();
+ const int startY = rect.top();
+ const int w = rect.width();
+ const int h = rect.height();
+ bool valueChange = false;
+
+ // Flip the alphas, so that 255-alpha pixels are 0, and 0-alpha are 255.
+ for (int y = startY; y < h; ++y) {
+ auto buffer = reinterpret_cast<DWORD *>(bufferPixels) + (y * bufferW);
+ for (int x = startX; x < w; ++x, ++buffer) {
+ if (allPixels) {
+ *buffer |= 0xFF000000;
+ continue;
+ }
+ unsigned int alphaValue = (*buffer) & 0xFF000000;
+ if (alphaValue == 0xFF000000) {
+ *buffer = 0;
+ valueChange = true;
+ } else if (alphaValue == 0) {
+ *buffer |= 0xFF000000;
+ valueChange = true;
+ }
+ }
+ }
+ return valueChange;
+}
+
+/*! \internal
+ Main theme drawing function.
+ Determines the correct lowlevel drawing method depending on several
+ factors.
+ Use drawBackgroundThruNativeBuffer() if:
+ - Painter does not have an HDC
+ - Theme part is flipped (mirrored horizontally)
+ else use drawBackgroundDirectly().
+ \note drawBackgroundThruNativeBuffer() can return false for large
+ sizes due to buffer()/CreateDIBSection() failing.
+*/
+bool QWindowsVistaStylePrivate::drawBackground(QWindowsThemeData &themeData, qreal correctionFactor)
+{
+ if (themeData.rect.isEmpty())
+ return true;
+
+ QPainter *painter = themeData.painter;
+ Q_ASSERT_X(painter != nullptr, "QWindowsVistaStylePrivate::drawBackground()", "Trying to draw a theme part without a painter");
+ if (!painter || !painter->isActive())
+ return false;
+
+ painter->save();
+
+ // Access paintDevice via engine since the painter may
+ // return the clip device which can still be a widget device in case of grabWidget().
+
+ bool translucentToplevel = false;
+ const QPaintDevice *paintDevice = painter->device();
+ const qreal aditionalDevicePixelRatio = themeData.widget ? themeData.widget->devicePixelRatio() : qreal(1);
+ if (paintDevice->devType() == QInternal::Widget) {
+ const QWidget *window = static_cast<const QWidget *>(paintDevice)->window();
+ translucentToplevel = window->testAttribute(Qt::WA_TranslucentBackground);
+ }
+
+ const TransformType tt = transformType(painter->deviceTransform(), aditionalDevicePixelRatio);
+
+ bool canDrawDirectly = false;
+ if (themeData.widget && painter->opacity() == 1.0 && !themeData.rotate
+ && !isFullyOpaque(themeData)
+ && tt != ComplexTransform && !themeData.mirrorVertically && !themeData.invertPixels
+ && !translucentToplevel) {
+ // Draw on backing store DC only for real widgets or backing store images.
+ const QPaintDevice *enginePaintDevice = painter->paintEngine()->paintDevice();
+ switch (enginePaintDevice->devType()) {
+ case QInternal::Widget:
+ canDrawDirectly = true;
+ break;
+ case QInternal::Image:
+ // Ensure the backing store has received as resize and is initialized.
+ if (QBackingStore *bs = backingStoreForWidget(themeData.widget)) {
+ if (bs->size().isValid() && bs->paintDevice() == enginePaintDevice)
+ canDrawDirectly = true;
+ }
+ break;
+ }
+ }
+
+ const HDC dc = canDrawDirectly ? hdcForWidgetBackingStore(themeData.widget) : nullptr;
+ const bool result = dc && qFuzzyCompare(correctionFactor, qreal(1))
+ ? drawBackgroundDirectly(dc, themeData, aditionalDevicePixelRatio)
+ : drawBackgroundThruNativeBuffer(themeData, aditionalDevicePixelRatio, correctionFactor);
+ painter->restore();
+ return result;
+}
+
+/*! \internal
+ This function draws the theme parts directly to the paintengines HDC.
+ Do not use this if you need to perform other transformations on the
+ resulting data.
+*/
+bool QWindowsVistaStylePrivate::drawBackgroundDirectly(HDC dc, QWindowsThemeData &themeData, qreal additionalDevicePixelRatio)
+{
+ QPainter *painter = themeData.painter;
+
+ const auto &deviceTransform = painter->deviceTransform();
+ const QPointF redirectionDelta(deviceTransform.dx(), deviceTransform.dy());
+ const QRect area = scaleRect(QRectF(themeData.rect), additionalDevicePixelRatio).translated(redirectionDelta).toRect();
+
+ QRegion sysRgn = painter->paintEngine()->systemClip();
+ if (sysRgn.isEmpty())
+ sysRgn = area;
+ else
+ sysRgn &= area;
+ if (painter->hasClipping())
+ sysRgn &= scaleRegion(painter->clipRegion(), additionalDevicePixelRatio).translated(redirectionDelta.toPoint());
+ HRGN hrgn = qt_hrgn_from_qregion(sysRgn);
+ SelectClipRgn(dc, hrgn);
+
+#ifdef DEBUG_XP_STYLE
+ printf("---[ DIRECT PAINTING ]------------------> Name(%-10s) Part(%d) State(%d)\n",
+ qPrintable(themeData.name), themeData.partId, themeData.stateId);
+ showProperties(themeData);
+#endif
+
+ RECT drawRECT = themeData.toRECT(area);
+ DTBGOPTS drawOptions;
+ memset(&drawOptions, 0, sizeof(drawOptions));
+ drawOptions.dwSize = sizeof(drawOptions);
+ drawOptions.rcClip = themeData.toRECT(sysRgn.boundingRect());
+ drawOptions.dwFlags = DTBG_CLIPRECT
+ | (themeData.noBorder ? DTBG_OMITBORDER : 0)
+ | (themeData.noContent ? DTBG_OMITCONTENT : 0)
+ | (themeData.mirrorHorizontally ? DTBG_MIRRORDC : 0);
+
+ const HRESULT result = DrawThemeBackgroundEx(themeData.handle(), dc, themeData.partId, themeData.stateId, &(drawRECT), &drawOptions);
+ SelectClipRgn(dc, nullptr);
+ DeleteObject(hrgn);
+ return SUCCEEDED(result);
+}
+
+/*! \internal
+ This function uses a secondary Native doublebuffer for painting parts.
+ It should only be used when the painteengine doesn't provide a proper
+ HDC for direct painting (e.g. when doing a grabWidget(), painting to
+ other pixmaps etc), or when special transformations are needed (e.g.
+ flips (horizontal mirroring only, vertical are handled by the theme
+ engine).
+
+ \a correctionFactor is an additional factor used to scale up controls
+ that are too small on High DPI screens, as has been observed for
+ WP_MDICLOSEBUTTON, WP_MDIRESTOREBUTTON, WP_MDIMINBUTTON (QTBUG-75927).
+*/
+bool QWindowsVistaStylePrivate::drawBackgroundThruNativeBuffer(QWindowsThemeData &themeData,
+ qreal additionalDevicePixelRatio,
+ qreal correctionFactor)
+{
+ QPainter *painter = themeData.painter;
+ QRectF rectF = scaleRect(QRectF(themeData.rect), additionalDevicePixelRatio);
+
+ if ((themeData.rotate + 90) % 180 == 0) { // Catch 90,270,etc.. degree flips.
+ rectF = QRectF(0, 0, rectF.height(), rectF.width());
+ }
+ rectF.moveTo(0, 0);
+
+ const bool hasCorrectionFactor = !qFuzzyCompare(correctionFactor, qreal(1));
+ QRect rect = rectF.toRect();
+ const QRect drawRect = hasCorrectionFactor
+ ? QRectF(rectF.topLeft() / correctionFactor, rectF.size() / correctionFactor).toRect()
+ : rect;
+ int partId = themeData.partId;
+ int stateId = themeData.stateId;
+ int w = rect.width();
+ int h = rect.height();
+
+ // Values initialized later, either from cached values, or from function calls
+ AlphaChannelType alphaType = UnknownAlpha;
+ bool stateHasData = true; // We assume so;
+ bool hasAlpha = false;
+ bool partIsTransparent;
+ bool potentialInvalidAlpha;
+
+ QString pixmapCacheKey = QStringLiteral("$qt_xp_");
+ pixmapCacheKey.append(themeName(themeData.theme));
+ pixmapCacheKey.append(QLatin1Char('p'));
+ pixmapCacheKey.append(QString::number(partId));
+ pixmapCacheKey.append(QLatin1Char('s'));
+ pixmapCacheKey.append(QString::number(stateId));
+ pixmapCacheKey.append(QLatin1Char('s'));
+ pixmapCacheKey.append(themeData.noBorder ? QLatin1Char('0') : QLatin1Char('1'));
+ pixmapCacheKey.append(QLatin1Char('b'));
+ pixmapCacheKey.append(themeData.noContent ? QLatin1Char('0') : QLatin1Char('1'));
+ pixmapCacheKey.append(QString::number(w));
+ pixmapCacheKey.append(QLatin1Char('w'));
+ pixmapCacheKey.append(QString::number(h));
+ pixmapCacheKey.append(QLatin1Char('h'));
+ pixmapCacheKey.append(QString::number(additionalDevicePixelRatio));
+ pixmapCacheKey.append(QLatin1Char('d'));
+ if (hasCorrectionFactor) {
+ pixmapCacheKey.append(QLatin1Char('c'));
+ pixmapCacheKey.append(QString::number(correctionFactor));
+ }
+
+ QPixmap cachedPixmap;
+ ThemeMapKey key(themeData);
+ ThemeMapData data = alphaCache.value(key);
+
+ bool haveCachedPixmap = false;
+ bool isCached = data.dataValid;
+ if (isCached) {
+ partIsTransparent = data.partIsTransparent;
+ hasAlpha = data.hasAlphaChannel;
+ alphaType = data.alphaType;
+ potentialInvalidAlpha = data.hadInvalidAlpha;
+
+ haveCachedPixmap = QPixmapCache::find(pixmapCacheKey, &cachedPixmap);
+
+#ifdef DEBUG_XP_STYLE
+ char buf[25];
+ ::sprintf(buf, "+ Pixmap(%3d, %3d) ]", w, h);
+ printf("---[ CACHED %s--------> Name(%-10s) Part(%d) State(%d)\n",
+ haveCachedPixmap ? buf : "]-------------------",
+ qPrintable(themeData.name), themeData.partId, themeData.stateId);
+#endif
+ } else {
+ // Not cached, so get values from Theme Engine
+ BOOL tmt_borderonly = false;
+ COLORREF tmt_transparentcolor = 0x0;
+ PROPERTYORIGIN proporigin = PO_NOTFOUND;
+ GetThemeBool(themeData.handle(), themeData.partId, themeData.stateId, TMT_BORDERONLY, &tmt_borderonly);
+ GetThemeColor(themeData.handle(), themeData.partId, themeData.stateId, TMT_TRANSPARENTCOLOR, &tmt_transparentcolor);
+ GetThemePropertyOrigin(themeData.handle(), themeData.partId, themeData.stateId, TMT_CAPTIONMARGINS, &proporigin);
+
+ partIsTransparent = isTransparent(themeData);
+
+ potentialInvalidAlpha = false;
+ GetThemePropertyOrigin(themeData.handle(), themeData.partId, themeData.stateId, TMT_GLYPHTYPE, &proporigin);
+ if (proporigin == PO_PART || proporigin == PO_STATE) {
+ int tmt_glyphtype = GT_NONE;
+ GetThemeEnumValue(themeData.handle(), themeData.partId, themeData.stateId, TMT_GLYPHTYPE, &tmt_glyphtype);
+ potentialInvalidAlpha = partIsTransparent && tmt_glyphtype == GT_IMAGEGLYPH;
+ }
+
+#ifdef DEBUG_XP_STYLE
+ printf("---[ NOT CACHED ]-----------------------> Name(%-10s) Part(%d) State(%d)\n",
+ qPrintable(themeData.name), themeData.partId, themeData.stateId);
+ printf("-->partIsTransparen = %d\n", partIsTransparent);
+ printf("-->potentialInvalidAlpha = %d\n", potentialInvalidAlpha);
+ showProperties(themeData);
+#endif
+ }
+ bool wasAlphaSwapped = false;
+ bool wasAlphaFixed = false;
+
+ // OLD PSDK Workaround ------------------------------------------------------------------------
+ // See if we need extra clipping for the older PSDK, which does
+ // not have a DrawThemeBackgroundEx function for DTGB_OMITBORDER
+ // and DTGB_OMITCONTENT
+ bool addBorderContentClipping = false;
+ QRegion extraClip;
+ QRect area = drawRect;
+ if (themeData.noBorder || themeData.noContent) {
+ extraClip = area;
+ // We are running on a system where the uxtheme.dll does not have
+ // the DrawThemeBackgroundEx function, so we need to clip away
+ // borders or contents manually.
+
+ int borderSize = 0;
+ PROPERTYORIGIN origin = PO_NOTFOUND;
+ GetThemePropertyOrigin(themeData.handle(), themeData.partId, themeData.stateId, TMT_BORDERSIZE, &origin);
+ GetThemeInt(themeData.handle(), themeData.partId, themeData.stateId, TMT_BORDERSIZE, &borderSize);
+ borderSize *= additionalDevicePixelRatio;
+
+ // Clip away border region
+ if ((origin == PO_CLASS || origin == PO_PART || origin == PO_STATE) && borderSize > 0) {
+ if (themeData.noBorder) {
+ extraClip &= area;
+ area = area.adjusted(-borderSize, -borderSize, borderSize, borderSize);
+ }
+
+ // Clip away content region
+ if (themeData.noContent) {
+ QRegion content = area.adjusted(borderSize, borderSize, -borderSize, -borderSize);
+ extraClip ^= content;
+ }
+ }
+ addBorderContentClipping = (themeData.noBorder | themeData.noContent);
+ }
+
+ QImage img;
+ if (!haveCachedPixmap) { // If the pixmap is not cached, generate it! -------------------------
+ if (!buffer(drawRect.width(), drawRect.height())) // Ensure a buffer of at least (w, h) in size
+ return false;
+ HDC dc = bufferHDC();
+
+ // Clear the buffer
+ if (alphaType != NoAlpha) {
+ // Consider have separate "memset" function for small chunks for more speedup
+ memset(bufferPixels, 0x00, bufferW * drawRect.height() * 4);
+ }
+
+ // Difference between area and rect
+ int dx = area.x() - drawRect.x();
+ int dy = area.y() - drawRect.y();
+
+ // Adjust so painting rect starts from Origo
+ rect.moveTo(0,0);
+ area.moveTo(dx,dy);
+ DTBGOPTS drawOptions;
+ drawOptions.dwSize = sizeof(drawOptions);
+ drawOptions.rcClip = themeData.toRECT(rect);
+ drawOptions.dwFlags = DTBG_CLIPRECT
+ | (themeData.noBorder ? DTBG_OMITBORDER : 0)
+ | (themeData.noContent ? DTBG_OMITCONTENT : 0);
+
+ // Drawing the part into the backing store
+ RECT wRect(themeData.toRECT(area));
+ DrawThemeBackgroundEx(themeData.handle(), dc, themeData.partId, themeData.stateId, &wRect, &drawOptions);
+
+ // If not cached, analyze the buffer data to figure
+ // out alpha type, and if it contains data
+ if (!isCached) {
+ // SHORTCUT: If the part's state has no data, cache it for NOOP later
+ if (!stateHasData) {
+ memset(static_cast<void *>(&data), 0, sizeof(data));
+ data.dataValid = true;
+ alphaCache.insert(key, data);
+ return true;
+ }
+ hasAlpha = hasAlphaChannel(rect);
+ if (!hasAlpha && partIsTransparent)
+ potentialInvalidAlpha = true;
+#if defined(DEBUG_XP_STYLE) && 1
+ dumpNativeDIB(drawRect.width(), drawRect.height());
+#endif
+ }
+
+ // Fix alpha values, if needed
+ if (potentialInvalidAlpha)
+ wasAlphaFixed = fixAlphaChannel(drawRect);
+
+ QImage::Format format;
+ if ((partIsTransparent && !wasAlphaSwapped) || (!partIsTransparent && hasAlpha)) {
+ format = QImage::Format_ARGB32_Premultiplied;
+ alphaType = RealAlpha;
+ } else if (wasAlphaSwapped) {
+ format = QImage::Format_ARGB32_Premultiplied;
+ alphaType = MaskAlpha;
+ } else {
+ format = QImage::Format_RGB32;
+ // The image data we got from the theme engine does not have any transparency,
+ // thus the alpha channel is set to 0.
+ // However, Format_RGB32 requires the alpha part to be set to 0xff, thus
+ // we must flip it from 0x00 to 0xff
+ swapAlphaChannel(rect, true);
+ alphaType = NoAlpha;
+ }
+#if defined(DEBUG_XP_STYLE) && 1
+ printf("Image format is: %s\n", alphaType == RealAlpha ? "Real Alpha" : alphaType == MaskAlpha ? "Masked Alpha" : "No Alpha");
+#endif
+ img = QImage(bufferPixels, bufferW, bufferH, format);
+ if (themeData.invertPixels)
+ img.invertPixels();
+
+ if (hasCorrectionFactor)
+ img = img.scaled(img.size() * correctionFactor, Qt::KeepAspectRatio, Qt::SmoothTransformation);
+ img.setDevicePixelRatio(additionalDevicePixelRatio);
+ }
+
+ // Blitting backing store
+ bool useRegion = partIsTransparent && !hasAlpha && !wasAlphaSwapped;
+
+ QRegion newRegion;
+ QRegion oldRegion;
+ if (useRegion) {
+ newRegion = region(themeData);
+ oldRegion = painter->clipRegion();
+ painter->setClipRegion(newRegion);
+#if defined(DEBUG_XP_STYLE) && 0
+ printf("Using region:\n");
+ for (const QRect &r : newRegion)
+ printf(" (%d, %d, %d, %d)\n", r.x(), r.y(), r.right(), r.bottom());
+#endif
+ }
+
+ if (addBorderContentClipping)
+ painter->setClipRegion(scaleRegion(extraClip, 1.0 / additionalDevicePixelRatio), Qt::IntersectClip);
+
+ if (!themeData.mirrorHorizontally && !themeData.mirrorVertically && !themeData.rotate) {
+ if (!haveCachedPixmap)
+ painter->drawImage(themeData.rect, img, rect);
+ else
+ painter->drawPixmap(themeData.rect, cachedPixmap);
+ } else {
+ // This is _slow_!
+ // Make a copy containing only the necessary data, and mirror
+ // on all wanted axes. Then draw the copy.
+ // If cached, the normal pixmap is cached, instead of caching
+ // all possible orientations for each part and state.
+ QImage imgCopy;
+ if (!haveCachedPixmap)
+ imgCopy = img.copy(rect);
+ else
+ imgCopy = cachedPixmap.toImage();
+
+ if (themeData.rotate) {
+ QTransform rotMatrix;
+ rotMatrix.rotate(themeData.rotate);
+ imgCopy = imgCopy.transformed(rotMatrix);
+ }
+ if (themeData.mirrorHorizontally || themeData.mirrorVertically)
+ imgCopy = imgCopy.mirrored(themeData.mirrorHorizontally, themeData.mirrorVertically);
+ painter->drawImage(themeData.rect, imgCopy);
+ }
+
+ if (useRegion || addBorderContentClipping) {
+ if (oldRegion.isEmpty())
+ painter->setClipping(false);
+ else
+ painter->setClipRegion(oldRegion);
+ }
+
+ // Cache the pixmap to avoid expensive swapAlphaChannel() calls
+ if (!haveCachedPixmap && w && h) {
+ QPixmap pix = QPixmap::fromImage(img).copy(rect);
+ QPixmapCache::insert(pixmapCacheKey, pix);
+#ifdef DEBUG_XP_STYLE
+ printf("+++Adding pixmap to cache, size(%d, %d), wasAlphaSwapped(%d), wasAlphaFixed(%d), name(%s)\n",
+ w, h, wasAlphaSwapped, wasAlphaFixed, qPrintable(pixmapCacheKey));
+#endif
+ }
+
+ // Add to theme part cache
+ if (!isCached) {
+ memset(static_cast<void *>(&data), 0, sizeof(data));
+ data.dataValid = true;
+ data.partIsTransparent = partIsTransparent;
+ data.alphaType = alphaType;
+ data.hasAlphaChannel = hasAlpha;
+ data.wasAlphaSwapped = wasAlphaSwapped;
+ data.hadInvalidAlpha = wasAlphaFixed;
+ alphaCache.insert(key, data);
+ }
+ return true;
+}
+
+/*!
+ \internal
+
+ Animations are started at a frame that is based on the current time,
+ which makes it impossible to run baseline tests with this style. Allow
+ overriding through a dynamic property.
+*/
+QTime QWindowsVistaStylePrivate::animationTime() const
+{
+ Q_Q(const QWindowsVistaStyle);
+ static bool animationTimeOverride = q->dynamicPropertyNames().contains("_qt_animation_time");
+ if (animationTimeOverride)
+ return q->property("_qt_animation_time").toTime();
+ return QTime::currentTime();
+}
+
+/* \internal
+ Checks and returns the style object
+*/
+inline QObject *styleObject(const QStyleOption *option) {
+ return option ? option->styleObject : nullptr;
+}
+
+/* \internal
+ Checks if we can animate on a style option
+*/
+bool canAnimate(const QStyleOption *option) {
+ return option
+ && option->styleObject
+ && !option->styleObject->property("_q_no_animation").toBool();
+}
+
+static inline QImage createAnimationBuffer(const QStyleOption *option, const QWidget *widget)
+{
+ const qreal devicePixelRatio = widget
+ ? widget->devicePixelRatioF() : qApp->devicePixelRatio();
+ QImage result(option->rect.size() * devicePixelRatio, QImage::Format_ARGB32_Premultiplied);
+ result.setDevicePixelRatio(devicePixelRatio);
+ result.fill(0);
+ return result;
+}
+
+/* \internal
+ Used by animations to clone a styleoption and shift its offset
+*/
+QStyleOption *clonedAnimationStyleOption(const QStyleOption*option) {
+ QStyleOption *styleOption = nullptr;
+ if (const QStyleOptionSlider *slider = qstyleoption_cast<const QStyleOptionSlider*>(option))
+ styleOption = new QStyleOptionSlider(*slider);
+ else if (const QStyleOptionSpinBox *spinbox = qstyleoption_cast<const QStyleOptionSpinBox*>(option))
+ styleOption = new QStyleOptionSpinBox(*spinbox);
+ else if (const QStyleOptionGroupBox *groupBox = qstyleoption_cast<const QStyleOptionGroupBox*>(option))
+ styleOption = new QStyleOptionGroupBox(*groupBox);
+ else if (const QStyleOptionComboBox *combo = qstyleoption_cast<const QStyleOptionComboBox*>(option))
+ styleOption = new QStyleOptionComboBox(*combo);
+ else if (const QStyleOptionButton *button = qstyleoption_cast<const QStyleOptionButton*>(option))
+ styleOption = new QStyleOptionButton(*button);
+ else
+ styleOption = new QStyleOption(*option);
+ styleOption->rect = QRect(QPoint(0,0), option->rect.size());
+ return styleOption;
+}
+
+/* \internal
+ Used by animations to delete cloned styleoption
+*/
+void deleteClonedAnimationStyleOption(const QStyleOption *option)
+{
+ if (const QStyleOptionSlider *slider = qstyleoption_cast<const QStyleOptionSlider*>(option))
+ delete slider;
+ else if (const QStyleOptionSpinBox *spinbox = qstyleoption_cast<const QStyleOptionSpinBox*>(option))
+ delete spinbox;
+ else if (const QStyleOptionGroupBox *groupBox = qstyleoption_cast<const QStyleOptionGroupBox*>(option))
+ delete groupBox;
+ else if (const QStyleOptionComboBox *combo = qstyleoption_cast<const QStyleOptionComboBox*>(option))
+ delete combo;
+ else if (const QStyleOptionButton *button = qstyleoption_cast<const QStyleOptionButton*>(option))
+ delete button;
+ else
+ delete option;
+}
+
+static void populateTitleBarButtonTheme(const QStyle *proxy, const QWidget *widget,
+ const QStyleOptionComplex *option,
+ QStyle::SubControl subControl,
+ bool isTitleBarActive, int part,
+ QWindowsThemeData *theme)
+{
+ theme->rect = proxy->subControlRect(QStyle::CC_TitleBar, option, subControl, widget);
+ theme->partId = part;
+ if (widget && !widget->isEnabled())
+ theme->stateId = RBS_DISABLED;
+ else if (option->activeSubControls == subControl && option->state.testFlag(QStyle::State_Sunken))
+ theme->stateId = RBS_PUSHED;
+ else if (option->activeSubControls == subControl && option->state.testFlag(QStyle::State_MouseOver))
+ theme->stateId = RBS_HOT;
+ else if (!isTitleBarActive)
+ theme->stateId = RBS_INACTIVE;
+ else
+ theme->stateId = RBS_NORMAL;
+}
+
+#if QT_CONFIG(mdiarea)
+// Helper for drawing MDI buttons into the corner widget of QMenuBar in case a
+// QMdiSubWindow is maximized.
+static void populateMdiButtonTheme(const QStyle *proxy, const QWidget *widget,
+ const QStyleOptionComplex *option,
+ QStyle::SubControl subControl, int part,
+ QWindowsThemeData *theme)
+{
+ theme->partId = part;
+ theme->rect = proxy->subControlRect(QStyle::CC_MdiControls, option, subControl, widget);
+ if (!option->state.testFlag(QStyle::State_Enabled))
+ theme->stateId = CBS_INACTIVE;
+ else if (option->state.testFlag(QStyle::State_Sunken) && option->activeSubControls.testFlag(subControl))
+ theme->stateId = CBS_PUSHED;
+ else if (option->state.testFlag(QStyle::State_MouseOver) && option->activeSubControls.testFlag(subControl))
+ theme->stateId = CBS_HOT;
+ else
+ theme->stateId = CBS_NORMAL;
+}
+
+// Calculate an small (max 2), empirical correction factor for scaling up
+// WP_MDICLOSEBUTTON, WP_MDIRESTOREBUTTON, WP_MDIMINBUTTON, which are too
+// small on High DPI screens (QTBUG-75927).
+static qreal mdiButtonCorrectionFactor(QWindowsThemeData &theme, const QPaintDevice *pd = nullptr)
+{
+ const auto dpr = pd ? pd->devicePixelRatio() : qApp->devicePixelRatio();
+ const QSizeF nativeSize = QSizeF(theme.size()) / dpr;
+ const QSizeF requestedSize(theme.rect.size());
+ const auto rawFactor = qMin(requestedSize.width() / nativeSize.width(),
+ requestedSize.height() / nativeSize.height());
+ const auto factor = rawFactor >= qreal(2) ? qreal(2) : qreal(1);
+ return factor;
+}
+#endif // QT_CONFIG(mdiarea)
+
+/*
+ This function is used by subControlRect to check if a button
+ should be drawn for the given subControl given a set of window flags.
+*/
+static bool buttonVisible(const QStyle::SubControl sc, const QStyleOptionTitleBar *tb){
+
+ bool isMinimized = tb->titleBarState & Qt::WindowMinimized;
+ bool isMaximized = tb->titleBarState & Qt::WindowMaximized;
+ const auto flags = tb->titleBarFlags;
+ bool retVal = false;
+ switch (sc) {
+ case QStyle::SC_TitleBarContextHelpButton:
+ if (flags & Qt::WindowContextHelpButtonHint)
+ retVal = true;
+ break;
+ case QStyle::SC_TitleBarMinButton:
+ if (!isMinimized && (flags & Qt::WindowMinimizeButtonHint))
+ retVal = true;
+ break;
+ case QStyle::SC_TitleBarNormalButton:
+ if (isMinimized && (flags & Qt::WindowMinimizeButtonHint))
+ retVal = true;
+ else if (isMaximized && (flags & Qt::WindowMaximizeButtonHint))
+ retVal = true;
+ break;
+ case QStyle::SC_TitleBarMaxButton:
+ if (!isMaximized && (flags & Qt::WindowMaximizeButtonHint))
+ retVal = true;
+ break;
+ case QStyle::SC_TitleBarShadeButton:
+ if (!isMinimized && flags & Qt::WindowShadeButtonHint)
+ retVal = true;
+ break;
+ case QStyle::SC_TitleBarUnshadeButton:
+ if (isMinimized && flags & Qt::WindowShadeButtonHint)
+ retVal = true;
+ break;
+ case QStyle::SC_TitleBarCloseButton:
+ if (flags & Qt::WindowSystemMenuHint)
+ retVal = true;
+ break;
+ case QStyle::SC_TitleBarSysMenu:
+ if (flags & Qt::WindowSystemMenuHint)
+ retVal = true;
+ break;
+ default :
+ retVal = true;
+ }
+ return retVal;
+}
+
+//convert Qt state flags to uxtheme button states
+static int buttonStateId(int flags, int partId)
+{
+ int stateId = 0;
+ if (partId == BP_RADIOBUTTON || partId == BP_CHECKBOX) {
+ if (!(flags & QStyle::State_Enabled))
+ stateId = RBS_UNCHECKEDDISABLED;
+ else if (flags & QStyle::State_Sunken)
+ stateId = RBS_UNCHECKEDPRESSED;
+ else if (flags & QStyle::State_MouseOver)
+ stateId = RBS_UNCHECKEDHOT;
+ else
+ stateId = RBS_UNCHECKEDNORMAL;
+
+ if (flags & QStyle::State_On)
+ stateId += RBS_CHECKEDNORMAL-1;
+
+ } else if (partId == BP_PUSHBUTTON) {
+ if (!(flags & QStyle::State_Enabled))
+ stateId = PBS_DISABLED;
+ else if (flags & (QStyle::State_Sunken | QStyle::State_On))
+ stateId = PBS_PRESSED;
+ else if (flags & QStyle::State_MouseOver)
+ stateId = PBS_HOT;
+ else
+ stateId = PBS_NORMAL;
+ } else {
+ Q_ASSERT(1);
+ }
+ return stateId;
+}
+
+static inline bool supportsStateTransition(QStyle::PrimitiveElement element,
+ const QStyleOption *option,
+ const QWidget *widget)
+{
+ bool result = false;
+ switch (element) {
+ case QStyle::PE_IndicatorRadioButton:
+ case QStyle::PE_IndicatorCheckBox:
+ result = true;
+ break;
+ // QTBUG-40634, do not animate when color is set in palette for PE_PanelLineEdit.
+ case QStyle::PE_FrameLineEdit:
+ result = !QWindowsVistaStylePrivate::isLineEditBaseColorSet(option, widget);
+ break;
+ default:
+ break;
+ }
+ return result;
+}
+
+/*!
+ \class QWindowsVistaStyle
+ \brief The QWindowsVistaStyle class provides a look and feel suitable for applications on Microsoft Windows Vista.
+ \since 4.3
+ \ingroup appearance
+ \inmodule QtWidgets
+ \internal
+
+ \warning This style is only available on the Windows Vista platform
+ because it makes use of Windows Vista's style engine.
+
+ \sa QMacStyle, QFusionStyle
+*/
+
+/*!
+ Constructs a QWindowsVistaStyle object.
+*/
+QWindowsVistaStyle::QWindowsVistaStyle() : QWindowsStyle(*new QWindowsVistaStylePrivate)
+{
+}
+
+/*!
+ \internal
+ Constructs a QWindowsStyle object.
+*/
+QWindowsVistaStyle::QWindowsVistaStyle(QWindowsVistaStylePrivate &dd) : QWindowsStyle(dd)
+{
+}
+
+/*!
+ Destructor.
+*/
+QWindowsVistaStyle::~QWindowsVistaStyle() = default;
+
+
+/*!
+ \internal
+
+ Animations are used for some state transitions on specific widgets.
+
+ Only one running animation can exist for a widget at any specific
+ time. Animations can be added through
+ QWindowsVistaStylePrivate::startAnimation(Animation *) and any
+ existing animation on a widget can be retrieved with
+ QWindowsVistaStylePrivate::widgetAnimation(Widget *).
+
+ Once an animation has been started,
+ QWindowsVistaStylePrivate::timerEvent(QTimerEvent *) will
+ continuously call update() on the widget until it is stopped,
+ meaning that drawPrimitive will be called many times until the
+ transition has completed. During this time, the result will be
+ retrieved by the Animation::paint(...) function and not by the style
+ itself.
+
+ To determine if a transition should occur, the style needs to know
+ the previous state of the widget as well as the current one. This is
+ solved by updating dynamic properties on the widget every time the
+ function is called.
+
+ Transitions interrupting existing transitions should always be
+ smooth, so whenever a hover-transition is started on a pulsating
+ button, it uses the current frame of the pulse-animation as the
+ starting image for the hover transition.
+
+ */
+void QWindowsVistaStyle::drawPrimitive(PrimitiveElement element, const QStyleOption *option,
+ QPainter *painter, const QWidget *widget) const
+{
+ if (!QWindowsVistaStylePrivate::useVista()) {
+ QWindowsStyle::drawPrimitive(element, option, painter, widget);
+ return;
+ }
+
+ QWindowsVistaStylePrivate *d = const_cast<QWindowsVistaStylePrivate*>(d_func());
+
+ int state = option->state;
+ QRect rect = option->rect;
+
+ if ((state & State_Enabled) && d->transitionsEnabled() && canAnimate(option)) {
+ if (supportsStateTransition(element, option, widget)) {
+ // Retrieve and update the dynamic properties tracking
+ // the previous state of the widget:
+ QObject *styleObject = option->styleObject;
+ styleObject->setProperty("_q_no_animation", true);
+ int oldState = styleObject->property("_q_stylestate").toInt();
+ QRect oldRect = styleObject->property("_q_stylerect").toRect();
+ QRect newRect = rect;
+ styleObject->setProperty("_q_stylestate", int(option->state));
+ styleObject->setProperty("_q_stylerect", option->rect);
+
+ bool doTransition = oldState &&
+ ((state & State_Sunken) != (oldState & State_Sunken) ||
+ (state & State_On) != (oldState & State_On) ||
+ (state & State_MouseOver) != (oldState & State_MouseOver));
+
+ if (oldRect != newRect ||
+ (state & State_Enabled) != (oldState & State_Enabled) ||
+ (state & State_Active) != (oldState & State_Active))
+ d->stopAnimation(styleObject);
+
+ if (state & State_ReadOnly && element == PE_FrameLineEdit) // Do not animate read only line edits
+ doTransition = false;
+
+ if (doTransition) {
+ QStyleOption *styleOption = clonedAnimationStyleOption(option);
+ styleOption->state = QStyle::State(oldState);
+
+ QWindowsVistaAnimation *animate = qobject_cast<QWindowsVistaAnimation *>(d->animation(styleObject));
+ QWindowsVistaTransition *transition = new QWindowsVistaTransition(styleObject);
+
+ // We create separate images for the initial and final transition states and store them in the
+ // Transition object.
+ QImage startImage = createAnimationBuffer(option, widget);
+ QPainter startPainter(&startImage);
+
+ QImage endImage = createAnimationBuffer(option, widget);
+ QPainter endPainter(&endImage);
+
+ // If we have a running animation on the widget already, we will use that to paint the initial
+ // state of the new transition, this ensures a smooth transition from a current animation such as a
+ // pulsating default button into the intended target state.
+ if (!animate)
+ proxy()->drawPrimitive(element, styleOption, &startPainter, widget);
+ else
+ animate->paint(&startPainter, styleOption);
+
+ transition->setStartImage(startImage);
+
+ // The end state of the transition is simply the result we would have painted
+ // if the style was not animated.
+ styleOption->styleObject = nullptr;
+ styleOption->state = option->state;
+ proxy()->drawPrimitive(element, styleOption, &endPainter, widget);
+
+ transition->setEndImage(endImage);
+
+ HTHEME theme;
+ int partId;
+ DWORD duration;
+ int fromState = 0;
+ int toState = 0;
+
+ //translate state flags to UXTHEME states :
+ if (element == PE_FrameLineEdit) {
+ theme = OpenThemeData(nullptr, L"Edit");
+ partId = EP_EDITBORDER_NOSCROLL;
+
+ if (oldState & State_HasFocus)
+ fromState = ETS_SELECTED;
+ else if (oldState & State_MouseOver)
+ fromState = ETS_HOT;
+ else
+ fromState = ETS_NORMAL;
+
+ if (state & State_HasFocus)
+ toState = ETS_SELECTED;
+ else if (state & State_MouseOver)
+ toState = ETS_HOT;
+ else
+ toState = ETS_NORMAL;
+
+ } else {
+ theme = OpenThemeData(nullptr, L"Button");
+ if (element == PE_IndicatorRadioButton)
+ partId = BP_RADIOBUTTON;
+ else if (element == PE_IndicatorCheckBox)
+ partId = BP_CHECKBOX;
+ else
+ partId = BP_PUSHBUTTON;
+
+ fromState = buttonStateId(oldState, partId);
+ toState = buttonStateId(option->state, partId);
+ }
+
+ // Retrieve the transition time between the states from the system.
+ if (theme
+ && SUCCEEDED(GetThemeTransitionDuration(theme, partId, fromState, toState,
+ TMT_TRANSITIONDURATIONS, &duration))) {
+ transition->setDuration(int(duration));
+ }
+ transition->setStartTime(d->animationTime());
+
+ deleteClonedAnimationStyleOption(styleOption);
+ d->startAnimation(transition);
+ }
+ styleObject->setProperty("_q_no_animation", false);
+ }
+ }
+
+ int themeNumber = -1;
+ int partId = 0;
+ int stateId = 0;
+ bool hMirrored = false;
+ bool vMirrored = false;
+ bool noBorder = false;
+ bool noContent = false;
+ int rotate = 0;
+
+ switch (element) {
+ case PE_PanelButtonCommand:
+ if (const auto *btn = qstyleoption_cast<const QStyleOptionButton *>(option)) {
+ QBrush fill;
+ if (!(state & State_Sunken) && (state & State_On))
+ fill = QBrush(option->palette.light().color(), Qt::Dense4Pattern);
+ else
+ fill = option->palette.brush(QPalette::Button);
+ if (btn->features & QStyleOptionButton::DefaultButton && state & State_Sunken) {
+ painter->setPen(option->palette.dark().color());
+ painter->setBrush(fill);
+ painter->drawRect(rect.adjusted(0, 0, -1, -1));
+ } else if (state & (State_Raised | State_On | State_Sunken)) {
+ qDrawWinButton(painter, rect, option->palette, state & (State_Sunken | State_On),
+ &fill);
+ } else {
+ painter->fillRect(rect, fill);
+ }
+ }
+ break;
+
+ case PE_PanelButtonTool:
+#if QT_CONFIG(dockwidget)
+ if (widget && widget->inherits("QDockWidgetTitleButton")) {
+ if (const QWidget *dw = widget->parentWidget())
+ if (dw->isWindow()) {
+ return;
+ }
+ }
+#endif // QT_CONFIG(dockwidget)
+ themeNumber = QWindowsVistaStylePrivate::ToolBarTheme;
+ partId = TP_BUTTON;
+ if (!(option->state & State_Enabled))
+ stateId = TS_DISABLED;
+ else if (option->state & State_Sunken)
+ stateId = TS_PRESSED;
+ else if (option->state & State_MouseOver)
+ stateId = option->state & State_On ? TS_HOTCHECKED : TS_HOT;
+ else if (option->state & State_On)
+ stateId = TS_CHECKED;
+ else if (!(option->state & State_AutoRaise))
+ stateId = TS_HOT;
+ else
+ stateId = TS_NORMAL;
+
+ break;
+
+ case PE_IndicatorHeaderArrow:
+ if (const auto *header = qstyleoption_cast<const QStyleOptionHeader *>(option)) {
+ int stateId = HSAS_SORTEDDOWN;
+ if (header->sortIndicator & QStyleOptionHeader::SortDown)
+ stateId = HSAS_SORTEDUP; //note that the uxtheme sort down indicator is the inverse of ours
+ QWindowsThemeData theme(widget, painter,
+ QWindowsVistaStylePrivate::HeaderTheme,
+ HP_HEADERSORTARROW, stateId, option->rect);
+ d->drawBackground(theme);
+ return;
+ }
+ break;
+
+ case PE_IndicatorCheckBox:
+ if (auto *animate =
+ qobject_cast<QWindowsVistaAnimation *>(d->animation(styleObject(option)))) {
+ animate->paint(painter, option);
+ return;
+ } else {
+ themeNumber = QWindowsVistaStylePrivate::ButtonTheme;
+ partId = BP_CHECKBOX;
+
+ if (!(option->state & State_Enabled))
+ stateId = CBS_UNCHECKEDDISABLED;
+ else if (option->state & State_Sunken)
+ stateId = CBS_UNCHECKEDPRESSED;
+ else if (option->state & State_MouseOver)
+ stateId = CBS_UNCHECKEDHOT;
+ else
+ stateId = CBS_UNCHECKEDNORMAL;
+
+ if (option->state & State_On)
+ stateId += CBS_CHECKEDNORMAL-1;
+ else if (option->state & State_NoChange)
+ stateId += CBS_MIXEDNORMAL-1;
+ }
+ break;
+
+ case PE_IndicatorItemViewItemCheck: {
+ QStyleOptionButton button;
+ button.QStyleOption::operator=(*option);
+ button.state &= ~State_MouseOver;
+ proxy()->drawPrimitive(PE_IndicatorCheckBox, &button, painter, widget);
+ return;
+ }
+
+ case PE_IndicatorBranch: {
+ QWindowsThemeData theme(widget, painter, QWindowsVistaStylePrivate::VistaTreeViewTheme);
+ static int decoration_size = 0;
+ if (!decoration_size && theme.isValid()) {
+ QWindowsThemeData themeSize = theme;
+ themeSize.partId = TVP_HOTGLYPH;
+ themeSize.stateId = GLPS_OPENED;
+ const QSizeF size = themeSize.size() * QWindowsStylePrivate::nativeMetricScaleFactor(widget);
+ decoration_size = qRound(qMax(size.width(), size.height()));
+ }
+ int mid_h = option->rect.x() + option->rect.width() / 2;
+ int mid_v = option->rect.y() + option->rect.height() / 2;
+ if (option->state & State_Children) {
+ int delta = decoration_size / 2;
+ theme.rect = QRect(mid_h - delta, mid_v - delta, decoration_size, decoration_size);
+ theme.partId = option->state & State_MouseOver ? TVP_HOTGLYPH : TVP_GLYPH;
+ theme.stateId = option->state & QStyle::State_Open ? GLPS_OPENED : GLPS_CLOSED;
+ if (option->direction == Qt::RightToLeft)
+ theme.mirrorHorizontally = true;
+ d->drawBackground(theme);
+ }
+ return;
+ }
+
+ case PE_PanelButtonBevel:
+ if (QWindowsVistaAnimation *animate =
+ qobject_cast<QWindowsVistaAnimation *>(d->animation(styleObject(option)))) {
+ animate->paint(painter, option);
+ return;
+ }
+
+ themeNumber = QWindowsVistaStylePrivate::ButtonTheme;
+ partId = BP_PUSHBUTTON;
+ if (!(option->state & State_Enabled))
+ stateId = PBS_DISABLED;
+ else if ((option->state & State_Sunken) || (option->state & State_On))
+ stateId = PBS_PRESSED;
+ else if (option->state & State_MouseOver)
+ stateId = PBS_HOT;
+ else
+ stateId = PBS_NORMAL;
+ break;
+
+ case PE_IndicatorRadioButton:
+ if (QWindowsVistaAnimation *animate =
+ qobject_cast<QWindowsVistaAnimation *>(d->animation(styleObject(option)))) {
+ animate->paint(painter, option);
+ return;
+ } else {
+ themeNumber = QWindowsVistaStylePrivate::ButtonTheme;
+ partId = BP_RADIOBUTTON;
+
+ if (!(option->state & State_Enabled))
+ stateId = RBS_UNCHECKEDDISABLED;
+ else if (option->state & State_Sunken)
+ stateId = RBS_UNCHECKEDPRESSED;
+ else if (option->state & State_MouseOver)
+ stateId = RBS_UNCHECKEDHOT;
+ else
+ stateId = RBS_UNCHECKEDNORMAL;
+
+ if (option->state & State_On)
+ stateId += RBS_CHECKEDNORMAL-1;
+ }
+ break;
+
+ case PE_Frame:
+#if QT_CONFIG(accessibility)
+ if (QStyleHelper::isInstanceOf(option->styleObject, QAccessible::EditableText)
+ || QStyleHelper::isInstanceOf(option->styleObject, QAccessible::StaticText) ||
+#else
+ if (
+#endif
+ (widget && widget->inherits("QTextEdit"))) {
+ painter->save();
+ int stateId = ETS_NORMAL;
+ if (!(state & State_Enabled))
+ stateId = ETS_DISABLED;
+ else if (state & State_ReadOnly)
+ stateId = ETS_READONLY;
+ else if (state & State_HasFocus)
+ stateId = ETS_SELECTED;
+ QWindowsThemeData theme(widget, painter,
+ QWindowsVistaStylePrivate::EditTheme,
+ EP_EDITBORDER_HVSCROLL, stateId, option->rect);
+ // Since EP_EDITBORDER_HVSCROLL does not us borderfill, theme.noContent cannot be used for clipping
+ int borderSize = 1;
+ GetThemeInt(theme.handle(), theme.partId, theme.stateId, TMT_BORDERSIZE, &borderSize);
+ QRegion clipRegion = option->rect;
+ QRegion content = option->rect.adjusted(borderSize, borderSize, -borderSize, -borderSize);
+ clipRegion ^= content;
+ painter->setClipRegion(clipRegion);
+ d->drawBackground(theme);
+ painter->restore();
+ return;
+ } else {
+ if (option->state & State_Raised)
+ return;
+
+ themeNumber = QWindowsVistaStylePrivate::ListViewTheme;
+ partId = LVP_LISTGROUP;
+ QWindowsThemeData theme(widget, nullptr, themeNumber, partId);
+
+ if (!(option->state & State_Enabled))
+ stateId = ETS_DISABLED;
+ else
+ stateId = ETS_NORMAL;
+
+ int fillType;
+
+ if (GetThemeEnumValue(theme.handle(), partId, stateId, TMT_BGTYPE, &fillType) == S_OK) {
+ if (fillType == BT_BORDERFILL) {
+ COLORREF bcRef;
+ GetThemeColor(theme.handle(), partId, stateId, TMT_BORDERCOLOR, &bcRef);
+ QColor bordercolor(qRgb(GetRValue(bcRef), GetGValue(bcRef), GetBValue(bcRef)));
+ QPen oldPen = painter->pen();
+
+ // Inner white border
+ painter->setPen(QPen(option->palette.base().color(), 0));
+ const qreal dpi = QStyleHelper::dpi(option);
+ const auto topLevelAdjustment = QStyleHelper::dpiScaled(0.5, dpi);
+ const auto bottomRightAdjustment = QStyleHelper::dpiScaled(-1, dpi);
+ painter->drawRect(QRectF(option->rect).adjusted(topLevelAdjustment, topLevelAdjustment,
+ bottomRightAdjustment, bottomRightAdjustment));
+ // Outer dark border
+ painter->setPen(QPen(bordercolor, 0));
+ painter->drawRect(QRectF(option->rect).adjusted(0, 0, -topLevelAdjustment, -topLevelAdjustment));
+ painter->setPen(oldPen);
+ }
+
+ if (fillType == BT_BORDERFILL || fillType == BT_NONE)
+ return;
+ }
+ }
+ break;
+
+ case PE_FrameMenu: {
+ int stateId = option->state & State_Active ? MB_ACTIVE : MB_INACTIVE;
+ QWindowsThemeData theme(widget, painter,
+ QWindowsVistaStylePrivate::MenuTheme,
+ MENU_POPUPBORDERS, stateId, option->rect);
+ d->drawBackground(theme);
+ return;
+ }
+
+ case PE_PanelMenuBar:
+ break;
+
+#if QT_CONFIG(dockwidget)
+ case PE_IndicatorDockWidgetResizeHandle:
+ return;
+
+ case PE_FrameDockWidget:
+ if (const auto *frm = qstyleoption_cast<const QStyleOptionFrame *>(option)) {
+ themeNumber = QWindowsVistaStylePrivate::WindowTheme;
+ if (option->state & State_Active)
+ stateId = FS_ACTIVE;
+ else
+ stateId = FS_INACTIVE;
+
+ int fwidth = proxy()->pixelMetric(PM_DockWidgetFrameWidth, frm, widget);
+
+ QWindowsThemeData theme(widget, painter, themeNumber, 0, stateId);
+
+ if (!theme.isValid())
+ break;
+
+ theme.rect = QRect(frm->rect.x(), frm->rect.y(), frm->rect.x()+fwidth, frm->rect.height()-fwidth);
+ theme.partId = WP_SMALLFRAMELEFT;
+ d->drawBackground(theme);
+ theme.rect = QRect(frm->rect.width()-fwidth, frm->rect.y(), fwidth, frm->rect.height()-fwidth);
+ theme.partId = WP_SMALLFRAMERIGHT;
+ d->drawBackground(theme);
+ theme.rect = QRect(frm->rect.x(), frm->rect.bottom()-fwidth+1, frm->rect.width(), fwidth);
+ theme.partId = WP_SMALLFRAMEBOTTOM;
+ d->drawBackground(theme);
+ return;
+ }
+ break;
+#endif // QT_CONFIG(dockwidget)
+
+ case PE_FrameTabWidget:
+ if (const auto *tab = qstyleoption_cast<const QStyleOptionTabWidgetFrame *>(option)) {
+ themeNumber = QWindowsVistaStylePrivate::TabTheme;
+ partId = TABP_PANE;
+
+ if (widget) {
+ bool useGradient = true;
+ const int maxlength = 256;
+ wchar_t themeFileName[maxlength];
+ wchar_t themeColor[maxlength];
+ // Due to a a scaling issue with the XP Silver theme, tab gradients are not used with it
+ if (GetCurrentThemeName(themeFileName, maxlength, themeColor, maxlength, nullptr, 0) == S_OK) {
+ wchar_t *offset = nullptr;
+ if ((offset = wcsrchr(themeFileName, QChar(QLatin1Char('\\')).unicode())) != nullptr) {
+ offset++;
+ if (!lstrcmp(offset, L"Luna.msstyles") && !lstrcmp(offset, L"Metallic"))
+ useGradient = false;
+ }
+ }
+ // This should work, but currently there's an error in the ::drawBackgroundDirectly()
+ // code, when using the HDC directly..
+ if (useGradient) {
+ QStyleOptionTabWidgetFrame frameOpt = *tab;
+ frameOpt.rect = widget->rect();
+ QRect contentsRect = subElementRect(SE_TabWidgetTabContents, &frameOpt, widget);
+ QRegion reg = option->rect;
+ reg -= contentsRect;
+ painter->setClipRegion(reg);
+ QWindowsThemeData theme(widget, painter, themeNumber, partId, stateId, rect);
+ theme.mirrorHorizontally = hMirrored;
+ theme.mirrorVertically = vMirrored;
+ d->drawBackground(theme);
+ painter->setClipRect(contentsRect);
+ partId = TABP_BODY;
+ }
+ }
+ switch (tab->shape) {
+ case QTabBar::RoundedNorth:
+ case QTabBar::TriangularNorth:
+ break;
+ case QTabBar::RoundedSouth:
+ case QTabBar::TriangularSouth:
+ vMirrored = true;
+ break;
+ case QTabBar::RoundedEast:
+ case QTabBar::TriangularEast:
+ rotate = 90;
+ break;
+ case QTabBar::RoundedWest:
+ case QTabBar::TriangularWest:
+ rotate = 90;
+ hMirrored = true;
+ break;
+ default:
+ break;
+ }
+ }
+ break;
+
+ case PE_FrameStatusBarItem:
+ themeNumber = QWindowsVistaStylePrivate::StatusTheme;
+ partId = SP_PANE;
+ break;
+
+ case PE_FrameWindow:
+ if (const auto *frm = qstyleoption_cast<const QStyleOptionFrame *>(option)) {
+ themeNumber = QWindowsVistaStylePrivate::WindowTheme;
+ if (option->state & State_Active)
+ stateId = FS_ACTIVE;
+ else
+ stateId = FS_INACTIVE;
+
+ int fwidth = int((frm->lineWidth + frm->midLineWidth) / QWindowsStylePrivate::nativeMetricScaleFactor(widget));
+
+ QWindowsThemeData theme(widget, painter, themeNumber, 0, stateId);
+ if (!theme.isValid())
+ break;
+
+ // May fail due to too-large buffers for large widgets, fall back to Windows style.
+ theme.rect = QRect(option->rect.x(), option->rect.y()+fwidth, option->rect.x()+fwidth, option->rect.height()-fwidth);
+ theme.partId = WP_FRAMELEFT;
+ if (!d->drawBackground(theme)) {
+ QWindowsStyle::drawPrimitive(element, option, painter, widget);
+ return;
+ }
+ theme.rect = QRect(option->rect.width()-fwidth, option->rect.y()+fwidth, fwidth, option->rect.height()-fwidth);
+ theme.partId = WP_FRAMERIGHT;
+ if (!d->drawBackground(theme)) {
+ QWindowsStyle::drawPrimitive(element, option, painter, widget);
+ return;
+ }
+ theme.rect = QRect(option->rect.x(), option->rect.height()-fwidth, option->rect.width(), fwidth);
+ theme.partId = WP_FRAMEBOTTOM;
+ if (!d->drawBackground(theme)) {
+ QWindowsStyle::drawPrimitive(element, option, painter, widget);
+ return;
+ }
+ theme.rect = QRect(option->rect.x(), option->rect.y(), option->rect.width(), option->rect.y()+fwidth);
+ theme.partId = WP_CAPTION;
+ if (!d->drawBackground(theme))
+ QWindowsStyle::drawPrimitive(element, option, painter, widget);
+ return;
+ }
+ break;
+
+ case PE_PanelLineEdit:
+ if (const auto *panel = qstyleoption_cast<const QStyleOptionFrame *>(option)) {
+ bool isEnabled = state & State_Enabled;
+ if (QWindowsVistaStylePrivate::isLineEditBaseColorSet(option, widget)) {
+ painter->fillRect(panel->rect, panel->palette.brush(QPalette::Base));
+ } else {
+ int partId = EP_BACKGROUND;
+ int stateId = EBS_NORMAL;
+ if (!isEnabled)
+ stateId = EBS_DISABLED;
+ else if (option->state & State_ReadOnly)
+ stateId = EBS_READONLY;
+ else if (option->state & State_MouseOver)
+ stateId = EBS_HOT;
+
+ QWindowsThemeData theme(nullptr, painter, QWindowsVistaStylePrivate::EditTheme,
+ partId, stateId, rect);
+ if (!theme.isValid()) {
+ QWindowsStyle::drawPrimitive(element, option, painter, widget);
+ return;
+ }
+ int bgType;
+ GetThemeEnumValue(theme.handle(), partId, stateId, TMT_BGTYPE, &bgType);
+ if (bgType == BT_IMAGEFILE) {
+ d->drawBackground(theme);
+ } else {
+ QBrush fillColor = option->palette.brush(QPalette::Base);
+ if (!isEnabled) {
+ PROPERTYORIGIN origin = PO_NOTFOUND;
+ GetThemePropertyOrigin(theme.handle(), theme.partId, theme.stateId, TMT_FILLCOLOR, &origin);
+ // Use only if the fill property comes from our part
+ if ((origin == PO_PART || origin == PO_STATE)) {
+ COLORREF bgRef;
+ GetThemeColor(theme.handle(), partId, stateId, TMT_FILLCOLOR, &bgRef);
+ fillColor = QBrush(qRgb(GetRValue(bgRef), GetGValue(bgRef), GetBValue(bgRef)));
+ }
+ }
+ painter->fillRect(option->rect, fillColor);
+ }
+ }
+ if (panel->lineWidth > 0)
+ proxy()->drawPrimitive(PE_FrameLineEdit, panel, painter, widget);
+ }
+ return;
+
+ case PE_IndicatorButtonDropDown:
+ themeNumber = QWindowsVistaStylePrivate::ToolBarTheme;
+ partId = TP_SPLITBUTTONDROPDOWN;
+ if (!(option->state & State_Enabled))
+ stateId = TS_DISABLED;
+ else if (option->state & State_Sunken)
+ stateId = TS_PRESSED;
+ else if (option->state & State_MouseOver)
+ stateId = option->state & State_On ? TS_HOTCHECKED : TS_HOT;
+ else if (option->state & State_On)
+ stateId = TS_CHECKED;
+ else if (!(option->state & State_AutoRaise))
+ stateId = TS_HOT;
+ else
+ stateId = TS_NORMAL;
+ if (option->direction == Qt::RightToLeft)
+ hMirrored = true;
+ break;
+
+ case PE_FrameLineEdit:
+ if (QWindowsVistaAnimation *animate = qobject_cast<QWindowsVistaAnimation *>(d->animation(styleObject(option)))) {
+ animate->paint(painter, option);
+ } else {
+ if (QWindowsVistaStylePrivate::isItemViewDelegateLineEdit(widget)) {
+ // we try to check if this lineedit is a delegate on a QAbstractItemView-derived class.
+ QPen oldPen = painter->pen();
+ // Inner white border
+ painter->setPen(QPen(option->palette.base().color(), 1));
+ painter->drawRect(option->rect.adjusted(1, 1, -2, -2));
+ // Outer dark border
+ painter->setPen(QPen(option->palette.shadow().color(), 1));
+ painter->drawRect(option->rect.adjusted(0, 0, -1, -1));
+ painter->setPen(oldPen);
+ return;
+ }
+ int stateId = ETS_NORMAL;
+ if (!(state & State_Enabled))
+ stateId = ETS_DISABLED;
+ else if (state & State_ReadOnly)
+ stateId = ETS_READONLY;
+ else if (state & State_HasFocus)
+ stateId = ETS_SELECTED;
+ else if (state & State_MouseOver)
+ stateId = ETS_HOT;
+ QWindowsThemeData theme(widget, painter,
+ QWindowsVistaStylePrivate::EditTheme,
+ EP_EDITBORDER_NOSCROLL, stateId, option->rect);
+ theme.noContent = true;
+ painter->save();
+ QRegion clipRegion = option->rect;
+ clipRegion -= option->rect.adjusted(2, 2, -2, -2);
+ painter->setClipRegion(clipRegion);
+ d->drawBackground(theme);
+ painter->restore();
+ }
+ return;
+
+ case PE_FrameGroupBox:
+ themeNumber = QWindowsVistaStylePrivate::ButtonTheme;
+ partId = BP_GROUPBOX;
+ if (!(option->state & State_Enabled))
+ stateId = GBS_DISABLED;
+ else
+ stateId = GBS_NORMAL;
+ if (const auto *frame = qstyleoption_cast<const QStyleOptionFrame *>(option)) {
+ if (frame->features & QStyleOptionFrame::Flat) {
+ // Windows XP does not have a theme part for a flat GroupBox, paint it with the windows style
+ QRect fr = frame->rect;
+ QPoint p1(fr.x(), fr.y() + 1);
+ QPoint p2(fr.x() + fr.width(), p1.y() + 1);
+ rect = QRect(p1, p2);
+ themeNumber = -1;
+ }
+ }
+ break;
+
+ case PE_IndicatorToolBarHandle: {
+ QWindowsThemeData theme;
+ QRect rect;
+ if (option->state & State_Horizontal) {
+ theme = QWindowsThemeData(widget, painter,
+ QWindowsVistaStylePrivate::RebarTheme,
+ RP_GRIPPER, ETS_NORMAL, option->rect.adjusted(0, 1, -2, -2));
+ rect = option->rect.adjusted(0, 1, 0, -2);
+ rect.setWidth(4);
+ } else {
+ theme = QWindowsThemeData(widget, painter, QWindowsVistaStylePrivate::RebarTheme,
+ RP_GRIPPERVERT, ETS_NORMAL, option->rect.adjusted(0, 1, -2, -2));
+ rect = option->rect.adjusted(1, 0, -1, 0);
+ rect.setHeight(4);
+ }
+ theme.rect = rect;
+ d->drawBackground(theme);
+ return;
+ }
+
+ case PE_IndicatorToolBarSeparator: {
+ QPen pen = painter->pen();
+ int margin = 3;
+ painter->setPen(option->palette.window().color().darker(114));
+ if (option->state & State_Horizontal) {
+ int x1 = option->rect.center().x();
+ painter->drawLine(QPoint(x1, option->rect.top() + margin), QPoint(x1, option->rect.bottom() - margin));
+ } else {
+ int y1 = option->rect.center().y();
+ painter->drawLine(QPoint(option->rect.left() + margin, y1), QPoint(option->rect.right() - margin, y1));
+ }
+ painter->setPen(pen);
+ return;
+ }
+
+ case PE_PanelTipLabel: {
+ QWindowsThemeData theme(widget, painter,
+ QWindowsVistaStylePrivate::ToolTipTheme,
+ TTP_STANDARD, TTSS_NORMAL, option->rect);
+ d->drawBackground(theme);
+ return;
+ }
+
+ case PE_FrameTabBarBase:
+ if (const auto *tbb = qstyleoption_cast<const QStyleOptionTabBarBase *>(option)) {
+ painter->save();
+ switch (tbb->shape) {
+ case QTabBar::RoundedNorth:
+ painter->setPen(QPen(tbb->palette.dark(), 0));
+ painter->drawLine(tbb->rect.topLeft(), tbb->rect.topRight());
+ break;
+ case QTabBar::RoundedWest:
+ painter->setPen(QPen(tbb->palette.dark(), 0));
+ painter->drawLine(tbb->rect.left(), tbb->rect.top(), tbb->rect.left(), tbb->rect.bottom());
+ break;
+ case QTabBar::RoundedSouth:
+ painter->setPen(QPen(tbb->palette.dark(), 0));
+ painter->drawLine(tbb->rect.left(), tbb->rect.top(),
+ tbb->rect.right(), tbb->rect.top());
+ break;
+ case QTabBar::RoundedEast:
+ painter->setPen(QPen(tbb->palette.dark(), 0));
+ painter->drawLine(tbb->rect.topLeft(), tbb->rect.bottomLeft());
+ break;
+ case QTabBar::TriangularNorth:
+ case QTabBar::TriangularEast:
+ case QTabBar::TriangularWest:
+ case QTabBar::TriangularSouth:
+ painter->restore();
+ QWindowsStyle::drawPrimitive(element, option, painter, widget);
+ return;
+ }
+ painter->restore();
+ }
+ return;
+
+ case PE_Widget: {
+#if QT_CONFIG(dialogbuttonbox)
+ const QDialogButtonBox *buttonBox = nullptr;
+ if (qobject_cast<const QMessageBox *> (widget))
+ buttonBox = widget->findChild<const QDialogButtonBox *>(QLatin1String("qt_msgbox_buttonbox"));
+#if QT_CONFIG(inputdialog)
+ else if (qobject_cast<const QInputDialog *> (widget))
+ buttonBox = widget->findChild<const QDialogButtonBox *>(QLatin1String("qt_inputdlg_buttonbox"));
+#endif // QT_CONFIG(inputdialog)
+ if (buttonBox) {
+ //draw white panel part
+ QWindowsThemeData theme(widget, painter,
+ QWindowsVistaStylePrivate::TaskDialogTheme,
+ TDLG_PRIMARYPANEL, 0, option->rect);
+ QRect toprect = option->rect;
+ toprect.setBottom(buttonBox->geometry().top());
+ theme.rect = toprect;
+ d->drawBackground(theme);
+
+ //draw bottom panel part
+ QRect buttonRect = option->rect;
+ buttonRect.setTop(buttonBox->geometry().top());
+ theme.rect = buttonRect;
+ theme.partId = TDLG_SECONDARYPANEL;
+ d->drawBackground(theme);
+ }
+#endif
+ return;
+ }
+
+ case PE_PanelItemViewItem: {
+ const QStyleOptionViewItem *vopt;
+ bool newStyle = true;
+ QAbstractItemView::SelectionBehavior selectionBehavior = QAbstractItemView::SelectRows;
+ QAbstractItemView::SelectionMode selectionMode = QAbstractItemView::NoSelection;
+ if (const QAbstractItemView *view = qobject_cast<const QAbstractItemView *>(widget)) {
+ newStyle = !qobject_cast<const QTableView*>(view);
+ selectionBehavior = view->selectionBehavior();
+ selectionMode = view->selectionMode();
+#if QT_CONFIG(accessibility)
+ } else if (!widget) {
+ newStyle = !QStyleHelper::hasAncestor(option->styleObject, QAccessible::MenuItem) ;
+#endif
+ }
+
+ if (newStyle && (vopt = qstyleoption_cast<const QStyleOptionViewItem *>(option))) {
+ bool selected = vopt->state & QStyle::State_Selected;
+ const bool hover = selectionMode != QAbstractItemView::NoSelection && (vopt->state & QStyle::State_MouseOver);
+ bool active = vopt->state & QStyle::State_Active;
+
+ if (vopt->features & QStyleOptionViewItem::Alternate)
+ painter->fillRect(vopt->rect, vopt->palette.alternateBase());
+
+ QPalette::ColorGroup cg = vopt->state & QStyle::State_Enabled
+ ? QPalette::Normal : QPalette::Disabled;
+ if (cg == QPalette::Normal && !(vopt->state & QStyle::State_Active))
+ cg = QPalette::Inactive;
+
+ QRect itemRect = subElementRect(QStyle::SE_ItemViewItemFocusRect, option, widget).adjusted(-1, 0, 1, 0);
+ itemRect.setTop(vopt->rect.top());
+ itemRect.setBottom(vopt->rect.bottom());
+
+ QSize sectionSize = itemRect.size();
+ if (vopt->showDecorationSelected)
+ sectionSize = vopt->rect.size();
+
+ if (selectionBehavior == QAbstractItemView::SelectRows)
+ sectionSize.setWidth(vopt->rect.width());
+ QPixmap pixmap;
+
+ if (vopt->backgroundBrush.style() != Qt::NoBrush) {
+ const QPointF oldBrushOrigin = painter->brushOrigin();
+ painter->setBrushOrigin(vopt->rect.topLeft());
+ painter->fillRect(vopt->rect, vopt->backgroundBrush);
+ painter->setBrushOrigin(oldBrushOrigin);
+ }
+
+ if (hover || selected) {
+ if (sectionSize.width() > 0 && sectionSize.height() > 0) {
+ QString key = QString::fromLatin1("qvdelegate-%1-%2-%3-%4-%5").arg(sectionSize.width())
+ .arg(sectionSize.height()).arg(selected).arg(active).arg(hover);
+ if (!QPixmapCache::find(key, &pixmap)) {
+ pixmap = QPixmap(sectionSize);
+ pixmap.fill(Qt::transparent);
+
+ int state;
+ if (selected && hover)
+ state = LISS_HOTSELECTED;
+ else if (selected && !active)
+ state = LISS_SELECTEDNOTFOCUS;
+ else if (selected)
+ state = LISS_SELECTED;
+ else
+ state = LISS_HOT;
+
+ QPainter pixmapPainter(&pixmap);
+
+ QWindowsThemeData theme(widget, &pixmapPainter,
+ QWindowsVistaStylePrivate::VistaTreeViewTheme,
+ LVP_LISTITEM, state, QRect(0, 0, sectionSize.width(), sectionSize.height()));
+
+ if (!theme.isValid())
+ break;
+
+ d->drawBackground(theme);
+ QPixmapCache::insert(key, pixmap);
+ }
+ }
+
+ if (vopt->showDecorationSelected) {
+ const int frame = 2; //Assumes a 2 pixel pixmap border
+ QRect srcRect = QRect(0, 0, sectionSize.width(), sectionSize.height());
+ QRect pixmapRect = vopt->rect;
+ bool reverse = vopt->direction == Qt::RightToLeft;
+ bool leftSection = vopt->viewItemPosition == QStyleOptionViewItem::Beginning;
+ bool rightSection = vopt->viewItemPosition == QStyleOptionViewItem::End;
+ if (vopt->viewItemPosition == QStyleOptionViewItem::OnlyOne
+ || vopt->viewItemPosition == QStyleOptionViewItem::Invalid)
+ painter->drawPixmap(pixmapRect.topLeft(), pixmap);
+ else if (reverse ? rightSection : leftSection){
+ painter->drawPixmap(QRect(pixmapRect.topLeft(),
+ QSize(frame, pixmapRect.height())), pixmap,
+ QRect(QPoint(0, 0), QSize(frame, pixmapRect.height())));
+ painter->drawPixmap(pixmapRect.adjusted(frame, 0, 0, 0),
+ pixmap, srcRect.adjusted(frame, 0, -frame, 0));
+ } else if (reverse ? leftSection : rightSection) {
+ painter->drawPixmap(QRect(pixmapRect.topRight() - QPoint(frame - 1, 0),
+ QSize(frame, pixmapRect.height())), pixmap,
+ QRect(QPoint(pixmapRect.width() - frame, 0),
+ QSize(frame, pixmapRect.height())));
+ painter->drawPixmap(pixmapRect.adjusted(0, 0, -frame, 0),
+ pixmap, srcRect.adjusted(frame, 0, -frame, 0));
+ } else if (vopt->viewItemPosition == QStyleOptionViewItem::Middle)
+ painter->drawPixmap(pixmapRect, pixmap,
+ srcRect.adjusted(frame, 0, -frame, 0));
+ } else {
+ if (vopt->text.isEmpty() && vopt->icon.isNull())
+ break;
+ painter->drawPixmap(itemRect.topLeft(), pixmap);
+ }
+ }
+ return;
+ }
+ break;
+ }
+
+ default:
+ break;
+ }
+
+ QWindowsThemeData theme(widget, painter, themeNumber, partId, stateId, rect);
+
+ if (!theme.isValid()) {
+ QWindowsStyle::drawPrimitive(element, option, painter, widget);
+ return;
+ }
+
+ theme.mirrorHorizontally = hMirrored;
+ theme.mirrorVertically = vMirrored;
+ theme.noBorder = noBorder;
+ theme.noContent = noContent;
+ theme.rotate = rotate;
+
+ d->drawBackground(theme);
+}
+
+/*! \internal */
+int QWindowsVistaStyle::styleHint(StyleHint hint, const QStyleOption *option, const QWidget *widget,
+ QStyleHintReturn *returnData) const
+{
+ QWindowsVistaStylePrivate *d = const_cast<QWindowsVistaStylePrivate*>(d_func());
+
+ int ret = 0;
+ switch (hint) {
+ case SH_EtchDisabledText:
+ ret = (qobject_cast<const QLabel*>(widget) != 0);
+ break;
+
+ case SH_SpinControls_DisableOnBounds:
+ ret = 0;
+ break;
+
+ case SH_TitleBar_AutoRaise:
+ case SH_TitleBar_NoBorder:
+ ret = 1;
+ break;
+
+ case SH_GroupBox_TextLabelColor:
+ if (!widget || widget->isEnabled())
+ ret = d->groupBoxTextColor;
+ else
+ ret = d->groupBoxTextColorDisabled;
+ break;
+
+ case SH_WindowFrame_Mask: {
+ ret = 1;
+ auto *mask = qstyleoption_cast<QStyleHintReturnMask *>(returnData);
+ const auto *titlebar = qstyleoption_cast<const QStyleOptionTitleBar *>(option);
+ if (mask && titlebar) {
+ // Note certain themes will not return the whole window frame but only the titlebar part when
+ // queried This function needs to return the entire window mask, hence we will only fetch the mask for the
+ // titlebar itself and add the remaining part of the window rect at the bottom.
+ int tbHeight = proxy()->pixelMetric(PM_TitleBarHeight, option, widget);
+ QRect titleBarRect = option->rect;
+ titleBarRect.setHeight(tbHeight);
+ QWindowsThemeData themeData;
+ if (titlebar->titleBarState & Qt::WindowMinimized) {
+ themeData = QWindowsThemeData(widget, nullptr,
+ QWindowsVistaStylePrivate::WindowTheme,
+ WP_MINCAPTION, CS_ACTIVE, titleBarRect);
+ } else
+ themeData = QWindowsThemeData(widget, nullptr,
+ QWindowsVistaStylePrivate::WindowTheme,
+ WP_CAPTION, CS_ACTIVE, titleBarRect);
+ mask->region = d->region(themeData) +
+ QRect(0, tbHeight, option->rect.width(), option->rect.height() - tbHeight);
+ }
+ break;
+ }
+
+#if QT_CONFIG(rubberband)
+ case SH_RubberBand_Mask:
+ if (qstyleoption_cast<const QStyleOptionRubberBand *>(option))
+ ret = 0;
+ break;
+#endif // QT_CONFIG(rubberband)
+
+ case SH_MessageBox_CenterButtons:
+ ret = false;
+ break;
+
+ case SH_ToolTip_Mask:
+ if (option) {
+ if (QStyleHintReturnMask *mask = qstyleoption_cast<QStyleHintReturnMask*>(returnData)) {
+ ret = true;
+ QWindowsThemeData themeData(widget, nullptr,
+ QWindowsVistaStylePrivate::ToolTipTheme,
+ TTP_STANDARD, TTSS_NORMAL, option->rect);
+ mask->region = d->region(themeData);
+ }
+ }
+ break;
+
+ case SH_Table_GridLineColor:
+ if (option)
+ ret = int(option->palette.color(QPalette::Base).darker(118).rgba());
+ else
+ ret = -1;
+ break;
+
+ case SH_Header_ArrowAlignment:
+ ret = Qt::AlignTop | Qt::AlignHCenter;
+ break;
+
+ case SH_ItemView_DrawDelegateFrame:
+ ret = 1;
+ break;
+
+ default:
+ ret = QWindowsStyle::styleHint(hint, option, widget, returnData);
+ break;
+ }
+
+ return ret;
+}
+
+
+/*!
+ \internal
+
+ see drawPrimitive for comments on the animation support
+ */
+void QWindowsVistaStyle::drawControl(ControlElement element, const QStyleOption *option,
+ QPainter *painter, const QWidget *widget) const
+{
+ if (!QWindowsVistaStylePrivate::useVista()) {
+ QWindowsStyle::drawControl(element, option, painter, widget);
+ return;
+ }
+
+ QWindowsVistaStylePrivate *d = const_cast<QWindowsVistaStylePrivate*>(d_func());
+
+ bool selected = option->state & State_Selected;
+ bool pressed = option->state & State_Sunken;
+ bool disabled = !(option->state & State_Enabled);
+
+ int state = option->state;
+ int themeNumber = -1;
+
+ QRect rect(option->rect);
+ State flags = option->state;
+ int partId = 0;
+ int stateId = 0;
+
+ if (d->transitionsEnabled() && canAnimate(option)) {
+ if (element == CE_PushButtonBevel) {
+ QRect oldRect;
+ QRect newRect;
+
+ QObject *styleObject = option->styleObject;
+
+ int oldState = styleObject->property("_q_stylestate").toInt();
+ oldRect = styleObject->property("_q_stylerect").toRect();
+ newRect = option->rect;
+ styleObject->setProperty("_q_stylestate", int(option->state));
+ styleObject->setProperty("_q_stylerect", option->rect);
+
+ bool wasDefault = false;
+ bool isDefault = false;
+ if (const QStyleOptionButton *button = qstyleoption_cast<const QStyleOptionButton *>(option)) {
+ wasDefault = styleObject->property("_q_isdefault").toBool();
+ isDefault = button->features & QStyleOptionButton::DefaultButton;
+ styleObject->setProperty("_q_isdefault", isDefault);
+ }
+
+ bool doTransition = ((state & State_Sunken) != (oldState & State_Sunken) ||
+ (state & State_On) != (oldState & State_On) ||
+ (state & State_MouseOver) != (oldState & State_MouseOver));
+
+ if (oldRect != newRect || (wasDefault && !isDefault)) {
+ doTransition = false;
+ d->stopAnimation(styleObject);
+ }
+
+ if (doTransition) {
+ styleObject->setProperty("_q_no_animation", true);
+
+ QWindowsVistaTransition *t = new QWindowsVistaTransition(styleObject);
+ QWindowsVistaAnimation *anim = qobject_cast<QWindowsVistaAnimation *>(d->animation(styleObject));
+ QStyleOption *styleOption = clonedAnimationStyleOption(option);
+ styleOption->state = QStyle::State(oldState);
+
+ QImage startImage = createAnimationBuffer(option, widget);
+ QPainter startPainter(&startImage);
+
+ // Use current state of existing animation if already one is running
+ if (!anim) {
+ proxy()->drawControl(element, styleOption, &startPainter, widget);
+ } else {
+ anim->paint(&startPainter, styleOption);
+ d->stopAnimation(styleObject);
+ }
+
+ t->setStartImage(startImage);
+ QImage endImage = createAnimationBuffer(option, widget);
+ QPainter endPainter(&endImage);
+ styleOption->state = option->state;
+ proxy()->drawControl(element, styleOption, &endPainter, widget);
+ t->setEndImage(endImage);
+
+
+ DWORD duration = 0;
+ const HTHEME theme = OpenThemeData(nullptr, L"Button");
+
+ int fromState = buttonStateId(oldState, BP_PUSHBUTTON);
+ int toState = buttonStateId(option->state, BP_PUSHBUTTON);
+ if (GetThemeTransitionDuration(theme, BP_PUSHBUTTON, fromState, toState, TMT_TRANSITIONDURATIONS, &duration) == S_OK)
+ t->setDuration(int(duration));
+ else
+ t->setDuration(0);
+ t->setStartTime(d->animationTime());
+ styleObject->setProperty("_q_no_animation", false);
+
+ deleteClonedAnimationStyleOption(styleOption);
+ d->startAnimation(t);
+ }
+
+ QWindowsVistaAnimation *anim = qobject_cast<QWindowsVistaAnimation *>(d->animation(styleObject));
+ if (anim) {
+ anim->paint(painter, option);
+ return;
+ }
+
+ }
+ }
+
+ bool hMirrored = false;
+ bool vMirrored = false;
+ int rotate = 0;
+
+ switch (element) {
+ case CE_PushButtonBevel:
+ if (const QStyleOptionButton *btn = qstyleoption_cast<const QStyleOptionButton *>(option)) {
+ themeNumber = QWindowsVistaStylePrivate::ButtonTheme;
+ partId = BP_PUSHBUTTON;
+ if (btn->features & QStyleOptionButton::CommandLinkButton)
+ partId = BP_COMMANDLINK;
+ bool justFlat = (btn->features & QStyleOptionButton::Flat) && !(flags & (State_On|State_Sunken));
+ if (!(flags & State_Enabled) && !(btn->features & QStyleOptionButton::Flat))
+ stateId = PBS_DISABLED;
+ else if (justFlat)
+ ;
+ else if (flags & (State_Sunken | State_On))
+ stateId = PBS_PRESSED;
+ else if (flags & State_MouseOver)
+ stateId = PBS_HOT;
+ else if (btn->features & QStyleOptionButton::DefaultButton && (state & State_Active))
+ stateId = PBS_DEFAULTED;
+ else
+ stateId = PBS_NORMAL;
+
+ if (!justFlat) {
+
+ if (d->transitionsEnabled() && (btn->features & QStyleOptionButton::DefaultButton) &&
+ !(state & (State_Sunken | State_On)) && !(state & State_MouseOver) &&
+ (state & State_Enabled) && (state & State_Active))
+ {
+ QWindowsVistaAnimation *anim = qobject_cast<QWindowsVistaAnimation *>(d->animation(styleObject(option)));
+
+ if (!anim) {
+ QImage startImage = createAnimationBuffer(option, widget);
+ QImage alternateImage = createAnimationBuffer(option, widget);
+
+ QWindowsVistaPulse *pulse = new QWindowsVistaPulse(styleObject(option));
+
+ QPainter startPainter(&startImage);
+ stateId = PBS_DEFAULTED;
+ QWindowsThemeData theme(widget, &startPainter, themeNumber, partId, stateId, rect);
+ d->drawBackground(theme);
+
+ QPainter alternatePainter(&alternateImage);
+ theme.stateId = PBS_DEFAULTED_ANIMATING;
+ theme.painter = &alternatePainter;
+ d->drawBackground(theme);
+
+ pulse->setStartImage(startImage);
+ pulse->setEndImage(alternateImage);
+ pulse->setStartTime(d->animationTime());
+ pulse->setDuration(2000);
+ d->startAnimation(pulse);
+ anim = pulse;
+ }
+
+ if (anim)
+ anim->paint(painter, option);
+ else {
+ QWindowsThemeData theme(widget, painter, themeNumber, partId, stateId, rect);
+ d->drawBackground(theme);
+ }
+ }
+ else {
+ QWindowsThemeData theme(widget, painter, themeNumber, partId, stateId, rect);
+ d->drawBackground(theme);
+ }
+ }
+
+ if (btn->features & QStyleOptionButton::HasMenu) {
+ int mbiw = 0, mbih = 0;
+ QWindowsThemeData theme(widget, nullptr, QWindowsVistaStylePrivate::ToolBarTheme,
+ TP_DROPDOWNBUTTON);
+ if (theme.isValid()) {
+ const QSizeF size = theme.size() * QStyleHelper::dpiScaled(1, option);
+ if (!size.isEmpty()) {
+ mbiw = qRound(size.width());
+ mbih = qRound(size.height());
+ }
+ }
+ QRect ir = subElementRect(SE_PushButtonContents, option, nullptr);
+ QStyleOptionButton newBtn = *btn;
+ newBtn.rect = QStyle::visualRect(option->direction, option->rect,
+ QRect(ir.right() - mbiw - 2,
+ option->rect.top() + (option->rect.height()/2) - (mbih/2),
+ mbiw + 1, mbih + 1));
+ proxy()->drawPrimitive(PE_IndicatorArrowDown, &newBtn, painter, widget);
+ }
+ }
+ return;
+
+ case CE_SizeGrip: {
+ themeNumber = QWindowsVistaStylePrivate::StatusTheme;
+ partId = SP_GRIPPER;
+ QWindowsThemeData theme(nullptr, painter, themeNumber, partId);
+ QSize size = (theme.size() * QWindowsStylePrivate::nativeMetricScaleFactor(widget)).toSize();
+ size.rheight()--;
+ if (const auto *sg = qstyleoption_cast<const QStyleOptionSizeGrip *>(option)) {
+ switch (sg->corner) {
+ case Qt::BottomRightCorner:
+ rect = QRect(QPoint(rect.right() - size.width(), rect.bottom() - size.height()), size);
+ break;
+ case Qt::BottomLeftCorner:
+ rect = QRect(QPoint(rect.left() + 1, rect.bottom() - size.height()), size);
+ hMirrored = true;
+ break;
+ case Qt::TopRightCorner:
+ rect = QRect(QPoint(rect.right() - size.width(), rect.top() + 1), size);
+ vMirrored = true;
+ break;
+ case Qt::TopLeftCorner:
+ rect = QRect(rect.topLeft() + QPoint(1, 1), size);
+ hMirrored = vMirrored = true;
+ }
+ }
+ break;
+ }
+
+ case CE_Splitter:
+ painter->eraseRect(option->rect);
+ return;
+
+ case CE_TabBarTab:
+ if (const auto *tab = qstyleoption_cast<const QStyleOptionTab *>(option))
+ stateId = tab->state & State_Enabled ? TIS_NORMAL : TIS_DISABLED;
+ break;
+
+ case CE_TabBarTabShape:
+ if (const auto *tab = qstyleoption_cast<const QStyleOptionTab *>(option)) {
+ themeNumber = QWindowsVistaStylePrivate::TabTheme;
+ const bool isDisabled = !(tab->state & State_Enabled);
+ const bool hasFocus = tab->state & State_HasFocus;
+ const bool isHot = tab->state & State_MouseOver;
+ const bool selected = tab->state & State_Selected;
+ bool lastTab = tab->position == QStyleOptionTab::End;
+ bool firstTab = tab->position == QStyleOptionTab::Beginning;
+ const bool onlyOne = tab->position == QStyleOptionTab::OnlyOneTab;
+ const bool leftAligned = proxy()->styleHint(SH_TabBar_Alignment, tab, widget) == Qt::AlignLeft;
+ const bool centerAligned = proxy()->styleHint(SH_TabBar_Alignment, tab, widget) == Qt::AlignCenter;
+ const int borderThickness = proxy()->pixelMetric(PM_DefaultFrameWidth, option, widget);
+ const int tabOverlap = proxy()->pixelMetric(PM_TabBarTabOverlap, option, widget);
+
+ if (isDisabled)
+ stateId = TIS_DISABLED;
+ else if (selected)
+ stateId = TIS_SELECTED;
+ else if (hasFocus)
+ stateId = TIS_FOCUSED;
+ else if (isHot)
+ stateId = TIS_HOT;
+ else
+ stateId = TIS_NORMAL;
+
+ // Selecting proper part depending on position
+ if (firstTab || onlyOne) {
+ if (leftAligned)
+ partId = TABP_TABITEMLEFTEDGE;
+ else if (centerAligned)
+ partId = TABP_TABITEM;
+ else // rightAligned
+ partId = TABP_TABITEMRIGHTEDGE;
+ } else {
+ partId = TABP_TABITEM;
+ }
+
+ if (tab->direction == Qt::RightToLeft
+ && (tab->shape == QTabBar::RoundedNorth || tab->shape == QTabBar::RoundedSouth)) {
+ bool temp = firstTab;
+ firstTab = lastTab;
+ lastTab = temp;
+ }
+
+ const bool begin = firstTab || onlyOne;
+ const bool end = lastTab || onlyOne;
+
+ switch (tab->shape) {
+ case QTabBar::RoundedNorth:
+ if (selected)
+ rect.adjust(begin ? 0 : -tabOverlap, 0, end ? 0 : tabOverlap, borderThickness);
+ else
+ rect.adjust(begin? tabOverlap : 0, tabOverlap, end ? -tabOverlap : 0, 0);
+ break;
+ case QTabBar::RoundedSouth:
+ //vMirrored = true;
+ rotate = 180; // Not 100% correct, but works
+ if (selected)
+ rect.adjust(begin ? 0 : -tabOverlap , -borderThickness, end ? 0 : tabOverlap, 0);
+ else
+ rect.adjust(begin ? tabOverlap : 0, 0, end ? -tabOverlap : 0 , -tabOverlap);
+ break;
+ case QTabBar::RoundedEast:
+ rotate = 90;
+ if (selected)
+ rect.adjust(-borderThickness, begin ? 0 : -tabOverlap, 0, end ? 0 : tabOverlap);
+ else
+ rect.adjust(0, begin ? tabOverlap : 0, -tabOverlap, end ? -tabOverlap : 0);
+ break;
+ case QTabBar::RoundedWest:
+ hMirrored = true;
+ rotate = 90;
+ if (selected)
+ rect.adjust(0, begin ? 0 : -tabOverlap, borderThickness, end ? 0 : tabOverlap);
+ else
+ rect.adjust(tabOverlap, begin ? tabOverlap : 0, 0, end ? -tabOverlap : 0);
+ break;
+ default:
+ themeNumber = -1; // Do our own painting for triangular
+ break;
+ }
+
+ if (!selected) {
+ switch (tab->shape) {
+ case QTabBar::RoundedNorth:
+ rect.adjust(0,0, 0,-1);
+ break;
+ case QTabBar::RoundedSouth:
+ rect.adjust(0,1, 0,0);
+ break;
+ case QTabBar::RoundedEast:
+ rect.adjust( 1,0, 0,0);
+ break;
+ case QTabBar::RoundedWest:
+ rect.adjust(0,0, -1,0);
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ break;
+
+ case CE_ProgressBarGroove: {
+ Qt::Orientation orient = Qt::Horizontal;
+ if (const auto *pb = qstyleoption_cast<const QStyleOptionProgressBar *>(option))
+ if (!(pb->state & QStyle::State_Horizontal))
+ orient = Qt::Vertical;
+
+ partId = (orient == Qt::Horizontal) ? PP_BAR : PP_BARVERT;
+ themeNumber = QWindowsVistaStylePrivate::ProgressTheme;
+ stateId = 1;
+ break;
+ }
+
+ case CE_ProgressBarContents:
+ if (const auto *bar = qstyleoption_cast<const QStyleOptionProgressBar *>(option)) {
+ bool isIndeterminate = (bar->minimum == 0 && bar->maximum == 0);
+ const bool vertical = !(bar->state & QStyle::State_Horizontal);
+ const bool inverted = bar->invertedAppearance;
+
+ if (isIndeterminate || (bar->progress > 0 && (bar->progress < bar->maximum) && d->transitionsEnabled())) {
+ if (!d->animation(styleObject(option)))
+ d->startAnimation(new QProgressStyleAnimation(d->animationFps, styleObject(option)));
+ } else {
+ d->stopAnimation(styleObject(option));
+ }
+
+ QWindowsThemeData theme(widget, painter,
+ QWindowsVistaStylePrivate::ProgressTheme,
+ vertical ? PP_FILLVERT : PP_FILL);
+ theme.rect = option->rect;
+ bool reverse = (bar->direction == Qt::LeftToRight && inverted) || (bar->direction == Qt::RightToLeft && !inverted);
+ QTime current = d->animationTime();
+
+ if (isIndeterminate) {
+ if (auto *progressAnimation = qobject_cast<QProgressStyleAnimation *>(d->animation(styleObject(option)))) {
+ int glowSize = 120;
+ int animationWidth = glowSize * 2 + (vertical ? theme.rect.height() : theme.rect.width());
+ int animOffset = progressAnimation->startTime().msecsTo(current) / 4;
+ if (animOffset > animationWidth)
+ progressAnimation->setStartTime(d->animationTime());
+ painter->save();
+ painter->setClipRect(theme.rect);
+ QRect animRect;
+ QSize pixmapSize(14, 14);
+ if (vertical) {
+ animRect = QRect(theme.rect.left(),
+ inverted ? rect.top() - glowSize + animOffset :
+ rect.bottom() + glowSize - animOffset,
+ rect.width(), glowSize);
+ pixmapSize.setHeight(animRect.height());
+ } else {
+ animRect = QRect(rect.left() - glowSize + animOffset,
+ rect.top(), glowSize, rect.height());
+ animRect = QStyle::visualRect(reverse ? Qt::RightToLeft : Qt::LeftToRight,
+ option->rect, animRect);
+ pixmapSize.setWidth(animRect.width());
+ }
+ QString name = QString::fromLatin1("qiprogress-%1-%2").arg(pixmapSize.width()).arg(pixmapSize.height());
+ QPixmap pixmap;
+ if (!QPixmapCache::find(name, &pixmap)) {
+ QImage image(pixmapSize, QImage::Format_ARGB32);
+ image.fill(Qt::transparent);
+ QPainter imagePainter(&image);
+ theme.painter = &imagePainter;
+ theme.partId = vertical ? PP_FILLVERT : PP_FILL;
+ theme.rect = QRect(QPoint(0,0), animRect.size());
+ QLinearGradient alphaGradient(0, 0, vertical ? 0 : image.width(),
+ vertical ? image.height() : 0);
+ alphaGradient.setColorAt(0, QColor(0, 0, 0, 0));
+ alphaGradient.setColorAt(0.5, QColor(0, 0, 0, 220));
+ alphaGradient.setColorAt(1, QColor(0, 0, 0, 0));
+ imagePainter.fillRect(image.rect(), alphaGradient);
+ imagePainter.setCompositionMode(QPainter::CompositionMode_SourceIn);
+ d->drawBackground(theme);
+ imagePainter.end();
+ pixmap = QPixmap::fromImage(image);
+ QPixmapCache::insert(name, pixmap);
+ }
+ painter->drawPixmap(animRect, pixmap);
+ painter->restore();
+ }
+ } else {
+ qint64 progress = qMax<qint64>(bar->progress, bar->minimum); // workaround for bug in QProgressBar
+
+ if (vertical) {
+ int maxHeight = option->rect.height();
+ int minHeight = 0;
+ double vc6_workaround = ((progress - qint64(bar->minimum)) / qMax(double(1.0), double(qint64(bar->maximum) - qint64(bar->minimum))) * maxHeight);
+ int height = isIndeterminate ? maxHeight: qMax(int(vc6_workaround), minHeight);
+ theme.rect.setHeight(height);
+ if (!inverted)
+ theme.rect.moveTop(rect.height() - theme.rect.height());
+ } else {
+ int maxWidth = option->rect.width();
+ int minWidth = 0;
+ double vc6_workaround = ((progress - qint64(bar->minimum)) / qMax(double(1.0), double(qint64(bar->maximum) - qint64(bar->minimum))) * maxWidth);
+ int width = isIndeterminate ? maxWidth : qMax(int(vc6_workaround), minWidth);
+ theme.rect.setWidth(width);
+ theme.rect = QStyle::visualRect(reverse ? Qt::RightToLeft : Qt::LeftToRight,
+ option->rect, theme.rect);
+ }
+ d->drawBackground(theme);
+
+ if (QProgressStyleAnimation *a = qobject_cast<QProgressStyleAnimation *>(d->animation(styleObject(option)))) {
+ int glowSize = 140;
+ int animationWidth = glowSize * 2 + (vertical ? theme.rect.height() : theme.rect.width());
+ int animOffset = a->startTime().msecsTo(current) / 4;
+ theme.partId = vertical ? PP_MOVEOVERLAYVERT : PP_MOVEOVERLAY;
+ if (animOffset > animationWidth) {
+ if (bar->progress < bar->maximum)
+ a->setStartTime(d->animationTime());
+ else
+ d->stopAnimation(styleObject(option)); //we stop the glow motion only after it has
+ //moved out of view
+ }
+ painter->save();
+ painter->setClipRect(theme.rect);
+ if (vertical) {
+ theme.rect = QRect(theme.rect.left(),
+ inverted ? rect.top() - glowSize + animOffset :
+ rect.bottom() + glowSize - animOffset,
+ rect.width(), glowSize);
+ } else {
+ theme.rect = QRect(rect.left() - glowSize + animOffset,rect.top(), glowSize, rect.height());
+ theme.rect = QStyle::visualRect(reverse ? Qt::RightToLeft : Qt::LeftToRight, option->rect, theme.rect);
+ }
+ d->drawBackground(theme);
+ painter->restore();
+ }
+ }
+ }
+ return;
+
+ case CE_MenuBarItem:
+ if (const auto *mbi = qstyleoption_cast<const QStyleOptionMenuItem *>(option)) {
+ if (mbi->menuItemType == QStyleOptionMenuItem::DefaultItem)
+ break;
+
+ QPalette::ColorRole textRole = disabled ? QPalette::Text : QPalette::ButtonText;
+ QPixmap pix = mbi->icon.pixmap(proxy()->pixelMetric(PM_SmallIconSize, option, widget), QIcon::Normal);
+
+ int alignment = Qt::AlignCenter | Qt::TextShowMnemonic | Qt::TextDontClip | Qt::TextSingleLine;
+ if (!proxy()->styleHint(SH_UnderlineShortcut, mbi, widget))
+ alignment |= Qt::TextHideMnemonic;
+
+ if (widget && mbi->palette.color(QPalette::Window) != Qt::transparent) { // Not needed for QtQuick Controls
+ //The rect adjustment is a workaround for the menu not really filling its background.
+ QWindowsThemeData theme(widget, painter,
+ QWindowsVistaStylePrivate::MenuTheme,
+ MENU_BARBACKGROUND, 0, option->rect.adjusted(-1, 0, 2, 1));
+ d->drawBackground(theme);
+
+ int stateId = MBI_NORMAL;
+ if (disabled)
+ stateId = MBI_DISABLED;
+ else if (pressed)
+ stateId = MBI_PUSHED;
+ else if (selected)
+ stateId = MBI_HOT;
+
+ QWindowsThemeData theme2(widget, painter,
+ QWindowsVistaStylePrivate::MenuTheme,
+ MENU_BARITEM, stateId, option->rect);
+ d->drawBackground(theme2);
+ }
+
+ if (!pix.isNull())
+ drawItemPixmap(painter, mbi->rect, alignment, pix);
+ else
+ drawItemText(painter, mbi->rect, alignment, mbi->palette, mbi->state & State_Enabled, mbi->text, textRole);
+ }
+ return;
+
+#if QT_CONFIG(menu)
+ case CE_MenuEmptyArea:
+ if (const auto *menuitem = qstyleoption_cast<const QStyleOptionMenuItem *>(option)) {
+ QBrush fill = menuitem->palette.brush((menuitem->state & State_Selected) ?
+ QPalette::Highlight : QPalette::Button);
+ painter->fillRect(rect, fill);
+ break;
+ }
+ return;
+
+ case CE_MenuItem:
+ if (const auto *menuitem = qstyleoption_cast<const QStyleOptionMenuItem *>(option)) {
+ // windows always has a check column, regardless whether we have an icon or not
+ const qreal factor = QWindowsVistaStylePrivate::nativeMetricScaleFactor(widget);
+ int checkcol = qRound(qreal(25) * factor);
+ const int gutterWidth = qRound(qreal(3) * factor);
+ {
+ QWindowsThemeData theme(widget, nullptr, QWindowsVistaStylePrivate::MenuTheme,
+ MENU_POPUPCHECKBACKGROUND, MBI_HOT);
+ QWindowsThemeData themeSize = theme;
+ themeSize.partId = MENU_POPUPCHECK;
+ themeSize.stateId = 0;
+ const QSizeF size = themeSize.size() * QWindowsStylePrivate::nativeMetricScaleFactor(widget);
+ const QMarginsF margins = themeSize.margins() * QWindowsStylePrivate::nativeMetricScaleFactor(widget);
+ checkcol = qMax(menuitem->maxIconWidth, qRound(gutterWidth + size.width() + margins.left() + margins.right()));
+ }
+ QRect rect = option->rect;
+
+ //draw vertical menu line
+ if (option->direction == Qt::LeftToRight)
+ checkcol += rect.x();
+ QPoint p1 = QStyle::visualPos(option->direction, menuitem->rect, QPoint(checkcol, rect.top()));
+ QPoint p2 = QStyle::visualPos(option->direction, menuitem->rect, QPoint(checkcol, rect.bottom()));
+ QRect gutterRect(p1.x(), p1.y(), gutterWidth, p2.y() - p1.y() + 1);
+ QWindowsThemeData theme2(widget, painter, QWindowsVistaStylePrivate::MenuTheme,
+ MENU_POPUPGUTTER, stateId, gutterRect);
+ d->drawBackground(theme2);
+
+ int x, y, w, h;
+ menuitem->rect.getRect(&x, &y, &w, &h);
+ int tab = menuitem->reservedShortcutWidth;
+ bool dis = !(menuitem->state & State_Enabled);
+ bool checked = menuitem->checkType != QStyleOptionMenuItem::NotCheckable
+ ? menuitem->checked : false;
+ bool act = menuitem->state & State_Selected;
+
+ if (menuitem->menuItemType == QStyleOptionMenuItem::Separator) {
+ int yoff = y-2 + h / 2;
+ const int separatorSize = qRound(qreal(6) * QWindowsStylePrivate::nativeMetricScaleFactor(widget));
+ QPoint p1 = QPoint(x + checkcol, yoff);
+ QPoint p2 = QPoint(x + w + separatorSize, yoff);
+ stateId = MBI_HOT;
+ QRect subRect(p1.x() + (gutterWidth - menuitem->rect.x()), p1.y(),
+ p2.x() - p1.x(), separatorSize);
+ subRect = QStyle::visualRect(option->direction, option->rect, subRect );
+ QWindowsThemeData theme2(widget, painter,
+ QWindowsVistaStylePrivate::MenuTheme,
+ MENU_POPUPSEPARATOR, stateId, subRect);
+ d->drawBackground(theme2);
+ return;
+ }
+
+ QRect vCheckRect = visualRect(option->direction, menuitem->rect, QRect(menuitem->rect.x(),
+ menuitem->rect.y(), checkcol - (gutterWidth + menuitem->rect.x()), menuitem->rect.height()));
+
+ if (act) {
+ stateId = dis ? MBI_DISABLED : MBI_HOT;
+ QWindowsThemeData theme2(widget, painter,
+ QWindowsVistaStylePrivate::MenuTheme,
+ MENU_POPUPITEM, stateId, option->rect);
+ d->drawBackground(theme2);
+ }
+
+ if (checked) {
+ QWindowsThemeData theme(widget, painter,
+ QWindowsVistaStylePrivate::MenuTheme,
+ MENU_POPUPCHECKBACKGROUND,
+ menuitem->icon.isNull() ? MBI_HOT : MBI_PUSHED, vCheckRect);
+ QWindowsThemeData themeSize = theme;
+ themeSize.partId = MENU_POPUPCHECK;
+ themeSize.stateId = 0;
+ const QSizeF size = themeSize.size() * QWindowsStylePrivate::nativeMetricScaleFactor(widget);
+ const QMarginsF margins = themeSize.margins() * QWindowsStylePrivate::nativeMetricScaleFactor(widget);
+ QRect checkRect(0, 0, qRound(size.width() + margins.left() + margins.right()),
+ qRound(size.height() + margins.bottom() + margins.top()));
+ checkRect.moveCenter(vCheckRect.center());
+ theme.rect = checkRect;
+
+ d->drawBackground(theme);
+
+ if (menuitem->icon.isNull()) {
+ checkRect = QRect(QPoint(0, 0), size.toSize());
+ checkRect.moveCenter(theme.rect.center());
+ theme.rect = checkRect;
+
+ theme.partId = MENU_POPUPCHECK;
+ bool bullet = menuitem->checkType & QStyleOptionMenuItem::Exclusive;
+ if (dis)
+ theme.stateId = bullet ? MC_BULLETDISABLED: MC_CHECKMARKDISABLED;
+ else
+ theme.stateId = bullet ? MC_BULLETNORMAL: MC_CHECKMARKNORMAL;
+ d->drawBackground(theme);
+ }
+ }
+
+ if (!menuitem->icon.isNull()) {
+ QIcon::Mode mode = dis ? QIcon::Disabled : QIcon::Normal;
+ if (act && !dis)
+ mode = QIcon::Active;
+ const auto size = proxy()->pixelMetric(PM_SmallIconSize, option, widget);
+ const auto dpr = painter->device()->devicePixelRatio();
+ const auto pixmap = menuitem->icon.pixmap({size, size}, dpr, mode,
+ checked ? QIcon::On : QIcon::Off);
+ QRect pmr(QPoint(0, 0), pixmap.deviceIndependentSize().toSize());
+ pmr.moveCenter(vCheckRect.center());
+ painter->setPen(menuitem->palette.text().color());
+ painter->drawPixmap(pmr.topLeft(), pixmap);
+ }
+
+ painter->setPen(menuitem->palette.buttonText().color());
+
+ const QColor textColor = menuitem->palette.text().color();
+ if (dis)
+ painter->setPen(textColor);
+
+ int xm = windowsItemFrame + checkcol + windowsItemHMargin + (gutterWidth - menuitem->rect.x()) - 1;
+ int xpos = menuitem->rect.x() + xm;
+ QRect textRect(xpos, y + windowsItemVMargin, w - xm - windowsRightBorder - tab + 1, h - 2 * windowsItemVMargin);
+ QRect vTextRect = visualRect(option->direction, menuitem->rect, textRect);
+ QString s = menuitem->text;
+ if (!s.isEmpty()) { // draw text
+ painter->save();
+ int t = s.indexOf(QLatin1Char('\t'));
+ int text_flags = Qt::AlignVCenter | Qt::TextShowMnemonic | Qt::TextDontClip | Qt::TextSingleLine;
+ if (!proxy()->styleHint(SH_UnderlineShortcut, menuitem, widget))
+ text_flags |= Qt::TextHideMnemonic;
+ text_flags |= Qt::AlignLeft;
+ if (t >= 0) {
+ QRect vShortcutRect = visualRect(option->direction, menuitem->rect,
+ QRect(textRect.topRight(), QPoint(menuitem->rect.right(), textRect.bottom())));
+ painter->drawText(vShortcutRect, text_flags, s.mid(t + 1));
+ s = s.left(t);
+ }
+ QFont font = menuitem->font;
+ if (menuitem->menuItemType == QStyleOptionMenuItem::DefaultItem)
+ font.setBold(true);
+ painter->setFont(font);
+ painter->setPen(textColor);
+ painter->drawText(vTextRect, text_flags, s.left(t));
+ painter->restore();
+ }
+ if (menuitem->menuItemType == QStyleOptionMenuItem::SubMenu) {// draw sub menu arrow
+ int dim = (h - 2 * windowsItemFrame) / 2;
+ PrimitiveElement arrow;
+ arrow = (option->direction == Qt::RightToLeft) ? PE_IndicatorArrowLeft : PE_IndicatorArrowRight;
+ xpos = x + w - windowsArrowHMargin - windowsItemFrame - dim;
+ QRect vSubMenuRect = visualRect(option->direction, menuitem->rect, QRect(xpos, y + h / 2 - dim / 2, dim, dim));
+ QStyleOptionMenuItem newMI = *menuitem;
+ newMI.rect = vSubMenuRect;
+ newMI.state = dis ? State_None : State_Enabled;
+ proxy()->drawPrimitive(arrow, &newMI, painter, widget);
+ }
+ }
+ return;
+#endif // QT_CONFIG(menu)
+
+ case CE_HeaderSection:
+ if (const QStyleOptionHeader *header = qstyleoption_cast<const QStyleOptionHeader *>(option)) {
+ partId = HP_HEADERITEM;
+ if (flags & State_Sunken)
+ stateId = HIS_PRESSED;
+ else if (flags & State_MouseOver)
+ stateId = HIS_HOT;
+ else
+ stateId = HIS_NORMAL;
+
+ if (header->sortIndicator != QStyleOptionHeader::None)
+ stateId += 3;
+
+ QWindowsThemeData theme(widget, painter,
+ QWindowsVistaStylePrivate::HeaderTheme,
+ partId, stateId, option->rect);
+ d->drawBackground(theme);
+ }
+ return;
+
+ case CE_MenuBarEmptyArea: {
+ stateId = MBI_NORMAL;
+ if (!(state & State_Enabled))
+ stateId = MBI_DISABLED;
+ QWindowsThemeData theme(widget, painter, QWindowsVistaStylePrivate::MenuTheme,
+ MENU_BARBACKGROUND, stateId, option->rect);
+ d->drawBackground(theme);
+ return;
+ }
+
+ case CE_ToolBar:
+ if (const auto *toolbar = qstyleoption_cast<const QStyleOptionToolBar *>(option)) {
+ QPalette pal = option->palette;
+ pal.setColor(QPalette::Dark, option->palette.window().color().darker(130));
+ QStyleOptionToolBar copyOpt = *toolbar;
+ copyOpt.palette = pal;
+ QWindowsStyle::drawControl(element, &copyOpt, painter, widget);
+ }
+ return;
+
+#if QT_CONFIG(dockwidget)
+ case CE_DockWidgetTitle:
+ if (const auto *dwOpt = qstyleoption_cast<const QStyleOptionDockWidget *>(option)) {
+ QRect rect = option->rect;
+ const QDockWidget *dw = qobject_cast<const QDockWidget *>(widget);
+ bool isFloating = dw && dw->isFloating();
+ int buttonMargin = 4;
+ int mw = proxy()->pixelMetric(QStyle::PM_DockWidgetTitleMargin, dwOpt, widget);
+ int fw = proxy()->pixelMetric(PM_DockWidgetFrameWidth, dwOpt, widget);
+
+ const bool verticalTitleBar = dwOpt->verticalTitleBar;
+
+ if (verticalTitleBar) {
+ rect = rect.transposed();
+
+ painter->translate(rect.left() - 1, rect.top() + rect.width());
+ painter->rotate(-90);
+ painter->translate(-rect.left() + 1, -rect.top());
+ }
+
+ QRect r = option->rect.adjusted(0, 2, -1, -3);
+ QRect titleRect = r;
+
+ if (dwOpt->closable) {
+ QSize sz = proxy()->standardIcon(QStyle::SP_TitleBarCloseButton, dwOpt, widget).actualSize(QSize(10, 10));
+ titleRect.adjust(0, 0, -sz.width() - mw - buttonMargin, 0);
+ }
+
+ if (dwOpt->floatable) {
+ QSize sz = proxy()->standardIcon(QStyle::SP_TitleBarMaxButton, dwOpt, widget).actualSize(QSize(10, 10));
+ titleRect.adjust(0, 0, -sz.width() - mw - buttonMargin, 0);
+ }
+
+ if (isFloating) {
+ titleRect.adjust(0, -fw, 0, 0);
+ if (widget && widget->windowIcon().cacheKey() != QApplication::windowIcon().cacheKey())
+ titleRect.adjust(titleRect.height() + mw, 0, 0, 0);
+ } else {
+ titleRect.adjust(mw, 0, 0, 0);
+ if (!dwOpt->floatable && !dwOpt->closable)
+ titleRect.adjust(0, 0, -mw, 0);
+ }
+
+ if (!verticalTitleBar)
+ titleRect = visualRect(dwOpt->direction, r, titleRect);
+
+ if (isFloating) {
+ const bool isActive = dwOpt->state & State_Active;
+ themeNumber = QWindowsVistaStylePrivate::WindowTheme;
+ if (isActive)
+ stateId = CS_ACTIVE;
+ else
+ stateId = CS_INACTIVE;
+
+ int titleHeight = rect.height() - 2;
+ rect = rect.adjusted(-fw, -fw, fw, 0);
+
+ QWindowsThemeData theme(widget, painter, themeNumber, 0, stateId);
+ if (!theme.isValid())
+ break;
+
+ // Draw small type title bar
+ theme.rect = rect;
+ theme.partId = WP_SMALLCAPTION;
+ d->drawBackground(theme);
+
+ // Figure out maximal button space on title bar
+
+ QIcon ico = widget->windowIcon();
+ bool hasIcon = (ico.cacheKey() != QApplication::windowIcon().cacheKey());
+ if (hasIcon) {
+ QPixmap pxIco = ico.pixmap(titleHeight);
+ if (!verticalTitleBar && dwOpt->direction == Qt::RightToLeft)
+ painter->drawPixmap(rect.width() - titleHeight - pxIco.width(), rect.bottom() - titleHeight - 2, pxIco);
+ else
+ painter->drawPixmap(fw, rect.bottom() - titleHeight - 2, pxIco);
+ }
+ if (!dwOpt->title.isEmpty()) {
+ QPen oldPen = painter->pen();
+ QFont oldFont = painter->font();
+ QFont titleFont = oldFont;
+ titleFont.setBold(true);
+ painter->setFont(titleFont);
+ QString titleText
+ = painter->fontMetrics().elidedText(dwOpt->title, Qt::ElideRight, titleRect.width());
+
+ int result = TST_NONE;
+ GetThemeEnumValue(theme.handle(), WP_SMALLCAPTION, isActive ? CS_ACTIVE : CS_INACTIVE, TMT_TEXTSHADOWTYPE, &result);
+ if (result != TST_NONE) {
+ COLORREF textShadowRef;
+ GetThemeColor(theme.handle(), WP_SMALLCAPTION, isActive ? CS_ACTIVE : CS_INACTIVE, TMT_TEXTSHADOWCOLOR, &textShadowRef);
+ QColor textShadow = qRgb(GetRValue(textShadowRef), GetGValue(textShadowRef), GetBValue(textShadowRef));
+ painter->setPen(textShadow);
+ drawItemText(painter, titleRect.adjusted(1, 1, 1, 1),
+ Qt::AlignLeft | Qt::AlignBottom | Qt::TextHideMnemonic, dwOpt->palette,
+ dwOpt->state & State_Enabled, titleText);
+ }
+
+ COLORREF captionText = GetSysColor(isActive ? COLOR_CAPTIONTEXT : COLOR_INACTIVECAPTIONTEXT);
+ QColor textColor = qRgb(GetRValue(captionText), GetGValue(captionText), GetBValue(captionText));
+ painter->setPen(textColor);
+ drawItemText(painter, titleRect,
+ Qt::AlignLeft | Qt::AlignBottom | Qt::TextHideMnemonic, dwOpt->palette,
+ dwOpt->state & State_Enabled, titleText);
+ painter->setFont(oldFont);
+ painter->setPen(oldPen);
+ }
+ } else {
+ painter->setBrush(option->palette.window().color().darker(110));
+ painter->setPen(option->palette.window().color().darker(130));
+ painter->drawRect(rect.adjusted(0, 1, -1, -3));
+
+ if (!dwOpt->title.isEmpty()) {
+ QString titleText = painter->fontMetrics().elidedText(dwOpt->title, Qt::ElideRight,
+ verticalTitleBar ? titleRect.height() : titleRect.width());
+ const int indent = 4;
+ drawItemText(painter, rect.adjusted(indent + 1, 1, -indent - 1, -1),
+ Qt::AlignLeft | Qt::AlignVCenter | Qt::TextHideMnemonic,
+ dwOpt->palette,
+ dwOpt->state & State_Enabled, titleText,
+ QPalette::WindowText);
+ }
+ }
+ }
+ return;
+#endif // QT_CONFIG(dockwidget)
+
+#if QT_CONFIG(rubberband)
+ case CE_RubberBand:
+ if (qstyleoption_cast<const QStyleOptionRubberBand *>(option)) {
+ QColor highlight = option->palette.color(QPalette::Active, QPalette::Highlight);
+ painter->save();
+ painter->setPen(highlight.darker(120));
+ QColor dimHighlight(qMin(highlight.red()/2 + 110, 255),
+ qMin(highlight.green()/2 + 110, 255),
+ qMin(highlight.blue()/2 + 110, 255),
+ (widget && widget->isWindow())? 255 : 127);
+ painter->setBrush(dimHighlight);
+ painter->drawRect(option->rect.adjusted(0, 0, -1, -1));
+ painter->restore();
+ return;
+ }
+ break;
+#endif // QT_CONFIG(rubberband)
+
+ case CE_HeaderEmptyArea:
+ if (option->state & State_Horizontal) {
+ themeNumber = QWindowsVistaStylePrivate::HeaderTheme;
+ stateId = HIS_NORMAL;
+ } else {
+ QWindowsStyle::drawControl(CE_HeaderEmptyArea, option, painter, widget);
+ return;
+ }
+ break;
+
+#if QT_CONFIG(itemviews)
+ case CE_ItemViewItem: {
+ const QStyleOptionViewItem *vopt;
+ const QAbstractItemView *view = qobject_cast<const QAbstractItemView *>(widget);
+ bool newStyle = true;
+
+ if (qobject_cast<const QTableView*>(widget))
+ newStyle = false;
+
+ QWindowsThemeData theme(widget, painter, themeNumber, partId, stateId, rect);
+
+ if (newStyle && view && (vopt = qstyleoption_cast<const QStyleOptionViewItem *>(option))) {
+ /*
+ // We cannot currently get the correct selection color for "explorer style" views
+ COLORREF cref = 0;
+ QWindowsThemeData theme(d->treeViewHelper(), 0, QLatin1String("LISTVIEW"), 0, 0);
+ unsigned int res = GetThemeColor(theme.handle(), LVP_LISTITEM, LISS_SELECTED, TMT_TEXTCOLOR, &cref);
+ QColor textColor(GetRValue(cref), GetGValue(cref), GetBValue(cref));
+ */
+ QPalette palette = vopt->palette;
+ palette.setColor(QPalette::All, QPalette::HighlightedText, palette.color(QPalette::Active, QPalette::Text));
+ // Note that setting a saturated color here results in ugly XOR colors in the focus rect
+ palette.setColor(QPalette::All, QPalette::Highlight, palette.base().color().darker(108));
+ QStyleOptionViewItem adjustedOption = *vopt;
+ adjustedOption.palette = palette;
+ // We hide the focusrect in singleselection as it is not required
+ if ((view->selectionMode() == QAbstractItemView::SingleSelection)
+ && !(vopt->state & State_KeyboardFocusChange))
+ adjustedOption.state &= ~State_HasFocus;
+ if (!theme.isValid()) {
+ QWindowsStyle::drawControl(element, &adjustedOption, painter, widget);
+ return;
+ }
+ } else {
+ if (!theme.isValid()) {
+ QWindowsStyle::drawControl(element, option, painter, widget);
+ return;
+ }
+ }
+
+ theme.rotate = rotate;
+ theme.mirrorHorizontally = hMirrored;
+ theme.mirrorVertically = vMirrored;
+ d->drawBackground(theme);
+ return;
+ }
+#endif // QT_CONFIG(itemviews)
+
+#if QT_CONFIG(combobox)
+ case CE_ComboBoxLabel:
+ QCommonStyle::drawControl(element, option, painter, widget);
+ return;
+#endif // QT_CONFIG(combobox)
+
+ default:
+ break;
+ }
+
+ QWindowsThemeData theme(widget, painter, themeNumber, partId, stateId, rect);
+
+ if (!theme.isValid()) {
+ QWindowsStyle::drawControl(element, option, painter, widget);
+ return;
+ }
+
+ theme.rotate = rotate;
+ theme.mirrorHorizontally = hMirrored;
+ theme.mirrorVertically = vMirrored;
+
+ d->drawBackground(theme);
+}
+
+/*!
+ \internal
+ see drawPrimitive for comments on the animation support
+
+ */
+void QWindowsVistaStyle::drawComplexControl(ComplexControl control, const QStyleOptionComplex *option,
+ QPainter *painter, const QWidget *widget) const
+{
+ QWindowsVistaStylePrivate *d = const_cast<QWindowsVistaStylePrivate*>(d_func());
+
+ if (!QWindowsVistaStylePrivate::useVista()) {
+ QWindowsStyle::drawComplexControl(control, option, painter, widget);
+ return;
+ }
+
+ State state = option->state;
+ SubControls sub = option->subControls;
+ QRect r = option->rect;
+
+ int partId = 0;
+ int stateId = 0;
+
+ State flags = option->state;
+ if (widget && widget->testAttribute(Qt::WA_UnderMouse) && widget->isActiveWindow())
+ flags |= State_MouseOver;
+
+ if (d->transitionsEnabled() && canAnimate(option))
+ {
+ if (control == CC_ScrollBar || control == CC_SpinBox || control == CC_ComboBox) {
+ QObject *styleObject = option->styleObject; // Can be widget or qquickitem
+
+ int oldState = styleObject->property("_q_stylestate").toInt();
+ int oldActiveControls = styleObject->property("_q_stylecontrols").toInt();
+
+ QRect oldRect = styleObject->property("_q_stylerect").toRect();
+ styleObject->setProperty("_q_stylestate", int(option->state));
+ styleObject->setProperty("_q_stylecontrols", int(option->activeSubControls));
+ styleObject->setProperty("_q_stylerect", option->rect);
+
+ bool doTransition = ((state & State_Sunken) != (oldState & State_Sunken)
+ || (state & State_On) != (oldState & State_On)
+ || (state & State_MouseOver) != (oldState & State_MouseOver)
+ || oldActiveControls != int(option->activeSubControls));
+
+ if (qstyleoption_cast<const QStyleOptionSlider *>(option)) {
+ QRect oldSliderPos = styleObject->property("_q_stylesliderpos").toRect();
+ QRect currentPos = proxy()->subControlRect(CC_ScrollBar, option, SC_ScrollBarSlider, widget);
+ styleObject->setProperty("_q_stylesliderpos", currentPos);
+ if (oldSliderPos != currentPos) {
+ doTransition = false;
+ d->stopAnimation(styleObject);
+ }
+ } else if (control == CC_SpinBox) {
+ //spinboxes have a transition when focus changes
+ if (!doTransition)
+ doTransition = (state & State_HasFocus) != (oldState & State_HasFocus);
+ }
+
+ if (oldRect != option->rect) {
+ doTransition = false;
+ d->stopAnimation(styleObject);
+ }
+
+ if (doTransition) {
+ QImage startImage = createAnimationBuffer(option, widget);
+ QPainter startPainter(&startImage);
+
+ QImage endImage = createAnimationBuffer(option, widget);
+ QPainter endPainter(&endImage);
+
+ QWindowsVistaAnimation *anim = qobject_cast<QWindowsVistaAnimation *>(d->animation(styleObject));
+ QWindowsVistaTransition *t = new QWindowsVistaTransition(styleObject);
+
+ // Draw the image that ends the animation by using the current styleoption
+ QStyleOptionComplex *styleOption = qstyleoption_cast<QStyleOptionComplex*>(clonedAnimationStyleOption(option));
+
+ styleObject->setProperty("_q_no_animation", true);
+
+ // Draw transition source
+ if (!anim) {
+ styleOption->state = QStyle::State(oldState);
+ styleOption->activeSubControls = QStyle::SubControl(oldActiveControls);
+ proxy()->drawComplexControl(control, styleOption, &startPainter, widget);
+ } else {
+ anim->paint(&startPainter, option);
+ }
+ t->setStartImage(startImage);
+
+ // Draw transition target
+ styleOption->state = option->state;
+ styleOption->activeSubControls = option->activeSubControls;
+ proxy()->drawComplexControl(control, styleOption, &endPainter, widget);
+
+ styleObject->setProperty("_q_no_animation", false);
+
+ t->setEndImage(endImage);
+ t->setStartTime(d->animationTime());
+
+ if (option->state & State_MouseOver || option->state & State_Sunken)
+ t->setDuration(150);
+ else
+ t->setDuration(500);
+
+ deleteClonedAnimationStyleOption(styleOption);
+ d->startAnimation(t);
+ }
+ if (QWindowsVistaAnimation *anim = qobject_cast<QWindowsVistaAnimation *>(d->animation(styleObject))) {
+ anim->paint(painter, option);
+ return;
+ }
+ }
+ }
+
+ switch (control) {
+
+#if QT_CONFIG(slider)
+ case CC_Slider:
+ if (const auto *slider = qstyleoption_cast<const QStyleOptionSlider *>(option)) {
+ QWindowsThemeData theme(widget, painter, QWindowsVistaStylePrivate::TrackBarTheme);
+ QRect slrect = slider->rect;
+ QRegion tickreg = slrect;
+ if (sub & SC_SliderGroove) {
+ theme.rect = proxy()->subControlRect(CC_Slider, option, SC_SliderGroove, widget);
+ if (slider->orientation == Qt::Horizontal) {
+ partId = TKP_TRACK;
+ stateId = TRS_NORMAL;
+ theme.rect = QRect(slrect.left(), theme.rect.center().y() - 2, slrect.width(), 4);
+ } else {
+ partId = TKP_TRACKVERT;
+ stateId = TRVS_NORMAL;
+ theme.rect = QRect(theme.rect.center().x() - 2, slrect.top(), 4, slrect.height());
+ }
+ theme.partId = partId;
+ theme.stateId = stateId;
+ d->drawBackground(theme);
+ tickreg -= theme.rect;
+ }
+ if (sub & SC_SliderTickmarks) {
+ int tickOffset = proxy()->pixelMetric(PM_SliderTickmarkOffset, slider, widget);
+ int ticks = slider->tickPosition;
+ int thickness = proxy()->pixelMetric(PM_SliderControlThickness, slider, widget);
+ int len = proxy()->pixelMetric(PM_SliderLength, slider, widget);
+ int available = proxy()->pixelMetric(PM_SliderSpaceAvailable, slider, widget);
+ int interval = slider->tickInterval;
+ if (interval <= 0) {
+ interval = slider->singleStep;
+ if (QStyle::sliderPositionFromValue(slider->minimum, slider->maximum, interval,
+ available)
+ - QStyle::sliderPositionFromValue(slider->minimum, slider->maximum,
+ 0, available) < 3)
+ interval = slider->pageStep;
+ }
+ if (!interval)
+ interval = 1;
+ int fudge = len / 2;
+ int pos;
+ int bothOffset = (ticks & QSlider::TicksAbove && ticks & QSlider::TicksBelow) ? 1 : 0;
+ painter->setPen(d->sliderTickColor);
+ QVarLengthArray<QLine, 32> lines;
+ int v = slider->minimum;
+ while (v <= slider->maximum + 1) {
+ if (v == slider->maximum + 1 && interval == 1)
+ break;
+ const int v_ = qMin(v, slider->maximum);
+ int tickLength = (v_ == slider->minimum || v_ >= slider->maximum) ? 4 : 3;
+ pos = QStyle::sliderPositionFromValue(slider->minimum, slider->maximum,
+ v_, available) + fudge;
+ if (slider->orientation == Qt::Horizontal) {
+ if (ticks & QSlider::TicksAbove) {
+ lines.append(QLine(pos, tickOffset - 1 - bothOffset,
+ pos, tickOffset - 1 - bothOffset - tickLength));
+ }
+
+ if (ticks & QSlider::TicksBelow) {
+ lines.append(QLine(pos, tickOffset + thickness + bothOffset,
+ pos, tickOffset + thickness + bothOffset + tickLength));
+ }
+ } else {
+ if (ticks & QSlider::TicksAbove) {
+ lines.append(QLine(tickOffset - 1 - bothOffset, pos,
+ tickOffset - 1 - bothOffset - tickLength, pos));
+ }
+
+ if (ticks & QSlider::TicksBelow) {
+ lines.append(QLine(tickOffset + thickness + bothOffset, pos,
+ tickOffset + thickness + bothOffset + tickLength, pos));
+ }
+ }
+ // in the case where maximum is max int
+ int nextInterval = v + interval;
+ if (nextInterval < v)
+ break;
+ v = nextInterval;
+ }
+ if (!lines.isEmpty()) {
+ painter->save();
+ painter->translate(slrect.topLeft());
+ painter->drawLines(lines.constData(), lines.size());
+ painter->restore();
+ }
+ }
+ if (sub & SC_SliderHandle) {
+ theme.rect = proxy()->subControlRect(CC_Slider, option, SC_SliderHandle, widget);
+ if (slider->orientation == Qt::Horizontal) {
+ if (slider->tickPosition == QSlider::TicksAbove)
+ partId = TKP_THUMBTOP;
+ else if (slider->tickPosition == QSlider::TicksBelow)
+ partId = TKP_THUMBBOTTOM;
+ else
+ partId = TKP_THUMB;
+
+ if (!(slider->state & State_Enabled))
+ stateId = TUS_DISABLED;
+ else if (slider->activeSubControls & SC_SliderHandle && (slider->state & State_Sunken))
+ stateId = TUS_PRESSED;
+ else if (slider->activeSubControls & SC_SliderHandle && (slider->state & State_MouseOver))
+ stateId = TUS_HOT;
+ else if (flags & State_HasFocus)
+ stateId = TUS_FOCUSED;
+ else
+ stateId = TUS_NORMAL;
+ } else {
+ if (slider->tickPosition == QSlider::TicksLeft)
+ partId = TKP_THUMBLEFT;
+ else if (slider->tickPosition == QSlider::TicksRight)
+ partId = TKP_THUMBRIGHT;
+ else
+ partId = TKP_THUMBVERT;
+
+ if (!(slider->state & State_Enabled))
+ stateId = TUVS_DISABLED;
+ else if (slider->activeSubControls & SC_SliderHandle && (slider->state & State_Sunken))
+ stateId = TUVS_PRESSED;
+ else if (slider->activeSubControls & SC_SliderHandle && (slider->state & State_MouseOver))
+ stateId = TUVS_HOT;
+ else if (flags & State_HasFocus)
+ stateId = TUVS_FOCUSED;
+ else
+ stateId = TUVS_NORMAL;
+ }
+ theme.partId = partId;
+ theme.stateId = stateId;
+ d->drawBackground(theme);
+ }
+ if (slider->state & State_HasFocus) {
+ QStyleOptionFocusRect fropt;
+ fropt.QStyleOption::operator=(*slider);
+ fropt.rect = subElementRect(SE_SliderFocusRect, slider, widget);
+ proxy()->drawPrimitive(PE_FrameFocusRect, &fropt, painter, widget);
+ }
+ }
+ break;
+#endif
+
+#if QT_CONFIG(toolbutton)
+ case CC_ToolButton:
+ if (const auto *toolbutton = qstyleoption_cast<const QStyleOptionToolButton *>(option)) {
+ QRect button, menuarea;
+ button = proxy()->subControlRect(control, toolbutton, SC_ToolButton, widget);
+ menuarea = proxy()->subControlRect(control, toolbutton, SC_ToolButtonMenu, widget);
+
+ State bflags = toolbutton->state & ~State_Sunken;
+ State mflags = bflags;
+ bool autoRaise = flags & State_AutoRaise;
+ if (autoRaise) {
+ if (!(bflags & State_MouseOver) || !(bflags & State_Enabled))
+ bflags &= ~State_Raised;
+ }
+
+ if (toolbutton->state & State_Sunken) {
+ if (toolbutton->activeSubControls & SC_ToolButton) {
+ bflags |= State_Sunken;
+ mflags |= State_MouseOver | State_Sunken;
+ } else if (toolbutton->activeSubControls & SC_ToolButtonMenu) {
+ mflags |= State_Sunken;
+ bflags |= State_MouseOver;
+ }
+ }
+
+ QStyleOption tool = *toolbutton;
+ if (toolbutton->subControls & SC_ToolButton) {
+ if (flags & (State_Sunken | State_On | State_Raised) || !autoRaise) {
+ if (toolbutton->features & QStyleOptionToolButton::MenuButtonPopup && autoRaise) {
+ QWindowsThemeData theme(widget, painter, QWindowsVistaStylePrivate::ToolBarTheme);
+ theme.partId = TP_SPLITBUTTON;
+ theme.rect = button;
+ if (!(bflags & State_Enabled))
+ stateId = TS_DISABLED;
+ else if (bflags & State_Sunken)
+ stateId = TS_PRESSED;
+ else if (bflags & State_MouseOver || !(flags & State_AutoRaise))
+ stateId = flags & State_On ? TS_HOTCHECKED : TS_HOT;
+ else if (bflags & State_On)
+ stateId = TS_CHECKED;
+ else
+ stateId = TS_NORMAL;
+ if (option->direction == Qt::RightToLeft)
+ theme.mirrorHorizontally = true;
+ theme.stateId = stateId;
+ d->drawBackground(theme);
+ } else {
+ tool.rect = option->rect;
+ tool.state = bflags;
+ if (autoRaise) // for tool bars
+ proxy()->drawPrimitive(PE_PanelButtonTool, &tool, painter, widget);
+ else
+ proxy()->drawPrimitive(PE_PanelButtonBevel, &tool, painter, widget);
+ }
+ }
+ }
+
+ if (toolbutton->state & State_HasFocus) {
+ QStyleOptionFocusRect fr;
+ fr.QStyleOption::operator=(*toolbutton);
+ fr.rect.adjust(3, 3, -3, -3);
+ if (toolbutton->features & QStyleOptionToolButton::MenuButtonPopup)
+ fr.rect.adjust(0, 0, -proxy()->pixelMetric(QStyle::PM_MenuButtonIndicator,
+ toolbutton, widget), 0);
+ proxy()->drawPrimitive(PE_FrameFocusRect, &fr, painter, widget);
+ }
+ QStyleOptionToolButton label = *toolbutton;
+ label.state = bflags;
+ int fw = 2;
+ if (!autoRaise)
+ label.state &= ~State_Sunken;
+ label.rect = button.adjusted(fw, fw, -fw, -fw);
+ proxy()->drawControl(CE_ToolButtonLabel, &label, painter, widget);
+
+ if (toolbutton->subControls & SC_ToolButtonMenu) {
+ tool.rect = menuarea;
+ tool.state = mflags;
+ if (autoRaise) {
+ proxy()->drawPrimitive(PE_IndicatorButtonDropDown, &tool, painter, widget);
+ } else {
+ tool.state = mflags;
+ menuarea.adjust(-2, 0, 0, 0);
+ // Draw menu button
+ if ((bflags & State_Sunken) != (mflags & State_Sunken)){
+ painter->save();
+ painter->setClipRect(menuarea);
+ tool.rect = option->rect;
+ proxy()->drawPrimitive(PE_PanelButtonBevel, &tool, painter, nullptr);
+ painter->restore();
+ }
+ // Draw arrow
+ painter->save();
+ painter->setPen(option->palette.dark().color());
+ painter->drawLine(menuarea.left(), menuarea.top() + 3,
+ menuarea.left(), menuarea.bottom() - 3);
+ painter->setPen(option->palette.light().color());
+ painter->drawLine(menuarea.left() - 1, menuarea.top() + 3,
+ menuarea.left() - 1, menuarea.bottom() - 3);
+
+ tool.rect = menuarea.adjusted(2, 3, -2, -1);
+ proxy()->drawPrimitive(PE_IndicatorArrowDown, &tool, painter, widget);
+ painter->restore();
+ }
+ } else if (toolbutton->features & QStyleOptionToolButton::HasMenu) {
+ int mbi = proxy()->pixelMetric(PM_MenuButtonIndicator, toolbutton, widget);
+ QRect ir = toolbutton->rect;
+ QStyleOptionToolButton newBtn = *toolbutton;
+ newBtn.rect = QRect(ir.right() + 4 - mbi, ir.height() - mbi + 4, mbi - 5, mbi - 5);
+ proxy()->drawPrimitive(PE_IndicatorArrowDown, &newBtn, painter, widget);
+ }
+ }
+ break;
+#endif // QT_CONFIG(toolbutton)
+
+ case CC_TitleBar:
+ if (const auto *tb = qstyleoption_cast<const QStyleOptionTitleBar *>(option)) {
+ const qreal factor = QWindowsStylePrivate::nativeMetricScaleFactor(widget);
+ bool isActive = tb->titleBarState & QStyle::State_Active;
+ QWindowsThemeData theme(widget, painter, QWindowsVistaStylePrivate::WindowTheme);
+ if (sub & SC_TitleBarLabel) {
+ partId = (tb->titleBarState & Qt::WindowMinimized) ? WP_MINCAPTION : WP_CAPTION;
+ theme.rect = option->rect;
+ if (widget && !widget->isEnabled())
+ stateId = CS_DISABLED;
+ else if (isActive)
+ stateId = CS_ACTIVE;
+ else
+ stateId = CS_INACTIVE;
+
+ theme.partId = partId;
+ theme.stateId = stateId;
+ d->drawBackground(theme);
+
+ QRect ir = proxy()->subControlRect(CC_TitleBar, tb, SC_TitleBarLabel, widget);
+
+ int result = TST_NONE;
+ GetThemeEnumValue(theme.handle(), WP_CAPTION, isActive ? CS_ACTIVE : CS_INACTIVE, TMT_TEXTSHADOWTYPE, &result);
+ if (result != TST_NONE) {
+ COLORREF textShadowRef;
+ GetThemeColor(theme.handle(), WP_CAPTION, isActive ? CS_ACTIVE : CS_INACTIVE, TMT_TEXTSHADOWCOLOR, &textShadowRef);
+ QColor textShadow = qRgb(GetRValue(textShadowRef), GetGValue(textShadowRef), GetBValue(textShadowRef));
+ painter->setPen(textShadow);
+ painter->drawText(int(ir.x() + 3 * factor), int(ir.y() + 2 * factor),
+ int(ir.width() - 1 * factor), ir.height(),
+ Qt::AlignLeft | Qt::AlignVCenter | Qt::TextSingleLine, tb->text);
+ }
+ COLORREF captionText = GetSysColor(isActive ? COLOR_CAPTIONTEXT : COLOR_INACTIVECAPTIONTEXT);
+ QColor textColor = qRgb(GetRValue(captionText), GetGValue(captionText), GetBValue(captionText));
+ painter->setPen(textColor);
+ painter->drawText(int(ir.x() + 2 * factor), int(ir.y() + 1 * factor),
+ int(ir.width() - 2 * factor), ir.height(),
+ Qt::AlignLeft | Qt::AlignVCenter | Qt::TextSingleLine, tb->text);
+ }
+ if (sub & SC_TitleBarSysMenu && tb->titleBarFlags & Qt::WindowSystemMenuHint) {
+ theme.rect = proxy()->subControlRect(CC_TitleBar, option, SC_TitleBarSysMenu, widget);
+ partId = WP_SYSBUTTON;
+ if ((widget && !widget->isEnabled()) || !isActive)
+ stateId = SBS_DISABLED;
+ else if (option->activeSubControls == SC_TitleBarSysMenu && (option->state & State_Sunken))
+ stateId = SBS_PUSHED;
+ else if (option->activeSubControls == SC_TitleBarSysMenu && (option->state & State_MouseOver))
+ stateId = SBS_HOT;
+ else
+ stateId = SBS_NORMAL;
+ if (!tb->icon.isNull()) {
+ tb->icon.paint(painter, theme.rect);
+ } else {
+ theme.partId = partId;
+ theme.stateId = stateId;
+ if (theme.size().isEmpty()) {
+ int iconSize = proxy()->pixelMetric(PM_SmallIconSize, tb, widget);
+ QPixmap pm = proxy()->standardIcon(SP_TitleBarMenuButton, tb, widget).pixmap(iconSize, iconSize);
+ painter->save();
+ drawItemPixmap(painter, theme.rect, Qt::AlignCenter, pm);
+ painter->restore();
+ } else {
+ d->drawBackground(theme);
+ }
+ }
+ }
+
+ if (sub & SC_TitleBarMinButton && tb->titleBarFlags & Qt::WindowMinimizeButtonHint
+ && !(tb->titleBarState & Qt::WindowMinimized)) {
+ populateTitleBarButtonTheme(proxy(), widget, option, SC_TitleBarMinButton, isActive, WP_MINBUTTON, &theme);
+ d->drawBackground(theme);
+ }
+ if (sub & SC_TitleBarMaxButton && tb->titleBarFlags & Qt::WindowMaximizeButtonHint
+ && !(tb->titleBarState & Qt::WindowMaximized)) {
+ populateTitleBarButtonTheme(proxy(), widget, option, SC_TitleBarMaxButton, isActive, WP_MAXBUTTON, &theme);
+ d->drawBackground(theme);
+ }
+ if (sub & SC_TitleBarContextHelpButton
+ && tb->titleBarFlags & Qt::WindowContextHelpButtonHint) {
+ populateTitleBarButtonTheme(proxy(), widget, option, SC_TitleBarContextHelpButton, isActive, WP_HELPBUTTON, &theme);
+ d->drawBackground(theme);
+ }
+ bool drawNormalButton = (sub & SC_TitleBarNormalButton)
+ && (((tb->titleBarFlags & Qt::WindowMinimizeButtonHint)
+ && (tb->titleBarState & Qt::WindowMinimized))
+ || ((tb->titleBarFlags & Qt::WindowMaximizeButtonHint)
+ && (tb->titleBarState & Qt::WindowMaximized)));
+ if (drawNormalButton) {
+ populateTitleBarButtonTheme(proxy(), widget, option, SC_TitleBarNormalButton, isActive, WP_RESTOREBUTTON, &theme);
+ d->drawBackground(theme);
+ }
+ if (sub & SC_TitleBarShadeButton && tb->titleBarFlags & Qt::WindowShadeButtonHint
+ && !(tb->titleBarState & Qt::WindowMinimized)) {
+ populateTitleBarButtonTheme(proxy(), widget, option, SC_TitleBarShadeButton, isActive, WP_MINBUTTON, &theme);
+ d->drawBackground(theme);
+ }
+ if (sub & SC_TitleBarUnshadeButton && tb->titleBarFlags & Qt::WindowShadeButtonHint
+ && tb->titleBarState & Qt::WindowMinimized) {
+ populateTitleBarButtonTheme(proxy(), widget, option, SC_TitleBarUnshadeButton, isActive, WP_RESTOREBUTTON, &theme);
+ d->drawBackground(theme);
+ }
+ if (sub & SC_TitleBarCloseButton && tb->titleBarFlags & Qt::WindowSystemMenuHint) {
+ populateTitleBarButtonTheme(proxy(), widget, option, SC_TitleBarCloseButton, isActive, WP_CLOSEBUTTON, &theme);
+ d->drawBackground(theme);
+ }
+ }
+ break;
+
+#if QT_CONFIG(mdiarea)
+ case CC_MdiControls: {
+ QWindowsThemeData theme(widget, painter, QWindowsVistaStylePrivate::WindowTheme, WP_MDICLOSEBUTTON, CBS_NORMAL);
+ if (Q_UNLIKELY(!theme.isValid()))
+ return;
+
+ if (option->subControls.testFlag(SC_MdiCloseButton)) {
+ populateMdiButtonTheme(proxy(), widget, option, SC_MdiCloseButton, WP_MDICLOSEBUTTON, &theme);
+ d->drawBackground(theme, mdiButtonCorrectionFactor(theme, widget));
+ }
+ if (option->subControls.testFlag(SC_MdiNormalButton)) {
+ populateMdiButtonTheme(proxy(), widget, option, SC_MdiNormalButton, WP_MDIRESTOREBUTTON, &theme);
+ d->drawBackground(theme, mdiButtonCorrectionFactor(theme, widget));
+ }
+ if (option->subControls.testFlag(QStyle::SC_MdiMinButton)) {
+ populateMdiButtonTheme(proxy(), widget, option, SC_MdiMinButton, WP_MDIMINBUTTON, &theme);
+ d->drawBackground(theme, mdiButtonCorrectionFactor(theme, widget));
+ }
+ break;
+ }
+#endif // QT_CONFIG(mdiarea)
+
+#if QT_CONFIG(dial)
+ case CC_Dial:
+ if (const auto *dial = qstyleoption_cast<const QStyleOptionSlider *>(option))
+ QStyleHelper::drawDial(dial, painter);
+ break;
+#endif // QT_CONFIG(dial)
+
+ case CC_ComboBox:
+ if (const QStyleOptionComboBox *cmb = qstyleoption_cast<const QStyleOptionComboBox *>(option)) {
+ if (cmb->editable) {
+ if (sub & SC_ComboBoxEditField) {
+ partId = EP_EDITBORDER_NOSCROLL;
+ if (!(flags & State_Enabled))
+ stateId = ETS_DISABLED;
+ else if (flags & State_MouseOver)
+ stateId = ETS_HOT;
+ else if (flags & State_HasFocus)
+ stateId = ETS_FOCUSED;
+ else
+ stateId = ETS_NORMAL;
+
+ QWindowsThemeData theme(widget, painter,
+ QWindowsVistaStylePrivate::EditTheme,
+ partId, stateId, r);
+
+ d->drawBackground(theme);
+ }
+ if (sub & SC_ComboBoxArrow) {
+ QRect subRect = proxy()->subControlRect(CC_ComboBox, option, SC_ComboBoxArrow, widget);
+ QWindowsThemeData theme(widget, painter, QWindowsVistaStylePrivate::ComboboxTheme);
+ theme.rect = subRect;
+ partId = option->direction == Qt::RightToLeft ? CP_DROPDOWNBUTTONLEFT : CP_DROPDOWNBUTTONRIGHT;
+
+ if (!(cmb->state & State_Enabled))
+ stateId = CBXS_DISABLED;
+ else if (cmb->state & State_Sunken || cmb->state & State_On)
+ stateId = CBXS_PRESSED;
+ else if (cmb->state & State_MouseOver && option->activeSubControls & SC_ComboBoxArrow)
+ stateId = CBXS_HOT;
+ else
+ stateId = CBXS_NORMAL;
+
+ theme.partId = partId;
+ theme.stateId = stateId;
+ d->drawBackground(theme);
+ }
+
+ } else {
+ if (sub & SC_ComboBoxFrame) {
+ QWindowsThemeData theme(widget, painter, QWindowsVistaStylePrivate::ComboboxTheme);
+ theme.rect = option->rect;
+ theme.partId = CP_READONLY;
+ if (!(cmb->state & State_Enabled))
+ theme.stateId = CBXS_DISABLED;
+ else if (cmb->state & State_Sunken || cmb->state & State_On)
+ theme.stateId = CBXS_PRESSED;
+ else if (cmb->state & State_MouseOver)
+ theme.stateId = CBXS_HOT;
+ else
+ theme.stateId = CBXS_NORMAL;
+ d->drawBackground(theme);
+ }
+ if (sub & SC_ComboBoxArrow) {
+ QWindowsThemeData theme(widget, painter, QWindowsVistaStylePrivate::ComboboxTheme);
+ theme.rect = proxy()->subControlRect(CC_ComboBox, option, SC_ComboBoxArrow, widget);
+ theme.partId = option->direction == Qt::RightToLeft ? CP_DROPDOWNBUTTONLEFT : CP_DROPDOWNBUTTONRIGHT;
+ if (!(cmb->state & State_Enabled))
+ theme.stateId = CBXS_DISABLED;
+ else
+ theme.stateId = CBXS_NORMAL;
+ d->drawBackground(theme);
+ }
+ if ((sub & SC_ComboBoxEditField) && (flags & State_HasFocus)) {
+ QStyleOptionFocusRect fropt;
+ fropt.QStyleOption::operator=(*cmb);
+ fropt.rect = proxy()->subControlRect(CC_ComboBox, option, SC_ComboBoxEditField, widget);
+ proxy()->drawPrimitive(PE_FrameFocusRect, &fropt, painter, widget);
+ }
+ }
+ }
+ break;
+
+ case CC_ScrollBar:
+ if (const QStyleOptionSlider *scrollbar = qstyleoption_cast<const QStyleOptionSlider *>(option)) {
+ QWindowsThemeData theme(widget, painter, QWindowsVistaStylePrivate::ScrollBarTheme);
+ bool maxedOut = (scrollbar->maximum == scrollbar->minimum);
+ if (maxedOut)
+ flags &= ~State_Enabled;
+
+ bool isHorz = flags & State_Horizontal;
+ bool isRTL = option->direction == Qt::RightToLeft;
+ if (sub & SC_ScrollBarAddLine) {
+ theme.rect = proxy()->subControlRect(CC_ScrollBar, option, SC_ScrollBarAddLine, widget);
+ partId = SBP_ARROWBTN;
+ if (!(flags & State_Enabled))
+ stateId = (isHorz ? (isRTL ? ABS_LEFTDISABLED : ABS_RIGHTDISABLED) : ABS_DOWNDISABLED);
+ else if (scrollbar->activeSubControls & SC_ScrollBarAddLine && (scrollbar->state & State_Sunken))
+ stateId = (isHorz ? (isRTL ? ABS_LEFTPRESSED : ABS_RIGHTPRESSED) : ABS_DOWNPRESSED);
+ else if (scrollbar->activeSubControls & SC_ScrollBarAddLine && (scrollbar->state & State_MouseOver))
+ stateId = (isHorz ? (isRTL ? ABS_LEFTHOT : ABS_RIGHTHOT) : ABS_DOWNHOT);
+ else if (scrollbar->state & State_MouseOver)
+ stateId = (isHorz ? (isRTL ? ABS_LEFTHOVER : ABS_RIGHTHOVER) : ABS_DOWNHOVER);
+ else
+ stateId = (isHorz ? (isRTL ? ABS_LEFTNORMAL : ABS_RIGHTNORMAL) : ABS_DOWNNORMAL);
+ theme.partId = partId;
+ theme.stateId = stateId;
+ d->drawBackground(theme);
+ }
+ if (sub & SC_ScrollBarSubLine) {
+ theme.rect = proxy()->subControlRect(CC_ScrollBar, option, SC_ScrollBarSubLine, widget);
+ partId = SBP_ARROWBTN;
+ if (!(flags & State_Enabled))
+ stateId = (isHorz ? (isRTL ? ABS_RIGHTDISABLED : ABS_LEFTDISABLED) : ABS_UPDISABLED);
+ else if (scrollbar->activeSubControls & SC_ScrollBarSubLine && (scrollbar->state & State_Sunken))
+ stateId = (isHorz ? (isRTL ? ABS_RIGHTPRESSED : ABS_LEFTPRESSED) : ABS_UPPRESSED);
+ else if (scrollbar->activeSubControls & SC_ScrollBarSubLine && (scrollbar->state & State_MouseOver))
+ stateId = (isHorz ? (isRTL ? ABS_RIGHTHOT : ABS_LEFTHOT) : ABS_UPHOT);
+ else if (scrollbar->state & State_MouseOver)
+ stateId = (isHorz ? (isRTL ? ABS_RIGHTHOVER : ABS_LEFTHOVER) : ABS_UPHOVER);
+ else
+ stateId = (isHorz ? (isRTL ? ABS_RIGHTNORMAL : ABS_LEFTNORMAL) : ABS_UPNORMAL);
+ theme.partId = partId;
+ theme.stateId = stateId;
+ d->drawBackground(theme);
+ }
+ if (maxedOut) {
+ theme.rect = proxy()->subControlRect(CC_ScrollBar, option, SC_ScrollBarSlider, widget);
+ theme.rect = theme.rect.united(proxy()->subControlRect(CC_ScrollBar, option, SC_ScrollBarSubPage, widget));
+ theme.rect = theme.rect.united(proxy()->subControlRect(CC_ScrollBar, option, SC_ScrollBarAddPage, widget));
+ partId = flags & State_Horizontal ? SBP_LOWERTRACKHORZ : SBP_LOWERTRACKVERT;
+ stateId = SCRBS_DISABLED;
+ theme.partId = partId;
+ theme.stateId = stateId;
+ d->drawBackground(theme);
+ } else {
+ if (sub & SC_ScrollBarSubPage) {
+ theme.rect = proxy()->subControlRect(CC_ScrollBar, option, SC_ScrollBarSubPage, widget);
+ partId = flags & State_Horizontal ? SBP_UPPERTRACKHORZ : SBP_UPPERTRACKVERT;
+ if (!(flags & State_Enabled))
+ stateId = SCRBS_DISABLED;
+ else if (scrollbar->activeSubControls & SC_ScrollBarSubPage && (scrollbar->state & State_Sunken))
+ stateId = SCRBS_PRESSED;
+ else if (scrollbar->activeSubControls & SC_ScrollBarSubPage && (scrollbar->state & State_MouseOver))
+ stateId = SCRBS_HOT;
+ else
+ stateId = SCRBS_NORMAL;
+ theme.partId = partId;
+ theme.stateId = stateId;
+ d->drawBackground(theme);
+ }
+ if (sub & SC_ScrollBarAddPage) {
+ theme.rect = proxy()->subControlRect(CC_ScrollBar, option, SC_ScrollBarAddPage, widget);
+ partId = flags & State_Horizontal ? SBP_LOWERTRACKHORZ : SBP_LOWERTRACKVERT;
+ if (!(flags & State_Enabled))
+ stateId = SCRBS_DISABLED;
+ else if (scrollbar->activeSubControls & SC_ScrollBarAddPage && (scrollbar->state & State_Sunken))
+ stateId = SCRBS_PRESSED;
+ else if (scrollbar->activeSubControls & SC_ScrollBarAddPage && (scrollbar->state & State_MouseOver))
+ stateId = SCRBS_HOT;
+ else
+ stateId = SCRBS_NORMAL;
+ theme.partId = partId;
+ theme.stateId = stateId;
+ d->drawBackground(theme);
+ }
+ if (sub & SC_ScrollBarSlider) {
+ theme.rect = proxy()->subControlRect(CC_ScrollBar, option, SC_ScrollBarSlider, widget);
+ if (!(flags & State_Enabled))
+ stateId = SCRBS_DISABLED;
+ else if (scrollbar->activeSubControls & SC_ScrollBarSlider && (scrollbar->state & State_Sunken))
+ stateId = SCRBS_PRESSED;
+ else if (scrollbar->activeSubControls & SC_ScrollBarSlider && (scrollbar->state & State_MouseOver))
+ stateId = SCRBS_HOT;
+ else if (option->state & State_MouseOver)
+ stateId = SCRBS_HOVER;
+ else
+ stateId = SCRBS_NORMAL;
+
+ // Draw handle
+ theme.partId = flags & State_Horizontal ? SBP_THUMBBTNHORZ : SBP_THUMBBTNVERT;
+ theme.stateId = stateId;
+ d->drawBackground(theme);
+ }
+ }
+ }
+ break;
+
+#if QT_CONFIG(spinbox)
+ case CC_SpinBox:
+ if (const QStyleOptionSpinBox *sb = qstyleoption_cast<const QStyleOptionSpinBox *>(option)) {
+ QWindowsThemeData theme(widget, painter, QWindowsVistaStylePrivate::SpinTheme);
+ if (sb->frame && (sub & SC_SpinBoxFrame)) {
+ partId = EP_EDITBORDER_NOSCROLL;
+ if (!(flags & State_Enabled))
+ stateId = ETS_DISABLED;
+ else if (flags & State_MouseOver)
+ stateId = ETS_HOT;
+ else if (flags & State_HasFocus)
+ stateId = ETS_SELECTED;
+ else
+ stateId = ETS_NORMAL;
+
+ QWindowsThemeData ftheme(widget, painter,
+ QWindowsVistaStylePrivate::EditTheme,
+ partId, stateId, r);
+ // The spinbox in Windows QStyle is drawn with frameless QLineEdit inside it
+ // That however breaks with QtQuickControls where this results in transparent
+ // spinbox background, so if there's no "widget" passed (QtQuickControls case),
+ // let ftheme.noContent be false, which fixes the spinbox rendering in QQC
+ ftheme.noContent = (widget != nullptr);
+ d->drawBackground(ftheme);
+ }
+ if (sub & SC_SpinBoxUp) {
+ theme.rect = proxy()->subControlRect(CC_SpinBox, option, SC_SpinBoxUp, widget).adjusted(0, 0, 0, 1);
+ partId = SPNP_UP;
+ if (!(sb->stepEnabled & QAbstractSpinBox::StepUpEnabled) || !(flags & State_Enabled))
+ stateId = UPS_DISABLED;
+ else if (sb->activeSubControls == SC_SpinBoxUp && (sb->state & State_Sunken))
+ stateId = UPS_PRESSED;
+ else if (sb->activeSubControls == SC_SpinBoxUp && (sb->state & State_MouseOver))
+ stateId = UPS_HOT;
+ else
+ stateId = UPS_NORMAL;
+ theme.partId = partId;
+ theme.stateId = stateId;
+ d->drawBackground(theme);
+ }
+ if (sub & SC_SpinBoxDown) {
+ theme.rect = proxy()->subControlRect(CC_SpinBox, option, SC_SpinBoxDown, widget);
+ partId = SPNP_DOWN;
+ if (!(sb->stepEnabled & QAbstractSpinBox::StepDownEnabled) || !(flags & State_Enabled))
+ stateId = DNS_DISABLED;
+ else if (sb->activeSubControls == SC_SpinBoxDown && (sb->state & State_Sunken))
+ stateId = DNS_PRESSED;
+ else if (sb->activeSubControls == SC_SpinBoxDown && (sb->state & State_MouseOver))
+ stateId = DNS_HOT;
+ else
+ stateId = DNS_NORMAL;
+ theme.partId = partId;
+ theme.stateId = stateId;
+ d->drawBackground(theme);
+ }
+ }
+ break;
+#endif // QT_CONFIG(spinbox)
+
+ default:
+ QWindowsStyle::drawComplexControl(control, option, painter, widget);
+ break;
+ }
+}
+
+/*!
+ \internal
+ */
+QSize QWindowsVistaStyle::sizeFromContents(ContentsType type, const QStyleOption *option,
+ const QSize &size, const QWidget *widget) const
+{
+ if (!QWindowsVistaStylePrivate::useVista())
+ return QWindowsStyle::sizeFromContents(type, option, size, widget);
+
+ QSize contentSize(size);
+
+ switch (type) {
+ case CT_LineEdit:
+ case CT_ComboBox: {
+ QWindowsThemeData buttontheme(widget, nullptr, QWindowsVistaStylePrivate::ButtonTheme, BP_PUSHBUTTON, PBS_NORMAL);
+ if (buttontheme.isValid()) {
+ const qreal factor = QWindowsStylePrivate::nativeMetricScaleFactor(widget);
+ const QMarginsF borderSize = buttontheme.margins() * factor;
+ if (!borderSize.isNull()) {
+ const qreal margin = qreal(2) * factor;
+ contentSize.rwidth() += qRound(borderSize.left() + borderSize.right() - margin);
+ contentSize.rheight() += int(borderSize.bottom() + borderSize.top() - margin
+ + qreal(1) / factor - 1);
+ }
+ const int textMargins = 2*(proxy()->pixelMetric(PM_FocusFrameHMargin, option) + 1);
+ contentSize += QSize(qMax(pixelMetric(QStyle::PM_ScrollBarExtent, option, widget)
+ + textMargins, 23), 0); //arrow button
+ }
+ break;
+ }
+
+ case CT_TabWidget:
+ contentSize += QSize(6, 6);
+ break;
+
+ case CT_Menu:
+ contentSize += QSize(1, 0);
+ break;
+
+#if QT_CONFIG(menubar)
+ case CT_MenuBarItem:
+ if (!contentSize.isEmpty())
+ contentSize += QSize(windowsItemHMargin * 5 + 1, 5);
+ break;
+#endif
+
+ case CT_MenuItem: {
+ contentSize = QWindowsStyle::sizeFromContents(type, option, size, widget);
+ QWindowsThemeData theme(widget, nullptr,
+ QWindowsVistaStylePrivate::MenuTheme,
+ MENU_POPUPCHECKBACKGROUND, MBI_HOT);
+ QWindowsThemeData themeSize = theme;
+ themeSize.partId = MENU_POPUPCHECK;
+ themeSize.stateId = 0;
+ const QSizeF size = themeSize.size() * QWindowsStylePrivate::nativeMetricScaleFactor(widget);
+ const QMarginsF margins = themeSize.margins() * QWindowsStylePrivate::nativeMetricScaleFactor(widget);
+ int minimumHeight = qMax(qRound(size.height() + margins.bottom() + margins.top()), contentSize.height());
+ contentSize.rwidth() += qRound(size.width() + margins.left() + margins.right());
+ if (const QStyleOptionMenuItem *menuitem = qstyleoption_cast<const QStyleOptionMenuItem *>(option)) {
+ if (menuitem->menuItemType != QStyleOptionMenuItem::Separator)
+ contentSize.setHeight(minimumHeight);
+ }
+ break;
+ }
+
+ case CT_MdiControls: {
+ contentSize.setHeight(int(QStyleHelper::dpiScaled(19, option)));
+ int width = 54;
+ if (const auto *styleOpt = qstyleoption_cast<const QStyleOptionComplex *>(option)) {
+ width = 0;
+ if (styleOpt->subControls & SC_MdiMinButton)
+ width += 17 + 1;
+ if (styleOpt->subControls & SC_MdiNormalButton)
+ width += 17 + 1;
+ if (styleOpt->subControls & SC_MdiCloseButton)
+ width += 17 + 1;
+ }
+ contentSize.setWidth(int(QStyleHelper::dpiScaled(width, option)));
+ break;
+ }
+
+ case CT_ItemViewItem:
+ contentSize = QWindowsStyle::sizeFromContents(type, option, size, widget);
+ contentSize.rheight() += 2;
+ break;
+
+ case CT_SpinBox: {
+ //Spinbox adds frame twice
+ contentSize = QWindowsStyle::sizeFromContents(type, option, size, widget);
+ int border = proxy()->pixelMetric(PM_SpinBoxFrameWidth, option, widget);
+ contentSize -= QSize(2*border, 2*border);
+ break;
+ }
+
+ case CT_HeaderSection:
+ // When there is a sort indicator it adds to the width but it is shown
+ // above the text natively and not on the side
+ if (QStyleOptionHeader *hdr = qstyleoption_cast<QStyleOptionHeader *>(const_cast<QStyleOption *>(option))) {
+ QStyleOptionHeader::SortIndicator sortInd = hdr->sortIndicator;
+ hdr->sortIndicator = QStyleOptionHeader::None;
+ contentSize = QWindowsStyle::sizeFromContents(type, hdr, size, widget);
+ hdr->sortIndicator = sortInd;
+ }
+ break;
+
+ default:
+ contentSize = QWindowsStyle::sizeFromContents(type, option, size, widget);
+ break;
+ }
+
+ return contentSize;
+}
+
+/*!
+ \internal
+ */
+QRect QWindowsVistaStyle::subElementRect(SubElement element, const QStyleOption *option, const QWidget *widget) const
+{
+ if (!QWindowsVistaStylePrivate::useVista())
+ return QWindowsStyle::subElementRect(element, option, widget);
+
+ QRect rect(option->rect);
+
+ switch (element) {
+ case SE_PushButtonContents:
+ if (const QStyleOptionButton *btn = qstyleoption_cast<const QStyleOptionButton *>(option)) {
+ MARGINS borderSize;
+ const HTHEME theme = OpenThemeData(widget ? QWindowsVistaStylePrivate::winId(widget) : nullptr, L"Button");
+ if (theme) {
+ int stateId = PBS_NORMAL;
+ if (!(option->state & State_Enabled))
+ stateId = PBS_DISABLED;
+ else if (option->state & State_Sunken)
+ stateId = PBS_PRESSED;
+ else if (option->state & State_MouseOver)
+ stateId = PBS_HOT;
+ else if (btn->features & QStyleOptionButton::DefaultButton)
+ stateId = PBS_DEFAULTED;
+
+ int border = proxy()->pixelMetric(PM_DefaultFrameWidth, btn, widget);
+ rect = option->rect.adjusted(border, border, -border, -border);
+
+ if (SUCCEEDED(GetThemeMargins(theme, nullptr, BP_PUSHBUTTON, stateId, TMT_CONTENTMARGINS, nullptr, &borderSize))) {
+ rect.adjust(borderSize.cxLeftWidth, borderSize.cyTopHeight,
+ -borderSize.cxRightWidth, -borderSize.cyBottomHeight);
+ rect = visualRect(option->direction, option->rect, rect);
+ }
+ }
+ }
+ break;
+
+ case SE_DockWidgetCloseButton:
+ case SE_DockWidgetFloatButton:
+ rect = QWindowsStyle::subElementRect(element, option, widget);
+ return rect.translated(0, 1);
+
+ case SE_TabWidgetTabContents:
+ rect = QWindowsStyle::subElementRect(element, option, widget);
+ if (qstyleoption_cast<const QStyleOptionTabWidgetFrame *>(option)) {
+ rect = QWindowsStyle::subElementRect(element, option, widget);
+ if (const QTabWidget *tabWidget = qobject_cast<const QTabWidget *>(widget)) {
+ if (tabWidget->documentMode())
+ break;
+ rect.adjust(0, 0, -2, -2);
+ }
+ }
+ break;
+
+ case SE_TabWidgetTabBar: {
+ rect = QWindowsStyle::subElementRect(element, option, widget);
+ const auto *twfOption = qstyleoption_cast<const QStyleOptionTabWidgetFrame *>(option);
+ if (twfOption && twfOption->direction == Qt::RightToLeft
+ && (twfOption->shape == QTabBar::RoundedNorth
+ || twfOption->shape == QTabBar::RoundedSouth))
+ {
+ QStyleOptionTab otherOption;
+ otherOption.shape = (twfOption->shape == QTabBar::RoundedNorth
+ ? QTabBar::RoundedEast : QTabBar::RoundedSouth);
+ int overlap = proxy()->pixelMetric(PM_TabBarBaseOverlap, &otherOption, widget);
+ int borderThickness = proxy()->pixelMetric(PM_DefaultFrameWidth, option, widget);
+ rect.adjust(-overlap + borderThickness, 0, -overlap + borderThickness, 0);
+ }
+ break;
+ }
+
+ case SE_HeaderArrow: {
+ rect = QWindowsStyle::subElementRect(element, option, widget);
+ QRect r = rect;
+ int h = option->rect.height();
+ int w = option->rect.width();
+ int x = option->rect.x();
+ int y = option->rect.y();
+ int margin = proxy()->pixelMetric(QStyle::PM_HeaderMargin, option, widget);
+
+ QWindowsThemeData theme(widget, nullptr,
+ QWindowsVistaStylePrivate::HeaderTheme,
+ HP_HEADERSORTARROW, HSAS_SORTEDDOWN, option->rect);
+
+ int arrowWidth = 13;
+ int arrowHeight = 5;
+ if (theme.isValid()) {
+ const QSizeF size = theme.size() * QWindowsStylePrivate::nativeMetricScaleFactor(widget);
+ if (!size.isEmpty()) {
+ arrowWidth = qRound(size.width());
+ arrowHeight = qRound(size.height());
+ }
+ }
+ if (option->state & State_Horizontal) {
+ r.setRect(x + w/2 - arrowWidth/2, y , arrowWidth, arrowHeight);
+ } else {
+ int vert_size = w / 2;
+ r.setRect(x + 5, y + h - margin * 2 - vert_size,
+ w - margin * 2 - 5, vert_size);
+ }
+ rect = visualRect(option->direction, option->rect, r);
+ break;
+ }
+
+ case SE_HeaderLabel: {
+ rect = QWindowsStyle::subElementRect(element, option, widget);
+ int margin = proxy()->pixelMetric(QStyle::PM_HeaderMargin, option, widget);
+ QRect r = option->rect;
+ r.setRect(option->rect.x() + margin, option->rect.y() + margin,
+ option->rect.width() - margin * 2, option->rect.height() - margin * 2);
+ if (const QStyleOptionHeader *header = qstyleoption_cast<const QStyleOptionHeader *>(option)) {
+ // Subtract width needed for arrow, if there is one
+ if (header->sortIndicator != QStyleOptionHeader::None) {
+ if (!(option->state & State_Horizontal)) //horizontal arrows are positioned on top
+ r.setHeight(r.height() - (option->rect.width() / 2) - (margin * 2));
+ }
+ }
+ rect = visualRect(option->direction, option->rect, r);
+ break;
+ }
+
+ case SE_ProgressBarContents:
+ rect = QCommonStyle::subElementRect(SE_ProgressBarGroove, option, widget);
+ break;
+
+ case SE_ItemViewItemDecoration:
+ rect = QWindowsStyle::subElementRect(element, option, widget);
+ if (qstyleoption_cast<const QStyleOptionViewItem *>(option))
+ rect.adjust(-2, 0, 2, 0);
+ break;
+
+ case SE_ItemViewItemFocusRect:
+ rect = QWindowsStyle::subElementRect(element, option, widget);
+ if (const QStyleOptionViewItem *vopt = qstyleoption_cast<const QStyleOptionViewItem *>(option)) {
+ QRect textRect = subElementRect(QStyle::SE_ItemViewItemText, option, widget);
+ QRect displayRect = subElementRect(QStyle::SE_ItemViewItemDecoration, option, widget);
+ if (!vopt->icon.isNull())
+ rect = textRect.united(displayRect);
+ else
+ rect = textRect;
+ rect = rect.adjusted(1, 0, -1, 0);
+ }
+ break;
+
+ default:
+ rect = QWindowsStyle::subElementRect(element, option, widget);
+ break;
+ }
+
+ return rect;
+}
+
+/*!
+ \internal
+ */
+QRect QWindowsVistaStyle::subControlRect(ComplexControl control, const QStyleOptionComplex *option,
+ SubControl subControl, const QWidget *widget) const
+{
+ if (!QWindowsVistaStylePrivate::useVista())
+ return QWindowsStyle::subControlRect(control, option, subControl, widget);
+
+ QRect rect;
+
+ switch (control) {
+#if QT_CONFIG(combobox)
+ case CC_ComboBox:
+ if (const auto *cb = qstyleoption_cast<const QStyleOptionComboBox *>(option)) {
+ const int x = cb->rect.x(), y = cb->rect.y(), wi = cb->rect.width(), he = cb->rect.height();
+ const int margin = cb->frame ? 3 : 0;
+ const int bmarg = cb->frame ? 2 : 0;
+ const int arrowWidth = qRound(QStyleHelper::dpiScaled(16, option));
+ const int arrowButtonWidth = bmarg + arrowWidth;
+ const int xpos = x + wi - arrowButtonWidth;
+
+ switch (subControl) {
+ case SC_ComboBoxFrame:
+ case SC_ComboBoxListBoxPopup:
+ rect = cb->rect;
+ break;
+
+ case SC_ComboBoxArrow: {
+ rect.setRect(xpos, y , arrowButtonWidth, he);
+ }
+ break;
+
+ case SC_ComboBoxEditField: {
+ rect.setRect(x + margin, y + margin, wi - 2 * margin - arrowWidth, he - 2 * margin);
+ }
+ break;
+
+ default:
+ break;
+ }
+ }
+ break;
+#endif // QT_CONFIG(combobox)
+
+ case CC_TitleBar:
+ if (const QStyleOptionTitleBar *tb = qstyleoption_cast<const QStyleOptionTitleBar *>(option)) {
+ if (!buttonVisible(subControl, tb))
+ return rect;
+ const qreal factor = QWindowsStylePrivate::nativeMetricScaleFactor(widget);
+ const bool isToolTitle = false;
+ const int height = tb->rect.height();
+ const int width = tb->rect.width();
+
+ const int buttonMargin = int(QStyleHelper::dpiScaled(4, option));
+ int buttonHeight = qRound(qreal(GetSystemMetrics(SM_CYSIZE)) * factor)
+ - buttonMargin;
+ const int buttonWidth =
+ qRound(qreal(GetSystemMetrics(SM_CXSIZE)) * factor - QStyleHelper::dpiScaled(4, option));
+
+ const int frameWidth = proxy()->pixelMetric(PM_MdiSubWindowFrameWidth, option, widget);
+ const bool sysmenuHint = (tb->titleBarFlags & Qt::WindowSystemMenuHint) != 0;
+ const bool minimizeHint = (tb->titleBarFlags & Qt::WindowMinimizeButtonHint) != 0;
+ const bool maximizeHint = (tb->titleBarFlags & Qt::WindowMaximizeButtonHint) != 0;
+ const bool contextHint = (tb->titleBarFlags & Qt::WindowContextHelpButtonHint) != 0;
+ const bool shadeHint = (tb->titleBarFlags & Qt::WindowShadeButtonHint) != 0;
+
+ bool isMinimized = tb->titleBarState & Qt::WindowMinimized;
+ bool isMaximized = tb->titleBarState & Qt::WindowMaximized;
+ int offset = 0;
+ const int delta = buttonWidth + 2;
+ int controlTop = option->rect.bottom() - buttonHeight - 2;
+
+ switch (subControl) {
+ case SC_TitleBarLabel: {
+ rect = QRect(frameWidth, 0, width - (buttonWidth + frameWidth + 10), height);
+ if (isToolTitle) {
+ if (sysmenuHint) {
+ rect.adjust(0, 0, int(-buttonWidth - 3 * factor), 0);
+ }
+ if (minimizeHint || maximizeHint)
+ rect.adjust(0, 0, int(-buttonWidth - 2 * factor), 0);
+ } else {
+ if (sysmenuHint) {
+ const int leftOffset = int(height - 8 * factor);
+ rect.adjust(leftOffset, 0, 0, int(4 * factor));
+ }
+ if (minimizeHint)
+ rect.adjust(0, 0, int(-buttonWidth - 2 * factor), 0);
+ if (maximizeHint)
+ rect.adjust(0, 0, int(-buttonWidth - 2 * factor), 0);
+ if (contextHint)
+ rect.adjust(0, 0, int(-buttonWidth - 2 * factor), 0);
+ if (shadeHint)
+ rect.adjust(0, 0, int(-buttonWidth - 2 * factor), 0);
+ }
+ rect.translate(0, int(2 * factor));
+ }
+ break;
+
+ case SC_TitleBarContextHelpButton:
+ if (tb->titleBarFlags & Qt::WindowContextHelpButtonHint)
+ offset += delta;
+ Q_FALLTHROUGH();
+ case SC_TitleBarMinButton:
+ if (!isMinimized && (tb->titleBarFlags & Qt::WindowMinimizeButtonHint))
+ offset += delta;
+ else if (subControl == SC_TitleBarMinButton)
+ break;
+ Q_FALLTHROUGH();
+ case SC_TitleBarNormalButton:
+ if (isMinimized && (tb->titleBarFlags & Qt::WindowMinimizeButtonHint))
+ offset += delta;
+ else if (isMaximized && (tb->titleBarFlags & Qt::WindowMaximizeButtonHint))
+ offset += delta;
+ else if (subControl == SC_TitleBarNormalButton)
+ break;
+ Q_FALLTHROUGH();
+ case SC_TitleBarMaxButton:
+ if (!isMaximized && (tb->titleBarFlags & Qt::WindowMaximizeButtonHint))
+ offset += delta;
+ else if (subControl == SC_TitleBarMaxButton)
+ break;
+ Q_FALLTHROUGH();
+ case SC_TitleBarShadeButton:
+ if (!isMinimized && (tb->titleBarFlags & Qt::WindowShadeButtonHint))
+ offset += delta;
+ else if (subControl == SC_TitleBarShadeButton)
+ break;
+ Q_FALLTHROUGH();
+ case SC_TitleBarUnshadeButton:
+ if (isMinimized && (tb->titleBarFlags & Qt::WindowShadeButtonHint))
+ offset += delta;
+ else if (subControl == SC_TitleBarUnshadeButton)
+ break;
+ Q_FALLTHROUGH();
+ case SC_TitleBarCloseButton:
+ if (tb->titleBarFlags & Qt::WindowSystemMenuHint)
+ offset += delta;
+ else if (subControl == SC_TitleBarCloseButton)
+ break;
+
+ rect.setRect(width - offset - controlTop + 1, controlTop,
+ buttonWidth, buttonHeight);
+ break;
+
+ case SC_TitleBarSysMenu: {
+ const int controlTop = int(6 * factor);
+ const int controlHeight = int(height - controlTop - 3 * factor);
+ int iconExtent = proxy()->pixelMetric(PM_SmallIconSize, option);
+ QSize iconSize = tb->icon.actualSize(QSize(iconExtent, iconExtent));
+ if (tb->icon.isNull())
+ iconSize = QSize(controlHeight, controlHeight);
+ int hPad = (controlHeight - iconSize.height())/2;
+ int vPad = (controlHeight - iconSize.width())/2;
+ rect = QRect(frameWidth + hPad, controlTop + vPad, iconSize.width(), iconSize.height());
+ rect.translate(0, int(3 * factor));
+ }
+ break;
+
+ default:
+ break;
+ }
+ }
+ break;
+
+#if QT_CONFIG(mdiarea)
+ case CC_MdiControls: {
+ int numSubControls = 0;
+ if (option->subControls & SC_MdiCloseButton)
+ ++numSubControls;
+ if (option->subControls & SC_MdiMinButton)
+ ++numSubControls;
+ if (option->subControls & SC_MdiNormalButton)
+ ++numSubControls;
+ if (numSubControls == 0)
+ break;
+
+ int buttonWidth = option->rect.width() / numSubControls;
+ int offset = 0;
+
+ switch (subControl) {
+ case SC_MdiCloseButton:
+ // Only one sub control, no offset needed.
+ if (numSubControls == 1)
+ break;
+ offset += buttonWidth;
+ Q_FALLTHROUGH();
+ case SC_MdiNormalButton:
+ // No offset needed if
+ // 1) There's only one sub control
+ // 2) We have a close button and a normal button (offset already added in SC_MdiClose)
+ if (numSubControls == 1 || (numSubControls == 2 && !(option->subControls & SC_MdiMinButton)))
+ break;
+ if (option->subControls & SC_MdiNormalButton)
+ offset += buttonWidth;
+ break;
+ default:
+ break;
+ }
+
+ rect = QRect(offset, 0, buttonWidth, option->rect.height());
+ break;
+ }
+#endif // QT_CONFIG(mdiarea)
+
+ default:
+ return QWindowsStyle::subControlRect(control, option, subControl, widget);
+ }
+
+ return visualRect(option->direction, option->rect, rect);
+}
+
+/*!
+ \internal
+ */
+QStyle::SubControl QWindowsVistaStyle::hitTestComplexControl(ComplexControl control, const QStyleOptionComplex *option,
+ const QPoint &pos, const QWidget *widget) const
+{
+ return QWindowsStyle::hitTestComplexControl(control, option, pos, widget);
+}
+
+/*!
+ \internal
+ */
+int QWindowsVistaStyle::pixelMetric(PixelMetric metric, const QStyleOption *option, const QWidget *widget) const
+{
+ if (!QWindowsVistaStylePrivate::useVista())
+ return QWindowsStyle::pixelMetric(metric, option, widget);
+
+ int ret = QWindowsVistaStylePrivate::fixedPixelMetric(metric);
+ if (ret != QWindowsStylePrivate::InvalidMetric)
+ return int(QStyleHelper::dpiScaled(ret, option));
+
+ int res = QWindowsVistaStylePrivate::pixelMetricFromSystemDp(metric, option, widget);
+ if (res != QWindowsStylePrivate::InvalidMetric)
+ return qRound(qreal(res) * QWindowsStylePrivate::nativeMetricScaleFactor(widget));
+
+ res = 0;
+
+ switch (metric) {
+ case PM_MenuBarPanelWidth:
+ case PM_ButtonDefaultIndicator:
+ res = 0;
+ break;
+
+ case PM_DefaultFrameWidth:
+ res = qobject_cast<const QListView*>(widget) ? 2 : 1;
+ break;
+ case PM_MenuPanelWidth:
+ case PM_SpinBoxFrameWidth:
+ res = 1;
+ break;
+
+ case PM_TabBarTabOverlap:
+ case PM_MenuHMargin:
+ case PM_MenuVMargin:
+ res = 2;
+ break;
+
+ case PM_TabBarBaseOverlap:
+ if (const auto *tab = qstyleoption_cast<const QStyleOptionTab *>(option)) {
+ switch (tab->shape) {
+ case QTabBar::RoundedNorth:
+ case QTabBar::TriangularNorth:
+ case QTabBar::RoundedWest:
+ case QTabBar::TriangularWest:
+ res = 1;
+ break;
+ case QTabBar::RoundedSouth:
+ case QTabBar::TriangularSouth:
+ res = 2;
+ break;
+ case QTabBar::RoundedEast:
+ case QTabBar::TriangularEast:
+ res = 3;
+ break;
+ }
+ }
+ break;
+
+ case PM_SplitterWidth:
+ res = QStyleHelper::dpiScaled(5., option);
+ break;
+
+ case PM_MdiSubWindowMinimizedWidth:
+ res = 160;
+ break;
+
+#if QT_CONFIG(toolbar)
+ case PM_ToolBarHandleExtent:
+ res = int(QStyleHelper::dpiScaled(8., option));
+ break;
+
+#endif // QT_CONFIG(toolbar)
+ case PM_DockWidgetSeparatorExtent:
+ case PM_DockWidgetTitleMargin:
+ res = int(QStyleHelper::dpiScaled(4., option));
+ break;
+
+ case PM_ButtonShiftHorizontal:
+ case PM_ButtonShiftVertical:
+ res = qstyleoption_cast<const QStyleOptionToolButton *>(option) ? 1 : 0;
+ break;
+
+ default:
+ res = QWindowsStyle::pixelMetric(metric, option, widget);
+ }
+
+ return res;
+}
+
+/*!
+ \internal
+ */
+void QWindowsVistaStyle::polish(QWidget *widget)
+{
+ QWindowsStyle::polish(widget);
+ if (!QWindowsVistaStylePrivate::useVista())
+ return;
+
+ if (false
+#if QT_CONFIG(abstractbutton)
+ || qobject_cast<QAbstractButton*>(widget)
+#endif // QT_CONFIG(abstractbutton)
+ || qobject_cast<QToolButton*>(widget)
+ || qobject_cast<QTabBar*>(widget)
+#if QT_CONFIG(combobox)
+ || qobject_cast<QComboBox*>(widget)
+#endif // QT_CONFIG(combobox)
+ || qobject_cast<QScrollBar*>(widget)
+ || qobject_cast<QSlider*>(widget)
+ || qobject_cast<QHeaderView*>(widget)
+#if QT_CONFIG(spinbox)
+ || qobject_cast<QAbstractSpinBox*>(widget)
+ || qobject_cast<QSpinBox*>(widget)
+#endif // QT_CONFIG(spinbox)
+ ) {
+ widget->setAttribute(Qt::WA_Hover);
+ }
+
+#if QT_CONFIG(rubberband)
+ if (qobject_cast<QRubberBand*>(widget))
+ widget->setWindowOpacity(0.6);
+#endif
+
+#if QT_CONFIG(lineedit)
+ if (qobject_cast<QLineEdit*>(widget))
+ widget->setAttribute(Qt::WA_Hover);
+ else
+#endif // QT_CONFIG(lineedit)
+ if (qobject_cast<QGroupBox*>(widget))
+ widget->setAttribute(Qt::WA_Hover);
+#if QT_CONFIG(commandlinkbutton)
+ else if (qobject_cast<QCommandLinkButton*>(widget)) {
+ widget->setProperty("_qt_usingVistaStyle", true);
+ QFont buttonFont = widget->font();
+ buttonFont.setFamilies(QStringList{QLatin1String("Segoe UI")});
+ widget->setFont(buttonFont);
+ QPalette pal = widget->palette();
+ pal.setColor(QPalette::Active, QPalette::ButtonText, QColor(21, 28, 85));
+ pal.setColor(QPalette::Active, QPalette::BrightText, QColor(7, 64, 229));
+ widget->setPalette(pal);
+ }
+#endif // QT_CONFIG(commandlinkbutton)
+ else if (widget->inherits("QTipLabel")) {
+ //note that since tooltips are not reused
+ //we do not have to care about unpolishing
+ widget->setContentsMargins(3, 0, 4, 0);
+ COLORREF bgRef;
+ HTHEME theme = OpenThemeData(widget ? QWindowsVistaStylePrivate::winId(widget) : nullptr, L"TOOLTIP");
+ if (theme && SUCCEEDED(GetThemeColor(theme, TTP_STANDARD, TTSS_NORMAL, TMT_TEXTCOLOR, &bgRef))) {
+ QColor textColor = QColor::fromRgb(bgRef);
+ QPalette pal;
+ pal.setColor(QPalette::All, QPalette::ToolTipText, textColor);
+ pal.setResolveMask(0);
+ widget->setPalette(pal);
+ }
+ } else if (qobject_cast<QMessageBox *> (widget)) {
+ widget->setAttribute(Qt::WA_StyledBackground);
+#if QT_CONFIG(dialogbuttonbox)
+ QDialogButtonBox *buttonBox = widget->findChild<QDialogButtonBox *>(QLatin1String("qt_msgbox_buttonbox"));
+ if (buttonBox)
+ buttonBox->setContentsMargins(0, 9, 0, 0);
+#endif
+ }
+#if QT_CONFIG(inputdialog)
+ else if (qobject_cast<QInputDialog *> (widget)) {
+ widget->setAttribute(Qt::WA_StyledBackground);
+#if QT_CONFIG(dialogbuttonbox)
+ QDialogButtonBox *buttonBox = widget->findChild<QDialogButtonBox *>(QLatin1String("qt_inputdlg_buttonbox"));
+ if (buttonBox)
+ buttonBox->setContentsMargins(0, 9, 0, 0);
+#endif
+ }
+#endif // QT_CONFIG(inputdialog)
+ else if (QTreeView *tree = qobject_cast<QTreeView *> (widget)) {
+ tree->viewport()->setAttribute(Qt::WA_Hover);
+ }
+ else if (QListView *list = qobject_cast<QListView *> (widget)) {
+ list->viewport()->setAttribute(Qt::WA_Hover);
+ }
+}
+
+/*!
+ \internal
+ */
+void QWindowsVistaStyle::unpolish(QWidget *widget)
+{
+ Q_D(QWindowsVistaStyle);
+
+#if QT_CONFIG(rubberband)
+ if (qobject_cast<QRubberBand*>(widget))
+ widget->setWindowOpacity(1.0);
+#endif
+
+ // Unpolish of widgets is the first thing that
+ // happens when a theme changes, or the theme
+ // engine is turned off. So we detect it here.
+ bool oldState = QWindowsVistaStylePrivate::useVista();
+ bool newState = QWindowsVistaStylePrivate::useVista(true);
+ if ((oldState != newState) && newState) {
+ d->cleanup(true);
+ d->init(true);
+ } else {
+ // Cleanup handle map, if just changing style,
+ // or turning it on. In both cases the values
+ // already in the map might be old (other style).
+ d->cleanupHandleMap();
+ }
+ if (false
+ #if QT_CONFIG(abstractbutton)
+ || qobject_cast<QAbstractButton*>(widget)
+ #endif
+ || qobject_cast<QToolButton*>(widget)
+ || qobject_cast<QTabBar*>(widget)
+ #if QT_CONFIG(combobox)
+ || qobject_cast<QComboBox*>(widget)
+ #endif // QT_CONFIG(combobox)
+ || qobject_cast<QScrollBar*>(widget)
+ || qobject_cast<QSlider*>(widget)
+ || qobject_cast<QHeaderView*>(widget)
+ #if QT_CONFIG(spinbox)
+ || qobject_cast<QAbstractSpinBox*>(widget)
+ || qobject_cast<QSpinBox*>(widget)
+ #endif // QT_CONFIG(spinbox)
+ ) {
+ widget->setAttribute(Qt::WA_Hover, false);
+ }
+
+ QWindowsStyle::unpolish(widget);
+
+ d->stopAnimation(widget);
+
+#if QT_CONFIG(lineedit)
+ if (qobject_cast<QLineEdit*>(widget))
+ widget->setAttribute(Qt::WA_Hover, false);
+ else {
+#endif // QT_CONFIG(lineedit)
+ if (qobject_cast<QGroupBox*>(widget))
+ widget->setAttribute(Qt::WA_Hover, false);
+ else if (qobject_cast<QMessageBox *> (widget)) {
+ widget->setAttribute(Qt::WA_StyledBackground, false);
+#if QT_CONFIG(dialogbuttonbox)
+ QDialogButtonBox *buttonBox = widget->findChild<QDialogButtonBox *>(QLatin1String("qt_msgbox_buttonbox"));
+ if (buttonBox)
+ buttonBox->setContentsMargins(0, 0, 0, 0);
+#endif
+ }
+#if QT_CONFIG(inputdialog)
+ else if (qobject_cast<QInputDialog *> (widget)) {
+ widget->setAttribute(Qt::WA_StyledBackground, false);
+#if QT_CONFIG(dialogbuttonbox)
+ QDialogButtonBox *buttonBox = widget->findChild<QDialogButtonBox *>(QLatin1String("qt_inputdlg_buttonbox"));
+ if (buttonBox)
+ buttonBox->setContentsMargins(0, 0, 0, 0);
+#endif
+ }
+#endif // QT_CONFIG(inputdialog)
+ else if (QTreeView *tree = qobject_cast<QTreeView *> (widget)) {
+ tree->viewport()->setAttribute(Qt::WA_Hover, false);
+ }
+#if QT_CONFIG(commandlinkbutton)
+ else if (qobject_cast<QCommandLinkButton*>(widget)) {
+ QFont font = QApplication::font("QCommandLinkButton");
+ QFont widgetFont = widget->font();
+ widgetFont.setFamilies(font.families()); //Only family set by polish
+ widget->setFont(widgetFont);
+ }
+#endif // QT_CONFIG(commandlinkbutton)
+ }
+}
+
+/*!
+ \internal
+ */
+void QWindowsVistaStyle::polish(QPalette &pal)
+{
+ Q_D(QWindowsVistaStyle);
+
+ if (QGuiApplicationPrivate::colorScheme() == Qt::ColorScheme::Dark) {
+ // System runs in dark mode, but the Vista style cannot use a dark palette.
+ // Overwrite with the light system palette.
+ using QWindowsApplication = QNativeInterface::Private::QWindowsApplication;
+ if (auto nativeWindowsApp = dynamic_cast<QWindowsApplication *>(QGuiApplicationPrivate::platformIntegration()))
+ nativeWindowsApp->populateLightSystemPalette(pal);
+ }
+
+ QPixmapCache::clear();
+ d->alphaCache.clear();
+ d->hasInitColors = false;
+
+ if (!d->hasInitColors) {
+ // Get text color for group box labels
+ QWindowsThemeData theme(nullptr, nullptr, QWindowsVistaStylePrivate::ButtonTheme, 0, 0);
+ COLORREF cref;
+ GetThemeColor(theme.handle(), BP_GROUPBOX, GBS_NORMAL, TMT_TEXTCOLOR, &cref);
+ d->groupBoxTextColor = qRgb(GetRValue(cref), GetGValue(cref), GetBValue(cref));
+ GetThemeColor(theme.handle(), BP_GROUPBOX, GBS_DISABLED, TMT_TEXTCOLOR, &cref);
+ d->groupBoxTextColorDisabled = qRgb(GetRValue(cref), GetGValue(cref), GetBValue(cref));
+ // Where does this color come from?
+ //GetThemeColor(theme.handle(), TKP_TICS, TSS_NORMAL, TMT_COLOR, &cref);
+ d->sliderTickColor = qRgb(165, 162, 148);
+ d->hasInitColors = true;
+ }
+
+ QWindowsStyle::polish(pal);
+ pal.setBrush(QPalette::AlternateBase, pal.base().color().darker(104));
+}
+
+/*!
+ \internal
+ */
+void QWindowsVistaStyle::polish(QApplication *app)
+{
+ // Override windows theme palettes to light
+ if (qApp->styleHints()->colorScheme() == Qt::ColorScheme::Dark) {
+ static const char* themedWidgets[] = {
+ "QToolButton",
+ "QAbstractButton",
+ "QCheckBox",
+ "QRadioButton",
+ "QHeaderView",
+ "QAbstractItemView",
+ "QMessageBoxLabel",
+ "QTabBar",
+ "QLabel",
+ "QGroupBox",
+ "QMenu",
+ "QMenuBar",
+ "QTextEdit",
+ "QTextControl",
+ "QLineEdit"
+ };
+ for (const auto& themedWidget : std::as_const(themedWidgets)) {
+ auto defaultResolveMask = QApplication::palette().resolveMask();
+ auto widgetResolveMask = QApplication::palette(themedWidget).resolveMask();
+ if (widgetResolveMask != defaultResolveMask)
+ QApplication::setPalette(QApplication::palette(), themedWidget);
+ }
+ }
+
+ QWindowsStyle::polish(app);
+}
+
+/*!
+ \internal
+ */
+QPixmap QWindowsVistaStyle::standardPixmap(StandardPixmap standardPixmap, const QStyleOption *option,
+ const QWidget *widget) const
+{
+ if (!QWindowsVistaStylePrivate::useVista()) {
+ return QWindowsStyle::standardPixmap(standardPixmap, option, widget);
+ }
+
+ switch (standardPixmap) {
+ case SP_TitleBarMaxButton:
+ case SP_TitleBarCloseButton:
+ if (qstyleoption_cast<const QStyleOptionDockWidget *>(option)) {
+ if (widget && widget->isWindow()) {
+ QWindowsThemeData theme(widget, nullptr, QWindowsVistaStylePrivate::WindowTheme, WP_SMALLCLOSEBUTTON, CBS_NORMAL);
+ if (theme.isValid()) {
+ const QSize size = (theme.size() * QWindowsStylePrivate::nativeMetricScaleFactor(widget)).toSize();
+ return QIcon(QWindowsStyle::standardPixmap(standardPixmap, option, widget)).pixmap(size);
+ }
+ }
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ return QWindowsStyle::standardPixmap(standardPixmap, option, widget);
+}
+
+/*!
+\reimp
+*/
+QIcon QWindowsVistaStyle::standardIcon(StandardPixmap standardIcon,
+ const QStyleOption *option,
+ const QWidget *widget) const
+{
+ if (!QWindowsVistaStylePrivate::useVista()) {
+ return QWindowsStyle::standardIcon(standardIcon, option, widget);
+ }
+
+ auto *d = const_cast<QWindowsVistaStylePrivate*>(d_func());
+
+ switch (standardIcon) {
+ case SP_TitleBarMaxButton:
+ if (qstyleoption_cast<const QStyleOptionDockWidget *>(option)) {
+ if (d->dockFloat.isNull()) {
+ QWindowsThemeData themeSize(nullptr, nullptr, QWindowsVistaStylePrivate::WindowTheme,
+ WP_SMALLCLOSEBUTTON, CBS_NORMAL);
+ QWindowsThemeData theme(nullptr, nullptr, QWindowsVistaStylePrivate::WindowTheme,
+ WP_MAXBUTTON, MAXBS_NORMAL);
+ if (theme.isValid()) {
+ const QSize size = (themeSize.size() * QWindowsStylePrivate::nativeMetricScaleFactor(widget)).toSize();
+ QPixmap pm(size);
+ pm.fill(Qt::transparent);
+ QPainter p(&pm);
+ theme.painter = &p;
+ theme.rect = QRect(QPoint(0, 0), size);
+ d->drawBackground(theme);
+ d->dockFloat.addPixmap(pm, QIcon::Normal, QIcon::Off); // Normal
+ pm.fill(Qt::transparent);
+ theme.stateId = MAXBS_PUSHED;
+ d->drawBackground(theme);
+ d->dockFloat.addPixmap(pm, QIcon::Normal, QIcon::On); // Pressed
+ pm.fill(Qt::transparent);
+ theme.stateId = MAXBS_HOT;
+ d->drawBackground(theme);
+ d->dockFloat.addPixmap(pm, QIcon::Active, QIcon::Off); // Hover
+ pm.fill(Qt::transparent);
+ theme.stateId = MAXBS_INACTIVE;
+ d->drawBackground(theme);
+ d->dockFloat.addPixmap(pm, QIcon::Disabled, QIcon::Off); // Disabled
+ }
+ }
+ if (widget && widget->isWindow())
+ return d->dockFloat;
+ }
+ break;
+
+ case SP_TitleBarCloseButton:
+ if (qstyleoption_cast<const QStyleOptionDockWidget *>(option)) {
+ if (d->dockClose.isNull()) {
+ QWindowsThemeData theme(nullptr, nullptr, QWindowsVistaStylePrivate::WindowTheme,
+ WP_SMALLCLOSEBUTTON, CBS_NORMAL);
+ if (theme.isValid()) {
+ const QSize size = (theme.size() * QWindowsStylePrivate::nativeMetricScaleFactor(widget)).toSize();
+ QPixmap pm(size);
+ pm.fill(Qt::transparent);
+ QPainter p(&pm);
+ theme.painter = &p;
+ theme.partId = WP_CLOSEBUTTON; // ####
+ theme.rect = QRect(QPoint(0, 0), size);
+ d->drawBackground(theme);
+ d->dockClose.addPixmap(pm, QIcon::Normal, QIcon::Off); // Normal
+ pm.fill(Qt::transparent);
+ theme.stateId = CBS_PUSHED;
+ d->drawBackground(theme);
+ d->dockClose.addPixmap(pm, QIcon::Normal, QIcon::On); // Pressed
+ pm.fill(Qt::transparent);
+ theme.stateId = CBS_HOT;
+ d->drawBackground(theme);
+ d->dockClose.addPixmap(pm, QIcon::Active, QIcon::Off); // Hover
+ pm.fill(Qt::transparent);
+ theme.stateId = CBS_INACTIVE;
+ d->drawBackground(theme);
+ d->dockClose.addPixmap(pm, QIcon::Disabled, QIcon::Off); // Disabled
+ }
+ }
+ if (widget && widget->isWindow())
+ return d->dockClose;
+ }
+ break;
+
+ case SP_TitleBarNormalButton:
+ if (qstyleoption_cast<const QStyleOptionDockWidget *>(option)) {
+ if (d->dockFloat.isNull()) {
+ QWindowsThemeData themeSize(nullptr, nullptr, QWindowsVistaStylePrivate::WindowTheme,
+ WP_SMALLCLOSEBUTTON, CBS_NORMAL);
+ QWindowsThemeData theme(nullptr, nullptr, QWindowsVistaStylePrivate::WindowTheme,
+ WP_RESTOREBUTTON, RBS_NORMAL);
+ if (theme.isValid()) {
+ const QSize size = (themeSize.size() * QWindowsStylePrivate::nativeMetricScaleFactor(widget)).toSize();
+ QPixmap pm(size);
+ pm.fill(Qt::transparent);
+ QPainter p(&pm);
+ theme.painter = &p;
+ theme.rect = QRect(QPoint(0, 0), size);
+ d->drawBackground(theme);
+ d->dockFloat.addPixmap(pm, QIcon::Normal, QIcon::Off); // Normal
+ pm.fill(Qt::transparent);
+ theme.stateId = RBS_PUSHED;
+ d->drawBackground(theme);
+ d->dockFloat.addPixmap(pm, QIcon::Normal, QIcon::On); // Pressed
+ pm.fill(Qt::transparent);
+ theme.stateId = RBS_HOT;
+ d->drawBackground(theme);
+ d->dockFloat.addPixmap(pm, QIcon::Active, QIcon::Off); // Hover
+ pm.fill(Qt::transparent);
+ theme.stateId = RBS_INACTIVE;
+ d->drawBackground(theme);
+ d->dockFloat.addPixmap(pm, QIcon::Disabled, QIcon::Off); // Disabled
+ }
+ }
+ if (widget && widget->isWindow())
+ return d->dockFloat;
+ }
+ break;
+
+ case SP_CommandLink: {
+ QWindowsThemeData theme(nullptr, nullptr, QWindowsVistaStylePrivate::ButtonTheme,
+ BP_COMMANDLINKGLYPH, CMDLGS_NORMAL);
+ if (theme.isValid()) {
+ const QSize size = theme.size().toSize();
+ QIcon linkGlyph;
+ QPixmap pm(size);
+ pm.fill(Qt::transparent);
+ QPainter p(&pm);
+ theme.painter = &p;
+ theme.rect = QRect(QPoint(0, 0), size);
+ d->drawBackground(theme);
+ linkGlyph.addPixmap(pm, QIcon::Normal, QIcon::Off); // Normal
+ pm.fill(Qt::transparent);
+
+ theme.stateId = CMDLGS_PRESSED;
+ d->drawBackground(theme);
+ linkGlyph.addPixmap(pm, QIcon::Normal, QIcon::On); // Pressed
+ pm.fill(Qt::transparent);
+
+ theme.stateId = CMDLGS_HOT;
+ d->drawBackground(theme);
+ linkGlyph.addPixmap(pm, QIcon::Active, QIcon::Off); // Hover
+ pm.fill(Qt::transparent);
+
+ theme.stateId = CMDLGS_DISABLED;
+ d->drawBackground(theme);
+ linkGlyph.addPixmap(pm, QIcon::Disabled, QIcon::Off); // Disabled
+ return linkGlyph;
+ }
+ break;
+ }
+
+ default:
+ break;
+ }
+
+ return QWindowsStyle::standardIcon(standardIcon, option, widget);
+}
+
+QT_END_NAMESPACE
diff --git a/src/plugins/styles/windowsvista/qwindowsvistastyle_p.h b/src/plugins/styles/modernwindows/qwindowsvistastyle_p.h
index 9d31d5dfee..ff5300beca 100644
--- a/src/plugins/styles/windowsvista/qwindowsvistastyle_p.h
+++ b/src/plugins/styles/modernwindows/qwindowsvistastyle_p.h
@@ -16,12 +16,12 @@
//
#include <QtWidgets/private/qtwidgetsglobal_p.h>
-#include "qwindowsxpstyle_p.h"
+#include <QtWidgets/private/qwindowsstyle_p.h>
QT_BEGIN_NAMESPACE
class QWindowsVistaStylePrivate;
-class QWindowsVistaStyle : public QWindowsXPStyle
+class QWindowsVistaStyle : public QWindowsStyle
{
Q_OBJECT
public:
@@ -58,7 +58,9 @@ public:
void polish(QWidget *widget) override;
void unpolish(QWidget *widget) override;
void polish(QPalette &pal) override;
-
+ void polish(QApplication *app) override;
+protected:
+ QWindowsVistaStyle(QWindowsVistaStylePrivate &dd);
private:
Q_DISABLE_COPY_MOVE(QWindowsVistaStyle)
Q_DECLARE_PRIVATE(QWindowsVistaStyle)
diff --git a/src/plugins/styles/modernwindows/qwindowsvistastyle_p_p.h b/src/plugins/styles/modernwindows/qwindowsvistastyle_p_p.h
new file mode 100644
index 0000000000..053e98b68d
--- /dev/null
+++ b/src/plugins/styles/modernwindows/qwindowsvistastyle_p_p.h
@@ -0,0 +1,176 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QWINDOWSVISTASTYLE_P_P_H
+#define QWINDOWSVISTASTYLE_P_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists for the convenience
+// of qapplication_*.cpp, qwidget*.cpp and qfiledialog.cpp. This header
+// file may change from version to version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtWidgets/private/qtwidgetsglobal_p.h>
+#include "qwindowsvistastyle_p.h"
+#include "qwindowsthemedata_p.h"
+#include <private/qpaintengine_raster_p.h>
+#include <qpaintengine.h>
+#include <qwidget.h>
+#include <qapplication.h>
+#include <qpixmapcache.h>
+#include <qstyleoption.h>
+#include <QtWidgets/private/qwindowsstyle_p_p.h>
+#include <qmap.h>
+
+#if QT_CONFIG(pushbutton)
+#include <qpushbutton.h>
+#endif
+#include <qradiobutton.h>
+#if QT_CONFIG(lineedit)
+#include <qlineedit.h>
+#endif
+#include <qgroupbox.h>
+#if QT_CONFIG(toolbutton)
+#include <qtoolbutton.h>
+#endif
+#if QT_CONFIG(spinbox)
+#include <qspinbox.h>
+#endif
+#if QT_CONFIG(toolbar)
+#include <qtoolbar.h>
+#endif
+#if QT_CONFIG(combobox)
+#include <qcombobox.h>
+#endif
+#if QT_CONFIG(scrollbar)
+#include <qscrollbar.h>
+#endif
+#if QT_CONFIG(progressbar)
+#include <qprogressbar.h>
+#endif
+#if QT_CONFIG(dockwidget)
+#include <qdockwidget.h>
+#endif
+#if QT_CONFIG(listview)
+#include <qlistview.h>
+#endif
+#if QT_CONFIG(treeview)
+#include <qtreeview.h>
+#endif
+#include <qtextedit.h>
+#include <qmessagebox.h>
+#if QT_CONFIG(dialogbuttonbox)
+#include <qdialogbuttonbox.h>
+#endif
+#include <qinputdialog.h>
+#if QT_CONFIG(tableview)
+#include <qtableview.h>
+#endif
+#include <qdatetime.h>
+#if QT_CONFIG(commandlinkbutton)
+#include <qcommandlinkbutton.h>
+#endif
+#include <qlabel.h>
+#include <qheaderview.h>
+#include <uxtheme.h>
+
+QT_BEGIN_NAMESPACE
+
+class QWindowsVistaStylePrivate : public QWindowsStylePrivate
+{
+ Q_DECLARE_PUBLIC(QWindowsVistaStyle)
+
+public:
+ enum Theme {
+ ButtonTheme,
+ ComboboxTheme,
+ EditTheme,
+ HeaderTheme,
+ ListViewTheme,
+ MenuTheme,
+ ProgressTheme,
+ RebarTheme,
+ ScrollBarTheme,
+ SpinTheme,
+ TabTheme,
+ TaskDialogTheme,
+ ToolBarTheme,
+ ToolTipTheme,
+ TrackBarTheme,
+ WindowTheme,
+ StatusTheme,
+ VistaTreeViewTheme, // arrow shape treeview indicators (Vista) obtained from "explorer" theme.
+ NThemes
+ };
+
+ QWindowsVistaStylePrivate()
+ { init(); }
+
+ ~QWindowsVistaStylePrivate()
+ { cleanup(); }
+
+ static HTHEME createTheme(int theme, HWND hwnd);
+ static QString themeName(int theme);
+ static bool isItemViewDelegateLineEdit(const QWidget *widget);
+ static int pixelMetricFromSystemDp(QStyle::PixelMetric pm, const QStyleOption *option = nullptr, const QWidget *widget = nullptr);
+ static int fixedPixelMetric(QStyle::PixelMetric pm);
+ static bool isLineEditBaseColorSet(const QStyleOption *option, const QWidget *widget);
+ static HWND winId(const QWidget *widget);
+ static bool useVista(bool update = false);
+ static QBackingStore *backingStoreForWidget(const QWidget *widget);
+ static HDC hdcForWidgetBackingStore(const QWidget *widget);
+
+ void init(bool force = false);
+ void cleanup(bool force = false);
+ void cleanupHandleMap();
+
+ HBITMAP buffer(int w = 0, int h = 0);
+ HDC bufferHDC()
+ { return bufferDC; }
+
+ bool isTransparent(QWindowsThemeData &QWindowsThemeData);
+ QRegion region(QWindowsThemeData &QWindowsThemeData);
+
+ bool drawBackground(QWindowsThemeData &QWindowsThemeData, qreal correctionFactor = 1);
+ bool drawBackgroundThruNativeBuffer(QWindowsThemeData &QWindowsThemeData, qreal aditionalDevicePixelRatio, qreal correctionFactor);
+ bool drawBackgroundDirectly(HDC dc, QWindowsThemeData &QWindowsThemeData, qreal aditionalDevicePixelRatio);
+
+ bool hasAlphaChannel(const QRect &rect);
+ bool fixAlphaChannel(const QRect &rect);
+ bool swapAlphaChannel(const QRect &rect, bool allPixels = false);
+
+ QRgb groupBoxTextColor = 0;
+ QRgb groupBoxTextColorDisabled = 0;
+ QRgb sliderTickColor = 0;
+ bool hasInitColors = false;
+ QIcon dockFloat, dockClose;
+
+ QTime animationTime() const;
+ bool transitionsEnabled() const;
+
+private:
+ static bool initVistaTreeViewTheming();
+ static void cleanupVistaTreeViewTheming();
+
+ static QBasicAtomicInt ref;
+ static bool useVistaTheme;
+
+ QHash<ThemeMapKey, ThemeMapData> alphaCache;
+ HDC bufferDC = nullptr;
+ HBITMAP bufferBitmap = nullptr;
+ HBITMAP nullBitmap = nullptr;
+ uchar *bufferPixels = nullptr;
+ int bufferW = 0;
+ int bufferH = 0;
+
+ static HWND m_vistaTreeViewHelper;
+};
+
+QT_END_NAMESPACE
+
+#endif // QWINDOWSVISTASTYLE_P_P_H
diff --git a/src/plugins/styles/windowsvista/main.cpp b/src/plugins/styles/windowsvista/main.cpp
deleted file mode 100644
index af832be0a8..0000000000
--- a/src/plugins/styles/windowsvista/main.cpp
+++ /dev/null
@@ -1,28 +0,0 @@
-// Copyright (C) 2017 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
-
-#include <QtWidgets/private/qtwidgetsglobal_p.h>
-#include <QtWidgets/qstyleplugin.h>
-#include "qwindowsvistastyle_p.h"
-
-QT_BEGIN_NAMESPACE
-
-class QWindowsVistaStylePlugin : public QStylePlugin
-{
- Q_OBJECT
- Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QStyleFactoryInterface" FILE "windowsvistastyle.json")
-public:
- QStyle *create(const QString &key) override;
-};
-
-QStyle *QWindowsVistaStylePlugin::create(const QString &key)
-{
- if (key.compare(QLatin1String("windowsvista"), Qt::CaseInsensitive) == 0)
- return new QWindowsVistaStyle();
-
- return nullptr;
-}
-
-QT_END_NAMESPACE
-
-#include "main.moc"
diff --git a/src/plugins/styles/windowsvista/qwindowsvistastyle.cpp b/src/plugins/styles/windowsvista/qwindowsvistastyle.cpp
deleted file mode 100644
index 0380ef82d8..0000000000
--- a/src/plugins/styles/windowsvista/qwindowsvistastyle.cpp
+++ /dev/null
@@ -1,2448 +0,0 @@
-// Copyright (C) 2016 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
-
-#include "qwindowsvistastyle_p.h"
-#include "qwindowsvistastyle_p_p.h"
-#include <qoperatingsystemversion.h>
-#include <qscreen.h>
-#include <qwindow.h>
-#include <private/qstyleanimation_p.h>
-#include <private/qstylehelper_p.h>
-#include <qpa/qplatformnativeinterface.h>
-
-QT_BEGIN_NAMESPACE
-
-static const int windowsItemFrame = 2; // menu item frame width
-static const int windowsItemHMargin = 3; // menu item hor text margin
-static const int windowsItemVMargin = 4; // menu item ver text margin
-static const int windowsArrowHMargin = 6; // arrow horizontal margin
-static const int windowsRightBorder = 15; // right border on windows
-
-#ifndef TMT_CONTENTMARGINS
-# define TMT_CONTENTMARGINS 3602
-#endif
-#ifndef TMT_SIZINGMARGINS
-# define TMT_SIZINGMARGINS 3601
-#endif
-#ifndef LISS_NORMAL
-# define LISS_NORMAL 1
-# define LISS_HOT 2
-# define LISS_SELECTED 3
-# define LISS_DISABLED 4
-# define LISS_SELECTEDNOTFOCUS 5
-# define LISS_HOTSELECTED 6
-#endif
-#ifndef BP_COMMANDLINK
-# define BP_COMMANDLINK 6
-# define BP_COMMANDLINKGLYPH 7
-# define CMDLGS_NORMAL 1
-# define CMDLGS_HOT 2
-# define CMDLGS_PRESSED 3
-# define CMDLGS_DISABLED 4
-#endif
-
-/* \internal
- Checks if we should use Vista style , or if we should
- fall back to Windows style.
-*/
-bool QWindowsVistaStylePrivate::useVista()
-{
- return QWindowsVistaStylePrivate::useXP();
-}
-
-/*!
- \internal
-
- Animations are started at a frame that is based on the current time,
- which makes it impossible to run baseline tests with this style. Allow
- overriding through a dynamic property.
-*/
-QTime QWindowsVistaStylePrivate::animationTime() const
-{
- Q_Q(const QWindowsVistaStyle);
- static bool animationTimeOverride = q->dynamicPropertyNames().contains("_qt_animation_time");
- if (animationTimeOverride)
- return q->property("_qt_animation_time").toTime();
- return QTime::currentTime();
-}
-
-/* \internal
- Checks and returns the style object
-*/
-inline QObject *styleObject(const QStyleOption *option) {
- return option ? option->styleObject : nullptr;
-}
-
-/* \internal
- Checks if we can animate on a style option
-*/
-bool canAnimate(const QStyleOption *option) {
- return option
- && option->styleObject
- && !option->styleObject->property("_q_no_animation").toBool();
-}
-
-static inline QImage createAnimationBuffer(const QStyleOption *option, const QWidget *widget)
-{
- const qreal devicePixelRatio = widget
- ? widget->devicePixelRatioF() : qApp->devicePixelRatio();
- QImage result(option->rect.size() * devicePixelRatio, QImage::Format_ARGB32_Premultiplied);
- result.setDevicePixelRatio(devicePixelRatio);
- result.fill(0);
- return result;
-}
-
-/* \internal
- Used by animations to clone a styleoption and shift its offset
-*/
-QStyleOption *clonedAnimationStyleOption(const QStyleOption*option) {
- QStyleOption *styleOption = nullptr;
- if (const QStyleOptionSlider *slider = qstyleoption_cast<const QStyleOptionSlider*>(option))
- styleOption = new QStyleOptionSlider(*slider);
- else if (const QStyleOptionSpinBox *spinbox = qstyleoption_cast<const QStyleOptionSpinBox*>(option))
- styleOption = new QStyleOptionSpinBox(*spinbox);
- else if (const QStyleOptionGroupBox *groupBox = qstyleoption_cast<const QStyleOptionGroupBox*>(option))
- styleOption = new QStyleOptionGroupBox(*groupBox);
- else if (const QStyleOptionComboBox *combo = qstyleoption_cast<const QStyleOptionComboBox*>(option))
- styleOption = new QStyleOptionComboBox(*combo);
- else if (const QStyleOptionButton *button = qstyleoption_cast<const QStyleOptionButton*>(option))
- styleOption = new QStyleOptionButton(*button);
- else
- styleOption = new QStyleOption(*option);
- styleOption->rect = QRect(QPoint(0,0), option->rect.size());
- return styleOption;
-}
-
-/* \internal
- Used by animations to delete cloned styleoption
-*/
-void deleteClonedAnimationStyleOption(const QStyleOption *option)
-{
- if (const QStyleOptionSlider *slider = qstyleoption_cast<const QStyleOptionSlider*>(option))
- delete slider;
- else if (const QStyleOptionSpinBox *spinbox = qstyleoption_cast<const QStyleOptionSpinBox*>(option))
- delete spinbox;
- else if (const QStyleOptionGroupBox *groupBox = qstyleoption_cast<const QStyleOptionGroupBox*>(option))
- delete groupBox;
- else if (const QStyleOptionComboBox *combo = qstyleoption_cast<const QStyleOptionComboBox*>(option))
- delete combo;
- else if (const QStyleOptionButton *button = qstyleoption_cast<const QStyleOptionButton*>(option))
- delete button;
- else
- delete option;
-}
-
-/*!
- \class QWindowsVistaStyle
- \brief The QWindowsVistaStyle class provides a look and feel suitable for applications on Microsoft Windows Vista.
- \since 4.3
- \ingroup appearance
- \inmodule QtWidgets
- \internal
-
- \warning This style is only available on the Windows Vista platform
- because it makes use of Windows Vista's style engine.
-
- \sa QMacStyle, QWindowsXPStyle, QFusionStyle
-*/
-
-/*!
- Constructs a QWindowsVistaStyle object.
-*/
-QWindowsVistaStyle::QWindowsVistaStyle()
- : QWindowsXPStyle(*new QWindowsVistaStylePrivate)
-{
-}
-
-/*!
- Destructor.
-*/
-QWindowsVistaStyle::~QWindowsVistaStyle() = default;
-
-//convert Qt state flags to uxtheme button states
-static int buttonStateId(int flags, int partId)
-{
- int stateId = 0;
- if (partId == BP_RADIOBUTTON || partId == BP_CHECKBOX) {
- if (!(flags & QStyle::State_Enabled))
- stateId = RBS_UNCHECKEDDISABLED;
- else if (flags & QStyle::State_Sunken)
- stateId = RBS_UNCHECKEDPRESSED;
- else if (flags & QStyle::State_MouseOver)
- stateId = RBS_UNCHECKEDHOT;
- else
- stateId = RBS_UNCHECKEDNORMAL;
-
- if (flags & QStyle::State_On)
- stateId += RBS_CHECKEDNORMAL-1;
-
- } else if (partId == BP_PUSHBUTTON) {
- if (!(flags & QStyle::State_Enabled))
- stateId = PBS_DISABLED;
- else if (flags & (QStyle::State_Sunken | QStyle::State_On))
- stateId = PBS_PRESSED;
- else if (flags & QStyle::State_MouseOver)
- stateId = PBS_HOT;
- else
- stateId = PBS_NORMAL;
- } else {
- Q_ASSERT(1);
- }
- return stateId;
-}
-
-bool QWindowsVistaAnimation::isUpdateNeeded() const
-{
- return QWindowsVistaStylePrivate::useVista();
-}
-
-void QWindowsVistaAnimation::paint(QPainter *painter, const QStyleOption *option)
-{
- painter->drawImage(option->rect, currentImage());
-}
-
-static inline bool supportsStateTransition(QStyle::PrimitiveElement element,
- const QStyleOption *option,
- const QWidget *widget)
-{
- bool result = false;
- switch (element) {
- case QStyle::PE_IndicatorRadioButton:
- case QStyle::PE_IndicatorCheckBox:
- result = true;
- break;
- // QTBUG-40634, do not animate when color is set in palette for PE_PanelLineEdit.
- case QStyle::PE_FrameLineEdit:
- result = !QWindowsXPStylePrivate::isLineEditBaseColorSet(option, widget);
- break;
- default:
- break;
- }
- return result;
-}
-
-/*!
- \internal
-
- Animations are used for some state transitions on specific widgets.
-
- Only one running animation can exist for a widget at any specific
- time. Animations can be added through
- QWindowsVistaStylePrivate::startAnimation(Animation *) and any
- existing animation on a widget can be retrieved with
- QWindowsVistaStylePrivate::widgetAnimation(Widget *).
-
- Once an animation has been started,
- QWindowsVistaStylePrivate::timerEvent(QTimerEvent *) will
- continuously call update() on the widget until it is stopped,
- meaning that drawPrimitive will be called many times until the
- transition has completed. During this time, the result will be
- retrieved by the Animation::paint(...) function and not by the style
- itself.
-
- To determine if a transition should occur, the style needs to know
- the previous state of the widget as well as the current one. This is
- solved by updating dynamic properties on the widget every time the
- function is called.
-
- Transitions interrupting existing transitions should always be
- smooth, so whenever a hover-transition is started on a pulsating
- button, it uses the current frame of the pulse-animation as the
- starting image for the hover transition.
-
- */
-
-void QWindowsVistaStyle::drawPrimitive(PrimitiveElement element, const QStyleOption *option,
- QPainter *painter, const QWidget *widget) const
-{
- QWindowsVistaStylePrivate *d = const_cast<QWindowsVistaStylePrivate*>(d_func());
-
- int state = option->state;
- if (!QWindowsVistaStylePrivate::useVista()) {
- QWindowsStyle::drawPrimitive(element, option, painter, widget);
- return;
- }
-
- if ((option->state & State_Enabled) && d->transitionsEnabled() && canAnimate(option)) {
- {
- QRect oldRect;
- QRect newRect;
-
- if (supportsStateTransition(element, option, widget)) {
- // Retrieve and update the dynamic properties tracking
- // the previous state of the widget:
- QObject *styleObject = option->styleObject;
- styleObject->setProperty("_q_no_animation", true);
-
- int oldState = styleObject->property("_q_stylestate").toInt();
- oldRect = styleObject->property("_q_stylerect").toRect();
- newRect = option->rect;
- styleObject->setProperty("_q_stylestate", int(option->state));
- styleObject->setProperty("_q_stylerect", option->rect);
-
- bool doTransition = oldState &&
- ((state & State_Sunken) != (oldState & State_Sunken) ||
- (state & State_On) != (oldState & State_On) ||
- (state & State_MouseOver) != (oldState & State_MouseOver));
-
- if (oldRect != newRect ||
- (state & State_Enabled) != (oldState & State_Enabled) ||
- (state & State_Active) != (oldState & State_Active))
- d->stopAnimation(styleObject);
-
- if (option->state & State_ReadOnly && element == PE_FrameLineEdit) // Do not animate read only line edits
- doTransition = false;
-
- if (doTransition) {
- QStyleOption *styleOption = clonedAnimationStyleOption(option);
- styleOption->state = QStyle::State(oldState);
-
- QWindowsVistaAnimation *anim = qobject_cast<QWindowsVistaAnimation *>(d->animation(styleObject));
- QWindowsVistaTransition *t = new QWindowsVistaTransition(styleObject);
-
- // We create separate images for the initial and final transition states and store them in the
- // Transition object.
- QImage startImage = createAnimationBuffer(option, widget);
- QPainter startPainter(&startImage);
-
- QImage endImage = createAnimationBuffer(option, widget);
- QPainter endPainter(&endImage);
-
- // If we have a running animation on the widget already, we will use that to paint the initial
- // state of the new transition, this ensures a smooth transition from a current animation such as a
- // pulsating default button into the intended target state.
- if (!anim)
- proxy()->drawPrimitive(element, styleOption, &startPainter, widget);
- else
- anim->paint(&startPainter, styleOption);
-
- t->setStartImage(startImage);
-
- // The end state of the transition is simply the result we would have painted
- // if the style was not animated.
- styleOption->styleObject = nullptr;
- styleOption->state = option->state;
- proxy()->drawPrimitive(element, styleOption, &endPainter, widget);
-
-
- t->setEndImage(endImage);
-
- HTHEME theme;
- int partId;
- DWORD duration;
- int fromState = 0;
- int toState = 0;
-
- //translate state flags to UXTHEME states :
- if (element == PE_FrameLineEdit) {
- theme = OpenThemeData(nullptr, L"Edit");
- partId = EP_EDITBORDER_NOSCROLL;
-
- if (oldState & State_MouseOver)
- fromState = ETS_HOT;
- else if (oldState & State_HasFocus)
- fromState = ETS_FOCUSED;
- else
- fromState = ETS_NORMAL;
-
- if (state & State_MouseOver)
- toState = ETS_HOT;
- else if (state & State_HasFocus)
- toState = ETS_FOCUSED;
- else
- toState = ETS_NORMAL;
-
- } else {
- theme = OpenThemeData(nullptr, L"Button");
- if (element == PE_IndicatorRadioButton)
- partId = BP_RADIOBUTTON;
- else if (element == PE_IndicatorCheckBox)
- partId = BP_CHECKBOX;
- else
- partId = BP_PUSHBUTTON;
-
- fromState = buttonStateId(oldState, partId);
- toState = buttonStateId(option->state, partId);
- }
-
- // Retrieve the transition time between the states from the system.
- if (theme
- && SUCCEEDED(GetThemeTransitionDuration(theme, partId, fromState, toState,
- TMT_TRANSITIONDURATIONS, &duration))) {
- t->setDuration(int(duration));
- }
- t->setStartTime(d->animationTime());
-
- deleteClonedAnimationStyleOption(styleOption);
- d->startAnimation(t);
- }
- styleObject->setProperty("_q_no_animation", false);
- }
-
- } // End of animation part
- }
-
- QRect rect = option->rect;
-
- switch (element) {
- case PE_IndicatorHeaderArrow:
- if (const QStyleOptionHeader *header = qstyleoption_cast<const QStyleOptionHeader *>(option)) {
- int stateId = HSAS_SORTEDDOWN;
- if (header->sortIndicator & QStyleOptionHeader::SortDown)
- stateId = HSAS_SORTEDUP; //note that the uxtheme sort down indicator is the inverse of ours
- XPThemeData theme(widget, painter,
- QWindowsXPStylePrivate::HeaderTheme,
- HP_HEADERSORTARROW, stateId, option->rect);
- d->drawBackground(theme);
- }
- break;
-
- case PE_IndicatorBranch:
- {
- XPThemeData theme(widget, painter, QWindowsXPStylePrivate::VistaTreeViewTheme);
- static int decoration_size = 0;
- if (!decoration_size && theme.isValid()) {
- XPThemeData themeSize = theme;
- themeSize.partId = TVP_HOTGLYPH;
- themeSize.stateId = GLPS_OPENED;
- const QSizeF size = themeSize.size() * QWindowsStylePrivate::nativeMetricScaleFactor(widget);
- decoration_size = qRound(qMax(size.width(), size.height()));
- }
- int mid_h = option->rect.x() + option->rect.width() / 2;
- int mid_v = option->rect.y() + option->rect.height() / 2;
- int bef_h = mid_h;
- int bef_v = mid_v;
- int aft_h = mid_h;
- int aft_v = mid_v;
- if (option->state & State_Children) {
- int delta = decoration_size / 2;
- theme.rect = QRect(bef_h - delta, bef_v - delta, decoration_size, decoration_size);
- theme.partId = option->state & State_MouseOver ? TVP_HOTGLYPH : TVP_GLYPH;
- theme.stateId = option->state & QStyle::State_Open ? GLPS_OPENED : GLPS_CLOSED;
- if (option->direction == Qt::RightToLeft)
- theme.mirrorHorizontally = true;
- d->drawBackground(theme);
- bef_h -= delta + 2;
- bef_v -= delta + 2;
- aft_h += delta - 2;
- aft_v += delta - 2;
- }
-#if 0
- QBrush brush(option->palette.dark().color(), Qt::Dense4Pattern);
- if (option->state & State_Item) {
- if (option->direction == Qt::RightToLeft)
- painter->fillRect(option->rect.left(), mid_v, bef_h - option->rect.left(), 1, brush);
- else
- painter->fillRect(aft_h, mid_v, option->rect.right() - aft_h + 1, 1, brush);
- }
- if (option->state & State_Sibling && option->rect.bottom() > aft_v)
- painter->fillRect(mid_h, aft_v, 1, option->rect.bottom() - aft_v + 1, brush);
- if (option->state & (State_Open | State_Children | State_Item | State_Sibling) && (bef_v > option->rect.y()))
- painter->fillRect(mid_h, option->rect.y(), 1, bef_v - option->rect.y(), brush);
-#endif
- }
- break;
-
- case PE_PanelButtonBevel:
- case PE_IndicatorCheckBox:
- case PE_IndicatorRadioButton:
- {
- if (QWindowsVistaAnimation *a =
- qobject_cast<QWindowsVistaAnimation *>(d->animation(styleObject(option)))){
- a->paint(painter, option);
- } else {
- QWindowsXPStyle::drawPrimitive(element, option, painter, widget);
- }
- }
- break;
-
- case PE_FrameMenu:
- {
- int stateId = option->state & State_Active ? MB_ACTIVE : MB_INACTIVE;
- XPThemeData theme(widget, painter,
- QWindowsXPStylePrivate::MenuTheme,
- MENU_POPUPBORDERS, stateId, option->rect);
- d->drawBackground(theme);
- }
- break;
- case PE_Frame: {
-#if QT_CONFIG(accessibility)
- if (QStyleHelper::isInstanceOf(option->styleObject, QAccessible::EditableText)
- || QStyleHelper::isInstanceOf(option->styleObject, QAccessible::StaticText) ||
-#else
- if (
-#endif
- (widget && widget->inherits("QTextEdit"))) {
- painter->save();
- int stateId = ETS_NORMAL;
- if (!(state & State_Enabled))
- stateId = ETS_DISABLED;
- else if (state & State_ReadOnly)
- stateId = ETS_READONLY;
- else if (state & State_HasFocus)
- stateId = ETS_SELECTED;
- XPThemeData theme(widget, painter,
- QWindowsXPStylePrivate::EditTheme,
- EP_EDITBORDER_HVSCROLL, stateId, option->rect);
- // Since EP_EDITBORDER_HVSCROLL does not us borderfill, theme.noContent cannot be used for clipping
- int borderSize = 1;
- GetThemeInt(theme.handle(), theme.partId, theme.stateId, TMT_BORDERSIZE, &borderSize);
- QRegion clipRegion = option->rect;
- QRegion content = option->rect.adjusted(borderSize, borderSize, -borderSize, -borderSize);
- clipRegion ^= content;
- painter->setClipRegion(clipRegion);
- d->drawBackground(theme);
- painter->restore();
- } else {
- QWindowsXPStyle::drawPrimitive(element, option, painter, widget);
- }
- }
- break;
-
- case PE_PanelLineEdit:
- if (const QStyleOptionFrame *panel = qstyleoption_cast<const QStyleOptionFrame *>(option)) {
- bool isEnabled = option->state & State_Enabled;
- if (QWindowsXPStylePrivate::isLineEditBaseColorSet(option, widget)) {
- painter->fillRect(panel->rect, panel->palette.brush(QPalette::Base));
- } else {
- int partId = EP_BACKGROUND;
- int stateId = EBS_NORMAL;
- if (!isEnabled)
- stateId = EBS_DISABLED;
- else if (state & State_ReadOnly)
- stateId = EBS_READONLY;
- else if (state & State_MouseOver)
- stateId = EBS_HOT;
-
- XPThemeData theme(nullptr, painter, QWindowsXPStylePrivate::EditTheme,
- partId, stateId, rect);
- if (!theme.isValid()) {
- QWindowsStyle::drawPrimitive(element, option, painter, widget);
- return;
- }
- int bgType;
- GetThemeEnumValue(theme.handle(), partId, stateId, TMT_BGTYPE, &bgType);
- if ( bgType == BT_IMAGEFILE ) {
- d->drawBackground(theme);
- } else {
- QBrush fillColor = option->palette.brush(QPalette::Base);
- if (!isEnabled) {
- PROPERTYORIGIN origin = PO_NOTFOUND;
- GetThemePropertyOrigin(theme.handle(), theme.partId, theme.stateId, TMT_FILLCOLOR, &origin);
- // Use only if the fill property comes from our part
- if ((origin == PO_PART || origin == PO_STATE)) {
- COLORREF bgRef;
- GetThemeColor(theme.handle(), partId, stateId, TMT_FILLCOLOR, &bgRef);
- fillColor = QBrush(qRgb(GetRValue(bgRef), GetGValue(bgRef), GetBValue(bgRef)));
- }
- }
- painter->fillRect(option->rect, fillColor);
- }
- }
- if (panel->lineWidth > 0)
- proxy()->drawPrimitive(PE_FrameLineEdit, panel, painter, widget);
- return;
- }
- break;
-
- case PE_FrameLineEdit:
- if (QWindowsVistaAnimation *anim = qobject_cast<QWindowsVistaAnimation *>(d->animation(styleObject(option)))) {
- anim->paint(painter, option);
- } else {
- QPainter *p = painter;
- if (QWindowsXPStylePrivate::isItemViewDelegateLineEdit(widget)) {
- // we try to check if this lineedit is a delegate on a QAbstractItemView-derived class.
- QPen oldPen = p->pen();
- // Inner white border
- p->setPen(QPen(option->palette.base().color(), 1));
- p->drawRect(option->rect.adjusted(1, 1, -2, -2));
- // Outer dark border
- p->setPen(QPen(option->palette.shadow().color(), 1));
- p->drawRect(option->rect.adjusted(0, 0, -1, -1));
- p->setPen(oldPen);
- return;
- }
- int stateId = ETS_NORMAL;
- if (!(state & State_Enabled))
- stateId = ETS_DISABLED;
- else if (state & State_ReadOnly)
- stateId = ETS_READONLY;
- else if (state & State_MouseOver)
- stateId = ETS_HOT;
- else if (state & State_HasFocus)
- stateId = ETS_SELECTED;
- XPThemeData theme(widget, painter,
- QWindowsXPStylePrivate::EditTheme,
- EP_EDITBORDER_NOSCROLL, stateId, option->rect);
- theme.noContent = true;
- painter->save();
- QRegion clipRegion = option->rect;
- clipRegion -= option->rect.adjusted(2, 2, -2, -2);
- painter->setClipRegion(clipRegion);
- d->drawBackground(theme);
- painter->restore();
- }
- break;
-
- case PE_IndicatorToolBarHandle:
- {
- XPThemeData theme;
- QRect rect;
- if (option->state & State_Horizontal) {
- theme = XPThemeData(widget, painter,
- QWindowsXPStylePrivate::RebarTheme,
- RP_GRIPPER, ETS_NORMAL, option->rect.adjusted(0, 1, -2, -2));
- rect = option->rect.adjusted(0, 1, 0, -2);
- rect.setWidth(4);
- } else {
- theme = XPThemeData(widget, painter, QWindowsXPStylePrivate::RebarTheme,
- RP_GRIPPERVERT, ETS_NORMAL, option->rect.adjusted(0, 1, -2, -2));
- rect = option->rect.adjusted(1, 0, -1, 0);
- rect.setHeight(4);
- }
- theme.rect = rect;
- d->drawBackground(theme);
- }
- break;
-
- case PE_IndicatorToolBarSeparator:
- {
- QPen pen = painter->pen();
- int margin = 3;
- painter->setPen(option->palette.window().color().darker(114));
- if (option->state & State_Horizontal) {
- int x1 = option->rect.center().x();
- painter->drawLine(QPoint(x1, option->rect.top() + margin), QPoint(x1, option->rect.bottom() - margin));
- } else {
- int y1 = option->rect.center().y();
- painter->drawLine(QPoint(option->rect.left() + margin, y1), QPoint(option->rect.right() - margin, y1));
- }
- painter->setPen(pen);
- }
- break;
-
- case PE_PanelTipLabel: {
- XPThemeData theme(widget, painter,
- QWindowsXPStylePrivate::ToolTipTheme,
- TTP_STANDARD, TTSS_NORMAL, option->rect);
- d->drawBackground(theme);
- break;
- }
-
- case PE_PanelItemViewItem:
- {
- const QStyleOptionViewItem *vopt;
- bool newStyle = true;
- QAbstractItemView::SelectionBehavior selectionBehavior = QAbstractItemView::SelectRows;
- QAbstractItemView::SelectionMode selectionMode = QAbstractItemView::NoSelection;
- if (const QAbstractItemView *view = qobject_cast<const QAbstractItemView *>(widget)) {
- newStyle = !qobject_cast<const QTableView*>(view);
- selectionBehavior = view->selectionBehavior();
- selectionMode = view->selectionMode();
-#if QT_CONFIG(accessibility)
- } else if (!widget) {
- newStyle = !QStyleHelper::hasAncestor(option->styleObject, QAccessible::MenuItem) ;
-#endif
- }
-
- if (newStyle && (vopt = qstyleoption_cast<const QStyleOptionViewItem *>(option))) {
- bool selected = vopt->state & QStyle::State_Selected;
- const bool hover = selectionMode != QAbstractItemView::NoSelection && (vopt->state & QStyle::State_MouseOver);
- bool active = vopt->state & QStyle::State_Active;
-
- if (vopt->features & QStyleOptionViewItem::Alternate)
- painter->fillRect(vopt->rect, vopt->palette.alternateBase());
-
- QPalette::ColorGroup cg = vopt->state & QStyle::State_Enabled
- ? QPalette::Normal : QPalette::Disabled;
- if (cg == QPalette::Normal && !(vopt->state & QStyle::State_Active))
- cg = QPalette::Inactive;
-
- QRect itemRect = subElementRect(QStyle::SE_ItemViewItemFocusRect, option, widget).adjusted(-1, 0, 1, 0);
- itemRect.setTop(vopt->rect.top());
- itemRect.setBottom(vopt->rect.bottom());
-
- QSize sectionSize = itemRect.size();
- if (vopt->showDecorationSelected)
- sectionSize = vopt->rect.size();
-
- if (selectionBehavior == QAbstractItemView::SelectRows)
- sectionSize.setWidth(vopt->rect.width());
- QPixmap pixmap;
-
- if (vopt->backgroundBrush.style() != Qt::NoBrush) {
- const QPointF oldBrushOrigin = painter->brushOrigin();
- painter->setBrushOrigin(vopt->rect.topLeft());
- painter->fillRect(vopt->rect, vopt->backgroundBrush);
- painter->setBrushOrigin(oldBrushOrigin);
- }
-
- if (hover || selected) {
- if (sectionSize.width() > 0 && sectionSize.height() > 0) {
- QString key = QString::fromLatin1("qvdelegate-%1-%2-%3-%4-%5").arg(sectionSize.width())
- .arg(sectionSize.height()).arg(selected).arg(active).arg(hover);
- if (!QPixmapCache::find(key, &pixmap)) {
- pixmap = QPixmap(sectionSize);
- pixmap.fill(Qt::transparent);
-
- int state;
- if (selected && hover)
- state = LISS_HOTSELECTED;
- else if (selected && !active)
- state = LISS_SELECTEDNOTFOCUS;
- else if (selected)
- state = LISS_SELECTED;
- else
- state = LISS_HOT;
-
- QPainter pixmapPainter(&pixmap);
- XPThemeData theme(widget, &pixmapPainter,
- QWindowsXPStylePrivate::VistaTreeViewTheme,
- LVP_LISTITEM, state, QRect(0, 0, sectionSize.width(), sectionSize.height()));
- if (theme.isValid()) {
- d->drawBackground(theme);
- } else {
- QWindowsXPStyle::drawPrimitive(PE_PanelItemViewItem, option, painter, widget);
- break;
- }
- QPixmapCache::insert(key, pixmap);
- }
- }
-
- if (vopt->showDecorationSelected) {
- const int frame = 2; //Assumes a 2 pixel pixmap border
- QRect srcRect = QRect(0, 0, sectionSize.width(), sectionSize.height());
- QRect pixmapRect = vopt->rect;
- bool reverse = vopt->direction == Qt::RightToLeft;
- bool leftSection = vopt->viewItemPosition == QStyleOptionViewItem::Beginning;
- bool rightSection = vopt->viewItemPosition == QStyleOptionViewItem::End;
- if (vopt->viewItemPosition == QStyleOptionViewItem::OnlyOne
- || vopt->viewItemPosition == QStyleOptionViewItem::Invalid)
- painter->drawPixmap(pixmapRect.topLeft(), pixmap);
- else if (reverse ? rightSection : leftSection){
- painter->drawPixmap(QRect(pixmapRect.topLeft(),
- QSize(frame, pixmapRect.height())), pixmap,
- QRect(QPoint(0, 0), QSize(frame, pixmapRect.height())));
- painter->drawPixmap(pixmapRect.adjusted(frame, 0, 0, 0),
- pixmap, srcRect.adjusted(frame, 0, -frame, 0));
- } else if (reverse ? leftSection : rightSection) {
- painter->drawPixmap(QRect(pixmapRect.topRight() - QPoint(frame - 1, 0),
- QSize(frame, pixmapRect.height())), pixmap,
- QRect(QPoint(pixmapRect.width() - frame, 0),
- QSize(frame, pixmapRect.height())));
- painter->drawPixmap(pixmapRect.adjusted(0, 0, -frame, 0),
- pixmap, srcRect.adjusted(frame, 0, -frame, 0));
- } else if (vopt->viewItemPosition == QStyleOptionViewItem::Middle)
- painter->drawPixmap(pixmapRect, pixmap,
- srcRect.adjusted(frame, 0, -frame, 0));
- } else {
- if (vopt->text.isEmpty() && vopt->icon.isNull())
- break;
- painter->drawPixmap(itemRect.topLeft(), pixmap);
- }
- }
- } else {
- QWindowsXPStyle::drawPrimitive(element, option, painter, widget);
- }
- break;
- }
- case PE_Widget:
- {
-#if QT_CONFIG(dialogbuttonbox)
- const QDialogButtonBox *buttonBox = nullptr;
-
- if (qobject_cast<const QMessageBox *> (widget))
- buttonBox = widget->findChild<const QDialogButtonBox *>(QLatin1String("qt_msgbox_buttonbox"));
-#if QT_CONFIG(inputdialog)
- else if (qobject_cast<const QInputDialog *> (widget))
- buttonBox = widget->findChild<const QDialogButtonBox *>(QLatin1String("qt_inputdlg_buttonbox"));
-#endif // QT_CONFIG(inputdialog)
-
- if (buttonBox) {
- //draw white panel part
- XPThemeData theme(widget, painter,
- QWindowsXPStylePrivate::TaskDialogTheme,
- TDLG_PRIMARYPANEL, 0, option->rect);
- QRect toprect = option->rect;
- toprect.setBottom(buttonBox->geometry().top());
- theme.rect = toprect;
- d->drawBackground(theme);
-
- //draw bottom panel part
- QRect buttonRect = option->rect;
- buttonRect.setTop(buttonBox->geometry().top());
- theme.rect = buttonRect;
- theme.partId = TDLG_SECONDARYPANEL;
- d->drawBackground(theme);
- }
-#endif
- }
- break;
- default:
- QWindowsXPStyle::drawPrimitive(element, option, painter, widget);
- break;
- }
-}
-
-
-/*!
- \internal
-
- see drawPrimitive for comments on the animation support
- */
-void QWindowsVistaStyle::drawControl(ControlElement element, const QStyleOption *option,
- QPainter *painter, const QWidget *widget) const
-{
- QWindowsVistaStylePrivate *d = const_cast<QWindowsVistaStylePrivate*>(d_func());
-
- if (!QWindowsVistaStylePrivate::useVista()) {
- QWindowsStyle::drawControl(element, option, painter, widget);
- return;
- }
-
- bool selected = option->state & State_Selected;
- bool pressed = option->state & State_Sunken;
- bool disabled = !(option->state & State_Enabled);
-
- int state = option->state;
- int themeNumber = -1;
-
- QRect rect(option->rect);
- State flags = option->state;
- int partId = 0;
- int stateId = 0;
-
- if (d->transitionsEnabled() && canAnimate(option))
- {
- if (element == CE_PushButtonBevel) {
- QRect oldRect;
- QRect newRect;
-
- QObject *styleObject = option->styleObject;
-
- int oldState = styleObject->property("_q_stylestate").toInt();
- oldRect = styleObject->property("_q_stylerect").toRect();
- newRect = option->rect;
- styleObject->setProperty("_q_stylestate", int(option->state));
- styleObject->setProperty("_q_stylerect", option->rect);
-
- bool wasDefault = false;
- bool isDefault = false;
- if (const QStyleOptionButton *button = qstyleoption_cast<const QStyleOptionButton *>(option)) {
- wasDefault = styleObject->property("_q_isdefault").toBool();
- isDefault = button->features & QStyleOptionButton::DefaultButton;
- styleObject->setProperty("_q_isdefault", isDefault);
- }
-
- bool doTransition = ((state & State_Sunken) != (oldState & State_Sunken) ||
- (state & State_On) != (oldState & State_On) ||
- (state & State_MouseOver) != (oldState & State_MouseOver));
-
- if (oldRect != newRect || (wasDefault && !isDefault)) {
- doTransition = false;
- d->stopAnimation(styleObject);
- }
-
- if (doTransition) {
- styleObject->setProperty("_q_no_animation", true);
-
- QWindowsVistaTransition *t = new QWindowsVistaTransition(styleObject);
- QWindowsVistaAnimation *anim = qobject_cast<QWindowsVistaAnimation *>(d->animation(styleObject));
- QStyleOption *styleOption = clonedAnimationStyleOption(option);
- styleOption->state = QStyle::State(oldState);
-
- QImage startImage = createAnimationBuffer(option, widget);
- QPainter startPainter(&startImage);
-
- // Use current state of existing animation if already one is running
- if (!anim) {
- proxy()->drawControl(element, styleOption, &startPainter, widget);
- } else {
- anim->paint(&startPainter, styleOption);
- d->stopAnimation(styleObject);
- }
-
- t->setStartImage(startImage);
- QImage endImage = createAnimationBuffer(option, widget);
- QPainter endPainter(&endImage);
- styleOption->state = option->state;
- proxy()->drawControl(element, styleOption, &endPainter, widget);
- t->setEndImage(endImage);
-
-
- DWORD duration = 0;
- const HTHEME theme = OpenThemeData(nullptr, L"Button");
-
- int fromState = buttonStateId(oldState, BP_PUSHBUTTON);
- int toState = buttonStateId(option->state, BP_PUSHBUTTON);
- if (GetThemeTransitionDuration(theme, BP_PUSHBUTTON, fromState, toState, TMT_TRANSITIONDURATIONS, &duration) == S_OK)
- t->setDuration(int(duration));
- else
- t->setDuration(0);
- t->setStartTime(d->animationTime());
- styleObject->setProperty("_q_no_animation", false);
-
- deleteClonedAnimationStyleOption(styleOption);
- d->startAnimation(t);
- }
-
- QWindowsVistaAnimation *anim = qobject_cast<QWindowsVistaAnimation *>(d->animation(styleObject));
- if (anim) {
- anim->paint(painter, option);
- return;
- }
-
- }
- }
- switch (element) {
- case CE_PushButtonBevel:
- if (const QStyleOptionButton *btn = qstyleoption_cast<const QStyleOptionButton *>(option))
- {
- themeNumber = QWindowsXPStylePrivate::ButtonTheme;
- partId = BP_PUSHBUTTON;
- if (btn->features & QStyleOptionButton::CommandLinkButton)
- partId = BP_COMMANDLINK;
- bool justFlat = (btn->features & QStyleOptionButton::Flat) && !(flags & (State_On|State_Sunken));
- if (!(flags & State_Enabled) && !(btn->features & QStyleOptionButton::Flat))
- stateId = PBS_DISABLED;
- else if (justFlat)
- ;
- else if (flags & (State_Sunken | State_On))
- stateId = PBS_PRESSED;
- else if (flags & State_MouseOver)
- stateId = PBS_HOT;
- else if (btn->features & QStyleOptionButton::DefaultButton && (state & State_Active))
- stateId = PBS_DEFAULTED;
- else
- stateId = PBS_NORMAL;
-
- if (!justFlat) {
-
- if (d->transitionsEnabled() && (btn->features & QStyleOptionButton::DefaultButton) &&
- !(state & (State_Sunken | State_On)) && !(state & State_MouseOver) &&
- (state & State_Enabled) && (state & State_Active))
- {
- QWindowsVistaAnimation *anim = qobject_cast<QWindowsVistaAnimation *>(d->animation(styleObject(option)));
-
- if (!anim) {
- QImage startImage = createAnimationBuffer(option, widget);
- QImage alternateImage = createAnimationBuffer(option, widget);
-
- QWindowsVistaPulse *pulse = new QWindowsVistaPulse(styleObject(option));
-
- QPainter startPainter(&startImage);
- stateId = PBS_DEFAULTED;
- XPThemeData theme(widget, &startPainter, themeNumber, partId, stateId, rect);
- d->drawBackground(theme);
-
- QPainter alternatePainter(&alternateImage);
- theme.stateId = PBS_DEFAULTED_ANIMATING;
- theme.painter = &alternatePainter;
- d->drawBackground(theme);
- pulse->setStartImage(startImage);
- pulse->setEndImage(alternateImage);
- pulse->setStartTime(d->animationTime());
- pulse->setDuration(2000);
- d->startAnimation(pulse);
- anim = pulse;
- }
-
- if (anim)
- anim->paint(painter, option);
- else {
- XPThemeData theme(widget, painter, themeNumber, partId, stateId, rect);
- d->drawBackground(theme);
- }
- }
- else {
- XPThemeData theme(widget, painter, themeNumber, partId, stateId, rect);
- d->drawBackground(theme);
- }
- }
-
- if (btn->features & QStyleOptionButton::HasMenu) {
- int mbiw = 0, mbih = 0;
- XPThemeData theme(widget, nullptr, QWindowsXPStylePrivate::ToolBarTheme,
- TP_DROPDOWNBUTTON);
- if (theme.isValid()) {
- const QSizeF size = theme.size() * QStyleHelper::dpiScaled(1, option);
- if (!size.isEmpty()) {
- mbiw = qRound(size.width());
- mbih = qRound(size.height());
- }
- }
- QRect ir = subElementRect(SE_PushButtonContents, option, nullptr);
- QStyleOptionButton newBtn = *btn;
- newBtn.rect = QStyle::visualRect(option->direction, option->rect,
- QRect(ir.right() - mbiw - 2,
- option->rect.top() + (option->rect.height()/2) - (mbih/2),
- mbiw + 1, mbih + 1));
- proxy()->drawPrimitive(PE_IndicatorArrowDown, &newBtn, painter, widget);
- }
- return;
- }
- break;
-
- case CE_ProgressBarContents:
- if (const QStyleOptionProgressBar *bar
- = qstyleoption_cast<const QStyleOptionProgressBar *>(option)) {
- bool isIndeterminate = (bar->minimum == 0 && bar->maximum == 0);
- const bool vertical = !(bar->state & QStyle::State_Horizontal);
- const bool inverted = bar->invertedAppearance;
-
- if (isIndeterminate || (bar->progress > 0 && (bar->progress < bar->maximum) && d->transitionsEnabled())) {
- if (!d->animation(styleObject(option)))
- d->startAnimation(new QProgressStyleAnimation(d->animationFps, styleObject(option)));
- } else {
- d->stopAnimation(styleObject(option));
- }
-
- XPThemeData theme(widget, painter,
- QWindowsXPStylePrivate::ProgressTheme,
- vertical ? PP_FILLVERT : PP_FILL);
- theme.rect = option->rect;
- bool reverse = (bar->direction == Qt::LeftToRight && inverted) || (bar->direction == Qt::RightToLeft && !inverted);
- QTime current = d->animationTime();
-
- if (isIndeterminate) {
- if (QProgressStyleAnimation *a = qobject_cast<QProgressStyleAnimation *>(d->animation(styleObject(option)))) {
- int glowSize = 120;
- int animationWidth = glowSize * 2 + (vertical ? theme.rect.height() : theme.rect.width());
- int animOffset = a->startTime().msecsTo(current) / 4;
- if (animOffset > animationWidth)
- a->setStartTime(d->animationTime());
- painter->save();
- painter->setClipRect(theme.rect);
- QRect animRect;
- QSize pixmapSize(14, 14);
- if (vertical) {
- animRect = QRect(theme.rect.left(),
- inverted ? rect.top() - glowSize + animOffset :
- rect.bottom() + glowSize - animOffset,
- rect.width(), glowSize);
- pixmapSize.setHeight(animRect.height());
- } else {
- animRect = QRect(rect.left() - glowSize + animOffset,
- rect.top(), glowSize, rect.height());
- animRect = QStyle::visualRect(reverse ? Qt::RightToLeft : Qt::LeftToRight,
- option->rect, animRect);
- pixmapSize.setWidth(animRect.width());
- }
- QString name = QString::fromLatin1("qiprogress-%1-%2").arg(pixmapSize.width()).arg(pixmapSize.height());
- QPixmap pixmap;
- if (!QPixmapCache::find(name, &pixmap)) {
- QImage image(pixmapSize, QImage::Format_ARGB32);
- image.fill(Qt::transparent);
- QPainter imagePainter(&image);
- theme.painter = &imagePainter;
- theme.partId = vertical ? PP_FILLVERT : PP_FILL;
- theme.rect = QRect(QPoint(0,0), animRect.size());
- QLinearGradient alphaGradient(0, 0, vertical ? 0 : image.width(),
- vertical ? image.height() : 0);
- alphaGradient.setColorAt(0, QColor(0, 0, 0, 0));
- alphaGradient.setColorAt(0.5, QColor(0, 0, 0, 220));
- alphaGradient.setColorAt(1, QColor(0, 0, 0, 0));
- imagePainter.fillRect(image.rect(), alphaGradient);
- imagePainter.setCompositionMode(QPainter::CompositionMode_SourceIn);
- d->drawBackground(theme);
- imagePainter.end();
- pixmap = QPixmap::fromImage(image);
- QPixmapCache::insert(name, pixmap);
- }
- painter->drawPixmap(animRect, pixmap);
- painter->restore();
- }
- }
- else {
- qint64 progress = qMax<qint64>(bar->progress, bar->minimum); // workaround for bug in QProgressBar
-
- if (vertical) {
- int maxHeight = option->rect.height();
- int minHeight = 0;
- double vc6_workaround = ((progress - qint64(bar->minimum)) / qMax(double(1.0), double(qint64(bar->maximum) - qint64(bar->minimum))) * maxHeight);
- int height = isIndeterminate ? maxHeight: qMax(int(vc6_workaround), minHeight);
- theme.rect.setHeight(height);
- if (!inverted)
- theme.rect.moveTop(rect.height() - theme.rect.height());
- } else {
- int maxWidth = option->rect.width();
- int minWidth = 0;
- double vc6_workaround = ((progress - qint64(bar->minimum)) / qMax(double(1.0), double(qint64(bar->maximum) - qint64(bar->minimum))) * maxWidth);
- int width = isIndeterminate ? maxWidth : qMax(int(vc6_workaround), minWidth);
- theme.rect.setWidth(width);
- theme.rect = QStyle::visualRect(reverse ? Qt::RightToLeft : Qt::LeftToRight,
- option->rect, theme.rect);
- }
- d->drawBackground(theme);
-
- if (QProgressStyleAnimation *a = qobject_cast<QProgressStyleAnimation *>(d->animation(styleObject(option)))) {
- int glowSize = 140;
- int animationWidth = glowSize * 2 + (vertical ? theme.rect.height() : theme.rect.width());
- int animOffset = a->startTime().msecsTo(current) / 4;
- theme.partId = vertical ? PP_MOVEOVERLAYVERT : PP_MOVEOVERLAY;
- if (animOffset > animationWidth) {
- if (bar->progress < bar->maximum)
- a->setStartTime(d->animationTime());
- else
- d->stopAnimation(styleObject(option)); //we stop the glow motion only after it has
- //moved out of view
- }
- painter->save();
- painter->setClipRect(theme.rect);
- if (vertical) {
- theme.rect = QRect(theme.rect.left(),
- inverted ? rect.top() - glowSize + animOffset :
- rect.bottom() + glowSize - animOffset,
- rect.width(), glowSize);
- } else {
- theme.rect = QRect(rect.left() - glowSize + animOffset,rect.top(), glowSize, rect.height());
- theme.rect = QStyle::visualRect(reverse ? Qt::RightToLeft : Qt::LeftToRight, option->rect, theme.rect);
- }
- d->drawBackground(theme);
- painter->restore();
- }
- }
- }
- break;
-
- case CE_MenuBarItem:
- {
-
- if (const QStyleOptionMenuItem *mbi = qstyleoption_cast<const QStyleOptionMenuItem *>(option))
- {
- if (mbi->menuItemType == QStyleOptionMenuItem::DefaultItem)
- break;
-
- QPalette::ColorRole textRole = disabled ? QPalette::Text : QPalette::ButtonText;
- QPixmap pix = mbi->icon.pixmap(proxy()->pixelMetric(PM_SmallIconSize, option, widget), QIcon::Normal);
-
- int alignment = Qt::AlignCenter | Qt::TextShowMnemonic | Qt::TextDontClip | Qt::TextSingleLine;
- if (!proxy()->styleHint(SH_UnderlineShortcut, mbi, widget))
- alignment |= Qt::TextHideMnemonic;
-
- if (widget && mbi->palette.color(QPalette::Window) != Qt::transparent) { // Not needed for QtQuick Controls
- //The rect adjustment is a workaround for the menu not really filling its background.
- XPThemeData theme(widget, painter,
- QWindowsXPStylePrivate::MenuTheme,
- MENU_BARBACKGROUND, 0, option->rect.adjusted(-1, 0, 2, 1));
- d->drawBackground(theme);
- }
-
- int stateId = MBI_NORMAL;
- if (disabled)
- stateId = MBI_DISABLED;
- else if (pressed)
- stateId = MBI_PUSHED;
- else if (selected)
- stateId = MBI_HOT;
-
- XPThemeData theme2(widget, painter,
- QWindowsXPStylePrivate::MenuTheme,
- MENU_BARITEM, stateId, option->rect);
- d->drawBackground(theme2);
-
- if (!pix.isNull())
- drawItemPixmap(painter, mbi->rect, alignment, pix);
- else
- drawItemText(painter, mbi->rect, alignment, mbi->palette, mbi->state & State_Enabled, mbi->text, textRole);
- }
- }
- break;
-#if QT_CONFIG(menu)
- case CE_MenuItem:
- if (const QStyleOptionMenuItem *menuitem = qstyleoption_cast<const QStyleOptionMenuItem *>(option)) {
- // windows always has a check column, regardless whether we have an icon or not
- const qreal factor = QWindowsXPStylePrivate::nativeMetricScaleFactor(widget);
- int checkcol = qRound(qreal(25) * factor);
- const int gutterWidth = qRound(qreal(3) * factor);
- {
- XPThemeData theme(widget, nullptr, QWindowsXPStylePrivate::MenuTheme,
- MENU_POPUPCHECKBACKGROUND, MBI_HOT);
- XPThemeData themeSize = theme;
- themeSize.partId = MENU_POPUPCHECK;
- themeSize.stateId = 0;
- const QSizeF size = themeSize.size() * QWindowsStylePrivate::nativeMetricScaleFactor(widget);
- const QMarginsF margins = themeSize.margins() * QWindowsStylePrivate::nativeMetricScaleFactor(widget);
- checkcol = qMax(menuitem->maxIconWidth, qRound(gutterWidth + size.width() + margins.left() + margins.right()));
- }
- QRect rect = option->rect;
-
- //draw vertical menu line
- if (option->direction == Qt::LeftToRight)
- checkcol += rect.x();
- QPoint p1 = QStyle::visualPos(option->direction, menuitem->rect, QPoint(checkcol, rect.top()));
- QPoint p2 = QStyle::visualPos(option->direction, menuitem->rect, QPoint(checkcol, rect.bottom()));
- QRect gutterRect(p1.x(), p1.y(), gutterWidth, p2.y() - p1.y() + 1);
- XPThemeData theme2(widget, painter, QWindowsXPStylePrivate::MenuTheme,
- MENU_POPUPGUTTER, stateId, gutterRect);
- d->drawBackground(theme2);
-
- int x, y, w, h;
- menuitem->rect.getRect(&x, &y, &w, &h);
- int tab = menuitem->reservedShortcutWidth;
- bool dis = !(menuitem->state & State_Enabled);
- bool checked = menuitem->checkType != QStyleOptionMenuItem::NotCheckable
- ? menuitem->checked : false;
- bool act = menuitem->state & State_Selected;
-
- if (menuitem->menuItemType == QStyleOptionMenuItem::Separator) {
- int yoff = y-2 + h / 2;
- const int separatorSize = qRound(qreal(6) * QWindowsStylePrivate::nativeMetricScaleFactor(widget));
- QPoint p1 = QPoint(x + checkcol, yoff);
- QPoint p2 = QPoint(x + w + separatorSize, yoff);
- stateId = MBI_HOT;
- QRect subRect(p1.x() + (gutterWidth - menuitem->rect.x()), p1.y(),
- p2.x() - p1.x(), separatorSize);
- subRect = QStyle::visualRect(option->direction, option->rect, subRect );
- XPThemeData theme2(widget, painter,
- QWindowsXPStylePrivate::MenuTheme,
- MENU_POPUPSEPARATOR, stateId, subRect);
- d->drawBackground(theme2);
- return;
- }
-
- QRect vCheckRect = visualRect(option->direction, menuitem->rect, QRect(menuitem->rect.x(),
- menuitem->rect.y(), checkcol - (gutterWidth + menuitem->rect.x()), menuitem->rect.height()));
-
- if (act) {
- stateId = dis ? MBI_DISABLED : MBI_HOT;
- XPThemeData theme2(widget, painter,
- QWindowsXPStylePrivate::MenuTheme,
- MENU_POPUPITEM, stateId, option->rect);
- d->drawBackground(theme2);
- }
-
- if (checked) {
- XPThemeData theme(widget, painter,
- QWindowsXPStylePrivate::MenuTheme,
- MENU_POPUPCHECKBACKGROUND,
- menuitem->icon.isNull() ? MBI_HOT : MBI_PUSHED, vCheckRect);
- XPThemeData themeSize = theme;
- themeSize.partId = MENU_POPUPCHECK;
- themeSize.stateId = 0;
- const QSizeF size = themeSize.size() * QWindowsStylePrivate::nativeMetricScaleFactor(widget);
- const QMarginsF margins = themeSize.margins() * QWindowsStylePrivate::nativeMetricScaleFactor(widget);
- QRect checkRect(0, 0, qRound(size.width() + margins.left() + margins.right()),
- qRound(size.height() + margins.bottom() + margins.top()));
- checkRect.moveCenter(vCheckRect.center());
- theme.rect = checkRect;
-
- d->drawBackground(theme);
-
- if (menuitem->icon.isNull()) {
- checkRect = QRect(QPoint(0, 0), size.toSize());
- checkRect.moveCenter(theme.rect.center());
- theme.rect = checkRect;
-
- theme.partId = MENU_POPUPCHECK;
- bool bullet = menuitem->checkType & QStyleOptionMenuItem::Exclusive;
- if (dis)
- theme.stateId = bullet ? MC_BULLETDISABLED: MC_CHECKMARKDISABLED;
- else
- theme.stateId = bullet ? MC_BULLETNORMAL: MC_CHECKMARKNORMAL;
- d->drawBackground(theme);
- }
- }
-
- if (!menuitem->icon.isNull()) {
- QIcon::Mode mode = dis ? QIcon::Disabled : QIcon::Normal;
- if (act && !dis)
- mode = QIcon::Active;
- QPixmap pixmap;
- if (checked)
- pixmap = menuitem->icon.pixmap(proxy()->pixelMetric(PM_SmallIconSize, option, widget), mode, QIcon::On);
- else
- pixmap = menuitem->icon.pixmap(proxy()->pixelMetric(PM_SmallIconSize, option, widget), mode);
- QRect pmr(QPoint(0, 0), pixmap.deviceIndependentSize().toSize());
- pmr.moveCenter(vCheckRect.center());
- painter->setPen(menuitem->palette.text().color());
- painter->drawPixmap(pmr.topLeft(), pixmap);
- }
-
- painter->setPen(menuitem->palette.buttonText().color());
-
- const QColor textColor = menuitem->palette.text().color();
- if (dis)
- painter->setPen(textColor);
-
- int xm = windowsItemFrame + checkcol + windowsItemHMargin + (gutterWidth - menuitem->rect.x()) - 1;
- int xpos = menuitem->rect.x() + xm;
- QRect textRect(xpos, y + windowsItemVMargin, w - xm - windowsRightBorder - tab + 1, h - 2 * windowsItemVMargin);
- QRect vTextRect = visualRect(option->direction, menuitem->rect, textRect);
- QString s = menuitem->text;
- if (!s.isEmpty()) { // draw text
- painter->save();
- int t = s.indexOf(QLatin1Char('\t'));
- int text_flags = Qt::AlignVCenter | Qt::TextShowMnemonic | Qt::TextDontClip | Qt::TextSingleLine;
- if (!proxy()->styleHint(SH_UnderlineShortcut, menuitem, widget))
- text_flags |= Qt::TextHideMnemonic;
- text_flags |= Qt::AlignLeft;
- if (t >= 0) {
- QRect vShortcutRect = visualRect(option->direction, menuitem->rect,
- QRect(textRect.topRight(), QPoint(menuitem->rect.right(), textRect.bottom())));
- painter->drawText(vShortcutRect, text_flags, s.mid(t + 1));
- s = s.left(t);
- }
- QFont font = menuitem->font;
- if (menuitem->menuItemType == QStyleOptionMenuItem::DefaultItem)
- font.setBold(true);
- painter->setFont(font);
- painter->setPen(textColor);
- painter->drawText(vTextRect, text_flags, s.left(t));
- painter->restore();
- }
- if (menuitem->menuItemType == QStyleOptionMenuItem::SubMenu) {// draw sub menu arrow
- int dim = (h - 2 * windowsItemFrame) / 2;
- PrimitiveElement arrow;
- arrow = (option->direction == Qt::RightToLeft) ? PE_IndicatorArrowLeft : PE_IndicatorArrowRight;
- xpos = x + w - windowsArrowHMargin - windowsItemFrame - dim;
- QRect vSubMenuRect = visualRect(option->direction, menuitem->rect, QRect(xpos, y + h / 2 - dim / 2, dim, dim));
- QStyleOptionMenuItem newMI = *menuitem;
- newMI.rect = vSubMenuRect;
- newMI.state = dis ? State_None : State_Enabled;
- proxy()->drawPrimitive(arrow, &newMI, painter, widget);
- }
- }
- break;
-#endif // QT_CONFIG(menu)
- case CE_HeaderSection:
- if (const QStyleOptionHeader *header = qstyleoption_cast<const QStyleOptionHeader *>(option)) {
- partId = HP_HEADERITEM;
- if (flags & State_Sunken)
- stateId = HIS_PRESSED;
- else if (flags & State_MouseOver)
- stateId = HIS_HOT;
- else
- stateId = HIS_NORMAL;
-
- if (header->sortIndicator != QStyleOptionHeader::None)
- stateId += 3;
-
- XPThemeData theme(widget, painter,
- QWindowsXPStylePrivate::HeaderTheme,
- partId, stateId, option->rect);
- d->drawBackground(theme);
- }
- break;
- case CE_MenuBarEmptyArea:
- {
- stateId = MBI_NORMAL;
- if (!(state & State_Enabled))
- stateId = MBI_DISABLED;
- XPThemeData theme(widget, painter,
- QWindowsXPStylePrivate::MenuTheme,
- MENU_BARBACKGROUND, stateId, option->rect);
- d->drawBackground(theme);
- }
- break;
- case CE_ToolBar:
- if (const QStyleOptionToolBar *toolbar = qstyleoption_cast<const QStyleOptionToolBar *>(option)) {
- QPalette pal = option->palette;
- pal.setColor(QPalette::Dark, option->palette.window().color().darker(130));
- QStyleOptionToolBar copyOpt = *toolbar;
- copyOpt.palette = pal;
- QWindowsStyle::drawControl(element, &copyOpt, painter, widget);
- }
- break;
-#if QT_CONFIG(dockwidget)
- case CE_DockWidgetTitle:
- if (const QStyleOptionDockWidget *dwOpt = qstyleoption_cast<const QStyleOptionDockWidget *>(option)) {
- const QDockWidget *dockWidget = qobject_cast<const QDockWidget *>(widget);
- QRect rect = option->rect;
- if (dockWidget && dockWidget->isFloating()) {
- QWindowsXPStyle::drawControl(element, option, painter, widget);
- break; //otherwise fall through
- }
-
- const bool verticalTitleBar = dwOpt->verticalTitleBar;
-
- if (verticalTitleBar) {
- rect = rect.transposed();
-
- painter->translate(rect.left() - 1, rect.top() + rect.width());
- painter->rotate(-90);
- painter->translate(-rect.left() + 1, -rect.top());
- }
-
- painter->setBrush(option->palette.window().color().darker(110));
- painter->setPen(option->palette.window().color().darker(130));
- painter->drawRect(rect.adjusted(0, 1, -1, -3));
-
- int buttonMargin = 4;
- int mw = proxy()->pixelMetric(QStyle::PM_DockWidgetTitleMargin, dwOpt, widget);
- int fw = proxy()->pixelMetric(PM_DockWidgetFrameWidth, dwOpt, widget);
- const QDockWidget *dw = qobject_cast<const QDockWidget *>(widget);
- bool isFloating = dw && dw->isFloating();
-
- QRect r = option->rect.adjusted(0, 2, -1, -3);
- QRect titleRect = r;
-
- if (dwOpt->closable) {
- QSize sz = proxy()->standardIcon(QStyle::SP_TitleBarCloseButton, dwOpt, widget).actualSize(QSize(10, 10));
- titleRect.adjust(0, 0, -sz.width() - mw - buttonMargin, 0);
- }
-
- if (dwOpt->floatable) {
- QSize sz = proxy()->standardIcon(QStyle::SP_TitleBarMaxButton, dwOpt, widget).actualSize(QSize(10, 10));
- titleRect.adjust(0, 0, -sz.width() - mw - buttonMargin, 0);
- }
-
- if (isFloating) {
- titleRect.adjust(0, -fw, 0, 0);
- if (widget && widget->windowIcon().cacheKey() != QApplication::windowIcon().cacheKey())
- titleRect.adjust(titleRect.height() + mw, 0, 0, 0);
- } else {
- titleRect.adjust(mw, 0, 0, 0);
- if (!dwOpt->floatable && !dwOpt->closable)
- titleRect.adjust(0, 0, -mw, 0);
- }
- if (!verticalTitleBar)
- titleRect = visualRect(dwOpt->direction, r, titleRect);
-
- if (!dwOpt->title.isEmpty()) {
- QString titleText = painter->fontMetrics().elidedText(dwOpt->title, Qt::ElideRight,
- verticalTitleBar ? titleRect.height() : titleRect.width());
- const int indent = 4;
- drawItemText(painter, rect.adjusted(indent + 1, 1, -indent - 1, -1),
- Qt::AlignLeft | Qt::AlignVCenter | Qt::TextShowMnemonic,
- dwOpt->palette,
- dwOpt->state & State_Enabled, titleText,
- QPalette::WindowText);
- }
- }
- break;
-#endif // QT_CONFIG(dockwidget)
-#if QT_CONFIG(itemviews)
- case CE_ItemViewItem:
- {
- const QStyleOptionViewItem *vopt;
-
- const QAbstractItemView *view = qobject_cast<const QAbstractItemView *>(widget);
- bool newStyle = true;
-
- if (qobject_cast<const QTableView*>(widget))
- newStyle = false;
-
- if (newStyle && view && (vopt = qstyleoption_cast<const QStyleOptionViewItem *>(option))) {
- /*
- // We cannot currently get the correct selection color for "explorer style" views
- COLORREF cref = 0;
- XPThemeData theme(d->treeViewHelper(), 0, QLatin1String("LISTVIEW"), 0, 0);
- unsigned int res = GetThemeColor(theme.handle(), LVP_LISTITEM, LISS_SELECTED, TMT_TEXTCOLOR, &cref);
- QColor textColor(GetRValue(cref), GetGValue(cref), GetBValue(cref));
- */
- QPalette palette = vopt->palette;
- palette.setColor(QPalette::All, QPalette::HighlightedText, palette.color(QPalette::Active, QPalette::Text));
- // Note that setting a saturated color here results in ugly XOR colors in the focus rect
- palette.setColor(QPalette::All, QPalette::Highlight, palette.base().color().darker(108));
- QStyleOptionViewItem adjustedOption = *vopt;
- adjustedOption.palette = palette;
- // We hide the focusrect in singleselection as it is not required
- if ((view->selectionMode() == QAbstractItemView::SingleSelection)
- && !(vopt->state & State_KeyboardFocusChange))
- adjustedOption.state &= ~State_HasFocus;
- QWindowsXPStyle::drawControl(element, &adjustedOption, painter, widget);
- } else {
- QWindowsXPStyle::drawControl(element, option, painter, widget);
- }
- break;
- }
-#endif // QT_CONFIG(itemviews)
-#if QT_CONFIG(combobox)
- case CE_ComboBoxLabel:
- QCommonStyle::drawControl(element, option, painter, widget);
- break;
-#endif // QT_CONFIG(combobox)
- default:
- QWindowsXPStyle::drawControl(element, option, painter, widget);
- break;
- }
-}
-
-/*!
- \internal
-
- see drawPrimitive for comments on the animation support
-
- */
-void QWindowsVistaStyle::drawComplexControl(ComplexControl control, const QStyleOptionComplex *option,
- QPainter *painter, const QWidget *widget) const
-{
- QWindowsVistaStylePrivate *d = const_cast<QWindowsVistaStylePrivate*>(d_func());
- if (!QWindowsVistaStylePrivate::useVista()) {
- QWindowsStyle::drawComplexControl(control, option, painter, widget);
- return;
- }
-
- State state = option->state;
- SubControls sub = option->subControls;
- QRect r = option->rect;
-
- int partId = 0;
- int stateId = 0;
-
- State flags = option->state;
- if (widget && widget->testAttribute(Qt::WA_UnderMouse) && widget->isActiveWindow())
- flags |= State_MouseOver;
-
- if (d->transitionsEnabled() && canAnimate(option))
- {
-
- if (control == CC_ScrollBar || control == CC_SpinBox || control == CC_ComboBox) {
-
- QObject *styleObject = option->styleObject; // Can be widget or qquickitem
-
- int oldState = styleObject->property("_q_stylestate").toInt();
- int oldActiveControls = styleObject->property("_q_stylecontrols").toInt();
-
- QRect oldRect = styleObject->property("_q_stylerect").toRect();
- styleObject->setProperty("_q_stylestate", int(option->state));
- styleObject->setProperty("_q_stylecontrols", int(option->activeSubControls));
- styleObject->setProperty("_q_stylerect", option->rect);
-
- bool doTransition = ((state & State_Sunken) != (oldState & State_Sunken) ||
- (state & State_On) != (oldState & State_On) ||
- (state & State_MouseOver) != (oldState & State_MouseOver) ||
- oldActiveControls != int(option->activeSubControls));
-
- if (qstyleoption_cast<const QStyleOptionSlider *>(option)) {
- QRect oldSliderPos = styleObject->property("_q_stylesliderpos").toRect();
- QRect currentPos = proxy()->subControlRect(CC_ScrollBar, option, SC_ScrollBarSlider, widget);
- styleObject->setProperty("_q_stylesliderpos", currentPos);
- if (oldSliderPos != currentPos) {
- doTransition = false;
- d->stopAnimation(styleObject);
- }
- } else if (control == CC_SpinBox) {
- //spinboxes have a transition when focus changes
- if (!doTransition)
- doTransition = (state & State_HasFocus) != (oldState & State_HasFocus);
- }
-
- if (oldRect != option->rect) {
- doTransition = false;
- d->stopAnimation(styleObject);
- }
-
- if (doTransition) {
- QImage startImage = createAnimationBuffer(option, widget);
- QPainter startPainter(&startImage);
-
- QImage endImage = createAnimationBuffer(option, widget);
- QPainter endPainter(&endImage);
-
- QWindowsVistaAnimation *anim = qobject_cast<QWindowsVistaAnimation *>(d->animation(styleObject));
- QWindowsVistaTransition *t = new QWindowsVistaTransition(styleObject);
-
- // Draw the image that ends the animation by using the current styleoption
- QStyleOptionComplex *styleOption = qstyleoption_cast<QStyleOptionComplex*>(clonedAnimationStyleOption(option));
-
- styleObject->setProperty("_q_no_animation", true);
-
- // Draw transition source
- if (!anim) {
- styleOption->state = QStyle::State(oldState);
- styleOption->activeSubControls = QStyle::SubControl(oldActiveControls);
- proxy()->drawComplexControl(control, styleOption, &startPainter, widget);
- } else {
- anim->paint(&startPainter, option);
- }
- t->setStartImage(startImage);
-
- // Draw transition target
- styleOption->state = option->state;
- styleOption->activeSubControls = option->activeSubControls;
- proxy()->drawComplexControl(control, styleOption, &endPainter, widget);
-
- styleObject->setProperty("_q_no_animation", false);
-
- t->setEndImage(endImage);
- t->setStartTime(d->animationTime());
-
- if (option->state & State_MouseOver || option->state & State_Sunken)
- t->setDuration(150);
- else
- t->setDuration(500);
-
- deleteClonedAnimationStyleOption(styleOption);
- d->startAnimation(t);
- }
- if (QWindowsVistaAnimation *anim = qobject_cast<QWindowsVistaAnimation *>(d->animation(styleObject))) {
- anim->paint(painter, option);
- return;
- }
- }
- }
-
- switch (control) {
- case CC_ComboBox:
- if (const QStyleOptionComboBox *cmb = qstyleoption_cast<const QStyleOptionComboBox *>(option))
- {
- if (cmb->editable) {
- if (sub & SC_ComboBoxEditField) {
- partId = EP_EDITBORDER_NOSCROLL;
- if (!(flags & State_Enabled))
- stateId = ETS_DISABLED;
- else if (flags & State_MouseOver)
- stateId = ETS_HOT;
- else if (flags & State_HasFocus)
- stateId = ETS_FOCUSED;
- else
- stateId = ETS_NORMAL;
-
- XPThemeData theme(widget, painter,
- QWindowsXPStylePrivate::EditTheme,
- partId, stateId, r);
-
- d->drawBackground(theme);
- }
- if (sub & SC_ComboBoxArrow) {
- QRect subRect = proxy()->subControlRect(CC_ComboBox, option, SC_ComboBoxArrow, widget);
- XPThemeData theme(widget, painter, QWindowsXPStylePrivate::ComboboxTheme);
- theme.rect = subRect;
- partId = option->direction == Qt::RightToLeft ? CP_DROPDOWNBUTTONLEFT : CP_DROPDOWNBUTTONRIGHT;
-
- if (!(cmb->state & State_Enabled))
- stateId = CBXS_DISABLED;
- else if (cmb->state & State_Sunken || cmb->state & State_On)
- stateId = CBXS_PRESSED;
- else if (cmb->state & State_MouseOver && option->activeSubControls & SC_ComboBoxArrow)
- stateId = CBXS_HOT;
- else
- stateId = CBXS_NORMAL;
-
- theme.partId = partId;
- theme.stateId = stateId;
- d->drawBackground(theme);
- }
-
- } else {
- if (sub & SC_ComboBoxFrame) {
- XPThemeData theme(widget, painter, QWindowsXPStylePrivate::ComboboxTheme);
- theme.rect = option->rect;
- theme.partId = CP_READONLY;
- if (!(cmb->state & State_Enabled))
- theme.stateId = CBXS_DISABLED;
- else if (cmb->state & State_Sunken || cmb->state & State_On)
- theme.stateId = CBXS_PRESSED;
- else if (cmb->state & State_MouseOver)
- theme.stateId = CBXS_HOT;
- else
- theme.stateId = CBXS_NORMAL;
- d->drawBackground(theme);
- }
- if (sub & SC_ComboBoxArrow) {
- XPThemeData theme(widget, painter, QWindowsXPStylePrivate::ComboboxTheme);
- theme.rect = proxy()->subControlRect(CC_ComboBox, option, SC_ComboBoxArrow, widget);
- theme.partId = option->direction == Qt::RightToLeft ? CP_DROPDOWNBUTTONLEFT : CP_DROPDOWNBUTTONRIGHT;
- if (!(cmb->state & State_Enabled))
- theme.stateId = CBXS_DISABLED;
- else
- theme.stateId = CBXS_NORMAL;
- d->drawBackground(theme);
- }
- if ((sub & SC_ComboBoxEditField) && (flags & State_HasFocus)) {
- QStyleOptionFocusRect fropt;
- fropt.QStyleOption::operator=(*cmb);
- fropt.rect = proxy()->subControlRect(CC_ComboBox, option, SC_ComboBoxEditField, widget);
- proxy()->drawPrimitive(PE_FrameFocusRect, &fropt, painter, widget);
- }
- }
- }
- break;
- case CC_ScrollBar:
- if (const QStyleOptionSlider *scrollbar = qstyleoption_cast<const QStyleOptionSlider *>(option))
- {
- XPThemeData theme(widget, painter, QWindowsXPStylePrivate::ScrollBarTheme);
- bool maxedOut = (scrollbar->maximum == scrollbar->minimum);
- if (maxedOut)
- flags &= ~State_Enabled;
-
- bool isHorz = flags & State_Horizontal;
- bool isRTL = option->direction == Qt::RightToLeft;
- if (sub & SC_ScrollBarAddLine) {
- theme.rect = proxy()->subControlRect(CC_ScrollBar, option, SC_ScrollBarAddLine, widget);
- partId = SBP_ARROWBTN;
- if (!(flags & State_Enabled))
- stateId = (isHorz ? (isRTL ? ABS_LEFTDISABLED : ABS_RIGHTDISABLED) : ABS_DOWNDISABLED);
- else if (scrollbar->activeSubControls & SC_ScrollBarAddLine && (scrollbar->state & State_Sunken))
- stateId = (isHorz ? (isRTL ? ABS_LEFTPRESSED : ABS_RIGHTPRESSED) : ABS_DOWNPRESSED);
- else if (scrollbar->activeSubControls & SC_ScrollBarAddLine && (scrollbar->state & State_MouseOver))
- stateId = (isHorz ? (isRTL ? ABS_LEFTHOT : ABS_RIGHTHOT) : ABS_DOWNHOT);
- else if (scrollbar->state & State_MouseOver)
- stateId = (isHorz ? (isRTL ? ABS_LEFTHOVER : ABS_RIGHTHOVER) : ABS_DOWNHOVER);
- else
- stateId = (isHorz ? (isRTL ? ABS_LEFTNORMAL : ABS_RIGHTNORMAL) : ABS_DOWNNORMAL);
- theme.partId = partId;
- theme.stateId = stateId;
- d->drawBackground(theme);
- }
- if (sub & SC_ScrollBarSubLine) {
- theme.rect = proxy()->subControlRect(CC_ScrollBar, option, SC_ScrollBarSubLine, widget);
- partId = SBP_ARROWBTN;
- if (!(flags & State_Enabled))
- stateId = (isHorz ? (isRTL ? ABS_RIGHTDISABLED : ABS_LEFTDISABLED) : ABS_UPDISABLED);
- else if (scrollbar->activeSubControls & SC_ScrollBarSubLine && (scrollbar->state & State_Sunken))
- stateId = (isHorz ? (isRTL ? ABS_RIGHTPRESSED : ABS_LEFTPRESSED) : ABS_UPPRESSED);
- else if (scrollbar->activeSubControls & SC_ScrollBarSubLine && (scrollbar->state & State_MouseOver))
- stateId = (isHorz ? (isRTL ? ABS_RIGHTHOT : ABS_LEFTHOT) : ABS_UPHOT);
- else if (scrollbar->state & State_MouseOver)
- stateId = (isHorz ? (isRTL ? ABS_RIGHTHOVER : ABS_LEFTHOVER) : ABS_UPHOVER);
- else
- stateId = (isHorz ? (isRTL ? ABS_RIGHTNORMAL : ABS_LEFTNORMAL) : ABS_UPNORMAL);
- theme.partId = partId;
- theme.stateId = stateId;
- d->drawBackground(theme);
- }
- if (maxedOut) {
- theme.rect = proxy()->subControlRect(CC_ScrollBar, option, SC_ScrollBarSlider, widget);
- theme.rect = theme.rect.united(proxy()->subControlRect(CC_ScrollBar, option, SC_ScrollBarSubPage, widget));
- theme.rect = theme.rect.united(proxy()->subControlRect(CC_ScrollBar, option, SC_ScrollBarAddPage, widget));
- partId = flags & State_Horizontal ? SBP_LOWERTRACKHORZ : SBP_LOWERTRACKVERT;
- stateId = SCRBS_DISABLED;
- theme.partId = partId;
- theme.stateId = stateId;
- d->drawBackground(theme);
- } else {
- if (sub & SC_ScrollBarSubPage) {
- theme.rect = proxy()->subControlRect(CC_ScrollBar, option, SC_ScrollBarSubPage, widget);
- partId = flags & State_Horizontal ? SBP_UPPERTRACKHORZ : SBP_UPPERTRACKVERT;
- if (!(flags & State_Enabled))
- stateId = SCRBS_DISABLED;
- else if (scrollbar->activeSubControls & SC_ScrollBarSubPage && (scrollbar->state & State_Sunken))
- stateId = SCRBS_PRESSED;
- else if (scrollbar->activeSubControls & SC_ScrollBarSubPage && (scrollbar->state & State_MouseOver))
- stateId = SCRBS_HOT;
- else
- stateId = SCRBS_NORMAL;
- theme.partId = partId;
- theme.stateId = stateId;
- d->drawBackground(theme);
- }
- if (sub & SC_ScrollBarAddPage) {
- theme.rect = proxy()->subControlRect(CC_ScrollBar, option, SC_ScrollBarAddPage, widget);
- partId = flags & State_Horizontal ? SBP_LOWERTRACKHORZ : SBP_LOWERTRACKVERT;
- if (!(flags & State_Enabled))
- stateId = SCRBS_DISABLED;
- else if (scrollbar->activeSubControls & SC_ScrollBarAddPage && (scrollbar->state & State_Sunken))
- stateId = SCRBS_PRESSED;
- else if (scrollbar->activeSubControls & SC_ScrollBarAddPage && (scrollbar->state & State_MouseOver))
- stateId = SCRBS_HOT;
- else
- stateId = SCRBS_NORMAL;
- theme.partId = partId;
- theme.stateId = stateId;
- d->drawBackground(theme);
- }
- if (sub & SC_ScrollBarSlider) {
- theme.rect = proxy()->subControlRect(CC_ScrollBar, option, SC_ScrollBarSlider, widget);
- if (!(flags & State_Enabled))
- stateId = SCRBS_DISABLED;
- else if (scrollbar->activeSubControls & SC_ScrollBarSlider && (scrollbar->state & State_Sunken))
- stateId = SCRBS_PRESSED;
- else if (scrollbar->activeSubControls & SC_ScrollBarSlider && (scrollbar->state & State_MouseOver))
- stateId = SCRBS_HOT;
- else if (option->state & State_MouseOver)
- stateId = SCRBS_HOVER;
- else
- stateId = SCRBS_NORMAL;
-
- // Draw handle
- theme.partId = flags & State_Horizontal ? SBP_THUMBBTNHORZ : SBP_THUMBBTNVERT;
- theme.stateId = stateId;
- d->drawBackground(theme);
- }
- }
- }
- break;
-#if QT_CONFIG(spinbox)
- case CC_SpinBox:
- if (const QStyleOptionSpinBox *sb = qstyleoption_cast<const QStyleOptionSpinBox *>(option))
- {
- XPThemeData theme(widget, painter, QWindowsXPStylePrivate::SpinTheme);
- if (sb->frame && (sub & SC_SpinBoxFrame)) {
- partId = EP_EDITBORDER_NOSCROLL;
- if (!(flags & State_Enabled))
- stateId = ETS_DISABLED;
- else if (flags & State_MouseOver)
- stateId = ETS_HOT;
- else if (flags & State_HasFocus)
- stateId = ETS_SELECTED;
- else
- stateId = ETS_NORMAL;
-
- XPThemeData ftheme(widget, painter,
- QWindowsXPStylePrivate::EditTheme,
- partId, stateId, r);
- // The spinbox in Windows QStyle is drawn with frameless QLineEdit inside it
- // That however breaks with QtQuickControls where this results in transparent
- // spinbox background, so if there's no "widget" passed (QtQuickControls case),
- // let ftheme.noContent be false, which fixes the spinbox rendering in QQC
- ftheme.noContent = (widget != nullptr);
- d->drawBackground(ftheme);
- }
- if (sub & SC_SpinBoxUp) {
- theme.rect = proxy()->subControlRect(CC_SpinBox, option, SC_SpinBoxUp, widget).adjusted(0, 0, 0, 1);
- partId = SPNP_UP;
- if (!(sb->stepEnabled & QAbstractSpinBox::StepUpEnabled) || !(flags & State_Enabled))
- stateId = UPS_DISABLED;
- else if (sb->activeSubControls == SC_SpinBoxUp && (sb->state & State_Sunken))
- stateId = UPS_PRESSED;
- else if (sb->activeSubControls == SC_SpinBoxUp && (sb->state & State_MouseOver))
- stateId = UPS_HOT;
- else
- stateId = UPS_NORMAL;
- theme.partId = partId;
- theme.stateId = stateId;
- d->drawBackground(theme);
- }
- if (sub & SC_SpinBoxDown) {
- theme.rect = proxy()->subControlRect(CC_SpinBox, option, SC_SpinBoxDown, widget);
- partId = SPNP_DOWN;
- if (!(sb->stepEnabled & QAbstractSpinBox::StepDownEnabled) || !(flags & State_Enabled))
- stateId = DNS_DISABLED;
- else if (sb->activeSubControls == SC_SpinBoxDown && (sb->state & State_Sunken))
- stateId = DNS_PRESSED;
- else if (sb->activeSubControls == SC_SpinBoxDown && (sb->state & State_MouseOver))
- stateId = DNS_HOT;
- else
- stateId = DNS_NORMAL;
- theme.partId = partId;
- theme.stateId = stateId;
- d->drawBackground(theme);
- }
- }
- break;
-#endif // QT_CONFIG(spinbox)
- default:
- QWindowsXPStyle::drawComplexControl(control, option, painter, widget);
- break;
- }
-}
-
-/*!
- \internal
- */
-QSize QWindowsVistaStyle::sizeFromContents(ContentsType type, const QStyleOption *option,
- const QSize &size, const QWidget *widget) const
-{
- if (!QWindowsVistaStylePrivate::useVista())
- return QWindowsStyle::sizeFromContents(type, option, size, widget);
-
- QSize sz(size);
- switch (type) {
- case CT_MenuItem:
- sz = QWindowsXPStyle::sizeFromContents(type, option, size, widget);
- int minimumHeight;
- {
- XPThemeData theme(widget, nullptr,
- QWindowsXPStylePrivate::MenuTheme,
- MENU_POPUPCHECKBACKGROUND, MBI_HOT);
- XPThemeData themeSize = theme;
- themeSize.partId = MENU_POPUPCHECK;
- themeSize.stateId = 0;
- const QSizeF size = themeSize.size() * QWindowsStylePrivate::nativeMetricScaleFactor(widget);
- const QMarginsF margins = themeSize.margins() * QWindowsStylePrivate::nativeMetricScaleFactor(widget);
- minimumHeight = qMax(qRound(size.height() + margins.bottom() + margins.top()), sz.height());
- sz.rwidth() += qRound(size.width() + margins.left() + margins.right());
- }
-
- if (const QStyleOptionMenuItem *menuitem = qstyleoption_cast<const QStyleOptionMenuItem *>(option)) {
- if (menuitem->menuItemType != QStyleOptionMenuItem::Separator)
- sz.setHeight(minimumHeight);
- }
- return sz;
-#if QT_CONFIG(menubar)
- case CT_MenuBarItem:
- if (!sz.isEmpty())
- sz += QSize(windowsItemHMargin * 5 + 1, 5);
- return sz;
-#endif
- case CT_ItemViewItem:
- sz = QWindowsXPStyle::sizeFromContents(type, option, size, widget);
- sz.rheight() += 2;
- return sz;
- case CT_SpinBox:
- {
- //Spinbox adds frame twice
- sz = QWindowsStyle::sizeFromContents(type, option, size, widget);
- int border = proxy()->pixelMetric(PM_SpinBoxFrameWidth, option, widget);
- sz -= QSize(2*border, 2*border);
- }
- return sz;
- case CT_HeaderSection:
- {
- // When there is a sort indicator it adds to the width but it is shown
- // above the text natively and not on the side
- if (QStyleOptionHeader *hdr = qstyleoption_cast<QStyleOptionHeader *>(const_cast<QStyleOption *>(option))) {
- QStyleOptionHeader::SortIndicator sortInd = hdr->sortIndicator;
- hdr->sortIndicator = QStyleOptionHeader::None;
- sz = QWindowsXPStyle::sizeFromContents(type, hdr, size, widget);
- hdr->sortIndicator = sortInd;
- return sz;
- }
- break;
- }
- default:
- break;
- }
- return QWindowsXPStyle::sizeFromContents(type, option, size, widget);
-}
-
-/*!
- \internal
- */
-QRect QWindowsVistaStyle::subElementRect(SubElement element, const QStyleOption *option, const QWidget *widget) const
-{
- if (!QWindowsVistaStylePrivate::useVista())
- return QWindowsStyle::subElementRect(element, option, widget);
-
- QRect rect = QWindowsXPStyle::subElementRect(element, option, widget);
- switch (element) {
-
- case SE_PushButtonContents:
- if (const QStyleOptionButton *btn = qstyleoption_cast<const QStyleOptionButton *>(option)) {
- MARGINS borderSize;
- const HTHEME theme = OpenThemeData(widget ? QWindowsVistaStylePrivate::winId(widget) : nullptr, L"Button");
- if (theme) {
- int stateId = PBS_NORMAL;
- if (!(option->state & State_Enabled))
- stateId = PBS_DISABLED;
- else if (option->state & State_Sunken)
- stateId = PBS_PRESSED;
- else if (option->state & State_MouseOver)
- stateId = PBS_HOT;
- else if (btn->features & QStyleOptionButton::DefaultButton)
- stateId = PBS_DEFAULTED;
-
- int border = proxy()->pixelMetric(PM_DefaultFrameWidth, btn, widget);
- rect = option->rect.adjusted(border, border, -border, -border);
-
- if (SUCCEEDED(GetThemeMargins(theme, nullptr, BP_PUSHBUTTON, stateId, TMT_CONTENTMARGINS, nullptr, &borderSize))) {
- rect.adjust(borderSize.cxLeftWidth, borderSize.cyTopHeight,
- -borderSize.cxRightWidth, -borderSize.cyBottomHeight);
- rect = visualRect(option->direction, option->rect, rect);
- }
- }
- }
- break;
-
- case SE_HeaderArrow:
- {
- QRect r = rect;
- int h = option->rect.height();
- int w = option->rect.width();
- int x = option->rect.x();
- int y = option->rect.y();
- int margin = proxy()->pixelMetric(QStyle::PM_HeaderMargin, option, widget);
-
- XPThemeData theme(widget, nullptr,
- QWindowsXPStylePrivate::HeaderTheme,
- HP_HEADERSORTARROW, HSAS_SORTEDDOWN, option->rect);
-
- int arrowWidth = 13;
- int arrowHeight = 5;
- if (theme.isValid()) {
- const QSizeF size = theme.size() * QWindowsStylePrivate::nativeMetricScaleFactor(widget);
- if (!size.isEmpty()) {
- arrowWidth = qRound(size.width());
- arrowHeight = qRound(size.height());
- }
- }
- if (option->state & State_Horizontal) {
- r.setRect(x + w/2 - arrowWidth/2, y , arrowWidth, arrowHeight);
- } else {
- int vert_size = w / 2;
- r.setRect(x + 5, y + h - margin * 2 - vert_size,
- w - margin * 2 - 5, vert_size);
- }
- rect = visualRect(option->direction, option->rect, r);
- }
- break;
-
- case SE_HeaderLabel:
- {
- int margin = proxy()->pixelMetric(QStyle::PM_HeaderMargin, option, widget);
- QRect r = option->rect;
- r.setRect(option->rect.x() + margin, option->rect.y() + margin,
- option->rect.width() - margin * 2, option->rect.height() - margin * 2);
- if (const QStyleOptionHeader *header = qstyleoption_cast<const QStyleOptionHeader *>(option)) {
- // Subtract width needed for arrow, if there is one
- if (header->sortIndicator != QStyleOptionHeader::None) {
- if (!(option->state & State_Horizontal)) //horizontal arrows are positioned on top
- r.setHeight(r.height() - (option->rect.width() / 2) - (margin * 2));
- }
- }
- rect = visualRect(option->direction, option->rect, r);
- }
- break;
- case SE_ProgressBarContents:
- rect = QCommonStyle::subElementRect(SE_ProgressBarGroove, option, widget);
- break;
- case SE_ItemViewItemDecoration:
- if (qstyleoption_cast<const QStyleOptionViewItem *>(option))
- rect.adjust(-2, 0, 2, 0);
- break;
- case SE_ItemViewItemFocusRect:
- if (const QStyleOptionViewItem *vopt = qstyleoption_cast<const QStyleOptionViewItem *>(option)) {
- QRect textRect = subElementRect(QStyle::SE_ItemViewItemText, option, widget);
- QRect displayRect = subElementRect(QStyle::SE_ItemViewItemDecoration, option, widget);
- if (!vopt->icon.isNull())
- rect = textRect.united(displayRect);
- else
- rect = textRect;
- rect = rect.adjusted(1, 0, -1, 0);
- }
- break;
- default:
- break;
- }
- return rect;
-}
-
-
-/*
- This function is used by subControlRect to check if a button
- should be drawn for the given subControl given a set of window flags.
-*/
-static bool buttonVisible(const QStyle::SubControl sc, const QStyleOptionTitleBar *tb){
-
- bool isMinimized = tb->titleBarState & Qt::WindowMinimized;
- bool isMaximized = tb->titleBarState & Qt::WindowMaximized;
- const auto flags = tb->titleBarFlags;
- bool retVal = false;
- switch (sc) {
- case QStyle::SC_TitleBarContextHelpButton:
- if (flags & Qt::WindowContextHelpButtonHint)
- retVal = true;
- break;
- case QStyle::SC_TitleBarMinButton:
- if (!isMinimized && (flags & Qt::WindowMinimizeButtonHint))
- retVal = true;
- break;
- case QStyle::SC_TitleBarNormalButton:
- if (isMinimized && (flags & Qt::WindowMinimizeButtonHint))
- retVal = true;
- else if (isMaximized && (flags & Qt::WindowMaximizeButtonHint))
- retVal = true;
- break;
- case QStyle::SC_TitleBarMaxButton:
- if (!isMaximized && (flags & Qt::WindowMaximizeButtonHint))
- retVal = true;
- break;
- case QStyle::SC_TitleBarShadeButton:
- if (!isMinimized && flags & Qt::WindowShadeButtonHint)
- retVal = true;
- break;
- case QStyle::SC_TitleBarUnshadeButton:
- if (isMinimized && flags & Qt::WindowShadeButtonHint)
- retVal = true;
- break;
- case QStyle::SC_TitleBarCloseButton:
- if (flags & Qt::WindowSystemMenuHint)
- retVal = true;
- break;
- case QStyle::SC_TitleBarSysMenu:
- if (flags & Qt::WindowSystemMenuHint)
- retVal = true;
- break;
- default :
- retVal = true;
- }
- return retVal;
-}
-
-
-/*! \internal */
-int QWindowsVistaStyle::styleHint(StyleHint hint, const QStyleOption *option, const QWidget *widget,
- QStyleHintReturn *returnData) const
-{
- QWindowsVistaStylePrivate *d = const_cast<QWindowsVistaStylePrivate*>(d_func());
- int ret = 0;
- switch (hint) {
- case SH_MessageBox_CenterButtons:
- ret = false;
- break;
- case SH_ToolTip_Mask:
- if (option) {
- if (QStyleHintReturnMask *mask = qstyleoption_cast<QStyleHintReturnMask*>(returnData)) {
- ret = true;
- XPThemeData themeData(widget, nullptr,
- QWindowsXPStylePrivate::ToolTipTheme,
- TTP_STANDARD, TTSS_NORMAL, option->rect);
- mask->region = d->region(themeData);
- }
- }
- break;
- case SH_Table_GridLineColor:
- if (option)
- ret = int(option->palette.color(QPalette::Base).darker(118).rgba());
- else
- ret = -1;
- break;
- case SH_Header_ArrowAlignment:
- ret = Qt::AlignTop | Qt::AlignHCenter;
- break;
- default:
- ret = QWindowsXPStyle::styleHint(hint, option, widget, returnData);
- break;
- }
- return ret;
-}
-
-
-/*!
- \internal
- */
-QRect QWindowsVistaStyle::subControlRect(ComplexControl control, const QStyleOptionComplex *option,
- SubControl subControl, const QWidget *widget) const
-{
- if (!QWindowsVistaStylePrivate::useVista())
- return QWindowsStyle::subControlRect(control, option, subControl, widget);
-
- QRect rect = QWindowsXPStyle::subControlRect(control, option, subControl, widget);
- switch (control) {
-#if QT_CONFIG(combobox)
- case CC_ComboBox:
- if (const QStyleOptionComboBox *cb = qstyleoption_cast<const QStyleOptionComboBox *>(option)) {
- const int x = cb->rect.x(), y = cb->rect.y(), wi = cb->rect.width(), he = cb->rect.height();
- const int margin = cb->frame ? 3 : 0;
- const int bmarg = cb->frame ? 2 : 0;
- const int arrowWidth = qRound(QStyleHelper::dpiScaled(16, option));
- const int arrowButtonWidth = bmarg + arrowWidth;
- const int xpos = x + wi - arrowButtonWidth;
-
- switch (subControl) {
- case SC_ComboBoxFrame:
- rect = cb->rect;
- break;
- case SC_ComboBoxArrow:
- rect.setRect(xpos, y , arrowButtonWidth, he);
- break;
- case SC_ComboBoxEditField:
- rect.setRect(x + margin, y + margin, wi - 2 * margin - arrowWidth, he - 2 * margin);
- break;
- case SC_ComboBoxListBoxPopup:
- rect = cb->rect;
- break;
- default:
- break;
- }
- rect = visualRect(cb->direction, cb->rect, rect);
- return rect;
- }
- break;
-#endif // QT_CONFIG(combobox)
- case CC_TitleBar:
- if (const QStyleOptionTitleBar *tb = qstyleoption_cast<const QStyleOptionTitleBar *>(option)) {
- if (!buttonVisible(subControl, tb))
- return rect;
- const qreal factor = QWindowsStylePrivate::nativeMetricScaleFactor(widget);
- const bool isToolTitle = false;
- const int height = tb->rect.height();
- const int width = tb->rect.width();
- const int buttonWidth =
- qRound(qreal(GetSystemMetrics(SM_CXSIZE)) * factor - QStyleHelper::dpiScaled(4, option));
-
- const int frameWidth = proxy()->pixelMetric(PM_MdiSubWindowFrameWidth, option, widget);
- const bool sysmenuHint = (tb->titleBarFlags & Qt::WindowSystemMenuHint) != 0;
- const bool minimizeHint = (tb->titleBarFlags & Qt::WindowMinimizeButtonHint) != 0;
- const bool maximizeHint = (tb->titleBarFlags & Qt::WindowMaximizeButtonHint) != 0;
- const bool contextHint = (tb->titleBarFlags & Qt::WindowContextHelpButtonHint) != 0;
- const bool shadeHint = (tb->titleBarFlags & Qt::WindowShadeButtonHint) != 0;
-
- switch (subControl) {
- case SC_TitleBarLabel:
- rect = QRect(frameWidth, 0, width - (buttonWidth + frameWidth + 10), height);
- if (isToolTitle) {
- if (sysmenuHint) {
- rect.adjust(0, 0, int(-buttonWidth - 3 * factor), 0);
- }
- if (minimizeHint || maximizeHint)
- rect.adjust(0, 0, int(-buttonWidth - 2 * factor), 0);
- } else {
- if (sysmenuHint) {
- const int leftOffset = int(height - 8 * factor);
- rect.adjust(leftOffset, 0, 0, int(4 * factor));
- }
- if (minimizeHint)
- rect.adjust(0, 0, int(-buttonWidth - 2 * factor), 0);
- if (maximizeHint)
- rect.adjust(0, 0, int(-buttonWidth - 2 * factor), 0);
- if (contextHint)
- rect.adjust(0, 0, int(-buttonWidth - 2 * factor), 0);
- if (shadeHint)
- rect.adjust(0, 0, int(-buttonWidth - 2 * factor), 0);
- }
- rect.translate(0, int(2 * factor));
- rect = visualRect(option->direction, option->rect, rect);
- break;
- case SC_TitleBarSysMenu:
- {
- const int controlTop = int(6 * factor);
- const int controlHeight = int(height - controlTop - 3 * factor);
- int iconExtent = proxy()->pixelMetric(PM_SmallIconSize, option);
- QSize iconSize = tb->icon.actualSize(QSize(iconExtent, iconExtent));
- if (tb->icon.isNull())
- iconSize = QSize(controlHeight, controlHeight);
- int hPad = (controlHeight - iconSize.height())/2;
- int vPad = (controlHeight - iconSize.width())/2;
- rect = QRect(frameWidth + hPad, controlTop + vPad, iconSize.width(), iconSize.height());
- rect.translate(0, int(3 * factor));
- rect = visualRect(option->direction, option->rect, rect);
- }
- break;
- default:
- break;
- }
- }
- break;
- default:
- break;
- }
- return rect;
-}
-
-/*!
- \internal
- */
-QStyle::SubControl QWindowsVistaStyle::hitTestComplexControl(ComplexControl control, const QStyleOptionComplex *option,
- const QPoint &pos, const QWidget *widget) const
-{
- if (!QWindowsVistaStylePrivate::useVista()) {
- return QWindowsStyle::hitTestComplexControl(control, option, pos, widget);
- }
- return QWindowsXPStyle::hitTestComplexControl(control, option, pos, widget);
-}
-
-int QWindowsVistaStylePrivate::fixedPixelMetric(QStyle::PixelMetric pm)
-{
- switch (pm) {
- case QStyle::PM_DockWidgetTitleBarButtonMargin:
- return 5;
- case QStyle::PM_ScrollBarSliderMin:
- return 18;
- case QStyle::PM_MenuHMargin:
- case QStyle::PM_MenuVMargin:
- return 0;
- case QStyle::PM_MenuPanelWidth:
- return 3;
- default:
- break;
- }
- return QWindowsVistaStylePrivate::InvalidMetric;
-}
-
-/*!
- \internal
- */
-int QWindowsVistaStyle::pixelMetric(PixelMetric metric, const QStyleOption *option, const QWidget *widget) const
-{
- if (!QWindowsVistaStylePrivate::useVista())
- return QWindowsStyle::pixelMetric(metric, option, widget);
-
- int ret = QWindowsVistaStylePrivate::fixedPixelMetric(metric);
- if (ret != QWindowsStylePrivate::InvalidMetric)
- return int(QStyleHelper::dpiScaled(ret, option));
-
- return QWindowsXPStyle::pixelMetric(metric, option, widget);
-}
-
-/*!
- \internal
- */
-void QWindowsVistaStyle::polish(QWidget *widget)
-{
- QWindowsXPStyle::polish(widget);
-#if QT_CONFIG(lineedit)
- if (qobject_cast<QLineEdit*>(widget))
- widget->setAttribute(Qt::WA_Hover);
- else
-#endif // QT_CONFIG(lineedit)
- if (qobject_cast<QGroupBox*>(widget))
- widget->setAttribute(Qt::WA_Hover);
-#if QT_CONFIG(commandlinkbutton)
- else if (qobject_cast<QCommandLinkButton*>(widget)) {
- widget->setProperty("_qt_usingVistaStyle", true);
- QFont buttonFont = widget->font();
- buttonFont.setFamilies(QStringList{QLatin1String("Segoe UI")});
- widget->setFont(buttonFont);
- QPalette pal = widget->palette();
- pal.setColor(QPalette::ButtonText, QColor(21, 28, 85));
- pal.setColor(QPalette::BrightText, QColor(7, 64, 229));
- widget->setPalette(pal);
- }
-#endif // QT_CONFIG(commandlinkbutton)
- else if (widget->inherits("QTipLabel")){
- //note that since tooltips are not reused
- //we do not have to care about unpolishing
- widget->setContentsMargins(3, 0, 4, 0);
- COLORREF bgRef;
- HTHEME theme = OpenThemeData(widget ? QWindowsVistaStylePrivate::winId(widget) : nullptr, L"TOOLTIP");
- if (theme && SUCCEEDED(GetThemeColor(theme, TTP_STANDARD, TTSS_NORMAL, TMT_TEXTCOLOR, &bgRef))) {
- QColor textColor = QColor::fromRgb(bgRef);
- QPalette pal;
- pal.setColor(QPalette::All, QPalette::ToolTipText, textColor);
- widget->setPalette(pal);
- }
- } else if (qobject_cast<QMessageBox *> (widget)) {
- widget->setAttribute(Qt::WA_StyledBackground);
-#if QT_CONFIG(dialogbuttonbox)
- QDialogButtonBox *buttonBox = widget->findChild<QDialogButtonBox *>(QLatin1String("qt_msgbox_buttonbox"));
- if (buttonBox)
- buttonBox->setContentsMargins(0, 9, 0, 0);
-#endif
- }
-#if QT_CONFIG(inputdialog)
- else if (qobject_cast<QInputDialog *> (widget)) {
- widget->setAttribute(Qt::WA_StyledBackground);
-#if QT_CONFIG(dialogbuttonbox)
- QDialogButtonBox *buttonBox = widget->findChild<QDialogButtonBox *>(QLatin1String("qt_inputdlg_buttonbox"));
- if (buttonBox)
- buttonBox->setContentsMargins(0, 9, 0, 0);
-#endif
- }
-#endif // QT_CONFIG(inputdialog)
- else if (QTreeView *tree = qobject_cast<QTreeView *> (widget)) {
- tree->viewport()->setAttribute(Qt::WA_Hover);
- }
- else if (QListView *list = qobject_cast<QListView *> (widget)) {
- list->viewport()->setAttribute(Qt::WA_Hover);
- }
-}
-
-/*!
- \internal
- */
-void QWindowsVistaStyle::unpolish(QWidget *widget)
-{
- QWindowsXPStyle::unpolish(widget);
-
- QWindowsVistaStylePrivate *d = d_func();
-
- d->stopAnimation(widget);
-
-#if QT_CONFIG(lineedit)
- if (qobject_cast<QLineEdit*>(widget))
- widget->setAttribute(Qt::WA_Hover, false);
- else
-#endif // QT_CONFIG(lineedit)
- if (qobject_cast<QGroupBox*>(widget))
- widget->setAttribute(Qt::WA_Hover, false);
- else if (qobject_cast<QMessageBox *> (widget)) {
- widget->setAttribute(Qt::WA_StyledBackground, false);
-#if QT_CONFIG(dialogbuttonbox)
- QDialogButtonBox *buttonBox = widget->findChild<QDialogButtonBox *>(QLatin1String("qt_msgbox_buttonbox"));
- if (buttonBox)
- buttonBox->setContentsMargins(0, 0, 0, 0);
-#endif
- }
-#if QT_CONFIG(inputdialog)
- else if (qobject_cast<QInputDialog *> (widget)) {
- widget->setAttribute(Qt::WA_StyledBackground, false);
-#if QT_CONFIG(dialogbuttonbox)
- QDialogButtonBox *buttonBox = widget->findChild<QDialogButtonBox *>(QLatin1String("qt_inputdlg_buttonbox"));
- if (buttonBox)
- buttonBox->setContentsMargins(0, 0, 0, 0);
-#endif
- }
-#endif // QT_CONFIG(inputdialog)
- else if (QTreeView *tree = qobject_cast<QTreeView *> (widget)) {
- tree->viewport()->setAttribute(Qt::WA_Hover, false);
- }
-#if QT_CONFIG(commandlinkbutton)
- else if (qobject_cast<QCommandLinkButton*>(widget)) {
- QFont font = QApplication::font("QCommandLinkButton");
- QFont widgetFont = widget->font();
- widgetFont.setFamilies(font.families()); //Only family set by polish
- widget->setFont(widgetFont);
- }
-#endif // QT_CONFIG(commandlinkbutton)
-}
-
-/*!
- \internal
- */
-void QWindowsVistaStyle::polish(QPalette &pal)
-{
- QWindowsStyle::polish(pal);
- pal.setBrush(QPalette::AlternateBase, pal.base().color().darker(104));
-}
-
-/*!
- \internal
- */
-QPixmap QWindowsVistaStyle::standardPixmap(StandardPixmap standardPixmap, const QStyleOption *option,
- const QWidget *widget) const
-{
- if (!QWindowsVistaStylePrivate::useVista()) {
- return QWindowsStyle::standardPixmap(standardPixmap, option, widget);
- }
- return QWindowsXPStyle::standardPixmap(standardPixmap, option, widget);
-}
-
-bool QWindowsVistaStylePrivate::transitionsEnabled() const
-{
- BOOL animEnabled = false;
- if (SystemParametersInfo(SPI_GETCLIENTAREAANIMATION, 0, &animEnabled, 0))
- {
- if (animEnabled)
- return true;
- }
- return false;
-}
-
-/*!
-\reimp
-*/
-QIcon QWindowsVistaStyle::standardIcon(StandardPixmap standardIcon,
- const QStyleOption *option,
- const QWidget *widget) const
-{
- if (!QWindowsVistaStylePrivate::useVista()) {
- return QWindowsStyle::standardIcon(standardIcon, option, widget);
- }
-
- QWindowsVistaStylePrivate *d = const_cast<QWindowsVistaStylePrivate *>(d_func());
- switch(standardIcon) {
- case SP_CommandLink:
- {
- XPThemeData theme(nullptr, nullptr,
- QWindowsXPStylePrivate::ButtonTheme,
- BP_COMMANDLINKGLYPH, CMDLGS_NORMAL);
- if (theme.isValid()) {
- const QSize size = theme.size().toSize();
- QIcon linkGlyph;
- QPixmap pm(size);
- pm.fill(Qt::transparent);
- QPainter p(&pm);
- theme.painter = &p;
- theme.rect = QRect(QPoint(0, 0), size);
- d->drawBackground(theme);
- linkGlyph.addPixmap(pm, QIcon::Normal, QIcon::Off); // Normal
- pm.fill(Qt::transparent);
-
- theme.stateId = CMDLGS_PRESSED;
- d->drawBackground(theme);
- linkGlyph.addPixmap(pm, QIcon::Normal, QIcon::On); // Pressed
- pm.fill(Qt::transparent);
-
- theme.stateId = CMDLGS_HOT;
- d->drawBackground(theme);
- linkGlyph.addPixmap(pm, QIcon::Active, QIcon::Off); // Hover
- pm.fill(Qt::transparent);
-
- theme.stateId = CMDLGS_DISABLED;
- d->drawBackground(theme);
- linkGlyph.addPixmap(pm, QIcon::Disabled, QIcon::Off); // Disabled
- return linkGlyph;
- }
- }
- break;
- default:
- break;
- }
- return QWindowsXPStyle::standardIcon(standardIcon, option, widget);
-}
-
-QT_END_NAMESPACE
diff --git a/src/plugins/styles/windowsvista/qwindowsvistastyle_p_p.h b/src/plugins/styles/windowsvista/qwindowsvistastyle_p_p.h
deleted file mode 100644
index 03aa4fdf9a..0000000000
--- a/src/plugins/styles/windowsvista/qwindowsvistastyle_p_p.h
+++ /dev/null
@@ -1,167 +0,0 @@
-// Copyright (C) 2016 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
-
-#ifndef QWINDOWSVISTASTYLE_P_P_H
-#define QWINDOWSVISTASTYLE_P_P_H
-
-//
-// W A R N I N G
-// -------------
-//
-// This file is not part of the Qt API. It exists for the convenience
-// of qapplication_*.cpp, qwidget*.cpp and qfiledialog.cpp. This header
-// file may change from version to version without notice, or even be removed.
-//
-// We mean it.
-//
-
-#include <QtWidgets/private/qtwidgetsglobal_p.h>
-#include "qwindowsvistastyle_p.h"
-#include "qwindowsxpstyle_p_p.h"
-#include <private/qstyleanimation_p.h>
-#include <private/qpaintengine_raster_p.h>
-#include <qpaintengine.h>
-#include <qwidget.h>
-#include <qapplication.h>
-#include <qpixmapcache.h>
-#include <qstyleoption.h>
-#if QT_CONFIG(pushbutton)
-#include <qpushbutton.h>
-#endif
-#include <qradiobutton.h>
-#if QT_CONFIG(lineedit)
-#include <qlineedit.h>
-#endif
-#include <qgroupbox.h>
-#if QT_CONFIG(toolbutton)
-#include <qtoolbutton.h>
-#endif
-#if QT_CONFIG(spinbox)
-#include <qspinbox.h>
-#endif
-#if QT_CONFIG(toolbar)
-#include <qtoolbar.h>
-#endif
-#if QT_CONFIG(combobox)
-#include <qcombobox.h>
-#endif
-#if QT_CONFIG(scrollbar)
-#include <qscrollbar.h>
-#endif
-#if QT_CONFIG(progressbar)
-#include <qprogressbar.h>
-#endif
-#if QT_CONFIG(dockwidget)
-#include <qdockwidget.h>
-#endif
-#if QT_CONFIG(listview)
-#include <qlistview.h>
-#endif
-#if QT_CONFIG(treeview)
-#include <qtreeview.h>
-#endif
-#include <qtextedit.h>
-#include <qmessagebox.h>
-#if QT_CONFIG(dialogbuttonbox)
-#include <qdialogbuttonbox.h>
-#endif
-#include <qinputdialog.h>
-#if QT_CONFIG(tableview)
-#include <qtableview.h>
-#endif
-#include <qdatetime.h>
-#if QT_CONFIG(commandlinkbutton)
-#include <qcommandlinkbutton.h>
-#endif
-
-QT_BEGIN_NAMESPACE
-
-#if !defined(SCHEMA_VERIFY_VSSYM32)
-#define TMT_ANIMATIONDURATION 5006
-#define TMT_TRANSITIONDURATIONS 6000
-#define EP_EDITBORDER_NOSCROLL 6
-#define EP_EDITBORDER_HVSCROLL 9
-#define EP_BACKGROUND 3
-#define EBS_NORMAL 1
-#define EBS_HOT 2
-#define EBS_DISABLED 3
-#define EBS_READONLY 5
-#define PBS_DEFAULTED_ANIMATING 6
-#define MBI_NORMAL 1
-#define MBI_HOT 2
-#define MBI_PUSHED 3
-#define MBI_DISABLED 4
-#define MB_ACTIVE 1
-#define MB_INACTIVE 2
-#define PP_FILL 5
-#define PP_FILLVERT 6
-#define PP_MOVEOVERLAY 8
-#define PP_MOVEOVERLAYVERT 10
-#define MENU_BARBACKGROUND 7
-#define MENU_BARITEM 8
-#define MENU_POPUPCHECK 11
-#define MENU_POPUPCHECKBACKGROUND 12
-#define MENU_POPUPGUTTER 13
-#define MENU_POPUPITEM 14
-#define MENU_POPUPBORDERS 10
-#define MENU_POPUPSEPARATOR 15
-#define MC_CHECKMARKNORMAL 1
-#define MC_CHECKMARKDISABLED 2
-#define MC_BULLETNORMAL 3
-#define MC_BULLETDISABLED 4
-#define ABS_UPHOVER 17
-#define ABS_DOWNHOVER 18
-#define ABS_LEFTHOVER 19
-#define ABS_RIGHTHOVER 20
-#define CP_DROPDOWNBUTTONRIGHT 6
-#define CP_DROPDOWNBUTTONLEFT 7
-#define SCRBS_HOVER 5
-#define TVP_HOTGLYPH 4
-#define SPI_GETCLIENTAREAANIMATION 0x1042
-#define TDLG_PRIMARYPANEL 1
-#define TDLG_SECONDARYPANEL 8
-#endif
-
-class QWindowsVistaAnimation : public QBlendStyleAnimation
-{
- Q_OBJECT
-public:
- QWindowsVistaAnimation(Type type, QObject *target) : QBlendStyleAnimation(type, target) { }
-
- bool isUpdateNeeded() const override;
- void paint(QPainter *painter, const QStyleOption *option);
-};
-
-
-// Handles state transition animations
-class QWindowsVistaTransition : public QWindowsVistaAnimation
-{
- Q_OBJECT
-public:
- QWindowsVistaTransition(QObject *target) : QWindowsVistaAnimation(Transition, target) {}
-};
-
-
-// Handles pulse animations (default buttons)
-class QWindowsVistaPulse: public QWindowsVistaAnimation
-{
- Q_OBJECT
-public:
- QWindowsVistaPulse(QObject *target) : QWindowsVistaAnimation(Pulse, target) {}
-};
-
-
-class QWindowsVistaStylePrivate : public QWindowsXPStylePrivate
-{
- Q_DECLARE_PUBLIC(QWindowsVistaStyle)
-
-public:
- static int fixedPixelMetric(QStyle::PixelMetric pm);
- static inline bool useVista();
- QTime animationTime() const;
- bool transitionsEnabled() const;
-};
-
-QT_END_NAMESPACE
-
-#endif // QWINDOWSVISTASTYLE_P_P_H
diff --git a/src/plugins/styles/windowsvista/qwindowsxpstyle.cpp b/src/plugins/styles/windowsvista/qwindowsxpstyle.cpp
deleted file mode 100644
index 1b80f66af2..0000000000
--- a/src/plugins/styles/windowsvista/qwindowsxpstyle.cpp
+++ /dev/null
@@ -1,3669 +0,0 @@
-// Copyright (C) 2022 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
-#include "qwindowsxpstyle_p.h"
-#include "qwindowsxpstyle_p_p.h"
-
-#include <private/qobject_p.h>
-#include <private/qpaintengine_raster_p.h>
-#include <private/qapplication_p.h>
-#include <private/qstylehelper_p.h>
-#include <private/qwidget_p.h>
-#include <qpainter.h>
-#include <qpaintengine.h>
-#include <qwidget.h>
-#include <qbackingstore.h>
-#include <qapplication.h>
-#include <qpixmapcache.h>
-#include <private/qapplication_p.h>
-#include <qpa/qplatformintegration.h>
-
-#if QT_CONFIG(toolbutton)
-#include <qtoolbutton.h>
-#endif
-#if QT_CONFIG(tabbar)
-#include <qtabbar.h>
-#endif
-#if QT_CONFIG(combobox)
-#include <qcombobox.h>
-#endif
-#if QT_CONFIG(scrollbar)
-#include <qscrollbar.h>
-#endif
-#include <qheaderview.h>
-#if QT_CONFIG(spinbox)
-#include <qspinbox.h>
-#endif
-#if QT_CONFIG(listview)
-#include <qlistview.h>
-#endif
-#if QT_CONFIG(stackedwidget)
-#include <qstackedwidget.h>
-#endif
-#if QT_CONFIG(pushbutton)
-#include <qpushbutton.h>
-#endif
-#if QT_CONFIG(toolbar)
-#include <qtoolbar.h>
-#endif
-#include <qlabel.h>
-#include <qvarlengtharray.h>
-#include <qdebug.h>
-
-#include <algorithm>
-
-QT_BEGIN_NAMESPACE
-
-// General const values
-static const int windowsItemFrame = 2; // menu item frame width
-static const int windowsItemHMargin = 3; // menu item hor text margin
-static const int windowsItemVMargin = 0; // menu item ver text margin
-static const int windowsArrowHMargin = 6; // arrow horizontal margin
-static const int windowsRightBorder = 12; // right border on windows
-
-// Theme names matching the QWindowsXPStylePrivate::Theme enumeration.
-static const wchar_t *themeNames[QWindowsXPStylePrivate::NThemes] =
-{
- L"BUTTON", L"COMBOBOX", L"EDIT", L"HEADER", L"LISTVIEW",
- L"MENU", L"PROGRESS", L"REBAR", L"SCROLLBAR", L"SPIN",
- L"TAB", L"TASKDIALOG", L"TOOLBAR", L"TOOLTIP", L"TRACKBAR",
- L"TREEVIEW", L"WINDOW", L"STATUS", L"TREEVIEW"
-};
-
-static inline QBackingStore *backingStoreForWidget(const QWidget *widget)
-{
- if (QBackingStore *backingStore = widget->backingStore())
- return backingStore;
- if (const QWidget *topLevel = widget->nativeParentWidget())
- if (QBackingStore *topLevelBackingStore = topLevel->backingStore())
- return topLevelBackingStore;
- return nullptr;
-}
-
-static inline HDC hdcForWidgetBackingStore(const QWidget *widget)
-{
- if (QBackingStore *backingStore = backingStoreForWidget(widget)) {
- QPlatformNativeInterface *nativeInterface = QGuiApplication::platformNativeInterface();
- if (nativeInterface)
- return static_cast<HDC>(nativeInterface->nativeResourceForBackingStore(QByteArrayLiteral("getDC"), backingStore));
- }
- return nullptr;
-}
-
-// Theme data helper ------------------------------------------------------------------------------
-/* \internal
- Returns \c true if the themedata is valid for use.
-*/
-bool XPThemeData::isValid()
-{
- return QWindowsXPStylePrivate::useXP() && theme >= 0 && handle();
-}
-
-
-/* \internal
- Returns the theme engine handle to the specific class.
- If the handle hasn't been opened before, it opens the data, and
- adds it to a static map, for caching.
-*/
-HTHEME XPThemeData::handle()
-{
- if (!QWindowsXPStylePrivate::useXP())
- return nullptr;
-
- if (!htheme)
- htheme = QWindowsXPStylePrivate::createTheme(theme, QWindowsXPStylePrivate::winId(widget));
- return htheme;
-}
-
-/* \internal
- Converts a QRect to the native RECT structure.
-*/
-RECT XPThemeData::toRECT(const QRect &qr)
-{
- RECT r;
- r.left = qr.x();
- r.right = qr.x() + qr.width();
- r.top = qr.y();
- r.bottom = qr.y() + qr.height();
- return r;
-}
-
-/* \internal
- Returns the native region of a part, if the part is considered
- transparent. The region is scaled to the parts size (rect).
-*/
-HRGN XPThemeData::mask(QWidget *widget)
-{
- if (!IsThemeBackgroundPartiallyTransparent(handle(), partId, stateId))
- return nullptr;
-
- HRGN hrgn;
- HDC dc = nullptr;
- if (widget)
- dc = hdcForWidgetBackingStore(widget);
- RECT nativeRect = toRECT(rect);
- GetThemeBackgroundRegion(handle(), dc, partId, stateId, &nativeRect, &hrgn);
- return hrgn;
-}
-
-// QWindowsXPStylePrivate -------------------------------------------------------------------------
-// Static initializations
-HWND QWindowsXPStylePrivate::m_vistaTreeViewHelper = nullptr;
-HTHEME QWindowsXPStylePrivate::m_themes[NThemes];
-bool QWindowsXPStylePrivate::use_xp = false;
-QBasicAtomicInt QWindowsXPStylePrivate::ref = Q_BASIC_ATOMIC_INITIALIZER(-1); // -1 based refcounting
-
-static void qt_add_rect(HRGN &winRegion, QRect r)
-{
- HRGN rgn = CreateRectRgn(r.left(), r.top(), r.x() + r.width(), r.y() + r.height());
- if (rgn) {
- HRGN dest = CreateRectRgn(0,0,0,0);
- int result = CombineRgn(dest, winRegion, rgn, RGN_OR);
- if (result) {
- DeleteObject(winRegion);
- winRegion = dest;
- }
- DeleteObject(rgn);
- }
-}
-
-static HRGN qt_hrgn_from_qregion(const QRegion &region)
-{
- HRGN hRegion = CreateRectRgn(0,0,0,0);
- if (region.rectCount() == 1) {
- qt_add_rect(hRegion, region.boundingRect());
- return hRegion;
- }
- for (const QRect &rect : region)
- qt_add_rect(hRegion, rect);
- return hRegion;
-}
-
-/* \internal
- Checks if the theme engine can/should be used, or if we should fall back
- to Windows style. For Windows 10, this will still return false for the
- High Contrast themes.
-*/
-bool QWindowsXPStylePrivate::useXP(bool update)
-{
- if (update) {
- use_xp = IsThemeActive() && (IsAppThemed() || !QCoreApplication::instance())
- && !QWindowsStylePrivate::isDarkMode();
- }
- return use_xp;
-}
-
-/* \internal
- Handles refcounting, and queries the theme engine for usage.
-*/
-void QWindowsXPStylePrivate::init(bool force)
-{
- if (ref.ref() && !force)
- return;
- if (!force) // -1 based atomic refcounting
- ref.ref();
-
- useXP(true);
- std::fill(m_themes, m_themes + NThemes, nullptr);
-}
-
-/* \internal
- Cleans up all static data.
-*/
-void QWindowsXPStylePrivate::cleanup(bool force)
-{
- if (bufferBitmap) {
- if (bufferDC && nullBitmap)
- SelectObject(bufferDC, nullBitmap);
- DeleteObject(bufferBitmap);
- bufferBitmap = nullptr;
- }
-
- if (bufferDC)
- DeleteDC(bufferDC);
- bufferDC = nullptr;
-
- if (ref.deref() && !force)
- return;
- if (!force) // -1 based atomic refcounting
- ref.deref();
-
- use_xp = false;
- cleanupHandleMap();
-}
-
-/* In order to obtain the correct VistaTreeViewTheme (arrows for PE_IndicatorBranch),
- * we need to set the windows "explorer" theme explicitly on a native
- * window and open the "TREEVIEW" theme handle passing its window handle
- * in order to get Vista-style item view themes (particularly drawBackground()
- * for selected items needs this).
- * We invoke a service of the native Windows interface to create
- * a non-visible window handle, open the theme on it and insert it into
- * the cache so that it is found by XPThemeData::handle() first.
- */
-
-static inline HWND createTreeViewHelperWindow()
-{
- using QWindowsApplication = QNativeInterface::Private::QWindowsApplication;
-
- HWND result = nullptr;
- if (auto nativeWindowsApp = dynamic_cast<QWindowsApplication *>(QGuiApplicationPrivate::platformIntegration()))
- result = nativeWindowsApp->createMessageWindow(QStringLiteral("QTreeViewThemeHelperWindowClass"),
- QStringLiteral("QTreeViewThemeHelperWindow"));
- return result;
-}
-
-bool QWindowsXPStylePrivate::initVistaTreeViewTheming()
-{
- if (m_vistaTreeViewHelper)
- return true;
-
- m_vistaTreeViewHelper = createTreeViewHelperWindow();
- if (!m_vistaTreeViewHelper) {
- qWarning("Unable to create the treeview helper window.");
- return false;
- }
- if (FAILED(SetWindowTheme(m_vistaTreeViewHelper, L"explorer", nullptr))) {
- qErrnoWarning("SetWindowTheme() failed.");
- cleanupVistaTreeViewTheming();
- return false;
- }
- return true;
-}
-
-void QWindowsXPStylePrivate::cleanupVistaTreeViewTheming()
-{
- if (m_vistaTreeViewHelper) {
- DestroyWindow(m_vistaTreeViewHelper);
- m_vistaTreeViewHelper = nullptr;
- }
-}
-
-/* \internal
- Closes all open theme data handles to ensure that we don't leak
- resources, and that we don't refer to old handles when for
- example the user changes the theme style.
-*/
-void QWindowsXPStylePrivate::cleanupHandleMap()
-{
- for (auto &theme : m_themes) {
- if (theme) {
- CloseThemeData(theme);
- theme = nullptr;
- }
- }
- QWindowsXPStylePrivate::cleanupVistaTreeViewTheming();
-}
-
-HTHEME QWindowsXPStylePrivate::createTheme(int theme, HWND hwnd)
-{
- if (Q_UNLIKELY(theme < 0 || theme >= NThemes || !hwnd)) {
- qWarning("Invalid parameters #%d, %p", theme, hwnd);
- return nullptr;
- }
- if (!m_themes[theme]) {
- const wchar_t *name = themeNames[theme];
- if (theme == VistaTreeViewTheme && QWindowsXPStylePrivate::initVistaTreeViewTheming())
- hwnd = QWindowsXPStylePrivate::m_vistaTreeViewHelper;
- m_themes[theme] = OpenThemeData(hwnd, name);
- if (Q_UNLIKELY(!m_themes[theme]))
- qErrnoWarning("OpenThemeData() failed for theme %d (%s).",
- theme, qPrintable(themeName(theme)));
- }
- return m_themes[theme];
-}
-
-QString QWindowsXPStylePrivate::themeName(int theme)
-{
- return theme >= 0 && theme < NThemes
- ? QString::fromWCharArray(themeNames[theme]) : QString();
-}
-
-bool QWindowsXPStylePrivate::isItemViewDelegateLineEdit(const QWidget *widget)
-{
- if (!widget)
- return false;
- const QWidget *parent1 = widget->parentWidget();
- // Exclude dialogs or other toplevels parented on item views.
- if (!parent1 || parent1->isWindow())
- return false;
- const QWidget *parent2 = parent1->parentWidget();
- return parent2 && widget->inherits("QLineEdit")
- && parent2->inherits("QAbstractItemView");
-}
-
-// Returns whether base color is set for this widget
-bool QWindowsXPStylePrivate::isLineEditBaseColorSet(const QStyleOption *option, const QWidget *widget)
-{
- uint resolveMask = option->palette.resolveMask();
- if (widget) {
- // Since spin box includes a line edit we need to resolve the palette mask also from
- // the parent, as while the color is always correct on the palette supplied by panel,
- // the mask can still be empty. If either mask specifies custom base color, use that.
-#if QT_CONFIG(spinbox)
- if (const QAbstractSpinBox *spinbox = qobject_cast<QAbstractSpinBox*>(widget->parentWidget()))
- resolveMask |= spinbox->palette().resolveMask();
-#endif // QT_CONFIG(spinbox)
- }
- return (resolveMask & (1 << QPalette::Base)) != 0;
-}
-
-static inline Qt::Orientation progressBarOrientation(const QStyleOption *option = nullptr)
-{
- if (const auto *pb = qstyleoption_cast<const QStyleOptionProgressBar *>(option))
- return pb->state & QStyle::State_Horizontal ? Qt::Horizontal : Qt::Vertical;
- return Qt::Horizontal;
-}
-
-/*! \internal
- This function will always return a valid window handle, and might
- create a limbo widget to do so.
- We often need a window handle to for example open theme data, so
- this function ensures that we get one.
-*/
-HWND QWindowsXPStylePrivate::winId(const QWidget *widget)
-{
- if (widget) {
- if (const HWND hwnd = QApplicationPrivate::getHWNDForWidget(const_cast<QWidget *>(widget)))
- return hwnd;
- }
-
- // Find top level with native window (there might be dialogs that do not have one).
- const auto allWindows = QGuiApplication::allWindows();
- for (const QWindow *window : allWindows) {
- if (window->isTopLevel() && window->type() != Qt::Desktop && window->handle() != nullptr)
- return reinterpret_cast<HWND>(window->winId());
- }
-
- return GetDesktopWindow();
-}
-
-/*! \internal
- Returns a native buffer (DIB section) of at least the size of
- ( \a x , \a y ). The buffer has a 32 bit depth, to not lose
- the alpha values on proper alpha-pixmaps.
-*/
-HBITMAP QWindowsXPStylePrivate::buffer(int w, int h)
-{
- // If we already have a HBITMAP which is of adequate size, just return that
- if (bufferBitmap) {
- if (bufferW >= w && bufferH >= h)
- return bufferBitmap;
- // Not big enough, discard the old one
- if (bufferDC && nullBitmap)
- SelectObject(bufferDC, nullBitmap);
- DeleteObject(bufferBitmap);
- bufferBitmap = nullptr;
- }
-
- w = qMax(bufferW, w);
- h = qMax(bufferH, h);
-
- if (!bufferDC) {
- HDC displayDC = GetDC(nullptr);
- bufferDC = CreateCompatibleDC(displayDC);
- ReleaseDC(nullptr, displayDC);
- }
-
- // Define the header
- BITMAPINFO bmi;
- memset(&bmi, 0, sizeof(bmi));
- bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
- bmi.bmiHeader.biWidth = w;
- bmi.bmiHeader.biHeight = -h;
- bmi.bmiHeader.biPlanes = 1;
- bmi.bmiHeader.biBitCount = 32;
- bmi.bmiHeader.biCompression = BI_RGB;
-
- // Create the pixmap
- bufferPixels = nullptr;
- bufferBitmap = CreateDIBSection(bufferDC, &bmi, DIB_RGB_COLORS, reinterpret_cast<void **>(&bufferPixels), nullptr, 0);
- GdiFlush();
- nullBitmap = static_cast<HBITMAP>(SelectObject(bufferDC, bufferBitmap));
-
- if (Q_UNLIKELY(!bufferBitmap)) {
- qErrnoWarning("QWindowsXPStylePrivate::buffer(%dx%d), CreateDIBSection() failed.", w, h);
- bufferW = 0;
- bufferH = 0;
- return nullptr;
- }
- if (Q_UNLIKELY(!bufferPixels)) {
- qErrnoWarning("QWindowsXPStylePrivate::buffer(%dx%d), CreateDIBSection() did not allocate pixel data.", w, h);
- bufferW = 0;
- bufferH = 0;
- return nullptr;
- }
- bufferW = w;
- bufferH = h;
-#ifdef DEBUG_XP_STYLE
- qDebug("Creating new dib section (%d, %d)", w, h);
-#endif
- return bufferBitmap;
-}
-
-/*! \internal
- Returns \c true if the part contains any transparency at all. This does
- not indicate what kind of transparency we're dealing with. It can be
- - Alpha transparency
- - Masked transparency
-*/
-bool QWindowsXPStylePrivate::isTransparent(XPThemeData &themeData)
-{
- return IsThemeBackgroundPartiallyTransparent(themeData.handle(), themeData.partId,
- themeData.stateId);
-}
-
-
-/*! \internal
- Returns a QRegion of the region of the part
-*/
-QRegion QWindowsXPStylePrivate::region(XPThemeData &themeData)
-{
- HRGN hRgn = nullptr;
- const qreal factor = QWindowsStylePrivate::nativeMetricScaleFactor(themeData.widget);
- RECT rect = themeData.toRECT(QRect(themeData.rect.topLeft() / factor, themeData.rect.size() / factor));
- if (!SUCCEEDED(GetThemeBackgroundRegion(themeData.handle(), bufferHDC(), themeData.partId,
- themeData.stateId, &rect, &hRgn))) {
- return QRegion();
- }
-
- HRGN dest = CreateRectRgn(0, 0, 0, 0);
- const bool success = CombineRgn(dest, hRgn, nullptr, RGN_COPY) != ERROR;
-
- QRegion region;
-
- if (success) {
- const auto numBytes = GetRegionData(dest, 0, nullptr);
- if (numBytes == 0)
- return QRegion();
-
- char *buf = new (std::nothrow) char[numBytes];
- if (!buf)
- return QRegion();
-
- RGNDATA *rd = reinterpret_cast<RGNDATA*>(buf);
- if (GetRegionData(dest, numBytes, rd) == 0) {
- delete [] buf;
- return QRegion();
- }
-
- RECT *r = reinterpret_cast<RECT*>(rd->Buffer);
- for (uint i = 0; i < rd->rdh.nCount; ++i) {
- QRect rect;
- rect.setCoords(int(r->left * factor), int(r->top * factor), int((r->right - 1) * factor), int((r->bottom - 1) * factor));
- ++r;
- region |= rect;
- }
-
- delete [] buf;
- }
-
- DeleteObject(hRgn);
- DeleteObject(dest);
-
- return region;
-}
-
-/*! \internal
- Returns \c true if the native doublebuffer contains pixels with
- varying alpha value.
-*/
-bool QWindowsXPStylePrivate::hasAlphaChannel(const QRect &rect)
-{
- const int startX = rect.left();
- const int startY = rect.top();
- const int w = rect.width();
- const int h = rect.height();
-
- int firstAlpha = -1;
- for (int y = startY; y < h/2; ++y) {
- auto buffer = reinterpret_cast<const DWORD *>(bufferPixels) + (y * bufferW);
- for (int x = startX; x < w; ++x, ++buffer) {
- int alpha = (*buffer) >> 24;
- if (firstAlpha == -1)
- firstAlpha = alpha;
- else if (alpha != firstAlpha)
- return true;
- }
- }
- return false;
-}
-
-/*! \internal
- When the theme engine paints both a true alpha pixmap and a glyph
- into our buffer, the glyph might not contain a proper alpha value.
- The rule of thumb for premultiplied pixmaps is that the color
- values of a pixel can never be higher than the alpha values, so
- we use this to our advantage here, and fix all instances where
- this occurs.
-*/
-bool QWindowsXPStylePrivate::fixAlphaChannel(const QRect &rect)
-{
- const int startX = rect.left();
- const int startY = rect.top();
- const int w = rect.width();
- const int h = rect.height();
- bool hasFixedAlphaValue = false;
-
- for (int y = startY; y < h; ++y) {
- auto buffer = reinterpret_cast<DWORD *>(bufferPixels) + (y * bufferW);
- for (int x = startX; x < w; ++x, ++buffer) {
- uint pixel = *buffer;
- int alpha = qAlpha(pixel);
- if (qRed(pixel) > alpha || qGreen(pixel) > alpha || qBlue(pixel) > alpha) {
- *buffer |= 0xff000000;
- hasFixedAlphaValue = true;
- }
- }
- }
- return hasFixedAlphaValue;
-}
-
-/*! \internal
- Swaps the alpha values on certain pixels:
- 0xFF?????? -> 0x00??????
- 0x00?????? -> 0xFF??????
- Used to determine the mask of a non-alpha transparent pixmap in
- the native doublebuffer, and swap the alphas so we may paint
- the image as a Premultiplied QImage with drawImage(), and obtain
- the mask transparency.
-*/
-bool QWindowsXPStylePrivate::swapAlphaChannel(const QRect &rect, bool allPixels)
-{
- const int startX = rect.left();
- const int startY = rect.top();
- const int w = rect.width();
- const int h = rect.height();
- bool valueChange = false;
-
- // Flip the alphas, so that 255-alpha pixels are 0, and 0-alpha are 255.
- for (int y = startY; y < h; ++y) {
- auto buffer = reinterpret_cast<DWORD *>(bufferPixels) + (y * bufferW);
- for (int x = startX; x < w; ++x, ++buffer) {
- if (allPixels) {
- *buffer |= 0xFF000000;
- continue;
- }
- unsigned int alphaValue = (*buffer) & 0xFF000000;
- if (alphaValue == 0xFF000000) {
- *buffer = 0;
- valueChange = true;
- } else if (alphaValue == 0) {
- *buffer |= 0xFF000000;
- valueChange = true;
- }
- }
- }
- return valueChange;
-}
-
-enum TransformType { SimpleTransform, HighDpiScalingTransform, ComplexTransform };
-
-static inline TransformType transformType(const QTransform &transform, qreal devicePixelRatio)
-{
- if (transform.type() <= QTransform::TxTranslate)
- return SimpleTransform;
- if (transform.type() > QTransform::TxScale)
- return ComplexTransform;
- return qFuzzyCompare(transform.m11(), devicePixelRatio)
- && qFuzzyCompare(transform.m22(), devicePixelRatio)
- ? HighDpiScalingTransform : ComplexTransform;
-}
-
-// QTBUG-60571: Exclude known fully opaque theme parts which produce values
-// invalid in ARGB32_Premultiplied (for example, 0x00ffffff).
-static inline bool isFullyOpaque(const XPThemeData &themeData)
-{
- return themeData.theme == QWindowsXPStylePrivate::TaskDialogTheme && themeData.partId == TDLG_PRIMARYPANEL;
-}
-
-/*! \internal
- Main theme drawing function.
- Determines the correct lowlevel drawing method depending on several
- factors.
- Use drawBackgroundThruNativeBuffer() if:
- - Painter does not have an HDC
- - Theme part is flipped (mirrored horizontally)
- else use drawBackgroundDirectly().
- \note drawBackgroundThruNativeBuffer() can return false for large
- sizes due to buffer()/CreateDIBSection() failing.
-*/
-bool QWindowsXPStylePrivate::drawBackground(XPThemeData &themeData, qreal correctionFactor)
-{
- if (themeData.rect.isEmpty())
- return true;
-
- QPainter *painter = themeData.painter;
- Q_ASSERT_X(painter != nullptr, "QWindowsXPStylePrivate::drawBackground()", "Trying to draw a theme part without a painter");
- if (!painter || !painter->isActive())
- return false;
-
- painter->save();
-
- // Access paintDevice via engine since the painter may
- // return the clip device which can still be a widget device in case of grabWidget().
-
- bool translucentToplevel = false;
- const QPaintDevice *paintDevice = painter->device();
- const qreal aditionalDevicePixelRatio = themeData.widget ? themeData.widget->devicePixelRatio() : qreal(1);
- if (paintDevice->devType() == QInternal::Widget) {
- const QWidget *window = static_cast<const QWidget *>(paintDevice)->window();
- translucentToplevel = window->testAttribute(Qt::WA_TranslucentBackground);
- }
-
- const TransformType tt = transformType(painter->deviceTransform(), aditionalDevicePixelRatio);
-
- bool canDrawDirectly = false;
- if (themeData.widget && painter->opacity() == 1.0 && !themeData.rotate
- && !isFullyOpaque(themeData)
- && tt != ComplexTransform && !themeData.mirrorVertically
- && !translucentToplevel) {
- // Draw on backing store DC only for real widgets or backing store images.
- const QPaintDevice *enginePaintDevice = painter->paintEngine()->paintDevice();
- switch (enginePaintDevice->devType()) {
- case QInternal::Widget:
- canDrawDirectly = true;
- break;
- case QInternal::Image:
- // Ensure the backing store has received as resize and is initialized.
- if (QBackingStore *bs = backingStoreForWidget(themeData.widget)) {
- if (bs->size().isValid() && bs->paintDevice() == enginePaintDevice)
- canDrawDirectly = true;
- }
- }
- }
-
- const HDC dc = canDrawDirectly ? hdcForWidgetBackingStore(themeData.widget) : nullptr;
- const bool result = dc && qFuzzyCompare(correctionFactor, qreal(1))
- ? drawBackgroundDirectly(dc, themeData, aditionalDevicePixelRatio)
- : drawBackgroundThruNativeBuffer(themeData, aditionalDevicePixelRatio, correctionFactor);
- painter->restore();
- return result;
-}
-
-static inline QRectF scaleRect(const QRectF &r, qreal factor)
-{
- return r.isValid() && factor > 1
- ? QRectF(r.topLeft() * factor, r.size() * factor) : r;
-}
-
-static QRegion scaleRegion(const QRegion &region, qreal factor)
-{
- if (region.isEmpty() || qFuzzyCompare(factor, qreal(1)))
- return region;
- QRegion result;
- for (const QRect &rect : region)
- result += QRectF(QPointF(rect.topLeft()) * factor, QSizeF(rect.size() * factor)).toRect();
- return result;
-}
-
-/*! \internal
- This function draws the theme parts directly to the paintengines HDC.
- Do not use this if you need to perform other transformations on the
- resulting data.
-*/
-bool QWindowsXPStylePrivate::drawBackgroundDirectly(HDC dc, XPThemeData &themeData, qreal additionalDevicePixelRatio)
-{
- QPainter *painter = themeData.painter;
-
- const auto &deviceTransform = painter->deviceTransform();
- const QPointF redirectionDelta(deviceTransform.dx(), deviceTransform.dy());
- const QRect area = scaleRect(QRectF(themeData.rect), additionalDevicePixelRatio).translated(redirectionDelta).toRect();
-
- QRegion sysRgn = painter->paintEngine()->systemClip();
- if (sysRgn.isEmpty())
- sysRgn = area;
- else
- sysRgn &= area;
- if (painter->hasClipping())
- sysRgn &= scaleRegion(painter->clipRegion(), additionalDevicePixelRatio).translated(redirectionDelta.toPoint());
- HRGN hrgn = qt_hrgn_from_qregion(sysRgn);
- SelectClipRgn(dc, hrgn);
-
-#ifdef DEBUG_XP_STYLE
- printf("---[ DIRECT PAINTING ]------------------> Name(%-10s) Part(%d) State(%d)\n",
- qPrintable(themeData.name), themeData.partId, themeData.stateId);
- showProperties(themeData);
-#endif
-
- RECT drawRECT = themeData.toRECT(area);
- DTBGOPTS drawOptions;
- memset(&drawOptions, 0, sizeof(drawOptions));
- drawOptions.dwSize = sizeof(drawOptions);
- drawOptions.rcClip = themeData.toRECT(sysRgn.boundingRect());
- drawOptions.dwFlags = DTBG_CLIPRECT
- | (themeData.noBorder ? DTBG_OMITBORDER : 0)
- | (themeData.noContent ? DTBG_OMITCONTENT : 0)
- | (themeData.mirrorHorizontally ? DTBG_MIRRORDC : 0);
-
- const HRESULT result = DrawThemeBackgroundEx(themeData.handle(), dc, themeData.partId, themeData.stateId, &(drawRECT), &drawOptions);
- SelectClipRgn(dc, nullptr);
- DeleteObject(hrgn);
- return SUCCEEDED(result);
-}
-
-/*! \internal
- This function uses a secondary Native doublebuffer for painting parts.
- It should only be used when the painteengine doesn't provide a proper
- HDC for direct painting (e.g. when doing a grabWidget(), painting to
- other pixmaps etc), or when special transformations are needed (e.g.
- flips (horizontal mirroring only, vertical are handled by the theme
- engine).
-
- \a correctionFactor is an additional factor used to scale up controls
- that are too small on High DPI screens, as has been observed for
- WP_MDICLOSEBUTTON, WP_MDIRESTOREBUTTON, WP_MDIMINBUTTON (QTBUG-75927).
-*/
-bool QWindowsXPStylePrivate::drawBackgroundThruNativeBuffer(XPThemeData &themeData,
- qreal additionalDevicePixelRatio,
- qreal correctionFactor)
-{
- QPainter *painter = themeData.painter;
- QRectF rectF = scaleRect(QRectF(themeData.rect), additionalDevicePixelRatio);
-
- if ((themeData.rotate + 90) % 180 == 0) { // Catch 90,270,etc.. degree flips.
- rectF = QRectF(0, 0, rectF.height(), rectF.width());
- }
- rectF.moveTo(0, 0);
-
- const bool hasCorrectionFactor = !qFuzzyCompare(correctionFactor, qreal(1));
- QRect rect = rectF.toRect();
- const QRect drawRect = hasCorrectionFactor
- ? QRectF(rectF.topLeft() / correctionFactor, rectF.size() / correctionFactor).toRect()
- : rect;
- int partId = themeData.partId;
- int stateId = themeData.stateId;
- int w = rect.width();
- int h = rect.height();
-
- // Values initialized later, either from cached values, or from function calls
- AlphaChannelType alphaType = UnknownAlpha;
- bool stateHasData = true; // We assume so;
- bool hasAlpha = false;
- bool partIsTransparent;
- bool potentialInvalidAlpha;
-
- QString pixmapCacheKey = QStringLiteral("$qt_xp_");
- pixmapCacheKey.append(themeName(themeData.theme));
- pixmapCacheKey.append(QLatin1Char('p'));
- pixmapCacheKey.append(QString::number(partId));
- pixmapCacheKey.append(QLatin1Char('s'));
- pixmapCacheKey.append(QString::number(stateId));
- pixmapCacheKey.append(QLatin1Char('s'));
- pixmapCacheKey.append(themeData.noBorder ? QLatin1Char('0') : QLatin1Char('1'));
- pixmapCacheKey.append(QLatin1Char('b'));
- pixmapCacheKey.append(themeData.noContent ? QLatin1Char('0') : QLatin1Char('1'));
- pixmapCacheKey.append(QString::number(w));
- pixmapCacheKey.append(QLatin1Char('w'));
- pixmapCacheKey.append(QString::number(h));
- pixmapCacheKey.append(QLatin1Char('h'));
- pixmapCacheKey.append(QString::number(additionalDevicePixelRatio));
- pixmapCacheKey.append(QLatin1Char('d'));
- if (hasCorrectionFactor) {
- pixmapCacheKey.append(QLatin1Char('c'));
- pixmapCacheKey.append(QString::number(correctionFactor));
- }
-
- QPixmap cachedPixmap;
- ThemeMapKey key(themeData);
- ThemeMapData data = alphaCache.value(key);
-
- bool haveCachedPixmap = false;
- bool isCached = data.dataValid;
- if (isCached) {
- partIsTransparent = data.partIsTransparent;
- hasAlpha = data.hasAlphaChannel;
- alphaType = data.alphaType;
- potentialInvalidAlpha = data.hadInvalidAlpha;
-
- haveCachedPixmap = QPixmapCache::find(pixmapCacheKey, &cachedPixmap);
-
-#ifdef DEBUG_XP_STYLE
- char buf[25];
- ::sprintf(buf, "+ Pixmap(%3d, %3d) ]", w, h);
- printf("---[ CACHED %s--------> Name(%-10s) Part(%d) State(%d)\n",
- haveCachedPixmap ? buf : "]-------------------",
- qPrintable(themeData.name), themeData.partId, themeData.stateId);
-#endif
- } else {
- // Not cached, so get values from Theme Engine
- BOOL tmt_borderonly = false;
- COLORREF tmt_transparentcolor = 0x0;
- PROPERTYORIGIN proporigin = PO_NOTFOUND;
- GetThemeBool(themeData.handle(), themeData.partId, themeData.stateId, TMT_BORDERONLY, &tmt_borderonly);
- GetThemeColor(themeData.handle(), themeData.partId, themeData.stateId, TMT_TRANSPARENTCOLOR, &tmt_transparentcolor);
- GetThemePropertyOrigin(themeData.handle(), themeData.partId, themeData.stateId, TMT_CAPTIONMARGINS, &proporigin);
-
- partIsTransparent = isTransparent(themeData);
-
- potentialInvalidAlpha = false;
- GetThemePropertyOrigin(themeData.handle(), themeData.partId, themeData.stateId, TMT_GLYPHTYPE, &proporigin);
- if (proporigin == PO_PART || proporigin == PO_STATE) {
- int tmt_glyphtype = GT_NONE;
- GetThemeEnumValue(themeData.handle(), themeData.partId, themeData.stateId, TMT_GLYPHTYPE, &tmt_glyphtype);
- potentialInvalidAlpha = partIsTransparent && tmt_glyphtype == GT_IMAGEGLYPH;
- }
-
-#ifdef DEBUG_XP_STYLE
- printf("---[ NOT CACHED ]-----------------------> Name(%-10s) Part(%d) State(%d)\n",
- qPrintable(themeData.name), themeData.partId, themeData.stateId);
- printf("-->partIsTransparen = %d\n", partIsTransparent);
- printf("-->potentialInvalidAlpha = %d\n", potentialInvalidAlpha);
- showProperties(themeData);
-#endif
- }
- bool wasAlphaSwapped = false;
- bool wasAlphaFixed = false;
-
- // OLD PSDK Workaround ------------------------------------------------------------------------
- // See if we need extra clipping for the older PSDK, which does
- // not have a DrawThemeBackgroundEx function for DTGB_OMITBORDER
- // and DTGB_OMITCONTENT
- bool addBorderContentClipping = false;
- QRegion extraClip;
- QRect area = drawRect;
- if (themeData.noBorder || themeData.noContent) {
- extraClip = area;
- // We are running on a system where the uxtheme.dll does not have
- // the DrawThemeBackgroundEx function, so we need to clip away
- // borders or contents manually.
-
- int borderSize = 0;
- PROPERTYORIGIN origin = PO_NOTFOUND;
- GetThemePropertyOrigin(themeData.handle(), themeData.partId, themeData.stateId, TMT_BORDERSIZE, &origin);
- GetThemeInt(themeData.handle(), themeData.partId, themeData.stateId, TMT_BORDERSIZE, &borderSize);
- borderSize *= additionalDevicePixelRatio;
-
- // Clip away border region
- if ((origin == PO_CLASS || origin == PO_PART || origin == PO_STATE) && borderSize > 0) {
- if (themeData.noBorder) {
- extraClip &= area;
- area = area.adjusted(-borderSize, -borderSize, borderSize, borderSize);
- }
-
- // Clip away content region
- if (themeData.noContent) {
- QRegion content = area.adjusted(borderSize, borderSize, -borderSize, -borderSize);
- extraClip ^= content;
- }
- }
- addBorderContentClipping = (themeData.noBorder | themeData.noContent);
- }
-
- QImage img;
- if (!haveCachedPixmap) { // If the pixmap is not cached, generate it! -------------------------
- if (!buffer(drawRect.width(), drawRect.height())) // Ensure a buffer of at least (w, h) in size
- return false;
- HDC dc = bufferHDC();
-
- // Clear the buffer
- if (alphaType != NoAlpha) {
- // Consider have separate "memset" function for small chunks for more speedup
- memset(bufferPixels, 0x00, bufferW * drawRect.height() * 4);
- }
-
- // Difference between area and rect
- int dx = area.x() - drawRect.x();
- int dy = area.y() - drawRect.y();
-
- // Adjust so painting rect starts from Origo
- rect.moveTo(0,0);
- area.moveTo(dx,dy);
- DTBGOPTS drawOptions;
- drawOptions.dwSize = sizeof(drawOptions);
- drawOptions.rcClip = themeData.toRECT(rect);
- drawOptions.dwFlags = DTBG_CLIPRECT
- | (themeData.noBorder ? DTBG_OMITBORDER : 0)
- | (themeData.noContent ? DTBG_OMITCONTENT : 0);
-
- // Drawing the part into the backing store
- RECT wRect(themeData.toRECT(area));
- DrawThemeBackgroundEx(themeData.handle(), dc, themeData.partId, themeData.stateId, &wRect, &drawOptions);
-
- // If not cached, analyze the buffer data to figure
- // out alpha type, and if it contains data
- if (!isCached) {
- // SHORTCUT: If the part's state has no data, cache it for NOOP later
- if (!stateHasData) {
- memset(static_cast<void *>(&data), 0, sizeof(data));
- data.dataValid = true;
- alphaCache.insert(key, data);
- return true;
- }
- hasAlpha = hasAlphaChannel(rect);
- if (!hasAlpha && partIsTransparent)
- potentialInvalidAlpha = true;
-#if defined(DEBUG_XP_STYLE) && 1
- dumpNativeDIB(drawRect.width(), drawRect.height());
-#endif
- }
-
- // Fix alpha values, if needed
- if (potentialInvalidAlpha)
- wasAlphaFixed = fixAlphaChannel(drawRect);
-
- QImage::Format format;
- if ((partIsTransparent && !wasAlphaSwapped) || (!partIsTransparent && hasAlpha)) {
- format = QImage::Format_ARGB32_Premultiplied;
- alphaType = RealAlpha;
- } else if (wasAlphaSwapped) {
- format = QImage::Format_ARGB32_Premultiplied;
- alphaType = MaskAlpha;
- } else {
- format = QImage::Format_RGB32;
- // The image data we got from the theme engine does not have any transparency,
- // thus the alpha channel is set to 0.
- // However, Format_RGB32 requires the alpha part to be set to 0xff, thus
- // we must flip it from 0x00 to 0xff
- swapAlphaChannel(rect, true);
- alphaType = NoAlpha;
- }
-#if defined(DEBUG_XP_STYLE) && 1
- printf("Image format is: %s\n", alphaType == RealAlpha ? "Real Alpha" : alphaType == MaskAlpha ? "Masked Alpha" : "No Alpha");
-#endif
- img = QImage(bufferPixels, bufferW, bufferH, format);
- if (hasCorrectionFactor)
- img = img.scaled(img.size() * correctionFactor, Qt::KeepAspectRatio, Qt::SmoothTransformation);
- img.setDevicePixelRatio(additionalDevicePixelRatio);
- }
-
- // Blitting backing store
- bool useRegion = partIsTransparent && !hasAlpha && !wasAlphaSwapped;
-
- QRegion newRegion;
- QRegion oldRegion;
- if (useRegion) {
- newRegion = region(themeData);
- oldRegion = painter->clipRegion();
- painter->setClipRegion(newRegion);
-#if defined(DEBUG_XP_STYLE) && 0
- printf("Using region:\n");
- for (const QRect &r : newRegion)
- printf(" (%d, %d, %d, %d)\n", r.x(), r.y(), r.right(), r.bottom());
-#endif
- }
-
- if (addBorderContentClipping)
- painter->setClipRegion(scaleRegion(extraClip, 1.0 / additionalDevicePixelRatio), Qt::IntersectClip);
-
- if (!themeData.mirrorHorizontally && !themeData.mirrorVertically && !themeData.rotate) {
- if (!haveCachedPixmap)
- painter->drawImage(themeData.rect, img, rect);
- else
- painter->drawPixmap(themeData.rect, cachedPixmap);
- } else {
- // This is _slow_!
- // Make a copy containing only the necessary data, and mirror
- // on all wanted axes. Then draw the copy.
- // If cached, the normal pixmap is cached, instead of caching
- // all possible orientations for each part and state.
- QImage imgCopy;
- if (!haveCachedPixmap)
- imgCopy = img.copy(rect);
- else
- imgCopy = cachedPixmap.toImage();
-
- if (themeData.rotate) {
- QTransform rotMatrix;
- rotMatrix.rotate(themeData.rotate);
- imgCopy = imgCopy.transformed(rotMatrix);
- }
- if (themeData.mirrorHorizontally || themeData.mirrorVertically)
- imgCopy = imgCopy.mirrored(themeData.mirrorHorizontally, themeData.mirrorVertically);
- painter->drawImage(themeData.rect, imgCopy);
- }
-
- if (useRegion || addBorderContentClipping) {
- if (oldRegion.isEmpty())
- painter->setClipping(false);
- else
- painter->setClipRegion(oldRegion);
- }
-
- // Cache the pixmap to avoid expensive swapAlphaChannel() calls
- if (!haveCachedPixmap && w && h) {
- QPixmap pix = QPixmap::fromImage(img).copy(rect);
- QPixmapCache::insert(pixmapCacheKey, pix);
-#ifdef DEBUG_XP_STYLE
- printf("+++Adding pixmap to cache, size(%d, %d), wasAlphaSwapped(%d), wasAlphaFixed(%d), name(%s)\n",
- w, h, wasAlphaSwapped, wasAlphaFixed, qPrintable(pixmapCacheKey));
-#endif
- }
-
- // Add to theme part cache
- if (!isCached) {
- memset(static_cast<void *>(&data), 0, sizeof(data));
- data.dataValid = true;
- data.partIsTransparent = partIsTransparent;
- data.alphaType = alphaType;
- data.hasAlphaChannel = hasAlpha;
- data.wasAlphaSwapped = wasAlphaSwapped;
- data.hadInvalidAlpha = wasAlphaFixed;
- alphaCache.insert(key, data);
- }
- return true;
-}
-
-
-// ------------------------------------------------------------------------------------------------
-
-/*!
- \class QWindowsXPStyle
- \brief The QWindowsXPStyle class provides a Microsoft Windows XP-like look and feel.
-
- \ingroup appearance
- \inmodule QtWidgets
- \internal
-
- \warning This style is only available on the Windows XP platform
- because it makes use of Windows XP's style engine.
-
- Most of the functions are documented in the base classes
- QWindowsStyle, QCommonStyle, and QStyle, but the
- QWindowsXPStyle overloads of drawComplexControl(), drawControl(),
- drawControlMask(), drawPrimitive(), proxy()->subControlRect(), and
- sizeFromContents(), are documented here.
-
- \image qwindowsxpstyle.png
- \sa QMacStyle, QWindowsStyle, QFusionStyle
-*/
-
-/*!
- \internal
-
- Constructs a QWindowsXPStyle object.
-*/
-QWindowsXPStyle::QWindowsXPStyle(QWindowsXPStylePrivate &dd) : QWindowsStyle(dd)
-{
-}
-
-/*!
- Destroys the style.
-*/
-QWindowsXPStyle::~QWindowsXPStyle() = default;
-
-/*! \reimp */
-void QWindowsXPStyle::polish(QWidget *widget)
-{
- QWindowsStyle::polish(widget);
- if (!QWindowsXPStylePrivate::useXP())
- return;
-
- if (false
-#if QT_CONFIG(abstractbutton)
- || qobject_cast<QAbstractButton*>(widget)
-#endif
- || qobject_cast<QToolButton*>(widget)
- || qobject_cast<QTabBar*>(widget)
-#if QT_CONFIG(combobox)
- || qobject_cast<QComboBox*>(widget)
-#endif // QT_CONFIG(combobox)
- || qobject_cast<QScrollBar*>(widget)
- || qobject_cast<QSlider*>(widget)
- || qobject_cast<QHeaderView*>(widget)
-#if QT_CONFIG(spinbox)
- || qobject_cast<QAbstractSpinBox*>(widget)
- || qobject_cast<QSpinBox*>(widget)
-#endif // QT_CONFIG(spinbox)
- ) {
- widget->setAttribute(Qt::WA_Hover);
- }
-
-#if QT_CONFIG(rubberband)
- if (qobject_cast<QRubberBand*>(widget))
- widget->setWindowOpacity(0.6);
-#endif
-
- Q_D(QWindowsXPStyle);
- if (!d->hasInitColors) {
- // Get text color for group box labels
- COLORREF cref;
- XPThemeData theme(widget, nullptr, QWindowsXPStylePrivate::ButtonTheme, 0, 0);
- GetThemeColor(theme.handle(), BP_GROUPBOX, GBS_NORMAL, TMT_TEXTCOLOR, &cref);
- d->groupBoxTextColor = qRgb(GetRValue(cref), GetGValue(cref), GetBValue(cref));
- GetThemeColor(theme.handle(), BP_GROUPBOX, GBS_DISABLED, TMT_TEXTCOLOR, &cref);
- d->groupBoxTextColorDisabled = qRgb(GetRValue(cref), GetGValue(cref), GetBValue(cref));
- // Where does this color come from?
- //GetThemeColor(theme.handle(), TKP_TICS, TSS_NORMAL, TMT_COLOR, &cref);
- d->sliderTickColor = qRgb(165, 162, 148);
- d->hasInitColors = true;
- }
-}
-
-/*! \reimp */
-void QWindowsXPStyle::polish(QPalette &pal)
-{
- QWindowsStyle::polish(pal);
- pal.setBrush(QPalette::AlternateBase, pal.base().color().darker(110));
-}
-
-/*! \reimp */
-void QWindowsXPStyle::unpolish(QWidget *widget)
-{
-#if QT_CONFIG(rubberband)
- if (qobject_cast<QRubberBand*>(widget))
- widget->setWindowOpacity(1.0);
-#endif
- Q_D(QWindowsXPStyle);
- // Unpolish of widgets is the first thing that
- // happens when a theme changes, or the theme
- // engine is turned off. So we detect it here.
- bool oldState = QWindowsXPStylePrivate::useXP();
- bool newState = QWindowsXPStylePrivate::useXP(true);
- if ((oldState != newState) && newState) {
- d->cleanup(true);
- d->init(true);
- } else {
- // Cleanup handle map, if just changing style,
- // or turning it on. In both cases the values
- // already in the map might be old (other style).
- d->cleanupHandleMap();
- }
- if (false
-#if QT_CONFIG(abstractbutton)
- || qobject_cast<QAbstractButton*>(widget)
-#endif
- || qobject_cast<QToolButton*>(widget)
- || qobject_cast<QTabBar*>(widget)
-#if QT_CONFIG(combobox)
- || qobject_cast<QComboBox*>(widget)
-#endif // QT_CONFIG(combobox)
- || qobject_cast<QScrollBar*>(widget)
- || qobject_cast<QSlider*>(widget)
- || qobject_cast<QHeaderView*>(widget)
-#if QT_CONFIG(spinbox)
- || qobject_cast<QAbstractSpinBox*>(widget)
- || qobject_cast<QSpinBox*>(widget)
-#endif // QT_CONFIG(spinbox)
- ) {
- widget->setAttribute(Qt::WA_Hover, false);
- }
- QWindowsStyle::unpolish(widget);
-}
-
-/*! \reimp */
-QRect QWindowsXPStyle::subElementRect(SubElement sr, const QStyleOption *option, const QWidget *widget) const
-{
- if (!QWindowsXPStylePrivate::useXP())
- return QWindowsStyle::subElementRect(sr, option, widget);
-
- QRect rect(option->rect);
- switch(sr) {
- case SE_DockWidgetCloseButton:
- case SE_DockWidgetFloatButton:
- rect = QWindowsStyle::subElementRect(sr, option, widget);
- return rect.translated(0, 1);
- break;
- case SE_TabWidgetTabContents:
- if (qstyleoption_cast<const QStyleOptionTabWidgetFrame *>(option)) {
- rect = QWindowsStyle::subElementRect(sr, option, widget);
- if (sr == SE_TabWidgetTabContents) {
- if (const QTabWidget *tabWidget = qobject_cast<const QTabWidget *>(widget)) {
- if (tabWidget->documentMode())
- break;
- }
-
- rect.adjust(0, 0, -2, -2);
- }
- }
- break;
- case SE_TabWidgetTabBar: {
- rect = QWindowsStyle::subElementRect(sr, option, widget);
- const auto *twfOption = qstyleoption_cast<const QStyleOptionTabWidgetFrame *>(option);
- if (twfOption && twfOption->direction == Qt::RightToLeft
- && (twfOption->shape == QTabBar::RoundedNorth
- || twfOption->shape == QTabBar::RoundedSouth))
- {
- QStyleOptionTab otherOption;
- otherOption.shape = (twfOption->shape == QTabBar::RoundedNorth
- ? QTabBar::RoundedEast : QTabBar::RoundedSouth);
- int overlap = proxy()->pixelMetric(PM_TabBarBaseOverlap, &otherOption, widget);
- int borderThickness = proxy()->pixelMetric(PM_DefaultFrameWidth, option, widget);
- rect.adjust(-overlap + borderThickness, 0, -overlap + borderThickness, 0);
- }
- break;
- }
- case SE_PushButtonContents:
- if (const auto *btn = qstyleoption_cast<const QStyleOptionButton *>(option)) {
- MARGINS borderSize;
- if (widget) {
- XPThemeData buttontheme(widget, nullptr, QWindowsXPStylePrivate::ButtonTheme);
- HTHEME theme = buttontheme.handle();
- if (theme) {
- int stateId;
- if (!(option->state & State_Enabled))
- stateId = PBS_DISABLED;
- else if (option->state & State_Sunken)
- stateId = PBS_PRESSED;
- else if (option->state & State_MouseOver)
- stateId = PBS_HOT;
- else if (btn->features & QStyleOptionButton::DefaultButton)
- stateId = PBS_DEFAULTED;
- else
- stateId = PBS_NORMAL;
-
- int border = proxy()->pixelMetric(PM_DefaultFrameWidth, btn, widget);
- rect = option->rect.adjusted(border, border, -border, -border);
-
- if (SUCCEEDED(GetThemeMargins(theme, nullptr, BP_PUSHBUTTON, stateId, TMT_CONTENTMARGINS, nullptr, &borderSize))) {
- rect.adjust(borderSize.cxLeftWidth, borderSize.cyTopHeight,
- -borderSize.cxRightWidth, -borderSize.cyBottomHeight);
- rect = visualRect(option->direction, option->rect, rect);
- }
- }
- }
- }
- break;
- case SE_ProgressBarContents:
- rect = QCommonStyle::subElementRect(SE_ProgressBarGroove, option, widget);
- if (option->state & QStyle::State_Horizontal)
- rect.adjust(4, 3, -4, -3);
- else
- rect.adjust(3, 2, -3, -2);
- break;
- default:
- rect = QWindowsStyle::subElementRect(sr, option, widget);
- }
- return rect;
-}
-
-/*!
- \reimp
-*/
-void QWindowsXPStyle::drawPrimitive(PrimitiveElement pe, const QStyleOption *option, QPainter *p,
- const QWidget *widget) const
-{
- if (!QWindowsXPStylePrivate::useXP()) {
- QWindowsStyle::drawPrimitive(pe, option, p, widget);
- return;
- }
-
- auto *d = const_cast<QWindowsXPStylePrivate*>(d_func());
- int themeNumber = -1;
- int partId = 0;
- int stateId = 0;
- QRect rect = option->rect;
- State flags = option->state;
- bool hMirrored = false;
- bool vMirrored = false;
- bool noBorder = false;
- bool noContent = false;
- int rotate = 0;
-
- switch (pe) {
- case PE_FrameTabBarBase:
- if (const auto *tbb = qstyleoption_cast<const QStyleOptionTabBarBase *>(option)) {
- p->save();
- switch (tbb->shape) {
- case QTabBar::RoundedNorth:
- p->setPen(QPen(tbb->palette.dark(), 0));
- p->drawLine(tbb->rect.topLeft(), tbb->rect.topRight());
- break;
- case QTabBar::RoundedWest:
- p->setPen(QPen(tbb->palette.dark(), 0));
- p->drawLine(tbb->rect.left(), tbb->rect.top(), tbb->rect.left(), tbb->rect.bottom());
- break;
- case QTabBar::RoundedSouth:
- p->setPen(QPen(tbb->palette.dark(), 0));
- p->drawLine(tbb->rect.left(), tbb->rect.top(),
- tbb->rect.right(), tbb->rect.top());
- break;
- case QTabBar::RoundedEast:
- p->setPen(QPen(tbb->palette.dark(), 0));
- p->drawLine(tbb->rect.topLeft(), tbb->rect.bottomLeft());
- break;
- case QTabBar::TriangularNorth:
- case QTabBar::TriangularEast:
- case QTabBar::TriangularWest:
- case QTabBar::TriangularSouth:
- p->restore();
- QWindowsStyle::drawPrimitive(pe, option, p, widget);
- return;
- }
- p->restore();
- }
- return;
- case PE_PanelButtonBevel:
- themeNumber = QWindowsXPStylePrivate::ButtonTheme;
- partId = BP_PUSHBUTTON;
- if (!(flags & State_Enabled))
- stateId = PBS_DISABLED;
- else if ((flags & State_Sunken) || (flags & State_On))
- stateId = PBS_PRESSED;
- else if (flags & State_MouseOver)
- stateId = PBS_HOT;
- //else if (flags & State_ButtonDefault)
- // stateId = PBS_DEFAULTED;
- else
- stateId = PBS_NORMAL;
- break;
-
- case PE_PanelButtonTool:
- if (widget && widget->inherits("QDockWidgetTitleButton")) {
- if (const QWidget *dw = widget->parentWidget()) {
- if (dw->isWindow())
- return;
- }
- }
- themeNumber = QWindowsXPStylePrivate::ToolBarTheme;
- partId = TP_BUTTON;
- if (!(flags & State_Enabled))
- stateId = TS_DISABLED;
- else if (flags & State_Sunken)
- stateId = TS_PRESSED;
- else if (flags & State_MouseOver)
- stateId = flags & State_On ? TS_HOTCHECKED : TS_HOT;
- else if (flags & State_On)
- stateId = TS_CHECKED;
- else if (!(flags & State_AutoRaise))
- stateId = TS_HOT;
- else
- stateId = TS_NORMAL;
- break;
-
- case PE_IndicatorButtonDropDown:
- themeNumber = QWindowsXPStylePrivate::ToolBarTheme;
- partId = TP_SPLITBUTTONDROPDOWN;
- if (!(flags & State_Enabled))
- stateId = TS_DISABLED;
- else if (flags & State_Sunken)
- stateId = TS_PRESSED;
- else if (flags & State_MouseOver)
- stateId = flags & State_On ? TS_HOTCHECKED : TS_HOT;
- else if (flags & State_On)
- stateId = TS_CHECKED;
- else if (!(flags & State_AutoRaise))
- stateId = TS_HOT;
- else
- stateId = TS_NORMAL;
- if (option->direction == Qt::RightToLeft)
- hMirrored = true;
- break;
-
- case PE_IndicatorCheckBox:
- themeNumber = QWindowsXPStylePrivate::ButtonTheme;
- partId = BP_CHECKBOX;
- if (!(flags & State_Enabled))
- stateId = CBS_UNCHECKEDDISABLED;
- else if (flags & State_Sunken)
- stateId = CBS_UNCHECKEDPRESSED;
- else if (flags & State_MouseOver)
- stateId = CBS_UNCHECKEDHOT;
- else
- stateId = CBS_UNCHECKEDNORMAL;
-
- if (flags & State_On)
- stateId += CBS_CHECKEDNORMAL-1;
- else if (flags & State_NoChange)
- stateId += CBS_MIXEDNORMAL-1;
-
- break;
-
- case PE_IndicatorRadioButton:
- themeNumber = QWindowsXPStylePrivate::ButtonTheme;
- partId = BP_RADIOBUTTON;
- if (!(flags & State_Enabled))
- stateId = RBS_UNCHECKEDDISABLED;
- else if (flags & State_Sunken)
- stateId = RBS_UNCHECKEDPRESSED;
- else if (flags & State_MouseOver)
- stateId = RBS_UNCHECKEDHOT;
- else
- stateId = RBS_UNCHECKEDNORMAL;
-
- if (flags & State_On)
- stateId += RBS_CHECKEDNORMAL-1;
- break;
-
- case PE_IndicatorDockWidgetResizeHandle:
- return;
-
- case PE_Frame: {
- if (flags & State_Raised)
- return;
- themeNumber = QWindowsXPStylePrivate::ListViewTheme;
- partId = LVP_LISTGROUP;
- XPThemeData theme(widget, nullptr, themeNumber, partId);
-
- if (!(flags & State_Enabled))
- stateId = ETS_DISABLED;
- else
- stateId = ETS_NORMAL;
- int fillType;
- if (GetThemeEnumValue(theme.handle(), partId, stateId, TMT_BGTYPE, &fillType) == S_OK) {
- if (fillType == BT_BORDERFILL) {
- COLORREF bcRef;
- GetThemeColor(theme.handle(), partId, stateId, TMT_BORDERCOLOR, &bcRef);
- QColor bordercolor(qRgb(GetRValue(bcRef), GetGValue(bcRef), GetBValue(bcRef)));
- QPen oldPen = p->pen();
- // int borderSize = 1;
- // GetThemeInt(theme.handle(), partId, stateId, TMT_BORDERCOLOR, &borderSize);
-
- // Inner white border
- p->setPen(QPen(option->palette.base().color(), 0));
- const qreal dpi = QStyleHelper::dpi(option);
- const auto topLevelAdjustment = QStyleHelper::dpiScaled(0.5, dpi);
- const auto bottomRightAdjustment = QStyleHelper::dpiScaled(-1, dpi);
- p->drawRect(QRectF(option->rect).adjusted(topLevelAdjustment, topLevelAdjustment,
- bottomRightAdjustment, bottomRightAdjustment));
- // Outer dark border
- p->setPen(QPen(bordercolor, 0));
- p->drawRect(QRectF(option->rect).adjusted(0, 0, -topLevelAdjustment, -topLevelAdjustment));
- p->setPen(oldPen);
- return;
- }
- if (fillType == BT_NONE)
- return;
- }
- break;
- }
-
- case PE_FrameTabWidget:
- if (const auto *tab = qstyleoption_cast<const QStyleOptionTabWidgetFrame *>(option)) {
- themeNumber = QWindowsXPStylePrivate::TabTheme;
- partId = TABP_PANE;
-
- if (widget) {
- bool useGradient = true;
- const int maxlength = 256;
- wchar_t themeFileName[maxlength];
- wchar_t themeColor[maxlength];
- // Due to a a scaling issue with the XP Silver theme, tab gradients are not used with it
- if (GetCurrentThemeName(themeFileName, maxlength, themeColor, maxlength, nullptr, 0) == S_OK) {
- wchar_t *offset = nullptr;
- if ((offset = wcsrchr(themeFileName, QChar(QLatin1Char('\\')).unicode())) != nullptr) {
- offset++;
- if (!lstrcmp(offset, L"Luna.msstyles") && !lstrcmp(offset, L"Metallic"))
- useGradient = false;
- }
- }
- // This should work, but currently there's an error in the ::drawBackgroundDirectly()
- // code, when using the HDC directly..
- if (useGradient) {
- QStyleOptionTabWidgetFrame frameOpt = *tab;
- frameOpt.rect = widget->rect();
- QRect contentsRect = subElementRect(SE_TabWidgetTabContents, &frameOpt, widget);
- QRegion reg = option->rect;
- reg -= contentsRect;
- p->setClipRegion(reg);
- XPThemeData theme(widget, p, themeNumber, partId, stateId, rect);
- theme.mirrorHorizontally = hMirrored;
- theme.mirrorVertically = vMirrored;
- d->drawBackground(theme);
- p->setClipRect(contentsRect);
- partId = TABP_BODY;
- }
- }
- switch (tab->shape) {
- case QTabBar::RoundedNorth:
- case QTabBar::TriangularNorth:
- break;
- case QTabBar::RoundedSouth:
- case QTabBar::TriangularSouth:
- vMirrored = true;
- break;
- case QTabBar::RoundedEast:
- case QTabBar::TriangularEast:
- rotate = 90;
- break;
- case QTabBar::RoundedWest:
- case QTabBar::TriangularWest:
- rotate = 90;
- hMirrored = true;
- break;
- default:
- break;
- }
- }
- break;
-
- case PE_FrameMenu:
- p->save();
- p->setPen(option->palette.dark().color());
- p->drawRect(rect.adjusted(0, 0, -1, -1));
- p->restore();
- return;
-
- case PE_PanelMenuBar:
- break;
-
- case PE_FrameDockWidget:
- if (const auto *frm = qstyleoption_cast<const QStyleOptionFrame *>(option)) {
- themeNumber = QWindowsXPStylePrivate::WindowTheme;
- if (flags & State_Active)
- stateId = FS_ACTIVE;
- else
- stateId = FS_INACTIVE;
-
- int fwidth = proxy()->pixelMetric(PM_DockWidgetFrameWidth, frm, widget);
-
- XPThemeData theme(widget, p, themeNumber, 0, stateId);
- if (!theme.isValid())
- break;
- theme.rect = QRect(frm->rect.x(), frm->rect.y(), frm->rect.x()+fwidth, frm->rect.height()-fwidth);
- theme.partId = WP_SMALLFRAMELEFT;
- d->drawBackground(theme);
- theme.rect = QRect(frm->rect.width()-fwidth, frm->rect.y(), fwidth, frm->rect.height()-fwidth);
- theme.partId = WP_SMALLFRAMERIGHT;
- d->drawBackground(theme);
- theme.rect = QRect(frm->rect.x(), frm->rect.bottom()-fwidth+1, frm->rect.width(), fwidth);
- theme.partId = WP_SMALLFRAMEBOTTOM;
- d->drawBackground(theme);
- return;
- }
- break;
-
- case PE_IndicatorHeaderArrow: {
-#if 0 // XP theme engine doesn't know about this :(
- name = QWindowsXPStylePrivate::HeaderTheme;
- partId = HP_HEADERSORTARROW;
- if (flags & State_Down)
- stateId = HSAS_SORTEDDOWN;
- else
- stateId = HSAS_SORTEDUP;
-#else
- if (const auto *header = qstyleoption_cast<const QStyleOptionHeader *>(option)) {
- p->save();
- p->setPen(option->palette.dark().color());
- p->translate(0, option->rect.height()/2 - 4);
- if (header->sortIndicator & QStyleOptionHeader::SortUp) { // invert logic to follow Windows style guide
- p->drawLine(option->rect.x(), option->rect.y(), option->rect.x()+8, option->rect.y());
- p->drawLine(option->rect.x()+1, option->rect.y()+1, option->rect.x()+7, option->rect.y()+1);
- p->drawLine(option->rect.x()+2, option->rect.y()+2, option->rect.x()+6, option->rect.y()+2);
- p->drawLine(option->rect.x()+3, option->rect.y()+3, option->rect.x()+5, option->rect.y()+3);
- p->drawPoint(option->rect.x()+4, option->rect.y()+4);
- } else if (header->sortIndicator & QStyleOptionHeader::SortDown) {
- p->drawLine(option->rect.x(), option->rect.y()+4, option->rect.x()+8, option->rect.y()+4);
- p->drawLine(option->rect.x()+1, option->rect.y()+3, option->rect.x()+7, option->rect.y()+3);
- p->drawLine(option->rect.x()+2, option->rect.y()+2, option->rect.x()+6, option->rect.y()+2);
- p->drawLine(option->rect.x()+3, option->rect.y()+1, option->rect.x()+5, option->rect.y()+1);
- p->drawPoint(option->rect.x()+4, option->rect.y());
- }
- p->restore();
- return;
- }
-#endif
- break;
- }
-
- case PE_FrameStatusBarItem:
- themeNumber = QWindowsXPStylePrivate::StatusTheme;
- partId = SP_PANE;
- break;
-
- case PE_FrameGroupBox:
- themeNumber = QWindowsXPStylePrivate::ButtonTheme;
- partId = BP_GROUPBOX;
- if (!(flags & State_Enabled))
- stateId = GBS_DISABLED;
- else
- stateId = GBS_NORMAL;
- if (const auto *frame = qstyleoption_cast<const QStyleOptionFrame *>(option)) {
- if (frame->features & QStyleOptionFrame::Flat) {
- // Windows XP does not have a theme part for a flat GroupBox, paint it with the windows style
- QRect fr = frame->rect;
- QPoint p1(fr.x(), fr.y() + 1);
- QPoint p2(fr.x() + fr.width(), p1.y() + 1);
- rect = QRect(p1, p2);
- themeNumber = -1;
- }
- }
- break;
-
- case PE_FrameWindow:
- if (const auto *frm = qstyleoption_cast<const QStyleOptionFrame *>(option)) {
- themeNumber = QWindowsXPStylePrivate::WindowTheme;
- if (flags & State_Active)
- stateId = FS_ACTIVE;
- else
- stateId = FS_INACTIVE;
-
- int fwidth = int((frm->lineWidth + frm->midLineWidth) / QWindowsStylePrivate::nativeMetricScaleFactor(widget));
-
- XPThemeData theme(widget, p, themeNumber, 0, stateId);
- if (!theme.isValid())
- break;
-
- // May fail due to too-large buffers for large widgets, fall back to Windows style.
- theme.rect = QRect(option->rect.x(), option->rect.y()+fwidth, option->rect.x()+fwidth, option->rect.height()-fwidth);
- theme.partId = WP_FRAMELEFT;
- if (!d->drawBackground(theme)) {
- QWindowsStyle::drawPrimitive(pe, option, p, widget);
- return;
- }
- theme.rect = QRect(option->rect.width()-fwidth, option->rect.y()+fwidth, fwidth, option->rect.height()-fwidth);
- theme.partId = WP_FRAMERIGHT;
- if (!d->drawBackground(theme)) {
- QWindowsStyle::drawPrimitive(pe, option, p, widget);
- return;
- }
- theme.rect = QRect(option->rect.x(), option->rect.height()-fwidth, option->rect.width(), fwidth);
- theme.partId = WP_FRAMEBOTTOM;
- if (!d->drawBackground(theme)) {
- QWindowsStyle::drawPrimitive(pe, option, p, widget);
- return;
- }
- theme.rect = QRect(option->rect.x(), option->rect.y(), option->rect.width(), option->rect.y()+fwidth);
- theme.partId = WP_CAPTION;
- if (!d->drawBackground(theme))
- QWindowsStyle::drawPrimitive(pe, option, p, widget);
- return;
- }
- break;
-
- case PE_IndicatorToolBarSeparator:
- if (option->rect.height() < 3) {
- // XP style requires a few pixels for the separator
- // to be visible.
- QWindowsStyle::drawPrimitive(pe, option, p, widget);
- return;
- }
- themeNumber = QWindowsXPStylePrivate::ToolBarTheme;
- partId = TP_SEPARATOR;
-
- if (option->state & State_Horizontal)
- partId = TP_SEPARATOR;
- else
- partId = TP_SEPARATORVERT;
-
- break;
-
- case PE_IndicatorToolBarHandle:
- themeNumber = QWindowsXPStylePrivate::RebarTheme;
- partId = RP_GRIPPER;
- if (option->state & State_Horizontal) {
- partId = RP_GRIPPER;
- rect.adjust(0, 0, -2, 0);
- }
- else {
- partId = RP_GRIPPERVERT;
- rect.adjust(0, 0, 0, -2);
- }
- break;
-
- case PE_IndicatorItemViewItemCheck: {
- QStyleOptionButton button;
- button.QStyleOption::operator=(*option);
- button.state &= ~State_MouseOver;
- proxy()->drawPrimitive(PE_IndicatorCheckBox, &button, p, widget);
- return;
- }
-
- default:
- break;
- }
-
- XPThemeData theme(widget, p, themeNumber, partId, stateId, rect);
- if (!theme.isValid()) {
- QWindowsStyle::drawPrimitive(pe, option, p, widget);
- return;
- }
- theme.mirrorHorizontally = hMirrored;
- theme.mirrorVertically = vMirrored;
- theme.noBorder = noBorder;
- theme.noContent = noContent;
- theme.rotate = rotate;
- d->drawBackground(theme);
-}
-
-/*!
- \reimp
-*/
-void QWindowsXPStyle::drawControl(ControlElement element, const QStyleOption *option, QPainter *p,
- const QWidget *widget) const
-{
- if (!QWindowsXPStylePrivate::useXP()) {
- QWindowsStyle::drawControl(element, option, p, widget);
- return;
- }
-
- auto *d = const_cast<QWindowsXPStylePrivate*>(d_func());
- QRect rect(option->rect);
-
- int rotate = 0;
- bool hMirrored = false;
- bool vMirrored = false;
-
- int themeNumber = -1;
- int partId = 0;
- int stateId = 0;
- switch (element) {
- case CE_SizeGrip: {
- themeNumber = QWindowsXPStylePrivate::StatusTheme;
- partId = SP_GRIPPER;
- XPThemeData theme(nullptr, p, themeNumber, partId);
- QSize size = (theme.size() * QWindowsStylePrivate::nativeMetricScaleFactor(widget)).toSize();
- size.rheight()--;
- if (const auto *sg = qstyleoption_cast<const QStyleOptionSizeGrip *>(option)) {
- switch (sg->corner) {
- case Qt::BottomRightCorner:
- rect = QRect(QPoint(rect.right() - size.width(), rect.bottom() - size.height()), size);
- break;
- case Qt::BottomLeftCorner:
- rect = QRect(QPoint(rect.left() + 1, rect.bottom() - size.height()), size);
- hMirrored = true;
- break;
- case Qt::TopRightCorner:
- rect = QRect(QPoint(rect.right() - size.width(), rect.top() + 1), size);
- vMirrored = true;
- break;
- case Qt::TopLeftCorner:
- rect = QRect(rect.topLeft() + QPoint(1, 1), size);
- hMirrored = vMirrored = true;
- }
- }
- break;
- }
-
- case CE_Splitter:
- p->eraseRect(option->rect);
- return;
-
- case CE_TabBarTab:
- if (const auto *tab = qstyleoption_cast<const QStyleOptionTab *>(option))
- stateId = tab->state & State_Enabled ? TIS_NORMAL : TIS_DISABLED;
- break;
-
- case CE_TabBarTabShape:
- if (const auto *tab = qstyleoption_cast<const QStyleOptionTab *>(option)) {
- themeNumber = QWindowsXPStylePrivate::TabTheme;
- const bool isDisabled = !(tab->state & State_Enabled);
- const bool hasFocus = tab->state & State_HasFocus;
- const bool isHot = tab->state & State_MouseOver;
- const bool selected = tab->state & State_Selected;
- bool lastTab = tab->position == QStyleOptionTab::End;
- bool firstTab = tab->position == QStyleOptionTab::Beginning;
- const bool onlyOne = tab->position == QStyleOptionTab::OnlyOneTab;
- const bool leftAligned = proxy()->styleHint(SH_TabBar_Alignment, tab, widget) == Qt::AlignLeft;
- const bool centerAligned = proxy()->styleHint(SH_TabBar_Alignment, tab, widget) == Qt::AlignCenter;
- const int borderThickness = proxy()->pixelMetric(PM_DefaultFrameWidth, option, widget);
- const int tabOverlap = proxy()->pixelMetric(PM_TabBarTabOverlap, option, widget);
-
- if (isDisabled)
- stateId = TIS_DISABLED;
- else if (selected)
- stateId = TIS_SELECTED;
- else if (hasFocus)
- stateId = TIS_FOCUSED;
- else if (isHot)
- stateId = TIS_HOT;
- else
- stateId = TIS_NORMAL;
-
- // Selecting proper part depending on position
- if (firstTab || onlyOne) {
- if (leftAligned)
- partId = TABP_TABITEMLEFTEDGE;
- else if (centerAligned)
- partId = TABP_TABITEM;
- else // rightAligned
- partId = TABP_TABITEMRIGHTEDGE;
- } else {
- partId = TABP_TABITEM;
- }
-
- if (tab->direction == Qt::RightToLeft
- && (tab->shape == QTabBar::RoundedNorth || tab->shape == QTabBar::RoundedSouth)) {
- bool temp = firstTab;
- firstTab = lastTab;
- lastTab = temp;
- }
- const bool begin = firstTab || onlyOne;
- const bool end = lastTab || onlyOne;
- switch (tab->shape) {
- case QTabBar::RoundedNorth:
- if (selected)
- rect.adjust(begin ? 0 : -tabOverlap, 0, end ? 0 : tabOverlap, borderThickness);
- else
- rect.adjust(begin? tabOverlap : 0, tabOverlap, end ? -tabOverlap : 0, 0);
- break;
- case QTabBar::RoundedSouth:
- //vMirrored = true;
- rotate = 180; // Not 100% correct, but works
- if (selected)
- rect.adjust(begin ? 0 : -tabOverlap , -borderThickness, end ? 0 : tabOverlap, 0);
- else
- rect.adjust(begin ? tabOverlap : 0, 0, end ? -tabOverlap : 0 , -tabOverlap);
- break;
- case QTabBar::RoundedEast:
- rotate = 90;
- if (selected)
- rect.adjust(-borderThickness, begin ? 0 : -tabOverlap, 0, end ? 0 : tabOverlap);
- else
- rect.adjust(0, begin ? tabOverlap : 0, -tabOverlap, end ? -tabOverlap : 0);
- break;
- case QTabBar::RoundedWest:
- hMirrored = true;
- rotate = 90;
- if (selected)
- rect.adjust(0, begin ? 0 : -tabOverlap, borderThickness, end ? 0 : tabOverlap);
- else
- rect.adjust(tabOverlap, begin ? tabOverlap : 0, 0, end ? -tabOverlap : 0);
- break;
- default:
- themeNumber = -1; // Do our own painting for triangular
- break;
- }
-
- if (!selected) {
- switch (tab->shape) {
- case QTabBar::RoundedNorth:
- rect.adjust(0,0, 0,-1);
- break;
- case QTabBar::RoundedSouth:
- rect.adjust(0,1, 0,0);
- break;
- case QTabBar::RoundedEast:
- rect.adjust( 1,0, 0,0);
- break;
- case QTabBar::RoundedWest:
- rect.adjust(0,0, -1,0);
- break;
- default:
- break;
- }
- }
- }
- break;
-
- case CE_ProgressBarGroove: {
- Qt::Orientation orient = progressBarOrientation(option);
- partId = (orient == Qt::Horizontal) ? PP_BAR : PP_BARVERT;
- themeNumber = QWindowsXPStylePrivate::ProgressTheme;
- stateId = 1;
- break;
- }
- case CE_MenuEmptyArea:
- case CE_MenuItem:
- if (const auto *menuitem = qstyleoption_cast<const QStyleOptionMenuItem *>(option)) {
- const int tab = menuitem->reservedShortcutWidth;
- const bool dis = !(menuitem->state & State_Enabled);
- const bool act = menuitem->state & State_Selected;
- const bool checkable = menuitem->menuHasCheckableItems;
- const bool checked = checkable ? menuitem->checked : false;
-
- // windows always has a check column, regardless whether we have an icon or not
- const int checkcol = qMax(menuitem->maxIconWidth, 12);
-
- int x, y, w, h;
- rect.getRect(&x, &y, &w, &h);
-
- QBrush fill = menuitem->palette.brush(act ? QPalette::Highlight : QPalette::Button);
- p->fillRect(rect, fill);
-
- if (element == CE_MenuEmptyArea)
- break;
-
- // draw separator -------------------------------------------------
- if (menuitem->menuItemType == QStyleOptionMenuItem::Separator) {
- int yoff = y-1 + h / 2;
- p->setPen(menuitem->palette.dark().color());
- p->drawLine(x, yoff, x+w, yoff);
- ++yoff;
- p->setPen(menuitem->palette.light().color());
- p->drawLine(x, yoff, x+w, yoff);
- return;
- }
-
- int xpos = x;
-
- // draw icon ------------------------------------------------------
- if (!menuitem->icon.isNull()) {
- QIcon::Mode mode = dis ? QIcon::Disabled : QIcon::Normal;
- if (act && !dis)
- mode = QIcon::Active;
- QPixmap pixmap = checked ?
- menuitem->icon.pixmap(proxy()->pixelMetric(PM_SmallIconSize, option, widget), mode, QIcon::On) :
- menuitem->icon.pixmap(proxy()->pixelMetric(PM_SmallIconSize, option, widget), mode);
- QRect iconRect(QPoint(0, 0), pixmap.deviceIndependentSize().toSize());
- iconRect.moveCenter(QRect(xpos, y, checkcol, h).center());
- QRect vIconRect = visualRect(option->direction, option->rect, iconRect);
- p->setPen(menuitem->palette.text().color());
- p->setBrush(Qt::NoBrush);
- if (checked)
- p->drawRect(vIconRect.adjusted(-1, -1, 0, 0));
- p->drawPixmap(vIconRect.topLeft(), pixmap);
-
- // draw checkmark -------------------------------------------------
- } else if (checked) {
- QStyleOptionMenuItem newMi = *menuitem;
- newMi.state = State_None;
- if (!dis)
- newMi.state |= State_Enabled;
- if (act)
- newMi.state |= State_On;
-
- QRect checkMarkRect = QRect(menuitem->rect.x() + windowsItemFrame,
- menuitem->rect.y() + windowsItemFrame,
- checkcol - 2 * windowsItemFrame,
- menuitem->rect.height() - 2*windowsItemFrame);
- newMi.rect = visualRect(option->direction, option->rect, checkMarkRect);
- proxy()->drawPrimitive(PE_IndicatorMenuCheckMark, &newMi, p, widget);
- }
-
- QColor textColor = dis ? menuitem->palette.text().color() :
- act ? menuitem->palette.highlightedText().color() : menuitem->palette.buttonText().color();
- p->setPen(textColor);
-
- // draw text ------------------------------------------------------
- int xm = windowsItemFrame + checkcol + windowsItemHMargin;
- xpos = menuitem->rect.x() + xm;
- QRect textRect(xpos, y + windowsItemVMargin, w - xm - windowsRightBorder - tab + 1, h - 2 * windowsItemVMargin);
- QRect vTextRect = visualRect(option->direction, option->rect, textRect);
- QString s = menuitem->text;
- if (!s.isEmpty()) {
- p->save();
- int t = s.indexOf(QLatin1Char('\t'));
- int text_flags = Qt::AlignVCenter|Qt::TextShowMnemonic | Qt::TextDontClip | Qt::TextSingleLine | Qt::AlignLeft;
- if (!proxy()->styleHint(SH_UnderlineShortcut, menuitem, widget))
- text_flags |= Qt::TextHideMnemonic;
- // draw tab text ----------------
- if (t >= 0) {
- QRect vShortcutRect = visualRect(option->direction, option->rect, QRect(textRect.topRight(), menuitem->rect.bottomRight()));
- if (dis && !act && proxy()->styleHint(SH_EtchDisabledText, option, widget)) {
- p->setPen(menuitem->palette.light().color());
- p->drawText(vShortcutRect.adjusted(1,1,1,1), text_flags, s.mid(t + 1));
- p->setPen(textColor);
- }
- p->drawText(vShortcutRect, text_flags, s.mid(t + 1));
- s = s.left(t);
- }
- QFont font = menuitem->font;
- if (menuitem->menuItemType == QStyleOptionMenuItem::DefaultItem)
- font.setBold(true);
- p->setFont(font);
- if (dis && !act && proxy()->styleHint(SH_EtchDisabledText, option, widget)) {
- p->setPen(menuitem->palette.light().color());
- p->drawText(vTextRect.adjusted(1,1,1,1), text_flags, s.left(t));
- p->setPen(textColor);
- }
- p->drawText(vTextRect, text_flags, s);
- p->restore();
- }
-
- // draw sub menu arrow --------------------------------------------
- if (menuitem->menuItemType == QStyleOptionMenuItem::SubMenu) {
- int dim = (h - 2) / 2;
- PrimitiveElement arrow;
- arrow = (option->direction == Qt::RightToLeft) ? PE_IndicatorArrowLeft : PE_IndicatorArrowRight;
- xpos = x + w - windowsArrowHMargin - windowsItemFrame - dim;
- QRect vSubMenuRect = visualRect(option->direction, option->rect, QRect(xpos, y + h / 2 - dim / 2, dim, dim));
- QStyleOptionMenuItem newMI = *menuitem;
- newMI.rect = vSubMenuRect;
- newMI.state = dis ? State_None : State_Enabled;
- if (act)
- newMI.palette.setColor(QPalette::ButtonText, newMI.palette.highlightedText().color());
- proxy()->drawPrimitive(arrow, &newMI, p, widget);
- }
- }
- return;
-
- case CE_MenuBarItem:
- if (const auto *mbi = qstyleoption_cast<const QStyleOptionMenuItem *>(option)) {
- if (mbi->menuItemType == QStyleOptionMenuItem::DefaultItem)
- break;
-
- bool act = mbi->state & State_Selected;
- bool dis = !(mbi->state & State_Enabled);
-
- QBrush fill = mbi->palette.brush(act ? QPalette::Highlight : QPalette::Button);
- QPalette::ColorRole textRole = dis ? QPalette::Text:
- act ? QPalette::HighlightedText : QPalette::ButtonText;
- QPixmap pix = mbi->icon.pixmap(proxy()->pixelMetric(PM_SmallIconSize, option, widget), QIcon::Normal);
-
- uint alignment = Qt::AlignCenter | Qt::TextShowMnemonic | Qt::TextDontClip | Qt::TextSingleLine;
- if (!proxy()->styleHint(SH_UnderlineShortcut, mbi, widget))
- alignment |= Qt::TextHideMnemonic;
-
- p->fillRect(rect, fill);
- if (!pix.isNull())
- drawItemPixmap(p, mbi->rect, alignment, pix);
- else
- drawItemText(p, mbi->rect, alignment, mbi->palette, mbi->state & State_Enabled, mbi->text, textRole);
- }
- return;
-#if QT_CONFIG(dockwidget)
- case CE_DockWidgetTitle:
- if (const auto *dwOpt = qstyleoption_cast<const QStyleOptionDockWidget *>(option)) {
- const int buttonMargin = 4;
- const int mw = proxy()->pixelMetric(QStyle::PM_DockWidgetTitleMargin, dwOpt, widget);
- const int fw = proxy()->pixelMetric(PM_DockWidgetFrameWidth, dwOpt, widget);
- const bool isFloating = widget && widget->isWindow();
- const bool isActive = dwOpt->state & State_Active;
-
- const bool verticalTitleBar = dwOpt->verticalTitleBar;
-
- if (verticalTitleBar) {
- rect = rect.transposed();
-
- p->translate(rect.left() - 1, rect.top() + rect.width());
- p->rotate(-90);
- p->translate(-rect.left() + 1, -rect.top());
- }
- QRect r = rect.adjusted(0, 2, -1, -3);
- QRect titleRect = r;
-
- if (dwOpt->closable) {
- QSize sz = proxy()->standardIcon(QStyle::SP_TitleBarCloseButton, dwOpt, widget).actualSize(QSize(10, 10));
- titleRect.adjust(0, 0, -sz.width() - mw - buttonMargin, 0);
- }
-
- if (dwOpt->floatable) {
- QSize sz = proxy()->standardIcon(QStyle::SP_TitleBarMaxButton, dwOpt, widget).actualSize(QSize(10, 10));
- titleRect.adjust(0, 0, -sz.width() - mw - buttonMargin, 0);
- }
-
- if (isFloating) {
- titleRect.adjust(0, -fw, 0, 0);
- if (widget != nullptr && widget->windowIcon().cacheKey() != QApplication::windowIcon().cacheKey())
- titleRect.adjust(titleRect.height() + mw, 0, 0, 0);
- } else {
- titleRect.adjust(mw, 0, 0, 0);
- if (!dwOpt->floatable && !dwOpt->closable)
- titleRect.adjust(0, 0, -mw, 0);
- }
-
- if (!verticalTitleBar)
- titleRect = visualRect(dwOpt->direction, r, titleRect);
-
- if (!isFloating) {
- QPen oldPen = p->pen();
- QString titleText = p->fontMetrics().elidedText(dwOpt->title, Qt::ElideRight, titleRect.width());
- p->setPen(dwOpt->palette.color(QPalette::Dark));
- p->drawRect(r);
-
- if (!titleText.isEmpty()) {
- drawItemText(p, titleRect,
- Qt::AlignLeft | Qt::AlignVCenter | Qt::TextShowMnemonic, dwOpt->palette,
- dwOpt->state & State_Enabled, titleText,
- QPalette::WindowText);
- }
-
- p->setPen(oldPen);
- } else {
- themeNumber = QWindowsXPStylePrivate::WindowTheme;
- if (isActive)
- stateId = CS_ACTIVE;
- else
- stateId = CS_INACTIVE;
-
- int titleHeight = rect.height() - 2;
- rect = rect.adjusted(-fw, -fw, fw, 0);
-
- XPThemeData theme(widget, p, themeNumber, 0, stateId);
- if (!theme.isValid())
- break;
-
- // Draw small type title bar
- theme.rect = rect;
- theme.partId = WP_SMALLCAPTION;
- d->drawBackground(theme);
-
- // Figure out maximal button space on title bar
-
- QIcon ico = widget->windowIcon();
- bool hasIcon = (ico.cacheKey() != QApplication::windowIcon().cacheKey());
- if (hasIcon) {
- QPixmap pxIco = ico.pixmap(titleHeight);
- if (!verticalTitleBar && dwOpt->direction == Qt::RightToLeft)
- p->drawPixmap(rect.width() - titleHeight - pxIco.width(), rect.bottom() - titleHeight - 2, pxIco);
- else
- p->drawPixmap(fw, rect.bottom() - titleHeight - 2, pxIco);
- }
- if (!dwOpt->title.isEmpty()) {
- QPen oldPen = p->pen();
- QFont oldFont = p->font();
- QFont titleFont = oldFont;
- titleFont.setBold(true);
- p->setFont(titleFont);
- QString titleText
- = p->fontMetrics().elidedText(dwOpt->title, Qt::ElideRight, titleRect.width());
-
- int result = TST_NONE;
- GetThemeEnumValue(theme.handle(), WP_SMALLCAPTION, isActive ? CS_ACTIVE : CS_INACTIVE, TMT_TEXTSHADOWTYPE, &result);
- if (result != TST_NONE) {
- COLORREF textShadowRef;
- GetThemeColor(theme.handle(), WP_SMALLCAPTION, isActive ? CS_ACTIVE : CS_INACTIVE, TMT_TEXTSHADOWCOLOR, &textShadowRef);
- QColor textShadow = qRgb(GetRValue(textShadowRef), GetGValue(textShadowRef), GetBValue(textShadowRef));
- p->setPen(textShadow);
- drawItemText(p, titleRect.adjusted(1, 1, 1, 1),
- Qt::AlignLeft | Qt::AlignBottom, dwOpt->palette,
- dwOpt->state & State_Enabled, titleText);
- }
-
- COLORREF captionText = GetSysColor(isActive ? COLOR_CAPTIONTEXT : COLOR_INACTIVECAPTIONTEXT);
- QColor textColor = qRgb(GetRValue(captionText), GetGValue(captionText), GetBValue(captionText));
- p->setPen(textColor);
- drawItemText(p, titleRect,
- Qt::AlignLeft | Qt::AlignBottom, dwOpt->palette,
- dwOpt->state & State_Enabled, titleText);
- p->setFont(oldFont);
- p->setPen(oldPen);
- }
-
- }
-
- return;
- }
- break;
-#endif // QT_CONFIG(dockwidget)
-#if QT_CONFIG(rubberband)
- case CE_RubberBand:
- if (qstyleoption_cast<const QStyleOptionRubberBand *>(option)) {
- QColor highlight = option->palette.color(QPalette::Active, QPalette::Highlight);
- p->save();
- p->setPen(highlight.darker(120));
- QColor dimHighlight(qMin(highlight.red()/2 + 110, 255),
- qMin(highlight.green()/2 + 110, 255),
- qMin(highlight.blue()/2 + 110, 255),
- (widget && widget->isWindow())? 255 : 127);
- p->setBrush(dimHighlight);
- p->drawRect(option->rect.adjusted(0, 0, -1, -1));
- p->restore();
- return;
- }
- break;
-#endif // QT_CONFIG(rubberband)
- case CE_HeaderEmptyArea:
- if (option->state & State_Horizontal) {
- themeNumber = QWindowsXPStylePrivate::HeaderTheme;
- stateId = HIS_NORMAL;
- } else {
- QWindowsStyle::drawControl(CE_HeaderEmptyArea, option, p, widget);
- return;
- }
- break;
- default:
- break;
- }
-
- XPThemeData theme(widget, p, themeNumber, partId, stateId, rect);
- if (!theme.isValid()) {
- QWindowsStyle::drawControl(element, option, p, widget);
- return;
- }
-
- theme.rotate = rotate;
- theme.mirrorHorizontally = hMirrored;
- theme.mirrorVertically = vMirrored;
- d->drawBackground(theme);
-}
-
-QRect QWindowsXPStylePrivate::scrollBarGripperBounds(QStyle::State flags, const QWidget *widget, XPThemeData *theme)
-{
- const bool horizontal = flags & QStyle::State_Horizontal;
- const qreal factor = QWindowsStylePrivate::nativeMetricScaleFactor(widget);
- const QMargins contentsMargin =
- (theme->margins(theme->rect, TMT_SIZINGMARGINS) * factor).toMargins();
- theme->partId = horizontal ? SBP_GRIPPERHORZ : SBP_GRIPPERVERT;
- const QSize size = (theme->size() * factor).toSize();
-
- const int hSpace = theme->rect.width() - size.width();
- const int vSpace = theme->rect.height() - size.height();
- const bool sufficientSpace = (horizontal && hSpace > (contentsMargin.left() + contentsMargin.right()))
- || vSpace > contentsMargin.top() + contentsMargin.bottom();
- return sufficientSpace ? QRect(theme->rect.topLeft() + QPoint(hSpace, vSpace) / 2, size) : QRect();
-}
-
-#if QT_CONFIG(mdiarea)
-// Helper for drawing MDI buttons into the corner widget of QMenuBar in case a
-// QMdiSubWindow is maximized.
-static void populateMdiButtonTheme(const QStyle *proxy, const QWidget *widget,
- const QStyleOptionComplex *option,
- QStyle::SubControl subControl, int part,
- XPThemeData *theme)
-{
- theme->partId = part;
- theme->rect = proxy->subControlRect(QStyle::CC_MdiControls, option, subControl, widget);
- if (!option->state.testFlag(QStyle::State_Enabled))
- theme->stateId = CBS_INACTIVE;
- else if (option->state.testFlag(QStyle::State_Sunken) && option->activeSubControls.testFlag(subControl))
- theme->stateId = CBS_PUSHED;
- else if (option->state.testFlag(QStyle::State_MouseOver) && option->activeSubControls.testFlag(subControl))
- theme->stateId = CBS_HOT;
- else
- theme->stateId = CBS_NORMAL;
-}
-
-// Calculate an small (max 2), empirical correction factor for scaling up
-// WP_MDICLOSEBUTTON, WP_MDIRESTOREBUTTON, WP_MDIMINBUTTON, which are too
-// small on High DPI screens (QTBUG-75927).
-static qreal mdiButtonCorrectionFactor(XPThemeData &theme, const QPaintDevice *pd = nullptr)
-{
- const auto dpr = pd ? pd->devicePixelRatio() : qApp->devicePixelRatio();
- const QSizeF nativeSize = QSizeF(theme.size()) / dpr;
- const QSizeF requestedSize(theme.rect.size());
- const auto rawFactor = qMin(requestedSize.width() / nativeSize.width(),
- requestedSize.height() / nativeSize.height());
- const auto factor = rawFactor >= qreal(2) ? qreal(2) : qreal(1);
- return factor;
-}
-#endif // QT_CONFIG(mdiarea)
-
-static void populateTitleBarButtonTheme(const QStyle *proxy, const QWidget *widget,
- const QStyleOptionComplex *option,
- QStyle::SubControl subControl,
- bool isTitleBarActive, int part,
- XPThemeData *theme)
-{
- theme->rect = proxy->subControlRect(QStyle::CC_TitleBar, option, subControl, widget);
- theme->partId = part;
- if (widget && !widget->isEnabled())
- theme->stateId = RBS_DISABLED;
- else if (option->activeSubControls == subControl && option->state.testFlag(QStyle::State_Sunken))
- theme->stateId = RBS_PUSHED;
- else if (option->activeSubControls == subControl && option->state.testFlag(QStyle::State_MouseOver))
- theme->stateId = RBS_HOT;
- else if (!isTitleBarActive)
- theme->stateId = RBS_INACTIVE;
- else
- theme->stateId = RBS_NORMAL;
-}
-
-/*!
- \reimp
-*/
-void QWindowsXPStyle::drawComplexControl(ComplexControl cc, const QStyleOptionComplex *option,
- QPainter *p, const QWidget *widget) const
-{
- if (!QWindowsXPStylePrivate::useXP()) {
- QWindowsStyle::drawComplexControl(cc, option, p, widget);
- return;
- }
-
- auto *d = const_cast<QWindowsXPStylePrivate*>(d_func());
-
- State flags = option->state;
- SubControls sub = option->subControls;
-
- int partId = 0;
- int stateId = 0;
- if (widget && widget->testAttribute(Qt::WA_UnderMouse) && widget->isActiveWindow())
- flags |= State_MouseOver;
-
- switch (cc) {
-#if QT_CONFIG(slider)
- case CC_Slider:
- if (const auto *slider = qstyleoption_cast<const QStyleOptionSlider *>(option)) {
- XPThemeData theme(widget, p, QWindowsXPStylePrivate::TrackBarTheme);
- QRect slrect = slider->rect;
- QRegion tickreg = slrect;
- if (sub & SC_SliderGroove) {
- theme.rect = proxy()->subControlRect(CC_Slider, option, SC_SliderGroove, widget);
- if (slider->orientation == Qt::Horizontal) {
- partId = TKP_TRACK;
- stateId = TRS_NORMAL;
- theme.rect = QRect(slrect.left(), theme.rect.center().y() - 2, slrect.width(), 4);
- } else {
- partId = TKP_TRACKVERT;
- stateId = TRVS_NORMAL;
- theme.rect = QRect(theme.rect.center().x() - 2, slrect.top(), 4, slrect.height());
- }
- theme.partId = partId;
- theme.stateId = stateId;
- d->drawBackground(theme);
- tickreg -= theme.rect;
- }
- if (sub & SC_SliderTickmarks) {
- int tickOffset = proxy()->pixelMetric(PM_SliderTickmarkOffset, slider, widget);
- int ticks = slider->tickPosition;
- int thickness = proxy()->pixelMetric(PM_SliderControlThickness, slider, widget);
- int len = proxy()->pixelMetric(PM_SliderLength, slider, widget);
- int available = proxy()->pixelMetric(PM_SliderSpaceAvailable, slider, widget);
- int interval = slider->tickInterval;
- if (interval <= 0) {
- interval = slider->singleStep;
- if (QStyle::sliderPositionFromValue(slider->minimum, slider->maximum, interval,
- available)
- - QStyle::sliderPositionFromValue(slider->minimum, slider->maximum,
- 0, available) < 3)
- interval = slider->pageStep;
- }
- if (!interval)
- interval = 1;
- int fudge = len / 2;
- int pos;
- int bothOffset = (ticks & QSlider::TicksAbove && ticks & QSlider::TicksBelow) ? 1 : 0;
- p->setPen(d->sliderTickColor);
- QVarLengthArray<QLine, 32> lines;
- int v = slider->minimum;
- while (v <= slider->maximum + 1) {
- if (v == slider->maximum + 1 && interval == 1)
- break;
- const int v_ = qMin(v, slider->maximum);
- int tickLength = (v_ == slider->minimum || v_ >= slider->maximum) ? 4 : 3;
- pos = QStyle::sliderPositionFromValue(slider->minimum, slider->maximum,
- v_, available) + fudge;
- if (slider->orientation == Qt::Horizontal) {
- if (ticks & QSlider::TicksAbove) {
- lines.append(QLine(pos, tickOffset - 1 - bothOffset,
- pos, tickOffset - 1 - bothOffset - tickLength));
- }
-
- if (ticks & QSlider::TicksBelow) {
- lines.append(QLine(pos, tickOffset + thickness + bothOffset,
- pos, tickOffset + thickness + bothOffset + tickLength));
- }
- } else {
- if (ticks & QSlider::TicksAbove) {
- lines.append(QLine(tickOffset - 1 - bothOffset, pos,
- tickOffset - 1 - bothOffset - tickLength, pos));
- }
-
- if (ticks & QSlider::TicksBelow) {
- lines.append(QLine(tickOffset + thickness + bothOffset, pos,
- tickOffset + thickness + bothOffset + tickLength, pos));
- }
- }
- // in the case where maximum is max int
- int nextInterval = v + interval;
- if (nextInterval < v)
- break;
- v = nextInterval;
- }
- if (!lines.isEmpty()) {
- p->save();
- p->translate(slrect.topLeft());
- p->drawLines(lines.constData(), lines.size());
- p->restore();
- }
- }
- if (sub & SC_SliderHandle) {
- theme.rect = proxy()->subControlRect(CC_Slider, option, SC_SliderHandle, widget);
- if (slider->orientation == Qt::Horizontal) {
- if (slider->tickPosition == QSlider::TicksAbove)
- partId = TKP_THUMBTOP;
- else if (slider->tickPosition == QSlider::TicksBelow)
- partId = TKP_THUMBBOTTOM;
- else
- partId = TKP_THUMB;
-
- if (!(slider->state & State_Enabled))
- stateId = TUS_DISABLED;
- else if (slider->activeSubControls & SC_SliderHandle && (slider->state & State_Sunken))
- stateId = TUS_PRESSED;
- else if (slider->activeSubControls & SC_SliderHandle && (slider->state & State_MouseOver))
- stateId = TUS_HOT;
- else if (flags & State_HasFocus)
- stateId = TUS_FOCUSED;
- else
- stateId = TUS_NORMAL;
- } else {
- if (slider->tickPosition == QSlider::TicksLeft)
- partId = TKP_THUMBLEFT;
- else if (slider->tickPosition == QSlider::TicksRight)
- partId = TKP_THUMBRIGHT;
- else
- partId = TKP_THUMBVERT;
-
- if (!(slider->state & State_Enabled))
- stateId = TUVS_DISABLED;
- else if (slider->activeSubControls & SC_SliderHandle && (slider->state & State_Sunken))
- stateId = TUVS_PRESSED;
- else if (slider->activeSubControls & SC_SliderHandle && (slider->state & State_MouseOver))
- stateId = TUVS_HOT;
- else if (flags & State_HasFocus)
- stateId = TUVS_FOCUSED;
- else
- stateId = TUVS_NORMAL;
- }
- theme.partId = partId;
- theme.stateId = stateId;
- d->drawBackground(theme);
- }
- if (slider->state & State_HasFocus) {
- QStyleOptionFocusRect fropt;
- fropt.QStyleOption::operator=(*slider);
- fropt.rect = subElementRect(SE_SliderFocusRect, slider, widget);
- proxy()->drawPrimitive(PE_FrameFocusRect, &fropt, p, widget);
- }
- }
- break;
-#endif
-#if QT_CONFIG(toolbutton)
- case CC_ToolButton:
- if (const auto *toolbutton = qstyleoption_cast<const QStyleOptionToolButton *>(option)) {
- QRect button, menuarea;
- button = proxy()->subControlRect(cc, toolbutton, SC_ToolButton, widget);
- menuarea = proxy()->subControlRect(cc, toolbutton, SC_ToolButtonMenu, widget);
-
- State bflags = toolbutton->state & ~State_Sunken;
- State mflags = bflags;
- bool autoRaise = flags & State_AutoRaise;
- if (autoRaise) {
- if (!(bflags & State_MouseOver) || !(bflags & State_Enabled))
- bflags &= ~State_Raised;
- }
-
- if (toolbutton->state & State_Sunken) {
- if (toolbutton->activeSubControls & SC_ToolButton) {
- bflags |= State_Sunken;
- mflags |= State_MouseOver | State_Sunken;
- } else if (toolbutton->activeSubControls & SC_ToolButtonMenu) {
- mflags |= State_Sunken;
- bflags |= State_MouseOver;
- }
- }
-
- QStyleOption tool = *toolbutton;
- if (toolbutton->subControls & SC_ToolButton) {
- if (flags & (State_Sunken | State_On | State_Raised) || !autoRaise) {
- if (toolbutton->features & QStyleOptionToolButton::MenuButtonPopup && autoRaise) {
- XPThemeData theme(widget, p, QWindowsXPStylePrivate::ToolBarTheme);
- theme.partId = TP_SPLITBUTTON;
- theme.rect = button;
- if (!(bflags & State_Enabled))
- stateId = TS_DISABLED;
- else if (bflags & State_Sunken)
- stateId = TS_PRESSED;
- else if (bflags & State_MouseOver || !(flags & State_AutoRaise))
- stateId = flags & State_On ? TS_HOTCHECKED : TS_HOT;
- else if (bflags & State_On)
- stateId = TS_CHECKED;
- else
- stateId = TS_NORMAL;
- if (option->direction == Qt::RightToLeft)
- theme.mirrorHorizontally = true;
- theme.stateId = stateId;
- d->drawBackground(theme);
- } else {
- tool.rect = option->rect;
- tool.state = bflags;
- if (autoRaise) // for tool bars
- proxy()->drawPrimitive(PE_PanelButtonTool, &tool, p, widget);
- else
- proxy()->drawPrimitive(PE_PanelButtonBevel, &tool, p, widget);
- }
- }
- }
-
- if (toolbutton->state & State_HasFocus) {
- QStyleOptionFocusRect fr;
- fr.QStyleOption::operator=(*toolbutton);
- fr.rect.adjust(3, 3, -3, -3);
- if (toolbutton->features & QStyleOptionToolButton::MenuButtonPopup)
- fr.rect.adjust(0, 0, -proxy()->pixelMetric(QStyle::PM_MenuButtonIndicator,
- toolbutton, widget), 0);
- proxy()->drawPrimitive(PE_FrameFocusRect, &fr, p, widget);
- }
- QStyleOptionToolButton label = *toolbutton;
- label.state = bflags;
- int fw = 2;
- if (!autoRaise)
- label.state &= ~State_Sunken;
- label.rect = button.adjusted(fw, fw, -fw, -fw);
- proxy()->drawControl(CE_ToolButtonLabel, &label, p, widget);
-
- if (toolbutton->subControls & SC_ToolButtonMenu) {
- tool.rect = menuarea;
- tool.state = mflags;
- if (autoRaise) {
- proxy()->drawPrimitive(PE_IndicatorButtonDropDown, &tool, p, widget);
- } else {
- tool.state = mflags;
- menuarea.adjust(-2, 0, 0, 0);
- // Draw menu button
- if ((bflags & State_Sunken) != (mflags & State_Sunken)){
- p->save();
- p->setClipRect(menuarea);
- tool.rect = option->rect;
- proxy()->drawPrimitive(PE_PanelButtonBevel, &tool, p, nullptr);
- p->restore();
- }
- // Draw arrow
- p->save();
- p->setPen(option->palette.dark().color());
- p->drawLine(menuarea.left(), menuarea.top() + 3,
- menuarea.left(), menuarea.bottom() - 3);
- p->setPen(option->palette.light().color());
- p->drawLine(menuarea.left() - 1, menuarea.top() + 3,
- menuarea.left() - 1, menuarea.bottom() - 3);
-
- tool.rect = menuarea.adjusted(2, 3, -2, -1);
- proxy()->drawPrimitive(PE_IndicatorArrowDown, &tool, p, widget);
- p->restore();
- }
- } else if (toolbutton->features & QStyleOptionToolButton::HasMenu) {
- int mbi = proxy()->pixelMetric(PM_MenuButtonIndicator, toolbutton, widget);
- QRect ir = toolbutton->rect;
- QStyleOptionToolButton newBtn = *toolbutton;
- newBtn.rect = QRect(ir.right() + 4 - mbi, ir.height() - mbi + 4, mbi - 5, mbi - 5);
- proxy()->drawPrimitive(PE_IndicatorArrowDown, &newBtn, p, widget);
- }
- }
- break;
-#endif // QT_CONFIG(toolbutton)
-
- case CC_TitleBar:
- if (const auto *tb = qstyleoption_cast<const QStyleOptionTitleBar *>(option)) {
- const qreal factor = QWindowsStylePrivate::nativeMetricScaleFactor(widget);
- bool isActive = tb->titleBarState & QStyle::State_Active;
- XPThemeData theme(widget, p, QWindowsXPStylePrivate::WindowTheme);
- if (sub & SC_TitleBarLabel) {
- partId = (tb->titleBarState & Qt::WindowMinimized) ? WP_MINCAPTION : WP_CAPTION;
- theme.rect = option->rect;
- if (widget && !widget->isEnabled())
- stateId = CS_DISABLED;
- else if (isActive)
- stateId = CS_ACTIVE;
- else
- stateId = CS_INACTIVE;
-
- theme.partId = partId;
- theme.stateId = stateId;
- d->drawBackground(theme);
-
- QRect ir = proxy()->subControlRect(CC_TitleBar, tb, SC_TitleBarLabel, widget);
-
- int result = TST_NONE;
- GetThemeEnumValue(theme.handle(), WP_CAPTION, isActive ? CS_ACTIVE : CS_INACTIVE, TMT_TEXTSHADOWTYPE, &result);
- if (result != TST_NONE) {
- COLORREF textShadowRef;
- GetThemeColor(theme.handle(), WP_CAPTION, isActive ? CS_ACTIVE : CS_INACTIVE, TMT_TEXTSHADOWCOLOR, &textShadowRef);
- QColor textShadow = qRgb(GetRValue(textShadowRef), GetGValue(textShadowRef), GetBValue(textShadowRef));
- p->setPen(textShadow);
- p->drawText(int(ir.x() + 3 * factor), int(ir.y() + 2 * factor),
- int(ir.width() - 1 * factor), ir.height(),
- Qt::AlignLeft | Qt::AlignVCenter | Qt::TextSingleLine, tb->text);
- }
- COLORREF captionText = GetSysColor(isActive ? COLOR_CAPTIONTEXT : COLOR_INACTIVECAPTIONTEXT);
- QColor textColor = qRgb(GetRValue(captionText), GetGValue(captionText), GetBValue(captionText));
- p->setPen(textColor);
- p->drawText(int(ir.x() + 2 * factor), int(ir.y() + 1 * factor),
- int(ir.width() - 2 * factor), ir.height(),
- Qt::AlignLeft | Qt::AlignVCenter | Qt::TextSingleLine, tb->text);
- }
- if (sub & SC_TitleBarSysMenu && tb->titleBarFlags & Qt::WindowSystemMenuHint) {
- theme.rect = proxy()->subControlRect(CC_TitleBar, option, SC_TitleBarSysMenu, widget);
- partId = WP_SYSBUTTON;
- if ((widget && !widget->isEnabled()) || !isActive)
- stateId = SBS_DISABLED;
- else if (option->activeSubControls == SC_TitleBarSysMenu && (option->state & State_Sunken))
- stateId = SBS_PUSHED;
- else if (option->activeSubControls == SC_TitleBarSysMenu && (option->state & State_MouseOver))
- stateId = SBS_HOT;
- else
- stateId = SBS_NORMAL;
- if (!tb->icon.isNull()) {
- tb->icon.paint(p, theme.rect);
- } else {
- theme.partId = partId;
- theme.stateId = stateId;
- if (theme.size().isEmpty()) {
- int iconSize = proxy()->pixelMetric(PM_SmallIconSize, tb, widget);
- QPixmap pm = proxy()->standardIcon(SP_TitleBarMenuButton, tb, widget).pixmap(iconSize, iconSize);
- p->save();
- drawItemPixmap(p, theme.rect, Qt::AlignCenter, pm);
- p->restore();
- } else {
- d->drawBackground(theme);
- }
- }
- }
-
- if (sub & SC_TitleBarMinButton && tb->titleBarFlags & Qt::WindowMinimizeButtonHint
- && !(tb->titleBarState & Qt::WindowMinimized)) {
- populateTitleBarButtonTheme(proxy(), widget, option, SC_TitleBarMinButton, isActive, WP_MINBUTTON, &theme);
- d->drawBackground(theme);
- }
- if (sub & SC_TitleBarMaxButton && tb->titleBarFlags & Qt::WindowMaximizeButtonHint
- && !(tb->titleBarState & Qt::WindowMaximized)) {
- populateTitleBarButtonTheme(proxy(), widget, option, SC_TitleBarMaxButton, isActive, WP_MAXBUTTON, &theme);
- d->drawBackground(theme);
- }
- if (sub & SC_TitleBarContextHelpButton
- && tb->titleBarFlags & Qt::WindowContextHelpButtonHint) {
- populateTitleBarButtonTheme(proxy(), widget, option, SC_TitleBarContextHelpButton, isActive, WP_HELPBUTTON, &theme);
- d->drawBackground(theme);
- }
- bool drawNormalButton = (sub & SC_TitleBarNormalButton)
- && (((tb->titleBarFlags & Qt::WindowMinimizeButtonHint)
- && (tb->titleBarState & Qt::WindowMinimized))
- || ((tb->titleBarFlags & Qt::WindowMaximizeButtonHint)
- && (tb->titleBarState & Qt::WindowMaximized)));
- if (drawNormalButton) {
- populateTitleBarButtonTheme(proxy(), widget, option, SC_TitleBarNormalButton, isActive, WP_RESTOREBUTTON, &theme);
- d->drawBackground(theme);
- }
- if (sub & SC_TitleBarShadeButton && tb->titleBarFlags & Qt::WindowShadeButtonHint
- && !(tb->titleBarState & Qt::WindowMinimized)) {
- populateTitleBarButtonTheme(proxy(), widget, option, SC_TitleBarShadeButton, isActive, WP_MINBUTTON, &theme);
- d->drawBackground(theme);
- }
- if (sub & SC_TitleBarUnshadeButton && tb->titleBarFlags & Qt::WindowShadeButtonHint
- && tb->titleBarState & Qt::WindowMinimized) {
- populateTitleBarButtonTheme(proxy(), widget, option, SC_TitleBarUnshadeButton, isActive, WP_RESTOREBUTTON, &theme);
- d->drawBackground(theme);
- }
- if (sub & SC_TitleBarCloseButton && tb->titleBarFlags & Qt::WindowSystemMenuHint) {
- populateTitleBarButtonTheme(proxy(), widget, option, SC_TitleBarCloseButton, isActive, WP_CLOSEBUTTON, &theme);
- d->drawBackground(theme);
- }
- }
- break;
-
-#if QT_CONFIG(mdiarea)
- case CC_MdiControls: {
- XPThemeData theme(widget, p, QWindowsXPStylePrivate::WindowTheme, WP_MDICLOSEBUTTON, CBS_NORMAL);
- if (Q_UNLIKELY(!theme.isValid()))
- return;
-
- if (option->subControls.testFlag(SC_MdiCloseButton)) {
- populateMdiButtonTheme(proxy(), widget, option, SC_MdiCloseButton, WP_MDICLOSEBUTTON, &theme);
- d->drawBackground(theme, mdiButtonCorrectionFactor(theme, widget));
- }
- if (option->subControls.testFlag(SC_MdiNormalButton)) {
- populateMdiButtonTheme(proxy(), widget, option, SC_MdiNormalButton, WP_MDIRESTOREBUTTON, &theme);
- d->drawBackground(theme, mdiButtonCorrectionFactor(theme, widget));
- }
- if (option->subControls.testFlag(QStyle::SC_MdiMinButton)) {
- populateMdiButtonTheme(proxy(), widget, option, SC_MdiMinButton, WP_MDIMINBUTTON, &theme);
- d->drawBackground(theme, mdiButtonCorrectionFactor(theme, widget));
- }
- break;
- }
-#endif // QT_CONFIG(mdiarea)
-#if QT_CONFIG(dial)
- case CC_Dial:
- if (const auto *dial = qstyleoption_cast<const QStyleOptionSlider *>(option))
- QStyleHelper::drawDial(dial, p);
- break;
-#endif // QT_CONFIG(dial)
- default:
- QWindowsStyle::drawComplexControl(cc, option, p, widget);
- break;
- }
-}
-
-int QWindowsXPStylePrivate::pixelMetricFromSystemDp(QStyle::PixelMetric pm, const QStyleOption *option, const QWidget *widget)
-{
- switch (pm) {
- case QStyle::PM_IndicatorWidth:
- return XPThemeData::themeSize(widget, nullptr, QWindowsXPStylePrivate::ButtonTheme, BP_CHECKBOX, CBS_UNCHECKEDNORMAL).width();
- case QStyle::PM_IndicatorHeight:
- return XPThemeData::themeSize(widget, nullptr, QWindowsXPStylePrivate::ButtonTheme, BP_CHECKBOX, CBS_UNCHECKEDNORMAL).height();
- case QStyle::PM_ExclusiveIndicatorWidth:
- return XPThemeData::themeSize(widget, nullptr, QWindowsXPStylePrivate::ButtonTheme, BP_RADIOBUTTON, RBS_UNCHECKEDNORMAL).width();
- case QStyle::PM_ExclusiveIndicatorHeight:
- return XPThemeData::themeSize(widget, nullptr, QWindowsXPStylePrivate::ButtonTheme, BP_RADIOBUTTON, RBS_UNCHECKEDNORMAL).height();
- case QStyle::PM_ProgressBarChunkWidth:
- return progressBarOrientation(option) == Qt::Horizontal
- ? XPThemeData::themeSize(widget, nullptr, QWindowsXPStylePrivate::ProgressTheme, PP_CHUNK).width()
- : XPThemeData::themeSize(widget, nullptr, QWindowsXPStylePrivate::ProgressTheme, PP_CHUNKVERT).height();
- case QStyle::PM_SliderThickness:
- return XPThemeData::themeSize(widget, nullptr, QWindowsXPStylePrivate::TrackBarTheme, TKP_THUMB).height();
- case QStyle::PM_TitleBarHeight:
- return widget && (widget->windowType() == Qt::Tool)
- ? GetSystemMetrics(SM_CYSMCAPTION) + GetSystemMetrics(SM_CXSIZEFRAME)
- : GetSystemMetrics(SM_CYCAPTION) + GetSystemMetrics(SM_CXSIZEFRAME);
- case QStyle::PM_MdiSubWindowFrameWidth:
- return XPThemeData::themeSize(widget, nullptr, QWindowsXPStylePrivate::WindowTheme, WP_FRAMELEFT, FS_ACTIVE).width();
- case QStyle::PM_DockWidgetFrameWidth:
- return XPThemeData::themeSize(widget, nullptr, QWindowsXPStylePrivate::WindowTheme, WP_SMALLFRAMERIGHT, FS_ACTIVE).width();
- default:
- break;
- }
- return QWindowsXPStylePrivate::InvalidMetric;
-}
-
-/*! \reimp */
-int QWindowsXPStyle::pixelMetric(PixelMetric pm, const QStyleOption *option, const QWidget *widget) const
-{
- if (!QWindowsXPStylePrivate::useXP())
- return QWindowsStyle::pixelMetric(pm, option, widget);
-
- int res = QWindowsXPStylePrivate::pixelMetricFromSystemDp(pm, option, widget);
- if (res != QWindowsStylePrivate::InvalidMetric)
- return qRound(qreal(res) * QWindowsStylePrivate::nativeMetricScaleFactor(widget));
-
- res = 0;
- switch (pm) {
- case PM_MenuBarPanelWidth:
- case PM_ButtonDefaultIndicator:
- res = 0;
- break;
-
- case PM_DefaultFrameWidth:
- res = qobject_cast<const QListView*>(widget) ? 2 : 1;
- break;
- case PM_MenuPanelWidth:
- case PM_SpinBoxFrameWidth:
- res = 1;
- break;
-
- case PM_TabBarTabOverlap:
- case PM_MenuHMargin:
- case PM_MenuVMargin:
- res = 2;
- break;
-
- case PM_TabBarBaseOverlap:
- if (const auto *tab = qstyleoption_cast<const QStyleOptionTab *>(option)) {
- switch (tab->shape) {
- case QTabBar::RoundedNorth:
- case QTabBar::TriangularNorth:
- case QTabBar::RoundedWest:
- case QTabBar::TriangularWest:
- res = 1;
- break;
- case QTabBar::RoundedSouth:
- case QTabBar::TriangularSouth:
- res = 2;
- break;
- case QTabBar::RoundedEast:
- case QTabBar::TriangularEast:
- res = 3;
- break;
- }
- }
- break;
-
- case PM_SplitterWidth:
- res = QStyleHelper::dpiScaled(5., option);
- break;
-
- case PM_MdiSubWindowMinimizedWidth:
- res = 160;
- break;
-
-#if QT_CONFIG(toolbar)
- case PM_ToolBarHandleExtent:
- res = int(QStyleHelper::dpiScaled(8., option));
- break;
-
-#endif // QT_CONFIG(toolbar)
- case PM_DockWidgetSeparatorExtent:
- case PM_DockWidgetTitleMargin:
- res = int(QStyleHelper::dpiScaled(4., option));
- break;
-
- case PM_ButtonShiftHorizontal:
- case PM_ButtonShiftVertical:
- res = qstyleoption_cast<const QStyleOptionToolButton *>(option) ? 1 : 0;
- break;
-
- default:
- res = QWindowsStyle::pixelMetric(pm, option, widget);
- }
-
- return res;
-}
-
-/*
- This function is used by subControlRect to check if a button
- should be drawn for the given subControl given a set of window flags.
-*/
-static bool buttonVisible(const QStyle::SubControl sc, const QStyleOptionTitleBar *tb)
-{
- bool isMinimized = tb->titleBarState & Qt::WindowMinimized;
- bool isMaximized = tb->titleBarState & Qt::WindowMaximized;
- const uint flags = tb->titleBarFlags;
- bool retVal = false;
- switch (sc) {
- case QStyle::SC_TitleBarContextHelpButton:
- if (flags & Qt::WindowContextHelpButtonHint)
- retVal = true;
- break;
- case QStyle::SC_TitleBarMinButton:
- if (!isMinimized && (flags & Qt::WindowMinimizeButtonHint))
- retVal = true;
- break;
- case QStyle::SC_TitleBarNormalButton:
- if (isMinimized && (flags & Qt::WindowMinimizeButtonHint))
- retVal = true;
- else if (isMaximized && (flags & Qt::WindowMaximizeButtonHint))
- retVal = true;
- break;
- case QStyle::SC_TitleBarMaxButton:
- if (!isMaximized && (flags & Qt::WindowMaximizeButtonHint))
- retVal = true;
- break;
- case QStyle::SC_TitleBarShadeButton:
- if (!isMinimized && flags & Qt::WindowShadeButtonHint)
- retVal = true;
- break;
- case QStyle::SC_TitleBarUnshadeButton:
- if (isMinimized && flags & Qt::WindowShadeButtonHint)
- retVal = true;
- break;
- case QStyle::SC_TitleBarCloseButton:
- if (flags & Qt::WindowSystemMenuHint)
- retVal = true;
- break;
- case QStyle::SC_TitleBarSysMenu:
- if (flags & Qt::WindowSystemMenuHint)
- retVal = true;
- break;
- default :
- retVal = true;
- }
- return retVal;
-}
-
-/*!
- \reimp
-*/
-QRect QWindowsXPStyle::subControlRect(ComplexControl cc, const QStyleOptionComplex *option,
- SubControl subControl, const QWidget *widget) const
-{
- if (!QWindowsXPStylePrivate::useXP())
- return QWindowsStyle::subControlRect(cc, option, subControl, widget);
-
- QRect rect;
-
- switch (cc) {
- case CC_TitleBar:
- if (const auto *tb = qstyleoption_cast<const QStyleOptionTitleBar *>(option)) {
- if (!buttonVisible(subControl, tb))
- return rect;
- const bool isToolTitle = false;
- const int height = tb->rect.height();
- const int width = tb->rect.width();
- const int buttonMargin = int(QStyleHelper::dpiScaled(4, option));
- const qreal factor = QWindowsStylePrivate::nativeMetricScaleFactor(widget);
- int buttonHeight = qRound(qreal(GetSystemMetrics(SM_CYSIZE)) * factor)
- - buttonMargin;
- int buttonWidth = qRound(qreal(GetSystemMetrics(SM_CXSIZE)) * factor)
- - buttonMargin;
- const int delta = buttonWidth + 2;
- int controlTop = option->rect.bottom() - buttonHeight - 2;
- const int frameWidth = proxy()->pixelMetric(PM_MdiSubWindowFrameWidth, option, widget);
- const bool sysmenuHint = (tb->titleBarFlags & Qt::WindowSystemMenuHint) != 0;
- const bool minimizeHint = (tb->titleBarFlags & Qt::WindowMinimizeButtonHint) != 0;
- const bool maximizeHint = (tb->titleBarFlags & Qt::WindowMaximizeButtonHint) != 0;
- const bool contextHint = (tb->titleBarFlags & Qt::WindowContextHelpButtonHint) != 0;
- const bool shadeHint = (tb->titleBarFlags & Qt::WindowShadeButtonHint) != 0;
- bool isMinimized = tb->titleBarState & Qt::WindowMinimized;
- bool isMaximized = tb->titleBarState & Qt::WindowMaximized;
- int offset = 0;
-
- switch (subControl) {
- case SC_TitleBarLabel:
- rect = QRect(frameWidth, 0, width - (buttonWidth + frameWidth + 10), height);
- if (isToolTitle) {
- if (sysmenuHint) {
- rect.adjust(0, 0, -buttonWidth - 3, 0);
- }
- if (minimizeHint || maximizeHint)
- rect.adjust(0, 0, -buttonWidth - 2, 0);
- } else {
- if (sysmenuHint) {
- const int leftOffset = height - 8;
- rect.adjust(leftOffset, 0, 0, 0);
- }
- if (minimizeHint)
- rect.adjust(0, 0, -buttonWidth - 2, 0);
- if (maximizeHint)
- rect.adjust(0, 0, -buttonWidth - 2, 0);
- if (contextHint)
- rect.adjust(0, 0, -buttonWidth - 2, 0);
- if (shadeHint)
- rect.adjust(0, 0, -buttonWidth - 2, 0);
- }
- break;
-
- case SC_TitleBarContextHelpButton:
- if (tb->titleBarFlags & Qt::WindowContextHelpButtonHint)
- offset += delta;
- Q_FALLTHROUGH();
- case SC_TitleBarMinButton:
- if (!isMinimized && (tb->titleBarFlags & Qt::WindowMinimizeButtonHint))
- offset += delta;
- else if (subControl == SC_TitleBarMinButton)
- break;
- Q_FALLTHROUGH();
- case SC_TitleBarNormalButton:
- if (isMinimized && (tb->titleBarFlags & Qt::WindowMinimizeButtonHint))
- offset += delta;
- else if (isMaximized && (tb->titleBarFlags & Qt::WindowMaximizeButtonHint))
- offset += delta;
- else if (subControl == SC_TitleBarNormalButton)
- break;
- Q_FALLTHROUGH();
- case SC_TitleBarMaxButton:
- if (!isMaximized && (tb->titleBarFlags & Qt::WindowMaximizeButtonHint))
- offset += delta;
- else if (subControl == SC_TitleBarMaxButton)
- break;
- Q_FALLTHROUGH();
- case SC_TitleBarShadeButton:
- if (!isMinimized && (tb->titleBarFlags & Qt::WindowShadeButtonHint))
- offset += delta;
- else if (subControl == SC_TitleBarShadeButton)
- break;
- Q_FALLTHROUGH();
- case SC_TitleBarUnshadeButton:
- if (isMinimized && (tb->titleBarFlags & Qt::WindowShadeButtonHint))
- offset += delta;
- else if (subControl == SC_TitleBarUnshadeButton)
- break;
- Q_FALLTHROUGH();
- case SC_TitleBarCloseButton:
- if (tb->titleBarFlags & Qt::WindowSystemMenuHint)
- offset += delta;
- else if (subControl == SC_TitleBarCloseButton)
- break;
-
- rect.setRect(width - offset - controlTop + 1, controlTop,
- buttonWidth, buttonHeight);
- break;
-
- case SC_TitleBarSysMenu: {
- const int controlTop = 6;
- const int controlHeight = height - controlTop - 3;
- const int iconExtent = proxy()->pixelMetric(PM_SmallIconSize, option);
- QSize iconSize = tb->icon.actualSize(QSize(iconExtent, iconExtent));
- if (tb->icon.isNull())
- iconSize = QSize(controlHeight, controlHeight);
- int hPad = (controlHeight - iconSize.height())/2;
- int vPad = (controlHeight - iconSize.width())/2;
- rect = QRect(frameWidth + hPad, controlTop + vPad, iconSize.width(), iconSize.height());
- break;
- }
- default:
- break;
- }
- }
- break;
-
- case CC_ComboBox:
- if (const auto *cmb = qstyleoption_cast<const QStyleOptionComboBox *>(option)) {
- const int x = cmb->rect.x(), y = cmb->rect.y(), wi = cmb->rect.width(), he = cmb->rect.height();
- const int xpos = x + wi - qRound(QStyleHelper::dpiScaled(1 + 16, option));
-
- switch (subControl) {
- case SC_ComboBoxFrame:
- rect = cmb->rect;
- break;
-
- case SC_ComboBoxArrow: {
- const qreal dpi = QStyleHelper::dpi(option);
- rect = QRect(xpos, y + qRound(QStyleHelper::dpiScaled(1, dpi)),
- qRound(QStyleHelper::dpiScaled(16, dpi)),
- he - qRound(QStyleHelper::dpiScaled(2, dpi)));
- break;
- }
-
- case SC_ComboBoxEditField: {
- const qreal dpi = QStyleHelper::dpi(option);
- const int frame = qRound(QStyleHelper::dpiScaled(2, dpi));
- rect = QRect(x + frame, y + frame,
- wi - qRound(QStyleHelper::dpiScaled(3 + 16, dpi)),
- he - qRound(QStyleHelper::dpiScaled(4, dpi)));
- break;
- }
-
- case SC_ComboBoxListBoxPopup:
- rect = cmb->rect;
- break;
-
- default:
- break;
- }
- }
- break;
-#if QT_CONFIG(mdiarea)
- case CC_MdiControls: {
- int numSubControls = 0;
- if (option->subControls & SC_MdiCloseButton)
- ++numSubControls;
- if (option->subControls & SC_MdiMinButton)
- ++numSubControls;
- if (option->subControls & SC_MdiNormalButton)
- ++numSubControls;
- if (numSubControls == 0)
- break;
-
- int buttonWidth = option->rect.width() / numSubControls;
- int offset = 0;
- switch (subControl) {
- case SC_MdiCloseButton:
- // Only one sub control, no offset needed.
- if (numSubControls == 1)
- break;
- offset += buttonWidth;
- Q_FALLTHROUGH();
- case SC_MdiNormalButton:
- // No offset needed if
- // 1) There's only one sub control
- // 2) We have a close button and a normal button (offset already added in SC_MdiClose)
- if (numSubControls == 1 || (numSubControls == 2 && !(option->subControls & SC_MdiMinButton)))
- break;
- if (option->subControls & SC_MdiNormalButton)
- offset += buttonWidth;
- break;
- default:
- break;
- }
- rect = QRect(offset, 0, buttonWidth, option->rect.height());
- break;
- }
-#endif // QT_CONFIG(mdiarea)
-
- default:
- rect = visualRect(option->direction, option->rect,
- QWindowsStyle::subControlRect(cc, option, subControl, widget));
- break;
- }
- return visualRect(option->direction, option->rect, rect);
-}
-
-/*!
- \reimp
-*/
-QSize QWindowsXPStyle::sizeFromContents(ContentsType ct, const QStyleOption *option,
- const QSize &contentsSize, const QWidget *widget) const
-{
- if (!QWindowsXPStylePrivate::useXP())
- return QWindowsStyle::sizeFromContents(ct, option, contentsSize, widget);
-
- QSize sz(contentsSize);
- switch (ct) {
- case CT_LineEdit:
- case CT_ComboBox: {
- XPThemeData buttontheme(widget, nullptr, QWindowsXPStylePrivate::ButtonTheme, BP_PUSHBUTTON, PBS_NORMAL);
- if (buttontheme.isValid()) {
- const qreal factor = QWindowsXPStylePrivate::nativeMetricScaleFactor(widget);
- const QMarginsF borderSize = buttontheme.margins() * factor;
- if (!borderSize.isNull()) {
- const qreal margin = qreal(2) * factor;
- sz.rwidth() += qRound(borderSize.left() + borderSize.right() - margin);
- sz.rheight() += int(borderSize.bottom() + borderSize.top() - margin
- + qreal(1) / factor - 1);
- }
- const int textMargins = 2*(proxy()->pixelMetric(PM_FocusFrameHMargin, option) + 1);
- sz += QSize(qMax(pixelMetric(QStyle::PM_ScrollBarExtent, option, widget)
- + textMargins, 23), 0); //arrow button
- }
- break;
- }
- case CT_TabWidget:
- sz += QSize(6, 6);
- break;
- case CT_Menu:
- sz += QSize(1, 0);
- break;
-#if QT_CONFIG(menubar)
- case CT_MenuBarItem:
- if (!sz.isEmpty())
- sz += QSize(windowsItemHMargin * 5 + 1, 6);
- break;
-#endif
- case CT_MenuItem:
- if (const auto *menuitem = qstyleoption_cast<const QStyleOptionMenuItem *>(option)) {
- if (menuitem->menuItemType != QStyleOptionMenuItem::Separator) {
- sz = QWindowsStyle::sizeFromContents(ct, option, sz, widget);
- sz.setHeight(sz.height() - 2);
- return sz;
- }
- }
- sz = QWindowsStyle::sizeFromContents(ct, option, sz, widget);
- break;
-
- case CT_MdiControls: {
- sz.setHeight(int(QStyleHelper::dpiScaled(19, option)));
- int width = 54;
- if (const auto *styleOpt = qstyleoption_cast<const QStyleOptionComplex *>(option)) {
- width = 0;
- if (styleOpt->subControls & SC_MdiMinButton)
- width += 17 + 1;
- if (styleOpt->subControls & SC_MdiNormalButton)
- width += 17 + 1;
- if (styleOpt->subControls & SC_MdiCloseButton)
- width += 17 + 1;
- }
- sz.setWidth(int(QStyleHelper::dpiScaled(width, option)));
- break;
- }
-
- default:
- sz = QWindowsStyle::sizeFromContents(ct, option, sz, widget);
- break;
- }
-
- return sz;
-}
-
-
-/*! \reimp */
-int QWindowsXPStyle::styleHint(StyleHint hint, const QStyleOption *option, const QWidget *widget,
- QStyleHintReturn *returnData) const
-{
- if (!QWindowsXPStylePrivate::useXP())
- return QWindowsStyle::styleHint(hint, option, widget, returnData);
-
- auto *d = const_cast<QWindowsXPStylePrivate*>(d_func());
- int res = 0;
- switch (hint) {
- case SH_EtchDisabledText:
- res = (qobject_cast<const QLabel*>(widget) != 0);
- break;
-
- case SH_SpinControls_DisableOnBounds:
- res = 0;
- break;
-
- case SH_TitleBar_AutoRaise:
- case SH_TitleBar_NoBorder:
- res = 1;
- break;
-
- case SH_GroupBox_TextLabelColor:
- if (!widget || widget->isEnabled())
- res = d->groupBoxTextColor;
- else
- res = d->groupBoxTextColorDisabled;
- break;
-
- case SH_Table_GridLineColor:
- res = 0xC0C0C0;
- break;
-
- case SH_WindowFrame_Mask: {
- res = 1;
- auto *mask = qstyleoption_cast<QStyleHintReturnMask *>(returnData);
- const auto *titlebar = qstyleoption_cast<const QStyleOptionTitleBar *>(option);
- if (mask && titlebar) {
- // Note certain themes will not return the whole window frame but only the titlebar part when
- // queried This function needs to return the entire window mask, hence we will only fetch the mask for the
- // titlebar itself and add the remaining part of the window rect at the bottom.
- int tbHeight = proxy()->pixelMetric(PM_TitleBarHeight, option, widget);
- QRect titleBarRect = option->rect;
- titleBarRect.setHeight(tbHeight);
- XPThemeData themeData;
- if (titlebar->titleBarState & Qt::WindowMinimized) {
- themeData = XPThemeData(widget, nullptr,
- QWindowsXPStylePrivate::WindowTheme,
- WP_MINCAPTION, CS_ACTIVE, titleBarRect);
- } else
- themeData = XPThemeData(widget, nullptr,
- QWindowsXPStylePrivate::WindowTheme,
- WP_CAPTION, CS_ACTIVE, titleBarRect);
- mask->region = d->region(themeData) +
- QRect(0, tbHeight, option->rect.width(), option->rect.height() - tbHeight);
- }
- break;
- }
-#if QT_CONFIG(rubberband)
- case SH_RubberBand_Mask:
- if (qstyleoption_cast<const QStyleOptionRubberBand *>(option))
- res = 0;
- break;
-#endif // QT_CONFIG(rubberband)
-
- case SH_ItemView_DrawDelegateFrame:
- res = 1;
- break;
-
- default:
- res = QWindowsStyle::styleHint(hint, option, widget, returnData);
- }
-
- return res;
-}
-
-/*! \reimp */
-QPalette QWindowsXPStyle::standardPalette() const
-{
- return QWindowsXPStylePrivate::useXP() ? QPalette() : QWindowsStyle::standardPalette();
-}
-
-/*!
- \reimp
-*/
-QPixmap QWindowsXPStyle::standardPixmap(StandardPixmap standardPixmap, const QStyleOption *option,
- const QWidget *widget) const
-{
- if (!QWindowsXPStylePrivate::useXP())
- return QWindowsStyle::standardPixmap(standardPixmap, option, widget);
-
- switch(standardPixmap) {
- case SP_TitleBarMaxButton:
- case SP_TitleBarCloseButton:
- if (qstyleoption_cast<const QStyleOptionDockWidget *>(option)) {
- if (widget && widget->isWindow()) {
- XPThemeData theme(widget, nullptr, QWindowsXPStylePrivate::WindowTheme, WP_SMALLCLOSEBUTTON, CBS_NORMAL);
- if (theme.isValid()) {
- const QSize size = (theme.size() * QWindowsStylePrivate::nativeMetricScaleFactor(widget)).toSize();
- return QIcon(QWindowsStyle::standardPixmap(standardPixmap, option, widget)).pixmap(size);
- }
- }
- }
- break;
- default:
- break;
- }
- return QWindowsStyle::standardPixmap(standardPixmap, option, widget);
-}
-
-/*!
- \reimp
-*/
-QIcon QWindowsXPStyle::standardIcon(StandardPixmap standardIcon,
- const QStyleOption *option,
- const QWidget *widget) const
-{
- if (!QWindowsXPStylePrivate::useXP())
- return QWindowsStyle::standardIcon(standardIcon, option, widget);
-
- auto *d = const_cast<QWindowsXPStylePrivate*>(d_func());
- switch(standardIcon) {
- case SP_TitleBarMaxButton:
- if (qstyleoption_cast<const QStyleOptionDockWidget *>(option)) {
- if (d->dockFloat.isNull()) {
- XPThemeData themeSize(nullptr, nullptr, QWindowsXPStylePrivate::WindowTheme,
- WP_SMALLCLOSEBUTTON, CBS_NORMAL);
- XPThemeData theme(nullptr, nullptr, QWindowsXPStylePrivate::WindowTheme,
- WP_MAXBUTTON, MAXBS_NORMAL);
- if (theme.isValid()) {
- const QSize size = (themeSize.size() * QWindowsStylePrivate::nativeMetricScaleFactor(widget)).toSize();
- QPixmap pm(size);
- pm.fill(Qt::transparent);
- QPainter p(&pm);
- theme.painter = &p;
- theme.rect = QRect(QPoint(0, 0), size);
- d->drawBackground(theme);
- d->dockFloat.addPixmap(pm, QIcon::Normal, QIcon::Off); // Normal
- pm.fill(Qt::transparent);
- theme.stateId = MAXBS_PUSHED;
- d->drawBackground(theme);
- d->dockFloat.addPixmap(pm, QIcon::Normal, QIcon::On); // Pressed
- pm.fill(Qt::transparent);
- theme.stateId = MAXBS_HOT;
- d->drawBackground(theme);
- d->dockFloat.addPixmap(pm, QIcon::Active, QIcon::Off); // Hover
- pm.fill(Qt::transparent);
- theme.stateId = MAXBS_INACTIVE;
- d->drawBackground(theme);
- d->dockFloat.addPixmap(pm, QIcon::Disabled, QIcon::Off); // Disabled
- }
- }
- if (widget && widget->isWindow())
- return d->dockFloat;
- }
- break;
- case SP_TitleBarCloseButton:
- if (qstyleoption_cast<const QStyleOptionDockWidget *>(option)) {
- if (d->dockClose.isNull()) {
- XPThemeData theme(nullptr, nullptr, QWindowsXPStylePrivate::WindowTheme,
- WP_SMALLCLOSEBUTTON, CBS_NORMAL);
- if (theme.isValid()) {
- const QSize size = (theme.size() * QWindowsStylePrivate::nativeMetricScaleFactor(widget)).toSize();
- QPixmap pm(size);
- pm.fill(Qt::transparent);
- QPainter p(&pm);
- theme.painter = &p;
- theme.partId = WP_CLOSEBUTTON; // ####
- theme.rect = QRect(QPoint(0, 0), size);
- d->drawBackground(theme);
- d->dockClose.addPixmap(pm, QIcon::Normal, QIcon::Off); // Normal
- pm.fill(Qt::transparent);
- theme.stateId = CBS_PUSHED;
- d->drawBackground(theme);
- d->dockClose.addPixmap(pm, QIcon::Normal, QIcon::On); // Pressed
- pm.fill(Qt::transparent);
- theme.stateId = CBS_HOT;
- d->drawBackground(theme);
- d->dockClose.addPixmap(pm, QIcon::Active, QIcon::Off); // Hover
- pm.fill(Qt::transparent);
- theme.stateId = CBS_INACTIVE;
- d->drawBackground(theme);
- d->dockClose.addPixmap(pm, QIcon::Disabled, QIcon::Off); // Disabled
- }
- }
- if (widget && widget->isWindow())
- return d->dockClose;
- }
- break;
- case SP_TitleBarNormalButton:
- if (qstyleoption_cast<const QStyleOptionDockWidget *>(option)) {
- if (d->dockFloat.isNull()) {
- XPThemeData themeSize(nullptr, nullptr, QWindowsXPStylePrivate::WindowTheme,
- WP_SMALLCLOSEBUTTON, CBS_NORMAL);
- XPThemeData theme(nullptr, nullptr, QWindowsXPStylePrivate::WindowTheme,
- WP_RESTOREBUTTON, RBS_NORMAL);
- if (theme.isValid()) {
- const QSize size = (themeSize.size() * QWindowsStylePrivate::nativeMetricScaleFactor(widget)).toSize();
- QPixmap pm(size);
- pm.fill(Qt::transparent);
- QPainter p(&pm);
- theme.painter = &p;
- theme.rect = QRect(QPoint(0, 0), size);
- d->drawBackground(theme);
- d->dockFloat.addPixmap(pm, QIcon::Normal, QIcon::Off); // Normal
- pm.fill(Qt::transparent);
- theme.stateId = RBS_PUSHED;
- d->drawBackground(theme);
- d->dockFloat.addPixmap(pm, QIcon::Normal, QIcon::On); // Pressed
- pm.fill(Qt::transparent);
- theme.stateId = RBS_HOT;
- d->drawBackground(theme);
- d->dockFloat.addPixmap(pm, QIcon::Active, QIcon::Off); // Hover
- pm.fill(Qt::transparent);
- theme.stateId = RBS_INACTIVE;
- d->drawBackground(theme);
- d->dockFloat.addPixmap(pm, QIcon::Disabled, QIcon::Off); // Disabled
- }
- }
- if (widget && widget->isWindow())
- return d->dockFloat;
- }
- break;
- default:
- break;
- }
-
- return QWindowsStyle::standardIcon(standardIcon, option, widget);
-}
-
-#ifndef QT_NO_DEBUG_STREAM
-QDebug operator<<(QDebug d, const XPThemeData &t)
-{
- QDebugStateSaver saver(d);
- d.nospace();
- d << "XPThemeData(" << t.widget << ", theme=#" << t.theme << ", " << t.htheme
- << ", partId=" << t.partId << ", stateId=" << t.stateId << ", rect=" << t.rect
- << ", mirrorHorizontally=" << t.mirrorHorizontally << ", mirrorVertically="
- << t.mirrorVertically << ", noBorder=" << t.noBorder << ", noContent=" << t.noContent
- << ", rotate=" << t.rotate << ')';
- return d;
-}
-
-QDebug operator<<(QDebug d, const ThemeMapKey &k)
-{
- QDebugStateSaver saver(d);
- d.nospace();
- d << "ThemeMapKey(theme=#" << k.theme
- << ", partId=" << k.partId << ", stateId=" << k.stateId
- << ", noBorder=" << k.noBorder << ", noContent=" << k.noContent << ')';
- return d;
-}
-
-QDebug operator<<(QDebug d, const ThemeMapData &td)
-{
- QDebugStateSaver saver(d);
- d.nospace();
- d << "ThemeMapData(alphaType=" << td.alphaType
- << ", dataValid=" << td.dataValid << ", partIsTransparent=" << td.partIsTransparent
- << ", hasAlphaChannel=" << td.hasAlphaChannel << ", wasAlphaSwapped=" << td.wasAlphaSwapped
- << ", hadInvalidAlpha=" << td.hadInvalidAlpha << ')';
- return d;
-}
-#endif // QT_NO_DEBUG_STREAM
-
-// Debugging code ---------------------------------------------------------------------[ START ]---
-// The code for this point on is not compiled by default, but only used as assisting
-// debugging code when you uncomment the DEBUG_XP_STYLE define at the top of the file.
-
-#ifdef DEBUG_XP_STYLE
-// The schema file expects these to be defined by the user.
-#define TMT_ENUMDEF 8
-#define TMT_ENUMVAL TEXT('A')
-#define TMT_ENUM TEXT('B')
-#define SCHEMA_STRINGS // For 2nd pass on schema file
-QT_BEGIN_INCLUDE_NAMESPACE
-#include <tmschema.h>
-QT_END_INCLUDE_NAMESPACE
-
-// A property's value, type and name combo
-struct PropPair {
- int propValue;
- int propType;
- LPCWSTR propName;
-};
-
-// Operator for sorting of PropPairs
-bool operator<(PropPair a, PropPair b) {
- return wcscmp(a.propName, b.propName) < 0;
-}
-
-// Our list of all possible properties
-static QList<PropPair> all_props;
-
-
-/*! \internal
- Dumps a portion of the full native DIB section double buffer.
- The DIB section double buffer is only used when doing special
- transformations to the theme part, or when the real double
- buffer in the paintengine does not have an HDC we may use
- directly.
- Since we cannot rely on the pixel data we get from Microsoft
- when drawing into the DIB section, we use this function to
- see the actual data we got, and can determine the appropriate
- action.
-*/
-void QWindowsXPStylePrivate::dumpNativeDIB(int w, int h)
-{
- if (w && h) {
- static int pCount = 0;
- DWORD *bufPix = (DWORD*)bufferPixels;
-
- char *bufferDump = new char[bufferH * bufferW * 16];
- char *bufferPos = bufferDump;
-
- memset(bufferDump, 0, sizeof(bufferDump));
- bufferPos += sprintf(bufferPos, "const int pixelBufferW%d = %d;\n", pCount, w);
- bufferPos += sprintf(bufferPos, "const int pixelBufferH%d = %d;\n", pCount, h);
- bufferPos += sprintf(bufferPos, "const unsigned DWORD pixelBuffer%d[] = {", pCount);
- for (int iy = 0; iy < h; ++iy) {
- bufferPos += sprintf(bufferPos, "\n ");
- bufPix = (DWORD*)(bufferPixels + (iy * bufferW * 4));
- for (int ix = 0; ix < w; ++ix) {
- bufferPos += sprintf(bufferPos, "0x%08x, ", *bufPix);
- ++bufPix;
- }
- }
- bufferPos += sprintf(bufferPos, "\n};\n\n");
- printf(bufferDump);
-
- delete[] bufferDump;
- ++pCount;
- }
-}
-
-/*! \internal
- Shows the value of a given property for a part.
-*/
-static void showProperty(XPThemeData &themeData, const PropPair &prop)
-{
- PROPERTYORIGIN origin = PO_NOTFOUND;
- GetThemePropertyOrigin(themeData.handle(), themeData.partId, themeData.stateId, prop.propValue, &origin);
- const char *originStr;
- switch(origin) {
- case PO_STATE:
- originStr = "State ";
- break;
- case PO_PART:
- originStr = "Part ";
- break;
- case PO_CLASS:
- originStr = "Class ";
- break;
- case PO_GLOBAL:
- originStr = "Globl ";
- break;
- case PO_NOTFOUND:
- default:
- originStr = "Unkwn ";
- break;
- }
-
- switch(prop.propType) {
- case TMT_STRING:
- {
- wchar_t buffer[512];
- GetThemeString(themeData.handle(), themeData.partId, themeData.stateId, prop.propValue, buffer, 512);
- printf(" (%sString) %-20S: %S\n", originStr, prop.propName, buffer);
- }
- break;
- case TMT_ENUM:
- {
- int result = -1;
- GetThemeEnumValue(themeData.handle(), themeData.partId, themeData.stateId, prop.propValue, &result);
- printf(" (%sEnum) %-20S: %d\n", originStr, prop.propName, result);
- }
- break;
- case TMT_INT:
- {
- int result = -1;
- GetThemeInt(themeData.handle(), themeData.partId, themeData.stateId, prop.propValue, &result);
- printf(" (%sint) %-20S: %d\n", originStr, prop.propName, result);
- }
- break;
- case TMT_BOOL:
- {
- BOOL result = false;
- GetThemeBool(themeData.handle(), themeData.partId, themeData.stateId, prop.propValue, &result);
- printf(" (%sbool) %-20S: %d\n", originStr, prop.propName, result);
- }
- break;
- case TMT_COLOR:
- {
- COLORREF result = 0;
- GetThemeColor(themeData.handle(), themeData.partId, themeData.stateId, prop.propValue, &result);
- printf(" (%scolor) %-20S: 0x%08X\n", originStr, prop.propName, result);
- }
- break;
- case TMT_MARGINS:
- {
- MARGINS result;
- memset(&result, 0, sizeof(result));
- GetThemeMargins(themeData.handle(), 0, themeData.partId, themeData.stateId, prop.propValue, 0, &result);
- printf(" (%smargins) %-20S: (%d, %d, %d, %d)\n", originStr,
- prop.propName, result.cxLeftWidth, result.cyTopHeight, result.cxRightWidth, result.cyBottomHeight);
- }
- break;
- case TMT_FILENAME:
- {
- wchar_t buffer[512];
- GetThemeFilename(themeData.handle(), themeData.partId, themeData.stateId, prop.propValue, buffer, 512);
- printf(" (%sfilename)%-20S: %S\n", originStr, prop.propName, buffer);
- }
- break;
- case TMT_SIZE:
- {
- SIZE result1;
- SIZE result2;
- SIZE result3;
- memset(&result1, 0, sizeof(result1));
- memset(&result2, 0, sizeof(result2));
- memset(&result3, 0, sizeof(result3));
- GetThemePartSize(themeData.handle(), 0, themeData.partId, themeData.stateId, 0, TS_MIN, &result1);
- GetThemePartSize(themeData.handle(), 0, themeData.partId, themeData.stateId, 0, TS_TRUE, &result2);
- GetThemePartSize(themeData.handle(), 0, themeData.partId, themeData.stateId, 0, TS_DRAW, &result3);
- printf(" (%ssize) %-20S: Min (%d, %d), True(%d, %d), Draw(%d, %d)\n", originStr, prop.propName,
- result1.cx, result1.cy, result2.cx, result2.cy, result3.cx, result3.cy);
- }
- break;
- case TMT_POSITION:
- {
- POINT result;
- memset(&result, 0, sizeof(result));
- GetThemePosition(themeData.handle(), themeData.partId, themeData.stateId, prop.propValue, &result);
- printf(" (%sPosition)%-20S: (%d, %d)\n", originStr, prop.propName, result.x, result.y);
- }
- break;
- case TMT_RECT:
- {
- RECT result;
- memset(&result, 0, sizeof(result));
- GetThemeRect(themeData.handle(), themeData.partId, themeData.stateId, prop.propValue, &result);
- printf(" (%sRect) %-20S: (%d, %d, %d, %d)\n", originStr, prop.propName, result.left, result.top, result.right, result.bottom);
- }
- break;
- case TMT_FONT:
- {
- LOGFONT result;
- memset(&result, 0, sizeof(result));
- GetThemeFont(themeData.handle(), 0, themeData.partId, themeData.stateId, prop.propValue, &result);
- printf(" (%sFont) %-20S: %S height(%d) width(%d) weight(%d)\n", originStr, prop.propName,
- result.lfFaceName, result.lfHeight, result.lfWidth, result.lfWeight);
- }
- break;
- case TMT_INTLIST:
- {
- INTLIST result;
- memset(&result, 0, sizeof(result));
- GetThemeIntList(themeData.handle(), themeData.partId, themeData.stateId, prop.propValue, &result);
- printf(" (%sInt list)%-20S: { ", originStr, prop.propName);
- for (int i = 0; i < result.iValueCount; ++i)
- printf("%d ", result.iValues[i]);
- printf("}\n");
- }
- break;
- default:
- printf(" %s%S : Unknown property type (%d)!\n", originStr, prop.propName, prop.propType);
- }
-}
-
-/*! \internal
- Dump all valid properties for a part.
- If it's the first time this function is called, then the name,
- enum value and documentation of all properties are shown, as
- well as all global properties.
-*/
-void QWindowsXPStylePrivate::showProperties(XPThemeData &themeData)
-{
- if (!all_props.count()) {
- const TMSCHEMAINFO *infoTable = GetSchemaInfo();
- for (int i = 0; i < infoTable->iPropCount; ++i) {
- int propType = infoTable->pPropTable[i].bPrimVal;
- int propValue = infoTable->pPropTable[i].sEnumVal;
- LPCWSTR propName = infoTable->pPropTable[i].pszName;
-
- switch(propType) {
- case TMT_ENUMDEF:
- case TMT_ENUMVAL:
- continue;
- default:
- if (propType != propValue) {
- PropPair prop;
- prop.propValue = propValue;
- prop.propName = propName;
- prop.propType = propType;
- all_props.append(prop);
- }
- }
- }
- std::sort(all_props.begin(), all_props.end());
-
- {// List all properties
- printf("part properties count = %d:\n", all_props.count());
- printf(" Enum Property Name Description\n");
- printf("-----------------------------------------------------------\n");
- wchar_t themeName[256];
- pGetCurrentThemeName(themeName, 256, 0, 0, 0, 0);
- for (int j = 0; j < all_props.count(); ++j) {
- PropPair prop = all_props.at(j);
- wchar_t buf[500];
- GetThemeDocumentationProperty(themeName, prop.propName, buf, 500);
- printf("%3d: (%4d) %-20S %S\n", j, prop.propValue, prop.propName, buf);
- }
- }
-
- {// Show Global values
- printf("Global Properties:\n");
- for (int j = 0; j < all_props.count(); ++j) {
- PropPair prop = all_props.at(j);
- PROPERTYORIGIN origin = PO_NOTFOUND;
- GetThemePropertyOrigin(themeData.handle(), themeData.partId, themeData.stateId, prop.propValue, &origin);
- if (origin == PO_GLOBAL) {
- showProperty(themeData, prop);
- }
- }
- }
- }
-
- for (int j = 0; j < all_props.count(); ++j) {
- PropPair prop = all_props.at(j);
- PROPERTYORIGIN origin = PO_NOTFOUND;
- GetThemePropertyOrigin(themeData.handle(), themeData.partId, themeData.stateId, prop.propValue, &origin);
- if (origin != PO_NOTFOUND) {
- showProperty(themeData, prop);
- }
- }
-}
-#endif
-// Debugging code -----------------------------------------------------------------------[ END ]---
-
-
-QT_END_NAMESPACE
diff --git a/src/plugins/styles/windowsvista/qwindowsxpstyle_p.h b/src/plugins/styles/windowsvista/qwindowsxpstyle_p.h
deleted file mode 100644
index 6fa7105ea6..0000000000
--- a/src/plugins/styles/windowsvista/qwindowsxpstyle_p.h
+++ /dev/null
@@ -1,69 +0,0 @@
-// Copyright (C) 2016 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
-
-#ifndef QWINDOWSXPSTYLE_P_H
-#define QWINDOWSXPSTYLE_P_H
-
-//
-// W A R N I N G
-// -------------
-//
-// This file is not part of the Qt API. It exists purely as an
-// implementation detail. This header file may change from version to
-// version without notice, or even be removed.
-//
-// We mean it.
-//
-
-#include <QtWidgets/private/qtwidgetsglobal_p.h>
-#include <QtWidgets/private/qwindowsstyle_p.h>
-
-QT_BEGIN_NAMESPACE
-
-class QWindowsXPStylePrivate;
-class QWindowsXPStyle : public QWindowsStyle
-{
- Q_OBJECT
-public:
- ~QWindowsXPStyle() override;
-
- void polish(QWidget*) override;
- void polish(QPalette&) override;
- void unpolish(QWidget*) override;
-
- void drawPrimitive(PrimitiveElement pe, const QStyleOption *option, QPainter *p,
- const QWidget *widget = nullptr) const override;
- void drawControl(ControlElement element, const QStyleOption *option, QPainter *p,
- const QWidget *wwidget = nullptr) const override;
- QRect subElementRect(SubElement r, const QStyleOption *option,
- const QWidget *widget = nullptr) const override;
- QRect subControlRect(ComplexControl cc, const QStyleOptionComplex *option, SubControl sc,
- const QWidget *widget = nullptr) const override;
- void drawComplexControl(ComplexControl cc, const QStyleOptionComplex *option, QPainter *p,
- const QWidget *widget = nullptr) const override;
- QSize sizeFromContents(ContentsType ct, const QStyleOption *option, const QSize &contentsSize,
- const QWidget *widget = nullptr) const override;
- int pixelMetric(PixelMetric pm, const QStyleOption *option = nullptr,
- const QWidget *widget = nullptr) const override;
- int styleHint(StyleHint hint, const QStyleOption *option = nullptr,
- const QWidget *widget = nullptr,
- QStyleHintReturn *returnData = nullptr) const override;
-
- QPalette standardPalette() const override;
- QPixmap standardPixmap(StandardPixmap standardIcon, const QStyleOption *option,
- const QWidget *widget = nullptr) const override;
- QIcon standardIcon(StandardPixmap standardIcon, const QStyleOption *option = nullptr,
- const QWidget *widget = nullptr) const override;
-
-protected:
- QWindowsXPStyle(QWindowsXPStylePrivate &dd);
-
-private:
- Q_DISABLE_COPY_MOVE(QWindowsXPStyle)
- Q_DECLARE_PRIVATE(QWindowsXPStyle)
- friend class QStyleFactory;
-};
-
-QT_END_NAMESPACE
-
-#endif // QWINDOWSXPSTYLE_P_H
diff --git a/src/plugins/styles/windowsvista/qwindowsxpstyle_p_p.h b/src/plugins/styles/windowsvista/qwindowsxpstyle_p_p.h
deleted file mode 100644
index fbbd493f54..0000000000
--- a/src/plugins/styles/windowsvista/qwindowsxpstyle_p_p.h
+++ /dev/null
@@ -1,309 +0,0 @@
-// Copyright (C) 2016 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
-
-#ifndef QWINDOWSXPSTYLE_P_P_H
-#define QWINDOWSXPSTYLE_P_P_H
-
-//
-// W A R N I N G
-// -------------
-//
-// This file is not part of the Qt API. It exists for the convenience
-// of qapplication_*.cpp, qwidget*.cpp and qfiledialog.cpp. This header
-// file may change from version to version without notice, or even be removed.
-//
-// We mean it.
-//
-
-#include <QtWidgets/private/qtwidgetsglobal_p.h>
-#include "qwindowsxpstyle_p.h"
-#include <QtWidgets/private/qwindowsstyle_p_p.h>
-#include <qmap.h>
-#include <qt_windows.h>
-
-#include <uxtheme.h>
-#include <vssym32.h>
-
-#include <limits.h>
-
-QT_BEGIN_NAMESPACE
-
-class QDebug;
-
-// TMT_TEXTSHADOWCOLOR is wrongly defined in mingw
-#if TMT_TEXTSHADOWCOLOR != 3818
-#undef TMT_TEXTSHADOWCOLOR
-#define TMT_TEXTSHADOWCOLOR 3818
-#endif
-#ifndef TST_NONE
-# define TST_NONE 0
-#endif
-
-// These defines are missing from the tmschema, but still exist as
-// states for their parts
-#ifndef MINBS_INACTIVE
-#define MINBS_INACTIVE 5
-#endif
-#ifndef MAXBS_INACTIVE
-#define MAXBS_INACTIVE 5
-#endif
-#ifndef RBS_INACTIVE
-#define RBS_INACTIVE 5
-#endif
-#ifndef HBS_INACTIVE
-#define HBS_INACTIVE 5
-#endif
-#ifndef CBS_INACTIVE
-#define CBS_INACTIVE 5
-#endif
-
-// Uncomment define below to build debug assisting code, and output
-// #define DEBUG_XP_STYLE
-
-// Declarations -----------------------------------------------------------------------------------
-class XPThemeData
-{
-public:
- explicit XPThemeData(const QWidget *w = nullptr, QPainter *p = nullptr, int themeIn = -1,
- int part = 0, int state = 0, const QRect &r = QRect())
- : widget(w), painter(p), theme(themeIn), partId(part), stateId(state),
- mirrorHorizontally(false), mirrorVertically(false), noBorder(false),
- noContent(false), rect(r)
- {}
-
- HRGN mask(QWidget *widget);
- HTHEME handle();
-
- static RECT toRECT(const QRect &qr);
- bool isValid();
-
- QSizeF size();
- QMarginsF margins(const QRect &rect, int propId = TMT_CONTENTMARGINS);
- QMarginsF margins(int propId = TMT_CONTENTMARGINS);
-
- static QSizeF themeSize(const QWidget *w = nullptr, QPainter *p = nullptr, int themeIn = -1, int part = 0, int state = 0);
- static QMarginsF themeMargins(const QRect &rect, const QWidget *w = nullptr, QPainter *p = nullptr, int themeIn = -1,
- int part = 0, int state = 0, int propId = TMT_CONTENTMARGINS);
- static QMarginsF themeMargins(const QWidget *w = nullptr, QPainter *p = nullptr, int themeIn = -1,
- int part = 0, int state = 0, int propId = TMT_CONTENTMARGINS);
-
- const QWidget *widget;
- QPainter *painter;
-
- int theme;
- HTHEME htheme = nullptr;
- int partId;
- int stateId;
-
- uint mirrorHorizontally : 1;
- uint mirrorVertically : 1;
- uint noBorder : 1;
- uint noContent : 1;
- uint rotate = 0;
- QRect rect;
-};
-
-struct ThemeMapKey {
- int theme = 0;
- int partId = -1;
- int stateId = -1;
- bool noBorder = false;
- bool noContent = false;
-
- ThemeMapKey() = default;
- ThemeMapKey(const XPThemeData &data)
- : theme(data.theme), partId(data.partId), stateId(data.stateId),
- noBorder(data.noBorder), noContent(data.noContent) {}
-
-};
-
-inline size_t qHash(const ThemeMapKey &key)
-{ return key.theme ^ key.partId ^ key.stateId; }
-
-inline bool operator==(const ThemeMapKey &k1, const ThemeMapKey &k2)
-{
- return k1.theme == k2.theme
- && k1.partId == k2.partId
- && k1.stateId == k2.stateId;
-}
-
-enum AlphaChannelType {
- UnknownAlpha = -1, // Alpha of part & state not yet known
- NoAlpha, // Totally opaque, no need to touch alpha (RGB)
- MaskAlpha, // Alpha channel must be fixed (ARGB)
- RealAlpha // Proper alpha values from Windows (ARGB_Premultiplied)
-};
-
-struct ThemeMapData {
- AlphaChannelType alphaType = UnknownAlpha; // Which type of alpha on part & state
-
- bool dataValid : 1; // Only used to detect if hash value is ok
- bool partIsTransparent : 1;
- bool hasAlphaChannel : 1; // True = part & state has real Alpha
- bool wasAlphaSwapped : 1; // True = alpha channel needs to be swapped
- bool hadInvalidAlpha : 1; // True = alpha channel contained invalid alpha values
-
- ThemeMapData() : dataValid(false), partIsTransparent(false),
- hasAlphaChannel(false), wasAlphaSwapped(false), hadInvalidAlpha(false) {}
-};
-
-#ifndef QT_NO_DEBUG_STREAM
-QDebug operator<<(QDebug d, const XPThemeData &t);
-QDebug operator<<(QDebug d, const ThemeMapKey &k);
-QDebug operator<<(QDebug d, const ThemeMapData &td);
-#endif
-
-class QWindowsXPStylePrivate : public QWindowsStylePrivate
-{
- Q_DECLARE_PUBLIC(QWindowsXPStyle)
-public:
- enum Theme {
- ButtonTheme,
- ComboboxTheme,
- EditTheme,
- HeaderTheme,
- ListViewTheme,
- MenuTheme,
- ProgressTheme,
- RebarTheme,
- ScrollBarTheme,
- SpinTheme,
- TabTheme,
- TaskDialogTheme,
- ToolBarTheme,
- ToolTipTheme,
- TrackBarTheme,
- XpTreeViewTheme, // '+'/'-' shape treeview indicators (XP)
- WindowTheme,
- StatusTheme,
- VistaTreeViewTheme, // arrow shape treeview indicators (Vista) obtained from "explorer" theme.
- NThemes
- };
-
- QWindowsXPStylePrivate()
- { init(); }
-
- ~QWindowsXPStylePrivate()
- { cleanup(); }
-
- static int pixelMetricFromSystemDp(QStyle::PixelMetric pm, const QStyleOption *option = nullptr, const QWidget *widget = nullptr);
- static int fixedPixelMetric(QStyle::PixelMetric pm, const QStyleOption *option = nullptr, const QWidget *widget = nullptr);
-
- static HWND winId(const QWidget *widget);
-
- void init(bool force = false);
- void cleanup(bool force = false);
- void cleanupHandleMap();
-
- HBITMAP buffer(int w = 0, int h = 0);
- HDC bufferHDC()
- { return bufferDC;}
-
- static bool useXP(bool update = false);
- static QRect scrollBarGripperBounds(QStyle::State flags, const QWidget *widget, XPThemeData *theme);
-
- bool isTransparent(XPThemeData &themeData);
- QRegion region(XPThemeData &themeData);
-
- bool drawBackground(XPThemeData &themeData, qreal correctionFactor = 1);
- bool drawBackgroundThruNativeBuffer(XPThemeData &themeData, qreal aditionalDevicePixelRatio, qreal correctionFactor);
- bool drawBackgroundDirectly(HDC dc, XPThemeData &themeData, qreal aditionalDevicePixelRatio);
-
- bool hasAlphaChannel(const QRect &rect);
- bool fixAlphaChannel(const QRect &rect);
- bool swapAlphaChannel(const QRect &rect, bool allPixels = false);
-
- QRgb groupBoxTextColor = 0;
- QRgb groupBoxTextColorDisabled = 0;
- QRgb sliderTickColor = 0;
- bool hasInitColors = false;
-
- static HTHEME createTheme(int theme, HWND hwnd);
- static QString themeName(int theme);
- static inline bool hasTheme(int theme) { return theme >= 0 && theme < NThemes && m_themes[theme]; }
- static bool isItemViewDelegateLineEdit(const QWidget *widget);
- static bool isLineEditBaseColorSet(const QStyleOption *option, const QWidget *widget);
-
- QIcon dockFloat, dockClose;
-
-private:
-#ifdef DEBUG_XP_STYLE
- void dumpNativeDIB(int w, int h);
- void showProperties(XPThemeData &themeData);
-#endif
-
- static bool initVistaTreeViewTheming();
- static void cleanupVistaTreeViewTheming();
-
- static QBasicAtomicInt ref;
- static bool use_xp;
-
- QHash<ThemeMapKey, ThemeMapData> alphaCache;
- HDC bufferDC = nullptr;
- HBITMAP bufferBitmap = nullptr;
- HBITMAP nullBitmap = nullptr;
- uchar *bufferPixels = nullptr;
- int bufferW = 0;
- int bufferH = 0;
-
- static HWND m_vistaTreeViewHelper;
- static HTHEME m_themes[NThemes];
-};
-
-inline QSizeF XPThemeData::size()
-{
- QSizeF result(0, 0);
- if (isValid()) {
- SIZE size;
- if (SUCCEEDED(GetThemePartSize(handle(), nullptr, partId, stateId, nullptr, TS_TRUE, &size)))
- result = QSize(size.cx, size.cy);
- }
- return result;
-}
-
-inline QMarginsF XPThemeData::margins(const QRect &qRect, int propId)
-{
- QMarginsF result(0, 0, 0 ,0);
- if (isValid()) {
- MARGINS margins;
- RECT rect = XPThemeData::toRECT(qRect);
- if (SUCCEEDED(GetThemeMargins(handle(), nullptr, partId, stateId, propId, &rect, &margins)))
- result = QMargins(margins.cxLeftWidth, margins.cyTopHeight, margins.cxRightWidth, margins.cyBottomHeight);
- }
- return result;
-}
-
-inline QMarginsF XPThemeData::margins(int propId)
-{
- QMarginsF result(0, 0, 0 ,0);
- if (isValid()) {
- MARGINS margins;
- if (SUCCEEDED(GetThemeMargins(handle(), nullptr, partId, stateId, propId, nullptr, &margins)))
- result = QMargins(margins.cxLeftWidth, margins.cyTopHeight, margins.cxRightWidth, margins.cyBottomHeight);
- }
- return result;
-}
-
-inline QSizeF XPThemeData::themeSize(const QWidget *w, QPainter *p, int themeIn, int part, int state)
-{
- XPThemeData theme(w, p, themeIn, part, state);
- return theme.size();
-}
-
-inline QMarginsF XPThemeData::themeMargins(const QRect &rect, const QWidget *w, QPainter *p, int themeIn,
- int part, int state, int propId)
-{
- XPThemeData theme(w, p, themeIn, part, state);
- return theme.margins(rect, propId);
-}
-
-inline QMarginsF XPThemeData::themeMargins(const QWidget *w, QPainter *p, int themeIn,
- int part, int state, int propId)
-{
- XPThemeData theme(w, p, themeIn, part, state);
- return theme.margins(propId);
-}
-
-QT_END_NAMESPACE
-
-#endif //QWINDOWSXPSTYLE_P_P_H
diff --git a/src/plugins/styles/windowsvista/windowsvistastyle.json b/src/plugins/styles/windowsvista/windowsvistastyle.json
deleted file mode 100644
index 771aa0c600..0000000000
--- a/src/plugins/styles/windowsvista/windowsvistastyle.json
+++ /dev/null
@@ -1,3 +0,0 @@
-{
- "Keys": [ "windowsvista" ]
-}
diff --git a/src/plugins/tls/CMakeLists.txt b/src/plugins/tls/CMakeLists.txt
index f0d22e66a7..91b9267123 100644
--- a/src/plugins/tls/CMakeLists.txt
+++ b/src/plugins/tls/CMakeLists.txt
@@ -1,5 +1,5 @@
# Copyright (C) 2022 The Qt Company Ltd.
-# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+# SPDX-License-Identifier: BSD-3-Clause
if(QT_FEATURE_securetransport)
add_subdirectory(securetransport)
diff --git a/src/plugins/tls/certonly/CMakeLists.txt b/src/plugins/tls/certonly/CMakeLists.txt
index 657104e7c3..495f408144 100644
--- a/src/plugins/tls/certonly/CMakeLists.txt
+++ b/src/plugins/tls/certonly/CMakeLists.txt
@@ -1,5 +1,5 @@
# Copyright (C) 2022 The Qt Company Ltd.
-# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+# SPDX-License-Identifier: BSD-3-Clause
qt_internal_add_plugin(QTlsBackendCertOnlyPlugin
OUTPUT_NAME qcertonlybackend
diff --git a/src/plugins/tls/openssl/CMakeLists.txt b/src/plugins/tls/openssl/CMakeLists.txt
index 4680fb415e..0e0a7a1552 100644
--- a/src/plugins/tls/openssl/CMakeLists.txt
+++ b/src/plugins/tls/openssl/CMakeLists.txt
@@ -1,5 +1,5 @@
# Copyright (C) 2022 The Qt Company Ltd.
-# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+# SPDX-License-Identifier: BSD-3-Clause
qt_internal_add_plugin(QTlsBackendOpenSSLPlugin
OUTPUT_NAME qopensslbackend
@@ -24,6 +24,10 @@ qt_internal_add_plugin(QTlsBackendOpenSSLPlugin
OPENSSL_API_COMPAT=0x10100000L
)
+if (WIN32) # Windows header issues
+ set_target_properties(QTlsBackendOpenSSLPlugin PROPERTIES UNITY_BUILD OFF)
+endif()
+
qt_internal_extend_target(QTlsBackendOpenSSLPlugin CONDITION QT_FEATURE_dtls
SOURCES
qdtls_openssl.cpp qdtls_openssl_p.h
diff --git a/src/plugins/tls/openssl/qdtls_openssl.cpp b/src/plugins/tls/openssl/qdtls_openssl.cpp
index fe8cbf23e0..fc07a29ec8 100644
--- a/src/plugins/tls/openssl/qdtls_openssl.cpp
+++ b/src/plugins/tls/openssl/qdtls_openssl.cpp
@@ -1,11 +1,7 @@
// Copyright (C) 2018 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
-#ifndef NOMINMAX
-#define NOMINMAX
-#endif // NOMINMAX
-
-#include <QtNetwork/private/qnativesocketengine_p.h>
+#include <QtNetwork/private/qnativesocketengine_p_p.h>
#include "qsslsocket_openssl_symbols_p.h"
#include "qdtls_openssl_p.h"
@@ -182,7 +178,7 @@ extern "C" int q_generate_cookie_callback(SSL *ssl, unsigned char *dst,
QMessageAuthenticationCode hmac(dtls->hashAlgorithm, dtls->secret);
hmac.addData(peerData);
- const QByteArray cookie = hmac.result();
+ const QByteArrayView cookie = hmac.resultView();
Q_ASSERT(cookie.size() >= 0);
// DTLS1_COOKIE_LENGTH is erroneously 256 bytes long, must be 255 - RFC 6347, 4.2.1.
*cookieLength = qMin(DTLS1_COOKIE_LENGTH - 1, cookie.size());
@@ -205,7 +201,7 @@ extern "C" int q_verify_cookie_callback(SSL *ssl, const unsigned char *cookie,
return 0;
return newCookieLength == cookieLength
- && !std::memcmp(cookie, newCookie, cookieLength);
+ && !q_CRYPTO_memcmp(cookie, newCookie, size_t(cookieLength));
}
extern "C" int q_X509DtlsCallback(int ok, X509_STORE_CTX *ctx)
@@ -1252,12 +1248,12 @@ unsigned QDtlsPrivateOpenSSL::pskClientCallback(const char *hint, char *identity
return 0;
// Copy data back into OpenSSL
- const int identityLength = qMin(pskAuthenticator.identity().length(),
+ const int identityLength = qMin(pskAuthenticator.identity().size(),
pskAuthenticator.maximumIdentityLength());
std::memcpy(identity, pskAuthenticator.identity().constData(), identityLength);
identity[identityLength] = 0;
- const int pskLength = qMin(pskAuthenticator.preSharedKey().length(),
+ const int pskLength = qMin(pskAuthenticator.preSharedKey().size(),
pskAuthenticator.maximumPreSharedKeyLength());
std::memcpy(psk, pskAuthenticator.preSharedKey().constData(), pskLength);
@@ -1283,7 +1279,7 @@ unsigned QDtlsPrivateOpenSSL::pskServerCallback(const char *identity, unsigned c
return 0;
// Copy data back into OpenSSL
- const int pskLength = qMin(pskAuthenticator.preSharedKey().length(),
+ const int pskLength = qMin(pskAuthenticator.preSharedKey().size(),
pskAuthenticator.maximumPreSharedKeyLength());
std::memcpy(psk, pskAuthenticator.preSharedKey().constData(), pskLength);
@@ -1328,7 +1324,7 @@ bool QDtlsPrivateOpenSSL::verifyPeer()
// Translate errors from the error list into QSslErrors
using CertClass = QTlsPrivate::X509CertificateOpenSSL;
errors.reserve(errors.size() + opensslErrors.size());
- for (const auto &error : qAsConst(opensslErrors)) {
+ for (const auto &error : std::as_const(opensslErrors)) {
const auto value = peerCertificateChain.value(error.depth);
errors << CertClass::openSSLErrorToQSslError(error.code, value);
}
diff --git a/src/plugins/tls/openssl/qsslcontext_openssl.cpp b/src/plugins/tls/openssl/qsslcontext_openssl.cpp
index 33f0d41a09..75c192bd01 100644
--- a/src/plugins/tls/openssl/qsslcontext_openssl.cpp
+++ b/src/plugins/tls/openssl/qsslcontext_openssl.cpp
@@ -218,7 +218,7 @@ SSL* QSslContext::createSsl()
QList<QByteArray> protocols = sslConfiguration.d.constData()->nextAllowedProtocols;
if (!protocols.isEmpty()) {
m_supportedNPNVersions.clear();
- for (int a = 0; a < protocols.count(); ++a) {
+ for (int a = 0; a < protocols.size(); ++a) {
if (protocols.at(a).size() > 255) {
qCWarning(lcTlsBackend) << "TLS NPN extension" << protocols.at(a)
<< "is too long and will be ignored.";
@@ -230,7 +230,7 @@ SSL* QSslContext::createSsl()
}
if (m_supportedNPNVersions.size()) {
m_npnContext.data = reinterpret_cast<unsigned char *>(m_supportedNPNVersions.data());
- m_npnContext.len = m_supportedNPNVersions.length();
+ m_npnContext.len = m_supportedNPNVersions.size();
m_npnContext.status = QSslConfiguration::NextProtocolNegotiationNone;
// Callback's type has a parameter 'const unsigned char ** out'
// since it was introduced in 1.0.2. Internally, OpenSSL's own code
@@ -632,7 +632,7 @@ QT_WARNING_POP
// If we have any intermediate certificates then we need to add them to our chain
bool first = true;
- for (const QSslCertificate &cert : qAsConst(configuration.d->localCertificateChain)) {
+ for (const QSslCertificate &cert : std::as_const(configuration.d->localCertificateChain)) {
if (first) {
first = false;
continue;
@@ -697,12 +697,14 @@ QT_WARNING_POP
return;
}
- if (!dhparams.isEmpty()) {
+ if (dhparams.isEmpty()) {
+ q_SSL_CTX_set_dh_auto(sslContext->ctx, 1);
+ } else {
#ifndef OPENSSL_NO_DEPRECATED_3_0
const QByteArray &params = dhparams.d->derData;
const char *ptr = params.constData();
DH *dh = q_d2i_DHparams(nullptr, reinterpret_cast<const unsigned char **>(&ptr),
- params.length());
+ params.size());
if (dh == nullptr)
qFatal("q_d2i_DHparams failed to convert QSslDiffieHellmanParameters to DER form");
q_SSL_CTX_set_tmp_dh(sslContext->ctx, dh);
diff --git a/src/plugins/tls/openssl/qssldiffiehellmanparameters_openssl.cpp b/src/plugins/tls/openssl/qssldiffiehellmanparameters_openssl.cpp
index 81cbc6a12d..16e31e605f 100644
--- a/src/plugins/tls/openssl/qssldiffiehellmanparameters_openssl.cpp
+++ b/src/plugins/tls/openssl/qssldiffiehellmanparameters_openssl.cpp
@@ -137,8 +137,9 @@ int QTlsBackendOpenSSL::dhParametersFromPem(const QByteArray &pem, QByteArray *d
if (isSafeDH(dh)) {
char *buf = nullptr;
const int len = q_i2d_DHparams(dh, reinterpret_cast<unsigned char **>(&buf));
+ const auto freeBuf = qScopeGuard([&] { q_OPENSSL_free(buf); });
if (len > 0)
- *data = QByteArray(buf, len);
+ data->assign({buf, len});
else
return DHParams::InvalidInputDataError;
} else {
diff --git a/src/plugins/tls/openssl/qsslsocket_openssl_symbols.cpp b/src/plugins/tls/openssl/qsslsocket_openssl_symbols.cpp
index 899a6c4819..4aa9ca6fb1 100644
--- a/src/plugins/tls/openssl/qsslsocket_openssl_symbols.cpp
+++ b/src/plugins/tls/openssl/qsslsocket_openssl_symbols.cpp
@@ -28,7 +28,6 @@
#elif QT_CONFIG(library)
# include <QtCore/qlibrary.h>
#endif
-#include <QtCore/qmutex.h>
#include <QtCore/qdatetime.h>
#if defined(Q_OS_UNIX)
#include <QtCore/qdir.h>
@@ -156,10 +155,10 @@ DEFINEFUNC3(int, X509_STORE_set_ex_data, X509_STORE *a, a, int idx, idx, void *d
DEFINEFUNC2(void *, X509_STORE_get_ex_data, X509_STORE *r, r, int idx, idx, return nullptr, return)
DEFINEFUNC(STACK_OF(X509) *, X509_STORE_CTX_get0_chain, X509_STORE_CTX *a, a, return nullptr, return)
DEFINEFUNC3(void, CRYPTO_free, void *str, str, const char *file, file, int line, line, return, DUMMYARG)
+DEFINEFUNC3(int, CRYPTO_memcmp, const void * in_a, in_a, const void * in_b, in_b, size_t len, len, return 1, return);
DEFINEFUNC(long, OpenSSL_version_num, void, DUMMYARG, return 0, return)
DEFINEFUNC(const char *, OpenSSL_version, int a, a, return nullptr, return)
DEFINEFUNC(unsigned long, SSL_SESSION_get_ticket_lifetime_hint, const SSL_SESSION *session, session, return 0, return)
-DEFINEFUNC4(void, DH_get0_pqg, const DH *dh, dh, const BIGNUM **p, p, const BIGNUM **q, q, const BIGNUM **g, g, return, DUMMYARG)
#if QT_CONFIG(dtls)
DEFINEFUNC2(int, DTLSv1_listen, SSL *s, s, BIO_ADDR *c, c, return -1, return)
@@ -263,7 +262,6 @@ DEFINEFUNC4(int, OBJ_obj2txt, char *a, a, int b, b, ASN1_OBJECT *c, c, int d, d,
DEFINEFUNC(int, OBJ_obj2nid, const ASN1_OBJECT *a, a, return NID_undef, return)
DEFINEFUNC4(EVP_PKEY *, PEM_read_bio_PrivateKey, BIO *a, a, EVP_PKEY **b, b, pem_password_cb *c, c, void *d, d, return nullptr, return)
-DEFINEFUNC4(DH *, PEM_read_bio_DHparams, BIO *a, a, DH **b, b, pem_password_cb *c, c, void *d, d, return nullptr, return)
DEFINEFUNC7(int, PEM_write_bio_PrivateKey, BIO *a, a, EVP_PKEY *b, b, const EVP_CIPHER *c, c, unsigned char *d, d, int e, e, pem_password_cb *f, f, void *g, g, return 0, return)
DEFINEFUNC7(int, PEM_write_bio_PrivateKey_traditional, BIO *a, a, EVP_PKEY *b, b, const EVP_CIPHER *c, c, unsigned char *d, d, int e, e, pem_password_cb *f, f, void *g, g, return 0, return)
DEFINEFUNC4(EVP_PKEY *, PEM_read_bio_PUBKEY, BIO *a, a, EVP_PKEY **b, b, pem_password_cb *c, c, void *d, d, return nullptr, return)
@@ -429,13 +427,21 @@ DEFINEFUNC2(void *, BIO_get_ex_data, BIO *b, b, int idx, idx, return nullptr, re
DEFINEFUNC3(int, BIO_set_ex_data, BIO *b, b, int idx, idx, void *data, data, return -1, return)
DEFINEFUNC3(void *, CRYPTO_malloc, size_t num, num, const char *file, file, int line, line, return nullptr, return)
+
+#ifndef OPENSSL_NO_DEPRECATED_3_0
DEFINEFUNC(DH *, DH_new, DUMMYARG, DUMMYARG, return nullptr, return)
DEFINEFUNC(void, DH_free, DH *dh, dh, return, DUMMYARG)
+DEFINEFUNC2(int, DH_check, DH *dh, dh, int *codes, codes, return 0, return)
+DEFINEFUNC4(void, DH_get0_pqg, const DH *dh, dh, const BIGNUM **p, p, const BIGNUM **q, q, const BIGNUM **g, g, return, DUMMYARG)
+
DEFINEFUNC3(DH *, d2i_DHparams, DH**a, a, const unsigned char **pp, pp, long length, length, return nullptr, return)
DEFINEFUNC2(int, i2d_DHparams, DH *a, a, unsigned char **p, p, return -1, return)
-DEFINEFUNC2(int, DH_check, DH *dh, dh, int *codes, codes, return 0, return)
+
+DEFINEFUNC4(DH *, PEM_read_bio_DHparams, BIO *a, a, DH **b, b, pem_password_cb *c, c, void *d, d, return nullptr, return)
+#endif
DEFINEFUNC3(BIGNUM *, BN_bin2bn, const unsigned char *s, s, int len, len, BIGNUM *ret, ret, return nullptr, return)
+
#ifndef OPENSSL_NO_EC
DEFINEFUNC2(size_t, EC_get_builtin_curves, EC_builtin_curve * r, r, size_t nitems, nitems, return 0, return)
DEFINEFUNC(int, EC_curve_nist2nid, const char *name, name, return 0, return)
@@ -551,7 +557,7 @@ struct LibGreaterThan
{
const auto lhsparts = lhs.split(u'.');
const auto rhsparts = rhs.split(u'.');
- Q_ASSERT(lhsparts.count() > 1 && rhsparts.count() > 1);
+ Q_ASSERT(lhsparts.size() > 1 && rhsparts.size() > 1);
// note: checking rhs < lhs, the same as lhs > rhs
return std::lexicographical_compare(rhsparts.begin() + 1, rhsparts.end(),
@@ -622,7 +628,7 @@ static QStringList findAllLibs(QLatin1StringView filter)
QStringList entryList = dir.entryList(filters, QDir::Files);
std::sort(entryList.begin(), entryList.end(), LibGreaterThan());
- for (const QString &entry : qAsConst(entryList))
+ for (const QString &entry : std::as_const(entryList))
found << path + u'/' + entry;
}
@@ -640,6 +646,12 @@ static QStringList findAllLibCrypto()
}
# endif
+#if (OPENSSL_VERSION_NUMBER >> 28) < 3
+#define QT_OPENSSL_VERSION "1_1"
+#elif OPENSSL_VERSION_MAJOR == 3 // Starting with 3.0 this define is available
+#define QT_OPENSSL_VERSION "3"
+#endif // > 3 intentionally left undefined
+
#ifdef Q_OS_WIN
struct LoadedOpenSsl {
@@ -671,12 +683,6 @@ static LoadedOpenSsl loadOpenSsl()
// MSVC and GCC. For 3.0 the version suffix changed again, to just '3'.
// For non-x86 builds, an architecture suffix is also appended.
-#if (OPENSSL_VERSION_NUMBER >> 28) < 3
-#define QT_OPENSSL_VERSION "1_1"
-#elif OPENSSL_VERSION_MAJOR == 3 // Starting with 3.0 this define is available
-#define QT_OPENSSL_VERSION "3"
-#endif // > 3 intentionally left undefined
-
#if defined(Q_PROCESSOR_X86_64)
#define QT_SSL_SUFFIX "-x64"
#elif defined(Q_PROCESSOR_ARM_64)
@@ -693,7 +699,7 @@ static LoadedOpenSsl loadOpenSsl()
#undef QT_SSL_SUFFIX
return result;
}
-#else
+#else // !Q_OS_WIN:
struct LoadedOpenSsl {
std::unique_ptr<QLibrary> ssl, crypto;
@@ -743,10 +749,22 @@ static LoadedOpenSsl loadOpenSsl()
#ifdef Q_OS_OPENBSD
libcrypto->setLoadHints(QLibrary::ExportExternalSymbolsHint);
#endif
-#if defined(SHLIB_VERSION_NUMBER) && !defined(Q_OS_QNX) // on QNX, the libs are always libssl.so and libcrypto.so
+
+#if !defined(Q_OS_QNX) // on QNX, the libs are always libssl.so and libcrypto.so
+
+#if defined(OPENSSL_SHLIB_VERSION)
+ // OpenSSL v.3 does not have SLIB_VERSION_NUMBER but has OPENSSL_SHLIB_VERSION.
+ // The comment about OPENSSL_SHLIB_VERSION in opensslv.h is a bit troublesome:
+ // "This is defined in free form."
+ auto shlibVersion = QString("%1"_L1).arg(OPENSSL_SHLIB_VERSION);
+ libssl->setFileNameAndVersion("ssl"_L1, shlibVersion);
+ libcrypto->setFileNameAndVersion("crypto"_L1, shlibVersion);
+#elif defined(SHLIB_VERSION_NUMBER)
// first attempt: the canonical name is libssl.so.<SHLIB_VERSION_NUMBER>
libssl->setFileNameAndVersion("ssl"_L1, SHLIB_VERSION_NUMBER ""_L1);
libcrypto->setFileNameAndVersion("crypto"_L1, SHLIB_VERSION_NUMBER ""_L1);
+#endif // OPENSSL_SHLIB_VERSION
+
if (libcrypto->load() && libssl->load()) {
// libssl.so.<SHLIB_VERSION_NUMBER> and libcrypto.so.<SHLIB_VERSION_NUMBER> found
return result;
@@ -754,7 +772,7 @@ static LoadedOpenSsl loadOpenSsl()
libssl->unload();
libcrypto->unload();
}
-#endif
+#endif // !defined(Q_OS_QNX)
#ifndef Q_OS_DARWIN
// second attempt: find the development files libssl.so and libcrypto.so
@@ -772,7 +790,7 @@ static LoadedOpenSsl loadOpenSsl()
return suffix;
};
- static QString suffix = QString::fromLatin1(openSSLSuffix("_1_1"));
+ static QString suffix = QString::fromLatin1(openSSLSuffix("_" QT_OPENSSL_VERSION));
libssl->setFileNameAndVersion("ssl"_L1 + suffix, -1);
libcrypto->setFileNameAndVersion("crypto"_L1 + suffix, -1);
@@ -832,412 +850,405 @@ static LoadedOpenSsl loadOpenSsl()
}
#endif
-Q_CONSTINIT static QBasicMutex symbolResolveMutex;
-Q_CONSTINIT static QBasicAtomicInt symbolsResolved = Q_BASIC_ATOMIC_INITIALIZER(false);
-Q_CONSTINIT static bool triedToResolveSymbols = false;
-
bool q_resolveOpenSslSymbols()
{
- if (symbolsResolved.loadAcquire())
- return true;
- QMutexLocker locker(&symbolResolveMutex);
- if (symbolsResolved.loadRelaxed())
- return true;
- if (triedToResolveSymbols)
- return false;
- triedToResolveSymbols = true;
-
- LoadedOpenSsl libs = loadOpenSsl();
- if (!libs.ssl || !libs.crypto) {
- qCWarning(lcTlsBackend, "Failed to load libssl/libcrypto.");
- return false;
- }
+ static bool symbolsResolved = []() {
+ LoadedOpenSsl libs = loadOpenSsl();
+ if (!libs.ssl || !libs.crypto) {
+ qCWarning(lcTlsBackend, "Failed to load libssl/libcrypto.");
+ return false;
+ }
- RESOLVEFUNC(OPENSSL_init_ssl)
- RESOLVEFUNC(OPENSSL_init_crypto)
- RESOLVEFUNC(ASN1_STRING_get0_data)
- RESOLVEFUNC(EVP_CIPHER_CTX_reset)
- RESOLVEFUNC(AUTHORITY_INFO_ACCESS_free)
- RESOLVEFUNC(EVP_PKEY_up_ref)
- RESOLVEFUNC(EVP_PKEY_CTX_new)
- RESOLVEFUNC(EVP_PKEY_param_check)
- RESOLVEFUNC(EVP_PKEY_CTX_free)
- RESOLVEFUNC(OPENSSL_sk_new_null)
- RESOLVEFUNC(OPENSSL_sk_push)
- RESOLVEFUNC(OPENSSL_sk_free)
- RESOLVEFUNC(OPENSSL_sk_num)
- RESOLVEFUNC(OPENSSL_sk_pop_free)
- RESOLVEFUNC(OPENSSL_sk_value)
- RESOLVEFUNC(DH_get0_pqg)
- RESOLVEFUNC(SSL_CTX_set_options)
- RESOLVEFUNC(SSL_set_info_callback)
- RESOLVEFUNC(SSL_alert_type_string)
- RESOLVEFUNC(SSL_alert_desc_string_long)
- RESOLVEFUNC(SSL_CTX_get_security_level)
- RESOLVEFUNC(SSL_CTX_set_security_level)
+ RESOLVEFUNC(OPENSSL_init_ssl)
+ RESOLVEFUNC(OPENSSL_init_crypto)
+ RESOLVEFUNC(ASN1_STRING_get0_data)
+ RESOLVEFUNC(EVP_CIPHER_CTX_reset)
+ RESOLVEFUNC(AUTHORITY_INFO_ACCESS_free)
+ RESOLVEFUNC(EVP_PKEY_up_ref)
+ RESOLVEFUNC(EVP_PKEY_CTX_new)
+ RESOLVEFUNC(EVP_PKEY_param_check)
+ RESOLVEFUNC(EVP_PKEY_CTX_free)
+ RESOLVEFUNC(OPENSSL_sk_new_null)
+ RESOLVEFUNC(OPENSSL_sk_push)
+ RESOLVEFUNC(OPENSSL_sk_free)
+ RESOLVEFUNC(OPENSSL_sk_num)
+ RESOLVEFUNC(OPENSSL_sk_pop_free)
+ RESOLVEFUNC(OPENSSL_sk_value)
+ RESOLVEFUNC(SSL_CTX_set_options)
+ RESOLVEFUNC(SSL_set_info_callback)
+ RESOLVEFUNC(SSL_alert_type_string)
+ RESOLVEFUNC(SSL_alert_desc_string_long)
+ RESOLVEFUNC(SSL_CTX_get_security_level)
+ RESOLVEFUNC(SSL_CTX_set_security_level)
#ifdef TLS1_3_VERSION
- RESOLVEFUNC(SSL_CTX_set_ciphersuites)
- RESOLVEFUNC(SSL_set_psk_use_session_callback)
- RESOLVEFUNC(SSL_CTX_sess_set_new_cb)
- RESOLVEFUNC(SSL_SESSION_is_resumable)
+ RESOLVEFUNC(SSL_CTX_set_ciphersuites)
+ RESOLVEFUNC(SSL_set_psk_use_session_callback)
+ RESOLVEFUNC(SSL_CTX_sess_set_new_cb)
+ RESOLVEFUNC(SSL_SESSION_is_resumable)
#endif // TLS 1.3 or OpenSSL > 1.1.1
- RESOLVEFUNC(SSL_get_client_random)
- RESOLVEFUNC(SSL_SESSION_get_master_key)
- RESOLVEFUNC(SSL_session_reused)
- RESOLVEFUNC(SSL_get_session)
- RESOLVEFUNC(SSL_set_options)
- RESOLVEFUNC(CRYPTO_get_ex_new_index)
- RESOLVEFUNC(TLS_method)
- RESOLVEFUNC(TLS_client_method)
- RESOLVEFUNC(TLS_server_method)
- RESOLVEFUNC(X509_up_ref)
- RESOLVEFUNC(X509_STORE_CTX_get0_chain)
- RESOLVEFUNC(X509_getm_notBefore)
- RESOLVEFUNC(X509_getm_notAfter)
- RESOLVEFUNC(ASN1_item_free)
- RESOLVEFUNC(X509V3_conf_free)
- RESOLVEFUNC(X509_get_version)
- RESOLVEFUNC(X509_get_pubkey)
- RESOLVEFUNC(X509_STORE_set_verify_cb)
- RESOLVEFUNC(X509_STORE_set_ex_data)
- RESOLVEFUNC(X509_STORE_get_ex_data)
- RESOLVEFUNC(CRYPTO_free)
- RESOLVEFUNC(OpenSSL_version_num)
- RESOLVEFUNC(OpenSSL_version)
-
- if (!_q_OpenSSL_version || !_q_OpenSSL_version_num) {
- // Apparently, we were built with OpenSSL 1.1 enabled but are now using
- // a wrong library.
- qCWarning(lcTlsBackend, "Incompatible version of OpenSSL");
- return false;
- }
+ RESOLVEFUNC(SSL_get_client_random)
+ RESOLVEFUNC(SSL_SESSION_get_master_key)
+ RESOLVEFUNC(SSL_session_reused)
+ RESOLVEFUNC(SSL_get_session)
+ RESOLVEFUNC(SSL_set_options)
+ RESOLVEFUNC(CRYPTO_get_ex_new_index)
+ RESOLVEFUNC(TLS_method)
+ RESOLVEFUNC(TLS_client_method)
+ RESOLVEFUNC(TLS_server_method)
+ RESOLVEFUNC(X509_up_ref)
+ RESOLVEFUNC(X509_STORE_CTX_get0_chain)
+ RESOLVEFUNC(X509_getm_notBefore)
+ RESOLVEFUNC(X509_getm_notAfter)
+ RESOLVEFUNC(ASN1_item_free)
+ RESOLVEFUNC(X509V3_conf_free)
+ RESOLVEFUNC(X509_get_version)
+ RESOLVEFUNC(X509_get_pubkey)
+ RESOLVEFUNC(X509_STORE_set_verify_cb)
+ RESOLVEFUNC(X509_STORE_set_ex_data)
+ RESOLVEFUNC(X509_STORE_get_ex_data)
+ RESOLVEFUNC(CRYPTO_free)
+ RESOLVEFUNC(CRYPTO_memcmp)
+ RESOLVEFUNC(OpenSSL_version_num)
+ RESOLVEFUNC(OpenSSL_version)
+
+ if (!_q_OpenSSL_version || !_q_OpenSSL_version_num) {
+ // Apparently, we were built with OpenSSL 1.1 enabled but are now using
+ // a wrong library.
+ qCWarning(lcTlsBackend, "Incompatible version of OpenSSL");
+ return false;
+ }
#if OPENSSL_VERSION_NUMBER >= 0x30000000
- if (q_OpenSSL_version_num() < 0x30000000) {
- qCWarning(lcTlsBackend, "Incompatible version of OpenSSL (built with OpenSSL >= 3.x, runtime version is < 3.x)");
- return false;
- }
+ if (q_OpenSSL_version_num() < 0x30000000) {
+ qCWarning(lcTlsBackend, "Incompatible version of OpenSSL (built with OpenSSL >= 3.x, runtime version is < 3.x)");
+ return false;
+ }
#else
- if (q_OpenSSL_version_num() >= 0x30000000) {
- qCWarning(lcTlsBackend, "Incompatible version of OpenSSL (built with OpenSSL 1.x, runtime version is >= 3.x)");
- return false;
- }
+ if (q_OpenSSL_version_num() >= 0x30000000) {
+ qCWarning(lcTlsBackend, "Incompatible version of OpenSSL (built with OpenSSL 1.x, runtime version is >= 3.x)");
+ return false;
+ }
#endif // OPENSSL_VERSION_NUMBER
- RESOLVEFUNC(SSL_SESSION_get_ticket_lifetime_hint)
+ RESOLVEFUNC(SSL_SESSION_get_ticket_lifetime_hint)
#if QT_CONFIG(dtls)
- RESOLVEFUNC(DTLSv1_listen)
- RESOLVEFUNC(BIO_ADDR_new)
- RESOLVEFUNC(BIO_ADDR_free)
- RESOLVEFUNC(BIO_meth_new)
- RESOLVEFUNC(BIO_meth_free)
- RESOLVEFUNC(BIO_meth_set_write)
- RESOLVEFUNC(BIO_meth_set_read)
- RESOLVEFUNC(BIO_meth_set_puts)
- RESOLVEFUNC(BIO_meth_set_ctrl)
- RESOLVEFUNC(BIO_meth_set_create)
- RESOLVEFUNC(BIO_meth_set_destroy)
+ RESOLVEFUNC(DTLSv1_listen)
+ RESOLVEFUNC(BIO_ADDR_new)
+ RESOLVEFUNC(BIO_ADDR_free)
+ RESOLVEFUNC(BIO_meth_new)
+ RESOLVEFUNC(BIO_meth_free)
+ RESOLVEFUNC(BIO_meth_set_write)
+ RESOLVEFUNC(BIO_meth_set_read)
+ RESOLVEFUNC(BIO_meth_set_puts)
+ RESOLVEFUNC(BIO_meth_set_ctrl)
+ RESOLVEFUNC(BIO_meth_set_create)
+ RESOLVEFUNC(BIO_meth_set_destroy)
#endif // dtls
#if QT_CONFIG(ocsp)
- RESOLVEFUNC(OCSP_SINGLERESP_get0_id)
- RESOLVEFUNC(d2i_OCSP_RESPONSE)
- RESOLVEFUNC(OCSP_RESPONSE_free)
- RESOLVEFUNC(OCSP_response_status)
- RESOLVEFUNC(OCSP_response_get1_basic)
- RESOLVEFUNC(OCSP_BASICRESP_free)
- RESOLVEFUNC(OCSP_basic_verify)
- RESOLVEFUNC(OCSP_resp_count)
- RESOLVEFUNC(OCSP_resp_get0)
- RESOLVEFUNC(OCSP_single_get0_status)
- RESOLVEFUNC(OCSP_check_validity)
- RESOLVEFUNC(OCSP_cert_to_id)
- RESOLVEFUNC(OCSP_id_get0_info)
- RESOLVEFUNC(OCSP_resp_get0_certs)
- RESOLVEFUNC(OCSP_basic_sign)
- RESOLVEFUNC(OCSP_response_create)
- RESOLVEFUNC(i2d_OCSP_RESPONSE)
- RESOLVEFUNC(OCSP_basic_add1_status)
- RESOLVEFUNC(OCSP_BASICRESP_new)
- RESOLVEFUNC(OCSP_CERTID_free)
- RESOLVEFUNC(OCSP_cert_to_id)
- RESOLVEFUNC(OCSP_id_cmp)
+ RESOLVEFUNC(OCSP_SINGLERESP_get0_id)
+ RESOLVEFUNC(d2i_OCSP_RESPONSE)
+ RESOLVEFUNC(OCSP_RESPONSE_free)
+ RESOLVEFUNC(OCSP_response_status)
+ RESOLVEFUNC(OCSP_response_get1_basic)
+ RESOLVEFUNC(OCSP_BASICRESP_free)
+ RESOLVEFUNC(OCSP_basic_verify)
+ RESOLVEFUNC(OCSP_resp_count)
+ RESOLVEFUNC(OCSP_resp_get0)
+ RESOLVEFUNC(OCSP_single_get0_status)
+ RESOLVEFUNC(OCSP_check_validity)
+ RESOLVEFUNC(OCSP_cert_to_id)
+ RESOLVEFUNC(OCSP_id_get0_info)
+ RESOLVEFUNC(OCSP_resp_get0_certs)
+ RESOLVEFUNC(OCSP_basic_sign)
+ RESOLVEFUNC(OCSP_response_create)
+ RESOLVEFUNC(i2d_OCSP_RESPONSE)
+ RESOLVEFUNC(OCSP_basic_add1_status)
+ RESOLVEFUNC(OCSP_BASICRESP_new)
+ RESOLVEFUNC(OCSP_CERTID_free)
+ RESOLVEFUNC(OCSP_cert_to_id)
+ RESOLVEFUNC(OCSP_id_cmp)
#endif // ocsp
- RESOLVEFUNC(BIO_set_data)
- RESOLVEFUNC(BIO_get_data)
- RESOLVEFUNC(BIO_set_init)
- RESOLVEFUNC(BIO_get_shutdown)
- RESOLVEFUNC(BIO_set_shutdown)
- RESOLVEFUNC(ASN1_INTEGER_get)
- RESOLVEFUNC(ASN1_INTEGER_cmp)
- RESOLVEFUNC(ASN1_STRING_length)
- RESOLVEFUNC(ASN1_STRING_to_UTF8)
- RESOLVEFUNC(ASN1_TIME_to_tm)
- RESOLVEFUNC(BIO_ctrl)
- RESOLVEFUNC(BIO_free)
- RESOLVEFUNC(BIO_new)
- RESOLVEFUNC(BIO_new_mem_buf)
- RESOLVEFUNC(BIO_read)
- RESOLVEFUNC(BIO_s_mem)
- RESOLVEFUNC(BIO_write)
- RESOLVEFUNC(BIO_set_flags)
- RESOLVEFUNC(BIO_clear_flags)
- RESOLVEFUNC(BIO_set_ex_data)
- RESOLVEFUNC(BIO_get_ex_data)
- RESOLVEFUNC(BN_num_bits)
- RESOLVEFUNC(BN_is_word)
- RESOLVEFUNC(BN_mod_word)
- RESOLVEFUNC(ERR_error_string)
- RESOLVEFUNC(ERR_error_string_n)
- RESOLVEFUNC(ERR_get_error)
- RESOLVEFUNC(EVP_CIPHER_CTX_new)
- RESOLVEFUNC(EVP_CIPHER_CTX_free)
- RESOLVEFUNC(EVP_CIPHER_CTX_ctrl)
- RESOLVEFUNC(EVP_CIPHER_CTX_set_key_length)
- RESOLVEFUNC(EVP_CipherInit)
- RESOLVEFUNC(EVP_CipherInit_ex)
- RESOLVEFUNC(EVP_CipherUpdate)
- RESOLVEFUNC(EVP_CipherFinal)
- RESOLVEFUNC(EVP_get_digestbyname)
+ RESOLVEFUNC(BIO_set_data)
+ RESOLVEFUNC(BIO_get_data)
+ RESOLVEFUNC(BIO_set_init)
+ RESOLVEFUNC(BIO_get_shutdown)
+ RESOLVEFUNC(BIO_set_shutdown)
+ RESOLVEFUNC(ASN1_INTEGER_get)
+ RESOLVEFUNC(ASN1_INTEGER_cmp)
+ RESOLVEFUNC(ASN1_STRING_length)
+ RESOLVEFUNC(ASN1_STRING_to_UTF8)
+ RESOLVEFUNC(ASN1_TIME_to_tm)
+ RESOLVEFUNC(BIO_ctrl)
+ RESOLVEFUNC(BIO_free)
+ RESOLVEFUNC(BIO_new)
+ RESOLVEFUNC(BIO_new_mem_buf)
+ RESOLVEFUNC(BIO_read)
+ RESOLVEFUNC(BIO_s_mem)
+ RESOLVEFUNC(BIO_write)
+ RESOLVEFUNC(BIO_set_flags)
+ RESOLVEFUNC(BIO_clear_flags)
+ RESOLVEFUNC(BIO_set_ex_data)
+ RESOLVEFUNC(BIO_get_ex_data)
+ RESOLVEFUNC(BN_num_bits)
+ RESOLVEFUNC(BN_is_word)
+ RESOLVEFUNC(BN_mod_word)
+ RESOLVEFUNC(ERR_error_string)
+ RESOLVEFUNC(ERR_error_string_n)
+ RESOLVEFUNC(ERR_get_error)
+ RESOLVEFUNC(EVP_CIPHER_CTX_new)
+ RESOLVEFUNC(EVP_CIPHER_CTX_free)
+ RESOLVEFUNC(EVP_CIPHER_CTX_ctrl)
+ RESOLVEFUNC(EVP_CIPHER_CTX_set_key_length)
+ RESOLVEFUNC(EVP_CipherInit)
+ RESOLVEFUNC(EVP_CipherInit_ex)
+ RESOLVEFUNC(EVP_CipherUpdate)
+ RESOLVEFUNC(EVP_CipherFinal)
+ RESOLVEFUNC(EVP_get_digestbyname)
#ifndef OPENSSL_NO_DES
- RESOLVEFUNC(EVP_des_cbc)
- RESOLVEFUNC(EVP_des_ede3_cbc)
+ RESOLVEFUNC(EVP_des_cbc)
+ RESOLVEFUNC(EVP_des_ede3_cbc)
#endif
#ifndef OPENSSL_NO_RC2
- RESOLVEFUNC(EVP_rc2_cbc)
+ RESOLVEFUNC(EVP_rc2_cbc)
#endif
#ifndef OPENSSL_NO_AES
- RESOLVEFUNC(EVP_aes_128_cbc)
- RESOLVEFUNC(EVP_aes_192_cbc)
- RESOLVEFUNC(EVP_aes_256_cbc)
+ RESOLVEFUNC(EVP_aes_128_cbc)
+ RESOLVEFUNC(EVP_aes_192_cbc)
+ RESOLVEFUNC(EVP_aes_256_cbc)
#endif
- RESOLVEFUNC(EVP_sha1)
- RESOLVEFUNC(EVP_PKEY_free)
- RESOLVEFUNC(EVP_PKEY_new)
- RESOLVEFUNC(EVP_PKEY_type)
- RESOLVEFUNC(OBJ_nid2sn)
- RESOLVEFUNC(OBJ_nid2ln)
- RESOLVEFUNC(OBJ_sn2nid)
- RESOLVEFUNC(OBJ_ln2nid)
- RESOLVEFUNC(i2t_ASN1_OBJECT)
- RESOLVEFUNC(OBJ_obj2txt)
- RESOLVEFUNC(OBJ_obj2nid)
- RESOLVEFUNC(PEM_read_bio_PrivateKey)
- RESOLVEFUNC(PEM_read_bio_DHparams)
- RESOLVEFUNC(PEM_write_bio_PrivateKey)
- RESOLVEFUNC(PEM_write_bio_PrivateKey_traditional)
- RESOLVEFUNC(PEM_read_bio_PUBKEY)
- RESOLVEFUNC(PEM_write_bio_PUBKEY)
- RESOLVEFUNC(RAND_seed)
- RESOLVEFUNC(RAND_status)
- RESOLVEFUNC(RAND_bytes)
- RESOLVEFUNC(SSL_CIPHER_description)
- RESOLVEFUNC(SSL_CIPHER_get_bits)
- RESOLVEFUNC(SSL_get_rbio)
- RESOLVEFUNC(SSL_CTX_check_private_key)
- RESOLVEFUNC(SSL_CTX_ctrl)
- RESOLVEFUNC(SSL_CTX_free)
- RESOLVEFUNC(SSL_CTX_new)
- RESOLVEFUNC(SSL_CTX_set_cipher_list)
- RESOLVEFUNC(SSL_CTX_callback_ctrl)
- RESOLVEFUNC(SSL_CTX_set_default_verify_paths)
- RESOLVEFUNC(SSL_CTX_set_verify)
- RESOLVEFUNC(SSL_CTX_set_verify_depth)
- RESOLVEFUNC(SSL_CTX_use_certificate)
- RESOLVEFUNC(SSL_CTX_use_certificate_file)
- RESOLVEFUNC(SSL_CTX_use_PrivateKey)
- RESOLVEFUNC(SSL_CTX_use_PrivateKey_file)
- RESOLVEFUNC(SSL_CTX_get_cert_store);
- RESOLVEFUNC(SSL_CONF_CTX_new);
- RESOLVEFUNC(SSL_CONF_CTX_free);
- RESOLVEFUNC(SSL_CONF_CTX_set_ssl_ctx);
- RESOLVEFUNC(SSL_CONF_CTX_set_flags);
- RESOLVEFUNC(SSL_CONF_CTX_finish);
- RESOLVEFUNC(SSL_CONF_cmd);
- RESOLVEFUNC(SSL_accept)
- RESOLVEFUNC(SSL_clear)
- RESOLVEFUNC(SSL_connect)
- RESOLVEFUNC(SSL_free)
- RESOLVEFUNC(SSL_get_ciphers)
- RESOLVEFUNC(SSL_get_current_cipher)
- RESOLVEFUNC(SSL_version)
- RESOLVEFUNC(SSL_get_error)
- RESOLVEFUNC(SSL_get_peer_cert_chain)
+ RESOLVEFUNC(EVP_sha1)
+ RESOLVEFUNC(EVP_PKEY_free)
+ RESOLVEFUNC(EVP_PKEY_new)
+ RESOLVEFUNC(EVP_PKEY_type)
+ RESOLVEFUNC(OBJ_nid2sn)
+ RESOLVEFUNC(OBJ_nid2ln)
+ RESOLVEFUNC(OBJ_sn2nid)
+ RESOLVEFUNC(OBJ_ln2nid)
+ RESOLVEFUNC(i2t_ASN1_OBJECT)
+ RESOLVEFUNC(OBJ_obj2txt)
+ RESOLVEFUNC(OBJ_obj2nid)
+ RESOLVEFUNC(PEM_read_bio_PrivateKey)
+ RESOLVEFUNC(PEM_write_bio_PrivateKey)
+ RESOLVEFUNC(PEM_write_bio_PrivateKey_traditional)
+ RESOLVEFUNC(PEM_read_bio_PUBKEY)
+ RESOLVEFUNC(PEM_write_bio_PUBKEY)
+ RESOLVEFUNC(RAND_seed)
+ RESOLVEFUNC(RAND_status)
+ RESOLVEFUNC(RAND_bytes)
+ RESOLVEFUNC(SSL_CIPHER_description)
+ RESOLVEFUNC(SSL_CIPHER_get_bits)
+ RESOLVEFUNC(SSL_get_rbio)
+ RESOLVEFUNC(SSL_CTX_check_private_key)
+ RESOLVEFUNC(SSL_CTX_ctrl)
+ RESOLVEFUNC(SSL_CTX_free)
+ RESOLVEFUNC(SSL_CTX_new)
+ RESOLVEFUNC(SSL_CTX_set_cipher_list)
+ RESOLVEFUNC(SSL_CTX_callback_ctrl)
+ RESOLVEFUNC(SSL_CTX_set_default_verify_paths)
+ RESOLVEFUNC(SSL_CTX_set_verify)
+ RESOLVEFUNC(SSL_CTX_set_verify_depth)
+ RESOLVEFUNC(SSL_CTX_use_certificate)
+ RESOLVEFUNC(SSL_CTX_use_certificate_file)
+ RESOLVEFUNC(SSL_CTX_use_PrivateKey)
+ RESOLVEFUNC(SSL_CTX_use_PrivateKey_file)
+ RESOLVEFUNC(SSL_CTX_get_cert_store);
+ RESOLVEFUNC(SSL_CONF_CTX_new);
+ RESOLVEFUNC(SSL_CONF_CTX_free);
+ RESOLVEFUNC(SSL_CONF_CTX_set_ssl_ctx);
+ RESOLVEFUNC(SSL_CONF_CTX_set_flags);
+ RESOLVEFUNC(SSL_CONF_CTX_finish);
+ RESOLVEFUNC(SSL_CONF_cmd);
+ RESOLVEFUNC(SSL_accept)
+ RESOLVEFUNC(SSL_clear)
+ RESOLVEFUNC(SSL_connect)
+ RESOLVEFUNC(SSL_free)
+ RESOLVEFUNC(SSL_get_ciphers)
+ RESOLVEFUNC(SSL_get_current_cipher)
+ RESOLVEFUNC(SSL_version)
+ RESOLVEFUNC(SSL_get_error)
+ RESOLVEFUNC(SSL_get_peer_cert_chain)
#if defined(OPENSSL_VERSION_MAJOR) && OPENSSL_VERSION_MAJOR >= 3
- RESOLVEFUNC(SSL_get1_peer_certificate)
- RESOLVEFUNC(EVP_PKEY_get_bits)
- RESOLVEFUNC(EVP_PKEY_get_base_id)
+ RESOLVEFUNC(SSL_get1_peer_certificate)
+ RESOLVEFUNC(EVP_PKEY_get_bits)
+ RESOLVEFUNC(EVP_PKEY_get_base_id)
#else
- RESOLVEFUNC(SSL_get_peer_certificate)
- RESOLVEFUNC(EVP_PKEY_base_id)
+ RESOLVEFUNC(SSL_get_peer_certificate)
+ RESOLVEFUNC(EVP_PKEY_base_id)
#endif // OPENSSL_VERSION_MAJOR >= 3
#ifndef OPENSSL_NO_DEPRECATED_3_0
- RESOLVEFUNC(EVP_PKEY_assign)
- RESOLVEFUNC(EVP_PKEY_cmp)
+ RESOLVEFUNC(DH_new)
+ RESOLVEFUNC(DH_free)
+ RESOLVEFUNC(DH_check)
+ RESOLVEFUNC(DH_get0_pqg)
- RESOLVEFUNC(EVP_PKEY_set1_RSA)
- RESOLVEFUNC(EVP_PKEY_set1_DSA)
- RESOLVEFUNC(EVP_PKEY_set1_DH)
+ RESOLVEFUNC(d2i_DHparams)
+ RESOLVEFUNC(i2d_DHparams)
- RESOLVEFUNC(EVP_PKEY_get1_DSA)
- RESOLVEFUNC(EVP_PKEY_get1_RSA)
- RESOLVEFUNC(EVP_PKEY_get1_DH)
+ RESOLVEFUNC(PEM_read_bio_DHparams)
- RESOLVEFUNC(PEM_read_bio_DSA_PUBKEY)
- RESOLVEFUNC(PEM_read_bio_RSA_PUBKEY)
- RESOLVEFUNC(PEM_read_bio_DSAPrivateKey)
- RESOLVEFUNC(PEM_read_bio_RSAPrivateKey)
+ RESOLVEFUNC(EVP_PKEY_assign)
+ RESOLVEFUNC(EVP_PKEY_cmp)
- RESOLVEFUNC(PEM_write_bio_DSA_PUBKEY)
- RESOLVEFUNC(PEM_write_bio_RSA_PUBKEY)
- RESOLVEFUNC(PEM_write_bio_DSAPrivateKey)
- RESOLVEFUNC(PEM_write_bio_RSAPrivateKey)
- RESOLVEFUNC(SSL_CTX_use_RSAPrivateKey)
+ RESOLVEFUNC(EVP_PKEY_set1_RSA)
+ RESOLVEFUNC(EVP_PKEY_set1_DSA)
+ RESOLVEFUNC(EVP_PKEY_set1_DH)
- RESOLVEFUNC(DSA_new)
- RESOLVEFUNC(DSA_free)
+ RESOLVEFUNC(EVP_PKEY_get1_DSA)
+ RESOLVEFUNC(EVP_PKEY_get1_RSA)
+ RESOLVEFUNC(EVP_PKEY_get1_DH)
- RESOLVEFUNC(RSA_new)
- RESOLVEFUNC(RSA_free)
+ RESOLVEFUNC(PEM_read_bio_DSA_PUBKEY)
+ RESOLVEFUNC(PEM_read_bio_RSA_PUBKEY)
+ RESOLVEFUNC(PEM_read_bio_DSAPrivateKey)
+ RESOLVEFUNC(PEM_read_bio_RSAPrivateKey)
- RESOLVEFUNC(DH_bits)
- RESOLVEFUNC(DSA_bits)
- RESOLVEFUNC(RSA_bits)
+ RESOLVEFUNC(PEM_write_bio_DSA_PUBKEY)
+ RESOLVEFUNC(PEM_write_bio_RSA_PUBKEY)
+ RESOLVEFUNC(PEM_write_bio_DSAPrivateKey)
+ RESOLVEFUNC(PEM_write_bio_RSAPrivateKey)
+ RESOLVEFUNC(SSL_CTX_use_RSAPrivateKey)
+
+ RESOLVEFUNC(DSA_new)
+ RESOLVEFUNC(DSA_free)
+
+ RESOLVEFUNC(RSA_new)
+ RESOLVEFUNC(RSA_free)
+
+ RESOLVEFUNC(DH_bits)
+ RESOLVEFUNC(DSA_bits)
+ RESOLVEFUNC(RSA_bits)
#ifndef OPENSSL_NO_EC
- RESOLVEFUNC(EVP_PKEY_set1_EC_KEY)
- RESOLVEFUNC(EVP_PKEY_get1_EC_KEY)
- RESOLVEFUNC(PEM_read_bio_EC_PUBKEY)
- RESOLVEFUNC(PEM_read_bio_ECPrivateKey)
- RESOLVEFUNC(PEM_write_bio_EC_PUBKEY)
- RESOLVEFUNC(PEM_write_bio_ECPrivateKey)
- RESOLVEFUNC(EC_KEY_get0_group)
- RESOLVEFUNC(EC_GROUP_get_degree)
- RESOLVEFUNC(EC_KEY_dup)
- RESOLVEFUNC(EC_KEY_new_by_curve_name)
- RESOLVEFUNC(EC_KEY_free)
+ RESOLVEFUNC(EVP_PKEY_set1_EC_KEY)
+ RESOLVEFUNC(EVP_PKEY_get1_EC_KEY)
+ RESOLVEFUNC(PEM_read_bio_EC_PUBKEY)
+ RESOLVEFUNC(PEM_read_bio_ECPrivateKey)
+ RESOLVEFUNC(PEM_write_bio_EC_PUBKEY)
+ RESOLVEFUNC(PEM_write_bio_ECPrivateKey)
+ RESOLVEFUNC(EC_KEY_get0_group)
+ RESOLVEFUNC(EC_GROUP_get_degree)
+ RESOLVEFUNC(EC_KEY_dup)
+ RESOLVEFUNC(EC_KEY_new_by_curve_name)
+ RESOLVEFUNC(EC_KEY_free)
#endif // OPENSSL_NO_EC
#endif // OPENSSL_NO_DEPRECATED_3_0
- RESOLVEFUNC(SSL_get_verify_result)
- RESOLVEFUNC(SSL_new)
- RESOLVEFUNC(SSL_get_SSL_CTX)
- RESOLVEFUNC(SSL_ctrl)
- RESOLVEFUNC(SSL_read)
- RESOLVEFUNC(SSL_set_accept_state)
- RESOLVEFUNC(SSL_set_bio)
- RESOLVEFUNC(SSL_set_connect_state)
- RESOLVEFUNC(SSL_shutdown)
- RESOLVEFUNC(SSL_in_init)
- RESOLVEFUNC(SSL_get_shutdown)
- RESOLVEFUNC(SSL_set_session)
- RESOLVEFUNC(SSL_SESSION_free)
- RESOLVEFUNC(SSL_get1_session)
- RESOLVEFUNC(SSL_get_session)
- RESOLVEFUNC(SSL_set_ex_data)
- RESOLVEFUNC(SSL_get_ex_data)
- RESOLVEFUNC(SSL_get_ex_data_X509_STORE_CTX_idx)
+ RESOLVEFUNC(SSL_get_verify_result)
+ RESOLVEFUNC(SSL_new)
+ RESOLVEFUNC(SSL_get_SSL_CTX)
+ RESOLVEFUNC(SSL_ctrl)
+ RESOLVEFUNC(SSL_read)
+ RESOLVEFUNC(SSL_set_accept_state)
+ RESOLVEFUNC(SSL_set_bio)
+ RESOLVEFUNC(SSL_set_connect_state)
+ RESOLVEFUNC(SSL_shutdown)
+ RESOLVEFUNC(SSL_in_init)
+ RESOLVEFUNC(SSL_get_shutdown)
+ RESOLVEFUNC(SSL_set_session)
+ RESOLVEFUNC(SSL_SESSION_free)
+ RESOLVEFUNC(SSL_get1_session)
+ RESOLVEFUNC(SSL_get_session)
+ RESOLVEFUNC(SSL_set_ex_data)
+ RESOLVEFUNC(SSL_get_ex_data)
+ RESOLVEFUNC(SSL_get_ex_data_X509_STORE_CTX_idx)
#ifndef OPENSSL_NO_PSK
- RESOLVEFUNC(SSL_set_psk_client_callback)
- RESOLVEFUNC(SSL_set_psk_server_callback)
- RESOLVEFUNC(SSL_CTX_use_psk_identity_hint)
+ RESOLVEFUNC(SSL_set_psk_client_callback)
+ RESOLVEFUNC(SSL_set_psk_server_callback)
+ RESOLVEFUNC(SSL_CTX_use_psk_identity_hint)
#endif // !OPENSSL_NO_PSK
- RESOLVEFUNC(SSL_write)
- RESOLVEFUNC(X509_NAME_entry_count)
- RESOLVEFUNC(X509_NAME_get_entry)
- RESOLVEFUNC(X509_NAME_ENTRY_get_data)
- RESOLVEFUNC(X509_NAME_ENTRY_get_object)
- RESOLVEFUNC(X509_PUBKEY_get)
- RESOLVEFUNC(X509_STORE_free)
- RESOLVEFUNC(X509_STORE_new)
- RESOLVEFUNC(X509_STORE_add_cert)
- RESOLVEFUNC(X509_STORE_CTX_free)
- RESOLVEFUNC(X509_STORE_CTX_init)
- RESOLVEFUNC(X509_STORE_CTX_new)
- RESOLVEFUNC(X509_STORE_CTX_set_purpose)
- RESOLVEFUNC(X509_STORE_CTX_get_error)
- RESOLVEFUNC(X509_STORE_CTX_get_error_depth)
- RESOLVEFUNC(X509_STORE_CTX_get_current_cert)
- RESOLVEFUNC(X509_STORE_CTX_get0_store)
- RESOLVEFUNC(X509_cmp)
- RESOLVEFUNC(X509_STORE_CTX_get_ex_data)
- RESOLVEFUNC(X509_dup)
- RESOLVEFUNC(X509_print)
- RESOLVEFUNC(X509_digest)
- RESOLVEFUNC(X509_EXTENSION_get_object)
- RESOLVEFUNC(X509_free)
- RESOLVEFUNC(X509_gmtime_adj)
- RESOLVEFUNC(ASN1_TIME_free)
- RESOLVEFUNC(X509_get_ext)
- RESOLVEFUNC(X509_get_ext_count)
- RESOLVEFUNC(X509_get_ext_d2i)
- RESOLVEFUNC(X509V3_EXT_get)
- RESOLVEFUNC(X509V3_EXT_d2i)
- RESOLVEFUNC(X509_EXTENSION_get_critical)
- RESOLVEFUNC(X509_EXTENSION_get_data)
- RESOLVEFUNC(BASIC_CONSTRAINTS_free)
- RESOLVEFUNC(AUTHORITY_KEYID_free)
- RESOLVEFUNC(GENERAL_NAME_free)
- RESOLVEFUNC(ASN1_STRING_print)
- RESOLVEFUNC(X509_check_issued)
- RESOLVEFUNC(X509_get_issuer_name)
- RESOLVEFUNC(X509_get_subject_name)
- RESOLVEFUNC(X509_get_serialNumber)
- RESOLVEFUNC(X509_verify_cert)
- RESOLVEFUNC(d2i_X509)
- RESOLVEFUNC(i2d_X509)
+ RESOLVEFUNC(SSL_write)
+ RESOLVEFUNC(X509_NAME_entry_count)
+ RESOLVEFUNC(X509_NAME_get_entry)
+ RESOLVEFUNC(X509_NAME_ENTRY_get_data)
+ RESOLVEFUNC(X509_NAME_ENTRY_get_object)
+ RESOLVEFUNC(X509_PUBKEY_get)
+ RESOLVEFUNC(X509_STORE_free)
+ RESOLVEFUNC(X509_STORE_new)
+ RESOLVEFUNC(X509_STORE_add_cert)
+ RESOLVEFUNC(X509_STORE_CTX_free)
+ RESOLVEFUNC(X509_STORE_CTX_init)
+ RESOLVEFUNC(X509_STORE_CTX_new)
+ RESOLVEFUNC(X509_STORE_CTX_set_purpose)
+ RESOLVEFUNC(X509_STORE_CTX_get_error)
+ RESOLVEFUNC(X509_STORE_CTX_get_error_depth)
+ RESOLVEFUNC(X509_STORE_CTX_get_current_cert)
+ RESOLVEFUNC(X509_STORE_CTX_get0_store)
+ RESOLVEFUNC(X509_cmp)
+ RESOLVEFUNC(X509_STORE_CTX_get_ex_data)
+ RESOLVEFUNC(X509_dup)
+ RESOLVEFUNC(X509_print)
+ RESOLVEFUNC(X509_digest)
+ RESOLVEFUNC(X509_EXTENSION_get_object)
+ RESOLVEFUNC(X509_free)
+ RESOLVEFUNC(X509_gmtime_adj)
+ RESOLVEFUNC(ASN1_TIME_free)
+ RESOLVEFUNC(X509_get_ext)
+ RESOLVEFUNC(X509_get_ext_count)
+ RESOLVEFUNC(X509_get_ext_d2i)
+ RESOLVEFUNC(X509V3_EXT_get)
+ RESOLVEFUNC(X509V3_EXT_d2i)
+ RESOLVEFUNC(X509_EXTENSION_get_critical)
+ RESOLVEFUNC(X509_EXTENSION_get_data)
+ RESOLVEFUNC(BASIC_CONSTRAINTS_free)
+ RESOLVEFUNC(AUTHORITY_KEYID_free)
+ RESOLVEFUNC(GENERAL_NAME_free)
+ RESOLVEFUNC(ASN1_STRING_print)
+ RESOLVEFUNC(X509_check_issued)
+ RESOLVEFUNC(X509_get_issuer_name)
+ RESOLVEFUNC(X509_get_subject_name)
+ RESOLVEFUNC(X509_get_serialNumber)
+ RESOLVEFUNC(X509_verify_cert)
+ RESOLVEFUNC(d2i_X509)
+ RESOLVEFUNC(i2d_X509)
#if OPENSSL_VERSION_MAJOR < 3
- RESOLVEFUNC(SSL_CTX_load_verify_locations)
+ RESOLVEFUNC(SSL_CTX_load_verify_locations)
#else
- RESOLVEFUNC(SSL_CTX_load_verify_dir)
+ RESOLVEFUNC(SSL_CTX_load_verify_dir)
#endif // OPENSSL_VERSION_MAJOR
- RESOLVEFUNC(i2d_SSL_SESSION)
- RESOLVEFUNC(d2i_SSL_SESSION)
+ RESOLVEFUNC(i2d_SSL_SESSION)
+ RESOLVEFUNC(d2i_SSL_SESSION)
#ifndef OPENSSL_NO_NEXTPROTONEG
- RESOLVEFUNC(SSL_select_next_proto)
- RESOLVEFUNC(SSL_CTX_set_next_proto_select_cb)
- RESOLVEFUNC(SSL_get0_next_proto_negotiated)
- RESOLVEFUNC(SSL_set_alpn_protos)
- RESOLVEFUNC(SSL_CTX_set_alpn_select_cb)
- RESOLVEFUNC(SSL_get0_alpn_selected)
+ RESOLVEFUNC(SSL_select_next_proto)
+ RESOLVEFUNC(SSL_CTX_set_next_proto_select_cb)
+ RESOLVEFUNC(SSL_get0_next_proto_negotiated)
+ RESOLVEFUNC(SSL_set_alpn_protos)
+ RESOLVEFUNC(SSL_CTX_set_alpn_select_cb)
+ RESOLVEFUNC(SSL_get0_alpn_selected)
#endif // !OPENSSL_NO_NEXTPROTONEG
#if QT_CONFIG(dtls)
- RESOLVEFUNC(SSL_CTX_set_cookie_generate_cb)
- RESOLVEFUNC(SSL_CTX_set_cookie_verify_cb)
- RESOLVEFUNC(DTLS_server_method)
- RESOLVEFUNC(DTLS_client_method)
+ RESOLVEFUNC(SSL_CTX_set_cookie_generate_cb)
+ RESOLVEFUNC(SSL_CTX_set_cookie_verify_cb)
+ RESOLVEFUNC(DTLS_server_method)
+ RESOLVEFUNC(DTLS_client_method)
#endif // dtls
- RESOLVEFUNC(CRYPTO_malloc)
- RESOLVEFUNC(DH_new)
- RESOLVEFUNC(DH_free)
- RESOLVEFUNC(d2i_DHparams)
- RESOLVEFUNC(i2d_DHparams)
- RESOLVEFUNC(DH_check)
- RESOLVEFUNC(BN_bin2bn)
+ RESOLVEFUNC(CRYPTO_malloc)
+ RESOLVEFUNC(BN_bin2bn)
#ifndef OPENSSL_NO_EC
- RESOLVEFUNC(EC_get_builtin_curves)
+ RESOLVEFUNC(EC_get_builtin_curves)
#endif // OPENSSL_NO_EC
- RESOLVEFUNC(PKCS12_parse)
- RESOLVEFUNC(d2i_PKCS12_bio)
- RESOLVEFUNC(PKCS12_free)
+ RESOLVEFUNC(PKCS12_parse)
+ RESOLVEFUNC(d2i_PKCS12_bio)
+ RESOLVEFUNC(PKCS12_free)
+ return true;
+ }();
- symbolsResolved.storeRelease(true);
- return true;
+ return symbolsResolved;
}
#endif // QT_CONFIG(library)
diff --git a/src/plugins/tls/openssl/qsslsocket_openssl_symbols_p.h b/src/plugins/tls/openssl/qsslsocket_openssl_symbols_p.h
index 3c387ab96b..a93c110b3f 100644
--- a/src/plugins/tls/openssl/qsslsocket_openssl_symbols_p.h
+++ b/src/plugins/tls/openssl/qsslsocket_openssl_symbols_p.h
@@ -233,7 +233,6 @@ void q_X509_STORE_set_verify_cb(X509_STORE *ctx, X509_STORE_CTX_verify_cb verify
int q_X509_STORE_set_ex_data(X509_STORE *ctx, int idx, void *data);
void *q_X509_STORE_get_ex_data(X509_STORE *r, int idx);
STACK_OF(X509) *q_X509_STORE_CTX_get0_chain(X509_STORE_CTX *ctx);
-void q_DH_get0_pqg(const DH *dh, const BIGNUM **p, const BIGNUM **q, const BIGNUM **g);
# define q_SSL_load_error_strings() q_OPENSSL_init_ssl(OPENSSL_INIT_LOAD_SSL_STRINGS \
| OPENSSL_INIT_LOAD_CRYPTO_STRINGS, NULL)
@@ -391,7 +390,6 @@ int q_OBJ_obj2nid(const ASN1_OBJECT *a);
#define q_EVP_get_digestbynid(a) q_EVP_get_digestbyname(q_OBJ_nid2sn(a))
EVP_PKEY *q_PEM_read_bio_PrivateKey(BIO *a, EVP_PKEY **b, pem_password_cb *c, void *d);
-DH *q_PEM_read_bio_DHparams(BIO *a, DH **b, pem_password_cb *c, void *d);
int q_PEM_write_bio_PrivateKey(BIO *a, EVP_PKEY *b, const EVP_CIPHER *c, unsigned char *d,
int e, pem_password_cb *f, void *g);
int q_PEM_write_bio_PrivateKey_traditional(BIO *a, EVP_PKEY *b, const EVP_CIPHER *c, unsigned char *d,
@@ -504,14 +502,21 @@ X509 *q_X509_STORE_CTX_get_current_cert(X509_STORE_CTX *ctx);
X509_STORE *q_X509_STORE_CTX_get0_store(X509_STORE_CTX *ctx);
// Diffie-Hellman support
+#ifndef OPENSSL_NO_DEPRECATED_3_0
DH *q_DH_new();
void q_DH_free(DH *dh);
+int q_DH_check(DH *dh, int *codes);
+void q_DH_get0_pqg(const DH *dh, const BIGNUM **p, const BIGNUM **q, const BIGNUM **g);
+
DH *q_d2i_DHparams(DH **a, const unsigned char **pp, long length);
int q_i2d_DHparams(DH *a, unsigned char **p);
-int q_DH_check(DH *dh, int *codes);
+
+DH *q_PEM_read_bio_DHparams(BIO *a, DH **b, pem_password_cb *c, void *d);
+#endif // OPENSSL_NO_DEPRECATED_3_0
BIGNUM *q_BN_bin2bn(const unsigned char *s, int len, BIGNUM *ret);
#define q_SSL_CTX_set_tmp_dh(ctx, dh) q_SSL_CTX_ctrl((ctx), SSL_CTRL_SET_TMP_DH, 0, (char *)dh)
+#define q_SSL_CTX_set_dh_auto(ctx, onoff) q_SSL_CTX_ctrl(ctx,SSL_CTRL_SET_DH_AUTO,onoff,NULL)
#ifndef OPENSSL_NO_EC
// EC Diffie-Hellman support
@@ -662,6 +667,7 @@ void *q_CRYPTO_malloc(size_t num, const char *file, int line);
#define q_OPENSSL_malloc(num) q_CRYPTO_malloc(num, "", 0)
void q_CRYPTO_free(void *str, const char *file, int line);
# define q_OPENSSL_free(addr) q_CRYPTO_free(addr, "", 0)
+int q_CRYPTO_memcmp(const void * in_a, const void * in_b, size_t len);
void q_SSL_set_info_callback(SSL *ssl, void (*cb) (const SSL *ssl, int type, int val));
const char *q_SSL_alert_type_string(int value);
diff --git a/src/plugins/tls/openssl/qtls_openssl.cpp b/src/plugins/tls/openssl/qtls_openssl.cpp
index 27abf1bc8d..57d09a649b 100644
--- a/src/plugins/tls/openssl/qtls_openssl.cpp
+++ b/src/plugins/tls/openssl/qtls_openssl.cpp
@@ -92,7 +92,7 @@ QSslCertificate findCertificateToFetch(const QList<QSslError> &tlsErrors, bool c
if (checkAIA) {
const auto extensions = certToFetch.extensions();
for (const auto &ext : extensions) {
- if (ext.oid() == QStringLiteral("1.3.6.1.5.5.7.1.1")) // See RFC 4325
+ if (ext.oid() == u"1.3.6.1.5.5.7.1.1") // See RFC 4325
return certToFetch;
}
//The only reason we check this extensions is because an application set trusted
@@ -490,7 +490,7 @@ void TlsCryptographOpenSSL::init(QSslSocket *qObj, QSslSocketPrivate *dObj)
handshakeInterrupted = false;
fetchAuthorityInformation = false;
- caToFetch = QSslCertificate{};
+ caToFetch.reset();
}
void TlsCryptographOpenSSL::checkSettingSslContext(std::shared_ptr<QSslContext> tlsContext)
@@ -577,7 +577,7 @@ bool TlsCryptographOpenSSL::startHandshake()
auto configuration = q->sslConfiguration();
if (!errorsReportedFromCallback) {
const auto &peerCertificateChain = configuration.peerCertificateChain();
- for (const auto &currentError : qAsConst(lastErrors)) {
+ for (const auto &currentError : std::as_const(lastErrors)) {
emit q->peerVerifyError(QTlsPrivate::X509CertificateOpenSSL::openSSLErrorToQSslError(currentError.code,
peerCertificateChain.value(currentError.depth)));
if (q->state() != QAbstractSocket::ConnectedState)
@@ -697,7 +697,7 @@ bool TlsCryptographOpenSSL::startHandshake()
// Translate errors from the error list into QSslErrors.
errors.reserve(errors.size() + errorList.size());
- for (const auto &error : qAsConst(errorList))
+ for (const auto &error : std::as_const(errorList))
errors << X509CertificateOpenSSL::openSSLErrorToQSslError(error.code, peerCertificateChain.value(error.depth));
if (!errors.isEmpty()) {
@@ -749,7 +749,7 @@ void TlsCryptographOpenSSL::enableHandshakeContinuation()
void TlsCryptographOpenSSL::cancelCAFetch()
{
fetchAuthorityInformation = false;
- caToFetch = QSslCertificate{};
+ caToFetch.reset();
}
void TlsCryptographOpenSSL::continueHandshake()
@@ -1726,11 +1726,11 @@ unsigned TlsCryptographOpenSSL::pskClientTlsCallback(const char *hint, char *ide
return 0;
// Copy data back into OpenSSL
- const int identityLength = qMin(authenticator.identity().length(), authenticator.maximumIdentityLength());
+ const int identityLength = qMin(authenticator.identity().size(), authenticator.maximumIdentityLength());
std::memcpy(identity, authenticator.identity().constData(), identityLength);
identity[identityLength] = 0;
- const int pskLength = qMin(authenticator.preSharedKey().length(), authenticator.maximumPreSharedKeyLength());
+ const int pskLength = qMin(authenticator.preSharedKey().size(), authenticator.maximumPreSharedKeyLength());
std::memcpy(psk, authenticator.preSharedKey().constData(), pskLength);
return pskLength;
}
@@ -1752,7 +1752,7 @@ unsigned TlsCryptographOpenSSL::pskServerTlsCallback(const char *identity, unsig
return 0;
// Copy data back into OpenSSL
- const int pskLength = qMin(authenticator.preSharedKey().length(), authenticator.maximumPreSharedKeyLength());
+ const int pskLength = qMin(authenticator.preSharedKey().size(), authenticator.maximumPreSharedKeyLength());
std::memcpy(psk, authenticator.preSharedKey().constData(), pskLength);
return pskLength;
}
@@ -1803,7 +1803,7 @@ void TlsCryptographOpenSSL::caRootLoaded(QSslCertificate cert, QSslCertificate t
Q_ASSERT(q);
//Done, fetched already:
- caToFetch = QSslCertificate{};
+ caToFetch.reset();
if (fetchAuthorityInformation) {
if (!q->sslConfiguration().caCertificates().contains(trustedRoot))
diff --git a/src/plugins/tls/openssl/qtls_openssl_p.h b/src/plugins/tls/openssl/qtls_openssl_p.h
index 31fede2ace..65d21a395b 100644
--- a/src/plugins/tls/openssl/qtls_openssl_p.h
+++ b/src/plugins/tls/openssl/qtls_openssl_p.h
@@ -120,7 +120,7 @@ private:
bool handshakeInterrupted = false;
bool fetchAuthorityInformation = false;
- QSslCertificate caToFetch;
+ std::optional<QSslCertificate> caToFetch;
bool inSetAndEmitError = false;
bool pendingFatalAlert = false;
diff --git a/src/plugins/tls/openssl/qtlsbackend_openssl.cpp b/src/plugins/tls/openssl/qtlsbackend_openssl.cpp
index ea31086fad..d73515724b 100644
--- a/src/plugins/tls/openssl/qtlsbackend_openssl.cpp
+++ b/src/plugins/tls/openssl/qtlsbackend_openssl.cpp
@@ -17,7 +17,7 @@
#include <QtNetwork/qssl.h>
#include <QtCore/qdir.h>
-#include <QtCore/qdiriterator.h>
+#include <QtCore/qdirlisting.h>
#include <QtCore/qlist.h>
#include <QtCore/qmutex.h>
#include <QtCore/qscopeguard.h>
@@ -31,9 +31,13 @@ QT_BEGIN_NAMESPACE
using namespace Qt::StringLiterals;
-Q_LOGGING_CATEGORY(lcTlsBackend, "qt.tlsbackend.ossl");
+#if defined(Q_OS_WIN) || defined(Q_OS_MACOS)
+constexpr auto DefaultWarningLevel = QtCriticalMsg;
+#else
+constexpr auto DefaultWarningLevel = QtDebugMsg;
+#endif
-Q_GLOBAL_STATIC(QRecursiveMutex, qt_opensslInitMutex)
+Q_LOGGING_CATEGORY(lcTlsBackend, "qt.tlsbackend.ossl", DefaultWarningLevel);
static void q_loadCiphersForConnection(SSL *connection, QList<QSslCipher> &ciphers,
QList<QSslCipher> &defaultCiphers)
@@ -59,8 +63,6 @@ static void q_loadCiphersForConnection(SSL *connection, QList<QSslCipher> &ciphe
}
}
-bool QTlsBackendOpenSSL::s_libraryLoaded = false;
-bool QTlsBackendOpenSSL::s_loadedCiphersAndCerts = false;
int QTlsBackendOpenSSL::s_indexForSSLExtraData = -1;
QString QTlsBackendOpenSSL::getErrorsFromOpenSsl()
@@ -92,12 +94,10 @@ void QTlsBackendOpenSSL::clearErrorQueue()
bool QTlsBackendOpenSSL::ensureLibraryLoaded()
{
- if (!q_resolveOpenSslSymbols())
- return false;
-
- const QMutexLocker locker(qt_opensslInitMutex());
+ static bool libraryLoaded = []() {
+ if (!q_resolveOpenSslSymbols())
+ return false;
- if (!s_libraryLoaded) {
// Initialize OpenSSL.
if (q_OPENSSL_init_ssl(0, nullptr) != 1)
return false;
@@ -119,10 +119,10 @@ bool QTlsBackendOpenSSL::ensureLibraryLoaded()
return false;
}
- s_libraryLoaded = true;
- }
+ return true;
+ }();
- return true;
+ return libraryLoaded;
}
QString QTlsBackendOpenSSL::backendName() const
@@ -175,11 +175,24 @@ void QTlsBackendOpenSSL::ensureInitialized() const
void QTlsBackendOpenSSL::ensureCiphersAndCertsLoaded() const
{
- const QMutexLocker locker(qt_opensslInitMutex());
+ Q_CONSTINIT static bool initializationStarted = false;
+ Q_CONSTINIT static QAtomicInt initialized = Q_BASIC_ATOMIC_INITIALIZER(0);
+ Q_CONSTINIT static QRecursiveMutex initMutex;
- if (s_loadedCiphersAndCerts)
+ if (initialized.loadAcquire())
return;
- s_loadedCiphersAndCerts = true;
+
+ const QMutexLocker locker(&initMutex);
+
+ if (initializationStarted || initialized.loadAcquire())
+ return;
+
+ // Indicate that the initialization has already started in the current
+ // thread in case of recursive calls. The atomic variable cannot be used
+ // for this because it is checked without holding the init mutex.
+ initializationStarted = true;
+
+ auto guard = qScopeGuard([] { initialized.storeRelease(1); });
resetDefaultCiphers();
resetDefaultEllipticCurves();
@@ -191,11 +204,11 @@ void QTlsBackendOpenSSL::ensureCiphersAndCertsLoaded() const
#elif defined(Q_OS_UNIX) && !defined(Q_OS_DARWIN)
// check whether we can enable on-demand root-cert loading (i.e. check whether the sym links are there)
const QList<QByteArray> dirs = QSslSocketPrivate::unixRootCertDirectories();
- QStringList symLinkFilter;
- symLinkFilter << "[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f].[0-9]"_L1;
+ const QStringList symLinkFilter{
+ u"[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f].[0-9]"_s};
for (const auto &dir : dirs) {
- QDirIterator iterator(QLatin1StringView(dir), symLinkFilter, QDir::Files);
- if (iterator.hasNext()) {
+ QDirListing dirList(QString::fromLatin1(dir), symLinkFilter, QDir::Files);
+ if (dirList.cbegin() != dirList.cend()) { // Not empty
QSslSocketPrivate::setRootCertOnDemandLoadingSupported(true);
break;
}
@@ -350,7 +363,9 @@ QList<QSslCertificate> systemCaCertificates()
QList<QSslCertificate> systemCerts;
#if defined(Q_OS_WIN)
HCERTSTORE hSystemStore;
- hSystemStore = CertOpenSystemStoreW(0, L"ROOT");
+ hSystemStore =
+ CertOpenStore(CERT_STORE_PROV_SYSTEM, 0, 0,
+ CERT_STORE_READONLY_FLAG | CERT_SYSTEM_STORE_CURRENT_USER, L"ROOT");
if (hSystemStore) {
PCCERT_CONTEXT pc = nullptr;
while (1) {
@@ -379,13 +394,12 @@ QList<QSslCertificate> systemCaCertificates()
currentDir.setNameFilters(QStringList{QStringLiteral("*.pem"), QStringLiteral("*.crt")});
for (const auto &directory : directories) {
currentDir.setPath(QLatin1StringView(directory));
- QDirIterator it(currentDir);
- while (it.hasNext()) {
+ for (const auto &dirEntry : QDirListing(currentDir)) {
// use canonical path here to not load the same certificate twice if symlinked
- certFiles.insert(it.nextFileInfo().canonicalFilePath());
+ certFiles.insert(dirEntry.canonicalFilePath());
}
}
- for (const QString& file : qAsConst(certFiles))
+ for (const QString& file : std::as_const(certFiles))
systemCerts.append(QSslCertificate::fromPath(file, QSsl::Pem));
}
#endif // platform
diff --git a/src/plugins/tls/openssl/qtlsbackend_openssl_p.h b/src/plugins/tls/openssl/qtlsbackend_openssl_p.h
index 1c5e68e0c6..b9f1f95df0 100644
--- a/src/plugins/tls/openssl/qtlsbackend_openssl_p.h
+++ b/src/plugins/tls/openssl/qtlsbackend_openssl_p.h
@@ -41,16 +41,13 @@ public:
static void logAndClearErrorQueue();
static void clearErrorQueue();
- static bool ensureLibraryLoaded();
// Index used in SSL_get_ex_data to get the matching TlsCryptographerOpenSSL:
- static bool s_libraryLoaded;
- static bool s_loadedCiphersAndCerts;
static int s_indexForSSLExtraData;
static QString msgErrorsDuringHandshake();
static QSslCipher qt_OpenSSL_cipher_to_QSslCipher(const SSL_CIPHER *cipher);
private:
-
+ static bool ensureLibraryLoaded();
QString backendName() const override;
bool isValid() const override;
long tlsLibraryVersionNumber() const override;
diff --git a/src/plugins/tls/openssl/qtlskey_openssl.cpp b/src/plugins/tls/openssl/qtlskey_openssl.cpp
index b17e1d9d74..294fc2ffcd 100644
--- a/src/plugins/tls/openssl/qtlskey_openssl.cpp
+++ b/src/plugins/tls/openssl/qtlskey_openssl.cpp
@@ -101,19 +101,19 @@ QByteArray TlsKeyOpenSSL::derFromPem(const QByteArray &pem, QMap<QByteArray, QBy
QByteArray der(pem);
int headerIndex = der.indexOf(header);
- int footerIndex = der.indexOf(footer, headerIndex + header.length());
+ int footerIndex = der.indexOf(footer, headerIndex + header.size());
if (type() != QSsl::PublicKey) {
if (headerIndex == -1 || footerIndex == -1) {
header = pkcs8Header(true);
footer = pkcs8Footer(true);
headerIndex = der.indexOf(header);
- footerIndex = der.indexOf(footer, headerIndex + header.length());
+ footerIndex = der.indexOf(footer, headerIndex + header.size());
}
if (headerIndex == -1 || footerIndex == -1) {
header = pkcs8Header(false);
footer = pkcs8Footer(false);
headerIndex = der.indexOf(header);
- footerIndex = der.indexOf(footer, headerIndex + header.length());
+ footerIndex = der.indexOf(footer, headerIndex + header.size());
}
}
if (headerIndex == -1 || footerIndex == -1)
@@ -124,7 +124,7 @@ QByteArray TlsKeyOpenSSL::derFromPem(const QByteArray &pem, QMap<QByteArray, QBy
if (der.contains("Proc-Type:")) {
// taken from QHttpNetworkReplyPrivate::parseHeader
int i = 0;
- while (i < der.length()) {
+ while (i < der.size()) {
int j = der.indexOf(':', i); // field-name
if (j == -1)
break;
@@ -143,7 +143,7 @@ QByteArray TlsKeyOpenSSL::derFromPem(const QByteArray &pem, QMap<QByteArray, QBy
int length = i -(hasCR ? 1: 0) - j;
value += der.mid(j, length).trimmed();
j = ++i;
- } while (i < der.length() && (der.at(i) == ' ' || der.at(i) == '\t'));
+ } while (i < der.size() && (der.at(i) == ' ' || der.at(i) == '\t'));
if (i == -1)
break; // something is wrong
@@ -222,7 +222,7 @@ Qt::HANDLE TlsKeyOpenSSL::handle() const
#else
qCWarning(lcTlsBackend,
"This version of OpenSSL disabled direct manipulation with RSA/DSA/DH/EC_KEY structures, consider using QSsl::Opaque instead.");
- return Qt::HANDLE(nullptr);
+ return Qt::HANDLE(genericKey);
#endif
}
diff --git a/src/plugins/tls/openssl/qwindowscarootfetcher.cpp b/src/plugins/tls/openssl/qwindowscarootfetcher.cpp
index 82ad3abfd0..a18aae0b71 100644
--- a/src/plugins/tls/openssl/qwindowscarootfetcher.cpp
+++ b/src/plugins/tls/openssl/qwindowscarootfetcher.cpp
@@ -245,3 +245,5 @@ QHCertStorePointer QWindowsCaRootFetcher::createAdditionalStore() const
}
QT_END_NAMESPACE
+
+#include "moc_qwindowscarootfetcher_p.cpp"
diff --git a/src/plugins/tls/openssl/qx509_openssl.cpp b/src/plugins/tls/openssl/qx509_openssl.cpp
index 29f98755bd..0cd3749f88 100644
--- a/src/plugins/tls/openssl/qx509_openssl.cpp
+++ b/src/plugins/tls/openssl/qx509_openssl.cpp
@@ -12,12 +12,13 @@
#include <QtNetwork/qsslsocket.h>
#include <QtNetwork/qhostaddress.h>
-#include <QtCore/qvarlengtharray.h>
-#include <QtCore/qscopeguard.h>
-#include <QtCore/qdatetime.h>
-#include <QtCore/qiodevice.h>
#include <QtCore/qendian.h>
+#include <QtCore/qdatetime.h>
#include <QtCore/qhash.h>
+#include <QtCore/qiodevice.h>
+#include <QtCore/qscopeguard.h>
+#include <QtCore/qtimezone.h>
+#include <QtCore/qvarlengtharray.h>
QT_BEGIN_NAMESPACE
@@ -77,7 +78,7 @@ QDateTime dateTimeFromASN1(const ASN1_TIME *aTime)
if (q_ASN1_TIME_to_tm(aTime, &lTime)) {
QDate resDate(lTime.tm_year + 1900, lTime.tm_mon + 1, lTime.tm_mday);
QTime resTime(lTime.tm_hour, lTime.tm_min, lTime.tm_sec);
- result = QDateTime(resDate, resTime, Qt::UTC);
+ result = QDateTime(resDate, resTime, QTimeZone::UTC);
}
return result;
@@ -616,7 +617,7 @@ QList<QSslError> X509CertificateOpenSSL::verify(const QList<QSslCertificate> &ca
const QString &hostName)
{
// This was previously QSslSocketPrivate::verify().
- if (certificateChain.count() <= 0)
+ if (certificateChain.size() <= 0)
return {QSslError(QSslError::UnspecifiedError)};
QList<QSslError> errors;
@@ -658,7 +659,7 @@ QList<QSslError> X509CertificateOpenSSL::verify(const QList<QSslCertificate> &ca
// Build the chain of intermediate certificates
STACK_OF(X509) *intermediates = nullptr;
- if (certificateChain.length() > 1) {
+ if (certificateChain.size() > 1) {
intermediates = (STACK_OF(X509) *) q_OPENSSL_sk_new_null();
if (!intermediates) {
@@ -710,7 +711,7 @@ QList<QSslError> X509CertificateOpenSSL::verify(const QList<QSslCertificate> &ca
// Translate errors from the error list into QSslErrors.
errors.reserve(errors.size() + lastErrors.size());
- for (const auto &error : qAsConst(lastErrors))
+ for (const auto &error : std::as_const(lastErrors))
errors << openSSLErrorToQSslError(error.code, certificateChain.value(error.depth));
return errors;
diff --git a/src/plugins/tls/schannel/CMakeLists.txt b/src/plugins/tls/schannel/CMakeLists.txt
index 196412ab57..a7f7fcd99f 100644
--- a/src/plugins/tls/schannel/CMakeLists.txt
+++ b/src/plugins/tls/schannel/CMakeLists.txt
@@ -1,5 +1,5 @@
# Copyright (C) 2022 The Qt Company Ltd.
-# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+# SPDX-License-Identifier: BSD-3-Clause
qt_internal_add_plugin(QSchannelBackendPlugin
OUTPUT_NAME qschannelbackend
@@ -29,4 +29,6 @@ qt_internal_add_plugin(QSchannelBackendPlugin
secur32
bcrypt
ncrypt
+ DEFINES
+ QT_NO_CAST_FROM_ASCII
)
diff --git a/src/plugins/tls/schannel/qtls_schannel.cpp b/src/plugins/tls/schannel/qtls_schannel.cpp
index ba14e61b57..a244a90ebc 100644
--- a/src/plugins/tls/schannel/qtls_schannel.cpp
+++ b/src/plugins/tls/schannel/qtls_schannel.cpp
@@ -26,17 +26,11 @@
#include <security.h>
#include <schnlsp.h>
-#if NTDDI_VERSION >= NTDDI_WINBLUE && !defined(Q_CC_MINGW)
+#if NTDDI_VERSION >= NTDDI_WINBLUE && defined(SECBUFFER_APPLICATION_PROTOCOLS)
// ALPN = Application Layer Protocol Negotiation
#define SUPPORTS_ALPN 1
#endif
-// Redstone 5/1809 has all the API available, but TLS 1.3 is not enabled until a later version of
-// Win 10, checked at runtime in supportsTls13()
-#if defined(NTDDI_WIN10_RS5) && NTDDI_VERSION >= NTDDI_WIN10_RS5
-#define SUPPORTS_TLS13 1
-#endif
-
// Not defined in MinGW
#ifndef SECBUFFER_ALERT
#define SECBUFFER_ALERT 17
@@ -93,6 +87,12 @@
#ifndef SP_PROT_TLS1_3
#define SP_PROT_TLS1_3 (SP_PROT_TLS1_3_CLIENT | SP_PROT_TLS1_3_SERVER)
#endif
+#ifndef BCRYPT_ECDH_ALGORITHM
+#define BCRYPT_ECDH_ALGORITHM L"ECDH"
+#endif
+#ifndef BCRYPT_ECDSA_ALGORITHM
+#define BCRYPT_ECDSA_ALGORITHM L"ECDSA"
+#endif
/*
@future!:
@@ -114,10 +114,6 @@
- Check if SEC_I_INCOMPLETE_CREDENTIALS is still returned for both "missing certificate" and
"missing PSK" when calling InitializeSecurityContext in "performHandshake".
- Medium priority:
- - Setting cipher-suites (or ALG_ID)
- - People have survived without it in WinRT
-
Low priority:
- Possibly make RAII wrappers for SecBuffer (which I commonly create QScopeGuards for)
@@ -133,39 +129,341 @@ Q_LOGGING_CATEGORY(lcTlsBackendSchannel, "qt.tlsbackend.schannel");
QByteArray _q_makePkcs12(const QList<QSslCertificate> &certs, const QSslKey &key,
const QString &passPhrase);
+namespace {
+bool supportsTls13();
+}
+
namespace QTlsPrivate {
-QList<QSslCipher> defaultCiphers()
+QList<QSslCipher> defaultCiphers();
+
+struct SchannelCipherInfo {
+ const char *openSslCipherSuite;
+ const char *schannelCipherSuite;
+ const char *keyExchangeMethod;
+ const char *authenticationMethod;
+ const char *encryptionMethod;
+ int encryptionBits;
+ const char *hashMethod;
+ QList<QSsl::SslProtocol> protocols;
+};
+
+// The list of supported ciphers according to
+// https://learn.microsoft.com/en-us/windows/win32/secauthn/tls-cipher-suites-in-windows-server-2022
+QT_WARNING_PUSH
+QT_WARNING_DISABLE_DEPRECATED
+std::array<SchannelCipherInfo, 44> schannelCipherInfo = {{
+ {"TLS_AES_256_GCM_SHA384", "TLS_AES_256_GCM_SHA384", "", "", "AES", 256, "SHA384", {QSsl::TlsV1_3}},
+ {"TLS_AES_128_GCM_SHA256", "TLS_AES_128_GCM_SHA256", "", "", "AES", 128, "SHA256", {QSsl::TlsV1_3}},
+ {"ECDHE-ECDSA-AES256-GCM-SHA384", "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", "ECDH", "ECDSA", "AES", 256, "SHA384", {QSsl::TlsV1_2}},
+ {"ECDHE-ECDSA-AES128-GCM-SHA256", "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", "ECDH", "ECDSA", "AES", 128, "SHA256", {QSsl::TlsV1_2}},
+ {"ECDHE-RSA-AES256-GCM-SHA384", "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", "ECDH", "RSA", "AES", 256, "SHA384", {QSsl::TlsV1_2}},
+ {"ECDHE-RSA-AES128-GCM-SHA256", "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", "ECDH", "RSA", "AES", 128, "SHA256", {QSsl::TlsV1_2}},
+ {"DHE-RSA-AES256-GCM-SHA384", "TLS_DHE_RSA_WITH_AES_256_GCM_SHA384", "DH", "RSA", "AES", 256, "SHA384", {QSsl::TlsV1_2}},
+ {"DHE-RSA-AES128-GCM-SHA256", "TLS_DHE_RSA_WITH_AES_128_GCM_SHA256", "DH", "RSA", "AES", 128, "SHA256", {QSsl::TlsV1_2}},
+ {"ECDHE-ECDSA-AES256-SHA384", "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384", "ECDH", "ECDSA", "AES", 256, "SHA384", {QSsl::TlsV1_2}},
+ {"ECDHE-ECDSA-AES128-SHA256", "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256", "ECDH", "ECDSA", "AES", 128, "SHA256", {QSsl::TlsV1_2}},
+ {"ECDHE-RSA-AES256-SHA384", "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384", "ECDH", "RSA", "AES", 256, "SHA384", {QSsl::TlsV1_2}},
+ {"ECDHE-RSA-AES128-SHA256", "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256", "ECDH", "RSA", "AES", 128, "SHA256", {QSsl::TlsV1_2}},
+ {"ECDHE-ECDSA-AES256-SHA", "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA", "ECDH", "ECDSA", "AES", 256, "SHA1", {QSsl::TlsV1_2, QSsl::TlsV1_1, QSsl::TlsV1_0}},
+ {"ECDHE-ECDSA-AES128-SHA", "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", "ECDH", "ECDSA", "AES", 128, "SHA1", {QSsl::TlsV1_2, QSsl::TlsV1_1, QSsl::TlsV1_0}},
+ {"ECDHE-RSA-AES256-SHA", "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", "ECDH", "RSA", "AES", 256, "SHA1", {QSsl::TlsV1_2, QSsl::TlsV1_1, QSsl::TlsV1_0}},
+ {"ECDHE-RSA-AES128-SHA", "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", "ECDH", "RSA", "AES", 128, "SHA1", {QSsl::TlsV1_2, QSsl::TlsV1_1, QSsl::TlsV1_0}},
+ {"AES256-GCM-SHA384", "TLS_RSA_WITH_AES_256_GCM_SHA384", "RSA", "RSA", "AES", 256, "SHA384", {QSsl::TlsV1_2}},
+ {"AES128-GCM-SHA256", "TLS_RSA_WITH_AES_128_GCM_SHA256", "RSA", "RSA", "AES", 128, "SHA256", {QSsl::TlsV1_2}},
+ {"AES256-SHA256", "TLS_RSA_WITH_AES_256_CBC_SHA256", "RSA", "RSA", "AES", 256, "SHA256", {QSsl::TlsV1_2}},
+ {"AES128-SHA256", "TLS_RSA_WITH_AES_128_CBC_SHA256", "RSA", "RSA", "AES", 128, "SHA256", {QSsl::TlsV1_2}},
+ {"AES256-SHA", "TLS_RSA_WITH_AES_256_CBC_SHA", "RSA", "RSA", "AES", 256, "SHA1", {QSsl::TlsV1_2, QSsl::TlsV1_1, QSsl::TlsV1_0}},
+ {"AES128-SHA", "TLS_RSA_WITH_AES_128_CBC_SHA", "RSA", "RSA", "AES", 128, "SHA1", {QSsl::TlsV1_2, QSsl::TlsV1_1, QSsl::TlsV1_0}},
+ {"DES-CBC3-SHA", "TLS_RSA_WITH_3DES_EDE_CBC_SHA", "RSA", "RSA", "3DES", 168, "SHA1", {QSsl::TlsV1_2, QSsl::TlsV1_1, QSsl::TlsV1_0}},
+ {"NULL-SHA256", "TLS_RSA_WITH_NULL_SHA256", "RSA", "RSA", "", 0, "SHA256", {QSsl::TlsV1_2}},
+ {"NULL-SHA", "TLS_RSA_WITH_NULL_SHA", "RSA", "RSA", "", 0, "SHA1", {QSsl::TlsV1_2, QSsl::TlsV1_1, QSsl::TlsV1_0}},
+
+ // the following cipher suites are not enabled by default in schannel provider
+ {"TLS_CHACHA20_POLY1305_SHA256", "TLS_CHACHA20_POLY1305_SHA256", "", "", "CHACHA20_POLY1305", 0, "", {QSsl::TlsV1_3}},
+ {"DHE-RSA-AES256-SHA", "TLS_DHE_RSA_WITH_AES_256_CBC_SHA", "DH", "RSA", "AES", 256, "SHA1", {QSsl::TlsV1_2, QSsl::TlsV1_1, QSsl::TlsV1_0}},
+ {"DHE-RSA-AES128-SHA", "TLS_DHE_RSA_WITH_AES_128_CBC_SHA", "DH", "RSA", "AES", 128, "SHA1", {QSsl::TlsV1_2, QSsl::TlsV1_1, QSsl::TlsV1_0}},
+ {"DHE-DSS-AES256-SHA256", "TLS_DHE_DSS_WITH_AES_256_CBC_SHA256", "DH", "DSA", "AES", 256, "SHA256", {QSsl::TlsV1_2}},
+ {"DHE-DSS-AES128-SHA256", "TLS_DHE_DSS_WITH_AES_128_CBC_SHA256", "DH", "DSA", "AES", 128, "SHA256", {QSsl::TlsV1_2}},
+ {"DHE-DSS-AES256-SHA", "TLS_DHE_DSS_WITH_AES_256_CBC_SHA", "DH", "DSA", "AES", 256, "SHA1", {QSsl::TlsV1_2, QSsl::TlsV1_1, QSsl::TlsV1_0}},
+ {"DHE-DSS-AES128-SHA", "TLS_DHE_DSS_WITH_AES_128_CBC_SHA", "DH", "DSA", "AES", 128, "SHA1", {QSsl::TlsV1_2, QSsl::TlsV1_1, QSsl::TlsV1_0}},
+ {"EDH-DSS-DES-CBC3-SHA", "TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA", "DH", "DSA", "3DES", 168, "SHA1", {QSsl::TlsV1_2, QSsl::TlsV1_1, QSsl::TlsV1_0}},
+ {"RC4-SHA", "TLS_RSA_WITH_RC4_128_SHA", "RSA", "RSA", "RC4", 128, "SHA1", {QSsl::TlsV1_2, QSsl::TlsV1_1, QSsl::TlsV1_0}},
+ {"RC4-MD5", "TLS_RSA_WITH_RC4_128_MD5", "RSA", "RSA", "RC4", 128, "MD5", {QSsl::TlsV1_2, QSsl::TlsV1_1, QSsl::TlsV1_0}},
+ {"DES-CBC-SHA", "TLS_RSA_WITH_DES_CBC_SHA", "RSA", "RSA", "DES", 56, "SHA1", {QSsl::TlsV1_2, QSsl::TlsV1_1, QSsl::TlsV1_0}},
+ {"EDH-DSS-DES-CBC-SHA", "TLS_DHE_DSS_WITH_DES_CBC_SHA", "DH", "DSA", "DES", 56, "SHA1", {QSsl::TlsV1_2, QSsl::TlsV1_1, QSsl::TlsV1_0}},
+ {"NULL-MD5", "TLS_RSA_WITH_NULL_MD5", "RSA", "RSA", "", 0, "MD5", {QSsl::TlsV1_2, QSsl::TlsV1_1, QSsl::TlsV1_0}},
+
+ // PSK cipher suites
+ {"PSK-AES256-GCM-SHA384", "TLS_PSK_WITH_AES_256_GCM_SHA384", "PSK", "", "AES", 256, "SHA384", {QSsl::TlsV1_2}},
+ {"PSK-AES128-GCM-SHA256", "TLS_PSK_WITH_AES_128_GCM_SHA256", "PSK", "", "AES", 128, "SHA256", {QSsl::TlsV1_2}},
+ {"PSK-AES256-CBC-SHA384", "TLS_PSK_WITH_AES_256_CBC_SHA384", "PSK", "", "AES", 256, "SHA384", {QSsl::TlsV1_2}},
+ {"PSK-AES128-CBC-SHA256", "TLS_PSK_WITH_AES_128_CBC_SHA256", "PSK", "", "AES", 128, "SHA256", {QSsl::TlsV1_2}},
+ {"PSK-NULL-SHA384", "TLS_PSK_WITH_NULL_SHA384", "PSK", "", "", 0, "SHA384", {QSsl::TlsV1_2}},
+ {"PSK-NULL-SHA256", "TLS_PSK_WITH_NULL_SHA256", "PSK", "", "", 0, "SHA256", {QSsl::TlsV1_2}},
+}};
+QT_WARNING_POP
+
+const SchannelCipherInfo *cipherInfoByOpenSslName(const QString &name)
+{
+ for (const auto &cipherInfo : schannelCipherInfo) {
+ if (name == QLatin1StringView(cipherInfo.openSslCipherSuite))
+ return &cipherInfo;
+ }
+
+ return nullptr;
+}
+
+UNICODE_STRING cbcChainingMode = {
+ sizeof(BCRYPT_CHAIN_MODE_CBC) - 2,
+ sizeof(BCRYPT_CHAIN_MODE_CBC),
+ const_cast<PWSTR>(BCRYPT_CHAIN_MODE_CBC)
+};
+
+UNICODE_STRING gcmChainingMode = {
+ sizeof(BCRYPT_CHAIN_MODE_GCM) - 2,
+ sizeof(BCRYPT_CHAIN_MODE_GCM),
+ const_cast<PWSTR>(BCRYPT_CHAIN_MODE_GCM)
+};
+
+/**
+ Determines which algorithms are not used by the requested ciphers to build
+ up a black list that can be passed to SCH_CREDENTIALS.
+ */
+QList<CRYPTO_SETTINGS> cryptoSettingsForCiphers(const QList<QSslCipher> &ciphers)
+{
+ static const QList<QSslCipher> defaultCipherList = defaultCiphers();
+
+ if (defaultCipherList == ciphers) {
+ // the ciphers have not been restricted for this session, so no black listing needed
+ return {};
+ }
+
+ QList<const SchannelCipherInfo*> cipherInfo;
+
+ for (const auto &cipher : ciphers) {
+ if (cipher.isNull())
+ continue;
+
+ const auto *info = cipherInfoByOpenSslName(cipher.name());
+ if (!cipherInfo.contains(info))
+ cipherInfo.append(info);
+ }
+
+ QList<CRYPTO_SETTINGS> cryptoSettings;
+
+ const auto assignUnicodeString = [](UNICODE_STRING &unicodeString, const wchar_t *characters) {
+ unicodeString.Length = static_cast<USHORT>(wcslen(characters) * sizeof(WCHAR));
+ unicodeString.MaximumLength = unicodeString.Length + sizeof(UNICODE_NULL);
+ unicodeString.Buffer = const_cast<wchar_t*>(characters);
+ };
+
+ // black list of key exchange algorithms
+ const auto allKeyExchangeAlgorithms = {BCRYPT_RSA_ALGORITHM,
+ BCRYPT_ECDH_ALGORITHM,
+ BCRYPT_DH_ALGORITHM};
+
+ for (const auto &algorithm : allKeyExchangeAlgorithms) {
+ const auto method = QStringView(algorithm);
+
+ const auto usesMethod = [method](const SchannelCipherInfo *info) {
+ return QLatin1StringView(info->keyExchangeMethod) == method;
+ };
+
+ const bool exclude = std::none_of(cipherInfo.cbegin(), cipherInfo.cend(), usesMethod);
+
+ if (exclude) {
+ CRYPTO_SETTINGS settings = {};
+ settings.eAlgorithmUsage = TlsParametersCngAlgUsageKeyExchange;
+ assignUnicodeString(settings.strCngAlgId, algorithm);
+ cryptoSettings.append(settings);
+ }
+ }
+
+ // black list of authentication algorithms
+ const auto allAuthenticationAlgorithms = {BCRYPT_RSA_ALGORITHM,
+ BCRYPT_DSA_ALGORITHM,
+ BCRYPT_ECDSA_ALGORITHM,
+ BCRYPT_DH_ALGORITHM};
+
+ for (const auto &algorithm : allAuthenticationAlgorithms) {
+ const auto method = QStringView(algorithm);
+
+ const auto usesMethod = [method](const SchannelCipherInfo *info) {
+ return QLatin1StringView(info->authenticationMethod) == method;
+ };
+
+ const bool exclude = std::none_of(cipherInfo.begin(), cipherInfo.end(), usesMethod);
+
+ if (exclude) {
+ CRYPTO_SETTINGS settings = {};
+ settings.eAlgorithmUsage = TlsParametersCngAlgUsageSignature;
+ assignUnicodeString(settings.strCngAlgId, algorithm);
+ cryptoSettings.append(settings);
+ }
+ }
+
+
+ // black list of encryption algorithms
+ const auto allEncryptionAlgorithms = {BCRYPT_AES_ALGORITHM,
+ BCRYPT_RC4_ALGORITHM,
+ BCRYPT_DES_ALGORITHM,
+ BCRYPT_3DES_ALGORITHM};
+
+ for (const auto &algorithm : allEncryptionAlgorithms) {
+ const auto method = QStringView(algorithm);
+
+ if (method == QLatin1StringView("AES")) {
+ bool uses128Bit = false;
+ bool uses256Bit = false;
+ bool usesGcm = false;
+ bool usesCbc = false;
+ for (const auto *info : cipherInfo) {
+ if (QLatin1StringView(info->encryptionMethod) == method) {
+ uses128Bit = uses128Bit || (info->encryptionBits == 128);
+ uses256Bit = uses256Bit || (info->encryptionBits == 256);
+ usesGcm = usesGcm ||
+ QLatin1StringView(info->schannelCipherSuite).contains("_GCM_"_L1);
+ usesCbc = usesCbc ||
+ QLatin1StringView(info->schannelCipherSuite).contains("_CBC_"_L1);
+ }
+ }
+
+ CRYPTO_SETTINGS settings = {};
+ settings.eAlgorithmUsage = TlsParametersCngAlgUsageCipher;
+ assignUnicodeString(settings.strCngAlgId, algorithm);
+
+ if (usesGcm && !usesCbc) {
+ settings.cChainingModes = 1;
+ settings.rgstrChainingModes = &cbcChainingMode;
+ } else if (!usesGcm && usesCbc) {
+ settings.cChainingModes = 1;
+ settings.rgstrChainingModes = &gcmChainingMode;
+ }
+
+ if (!uses128Bit && uses256Bit) {
+ settings.dwMinBitLength = 256;
+ cryptoSettings.append(settings);
+ } else if (uses128Bit && !uses256Bit) {
+ settings.dwMaxBitLength = 128;
+ cryptoSettings.append(settings);
+ } else if (!uses128Bit && !uses256Bit) {
+ cryptoSettings.append(settings);
+ }
+ } else {
+ const auto usesMethod = [method](const SchannelCipherInfo *info) {
+ return QLatin1StringView(info->encryptionMethod) == method;
+ };
+
+ const bool exclude = std::none_of(cipherInfo.begin(), cipherInfo.end(), usesMethod);
+
+ if (exclude) {
+ CRYPTO_SETTINGS settings = {};
+ settings.eAlgorithmUsage = TlsParametersCngAlgUsageCipher;
+ assignUnicodeString(settings.strCngAlgId, algorithm);
+ cryptoSettings.append(settings);
+ }
+ }
+ }
+
+ // black list of hash algorithms
+ const auto allHashAlgorithms = {BCRYPT_MD5_ALGORITHM,
+ BCRYPT_SHA1_ALGORITHM,
+ BCRYPT_SHA256_ALGORITHM,
+ BCRYPT_SHA384_ALGORITHM};
+
+ for (const auto &algorithm : allHashAlgorithms) {
+ const auto method = QStringView(algorithm);
+
+ const auto usesMethod = [method](const SchannelCipherInfo *info) {
+ return QLatin1StringView(info->hashMethod) == method;
+ };
+
+ const bool exclude = std::none_of(cipherInfo.begin(), cipherInfo.end(), usesMethod);
+
+ if (exclude) {
+ CRYPTO_SETTINGS settings = {};
+ settings.eAlgorithmUsage = TlsParametersCngAlgUsageDigest;
+ assignUnicodeString(settings.strCngAlgId, algorithm);
+ cryptoSettings.append(settings);
+ }
+ }
+
+ return cryptoSettings;
+}
+
+QList<QSslCipher> ciphersByName(QStringView schannelSuiteName)
{
- // Previously the code was in QSslSocketBackendPrivate.
QList<QSslCipher> ciphers;
- const QString protocolStrings[] = { QStringLiteral("TLSv1"), QStringLiteral("TLSv1.1"),
- QStringLiteral("TLSv1.2"), QStringLiteral("TLSv1.3") };
+
+ for (const auto &cipher : schannelCipherInfo) {
+ if (QLatin1StringView(cipher.schannelCipherSuite) == schannelSuiteName) {
+ for (const auto &protocol : cipher.protocols) {
QT_WARNING_PUSH
QT_WARNING_DISABLE_DEPRECATED
- const QSsl::SslProtocol protocols[] = { QSsl::TlsV1_0, QSsl::TlsV1_1,
- QSsl::TlsV1_2, QSsl::TlsV1_3 };
+ const QString protocolName = (
+ protocol == QSsl::TlsV1_0 ? QStringLiteral("TLSv1.0") :
+ protocol == QSsl::TlsV1_1 ? QStringLiteral("TLSv1.1") :
+ protocol == QSsl::TlsV1_2 ? QStringLiteral("TLSv1.2") :
+ protocol == QSsl::TlsV1_3 ? QStringLiteral("TLSv1.3") :
+ QString());
QT_WARNING_POP
- const int size = ARRAYSIZE(protocols);
- static_assert(size == ARRAYSIZE(protocolStrings));
- ciphers.reserve(size);
- for (int i = 0; i < size; ++i) {
- const QSslCipher cipher = QTlsBackend::createCipher(QStringLiteral("Schannel"),
- protocols[i], protocolStrings[i]);
- ciphers.append(cipher);
+ ciphers.append(QTlsBackend::createCiphersuite(QLatin1StringView(cipher.openSslCipherSuite),
+ QLatin1StringView(cipher.keyExchangeMethod),
+ QLatin1StringView(cipher.encryptionMethod),
+ QLatin1StringView(cipher.authenticationMethod),
+ cipher.encryptionBits,
+ protocol, protocolName));
+ }
+ }
}
return ciphers;
-
}
-} // namespace QTlsPrivate
+QList<QSslCipher> defaultCiphers()
+{
+ ULONG contextFunctionsCount = {};
+ PCRYPT_CONTEXT_FUNCTIONS contextFunctions = {};
-namespace {
-bool supportsTls13();
+ const auto status = BCryptEnumContextFunctions(CRYPT_LOCAL, L"SSL", NCRYPT_SCHANNEL_INTERFACE,
+ &contextFunctionsCount, &contextFunctions);
+ if (!NT_SUCCESS(status)) {
+ qCWarning(lcTlsBackendSchannel, "Failed to enumerate ciphers");
+ return {};
+ }
+
+ const bool supportsV13 = supportsTls13();
+
+ QList<QSslCipher> ciphers;
+
+ for (ULONG index = 0; index < contextFunctions->cFunctions; ++index) {
+ const auto suiteName = QStringView(contextFunctions->rgpszFunctions[index]);
+
+ const QList<QSslCipher> allCiphers = ciphersByName(suiteName);
+
+ for (const auto &cipher : allCiphers) {
+ if (!supportsV13 && (cipher.protocol() == QSsl::TlsV1_3))
+ continue;
+
+ ciphers.append(cipher);
+ }
+ }
+
+ BCryptFreeBuffer(contextFunctions);
+
+ return ciphers;
+}
+
+bool containsTls13Cipher(const QList<QSslCipher> &ciphers)
+{
+ return std::any_of(ciphers.cbegin(), ciphers.cend(),
+ [](const QSslCipher &cipher) { return cipher.protocol() == QSsl::TlsV1_3; });
}
+} // namespace QTlsPrivate
+
bool QSchannelBackend::s_loadedCiphersAndCerts = false;
Q_GLOBAL_STATIC(QRecursiveMutex, qt_schannel_mutex)
@@ -298,7 +596,11 @@ QList<QSslCertificate> QSchannelBackend::systemCaCertificatesImplementation()
// Similar to non-Darwin version found in qtlsbackend_openssl.cpp,
// QTlsPrivate::systemCaCertificates function.
QList<QSslCertificate> systemCerts;
- auto hSystemStore = QHCertStorePointer(CertOpenSystemStore(0, L"ROOT"));
+
+ auto hSystemStore = QHCertStorePointer(
+ CertOpenStore(CERT_STORE_PROV_SYSTEM, 0, 0,
+ CERT_STORE_READONLY_FLAG | CERT_SYSTEM_STORE_CURRENT_USER, L"ROOT"));
+
if (hSystemStore) {
PCCERT_CONTEXT pc = nullptr;
while ((pc = CertFindCertificateInStore(hSystemStore.get(), X509_ASN_ENCODING, 0,
@@ -319,6 +621,11 @@ QTlsPrivate::X509DerReaderPtr QSchannelBackend::X509DerReader() const
return QTlsPrivate::X509CertificateGeneric::certificatesFromDer;
}
+QTlsPrivate::X509Pkcs12ReaderPtr QSchannelBackend::X509Pkcs12Reader() const
+{
+ return QTlsPrivate::X509CertificateSchannel::importPkcs12;
+}
+
namespace {
SecBuffer createSecBuffer(void *ptr, unsigned long length, unsigned long bufferType)
@@ -383,7 +690,6 @@ QString schannelErrorToString(qint32 status)
bool supportsTls13()
{
-#ifdef SUPPORTS_TLS13
static bool supported = []() {
const auto current = QOperatingSystemVersion::current();
// 20221 just happens to be the preview version I run on my laptop where I tested TLS 1.3.
@@ -391,10 +697,8 @@ bool supportsTls13()
QOperatingSystemVersion(QOperatingSystemVersion::Windows, 10, 0, 20221);
return current >= minimum;
}();
+
return supported;
-#else
- return false;
-#endif
}
DWORD toSchannelProtocol(QSsl::SslProtocol protocol)
@@ -459,17 +763,15 @@ QT_WARNING_POP
return protocols;
}
-#ifdef SUPPORTS_TLS13
// In the new API that descended down upon us we are not asked which protocols we want
// but rather which protocols we don't want. So now we have this function to disable
// anything that is not enabled.
-DWORD toSchannelProtocolNegated(QSsl::SslProtocol protocol)
+DWORD negatedSchannelProtocols(DWORD wantedProtocols)
{
DWORD protocols = SP_PROT_ALL; // all protocols
- protocols &= ~toSchannelProtocol(protocol); // minus the one(s) we want
+ protocols &= ~wantedProtocols; // minus the one(s) we want
return protocols;
}
-#endif
/*!
\internal
@@ -507,8 +809,7 @@ bool netscapeWrongCertType(const QList<QSslCertificateExtension> &extensions, bo
const auto netscapeIt = std::find_if(
extensions.cbegin(), extensions.cend(),
[](const QSslCertificateExtension &extension) {
- const auto netscapeCertType = QStringLiteral("2.16.840.1.113730.1.1");
- return extension.oid() == netscapeCertType;
+ return extension.oid() == u"2.16.840.1.113730.1.1";
});
if (netscapeIt != extensions.cend()) {
const QByteArray netscapeCertTypeByte = netscapeIt->value().toByteArray();
@@ -679,6 +980,10 @@ qint64 checkIncompleteData(const SecBuffer &secBuffer)
return 0;
}
+DWORD defaultCredsFlag()
+{
+ return qEnvironmentVariableIsSet("QT_SCH_DEFAULT_CREDS") ? 0 : SCH_CRED_NO_DEFAULT_CREDS;
+}
} // anonymous namespace
@@ -718,6 +1023,10 @@ bool TlsCryptographSchannel::sendToken(void *token, unsigned long tokenLength, b
Q_ASSERT(d);
auto *plainSocket = d->plainTcpSocket();
Q_ASSERT(plainSocket);
+ if (plainSocket->state() == QAbstractSocket::UnconnectedState || !plainSocket->isValid()
+ || !plainSocket->isOpen()) {
+ return false;
+ }
const qint64 written = plainSocket->write(static_cast<const char *>(token), tokenLength);
if (written != qint64(tokenLength)) {
@@ -780,7 +1089,7 @@ bool TlsCryptographSchannel::acquireCredentialsHandle()
Q_ASSERT(schannelState == SchannelState::InitializeHandshake);
const bool isClient = d->tlsMode() == QSslSocket::SslClientMode;
- const DWORD protocols = toSchannelProtocol(configuration.protocol());
+ DWORD protocols = toSchannelProtocol(configuration.protocol());
if (protocols == DWORD(-1)) {
setErrorAndEmit(d, QAbstractSocket::SslInvalidUserDataError,
QSslSocket::tr("Invalid protocol chosen"));
@@ -834,80 +1143,50 @@ bool TlsCryptographSchannel::acquireCredentialsHandle()
certsCount = 1;
Q_ASSERT(localCertContext);
}
- void *credentials = nullptr;
-#ifdef SUPPORTS_TLS13
+
+ const QList<QSslCipher> ciphers = configuration.ciphers();
+ if (!ciphers.isEmpty() && !containsTls13Cipher(ciphers))
+ protocols &= ~SP_PROT_TLS1_3;
+
+ QList<CRYPTO_SETTINGS> cryptoSettings;
+ if (!ciphers.isEmpty())
+ cryptoSettings = cryptoSettingsForCiphers(ciphers);
+
TLS_PARAMETERS tlsParameters = {
0,
nullptr,
- toSchannelProtocolNegated(configuration.protocol()), // what protocols to disable
+ negatedSchannelProtocols(protocols), // what protocols to disable
+ static_cast<DWORD>(cryptoSettings.size()),
+ (cryptoSettings.isEmpty() ? nullptr : cryptoSettings.data()),
+ 0
+ };
+
+ SCH_CREDENTIALS credentials = {
+ SCH_CREDENTIALS_VERSION,
0,
+ certsCount,
+ &localCertContext,
nullptr,
- 0
+ 0,
+ nullptr,
+ 0,
+ SCH_CRED_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT | defaultCredsFlag(),
+ 1,
+ &tlsParameters
};
- if (supportsTls13()) {
- SCH_CREDENTIALS *cred = new SCH_CREDENTIALS{
- SCH_CREDENTIALS_VERSION,
- 0,
- certsCount,
- &localCertContext,
- nullptr,
- 0,
- nullptr,
- 0,
- SCH_CRED_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT
- | SCH_CRED_NO_DEFAULT_CREDS,
- 1,
- &tlsParameters
- };
- credentials = cred;
- } else
-#endif // SUPPORTS_TLS13
- {
- SCHANNEL_CRED *cred = new SCHANNEL_CRED{
- SCHANNEL_CRED_VERSION, // dwVersion
- certsCount, // cCreds
- &localCertContext, // paCred (certificate(s) containing a private key for authentication)
- nullptr, // hRootStore
-
- 0, // cMappers (reserved)
- nullptr, // aphMappers (reserved)
-
- 0, // cSupportedAlgs
- nullptr, // palgSupportedAlgs (nullptr = system default)
-
- protocols, // grbitEnabledProtocols
- 0, // dwMinimumCipherStrength (0 = system default)
- 0, // dwMaximumCipherStrength (0 = system default)
- 0, // dwSessionLifespan (0 = schannel default, 10 hours)
- SCH_CRED_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT
- | SCH_CRED_NO_DEFAULT_CREDS, // dwFlags
- 0 // dwCredFormat (must be 0)
- };
- credentials = cred;
- }
- Q_ASSERT(credentials != nullptr);
TimeStamp expiration{};
auto status = AcquireCredentialsHandle(nullptr, // pszPrincipal (unused)
const_cast<wchar_t *>(UNISP_NAME), // pszPackage
isClient ? SECPKG_CRED_OUTBOUND : SECPKG_CRED_INBOUND, // fCredentialUse
nullptr, // pvLogonID (unused)
- credentials, // pAuthData
+ &credentials, // pAuthData
nullptr, // pGetKeyFn (unused)
nullptr, // pvGetKeyArgument (unused)
&credentialHandle, // phCredential
&expiration // ptsExpir
);
-#ifdef SUPPORTS_TLS13
- if (supportsTls13()) {
- delete static_cast<SCH_CREDENTIALS *>(credentials);
- } else
-#endif // SUPPORTS_TLS13
- {
- delete static_cast<SCHANNEL_CRED *>(credentials);
- }
-
if (status != SEC_E_OK) {
setErrorAndEmit(d, QAbstractSocket::SslInternalError, schannelErrorToString(status));
return false;
@@ -1112,7 +1391,8 @@ bool TlsCryptographSchannel::performHandshake()
auto *plainSocket = d->plainTcpSocket();
Q_ASSERT(plainSocket);
- if (plainSocket->state() == QAbstractSocket::UnconnectedState) {
+ if (plainSocket->state() == QAbstractSocket::UnconnectedState || !plainSocket->isValid()
+ || !plainSocket->isOpen()) {
setErrorAndEmit(d, QAbstractSocket::RemoteHostClosedError,
QSslSocket::tr("The TLS/SSL connection has been closed"));
return false;
@@ -1281,6 +1561,11 @@ bool TlsCryptographSchannel::verifyHandshake()
// Get session cipher info
status = QueryContextAttributes(&contextHandle,
+ SECPKG_ATTR_CIPHER_INFO,
+ &cipherInfo);
+ CHECK_STATUS(status);
+
+ status = QueryContextAttributes(&contextHandle,
SECPKG_ATTR_CONNECTION_INFO,
&connectionInfo);
CHECK_STATUS(status);
@@ -1433,6 +1718,7 @@ void TlsCryptographSchannel::reset()
deallocateContext();
freeCredentialsHandle(); // in case we already had one (@future: session resumption requires re-use)
+ cipherInfo = {};
connectionInfo = {};
streamSizes = {};
@@ -1482,8 +1768,10 @@ void TlsCryptographSchannel::transmit()
return; // This function should not have been called
// Can happen if called through QSslSocket::abort->QSslSocket::close->QSslSocket::flush->here
- if (plainSocket->state() == QAbstractSocket::SocketState::UnconnectedState)
+ if (plainSocket->state() == QAbstractSocket::UnconnectedState || !plainSocket->isValid()
+ || !plainSocket->isOpen()) {
return;
+ }
if (schannelState != SchannelState::Done) {
continueHandshake();
@@ -1635,8 +1923,12 @@ void TlsCryptographSchannel::transmit()
qCWarning(lcTlsBackendSchannel, "The internal SSPI handle is invalid!");
Q_UNREACHABLE();
} else if (status == SEC_E_INVALID_TOKEN) {
- qCWarning(lcTlsBackendSchannel, "Got SEC_E_INVALID_TOKEN!");
- Q_UNREACHABLE(); // Happened once due to a bug, but shouldn't generally happen(?)
+ // Supposedly we have an invalid token, it's under-documented what
+ // this means, so to be safe we disconnect.
+ shutdown = true;
+ disconnectFromHost();
+ setErrorAndEmit(d, QAbstractSocket::SslInternalError, schannelErrorToString(status));
+ break;
} else if (status == SEC_E_MESSAGE_ALTERED) {
// The message has been altered, disconnect now.
shutdown = true; // skips sending the shutdown alert
@@ -1656,8 +1948,6 @@ void TlsCryptographSchannel::transmit()
} else if (status == SEC_I_CONTEXT_EXPIRED) {
// 'remote' has initiated a shutdown
disconnectFromHost();
- setErrorAndEmit(d, QAbstractSocket::RemoteHostClosedError,
- schannelErrorToString(status));
break;
} else if (status == SEC_I_RENEGOTIATE) {
// 'remote' wants to renegotiate
@@ -1809,8 +2099,17 @@ QSslCipher TlsCryptographSchannel::sessionCipher() const
Q_ASSERT(q);
if (!q->isEncrypted())
- return QSslCipher();
- return QSslCipher(QStringLiteral("Schannel"), sessionProtocol());
+ return {};
+
+ const auto sessionProtocol = toQtSslProtocol(connectionInfo.dwProtocol);
+
+ const auto ciphers = ciphersByName(QStringView(cipherInfo.szCipherSuite));
+ for (const auto& cipher : ciphers) {
+ if (cipher.protocol() == sessionProtocol)
+ return cipher;
+ }
+
+ return {};
}
QSsl::SslProtocol TlsCryptographSchannel::sessionProtocol() const
@@ -1994,7 +2293,10 @@ bool TlsCryptographSchannel::verifyCertContext(CERT_CONTEXT *certContext)
// the Ca list, not just included during verification.
// That being said, it's not trivial to add the root certificates (if and only if they
// came from the system root store). And I don't see this mentioned in our documentation.
- auto rootStore = QHCertStorePointer(CertOpenSystemStore(0, L"ROOT"));
+ auto rootStore = QHCertStorePointer(
+ CertOpenStore(CERT_STORE_PROV_SYSTEM, 0, 0,
+ CERT_STORE_READONLY_FLAG | CERT_SYSTEM_STORE_CURRENT_USER, L"ROOT"));
+
if (!rootStore) {
#ifdef QSSLSOCKET_DEBUG
qCWarning(lcTlsBackendSchannel, "Failed to open the system root CA certificate store!");
@@ -2108,10 +2410,40 @@ bool TlsCryptographSchannel::verifyCertContext(CERT_CONTEXT *certContext)
verifyDepth = DWORD(q->peerVerifyDepth());
const auto &caCertificates = q->sslConfiguration().caCertificates();
+
+ if (!rootCertOnDemandLoadingAllowed()
+ && !(chain->TrustStatus.dwErrorStatus & CERT_TRUST_IS_PARTIAL_CHAIN)
+ && (q->peerVerifyMode() == QSslSocket::VerifyPeer
+ || (isClient && q->peerVerifyMode() == QSslSocket::AutoVerifyPeer))) {
+ // When verifying a peer Windows "helpfully" builds a chain that
+ // may include roots from the system store. But we don't want that if
+ // the user has set their own CA certificates.
+ // Since Windows claims this is not a partial chain the root is included
+ // and we have to check that it is one of our configured CAs.
+ CERT_CHAIN_ELEMENT *element = chain->rgpElement[chain->cElement - 1];
+ QSslCertificate certificate = getCertificateFromChainElement(element);
+ if (!caCertificates.contains(certificate)) {
+ auto error = QSslError(QSslError::CertificateUntrusted, certificate);
+ sslErrors += error;
+ emit q->peerVerifyError(error);
+ if (q->state() != QAbstractSocket::ConnectedState)
+ return false;
+ }
+ }
+
QList<QSslCertificate> peerCertificateChain;
for (DWORD i = 0; i < verifyDepth; i++) {
CERT_CHAIN_ELEMENT *element = chain->rgpElement[i];
QSslCertificate certificate = getCertificateFromChainElement(element);
+ if (certificate.isNull()) {
+ const auto &previousCert = !peerCertificateChain.isEmpty() ? peerCertificateChain.last()
+ : QSslCertificate();
+ auto error = QSslError(QSslError::SslError::UnableToGetIssuerCertificate, previousCert);
+ sslErrors += error;
+ emit q->peerVerifyError(error);
+ if (previousCert.isNull() || q->state() != QAbstractSocket::ConnectedState)
+ return false;
+ }
const QList<QSslCertificateExtension> extensions = certificate.extensions();
#ifdef QSSLSOCKET_DEBUG
@@ -2259,7 +2591,7 @@ bool TlsCryptographSchannel::verifyCertContext(CERT_CONTEXT *certContext)
}
if (!peerCertificateChain.isEmpty())
- QTlsBackend::storePeerCertificate(d, peerCertificateChain.first());
+ QTlsBackend::storePeerCertificate(d, peerCertificateChain.constFirst());
const auto &configuration = q->sslConfiguration(); // Probably, updated by QTlsBackend::storePeerCertificate etc.
// @Note: Somewhat copied from qsslsocket_mac.cpp
diff --git a/src/plugins/tls/schannel/qtls_schannel_p.h b/src/plugins/tls/schannel/qtls_schannel_p.h
index f6bfdbc7cf..fab8777249 100644
--- a/src/plugins/tls/schannel/qtls_schannel_p.h
+++ b/src/plugins/tls/schannel/qtls_schannel_p.h
@@ -93,6 +93,7 @@ private:
QSslSocket *q = nullptr;
QSslSocketPrivate *d = nullptr;
+ SecPkgContext_CipherInfo cipherInfo = {};
SecPkgContext_ConnectionInfo connectionInfo = {};
SecPkgContext_StreamSizes streamSizes = {};
diff --git a/src/plugins/tls/schannel/qtlsbackend_schannel_p.h b/src/plugins/tls/schannel/qtlsbackend_schannel_p.h
index a70f8922a6..7c2d675e79 100644
--- a/src/plugins/tls/schannel/qtlsbackend_schannel_p.h
+++ b/src/plugins/tls/schannel/qtlsbackend_schannel_p.h
@@ -57,6 +57,7 @@ private:
QTlsPrivate::X509PemReaderPtr X509PemReader() const override;
QTlsPrivate::X509DerReaderPtr X509DerReader() const override;
+ QTlsPrivate::X509Pkcs12ReaderPtr X509Pkcs12Reader() const override;
static bool s_loadedCiphersAndCerts;
};
diff --git a/src/plugins/tls/schannel/qx509_schannel.cpp b/src/plugins/tls/schannel/qx509_schannel.cpp
index 5a5e625268..d9d82dce29 100644
--- a/src/plugins/tls/schannel/qx509_schannel.cpp
+++ b/src/plugins/tls/schannel/qx509_schannel.cpp
@@ -1,9 +1,11 @@
// Copyright (C) 2021 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+#include "qtlsbackend_schannel_p.h"
#include "qtlskey_schannel_p.h"
#include "qx509_schannel_p.h"
+#include <QtCore/private/qsystemerror_p.h>
#include <QtNetwork/private/qsslcertificate_p.h>
#include <memory>
@@ -39,13 +41,173 @@ QSslCertificate X509CertificateSchannel::QSslCertificate_from_CERT_CONTEXT(const
QByteArray derData = QByteArray((const char *)certificateContext->pbCertEncoded,
certificateContext->cbCertEncoded);
QSslCertificate certificate(derData, QSsl::Der);
-
- auto *certBackend = QTlsBackend::backend<X509CertificateSchannel>(certificate);
- Q_ASSERT(certBackend);
- certBackend->certificateContext = CertDuplicateCertificateContext(certificateContext);
+ if (!certificate.isNull()) {
+ auto *certBackend = QTlsBackend::backend<X509CertificateSchannel>(certificate);
+ Q_ASSERT(certBackend);
+ certBackend->certificateContext = CertDuplicateCertificateContext(certificateContext);
+ }
return certificate;
}
+bool X509CertificateSchannel::importPkcs12(QIODevice *device, QSslKey *key, QSslCertificate *cert,
+ QList<QSslCertificate> *caCertificates,
+ const QByteArray &passPhrase)
+{
+ // These are required
+ Q_ASSERT(device);
+ Q_ASSERT(key);
+ Q_ASSERT(cert);
+
+ QByteArray pkcs12data = device->readAll();
+ if (pkcs12data.size() == 0)
+ return false;
+
+ CRYPT_DATA_BLOB dataBlob;
+ dataBlob.cbData = pkcs12data.size();
+ dataBlob.pbData = reinterpret_cast<BYTE*>(pkcs12data.data());
+
+ const auto password = QString::fromUtf8(passPhrase);
+
+ const DWORD flags = (CRYPT_EXPORTABLE | PKCS12_NO_PERSIST_KEY | PKCS12_PREFER_CNG_KSP);
+
+ auto certStore = QHCertStorePointer(PFXImportCertStore(&dataBlob,
+ reinterpret_cast<LPCWSTR>(password.utf16()),
+ flags));
+
+ if (!certStore) {
+ qCWarning(lcTlsBackendSchannel, "Failed to import PFX data: %s",
+ qPrintable(QSystemError::windowsString()));
+ return false;
+ }
+
+ // first extract the certificate with the private key
+ const auto certContext = QPCCertContextPointer(CertFindCertificateInStore(certStore.get(),
+ X509_ASN_ENCODING |
+ PKCS_7_ASN_ENCODING,
+ 0,
+ CERT_FIND_HAS_PRIVATE_KEY,
+ nullptr, nullptr));
+
+ if (!certContext) {
+ qCWarning(lcTlsBackendSchannel, "Failed to find certificate in PFX store: %s",
+ qPrintable(QSystemError::windowsString()));
+ return false;
+ }
+
+ *cert = QSslCertificate_from_CERT_CONTEXT(certContext.get());
+
+ // retrieve the private key for the certificate
+ NCRYPT_KEY_HANDLE keyHandle = {};
+ DWORD keyHandleSize = sizeof(keyHandle);
+ if (!CertGetCertificateContextProperty(certContext.get(), CERT_NCRYPT_KEY_HANDLE_PROP_ID,
+ &keyHandle, &keyHandleSize)) {
+ qCWarning(lcTlsBackendSchannel, "Failed to find private key handle in certificate context: %s",
+ qPrintable(QSystemError::windowsString()));
+ return false;
+ }
+
+ SECURITY_STATUS securityStatus = ERROR_SUCCESS;
+
+ // we need the 'NCRYPT_ALLOW_PLAINTEXT_EXPORT_FLAG' to make NCryptExportKey succeed
+ DWORD policy = (NCRYPT_ALLOW_EXPORT_FLAG | NCRYPT_ALLOW_PLAINTEXT_EXPORT_FLAG);
+ DWORD policySize = sizeof(policy);
+
+ securityStatus = NCryptSetProperty(keyHandle, NCRYPT_EXPORT_POLICY_PROPERTY,
+ reinterpret_cast<BYTE*>(&policy), policySize, 0);
+ if (securityStatus != ERROR_SUCCESS) {
+ qCWarning(lcTlsBackendSchannel, "Failed to update export policy of private key: 0x%x",
+ static_cast<unsigned int>(securityStatus));
+ return false;
+ }
+
+ DWORD blobSize = {};
+ securityStatus = NCryptExportKey(keyHandle, {}, BCRYPT_RSAFULLPRIVATE_BLOB,
+ nullptr, nullptr, 0, &blobSize, 0);
+ if (securityStatus != ERROR_SUCCESS) {
+ qCWarning(lcTlsBackendSchannel, "Failed to retrieve private key size: 0x%x",
+ static_cast<unsigned int>(securityStatus));
+ return false;
+ }
+
+ std::vector<BYTE> blob(blobSize);
+ securityStatus = NCryptExportKey(keyHandle, {}, BCRYPT_RSAFULLPRIVATE_BLOB,
+ nullptr, blob.data(), blobSize, &blobSize, 0);
+ if (securityStatus != ERROR_SUCCESS) {
+ qCWarning(lcTlsBackendSchannel, "Failed to retrieve private key from certificate: 0x%x",
+ static_cast<unsigned int>(securityStatus));
+ return false;
+ }
+
+ DWORD privateKeySize = {};
+
+ if (!CryptEncodeObject(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, CNG_RSA_PRIVATE_KEY_BLOB,
+ blob.data(), nullptr, &privateKeySize)) {
+ qCWarning(lcTlsBackendSchannel, "Failed to encode private key to key info: %s",
+ qPrintable(QSystemError::windowsString()));
+ return false;
+ }
+
+ std::vector<BYTE> privateKeyData(privateKeySize);
+
+ CRYPT_PRIVATE_KEY_INFO privateKeyInfo = {};
+ privateKeyInfo.Algorithm.pszObjId = const_cast<PSTR>(szOID_RSA_RSA);
+ privateKeyInfo.PrivateKey.cbData = privateKeySize;
+ privateKeyInfo.PrivateKey.pbData = privateKeyData.data();
+
+ if (!CryptEncodeObject(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
+ CNG_RSA_PRIVATE_KEY_BLOB, blob.data(),
+ privateKeyInfo.PrivateKey.pbData, &privateKeyInfo.PrivateKey.cbData)) {
+ qCWarning(lcTlsBackendSchannel, "Failed to encode private key to key info: %s",
+ qPrintable(QSystemError::windowsString()));
+ return false;
+ }
+
+
+ DWORD derSize = {};
+
+ if (!CryptEncodeObject(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, PKCS_PRIVATE_KEY_INFO,
+ &privateKeyInfo, nullptr, &derSize)) {
+ qCWarning(lcTlsBackendSchannel, "Failed to encode key info to DER format: %s",
+ qPrintable(QSystemError::windowsString()));
+
+ return false;
+ }
+
+ QByteArray derData(derSize, Qt::Uninitialized);
+
+ if (!CryptEncodeObject(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, PKCS_PRIVATE_KEY_INFO,
+ &privateKeyInfo, reinterpret_cast<BYTE*>(derData.data()), &derSize)) {
+ qCWarning(lcTlsBackendSchannel, "Failed to encode key info to DER format: %s",
+ qPrintable(QSystemError::windowsString()));
+
+ return false;
+ }
+
+ *key = QSslKey(derData, QSsl::Rsa, QSsl::Der, QSsl::PrivateKey);
+ if (key->isNull()) {
+ qCWarning(lcTlsBackendSchannel, "Failed to parse private key from DER format");
+ return false;
+ }
+
+ // fetch all the remaining certificates as CA certificates
+ if (caCertificates) {
+ PCCERT_CONTEXT caCertContext = nullptr;
+ while ((caCertContext = CertFindCertificateInStore(certStore.get(),
+ X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
+ 0, CERT_FIND_ANY, nullptr, caCertContext))) {
+ if (CertCompareCertificate(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
+ certContext->pCertInfo, caCertContext->pCertInfo))
+ continue; // ignore the certificate with private key
+
+ auto caCertificate = QSslCertificate_from_CERT_CONTEXT(caCertContext);
+
+ caCertificates->append(caCertificate);
+ }
+ }
+
+ return true;
+}
+
} // namespace QTlsPrivate
QT_END_NAMESPACE
diff --git a/src/plugins/tls/schannel/qx509_schannel_p.h b/src/plugins/tls/schannel/qx509_schannel_p.h
index 17b91983f2..4625c9584c 100644
--- a/src/plugins/tls/schannel/qx509_schannel_p.h
+++ b/src/plugins/tls/schannel/qx509_schannel_p.h
@@ -36,6 +36,10 @@ public:
Qt::HANDLE handle() const override;
static QSslCertificate QSslCertificate_from_CERT_CONTEXT(const CERT_CONTEXT *certificateContext);
+
+ static bool importPkcs12(QIODevice *device, QSslKey *key, QSslCertificate *cert,
+ QList<QSslCertificate> *caCertificates,
+ const QByteArray &passPhrase);
private:
const CERT_CONTEXT *certificateContext = nullptr;
diff --git a/src/plugins/tls/securetransport/CMakeLists.txt b/src/plugins/tls/securetransport/CMakeLists.txt
index 2d504cc1bf..bb560229e8 100644
--- a/src/plugins/tls/securetransport/CMakeLists.txt
+++ b/src/plugins/tls/securetransport/CMakeLists.txt
@@ -1,5 +1,5 @@
# Copyright (C) 2022 The Qt Company Ltd.
-# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+# SPDX-License-Identifier: BSD-3-Clause
qt_internal_add_plugin(QSecureTransportBackendPlugin
OUTPUT_NAME qsecuretransportbackend
diff --git a/src/plugins/tls/securetransport/qtls_st.cpp b/src/plugins/tls/securetransport/qtls_st.cpp
index 39e2671692..48b7f3364f 100644
--- a/src/plugins/tls/securetransport/qtls_st.cpp
+++ b/src/plugins/tls/securetransport/qtls_st.cpp
@@ -373,6 +373,7 @@ void TlsCryptographSecureTransport::disconnectFromHost()
if (context) {
if (!shutdown) {
SSLClose(context);
+ context.reset(nullptr);
shutdown = true;
}
}
@@ -1075,7 +1076,7 @@ bool TlsCryptographSecureTransport::verifyPeerTrust()
QTlsBackend::storePeerCertificate(d, peerCertificateChain.at(0));
// Check the whole chain for blacklisting (including root, as we check for subjectInfo and issuer):
- for (const QSslCertificate &cert : qAsConst(peerCertificateChain)) {
+ for (const QSslCertificate &cert : std::as_const(peerCertificateChain)) {
if (QSslCertificatePrivate::isBlacklisted(cert) && !canIgnoreVerify) {
const QSslError error(QSslError::CertificateBlacklisted, cert);
errors << error;
@@ -1126,8 +1127,6 @@ bool TlsCryptographSecureTransport::verifyPeerTrust()
QCFType<CFDataRef> certData = cert.toDer().toCFData();
if (QCFType<SecCertificateRef> secRef = SecCertificateCreateWithData(nullptr, certData))
CFArrayAppendValue(certArray, secRef);
- else
- qCWarning(lcSecureTransport, "Failed to create SecCertificate from QSslCertificate");
}
SecTrustSetAnchorCertificates(trust, certArray);
diff --git a/src/plugins/tls/securetransport/qtls_st_p.h b/src/plugins/tls/securetransport/qtls_st_p.h
index 16f7104e67..2903ef4815 100644
--- a/src/plugins/tls/securetransport/qtls_st_p.h
+++ b/src/plugins/tls/securetransport/qtls_st_p.h
@@ -27,6 +27,11 @@
#include <QtNetwork/qabstractsocket.h>
#include <QtNetwork/private/qsslsocket_p.h>
+#warning SecureTransport was deprecated in macOS 10.15 and iOS 13, \
+and is no longer supported. We should be using Network.framework instead. \
+See QTBUG-85231 for more information.
+QT_WARNING_DISABLE_DEPRECATED
+
#include <Security/Security.h>
#include <Security/SecureTransport.h>
diff --git a/src/plugins/tls/securetransport/qtlsbackend_st.cpp b/src/plugins/tls/securetransport/qtlsbackend_st.cpp
index 4c385b5af6..54e45d1720 100644
--- a/src/plugins/tls/securetransport/qtlsbackend_st.cpp
+++ b/src/plugins/tls/securetransport/qtlsbackend_st.cpp
@@ -357,3 +357,5 @@ QTlsPrivate::TlsCryptograph *QSecureTransportBackend::createTlsCryptograph() con
QT_END_NAMESPACE
+
+#include "moc_qtlsbackend_st_p.cpp"
diff --git a/src/plugins/tls/shared/qasn1element.cpp b/src/plugins/tls/shared/qasn1element.cpp
index cbf038f569..97be46866d 100644
--- a/src/plugins/tls/shared/qasn1element.cpp
+++ b/src/plugins/tls/shared/qasn1element.cpp
@@ -6,13 +6,17 @@
#include <QtCore/qdatastream.h>
#include <QtCore/qdatetime.h>
+#include <QtCore/qtimezone.h>
#include <QtCore/qlist.h>
#include <QDebug>
+#include <private/qtools_p.h>
#include <limits>
QT_BEGIN_NAMESPACE
+using namespace QtMiscUtils;
+
typedef QMap<QByteArray, QByteArray> OidNameMap;
static OidNameMap createOidMap()
{
@@ -213,11 +217,6 @@ QDateTime QAsn1Element::toDateTime() const
// QDateTime::fromString is lenient and accepts +- signs in front
// of the year; but ASN.1 doesn't allow them.
- const auto isAsciiDigit = [](char c)
- {
- return c >= '0' && c <= '9';
- };
-
if (!isAsciiDigit(mValue[0]))
return result;
@@ -226,31 +225,28 @@ QDateTime QAsn1Element::toDateTime() const
return result;
if (mType == UtcTimeType && mValue.size() == 13) {
- result = QDateTime::fromString(QString::fromLatin1(mValue),
- QStringLiteral("yyMMddHHmmsst"));
- if (!result.isValid())
- return result;
-
- Q_ASSERT(result.timeSpec() == Qt::UTC);
-
- QDate date = result.date();
-
// RFC 2459:
// Where YY is greater than or equal to 50, the year shall be
// interpreted as 19YY; and
//
// Where YY is less than 50, the year shall be interpreted as 20YY.
//
- // QDateTime interprets the 'yy' format as 19yy, so we may need to adjust
- // the year (bring it in the [1950, 2049] range).
- if (date.year() < 1950)
- result.setDate(date.addYears(100));
+ // so use 1950 as base year.
+ constexpr int rfc2459CenturyStart = 1950;
+ const QLatin1StringView inputView(mValue);
+ QDate date = QDate::fromString(inputView.first(6), u"yyMMdd", rfc2459CenturyStart);
+ if (!date.isValid())
+ return result;
- Q_ASSERT(result.date().year() >= 1950);
- Q_ASSERT(result.date().year() <= 2049);
+ Q_ASSERT(date.year() >= rfc2459CenturyStart);
+ Q_ASSERT(date.year() < 100 + rfc2459CenturyStart);
+
+ QTime time = QTime::fromString(inputView.sliced(6, 6), u"HHmmss");
+ if (!time.isValid())
+ return result;
+ result = QDateTime(date, time, QTimeZone::UTC);
} else if (mType == GeneralizedTimeType && mValue.size() == 15) {
- result = QDateTime::fromString(QString::fromLatin1(mValue),
- QStringLiteral("yyyyMMddHHmmsst"));
+ result = QDateTime::fromString(QString::fromLatin1(mValue), u"yyyyMMddHHmmsst");
}
return result;
diff --git a/src/plugins/tls/shared/qsslsocket_mac_shared.cpp b/src/plugins/tls/shared/qsslsocket_mac_shared.cpp
index f40d2fb770..1257240ee2 100644
--- a/src/plugins/tls/shared/qsslsocket_mac_shared.cpp
+++ b/src/plugins/tls/shared/qsslsocket_mac_shared.cpp
@@ -6,6 +6,7 @@
#include <QtNetwork/qsslcertificate.h>
+#include <QtCore/qloggingcategory.h>
#include <QtCore/qglobal.h>
#include <QtCore/qdebug.h>
@@ -21,6 +22,8 @@
QT_BEGIN_NAMESPACE
+Q_LOGGING_CATEGORY(lcX509, "qt.mac.shared.x509");
+
#ifdef Q_OS_MACOS
namespace {
@@ -74,6 +77,52 @@ bool isCaCertificateTrusted(SecCertificateRef cfCert, int domain)
return false;
}
+bool canDERBeParsed(CFDataRef derData, const QSslCertificate &qtCert)
+{
+ // We are observing certificates, that while accepted when we copy them
+ // from the keychain(s), later give us 'Failed to create SslCertificate
+ // from QSslCertificate'. It's interesting to know at what step the failure
+ // occurred. Let's check it and skip it below if it's not valid.
+
+ auto checkDer = [](CFDataRef derData, const char *source)
+ {
+ Q_ASSERT(source);
+ Q_ASSERT(derData);
+
+ const auto cfLength = CFDataGetLength(derData);
+ if (cfLength <= 0) {
+ qCWarning(lcX509) << source << "returned faulty DER data with invalid length.";
+ return false;
+ }
+
+ QCFType<SecCertificateRef> secRef = SecCertificateCreateWithData(nullptr, derData);
+ if (!secRef) {
+ qCWarning(lcX509) << source << "returned faulty DER data which cannot be parsed back.";
+ return false;
+ }
+ return true;
+ };
+
+ if (!checkDer(derData, "SecCertificateCopyData")) {
+ qCDebug(lcX509) << "Faulty QSslCertificate is:" << qtCert;// Just in case we managed to parse something.
+ return false;
+ }
+
+ // Generic parser failed?
+ if (qtCert.isNull()) {
+ qCWarning(lcX509, "QSslCertificate failed to parse DER");
+ return false;
+ }
+
+ const QCFType<CFDataRef> qtDerData = qtCert.toDer().toCFData();
+ if (!checkDer(qtDerData, "QSslCertificate")) {
+ qCWarning(lcX509) << "Faulty QSslCertificate is:" << qtCert;
+ return false;
+ }
+
+ return true;
+}
+
} // unnamed namespace
#endif // Q_OS_MACOS
@@ -94,8 +143,19 @@ QList<QSslCertificate> systemCaCertificates()
SecCertificateRef cfCert = (SecCertificateRef)CFArrayGetValueAtIndex(cfCerts, i);
QCFType<CFDataRef> derData = SecCertificateCopyData(cfCert);
if (isCaCertificateTrusted(cfCert, dom)) {
- if (derData)
- systemCerts << QSslCertificate(QByteArray::fromCFData(derData), QSsl::Der);
+ if (derData) {
+ const auto newCert = QSslCertificate(QByteArray::fromCFData(derData), QSsl::Der);
+ if (!canDERBeParsed(derData, newCert)) {
+ // Last attempt to get some information about the certificate:
+ CFShow(cfCert);
+ continue;
+ }
+ systemCerts << newCert;
+ } else {
+ // "Returns NULL if the data passed in the certificate parameter
+ // is not a valid certificate object."
+ qCWarning(lcX509, "SecCertificateCopyData returned invalid DER data (nullptr).");
+ }
}
}
}
diff --git a/src/plugins/tls/shared/qwincrypt_p.h b/src/plugins/tls/shared/qwincrypt_p.h
index 1b1f0f16c0..48ca4247fa 100644
--- a/src/plugins/tls/shared/qwincrypt_p.h
+++ b/src/plugins/tls/shared/qwincrypt_p.h
@@ -40,6 +40,16 @@ struct QHCertStoreDeleter {
// A simple RAII type used by Schannel code and Window CA fetcher class:
using QHCertStorePointer = std::unique_ptr<void, QHCertStoreDeleter>;
+struct QPCCertContextDeleter {
+ void operator()(PCCERT_CONTEXT context) const
+ {
+ CertFreeCertificateContext(context);
+ }
+};
+
+// A simple RAII type used by Schannel code
+using QPCCertContextPointer = std::unique_ptr<const CERT_CONTEXT, QPCCertContextDeleter>;
+
QT_END_NAMESPACE
#endif // QWINCRYPT_P_H
diff --git a/src/plugins/tls/shared/qx509_generic.cpp b/src/plugins/tls/shared/qx509_generic.cpp
index cfe2786680..5006db1a72 100644
--- a/src/plugins/tls/shared/qx509_generic.cpp
+++ b/src/plugins/tls/shared/qx509_generic.cpp
@@ -118,7 +118,7 @@ QList<QSslCertificate> X509CertificateGeneric::certificatesFromPem(const QByteAr
QByteArray decoded = QByteArray::fromBase64(
QByteArray::fromRawData(pem.data() + startPos, endPos - startPos));
- certificates << certificatesFromDer(decoded, 1);;
+ certificates << certificatesFromDer(decoded, 1);
}
return certificates;
@@ -188,7 +188,7 @@ bool X509CertificateGeneric::parse(const QByteArray &data)
if (!elem.read(certStream) || elem.type() != QAsn1Element::SequenceType)
return false;
- QByteArray issuerDer = data.mid(dataStream.device()->pos() - elem.value().length(), elem.value().length());
+ QByteArray issuerDer = data.mid(dataStream.device()->pos() - elem.value().size(), elem.value().size());
issuerInfoEntries = elem.toInfo();
// validity period
@@ -215,7 +215,7 @@ bool X509CertificateGeneric::parse(const QByteArray &data)
if (!elem.read(certStream) || elem.type() != QAsn1Element::SequenceType)
return false;
- QByteArray subjectDer = data.mid(dataStream.device()->pos() - elem.value().length(), elem.value().length());
+ QByteArray subjectDer = data.mid(dataStream.device()->pos() - elem.value().size(), elem.value().size());
subjectInfoEntries = elem.toInfo();
subjectMatchesIssuer = issuerDer == subjectDer;
@@ -285,7 +285,7 @@ bool X509CertificateGeneric::parse(const QByteArray &data)
case QAsn1Element::IpAddressType: {
QHostAddress ipAddress;
QByteArray ipAddrValue = nameElem.value();
- switch (ipAddrValue.length()) {
+ switch (ipAddrValue.size()) {
case 4: // IPv4
ipAddress = QHostAddress(qFromBigEndian(*reinterpret_cast<quint32 *>(ipAddrValue.data())));
break;
diff --git a/src/plugins/tracing/CMakeLists.txt b/src/plugins/tracing/CMakeLists.txt
new file mode 100644
index 0000000000..823e11c174
--- /dev/null
+++ b/src/plugins/tracing/CMakeLists.txt
@@ -0,0 +1,32 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+function(make_includable input_file output_file)
+ get_filename_component(infile "${CMAKE_CURRENT_SOURCE_DIR}/${input_file}" ABSOLUTE)
+ set(outfile ${CMAKE_CURRENT_BINARY_DIR}/${output_file})
+ file(READ ${infile} content)
+ set(content "R\"(${content})\"")
+ file(WRITE ${outfile} "${content}")
+endfunction(make_includable)
+
+make_includable(metadata_template.txt metadata_template.h)
+
+qt_internal_add_plugin(QCtfTracePlugin
+ CLASS_NAME QCtfTracePlugin
+ PLUGIN_TYPE tracing
+ SOURCES
+ qctflib_p.h qctflib.cpp metadata_template.txt qctfplugin.cpp qctfplugin_p.h
+ qctfserver_p.h qctfserver.cpp
+ LIBRARIES
+ Qt::Core Qt::CorePrivate Qt::Network
+)
+
+qt_internal_extend_target(QCtfTracePlugin CONDITION QT_FEATURE_zstd
+ LIBRARIES
+ WrapZSTD::WrapZSTD
+)
+
+qt_internal_extend_target(QCtfTracePlugin CONDITION (QT_FEATURE_cxx17_filesystem) AND (GCC AND (QMAKE_GCC_MAJOR_VERSION LESS 9))
+ LINK_OPTIONS
+ "-lstdc++fs"
+)
diff --git a/src/plugins/tracing/metadata_template.txt b/src/plugins/tracing/metadata_template.txt
new file mode 100644
index 0000000000..5f27e79da5
--- /dev/null
+++ b/src/plugins/tracing/metadata_template.txt
@@ -0,0 +1,77 @@
+/* CTF 1.8 */
+
+typealias integer { size = 8; align = 8; signed = false; } := uint8_t;
+typealias integer { size = 16; align = 8; signed = false; } := uint16_t;
+typealias integer { size = 32; align = 8; signed = false; } := uint32_t;
+typealias integer { size = 64; align = 8; signed = false; } := uint64_t;
+typealias integer { size = 8; align = 8; signed = true; } := int8_t;
+typealias integer { size = 16; align = 8; signed = true; } := int16_t;
+typealias integer { size = 32; align = 8; signed = true; } := int32_t;
+typealias integer { size = 64; align = 8; signed = true; } := int64_t;
+typealias integer { size = 32; align = 8; signed = true; base = 16; } := intptr32_t;
+typealias integer { size = 64; align = 8; signed = true; base = 16; } := intptr64_t;
+typealias floating_point { exp_dig = 8; mant_dig = 24; align = 8; byte_order = native; } := float;
+typealias floating_point { exp_dig = 11; mant_dig = 53; align = 8; byte_order = native; } := double;
+
+typealias enum : integer { size = 8; } {
+ false,
+ true
+} := Boolean;
+
+trace {
+ major = 1;
+ minor = 8;
+ uuid = "$TRACE_UUID";
+ byte_order = $ENDIANNESS;
+ packet.header := struct {
+ uint32_t magic;
+ uint8_t uuid[16];
+ uint32_t stream_id;
+ } align(8);
+};
+
+env {
+ domain = "ust";
+ tracer_name = "qtctf";
+ tracer_major = 1;
+ tracer_minor = 0;
+ architecture_bit_width = $ARC_BIT_WIDTH;
+ trace_name = "$SESSION_NAME";
+ trace_creation_datetime = "$CREATION_TIME";
+ hostname = "$HOST_NAME";
+};
+
+clock {
+ name = "$CLOCK_NAME";
+ uuid = "53836526-5a62-4de0-93c8-3a1970afab23";
+ description = "$CLOCK_TYPE";
+ freq = $CLOCK_FREQUENCY;
+ offset = $CLOCK_OFFSET;
+};
+
+typealias integer {
+ size = 64; align = 8; signed = false;
+ map = clock.monotonic.value;
+} := uint64_clock_monotonic_t;
+
+struct packet_context {
+ uint64_clock_monotonic_t timestamp_begin;
+ uint64_clock_monotonic_t timestamp_end;
+ uint64_t content_size;
+ uint64_t packet_size;
+ uint64_t packet_seq_num;
+ uint64_t events_discarded;
+ uint32_t thread_id;
+ string thread_name;
+} align(8);
+
+struct event_header {
+ uint32_t id;
+ uint64_clock_monotonic_t timestamp;
+} align(8);
+
+stream {
+ id = 0;
+ event.header := struct event_header;
+ packet.context := struct packet_context;
+};
diff --git a/src/plugins/tracing/qctflib.cpp b/src/plugins/tracing/qctflib.cpp
new file mode 100644
index 0000000000..fe3946d27c
--- /dev/null
+++ b/src/plugins/tracing/qctflib.cpp
@@ -0,0 +1,447 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+#define BUILD_LIBRARY
+#include <qstring.h>
+#include <qthread.h>
+#include <stdio.h>
+#include <qjsondocument.h>
+#include <qjsonarray.h>
+#include <qjsonobject.h>
+#include <qfileinfo.h>
+#include <qrect.h>
+#include <qsize.h>
+#include <qmetaobject.h>
+#include <qendian.h>
+#include <qplatformdefs.h>
+#include "qctflib_p.h"
+
+#if QT_CONFIG(cxx17_filesystem)
+#include <filesystem>
+#endif
+
+QT_BEGIN_NAMESPACE
+
+using namespace Qt::StringLiterals;
+
+Q_LOGGING_CATEGORY(lcDebugTrace, "qt.core.ctf", QtWarningMsg)
+
+static const size_t packetHeaderSize = 24 + 6 * 8 + 4;
+static const size_t packetSize = 4096;
+
+static const char traceMetadataTemplate[] =
+#include "metadata_template.h"
+;
+static const size_t traceMetadataSize = sizeof(traceMetadataTemplate);
+
+static inline QString allLiteral() { return QStringLiteral("all"); }
+static inline QString defaultLiteral() { return QStringLiteral("default"); }
+
+
+template <typename T>
+static QByteArray &operator<<(QByteArray &arr, T val)
+{
+ static_assert(std::is_arithmetic_v<T>);
+ arr.append(reinterpret_cast<char *>(&val), sizeof(val));
+ return arr;
+}
+
+static FILE *openFile(const QString &filename, const QString &mode)
+{
+#ifdef Q_OS_WINDOWS
+ return _wfopen(qUtf16Printable(filename), qUtf16Printable(mode));
+#else
+ return fopen(qPrintable(filename), qPrintable(mode));
+#endif
+}
+
+QCtfLibImpl *QCtfLibImpl::s_instance = nullptr;
+
+QCtfLib *QCtfLibImpl::instance()
+{
+ if (!s_instance)
+ s_instance = new QCtfLibImpl();
+ return s_instance;
+}
+
+void QCtfLibImpl::cleanup()
+{
+ delete s_instance;
+ s_instance = nullptr;
+}
+
+void QCtfLibImpl::handleSessionChange()
+{
+ m_sessionChanged = true;
+}
+
+void QCtfLibImpl::handleStatusChange(QCtfServer::ServerStatus status)
+{
+ switch (status) {
+ case QCtfServer::Error: {
+ m_serverClosed = true;
+ } break;
+ default:
+ break;
+ }
+}
+
+void QCtfLibImpl::buildMetadata()
+{
+ const QString mhn = QSysInfo::machineHostName();
+ QString metadata = QString::fromUtf8(traceMetadataTemplate, traceMetadataSize);
+ metadata.replace(QStringLiteral("$TRACE_UUID"), s_TraceUuid.toString(QUuid::WithoutBraces));
+ metadata.replace(QStringLiteral("$ARC_BIT_WIDTH"), QString::number(Q_PROCESSOR_WORDSIZE * 8));
+ metadata.replace(QStringLiteral("$SESSION_NAME"), m_session.name);
+ metadata.replace(QStringLiteral("$CREATION_TIME"), m_datetime.toString(Qt::ISODate));
+ metadata.replace(QStringLiteral("$HOST_NAME"), mhn);
+ metadata.replace(QStringLiteral("$CLOCK_FREQUENCY"), QStringLiteral("1000000000"));
+ metadata.replace(QStringLiteral("$CLOCK_NAME"), QStringLiteral("monotonic"));
+ metadata.replace(QStringLiteral("$CLOCK_TYPE"), QStringLiteral("Monotonic clock"));
+ metadata.replace(QStringLiteral("$CLOCK_OFFSET"), QString::number(m_datetime.toMSecsSinceEpoch() * 1000000));
+ metadata.replace(QStringLiteral("$ENDIANNESS"), QSysInfo::ByteOrder == QSysInfo::BigEndian ? u"be"_s : u"le"_s);
+ writeMetadata(metadata, true);
+}
+
+QCtfLibImpl::QCtfLibImpl()
+{
+ QString location = qEnvironmentVariable("QTRACE_LOCATION");
+ if (location.isEmpty()) {
+ qCInfo(lcDebugTrace) << "QTRACE_LOCATION not set";
+ return;
+ }
+
+ if (location.startsWith(u"tcp")) {
+ QUrl url(location);
+ m_server.reset(new QCtfServer());
+ m_server->setCallback(this);
+ m_server->setHost(url.host());
+ m_server->setPort(url.port());
+ m_server->startServer();
+ m_streaming = true;
+ m_session.tracepoints.append(allLiteral());
+ m_session.name = defaultLiteral();
+ } else {
+#if !QT_CONFIG(cxx17_filesystem)
+ qCWarning(lcDebugTrace) << "Unable to use filesystem";
+ return;
+#endif
+ // Check if the location is writable
+ if (QT_ACCESS(qPrintable(location), W_OK) != 0) {
+ qCWarning(lcDebugTrace) << "Unable to write to location";
+ return;
+ }
+ const QString filename = location + u"/session.json";
+ FILE *file = openFile(qPrintable(filename), "rb"_L1);
+ if (!file) {
+ qCWarning(lcDebugTrace) << "unable to open session file: "
+ << filename << ", " << qt_error_string();
+ m_location = location;
+ m_session.tracepoints.append(allLiteral());
+ m_session.name = defaultLiteral();
+ } else {
+ QT_STATBUF stat;
+ if (QT_FSTAT(QT_FILENO(file), &stat) != 0) {
+ qCWarning(lcDebugTrace) << "Unable to stat session file, " << qt_error_string();
+ return;
+ }
+ qsizetype filesize = qMin(stat.st_size, std::numeric_limits<qsizetype>::max());
+ QByteArray data(filesize, Qt::Uninitialized);
+ qsizetype size = static_cast<qsizetype>(fread(data.data(), 1, filesize, file));
+ fclose(file);
+ if (size != filesize)
+ return;
+ QJsonDocument json(QJsonDocument::fromJson(data));
+ QJsonObject obj = json.object();
+ bool valid = false;
+ if (!obj.isEmpty()) {
+ const auto it = obj.begin();
+ if (it.value().isArray()) {
+ m_session.name = it.key();
+ for (auto var : it.value().toArray())
+ m_session.tracepoints.append(var.toString());
+ valid = true;
+ }
+ }
+ if (!valid) {
+ qCWarning(lcDebugTrace) << "Session file is not valid";
+ m_session.tracepoints.append(allLiteral());
+ m_session.name = defaultLiteral();
+ }
+ m_location = location + u"/ust";
+#if QT_CONFIG(cxx17_filesystem)
+ std::filesystem::create_directory(qPrintable(m_location), qPrintable(location));
+#endif
+ }
+ clearLocation();
+ }
+
+ m_session.all = m_session.tracepoints.contains(allLiteral());
+ // Get datetime to when the timer was started to store the offset to epoch time for the traces
+ m_datetime = QDateTime::currentDateTime().toUTC();
+ m_timer.start();
+ if (!m_streaming)
+ buildMetadata();
+}
+
+void QCtfLibImpl::clearLocation()
+{
+#if QT_CONFIG(cxx17_filesystem)
+ const std::filesystem::path location{qUtf16Printable(m_location)};
+ for (auto const& dirEntry : std::filesystem::directory_iterator{location})
+ {
+ const auto path = dirEntry.path();
+#if __cplusplus > 201703L
+ if (dirEntry.is_regular_file()
+ && path.filename().wstring().starts_with(std::wstring_view(L"channel_"))
+ && !path.has_extension()) {
+#else
+ const auto strview = std::wstring_view(L"channel_");
+ const auto sub = path.filename().wstring().substr(0, strview.length());
+ if (dirEntry.is_regular_file() && sub.compare(strview) == 0
+ && !path.has_extension()) {
+#endif
+ if (!std::filesystem::remove(path)) {
+ qCInfo(lcDebugTrace) << "Unable to clear output location.";
+ break;
+ }
+ }
+ }
+#endif
+}
+
+void QCtfLibImpl::writeMetadata(const QString &metadata, bool overwrite)
+{
+ if (m_streaming) {
+ auto mt = metadata.toUtf8();
+ mt.resize(mt.size() - 1);
+ m_server->bufferData(QStringLiteral("metadata"), mt, overwrite);
+ } else {
+ FILE *file = nullptr;
+ file = openFile(qPrintable(m_location + "/metadata"_L1), overwrite ? "w+b"_L1: "ab"_L1);
+ if (!file)
+ return;
+
+ if (!overwrite)
+ fputs("\n", file);
+
+ // data contains zero at the end, hence size - 1.
+ const QByteArray data = metadata.toUtf8();
+ fwrite(data.data(), data.size() - 1, 1, file);
+ fclose(file);
+ }
+}
+
+void QCtfLibImpl::writeCtfPacket(QCtfLibImpl::Channel &ch)
+{
+ FILE *file = nullptr;
+ if (!m_streaming)
+ file = openFile(ch.channelName, "ab"_L1);
+ if (file || m_streaming) {
+ /* Each packet contains header and context, which are defined in the metadata.txt */
+ QByteArray packet;
+ packet << s_CtfHeaderMagic;
+ packet.append(QByteArrayView(s_TraceUuid.toBytes()));
+
+ packet << quint32(0);
+ packet << ch.minTimestamp;
+ packet << ch.maxTimestamp;
+ packet << quint64(ch.data.size() + packetHeaderSize + ch.threadNameLength) * 8u;
+ packet << quint64(packetSize) * 8u;
+ packet << ch.seqnumber++;
+ packet << quint64(0);
+ packet << ch.threadIndex;
+ if (ch.threadName.size())
+ packet.append(ch.threadName);
+ packet << (char)0;
+
+ Q_ASSERT(ch.data.size() + packetHeaderSize + ch.threadNameLength <= packetSize);
+ Q_ASSERT(packet.size() == qsizetype(packetHeaderSize + ch.threadNameLength));
+ if (m_streaming) {
+ ch.data.resize(packetSize - packet.size(), 0);
+ packet += ch.data;
+ m_server->bufferData(QString::fromLatin1(ch.channelName), packet, false);
+ } else {
+ fwrite(packet.data(), packet.size(), 1, file);
+ ch.data.resize(packetSize - packet.size(), 0);
+ fwrite(ch.data.data(), ch.data.size(), 1, file);
+ fclose(file);
+ }
+ }
+}
+
+QCtfLibImpl::Channel::~Channel()
+{
+ impl->writeCtfPacket(*this);
+ impl->removeChannel(this);
+}
+
+QCtfLibImpl::~QCtfLibImpl()
+{
+ if (!m_server.isNull())
+ m_server->stopServer();
+ qDeleteAll(m_eventPrivs);
+}
+
+void QCtfLibImpl::removeChannel(Channel *ch)
+{
+ const QMutexLocker lock(&m_mutex);
+ m_channels.removeOne(ch);
+}
+
+bool QCtfLibImpl::tracepointEnabled(const QCtfTracePointEvent &point)
+{
+ if (m_sessionChanged) {
+ const QMutexLocker lock(&m_mutex);
+ buildMetadata();
+ m_session.name = m_server->sessionName();
+ m_session.tracepoints = m_server->sessionTracepoints().split(';');
+ m_session.all = m_session.tracepoints.contains(allLiteral());
+ m_sessionChanged = false;
+ for (const auto &meta : m_additionalMetadata)
+ writeMetadata(meta->metadata);
+ for (auto *priv : m_eventPrivs)
+ writeMetadata(priv->metadata);
+ quint64 timestamp = m_timer.nsecsElapsed();
+ for (auto *ch : m_channels) {
+ writeCtfPacket(*ch);
+ ch->data.clear();
+ ch->minTimestamp = ch->maxTimestamp = timestamp;
+ }
+ }
+ if (m_streaming && (m_serverClosed || (!m_server->bufferOnIdle() && m_server->status() == QCtfServer::Idle)))
+ return false;
+ return m_session.all || m_session.tracepoints.contains(point.provider.provider);
+}
+
+static QString toMetadata(const QString &provider, const QString &name, const QString &metadata, quint32 eventId)
+{
+/*
+ generates event structure:
+event {
+ name = provider:tracepoint_name;
+ id = eventId;
+ stream_id = 0;
+ loglevel = 13;
+ fields := struct {
+ metadata
+ };
+};
+*/
+ return QStringView(u"event {\n name = \"") + provider + QLatin1Char(':') + name + u"\";\n"
+ + u" id = " + QString::number(eventId) + u";\n"
+ + u" stream_id = 0;\n loglevel = 13;\n fields := struct {\n "
+ + metadata + u"\n };\n};\n";
+}
+
+QCtfTracePointPrivate *QCtfLibImpl::initializeTracepoint(const QCtfTracePointEvent &point)
+{
+ QMutexLocker lock(&m_mutex);
+ QCtfTracePointPrivate *priv = point.d;
+ if (!point.d) {
+ if (const auto &it = m_eventPrivs.find(point.eventName); it != m_eventPrivs.end()) {
+ priv = *it;
+ } else {
+ priv = new QCtfTracePointPrivate();
+ m_eventPrivs.insert(point.eventName, priv);
+ priv->id = eventId();
+ priv->metadata = toMetadata(point.provider.provider, point.eventName, point.metadata, priv->id);
+ }
+ }
+ return priv;
+}
+
+void QCtfLibImpl::doTracepoint(const QCtfTracePointEvent &point, const QByteArray &arr)
+{
+ QCtfTracePointPrivate *priv = point.d;
+ quint64 timestamp = 0;
+ QThread *thread = nullptr;
+ if (m_streaming && m_serverClosed)
+ return;
+ {
+ QMutexLocker lock(&m_mutex);
+ if (!priv->metadataWritten) {
+ priv->metadataWritten = true;
+ auto providerMetadata = point.provider.metadata;
+ while (providerMetadata) {
+ registerMetadata(*providerMetadata);
+ providerMetadata = providerMetadata->next;
+ }
+ if (m_newAdditionalMetadata.size()) {
+ for (const QString &name : m_newAdditionalMetadata)
+ writeMetadata(m_additionalMetadata[name]->metadata);
+ m_newAdditionalMetadata.clear();
+ }
+ writeMetadata(priv->metadata);
+ }
+ timestamp = m_timer.nsecsElapsed();
+ }
+ if (arr.size() != point.size) {
+ if (arr.size() < point.size)
+ return;
+ if (arr.size() > point.size && !point.variableSize && !point.metadata.isEmpty())
+ return;
+ }
+
+ thread = QThread::currentThread();
+ if (thread == nullptr)
+ return;
+
+ Channel &ch = m_threadData.localData();
+
+ if (ch.channelName[0] == 0) {
+ ch.impl = this;
+ m_channels.append(&ch);
+ m_threadIndices.insert(thread, m_threadIndices.size());
+ sprintf(ch.channelName, "%s/channel_%d", qPrintable(m_location), m_threadIndices[thread]);
+ ch.minTimestamp = ch.maxTimestamp = timestamp;
+ ch.thread = thread;
+ ch.threadIndex = m_threadIndices[thread];
+ ch.threadName = thread->objectName().toUtf8();
+ if (ch.threadName.isEmpty()) {
+ const QMetaObject *obj = thread->metaObject();
+ ch.threadName = QByteArray(obj->className());
+ }
+ ch.threadNameLength = ch.threadName.size() + 1;
+ }
+ if (ch.locked)
+ return;
+ Q_ASSERT(ch.thread == thread);
+ ch.locked = true;
+
+ QByteArray event;
+ event << priv->id << timestamp;
+ if (!point.metadata.isEmpty())
+ event.append(arr);
+
+ if (ch.threadNameLength + ch.data.size() + event.size() + packetHeaderSize >= packetSize) {
+ writeCtfPacket(ch);
+ ch.data = event;
+ ch.minTimestamp = ch.maxTimestamp = timestamp;
+ } else {
+ ch.data.append(event);
+ }
+
+ ch.locked = false;
+ ch.maxTimestamp = timestamp;
+}
+
+bool QCtfLibImpl::sessionEnabled()
+{
+ return !m_session.name.isEmpty();
+}
+
+int QCtfLibImpl::eventId()
+{
+ return m_eventId++;
+}
+
+void QCtfLibImpl::registerMetadata(const QCtfTraceMetadata &metadata)
+{
+ if (m_additionalMetadata.contains(metadata.name))
+ return;
+
+ m_additionalMetadata.insert(metadata.name, &metadata);
+ m_newAdditionalMetadata.insert(metadata.name);
+}
+
+QT_END_NAMESPACE
diff --git a/src/plugins/tracing/qctflib_p.h b/src/plugins/tracing/qctflib_p.h
new file mode 100644
index 0000000000..297d38ee50
--- /dev/null
+++ b/src/plugins/tracing/qctflib_p.h
@@ -0,0 +1,125 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QT_CTFLIB_H
+#define QT_CTFLIB_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+//
+
+#include <private/qctf_p.h>
+#include "qctfplugin_p.h"
+#include <qstring.h>
+#include <qmutex.h>
+#include <qelapsedtimer.h>
+#include <qhash.h>
+#include <qset.h>
+#include <qthreadstorage.h>
+#include <qthread.h>
+#include <qloggingcategory.h>
+#include "qctfserver_p.h"
+
+QT_BEGIN_NAMESPACE
+
+Q_DECLARE_LOGGING_CATEGORY(lcDebugTrace)
+
+struct QCtfTracePointPrivate
+{
+ QString metadata;
+ quint32 id = 0;
+ quint32 payloadSize = 0;
+ bool metadataWritten = false;
+};
+
+class QCtfLibImpl : public QCtfLib, public QCtfServer::ServerCallback
+{
+ struct Session
+ {
+ QString name;
+ QStringList tracepoints;
+ bool all = false;
+ };
+ struct Channel
+ {
+ char channelName[512];
+ QByteArray data;
+ quint64 minTimestamp = 0;
+ quint64 maxTimestamp = 0;
+ quint64 seqnumber = 0;
+ QThread *thread = nullptr;
+ quint32 threadIndex = 0;
+ QByteArray threadName;
+ quint32 threadNameLength = 0;
+ bool locked = false;
+ QCtfLibImpl *impl = nullptr;
+ Channel()
+ {
+ memset(channelName, 0, sizeof(channelName));
+ }
+
+ ~Channel();
+ };
+
+public:
+ QCtfLibImpl();
+ ~QCtfLibImpl();
+
+ bool tracepointEnabled(const QCtfTracePointEvent &point) override;
+ void doTracepoint(const QCtfTracePointEvent &point, const QByteArray &arr) override;
+ bool sessionEnabled() override;
+ QCtfTracePointPrivate *initializeTracepoint(const QCtfTracePointEvent &point) override;
+ void registerMetadata(const QCtfTraceMetadata &metadata);
+ int eventId();
+ void shutdown(bool *) override
+ {
+
+ }
+
+ static QCtfLib *instance();
+ static void cleanup();
+private:
+ static QCtfLibImpl *s_instance;
+ QHash<QString, QCtfTracePointPrivate *> m_eventPrivs;
+ void removeChannel(Channel *ch);
+ void updateMetadata(const QCtfTracePointEvent &point);
+ void writeMetadata(const QString &metadata, bool overwrite = false);
+ void clearLocation();
+ void handleSessionChange() override;
+ void handleStatusChange(QCtfServer::ServerStatus status) override;
+ void writeCtfPacket(Channel &ch);
+ void buildMetadata();
+
+ static constexpr QUuid s_TraceUuid = QUuid(0x3e589c95, 0xed11, 0xc159, 0x42, 0x02, 0x6a, 0x9b, 0x02, 0x00, 0x12, 0xac);
+ static constexpr quint32 s_CtfHeaderMagic = 0xC1FC1FC1;
+
+ QMutex m_mutex;
+ QElapsedTimer m_timer;
+ QString m_metadata;
+ QString m_location;
+ Session m_session;
+ QHash<QThread*, quint32> m_threadIndices;
+ QThreadStorage<Channel> m_threadData;
+ QList<Channel *> m_channels;
+ QHash<QString, const QCtfTraceMetadata *> m_additionalMetadata;
+ QSet<QString> m_newAdditionalMetadata;
+ QDateTime m_datetime;
+ int m_eventId = 0;
+ bool m_streaming = false;
+ std::atomic_bool m_sessionChanged = false;
+ std::atomic_bool m_serverClosed = false;
+ QScopedPointer<QCtfServer> m_server;
+ friend struct Channel;
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/plugins/tracing/qctfplugin.cpp b/src/plugins/tracing/qctfplugin.cpp
new file mode 100644
index 0000000000..93e508e199
--- /dev/null
+++ b/src/plugins/tracing/qctfplugin.cpp
@@ -0,0 +1,63 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#define BUILD_LIBRARY
+#include <qstring.h>
+#include "qctfplugin_p.h"
+#include "qctflib_p.h"
+
+QT_BEGIN_NAMESPACE
+
+class QCtfTracePlugin : public QCtfLib
+{
+ Q_OBJECT
+ Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QCtfLib" FILE "trace.json")
+ Q_INTERFACES(QCtfLib)
+
+public:
+ QCtfTracePlugin()
+ {
+
+ }
+ ~QCtfTracePlugin()
+ {
+ m_cleanup = true;
+ *m_shutdown = true;
+ QCtfLibImpl::cleanup();
+ }
+ void shutdown(bool *shutdown) override
+ {
+ m_shutdown = shutdown;
+ }
+ bool tracepointEnabled(const QCtfTracePointEvent &point) override
+ {
+ if (m_cleanup)
+ return false;
+ return QCtfLibImpl::instance()->tracepointEnabled(point);
+ }
+ void doTracepoint(const QCtfTracePointEvent &point, const QByteArray &arr) override
+ {
+ if (m_cleanup)
+ return;
+ QCtfLibImpl::instance()->doTracepoint(point, arr);
+ }
+ bool sessionEnabled() override
+ {
+ if (m_cleanup)
+ return false;
+ return QCtfLibImpl::instance()->sessionEnabled();
+ }
+ QCtfTracePointPrivate *initializeTracepoint(const QCtfTracePointEvent &point) override
+ {
+ if (m_cleanup)
+ return nullptr;
+ return QCtfLibImpl::instance()->initializeTracepoint(point);
+ }
+private:
+ bool m_cleanup = false;
+ bool *m_shutdown = nullptr;
+};
+
+QT_END_NAMESPACE
+
+#include "qctfplugin.moc"
diff --git a/src/plugins/tracing/qctfplugin_p.h b/src/plugins/tracing/qctfplugin_p.h
new file mode 100644
index 0000000000..987c4d925f
--- /dev/null
+++ b/src/plugins/tracing/qctfplugin_p.h
@@ -0,0 +1,28 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef Q_CTFPLUGIN_P_H
+#define Q_CTFPLUGIN_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+//
+
+#include <private/qctf_p.h>
+#include <qplugin.h>
+
+QT_BEGIN_NAMESPACE
+
+Q_DECLARE_INTERFACE(QCtfLib, "org.qt-project.Qt.QCtfLib")
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/plugins/tracing/qctfserver.cpp b/src/plugins/tracing/qctfserver.cpp
new file mode 100644
index 0000000000..d97c345e11
--- /dev/null
+++ b/src/plugins/tracing/qctfserver.cpp
@@ -0,0 +1,404 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include <qloggingcategory.h>
+#include "qctfserver_p.h"
+
+#if QT_CONFIG(zstd)
+#include <zstd.h>
+#endif
+
+using namespace Qt::Literals::StringLiterals;
+
+Q_LOGGING_CATEGORY(lcCtfInfoTrace, "qt.core.ctfserver", QtWarningMsg)
+
+#if QT_CONFIG(zstd)
+static QByteArray zstdCompress(ZSTD_CCtx *&context, const QByteArray &data, int compression)
+{
+ if (context == nullptr)
+ context = ZSTD_createCCtx();
+ qsizetype size = data.size();
+ size = ZSTD_COMPRESSBOUND(size);
+ QByteArray compressed(size, Qt::Uninitialized);
+ char *dst = compressed.data();
+ size_t n = ZSTD_compressCCtx(context, dst, size,
+ data.constData(), data.size(),
+ compression);
+ if (ZSTD_isError(n)) {
+ qCWarning(lcCtfInfoTrace) << "Compression with zstd failed: " << QString::fromUtf8(ZSTD_getErrorName(n));
+ return {};
+ }
+ compressed.truncate(n);
+ return compressed;
+}
+#endif
+
+QCtfServer::QCtfServer(QObject *parent)
+ : QThread(parent)
+{
+ m_keySet << "cliendId"_L1
+ << "clientVersion"_L1
+ << "sessionName"_L1
+ << "sessionTracepoints"_L1
+ << "flags"_L1
+ << "bufferSize"_L1
+ << "compressionScheme"_L1;
+}
+
+QCtfServer::~QCtfServer()
+{
+#if QT_CONFIG(zstd)
+ ZSTD_freeCCtx(m_zstdCCtx);
+#endif
+}
+
+void QCtfServer::setHost(const QString &address)
+{
+ m_address = address;
+}
+
+void QCtfServer::setPort(int port)
+{
+ m_port = port;
+}
+
+void QCtfServer::setCallback(ServerCallback *cb)
+{
+ m_cb = cb;
+}
+
+QString QCtfServer::sessionName() const
+{
+ return m_req.sessionName;
+}
+
+QString QCtfServer::sessionTracepoints() const
+{
+ return m_req.sessionTracepoints;
+}
+
+bool QCtfServer::bufferOnIdle() const
+{
+ return m_bufferOnIdle;
+}
+
+QCtfServer::ServerStatus QCtfServer::status() const
+{
+ return m_status;
+}
+
+void QCtfServer::setStatusAndNotify(ServerStatus status)
+{
+ m_status = status;
+ m_cb->handleStatusChange(status);
+}
+
+void QCtfServer::bytesWritten(qint64 size)
+{
+ m_writtenSize += size;
+ if (m_writtenSize >= m_waitWriteSize && m_eventLoop)
+ m_eventLoop->exit();
+}
+
+void QCtfServer::initWrite()
+{
+ m_waitWriteSize = 0;
+ m_writtenSize = 0;
+}
+
+bool QCtfServer::waitSocket()
+{
+ if (m_eventLoop)
+ m_eventLoop->exec();
+ return m_socket->state() == QTcpSocket::ConnectedState;
+}
+
+void QCtfServer::handleString(QCborStreamReader &cbor)
+{
+ const auto readString = [](QCborStreamReader &cbor) -> QString {
+ QString result;
+ auto r = cbor.readString();
+ while (r.status == QCborStreamReader::Ok) {
+ result += r.data;
+ r = cbor.readString();
+ }
+
+ if (r.status == QCborStreamReader::Error) {
+ // handle error condition
+ result.clear();
+ }
+ return result;
+ };
+ do {
+ if (m_currentKey.isEmpty()) {
+ m_currentKey = readString(cbor);
+ } else {
+ switch (m_keySet.indexOf(m_currentKey)) {
+ case RequestSessionName:
+ m_req.sessionName = readString(cbor);
+ break;
+ case RequestSessionTracepoints:
+ m_req.sessionTracepoints = readString(cbor);
+ break;
+ case RequestCompressionScheme:
+ m_requestedCompressionScheme = readString(cbor);
+ break;
+ default:
+ // handle error
+ break;
+ }
+ m_currentKey.clear();
+ }
+ if (cbor.lastError() == QCborError::EndOfFile) {
+ if (!waitSocket())
+ return;
+ cbor.reparse();
+ }
+ } while (cbor.lastError() == QCborError::EndOfFile);
+}
+
+void QCtfServer::handleFixedWidth(QCborStreamReader &cbor)
+{
+ switch (m_keySet.indexOf(m_currentKey)) {
+ case RequestClientId:
+ if (!cbor.isUnsignedInteger())
+ return;
+ m_req.clientId = cbor.toUnsignedInteger();
+ break;
+ case RequestClientVersion:
+ if (!cbor.isUnsignedInteger())
+ return;
+ m_req.clientVersion = cbor.toUnsignedInteger();
+ break;
+ case RequestFlags:
+ if (!cbor.isUnsignedInteger())
+ return;
+ m_req.flags = cbor.toUnsignedInteger();
+ break;
+ case RequestBufferSize:
+ if (!cbor.isUnsignedInteger())
+ return;
+ m_req.bufferSize = cbor.toUnsignedInteger();
+ break;
+ default:
+ // handle error
+ break;
+ }
+ m_currentKey.clear();
+}
+
+void QCtfServer::readCbor(QCborStreamReader &cbor)
+{
+ switch (cbor.type()) {
+ case QCborStreamReader::UnsignedInteger:
+ case QCborStreamReader::NegativeInteger:
+ case QCborStreamReader::SimpleType:
+ case QCborStreamReader::Float16:
+ case QCborStreamReader::Float:
+ case QCborStreamReader::Double:
+ handleFixedWidth(cbor);
+ cbor.next();
+ break;
+ case QCborStreamReader::ByteArray:
+ case QCborStreamReader::String:
+ handleString(cbor);
+ break;
+ case QCborStreamReader::Array:
+ case QCborStreamReader::Map:
+ cbor.enterContainer();
+ while (cbor.lastError() == QCborError::NoError && cbor.hasNext())
+ readCbor(cbor);
+ if (cbor.lastError() == QCborError::NoError)
+ cbor.leaveContainer();
+ default:
+ break;
+ }
+}
+
+void QCtfServer::writePacket(TracePacket &packet, QCborStreamWriter &cbor)
+{
+ cbor.startMap(4);
+ cbor.append("magic"_L1);
+ cbor.append(packet.PacketMagicNumber);
+ cbor.append("name"_L1);
+ cbor.append(QString::fromUtf8(packet.stream_name));
+ cbor.append("flags"_L1);
+ cbor.append(packet.flags);
+
+ cbor.append("data"_L1);
+ if (m_compression > 0) {
+ QByteArray compressed;
+#if QT_CONFIG(zstd)
+ if (m_requestedCompressionScheme == QStringLiteral("zstd"))
+ compressed = zstdCompress(m_zstdCCtx, packet.stream_data, m_compression);
+ else
+#endif
+ compressed = qCompress(packet.stream_data, m_compression);
+
+ cbor.append(compressed);
+ } else {
+ cbor.append(packet.stream_data);
+ }
+
+ cbor.endMap();
+}
+
+bool QCtfServer::recognizedCompressionScheme() const
+{
+ if (m_requestedCompressionScheme.isEmpty())
+ return true;
+#if QT_CONFIG(zstd)
+ if (m_requestedCompressionScheme == QStringLiteral("zstd"))
+ return true;
+#endif
+ if (m_requestedCompressionScheme == QStringLiteral("zlib"))
+ return true;
+ return false;
+}
+
+void QCtfServer::run()
+{
+ m_server = new QTcpServer();
+ QHostAddress addr;
+ if (m_address.isEmpty())
+ addr = QHostAddress(QHostAddress::Any);
+ else
+ addr = QHostAddress(m_address);
+
+ qCInfo(lcCtfInfoTrace) << "Starting CTF server: " << m_address << ", port: " << m_port;
+
+ while (m_stopping == 0) {
+ if (!m_server->isListening()) {
+ if (!m_server->listen(addr, m_port)) {
+ qCInfo(lcCtfInfoTrace) << "Unable to start server";
+ m_stopping = 1;
+ setStatusAndNotify(Error);
+ }
+ }
+ setStatusAndNotify(Idle);
+ if (m_server->waitForNewConnection(-1)) {
+ qCInfo(lcCtfInfoTrace) << "client connection";
+ m_eventLoop = new QEventLoop();
+ m_socket = m_server->nextPendingConnection();
+
+ QObject::connect(m_socket, &QTcpSocket::readyRead, [&](){
+ if (m_eventLoop) m_eventLoop->exit();
+ });
+ QObject::connect(m_socket, &QTcpSocket::bytesWritten, this, &QCtfServer::bytesWritten);
+ QObject::connect(m_socket, &QTcpSocket::disconnected, [&](){
+ if (m_eventLoop) m_eventLoop->exit();
+ });
+
+ m_server->close(); // Do not wait for more connections
+ setStatusAndNotify(Connected);
+
+ if (waitSocket())
+ {
+ QCborStreamReader cbor(m_socket);
+
+ m_req = {};
+ while (cbor.hasNext() && cbor.lastError() == QCborError::NoError)
+ readCbor(cbor);
+
+ if (!m_req.isValid()) {
+ qCInfo(lcCtfInfoTrace) << "Invalid trace request.";
+ m_socket->close();
+ } else {
+ m_compression = m_req.flags & CompressionMask;
+#if QT_CONFIG(zstd)
+ m_compression = qMin(m_compression, ZSTD_maxCLevel());
+#else
+ m_compression = qMin(m_compression, 9);
+#endif
+ m_bufferOnIdle = !(m_req.flags & DontBufferOnIdle);
+
+ m_maxPackets = qMax(m_req.bufferSize / TracePacket::PacketSize, 16u);
+
+ if (!recognizedCompressionScheme()) {
+ qCWarning(lcCtfInfoTrace) << "Client requested unrecognized compression scheme: " << m_requestedCompressionScheme;
+ m_requestedCompressionScheme.clear();
+ m_compression = 0;
+ }
+
+ qCInfo(lcCtfInfoTrace) << "request received: " << m_req.sessionName << ", " << m_req.sessionTracepoints;
+
+ m_cb->handleSessionChange();
+ {
+ TraceResponse resp;
+ resp.serverId = ServerId;
+ resp.serverVersion = 1;
+ resp.serverName = QStringLiteral("Ctf Server");
+
+ QCborStreamWriter cbor(m_socket);
+ cbor.startMap(m_compression ? 4 : 3);
+ cbor.append("serverId"_L1);
+ cbor.append(resp.serverId);
+ cbor.append("serverVersion"_L1);
+ cbor.append(resp.serverVersion);
+ cbor.append("serverName"_L1);
+ cbor.append(resp.serverName);
+ if (m_compression) {
+ cbor.append("compressionScheme"_L1);
+ cbor.append(m_requestedCompressionScheme);
+ }
+ cbor.endMap();
+ }
+
+ qCInfo(lcCtfInfoTrace) << "response sent, sending data";
+ if (waitSocket()) {
+ while (m_socket->state() == QTcpSocket::ConnectedState) {
+ QList<TracePacket> packets;
+ {
+ QMutexLocker lock(&m_mutex);
+ while (m_packets.size() == 0)
+ m_bufferHasData.wait(&m_mutex);
+ packets = std::exchange(m_packets, {});
+ }
+
+ {
+ QCborStreamWriter cbor(m_socket);
+ for (TracePacket &packet : packets) {
+ writePacket(packet, cbor);
+ if (!waitSocket())
+ break;
+ }
+ }
+ qCInfo(lcCtfInfoTrace) << packets.size() << " packets written";
+ }
+ }
+
+ qCInfo(lcCtfInfoTrace) << "client connection closed";
+ }
+ }
+ delete m_eventLoop;
+ m_eventLoop = nullptr;
+ } else {
+ qCInfo(lcCtfInfoTrace) << "error: " << m_server->errorString();
+ m_stopping = 1;
+ setStatusAndNotify(Error);
+ }
+ }
+}
+
+void QCtfServer::startServer()
+{
+ start();
+}
+void QCtfServer::stopServer()
+{
+ this->m_stopping = 1;
+ wait();
+}
+
+void QCtfServer::bufferData(const QString &stream, const QByteArray &data, quint32 flags)
+{
+ QMutexLocker lock(&m_mutex);
+ TracePacket packet;
+ packet.stream_name = stream.toUtf8();
+ packet.stream_data = data;
+ packet.flags = flags;
+ m_packets.append(packet);
+ if (m_packets.size() > m_maxPackets)
+ m_packets.pop_front();
+ m_bufferHasData.wakeOne();
+}
diff --git a/src/plugins/tracing/qctfserver_p.h b/src/plugins/tracing/qctfserver_p.h
new file mode 100644
index 0000000000..3660df7f09
--- /dev/null
+++ b/src/plugins/tracing/qctfserver_p.h
@@ -0,0 +1,194 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QT_CTFSERVER_H
+#define QT_CTFSERVER_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+//
+
+#include <qbytearray.h>
+#include <qdatastream.h>
+#include <qthread.h>
+#include <qmutex.h>
+#include <qwaitcondition.h>
+#include <qeventloop.h>
+#include <QtNetwork/qtcpserver.h>
+#include <QtNetwork/qtcpsocket.h>
+#include <qcborstreamreader.h>
+#include <qcborstreamwriter.h>
+#include <qlist.h>
+
+typedef struct ZSTD_CCtx_s ZSTD_CCtx;
+
+QT_BEGIN_NAMESPACE
+
+class QCtfServer;
+struct TracePacket
+{
+ static constexpr quint32 PacketMagicNumber = 0x100924da;
+ static constexpr quint32 PacketSize = 4096 + 9;
+ QByteArray stream_name;
+ QByteArray stream_data;
+ quint32 flags = 0;
+
+ TracePacket() = default;
+
+ TracePacket(const TracePacket &t)
+ {
+ stream_name = t.stream_name;
+ stream_data = t.stream_data;
+ flags = t.flags;
+ }
+ TracePacket &operator = (const TracePacket &t)
+ {
+ stream_name = t.stream_name;
+ stream_data = t.stream_data;
+ flags = t.flags;
+ return *this;
+ }
+ TracePacket(TracePacket &&t)
+ {
+ stream_name = std::move(t.stream_name);
+ stream_data = std::move(t.stream_data);
+ flags = t.flags;
+ }
+ TracePacket &operator = (TracePacket &&t)
+ {
+ stream_name = std::move(t.stream_name);
+ stream_data = std::move(t.stream_data);
+ flags = t.flags;
+ return *this;
+ }
+};
+
+auto constexpr operator""_MB(quint64 s) -> quint64
+{
+ return s * 1024ul * 1024ul;
+}
+
+struct TraceRequest
+{
+ quint32 clientId;
+ quint32 clientVersion;
+ quint32 flags;
+ quint32 bufferSize;
+ QString sessionName;
+ QString sessionTracepoints;
+
+ static constexpr quint32 MaxBufferSize = 1024_MB;
+
+ bool isValid() const
+ {
+ if (clientId != 0 && clientVersion != 0 && !sessionName.isEmpty()
+ && !sessionTracepoints.isEmpty() && bufferSize < MaxBufferSize)
+ return true;
+ return false;
+ }
+};
+
+struct TraceResponse
+{
+ quint32 serverId;
+ quint32 serverVersion;
+ QString serverName;
+};
+
+class QCtfServer : public QThread
+{
+ Q_OBJECT
+public:
+ enum ServerStatus
+ {
+ Uninitialized,
+ Idle,
+ Connected,
+ Error,
+ };
+ enum ServerFlags
+ {
+ CompressionMask = 255,
+ DontBufferOnIdle = 256, // not set -> the server is buffering even without client connection
+ // set -> the server is buffering only when client is connected
+ };
+ enum RequestIds
+ {
+ RequestClientId = 0,
+ RequestClientVersion,
+ RequestSessionName,
+ RequestSessionTracepoints,
+ RequestFlags,
+ RequestBufferSize,
+ RequestCompressionScheme,
+ };
+
+ struct ServerCallback
+ {
+ virtual void handleSessionChange() = 0;
+ virtual void handleStatusChange(ServerStatus status) = 0;
+ };
+ QCtfServer(QObject *parent = nullptr);
+ ~QCtfServer();
+ void setCallback(ServerCallback *cb);
+ void setHost(const QString &address);
+ void setPort(int port);
+ void run() override;
+ void startServer();
+ void stopServer();
+ void bufferData(const QString &stream, const QByteArray &data, quint32 flags);
+ QString sessionName() const;
+ QString sessionTracepoints() const;
+ bool bufferOnIdle() const;
+ ServerStatus status() const;
+private:
+
+ void initWrite();
+ void bytesWritten(qint64 size);
+ bool waitSocket();
+ void readCbor(QCborStreamReader &cbor);
+ void handleString(QCborStreamReader &cbor);
+ void handleFixedWidth(QCborStreamReader &cbor);
+ bool recognizedCompressionScheme() const;
+ void setStatusAndNotify(ServerStatus status);
+ void writePacket(TracePacket &packet, QCborStreamWriter &cbor);
+
+ QMutex m_mutex;
+ QWaitCondition m_bufferHasData;
+ QList<TracePacket> m_packets;
+ QString m_address;
+ QTcpServer *m_server = nullptr;
+ QTcpSocket *m_socket = nullptr;
+ QEventLoop *m_eventLoop = nullptr;
+ QList<QString> m_keySet;
+ TraceRequest m_req;
+ ServerCallback *m_cb = nullptr;
+ ServerStatus m_status = Uninitialized;
+ qint64 m_waitWriteSize = 0;
+ qint64 m_writtenSize = 0;
+ int m_port;
+ int m_compression = 0;
+ int m_maxPackets = DefaultMaxPackets;
+ QAtomicInt m_stopping;
+ bool m_bufferOnIdle = true;
+ QString m_currentKey;
+ QString m_requestedCompressionScheme;
+#if QT_CONFIG(zstd)
+ ZSTD_CCtx *m_zstdCCtx = nullptr;
+#endif
+
+ static constexpr quint32 ServerId = 1;
+ static constexpr quint32 DefaultMaxPackets = 256; // 1 MB
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/plugins/tracing/trace.json b/src/plugins/tracing/trace.json
new file mode 100644
index 0000000000..1b991122d4
--- /dev/null
+++ b/src/plugins/tracing/trace.json
@@ -0,0 +1,3 @@
+{
+ "Keys": [ "CTF" ]
+}